(Another) Ward Picture Generator

5 views
Skip to first unread message

Dave Smith

unread,
Feb 9, 2009, 12:40:16 AM2/9/09
to ur...@googlegroups.com
Hi there. First time writer, long time reader.

I have been hacking on Dan's ward picture generator[1] for about a week
to add parent names, children names, addresses, and phone numbers. I
also wanted a slightly different column-based format. For whatever it's
worth, I've attached my updated version. Please be gentle -- it's my
first attempt at Ruby.

By the way, this one requires you to put your username/password in a
file called wardlogin.conf in the same directory as the script (to make
it easier to distribute without sanitizing it each time).

--Dave

[1]
http://groups.google.com/group/urug/browse_thread/thread/612c23ee1fde87f4

ward_picture_directory_generator.rb

Dave Smith

unread,
Feb 9, 2009, 12:48:43 AM2/9/09
to ur...@googlegroups.com
Dave Smith wrote:
> I have been hacking on Dan's ward picture generator[1] for about a week

Woops, a typo sneaked into that last version. Please disregard in favor
of this one instead.

--Dave

ward_picture_directory_generator.rb

Lee Jensen

unread,
Feb 9, 2009, 1:36:56 PM2/9/09
to ur...@googlegroups.com
Even easier way to distribute code:

http://github.com

