summaryrefslogtreecommitdiff
path: root/data/rbot/plugins/twitter.rb
blob: ec5e3b92a474e36cf9565c3ac1f54f135eee8b28 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#-- vim:sw=2:et
#++
#
# :title: Twitter Status Update for rbot
#
# Author:: Carter Parks (carterparks) <carter@carterparks.com>
# Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
#
# Copyright:: (C) 2007 Carter Parks
# Copyright:: (C) 2007 Giuseppe Bilotta
#
# Users can setup their twitter username and password and then begin updating
# twitter whenever

require 'rexml/rexml'
require 'cgi'

class TwitterPlugin < Plugin
  Config.register Config::IntegerValue.new('twitter.status_count',
    :default => 1, :validate => Proc.new { |v| v > 0 && v <= 10},
    :desc => "Maximum number of status updates shown by 'twitter status'")
  Config.register Config::IntegerValue.new('twitter.friends_status_count',
    :default => 3, :validate => Proc.new { |v| v > 0 && v <= 10},
    :desc => "Maximum number of status updates shown by 'twitter friends status'")

  def initialize
    super

    class << @registry
      def store(val)
        val
      end
      def restore(val)
        val
      end
    end

    @header = {
      'X-Twitter-Client' => 'rbot twitter plugin'
    }
  end

  # return a help string when the bot is asked for help on this plugin
  def help(plugin, topic="")
    return "twitter status [nick] => show nick's (or your) status, use 'twitter friends status [nick]' to also show the friends' timeline | twitter update [status] => updates your status on twitter | twitter identify [username] [password] => ties your nick to your twitter username and password | twitter actions [on|off] => enable/disable twitting of actions (/me does ...)"
  end

  # update the status on twitter
  def get_status(m, params)

    nick = params[:nick] || @registry[m.sourcenick + "_username"]

    friends = params[:friends]

    if not nick
      m.reply "you should specify the username of the twitter touse, or identify using 'twitter identify [username] [password]'"
      return false
    end

    user = URI.escape(nick)

    count = @bot.config['twitter.status_count']
    unless friends
      uri = "http://twitter.com/statuses/user_timeline/#{user}.xml?count=#{count}"
    else
      count = @bot.config['twitter.friends_status_count']
      auth = ""
      if m.private?
        auth << URI.escape(@registry[m.sourcenick + "_username"])
        auth << ":"
        auth << URI.escape(@registry[m.sourcenick + "_password"])
        auth << "@"
      end
      uri = "http://#{auth}twitter.com/statuses/friends_timeline/#{user}.xml"
    end

    response = @bot.httputil.get(uri, :headers => @header, :cache => false)
    debug response

    texts = []

    if response
      begin
        rex = REXML::Document.new(response)
        rex.root.elements.each("status") { |st|
          # month, day, hour, min, sec, year = st.elements['created_at'].text.match(/\w+ (\w+) (\d+) (\d+):(\d+):(\d+) \S+ (\d+)/)[1..6]
          # debug [year, month, day, hour, min, sec].inspect
          # time = Time.local(year.to_i, month, day.to_i, hour.to_i, min.to_i, sec.to_i)
          time = Time.parse(st.elements['created_at'].text)
          now = Time.now
          # Sometimes, time can be in the future; invert the relation in this case
          delta = ((time > now) ? time - now : now - time)
          msg = st.elements['text'].to_s + " (#{Utils.secs_to_string(delta.to_i)} ago via #{st.elements['source'].to_s})"
          author = ""
          if friends
            author = Utils.decode_html_entities(st.elements['user'].elements['name'].text) + ": " rescue ""
          end
          texts << author+Utils.decode_html_entities(msg).ircify_html
        }
        if friends
          # friends always return the latest 20 updates, so we clip the count
          texts[count..-1]=nil
        end
      rescue
        error $!
        if friends
          m.reply "could not parse status for #{nick}'s friends"
        else
          m.reply "could not parse status for #{nick}"
        end
        return false
      end
      m.reply texts.reverse.join("\n")
      return true
    else
      if friends
        rep = "could not get status for #{nick}'s friends"
        rep << ", try asking in private" unless m.private?
      else
        rep = "could not get status for #{nick}"
      end
      m.reply rep
      return false
    end
  end

  # update the status on twitter
  def update_status(m, params)


    unless @registry.has_key?(m.sourcenick + "_password") && @registry.has_key?(m.sourcenick + "_username")
      m.reply "you must identify using 'twitter identify [username] [password]'"
      return false
    end

    user = URI.escape(@registry[m.sourcenick + "_username"])
    pass = URI.escape(@registry[m.sourcenick + "_password"])
    uri = "http://#{user}:#{pass}@twitter.com/statuses/update.xml"

    msg = params[:status].to_s

    if msg.length > 160
      m.reply "your status message update is too long, please keep it under 140 characters if possible, 160 characters maximum"
      return
    end

    if msg.length > 140
      m.reply "your status message is longer than 140 characters, which is not optimal, but I'm going to update anyway"
    end

    source = "source=rbot"
    msg = "status=#{CGI.escape(msg)}"
    body = [source,msg].join("&")

    response = @bot.httputil.post(uri, body, :headers => @header)
    debug response

    reply_method = params[:notify] ? :notify : :reply
    if response.class == Net::HTTPOK
      m.__send__(reply_method, "status updated")
    else
      m.__send__(reply_method, "could not update status")
    end
  end

  # ties a nickname to a twitter username and password
  def identify(m, params)
    @registry[m.sourcenick + "_username"] = params[:username].to_s
    @registry[m.sourcenick + "_password"] = params[:password].to_s
    m.reply "you're all setup!"
  end

  # update on ACTION if the user has enabled the option
  def ctcp_listen(m)
    return unless m.action?
    return unless @registry[m.sourcenick + "_actions"]
    update_status(m, :status => m.message, :notify => true)
  end

  # show or toggle action twitting
  def actions(m, params)
    case params[:toggle]
    when 'on'
      @registry[m.sourcenick + "_actions"] = true
      m.okay
    when 'off'
      @registry.delete(m.sourcenick + "_actions")
      m.okay
    else
      if @registry[m.sourcenick + "_actions"]
        m.reply _("actions will be twitted")
      else
        m.reply _("actions will not be twitted")
      end
    end
  end
end

# create an instance of our plugin class and register for the "length" command
plugin = TwitterPlugin.new
plugin.map 'twitter identify :username :password', :action => "identify", :public => false
plugin.map 'twitter update *status', :action => "update_status", :threaded => true
plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
plugin.map 'twitter :friends [status] [:nick]', :action => "get_status", :requirements => { :friends => /^friends?$/ }, :threaded => true