Patches for ruby-dbus
[twitterpathy.git] / twitterpathy.rb
blobfe39dc388e428d45b43da1e5e2bc183806d72301
1 #!/usr/bin/env ruby
3 require 'rubygems'
4 gem('twitter4r', '>=0.2.1')
5 require 'twitter'
6 require 'dbus'
8 CONN_MGR_PARAM_FLAG_REQUIRED = 1
9 CONN_MGR_PARAM_FLAG_REGISTER = 2
10 CONN_MGR_PARAM_FLAG_HAS_DEFAULT = 4
11 CONN_MGR_PARAM_FLAG_SECRET = 8
13 HANDLE_TYPE_NONE = 0
14 HANDLE_TYPE_CONTACT = 1
15 HANDLE_TYPE_ROOM = 2
16 HANDLE_TYPE_LIST = 3
17 HANDLE_TYPE_GROUP = 4
19 CONNECTION_STATUS_CONNECTED = 0
20 CONNECTION_STATUS_CONNECTING = 1
21 CONNECTION_STATUS_DISCONNECTED = 2
23 CONNECTION_STATUS_REASON_NONE_SPECIFIED = 0
24 CONNECTION_STATUS_REASON_REQUESTED = 1
25 CONNECTION_STATUS_REASON_NETWORK_ERROR = 2
26 CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED = 3
27 CONNECTION_STATUS_REASON_ENCRYPTION_ERROR = 4
28 CONNECTION_STATUS_REASON_NAME_IN_USE = 5
29 CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED = 6
30 CONNECTION_STATUS_REASON_CERT_UNTRUSTED = 7
31 CONNECTION_STATUS_REASON_CERT_EXPIRED = 8
32 CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED = 9
33 CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH = 10
34 CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH = 11
35 CONNECTION_STATUS_REASON_CERT_SELF_SIGNED = 12
36 CONNECTION_STATUS_REASON_CERT_OTHER_ERROR = 13
38 # TODO: other flags in this set
39 CHANNEL_GROUP_FLAG_PROPERTIES = 2048
41 #Handles used for contact lists, of type HANDLE_TYPE_LIST
42 LIST_HANDLE_FOLLOWERS = 1
43 LIST_HANDLE_FRIENDS = 2
45 class Twitterpathy < DBus::Object
46         dbus_interface "org.freedesktop.Telepathy.ConnectionManager" do
47                 dbus_method :GetParameters, "in proto:s, out parameters:a(susv)" do |proto|
48                         puts "GetParameters(#{proto.inspect})"
49                         $stderr.puts "Attempt to use unsupported protocol #{proto}" if proto != 'twitter'
50                         
51                         parameters = [['account', CONN_MGR_PARAM_FLAG_REQUIRED | CONN_MGR_PARAM_FLAG_REGISTER, 's', ['s', '']], ['password', CONN_MGR_PARAM_FLAG_REQUIRED | CONN_MGR_PARAM_FLAG_REGISTER | CONN_MGR_PARAM_FLAG_SECRET, 's', ['s', '']]]
52                         [parameters]
53                 end
54                 
55                 dbus_method :ListProtocols, "out protocols:as" do ||
56                         puts "ListProtocols"
57                         protocols = ['twitter']
58                         [protocols]
59                 end
60                 
61                 dbus_method :RequestConnection, "in proto:s, in parameters:a{sv}, out service:s, out object:o" do |proto, parameters|
62                         puts "RequestConnection(#{proto.inspect}, #{parameters.inspect})"
63                         $stderr.puts "Attempt to use unsupported protocol #{proto}" if proto != 'twitter'
64                         
65                         object = "/org/freedesktop/Telepathy/Connection/Twitterpathy/twitter/#{parameters['account']}"
66                         service = "org.freedesktop.Telepathy.Connection.Twitterpathy.twitter.#{parameters['account']}"
67                         
68                         #Register a new D-BUS service for this connection
69                         connection_service = $bus.request_service(service)
70                         
71                         #Create the connection object, and export it on the new service
72                         connection = TwitterConnection.new(object, parameters['account'], parameters['password'], connection_service)
73                         connection_service.export(connection)
74                         
75                         #Send signal
76                         NewConnection(service, object, 'twitter')
77                         
78                         [service, object]
79                 end
80                 
81                 dbus_signal :NewConnection, 'bus_name:s, object_path:o, proto:s'
82         end
83 end
85 class TwitterConnection < DBus::Object
86         attr_reader :me, :myfollowerhandles
87         
88         def initialize(path, account, password, connection_service)
89                 puts "initialize TwitterConnection(#{path}, #{account}, ***, #{connection_service})"
90                 @account = account
91                 @password = password
92                 @connection_service = connection_service
93                 @status = CONNECTION_STATUS_CONNECTING
94                 @handles = {HANDLE_TYPE_NONE => {}, HANDLE_TYPE_CONTACT => {}, HANDLE_TYPE_ROOM => {}, HANDLE_TYPE_LIST => {}, HANDLE_TYPE_GROUP => {}}
95                 super(path)
96         end
97         
98         dbus_interface 'org.freedesktop.Telepathy.Connection' do
99                 dbus_method :Connect, '' do ||
100                         puts "Connect, account=#{@account}"
101                         
102                         @twitter_client = Twitter::Client.new(:login => @account, :password => @password)
103                         
104                         @me = @twitter_client.my(:info)
105                         @handles[HANDLE_TYPE_CONTACT][@me.id] = @me
106                         
107                         puts "Connected as #{@me.inspect}"
108                         
109                         #Load followers
110                         myfollowers = @twitter_client.my(:followers)
111                         @myfollowerhandles = myfollowers.map{|follower| follower.id }
112                         myfollowers.each do |follower|
113                                 @handles[HANDLE_TYPE_CONTACT][follower.id] = follower
114                         end
115                         
116                         followers_channel_object = "/org/freedesktop/Telepathy/Connection/Twitterpathy/twitter/#{@account}/contactlist/followers"
117                         followers_channel = FollowersChannel.new(followers_channel_object, self)
118                         @connection_service.export(followers_channel)
119                         
120                         @channels = [[followers_channel_object, 'org.freedesktop.Telepathy.Channel.Type.ContactList', HANDLE_TYPE_LIST, LIST_HANDLE_FOLLOWERS]]
121                         @handles[HANDLE_TYPE_LIST][LIST_HANDLE_FOLLOWERS] = followers_channel
122                         
123                         @status = CONNECTION_STATUS_CONNECTED
124                         
125                         StatusChanged(@status, CONNECTION_STATUS_REASON_REQUESTED)
126                         NewChannel(followers_channel_object, 'org.freedesktop.Telepathy.Channel.Type.ContactList', HANDLE_TYPE_LIST, LIST_HANDLE_FOLLOWERS, false)
127                 end
128                 
129                 dbus_method :Disconnect, '' do ||
130                         puts 'Disconnect'
131                         
132                         @status = CONNECTION_STATUS_DISCONNECTED
133                         
134                         StatusChanged(@status, CONNECTION_STATUS_REASON_REQUESTED)
135                 end
136                 
137                 dbus_method :GetInterfaces, 'out interfaces:as' do ||
138                         interfaces = []
139                         [interfaces]
140                 end
141                 
142                 dbus_method :GetProtocol, 'out protocol:s' do ||
143                         protocol = 'twitter'
144                         [protocol]
145                 end
146                 
147                 dbus_method :GetSelfHandle, 'out self_handle:u' do ||
148                         self_handle = @me.id
149                         [self_handle]
150                 end
151                 
152                 dbus_method :GetStatus, 'out status:u' do ||
153                         [@status]
154                 end
155                 
156                 dbus_method :HoldHandles, 'in handle_type:u, in handles:au' do |handle_type, handles|
157                         # TODO
158                 end
159                 
160                 dbus_method :InspectHandles, 'in handle_type:u, in handles:au, out strings:as' do |handle_type, handles|
161                         strings = handles.map{|handle| @handles[handle_type][handle].inspect }
162                         [strings]
163                 end
164                 
165                 dbus_method :ListChannels, 'out channels:a(osuu)' do ||
166                         [@channels]
167                 end
168                 
169                 dbus_method :ReleaseHandles, 'in handle_type:u, in handles:au' do |handle_type, handles|
170                         # TODO
171                 end
172                 
173                 dbus_method :RequestChannel, 'in type:s, in handle_type:u, in handle:u, in suppress_handler:b, out channel:o' do |type, handle_type, handle, suppress_handler|
174                         if type == 'org.freedesktop.Telepathy.Channel.Type.ContactList' && handle_type == HANDLE_TYPE_LIST && handle == LIST_HANDLE_FOLLOWERS
175                                 channel = "/org/freedesktop/Telepathy/Connection/Twitterpathy/twitter/#{@account}/contactlist/followers"
176                         else
177                                 # TODO: throw org.freedesktop.Telepathy.Error.NotImplemented or org.freedesktop.Telepathy.Error.InvalidHandle
178                                 channel = ''
179                         end
180                         [channel]
181                 end
182                 
183                 dbus_method :RequestHandles, 'in handle_type:u, in names:as, out handles:au' do |handle_type, names|
184                         puts "RequestHandles(#{handle_type.inspect}, #{names.inspect})"
185                         
186                         if handle_type == HANDLE_TYPE_CONTACT
187                                 handles = names.map {|name|
188                                         # TODO: make this more efficient?
189                                         contact_handle = nil
190                                         @handles[HANDLE_TYPE_CONTACT].each do |handle, contact|
191                                                 if contact.name == name
192                                                         contact_handle = handle
193                                                         break
194                                                 end
195                                         end
196                                         
197                                         if contact_handle.nil?
198                                                 contact = @twitter_client.user(name) # TODO: handle errors
199                                                 @handles[HANDLE_TYPE_CONTACT][contact.id] = contact
200                                                 contact_handle = contact.id
201                                         end
202                                         
203                                         contact_handle
204                                 }
205                         elsif handle_type = HANDLE_TYPE_LIST
206                                 handles = names.map {|name|
207                                         if name == 'publish'
208                                                 LIST_HANDLE_FOLLOWERS
209                                         else
210                                                 # TODO
211                                                 0
212                                         end
213                                 }
214                         else
215                                 # TODO: other types
216                                 handles = []
217                         end
218                         
219                         [handles]
220                 end
221                 
222                 dbus_signal :NewChannel, 'object_path:o, channel_type:s, handle_type:u, handle:u, suppress_handler:b'
223                 dbus_signal :StatusChanged, 'status:u, reason:u'
224         end
227 class FollowersChannel < DBus::Object
228         def initialize(path, connection)
229                 @connection = connection
230                 @properties = {
231                         'org.freedesktop.Telepathy.Channel' => {
232                                 'ChannelType' => ['s', 'org.freedesktop.Telepathy.Channel.Type.ContactList'],
233                                 'Interfaces' => ['as', ['org.freedesktop.Telepathy.Channel.Interface.Group']],
234                                 'TargetHandle' => ['u', LIST_HANDLE_FOLLOWERS],
235                                 'TargetID' => ['s', 'Followers'],
236                                 'TargetHandleType' => ['u', HANDLE_TYPE_LIST]
237                         },
238                         'org.freedesktop.Telepathy.Channel.Interface.Group' => {
239                                 'GroupFlags' => ['u', CHANNEL_GROUP_FLAG_PROPERTIES],
240                                 'HandleOwners' => ['a{uu}', {}],
241                                 'LocalPendingMembers' => ['a(uuus)', []],
242                                 'Members' => ['au', connection.myfollowerhandles],
243                                 'RemotePendingMembers' => ['au', []],
244                                 'SelfHandle' => ['u', connection.me.id]
245                         }
246                 }
247                 super(path)
248         end
249         
250         dbus_interface 'org.freedesktop.Telepathy.Channel' do
251                 dbus_method :Close, '' do ||
252                         # TODO: throw org.freedesktop.Telepathy.Error.NotImplemented
253                 end
254                 
255                 #Deprecated methods, just return appropriate properties
256                 dbus_method :GetChannelType, 'out ChannelType:s' do ||
257                         [@properties['org.freedesktop.Telepathy.Channel']['ChannelType'][1]]
258                 end
259                 
260                 dbus_method :GetHandle, 'out TargetHandleType:u, out TargetHandle:u' do ||
261                         [@properties['org.freedesktop.Telepathy.Channel']['TargetHandleType'][1], @properties['org.freedesktop.Telepathy.Channel']['TargetHandle'][1]]
262                 end
263                 
264                 dbus_method :GetInterfaces, 'out Interfaces:as' do ||
265                         [@properties['org.freedesktop.Telepathy.Channel']['Interfaces'][1]]
266                 end
267                 
268                 dbus_signal :Closed, ''
269         end
270         
271         # TODO: make a nice general way to add properties to classes
272         dbus_interface 'org.freedesktop.DBus.Properties' do
273                 dbus_method :Get, 'in interface_name:s, in property_name:s, out value:v' do |interface_name, property_name|
274                         value = @properties[interface_name][property_name]
275                         [value]
276                 end
277                 
278                 dbus_method :Set, 'in interface_name:s, in property_name:s, in value:v' do |interface_name, property_name, value|
279                 end
280                 
281                 dbus_method :GetAll, 'in interface_name:s, out props:a{sv}' do |interface_name|
282                         props = @properties[interface_name]
283                         [props]
284                 end
285         end
286         
287         dbus_interface 'org.freedesktop.Telepathy.Channel.Type.ContactList' do
288         end
289         
290         dbus_interface 'org.freedesktop.Telepathy.Channel.Interface.Group' do
291                 dbus_method :AddMembers, 'in contacts:au, in message:s' do |contacts, message|
292                         # TODO
293                 end
294                 
295                 dbus_method :RemoveMembers, 'in contacts:au, in message:s' do |contacts, message|
296                         # TODO
297                 end
298                 
299                 dbus_method :RemoveMembersWithReason, 'in contacts:au, in message:s, in reason:u' do |contacts, message, reason|
300                         # TODO
301                 end
302                 
303                 #Deprecated methods
304                 dbus_method :GetGroupFlags, 'out GroupFlags:u' do ||
305                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['GroupFlags'][1]]
306                 end
307                 
308                 dbus_method :GetLocalPendingMembers, 'out LocalPendingMembers:au' do ||
309                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['LocalPendingMembers'][1].map{|member| member[0]}]
310                 end
311                 
312                 dbus_method :GetLocalPendingMembersWithInfo, 'out LocalPendingMembers:a(uuus)' do ||
313                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['LocalPendingMembers'][1]]
314                 end
315                 
316                 dbus_method :GetMembers, 'out Members:au' do ||
317                         puts "GetMembers, returning #{@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['Members'][1].inspect}"
318                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['Members'][1]]
319                 end
320                 
321                 dbus_method :GetRemotePendingMembers, 'out RemotePendingMembers:au' do ||
322                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['RemotePendingMembers'][1]]
323                 end
324                 
325                 dbus_method :GetSelfHandle, 'out SelfHandle:u' do ||
326                         [@properties['org.freedesktop.Telepathy.Channel.Interface.Group']['SelfHandle'][1]]
327                 end
328                 
329                 dbus_signal :HandleOwnersChanged, 'added:a{uu}, removed:au'
330                 dbus_signal :SelfHandleChanged, 'self_handle:u'
331                 dbus_signal :GroupFlagsChanged, 'added:u, removed:u'
332                 dbus_signal :MembersChanged, 'message:s, added:au, removed:au, local_pending:au, remote_pending:au, actor:u, reason:u'
333         end
336 $bus = DBus::SessionBus.instance
338 service = $bus.request_service("org.freedesktop.Telepathy.ConnectionManager.Twitterpathy")
340 exported_obj = Twitterpathy.new("/org/freedesktop/Telepathy/ConnectionManager/Twitterpathy")
341 service.export(exported_obj)
343 puts "listening"
344 main = DBus::Main.new
345 main << $bus
346 main.run