:)
> #!/usr/bin/ruby
> # LDS.org ward picture directory generator
> #
> # Dan Amelang (d...@amelang.net) - January 2007
> # Released under the Ruby license
> #
> # Dave Smith (da...@thesmithfam.org) - February 2008
> # Added parents, children, addresses, and phone numbers
> #
> # TODO:
> # Don't print "The ... Family" if there is only one person in the family
> # Add list of families w/out photo at the end
> # Package as an executable (report Ruby2exe bug)
> # documentation
> # GUI file chooser
> # GUI username and password entry
> # GUI progress/status widget
>
> puts "Loading libraries (so dang slow)..."
>
> trap("INT") {
> puts "Exiting"
> exit
> }
>
> require 'rubygems'
> require 'mechanize'
> require 'ftools'
> require 'pdf/writer'
>
> username = ''
> password = ''
>
> login_file = File.dirname($0) + "/wardlogin.conf"
> if File.exists?(login_file)
> puts "Reading username/password from '" + login_file + "'"
> File.open(login_file).each { |line|
> line.strip!
> if line == ''
> abort "Woops. No empty lines aloud in " + login_file
> elsif username == ''
> username = line
> elsif password == ''
> password = line
> else
> abort "Woops. wardlogin.conf should only have a user line and a password line, nothing else."
> end
> }
> else
> abort "Please create wardlogin.conf with your LDS username and password on separate lines in this directory: " + File.dirname($0)
> end
>
> # Start up our "web browser"
> agent = WWW::Mechanize.new
>
> # We'll pretend to be IE, just be on the safe side
> agent.user_agent_alias = 'Windows IE 6'
>
> # Start at the main LDS.org page
> begin
> puts "Browsing to lds.org..."
> page = agent.get('http://lds.org/')
> rescue Exception
> abort "Unable to connect to lds.org! Check your internet connection."
> end
>
> # Go to the login page
> puts "Browsing to the ward login page..."
> page = page.links.find {|l| l.text =~ /ward.*site/i}.click
>
> # Find the login form
> form = page.forms.find {|f| f.name =~ /log/i}
>
> # Enter the username
> form.fields.find {|f| f.name =~ /user/i}.value = username
>
> # Enter the password
> form.fields.find {|f| f.name =~ /pass/i}.value = password
>
> # Try to login
> puts "Logging in as '" + username + "'..."
> page = form.submit
>
> # Check for a failed login
> if /login/i.match(page.title)
> abort "Unable to login! Incorrect username and/or password?"
> end
>
> # We're in!
>
> # Go to the membership directory page
> puts "Navigating to the membership directory..."
> page = page.links.find {|l| l.text =~ /member.*directory/i}.click
>
> # Find the link to the version of the directory with the photos
> link = page.links.find {|l| l.text =~ /photo/i}
>
> # The actual URL for the photo directory is embedded in javascript
> # in the HREF field. So we have to dig it out ourselves.
> # TODO Can we pull the base of the URI from the agent (or page)
> # instead of hard coding it?
> photo_directory_url =
> 'https://secure.lds.org' + /(\/.*\.html)/i.match(link.uri.to_s)[1]
> puts "Downloading the ward photo directory (this can take a few minutes)"
> puts " " + photo_directory_url
> page = agent.get(photo_directory_url)
>
> # Yes, this page actually has _two_ HTML title elements!
> ward_name = page.search('/html/head/title')[0].all_text.strip
> ward_name.slice!(' Membership Directory')
>
> puts "Ward name: '" + ward_name + "'"
>
> Family = Struct.new(:last_name, :parents, :children, :phone, :address, :picture)
> families = []
>
> CHILD_REGEX = /^      /
> PARENT_REGEX = /^   /
> FAMILY_QUERY = '/html/body/table/tr/td[@class=\'eventsource\']/table/tr'
>
> puts 'Searching for families...'
> results = page.search(FAMILY_QUERY)
> picture_counter = 0
> picture_file_names = []
> results.each do |family|
> name_search = family.search('tr/td/table/tr/td')
> if name_search != nil and name_search.size > 0
> last_name = name_search[0].all_text.strip
> puts "Family: " + last_name
> parents = []
> children = []
> for i in 1...name_search.size
> first_name = name_search[i].all_text.strip
> if first_name =~ CHILD_REGEX
> first_name.slice!(CHILD_REGEX)
> children << first_name
> elsif first_name =~ PARENT_REGEX
> first_name.slice!(PARENT_REGEX)
> parents << first_name
> end
> end
>
> puts " Parents: " + parents.join(', ')
> puts " Children: " + children.join(', ')
>
> img_search = family.search('img')
> picture = ''
> if img = img_search.at('img')
> picture_url = 'https://secure.lds.org' + img['src']
> puts " Picture: " + picture_url
> picture_data = agent.get_file(picture_url)
> picture = "/tmp/ward-photo-" + picture_counter.to_s + ".jpg"
> puts " Saving picture to '" + picture + "'"
> File.open(picture, 'w') {|f| f.write(picture_data) }
> picture_counter = picture_counter + 1
> picture_file_names << picture
> else
> puts " Picture: (none)"
> end
>
> phone_search = family.search('tr/td/table/tr/td')
> phone_text = ''
> if phone_search.size >= 2 and phone_text = phone_search[1].all_text.strip
> puts " Phone: " + phone_text
> else
> puts " Phone: (none)"
> end
>
> address_search = family.search('tr/td')
> address_text = ''
> if address_search.size >= 2 and address_text = address_search[1].all_text.strip.split("\n")[0]
> puts " Address: " + address_text
> else
> address_text = ''
> puts " Address: (none)"
> end
>
> families << Family.new(last_name, parents, children, phone_text, address_text, picture)
>
> end # if we found the family's last name
>
> end # for each family
>
> puts "Generating the PDF..."
>
> class Array
> # Handy for dividing an array into fixed-size pieces (later versions of Ruby
> # have a built-in way to do this)
> def / len
> inject([]) do |ary, x|
> ary << [] if [*ary.last].nitems % len == 0
> ary.last << x
> ary
> end
> end
> end
>
> pdf = PDF::Writer.new(:orientation => :portrait)
> pdf.select_font "Times-Roman"
>
> # Insert the ward name and timestamp at the top
> pdf.text ward_name, :font_size => 30, :justification => :center
> pdf.text "\n", :font_size => 15
> pdf.text Time.now.strftime("%B %d, %Y"), :font_size => 15,
> :justification => :center
>
> pdf.font_size = 10
> pdf.start_columns 2
>
> families.each do |family|
>
> puts "Adding the '" + family.last_name + "' family to the PDF..."
>
> info_text = family.last_name
> info_text << "\n"
> info_text << family.parents.join(', ')
>
> if family.children.length > 0
> info_text << "\n"
> info_text << family.children.join(', ')
> end
>
> puts " Address: '" + family.address + "'"
> if family.address != ''
> info_text << "\n"
> info_text << family.address
> end
>
> puts " Phone: '" + family.phone + "'"
> if family.phone != ''
> info_text << "\n"
> info_text << family.phone
> end
>
> pdf.text info_text
>
> if family.picture != ''
> pdf.image(family.picture, :resize => 0.7, :pad => 1)
> end
>
> pdf.text "\n"
> end
>
> puts "Cleaning up temporary files..."
> picture_file_names.each do |file|
> File.delete(file)
> end
>
> pdf.save_as('Ward_Picture_Directory.pdf')
> puts 'Saved generated picture directory in file named "Ward_Picture_Directory.pdf"'
>
>

Jake Cahoon

unread,
Feb 9, 2009, 5:04:45 PM2/9/09
to ur...@googlegroups.com
I'll take a look at this.  I wrote similar code with Prawn instead of PDF::Writer.  I separated the process into two scripts.  The first downloads all the pictures and generates a CSV file.  It allows edits to be made to the member info like adding email addresses.  The second script creates the PDF from the CSV and images.

My script is focused on singles wards but it wouldn't be hard to adapt.  I'll see what code I can merge in.

I also have one that does the same thing for the stake level.  I know several single stakes at BYU use it.

Jake Cahoon
--
Jake Cahoon
M: 801-369-9813
Skype: jake.cahoon
Reply all
Reply to author
Forward
0 new messages