Skip to content

Instantly share code, notes, and snippets.

@rolandoam
Created June 9, 2009 16:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rolandoam/126628 to your computer and use it in GitHub Desktop.
Save rolandoam/126628 to your computer and use it in GitHub Desktop.
cocos2d email gateway
#!/usr/bin/env ruby
# Copyright (c) 2009, Rolando Abarca M.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
require 'xmlrpc/client'
require 'net/smtp'
require 'net/imap'
require 'rubygems'
begin
recs = %w(sqlite3 sequel tmail tlsmail main).each { |f| require f }
rescue LoadError
puts "You need to install one of (or all) #{recs.join(', ')} gems in order to use this script"
puts " sudo gem install #{recs.map {|r| r == 'sqlite3' ? 'sqlite3-ruby' : r }.join(' ')}"
exit(-1)
end
class Gateway
SUBJECT_EXTRA = "cocos2d-iphone-discuss"
def initialize(options)
raise "Need email!" if options[:email].nil?
@url = options[:url]
@dbfile = options[:dbfile] || "gateway.db"
@domain = options[:email].split('@').last
@db = File.exists?(@dbfile) ? Sequel.connect("sqlite://#{@dbfile}") : init_schema(@dbfile, options[:email])
end
# create the initial schema
def init_schema(dbfile, email)
db = Sequel.connect("sqlite://#{dbfile}")
db << <<-EOS
CREATE TABLE settings (
last_check time, -- when was the last time we checked for new topics
email varchar(255), -- email of the gateway
smtp_server varchar(255), -- smtp server
smtp_user varchar(255), -- smtp user
smtp_pass varchar(255), -- smtp pass
smtp_tls int default 1 -- smtp uses ssl?
);
-- every user must supply the credentials to login
CREATE TABLE recipients (
id integer primary key,
name varchar(255), -- name
email varchar(255), -- email (to send emails to)
user varchar(255), -- forum username
pass varchar(255), -- forum password
forums varchar(20) -- what forums is the user subscribed to (comma separated)
);
CREATE TABLE forums (
id integer primary key,
name varchar(255),
forum_id integer
);
EOS
# insert initial default
db[:settings] << {:email => email, :last_check => Time.now}
get_forums.each do |forum|
db[:forums] << {:name => forum['forum_slug'], :forum_id => forum['forum_id']}
end
db
end
def email
@email ||= @db[:settings].first[:email]
end
def get_forums
server = XMLRPC::Client.new2(@url)
server.call('bb.getForums', 'anonymous', 'user')
end
def update_current_check
if block_given?
last_check = @db[:settings].first[:last_check]
yield(last_check)
end
@db[:settings].update(:last_check => Time.now)
end
def new_posts(forum_id)
server = XMLRPC::Client.new2(@url)
posts = []
begin
topics = server.call("bb.getTopics", 'anonymous', 'user', forum_id)
rescue Exception
# some times, bb.getTopics returns an error... who knows why is that...
return posts
end
update_current_check do |last_check|
topics.select { |t| Time.parse(t['topic_time'] + " UTC") > last_check }.each { |topic|
server.call("bb.getPosts", 'anonymous', 'user', topic['topic_id']).select { |t| Time.parse(t['post_time'] + " UTC") > last_check }.each { |post|
posts << {
:post_id => post['post_id'],
:posted_by => post['poster_display_name'].downcase.tr(' ','-'),
:topic_id => topic['topic_id'],
:subject => topic['topic_title'],
:uri => post['post_uri'],
:text => post['post_text']
}
}
}
end
posts
end
def get_topic(topic_id)
server = XMLRPC::Client.new2(@url)
server.call("bb.getTopic", 'anonymous', 'user', topic_id)
end
def get_post(post_id)
server = XMLRPC::Client.new2(@url)
server.call("bb.getPost", 'anonymous', 'user', post_id)
end
def add_user(email)
user = pass = nil
# check if the email has auth in it (user:pass:email@domain.com)
if email.include?(':')
arr = email.split(':')
email = arr[2]
user = arr[0]
pass = arr[1]
end
# check if the email is already added
if @db[:recipients].filter(:email => email).count == 0
@db[:recipients] << {:email => email, :user => user, :pass => pass}
end
end
def auth_for_email(email)
recp = @db[:recipients].filter(:email => email).first
[recp[:user], recp[:pass]] rescue [nil, nil]
end
def set_smtp_credentials(server, user, password, use_tls = true)
@db[:settings].update(:smtp_server => server,
:smtp_user => user,
:smtp_pass => password,
:smtp_tls => use_tls ? 1 : 0)
end
# just a simple check
def html_clean(txt)
txt.gsub("'", '&#39;').gsub('"', '&#34;')
end
def new_topic_from_email(email, forum_id, topic_id = nil)
server = XMLRPC::Client.new2(@url)
#puts "creating new topic from email (forum: #{forum_id}, topic: #{topic_id})"
body = html_clean(email.body)
user, pass = auth_for_email(email.from.first)
if user && pass
if topic_id.nil?
server.call("bb.newTopic", user, pass, {
:text => body,
:title => email.subject,
:forum_id => forum_id
})
else
server.call("bb.newPost", user, pass, {
:text => body,
:topic_id => topic_id
})
end
end
end
def check_and_process(forum_id)
with_lock do
secure_check_and_process(forum_id)
end
end
def with_lock
tmp_name = "/tmp/#{File.basename($0)}.tmp.lock"
return if File.exists?(tmp_name)
begin
File.open(tmp_name, "w+") do |f|
yield if block_given?
end
ensure
system("rm -f #{tmp_name}")
end
end
def secure_check_and_process(forum_id)
recps = @db[:recipients].to_a
settings = @db[:settings].first
# start smtp connection
if settings[:smtp_tls] == 1
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
end
Net::SMTP.start(settings[:smtp_server], 25, @domain, settings[:smtp_user], settings[:smtp_pass], :login) do |smtp|
new_posts(forum_id).each { |post|
# set the topic id on the subject, for easy parsing
subject = "[#{SUBJECT_EXTRA}##{post[:topic_id]}] #{post[:subject]}"
mail = TMail::Mail.new
mail.subject = subject
mail.content_type = "text/html"
body_txt = <<-EOS
#{post[:posted_by]} says:
#{post[:text]}
Read the <a href="#{post[:uri]}">whole thread</a>.
EOS
mail.body = body_txt
# gmail does not allow to do this
#mail.from = post[:posted_by] + "-user@cocos2d-iphone.com"
#mail.reply_to = email
mail.add_message_id(@domain)
# send the same email to each recipient
recps.each do |recp|
mail.to = [recp[:email]]
smtp.send_message mail.to_s, settings[:email], [recp[:email]]
end
}
end
# now check the email
imap = Net::IMAP.new("imap.gmail.com", 993, true)
imap.login(settings[:smtp_user], settings[:smtp_pass])
imap.select('INBOX')
valid_emails = @db[:recipients].select(:email).map { |r| r[:email] }
imap.search(['NOT', 'SEEN']).each do |msg_id|
email = TMail::Mail.parse(imap.fetch(msg_id, 'RFC822')[0].attr['RFC822'])
if email && valid_emails.include?(email.from.first)
reply_post = nil
if md = email.subject.match(/\[#{SUBJECT_EXTRA}#(\d+)\]/)
reply_post = md[1].to_i
end
new_topic_from_email(email, forum_id, reply_post)
end
# delete
imap.store(msg_id, "+FLAGS", [:Deleted]) unless ENV['DEBUG']
end
imap.expunge
imap.close
end
end
Main {
option('add-user') {
argument_required
cast :string
description 'add a user to the email list'
}
option('email') {
argument_required
cast :string
description 'the email of the gateway'
default 'cocos2d-iphone-discuss@gamesforfood.cl'
}
option('forum', 'f') {
argument_required
cast :string
description 'the forum xmlrpc url'
default 'http://www.cocos2d-iphone.com/forum/xmlrpc.php'
}
option('forum-id', 'F') {
argument_required
cast :int
description 'the forum-id to check'
default 2
}
option('db-file', 'd') {
argument_required
cast :string
description 'the database (sqlite3) file'
default 'gateway.db'
}
option('smtp-server') {
argument_required
description 'the smtp server'
}
option('smtp-user') {
argument_required
description 'the smtp user'
}
option('smtp-password') {
argument_required
description 'the smtp password'
}
def run
gw = Gateway.new(:url => params['forum'].value, :dbfile => params['db-file'].value, :email => params['email'].value)
# just add a user if required
if params['add-user'].given?
gw.add_user(params['add-user'].value)
elsif params['smtp-server'].given?
gw.set_smtp_credentials(params['smtp-server'].value, params['smtp-user'].value, params['smtp-password'].value)
else
gw.check_and_process(params['forum-id'].value)
end
end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment