#!/usr/bin/ruby # No license. No copyright. Use as you will. # An email to ccoupe@cableone.net or comment on werehosed.mvmanila.com would # be welcome. Questions will be answered. # No whining about the indentation, case or lack of, OK? It's a demo, a one off require 'net/http' require 'uri' require 'pp' # # get a list of pictures,albums, and details from a Gallery 2 site # # # Must use multipart form data. Some stuff in in the url, some in http header # and some in the body of the HTTP POST # Borrowed from the python library # http://slowhome.org/projects/gup # Additional hints and code from # http://snipplr.com/view/2476/ruby-rest/ # The cookie handling code is primitive, at best # The documentation at http://codex.gallery2.org/Gallery_Remote:Protocol # is less a bit opaque # Comment or uncomment those puts and pp statements as you see fit. class Gallery def initialize(site,user,pass) @siteurl = 'http://'+site+'/main.php' @user = user @password = pass @boundary = 'AaB03x' @last_authtoken = nil @need_login = true # T ==> we need to login, @cookie = nil end def encode_multipartformdata(parameters = {}) ret = String.new parameters.each do |key, value| unless value == nil || value.empty? ret << "\r\n--" << @boundary << "\r\n" ret << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" ret << value end end ret << "\r\n--" << @boundary << "--\r\n" end # Not really a parser - It's a hack. # these are confusing because there's muplitples cookies from Gallery # The first is expired. The Second and Third are duplicates # and the date has a comma but we need comma to separate the cookies # All I claim is that it worked for me, once def parsecookie(str) rawcks = str.split(',') cookies = [] i = 0 f1 = '' while i < rawcks.length f1 = rawcks[i] cookies << "#{f1},#{rawcks[i+1]}".strip i = i + 2 end return cookies[-1] end def request(command,version,args) uri = URI.parse(@siteurl) req = Net::HTTP::Post.new(uri.path) #req = Net::HTTP::Get.new(uri.path) if @cookie != nil req.add_field('Cookie',@cookie) end req.add_field('Content-type', "multipart/form-data; boundary=#{@boundary}") req.add_field('Accept', 'text/plain') fargs = {'g2_controller' => 'remote:GalleryRemote', 'g2_form[cmd]' => command, 'g2_form[protocol_version]'=> version.to_str, 'g2_authToken' => @last_authtoken} fargs.merge!(args) req.body = encode_multipartformdata(fargs) http = Net::HTTP.new(uri.host, uri.port) # The post is built. Send it resp = http.start() { |conn| conn.request(req) } resp.value() # raise exception if resp.code != 2xx if ck = resp.response['set-cookie'] @cookie = parsecookie(ck) $stderr.puts "New Cookie #{@cookie}" end ret = [] # array of arrays g2status = 0 resp.body.each do | ln | next if ln =~ /^\s*#/ flds = ln.split('=') #puts "#{flds[0]} == #{flds[1]}" if flds[0]=='auth_token' @last_authtoken = flds[1] #puts @last_authtoken elsif flds[0] == 'status' g2status = flds[1].to_i else ret << [flds[0],flds[1]] end end return g2status,ret end # returns true if logged in and have a token def login arguments = {'g2_form[uname]'=> @user, 'g2_form[password]' => @password} st, resp = request('login','2.0', arguments) puts "Logged in" if st==0 # should raise an exception if not return st==0 end #Request a list of albums def fetch_albums_prune() status, data = self.request('fetch-albums-prune', '2.2', {'g2_form[no_perms]' => 'yes'}) if !status # Really should throw exception puts "Failed fetch_albums_prune Command: #{status}" end lastref = 1 nm = 0 title = '' parent = 0 data.each do |pair| # we only care about some of these pairs if pair[0].index('album.') == nil next end if pair[0].index('album.perms.') next end # we care about name parent,title, refnum # execpt for title, they are converted to ints # we use the refnum to see when the album is changed flds = pair[0].split('.') refnum = flds[-1].to_i if refnum != lastref # it's changed - finish up addAlbum(lastref,nm,parent,title) lastref = refnum end if flds[1] == 'name' nm = pair[1].to_i elsif flds[1] == 'title' title = pair[1] elsif flds[1] == 'parent' parent = pair[1].to_i else next end #puts "#{pair[0]} ==> #{pair[1]}" end # there's one left over addAlbum(lastref,nm,parent,title) # now build the tree buildTree() end def fetch_album_images(itemid) status, data = self.request('fetch-album-images', '2.4', {'g2_form[set_albumName]' => itemid, 'g2_form[albums_too]' => 'no', 'g2_form[extrafields]' => 'yes' }) if !status # Really should throw exception puts "Failed fetch_album_images Command: #{status}" end lastref = 1 bukkit = {} # Think LOL Cats data.each do |pair| #puts "#{pair[0]} = #{pair[1]}" # we only care about some of these pairs if pair[0].index('image.') == nil next end flds = pair[0].split('.') refnum = flds[-1].to_i if refnum != lastref # it's changed - finish up # convert the bukkit to an object or a DB lookup or or or #pp bukkit p bukkit['title'] bukkit = {} lastref = refnum end # grab the middle flds to make a key k = flds[1..-2].to_s bukkit[k] = pair[1].strip #puts "#{pair[0]} ==> #{pair[1]}" end # there's one left over #pp bukkit p bukkit['title'] end end # Class class Album attr_accessor :id,:parent,:title def initialize(id,parent,title) @id = id.to_s @parent = parent.to_s @title = title end end # ------ loose functions not in a class. ----------- $albums = {} def addAlbum(ref,id,parent,title) a = Album.new(id,parent,title) $albums[a.id] = a #puts "#{ref},#{id},#{parent},#{title}" end $tree = {} def buildTree() $albums.each_pair do |id, alb| if $tree[alb.parent] == nil $tree[alb.parent] = [id] else $tree[alb.parent] << id end end #pp $tree printTree($tree['0'],1) end def printTree(ary,level) if ary == nil # just to be safe return end lead = ' '*level ary.each do |n| puts "#{lead}#{$albums[n].title}" if $tree[n] printTree($tree[n],level+1) end end end def crawlTree(ary) ary.each do |n| # go get the image info (zero images is possible if $tree[n] crawlTree(n) end end end # open a connection to the server g2 = Gallery.new('www.mvmanila.com/gallery','username','password') g2.login #g2.fetch_albums_prune() g2.fetch_album_images('152') # only a few images in here