Index: trunk/extensions/XMLRC/udp2xmpp.py |
— | — | @@ -1,386 +0,0 @@ |
2 | | -#!/usr/bin/python |
3 | | - |
4 | | -############################################################################## |
5 | | -# UDP to XMPP relay server for XMLRC |
6 | | -# |
7 | | -# |
8 | | -# Copyright (c) 2010, Wikimedia Deutschland; Author: Daniel Kinzler |
9 | | -# All rights reserved. |
10 | | -# |
11 | | -# This program is free software: you can redistribute it and/or modify |
12 | | -# it under the terms of the GNU General Public License as published by |
13 | | -# the Free Software Foundation, either version 3 of the License, or |
14 | | -# (at your option) any later version. |
15 | | -# |
16 | | -# This program is distributed in the hope that it will be useful, |
17 | | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | | -# GNU General Public License for more details. |
20 | | -# |
21 | | -# You should have received a copy of the GNU General Public License |
22 | | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
23 | | -############################################################################## |
24 | | - |
25 | | -import sys, os, time, select, socket |
26 | | -import xmpp # using the xmpppy library <http://xmpppy.sourceforge.net/>, GPL |
27 | | -import simplexml # using simplexml library <http://pypi.python.org/pypi/simplexml/0.6.1>, GPL |
28 | | - |
29 | | -RC_EDIT= 0 |
30 | | -RC_NEW= 1 |
31 | | -RC_MOVE= 2 |
32 | | -RC_LOG= 3 |
33 | | -RC_MOVE_OVER_REDIRECT= 4 |
34 | | - |
35 | | -################################################################################ |
36 | | -class Relay: |
37 | | - def __init__( self, console_encoding = 'utf-8' ): |
38 | | - self.console_encoding = console_encoding |
39 | | - self.channels = {} |
40 | | - |
41 | | - def warn(self, message): |
42 | | - sys.stderr.write( "WARNING: %s\n" % ( message.encode( self.console_encoding ) ) ) |
43 | | - |
44 | | - def info(self, message): |
45 | | - sys.stderr.write( "INFO: %s\n" % ( message.encode( self.console_encoding ) ) ) |
46 | | - |
47 | | - def debug(self, message): |
48 | | - sys.stderr.write( "DEBUG: %s\n" % ( message.encode( self.console_encoding ) ) ) |
49 | | - |
50 | | - def get_all_channels(self): |
51 | | - return self.targets.values() |
52 | | - |
53 | | - def get_channel( self, name ): |
54 | | - return self.channels.get( name ) |
55 | | - |
56 | | - def add_channel( self, name, channel ): |
57 | | - return self.channels[ name ] = channel |
58 | | - |
59 | | - def broadcast_message( self, message, xml = None ): |
60 | | - targets = self.get_all_channels() |
61 | | - |
62 | | - for t in targets: |
63 | | - t.send_message( message, xml = xml ) |
64 | | - |
65 | | - def process_command(self, command): |
66 | | - if ( command == 'quit' ): |
67 | | - self.online = False |
68 | | - elif ( command.startswith( '/' ) ): |
69 | | - self.broadcast_message( command ) |
70 | | - |
71 | | - def get_rc_text( self, rc ): |
72 | | - for k, v in rc.items(): |
73 | | - locals[k] = v |
74 | | - |
75 | | - if rc_type == RC_LOG: |
76 | | - target = "Special:Log/" + rc_log_type; |
77 | | - url = '' |
78 | | - else: |
79 | | - target = title; |
80 | | - |
81 | | - if rc_type == RC_NEW: |
82 | | - url = "oldid=" + rc_this_oldid |
83 | | - else: |
84 | | - url = "diff=" + rc_this_oldid + "&oldid=" + rc_last_oldid |
85 | | - url += "&rcid=" + rc_id |
86 | | - |
87 | | - url = self.get_wiki_url( wikiid, target ); |
88 | | - |
89 | | - if locals.get('oldlen') && locals.get('newlen') ) { |
90 | | - szdiff = newlen - oldlen; |
91 | | - if szdiff >= 0: |
92 | | - szdiff = '+' + szdiff |
93 | | - |
94 | | - szdiff = '(' + $szdiff + ')' |
95 | | - else: |
96 | | - szdiff = '' |
97 | | - |
98 | | - if rc_type == RC_LOG: |
99 | | - targetText = title |
100 | | - flag = logaction |
101 | | - else: |
102 | | - flag = '' |
103 | | - |
104 | | - if type == 'new': |
105 | | - flage += 'N'; |
106 | | - |
107 | | - if minor: |
108 | | - flage += 'M'; |
109 | | - |
110 | | - if bot: |
111 | | - flage += 'B'; |
112 | | - |
113 | | - if anon: |
114 | | - flage += 'A'; |
115 | | - |
116 | | - fullString = "%s %s %s * %s * %s %s" % ( title, flag, url, user, szdiff, comment ); |
117 | | - |
118 | | - return $fullString; |
119 | | - |
120 | | - def get_wiki_url( self, wikiid, page ): |
121 | | - raise Exception("oops!") !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
122 | | - |
123 | | - def relay_rc_message( self, rc ): |
124 | | - w = rc['wikiid'] |
125 | | - t = self.get_channel( w ) |
126 | | - |
127 | | - if not t: |
128 | | - self.warn( "no channel found for %s, discarding message " % s ) |
129 | | - return False |
130 | | - else: |
131 | | - m = self.get_rc_text( rc ) |
132 | | - return t.send_message( m, rc ) |
133 | | - |
134 | | - def service_loop( self, connections* ): |
135 | | - socketlist = {} |
136 | | - for con in connections: |
137 | | - socketlist[ con.get_socket() ] = con |
138 | | - |
139 | | - self.online = 1 |
140 | | - |
141 | | - while self.online: |
142 | | - (i , o, e) = select.select(socketlist.keys(),[],[],1) |
143 | | - |
144 | | - for sock in i: |
145 | | - con = socketlist[ sock ] |
146 | | - if con: |
147 | | - con.process() |
148 | | - else: |
149 | | - raise Exception("Unknown socket: %s" % repr(sock)) |
150 | | - |
151 | | - # FIXME: error recovery (especially when send failed) |
152 | | - |
153 | | - for sock in e: |
154 | | - raise Exception("Error in socket: %s" % repr(sock)) |
155 | | - |
156 | | - self.info("service loop terminated, disconnecting") |
157 | | - |
158 | | - for con in connections: |
159 | | - con.close() |
160 | | - |
161 | | - self.info("done.") |
162 | | - |
163 | | -################################################################################ |
164 | | -class Connection: |
165 | | - def __init__( self, relay ): |
166 | | - self.relay = relay |
167 | | - |
168 | | - def warn(self, message): |
169 | | - self.relay.warn( message ) |
170 | | - |
171 | | - def info(self, message): |
172 | | - self.relay.info( message ) |
173 | | - |
174 | | - def debug(self, message): |
175 | | - self.relay.debug( message ) |
176 | | - |
177 | | -class XmppConnection (Connection): |
178 | | - def __init__( self, relay, jabber, message_encoding = 'utf-8' ): |
179 | | - super( XmppConnection, self ).__init__( relay ) |
180 | | - self.jabber = jabber |
181 | | - self.message_encoding = message_encoding |
182 | | - |
183 | | - def process( self ): |
184 | | - self.jabber.Process(1) |
185 | | - |
186 | | - if not self.jabber.isConnected(): |
187 | | - self.warn("connection lost, reconnecting...") |
188 | | - |
189 | | - if self.jabber.reconnectAndReauth(): |
190 | | - self.warn("re-connect successful.") |
191 | | - self.on_connect() |
192 | | - |
193 | | - def close( self ): |
194 | | - self.jabber.disconnect() |
195 | | - |
196 | | - def make_jabber_channel( self, jid ): |
197 | | - return JabberChannel( self, jid ) |
198 | | - |
199 | | - def make_muc_channel( self, jid, nick ): |
200 | | - return MucChannel( self, jid, nick ) |
201 | | - |
202 | | - def process_message(self, con, message): |
203 | | - if (message.getError()): |
204 | | - self.warn("received %s error from <%s>: %s\n" % (message.getType(), message.getError(), message.getFrom() )) |
205 | | - elif message.getBody(): |
206 | | - self.debug("discarding %s message from <%s>: %s\n" % (message.getType(), message.getFrom(), message.getBody() )) |
207 | | - |
208 | | - def register_handlers(self): |
209 | | - self.jabber.RegisterHandler( 'message', self.process_message ) |
210 | | - |
211 | | - def connect( self, jid, password ): |
212 | | - con= self.jabber.connect() |
213 | | - |
214 | | - if not con: |
215 | | - self.warn( 'could not connect!' ) |
216 | | - return False |
217 | | - |
218 | | - self.debug( 'connected with %s' % con ) |
219 | | - |
220 | | - auth= self.jabber.auth( jid.getNode(), password, resource= jid.getResource() ) |
221 | | - |
222 | | - if not auth: |
223 | | - self.warn( 'could not authenticate as %s!' %s jid ) |
224 | | - return False |
225 | | - |
226 | | - self.debug('authenticated using %s as %s' % ( auth, jid ) ) |
227 | | - |
228 | | - self.register_handlers() |
229 | | - |
230 | | - self.info( 'connected %s' % ( jid ) ) |
231 | | - |
232 | | - self.on_connect() |
233 | | - |
234 | | - return con |
235 | | - |
236 | | - def on_connect( self ): |
237 | | - self.jabber.sendInitPresence(self) |
238 | | - self.roster = self.jabber.getRoster() |
239 | | - |
240 | | - def get_socket( self ): |
241 | | - return self.jabber.Connection._sock |
242 | | - |
243 | | -class CommandConnection (Connection): |
244 | | - def __init__( self, relay, socket ): |
245 | | - super( CommandConnection, self ).__init__( relay ) |
246 | | - self.socket = socket |
247 | | - |
248 | | - def close( self ): |
249 | | - if self.socket != sys.stdin: |
250 | | - self.socket.close() |
251 | | - |
252 | | - def process(self): |
253 | | - msg = self.socket.readline().sub('^\\s+|\\s+$', '') |
254 | | - |
255 | | - if (msg.startswith('/')): |
256 | | - self.process_command( msg ) |
257 | | - else: |
258 | | - self.relay.broadcast_message( msg ) |
259 | | - |
260 | | - def process_command(self, command): |
261 | | - self.relay.process_command( command ) |
262 | | - |
263 | | - def get_socket( self ): |
264 | | - return self.socket |
265 | | - |
266 | | -class UdpConnection (Connection): |
267 | | - def __init__( self, relay, buffsize = 8192 ): |
268 | | - super( UdpConnection, self ).__init__( relay ) |
269 | | - self.buffsize = buffsize |
270 | | - self.socket = None |
271 | | - |
272 | | - def close( self ): |
273 | | - self.socket.close() |
274 | | - |
275 | | - def process(self): |
276 | | - msg = socket.recvfrom( self.buffsize ) |
277 | | - |
278 | | - self.process_rc_packet( msg ) |
279 | | - |
280 | | - def process_rc_packet(self, data): |
281 | | - dom = simplexml.simplexml( data ) |
282 | | - self.relay.relay_rc_message( dom.item[0] ) |
283 | | - |
284 | | - #FIXME: error recovery (xml parser error, etc) |
285 | | - |
286 | | - def connect( self, port, host = '0.0.0.0' ): |
287 | | - self.socket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) |
288 | | - self.socket.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) |
289 | | - self.socket.bind( (host, port) ) |
290 | | - |
291 | | - def get_socket( self ): |
292 | | - return self.socket |
293 | | - |
294 | | -################################################################################## |
295 | | - |
296 | | -class Channel: |
297 | | - def __init__( self, connection ): |
298 | | - self.connection = connection |
299 | | - |
300 | | -class JabberChannel (Channel): |
301 | | - def __init__( self, connection, jid ): |
302 | | - super( JabberChannel, self ).__init__( connection ) |
303 | | - self.connection = connection |
304 | | - self.jid = jid |
305 | | - self.message_type = 'chat' |
306 | | - |
307 | | - def compose_message( self, message, xml = None, mtype = None ): |
308 | | - if type( message ) == unicode: |
309 | | - message = message.encode( self.message_encoding ) |
310 | | - |
311 | | - if type( message ) == str: |
312 | | - if mtype is None: |
313 | | - mtype = self.message_type |
314 | | - |
315 | | - message = xmpp.protocol.Message( jid, body= message, type= mtype ) |
316 | | - |
317 | | - if xml: |
318 | | - message.addChild( node = xml ) |
319 | | - else: |
320 | | - if xml: |
321 | | - raise Exception("Message already composed, can't attach XML!") |
322 | | - |
323 | | - if mtype is not None and mtype != message.getType(): |
324 | | - raise Exception("Message already composed with incompatible type! ( %s != %s )" % (mtype, message.getType()) ) |
325 | | - |
326 | | - return message |
327 | | - |
328 | | - def send_message( self, message, xml = None, mtype = None ): |
329 | | - message = self.compose_message( message, mtype = mtype, xml = xml ) |
330 | | - |
331 | | - return self.connection.jabber.send( message ) |
332 | | - |
333 | | -class MucChannel (JabberChannel): |
334 | | - def __init__( self, connection, room_jid, room_nick ): |
335 | | - super( MucChannel, self ).__init__( connection, jid ) |
336 | | - self.nick = room_nick |
337 | | - self.message_type = 'groupchat' |
338 | | - |
339 | | - def join_muc(self): |
340 | | - # use our own desired nickname as resource part of the group's JID |
341 | | - jid = self.jid.__str__( wresource= 0 ) + "/" + self.nick; |
342 | | - |
343 | | - #create presence stanza |
344 | | - join = xmpp.Presence( to= jid ) |
345 | | - |
346 | | - #announce full MUC support |
347 | | - join.addChild( name = 'x', namespace = 'http://jabber.org/protocol/muc' ) |
348 | | - |
349 | | - self.connection.jabber.send( join ) |
350 | | - |
351 | | - self.info('joined room %s' % room) |
352 | | - |
353 | | - return True |
354 | | - |
355 | | -################################################################################## |
356 | | - |
357 | | -if __name__ == '__main__': |
358 | | - |
359 | | - if len(sys.argv) < 4: |
360 | | - print "Syntax: ytalk tojis myjid mypass" |
361 | | - sys.exit(0) |
362 | | - |
363 | | - tojid = sys.argv[1] |
364 | | - myjid = sys.argv[2] |
365 | | - mypass = sys.argv[3] |
366 | | - group = None |
367 | | - type = 'chat' |
368 | | - |
369 | | - jid=xmpp.protocol.JID(myjid) |
370 | | - |
371 | | - if tojid.startswith('#'): |
372 | | - tojid = tojid[1:] |
373 | | - group = tojid + "/" + jid.getNode() |
374 | | - type = 'groupchat' |
375 | | - |
376 | | - cl=xmpp.Client(jid.getDomain(),debug=[]) |
377 | | - |
378 | | - bot=Bot(cl,tojid,type) |
379 | | - |
380 | | - if not bot.xmpp_connect(): |
381 | | - sys.stderr.write("Could not connect to server, or password mismatch!\n") |
382 | | - sys.exit(1) |
383 | | - |
384 | | - if group and not bot.xmpp_join(group): |
385 | | - sys.stderr.write("Could not join group "+group+"!\n") |
386 | | - sys.exit(1) |
387 | | - |
Index: trunk/extensions/XMLRC/bridge/udp2xmpp-wikis.ini.sample |
— | — | @@ -0,0 +1,34 @@ |
| 2 | +# Wiki info for udp2xmpp bridge. Make sure |
| 3 | +# wiki-info-file in udp2xmpp.ini points here. |
| 4 | + |
| 5 | +# Create one section per wiki. |
| 6 | +# The secion names must be the wiki IDs as returned by wfWikiID(). |
| 7 | +# A wiki's ID is the name of the database it uses ( $wgDBname ) plus, |
| 8 | +# if applicable, the table prefix ( $wgDBname-$wgDBprefix ). |
| 9 | + |
| 10 | +# Alternatively, for a single wiki, you can put |
| 11 | +# the channel config directly into udp2xmpp.ini, |
| 12 | +# using the wiki-info-section option |
| 13 | + |
| 14 | +# each section must specify the following parametrs: |
| 15 | +# |
| 16 | +# page-url: the URL of pages in your wiki, with $1 as a placeholder |
| 17 | +# for the page name. |
| 18 | +# |
| 19 | +# channel-type: type of the notification channel. can be 'jabber' |
| 20 | +# or 'muc'. |
| 21 | +# |
| 22 | +# channel: the channle's name/address. The meaning depends on the |
| 23 | +# channel type, but for both 'jabber' and 'muc' this is |
| 24 | +# the XMPP JID, of the target user or chat group respectively. |
| 25 | + |
| 26 | +# Adopt the below to your own wiki's parameters. |
| 27 | +[acme] |
| 28 | +page-url: http://acme.com/wiki/$1 |
| 29 | +channel-type: muc |
| 30 | +channel: acme-rc@conference.jabber.example.com |
| 31 | + |
| 32 | +#[bcme] |
| 33 | +#page-url: http://bcme.com/wiki/$1 |
| 34 | +#channel-type: jabber |
| 35 | +#channel: john@jabber.example.com |
Property changes on: trunk/extensions/XMLRC/bridge/udp2xmpp-wikis.ini.sample |
___________________________________________________________________ |
Added: svn:mergeinfo |
Index: trunk/extensions/XMLRC/bridge/udp2xmpp.py |
— | — | @@ -0,0 +1,578 @@ |
| 2 | +#!/usr/bin/python |
| 3 | + |
| 4 | +############################################################################## |
| 5 | +# UDP to XMPP relay server for XMLRC |
| 6 | +# |
| 7 | +# |
| 8 | +# Copyright (c) 2010, Wikimedia Deutschland; Author: Daniel Kinzler |
| 9 | +# All rights reserved. |
| 10 | +# |
| 11 | +# This program is free software: you can redistribute it and/or modify |
| 12 | +# it under the terms of the GNU General Public License as published by |
| 13 | +# the Free Software Foundation, either version 3 of the License, or |
| 14 | +# (at your option) any later version. |
| 15 | +# |
| 16 | +# This program is distributed in the hope that it will be useful, |
| 17 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | +# GNU General Public License for more details. |
| 20 | +# |
| 21 | +# You should have received a copy of the GNU General Public License |
| 22 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 23 | +############################################################################## |
| 24 | + |
| 25 | +import sys, os, os.path, time |
| 26 | +import ConfigParser, optparse |
| 27 | +import select, socket, urllib |
| 28 | +import xmpp # using the xmpppy library <http://xmpppy.sourceforge.net/>, GPL |
| 29 | +#import simplexml # using simplexml library <http://pypi.python.org/pypi/simplexml/0.6.1>, GPL |
| 30 | + |
| 31 | +RC_EDIT= 0 |
| 32 | +RC_NEW= 1 |
| 33 | +RC_MOVE= 2 |
| 34 | +RC_LOG= 3 |
| 35 | +RC_MOVE_OVER_REDIRECT= 4 |
| 36 | + |
| 37 | +LOG_MUTE = 0 |
| 38 | +LOG_QUIET = 10 |
| 39 | +LOG_VERBOSE = 20 |
| 40 | +LOG_DEBUG = 30 |
| 41 | + |
| 42 | +################################################################################ |
| 43 | +class WikiInfo(object): |
| 44 | + def __init__( self, config ): |
| 45 | + self.config = config |
| 46 | + |
| 47 | + def get_wikis( self ): |
| 48 | + self.sections() |
| 49 | + |
| 50 | + def get_wiki_property( self, wiki, prop ): |
| 51 | + opt = self.config.options( wiki ) |
| 52 | + |
| 53 | + if opt is None: |
| 54 | + return False |
| 55 | + |
| 56 | + return opt.get( prop ) |
| 57 | + |
| 58 | + def get_wiki_page_url( self, wiki ): |
| 59 | + return self.get_wiki_property( wiki, 'page-url' ) |
| 60 | + |
| 61 | + def get_wiki_channel_type( self, wiki ): |
| 62 | + return self.get_wiki_property( wiki, 'channel-type' ) |
| 63 | + |
| 64 | + def get_wiki_channel_spec( self, wiki ): |
| 65 | + return self.get_wiki_property( wiki, 'channel' ) |
| 66 | + |
| 67 | +class Relay(object): |
| 68 | + def __init__( self, wiki_info, console_encoding = 'utf-8' ): |
| 69 | + self.console_encoding = console_encoding |
| 70 | + self.channels = {} |
| 71 | + self.loglevel = LOG_VERBOSE |
| 72 | + self.wiki_info = wiki_info; |
| 73 | + |
| 74 | + def warn(self, message): |
| 75 | + if self.loglevel >= LOG_QUIET: |
| 76 | + sys.stderr.write( "WARNING: %s\n" % ( message.encode( self.console_encoding ) ) ) |
| 77 | + |
| 78 | + def info(self, message): |
| 79 | + if self.loglevel >= LOG_VERBOSE: |
| 80 | + sys.stderr.write( "INFO: %s\n" % ( message.encode( self.console_encoding ) ) ) |
| 81 | + |
| 82 | + def debug(self, message): |
| 83 | + if self.loglevel >= LOG_DEBUG: |
| 84 | + sys.stderr.write( "DEBUG: %s\n" % ( message.encode( self.console_encoding ) ) ) |
| 85 | + |
| 86 | + def get_all_channels(self): |
| 87 | + return self.targets.values() |
| 88 | + |
| 89 | + def get_channel( self, name ): |
| 90 | + return self.channels.get( name ) |
| 91 | + |
| 92 | + def add_channel( self, name, channel ): |
| 93 | + self.channels[ name ] = channel |
| 94 | + self.debug("added channel " + name) |
| 95 | + |
| 96 | + def create_channels( self, names, factories ): |
| 97 | + for wiki in names: |
| 98 | + t = self.wiki_info.get_wiki_channel_type( name ) |
| 99 | + x = self.wiki_info.get_wiki_channel_spec( name ) |
| 100 | + |
| 101 | + f = factories[ t ] |
| 102 | + channel = f( x ) |
| 103 | + |
| 104 | + channel.join() #FIXME: error detection / recovery! |
| 105 | + |
| 106 | + self.add_channel( name, channel ) |
| 107 | + |
| 108 | + def broadcast_message( self, message, xml = None ): |
| 109 | + targets = self.get_all_channels() |
| 110 | + |
| 111 | + for t in targets: |
| 112 | + t.send_message( message, xml = xml ) |
| 113 | + |
| 114 | + def process_command(self, line): |
| 115 | + args = line.split() |
| 116 | + command = args[0] |
| 117 | + args = args[1:] |
| 118 | + |
| 119 | + if ( command == 'quit' ): |
| 120 | + self.online = False |
| 121 | + #elif ( command.startswith( '/' ) ): |
| 122 | + # self.broadcast_message( line[1:] ) |
| 123 | + elif ( command == 'send' ): |
| 124 | + self.broadcast_message( ' '.join(args) ) |
| 125 | + elif ( command == 'debug' ): |
| 126 | + self.loglevel = LOG_DEBUG |
| 127 | + elif ( command == 'verbose' ): |
| 128 | + self.loglevel = LOG_VERBOSE |
| 129 | + elif ( command == 'quiet' ): |
| 130 | + self.loglevel = LOG_QUIET |
| 131 | + |
| 132 | + def get_rc_text( self, rc ): |
| 133 | + for k, v in rc.items(): |
| 134 | + locals[k] = v |
| 135 | + |
| 136 | + if rc_type == RC_LOG: |
| 137 | + target = "Special:Log/" + rc_log_type; |
| 138 | + url = '' |
| 139 | + else: |
| 140 | + target = title; |
| 141 | + |
| 142 | + if rc_type == RC_NEW: |
| 143 | + url = "oldid=" + rc_this_oldid |
| 144 | + else: |
| 145 | + url = "diff=" + rc_this_oldid + "&oldid=" + rc_last_oldid |
| 146 | + url += "&rcid=" + rc_id |
| 147 | + |
| 148 | + url = self.get_wiki_url( wikiid, target ); |
| 149 | + if not url: url = '' |
| 150 | + |
| 151 | + if locals.get('oldlen') and locals.get('newlen'): |
| 152 | + szdiff = newlen - oldlen; |
| 153 | + if szdiff >= 0: |
| 154 | + szdiff = '+' + szdiff |
| 155 | + |
| 156 | + szdiff = '(' + szdiff + ')' |
| 157 | + else: |
| 158 | + szdiff = '' |
| 159 | + |
| 160 | + if rc_type == RC_LOG: |
| 161 | + targetText = title |
| 162 | + flag = logaction |
| 163 | + else: |
| 164 | + flag = '' |
| 165 | + |
| 166 | + if type == 'new': |
| 167 | + flage += 'N'; |
| 168 | + |
| 169 | + if minor: |
| 170 | + flage += 'M'; |
| 171 | + |
| 172 | + if bot: |
| 173 | + flage += 'B'; |
| 174 | + |
| 175 | + if anon: |
| 176 | + flage += 'A'; |
| 177 | + |
| 178 | + fullString = "%s %s %s * %s * %s %s" % ( title, flag, url, user, szdiff, comment ); |
| 179 | + |
| 180 | + return fullString; |
| 181 | + |
| 182 | + def get_wiki_url( self, wikiid, page ): |
| 183 | + u = self.wiki_info.get_wiki_page_url( wikiid ) |
| 184 | + if not u: return False |
| 185 | + |
| 186 | + return u.sub( '$1', urllib.quote( page ) ) |
| 187 | + |
| 188 | + def relay_rc_message( self, rc ): |
| 189 | + w = rc['wikiid'] |
| 190 | + t = self.get_channel( w ) |
| 191 | + |
| 192 | + if not t: |
| 193 | + self.warn( "no channel found for %s, discarding message " % s ) |
| 194 | + return False |
| 195 | + else: |
| 196 | + m = self.get_rc_text( rc ) |
| 197 | + return t.send_message( m, rc ) |
| 198 | + |
| 199 | + def service_loop( self, *connections ): |
| 200 | + socketlist = {} |
| 201 | + for con in connections: |
| 202 | + socketlist[ con.get_socket() ] = con |
| 203 | + |
| 204 | + self.online = 1 |
| 205 | + |
| 206 | + while self.online: |
| 207 | + (i , o, e) = select.select(socketlist.keys(),[],[],1) |
| 208 | + |
| 209 | + for sock in i: |
| 210 | + con = socketlist[ sock ] |
| 211 | + if con: |
| 212 | + con.process() |
| 213 | + else: |
| 214 | + raise Exception("Unknown socket: %s" % repr(sock)) |
| 215 | + |
| 216 | + # FIXME: error recovery (especially when send failed) |
| 217 | + |
| 218 | + for sock in e: |
| 219 | + raise Exception("Error in socket: %s" % repr(sock)) |
| 220 | + |
| 221 | + self.info("service loop terminated, disconnecting") |
| 222 | + |
| 223 | + for con in connections: |
| 224 | + con.close() |
| 225 | + |
| 226 | + self.info("done.") |
| 227 | + |
| 228 | +################################################################################ |
| 229 | +class Connection(object): |
| 230 | + def __init__( self, relay ): |
| 231 | + self.relay = relay |
| 232 | + |
| 233 | + def warn(self, message): |
| 234 | + self.relay.warn( message ) |
| 235 | + |
| 236 | + def info(self, message): |
| 237 | + self.relay.info( message ) |
| 238 | + |
| 239 | + def debug(self, message): |
| 240 | + self.relay.debug( message ) |
| 241 | + |
| 242 | +class XmppConnection (Connection): |
| 243 | + def __init__( self, relay, message_encoding = 'utf-8' ): |
| 244 | + super( XmppConnection, self ).__init__( relay ) |
| 245 | + self.message_encoding = message_encoding |
| 246 | + |
| 247 | + def process( self ): |
| 248 | + self.jabber.Process(1) |
| 249 | + |
| 250 | + if not self.jabber.isConnected(): |
| 251 | + self.warn("connection lost, reconnecting...") |
| 252 | + |
| 253 | + if self.jabber.reconnectAndReauth(): |
| 254 | + self.warn("re-connect successful.") |
| 255 | + self.on_connect() |
| 256 | + |
| 257 | + def close( self ): |
| 258 | + self.jabber.disconnect() |
| 259 | + |
| 260 | + def make_jabber_channel( self, jid ): |
| 261 | + return JabberChannel( self, jid ) |
| 262 | + |
| 263 | + def make_muc_channel( self, room_jid, room_nick = None ): |
| 264 | + |
| 265 | + if not room_nick: |
| 266 | + room_nick = room_jid.getResource() |
| 267 | + |
| 268 | + if not room_nick: |
| 269 | + room_nick = connection.jid.getNode() |
| 270 | + |
| 271 | + return MucChannel( self, room_jid, room_nick ) |
| 272 | + |
| 273 | + def process_message(self, con, message): |
| 274 | + if (message.getError()): |
| 275 | + self.warn("received %s error from <%s>: %s\n" % (message.getType(), message.getError(), message.getFrom() )) |
| 276 | + elif message.getBody(): |
| 277 | + self.debug("discarding %s message from <%s>: %s\n" % (message.getType(), message.getFrom(), message.getBody() )) |
| 278 | + |
| 279 | + def register_handlers(self): |
| 280 | + self.jabber.RegisterHandler( 'message', self.process_message ) |
| 281 | + |
| 282 | + def guess_local_resource(self): |
| 283 | + resource = "%s-%d" % ( socket.gethostname(), os.getpid() ) |
| 284 | + |
| 285 | + return resource; |
| 286 | + |
| 287 | + def connect( self, jid, password ): |
| 288 | + |
| 289 | + if type( jid ) != object: |
| 290 | + jid = xmpp.protocol.JID( jid ) |
| 291 | + |
| 292 | + if jid.getResource() is None: |
| 293 | + jid = xmpp.protocol.JID( host= jid.getHost(), node= jid.getNode(), resource = self.guess_local_resource() ) |
| 294 | + |
| 295 | + self.jabber = xmpp.Client(jid.getDomain(),debug=[]) |
| 296 | + con= self.jabber.connect() |
| 297 | + |
| 298 | + if not con: |
| 299 | + self.warn( 'could not connect to %s!' % jid.getDomain() ) |
| 300 | + return False |
| 301 | + |
| 302 | + self.debug( 'connected with %s' % con ) |
| 303 | + |
| 304 | + auth= self.jabber.auth( jid.getNode(), password, resource= jid.getResource() ) |
| 305 | + |
| 306 | + if not auth: |
| 307 | + self.warn( 'could not authenticate as %s!' % jid ) |
| 308 | + return False |
| 309 | + |
| 310 | + self.debug('authenticated using %s as %s' % ( auth, jid ) ) |
| 311 | + |
| 312 | + self.register_handlers() |
| 313 | + |
| 314 | + self.jid = jid; |
| 315 | + self.info( 'connected as %s' % ( jid ) ) |
| 316 | + |
| 317 | + self.on_connect() |
| 318 | + |
| 319 | + return con |
| 320 | + |
| 321 | + def on_connect( self ): |
| 322 | + self.jabber.sendInitPresence(self) |
| 323 | + self.roster = self.jabber.getRoster() |
| 324 | + |
| 325 | + def get_socket( self ): |
| 326 | + return self.jabber.Connection._sock |
| 327 | + |
| 328 | +class CommandConnection (Connection): |
| 329 | + def __init__( self, relay, socket ): |
| 330 | + super( CommandConnection, self ).__init__( relay ) |
| 331 | + self.socket = socket |
| 332 | + |
| 333 | + def close( self ): |
| 334 | + if self.socket != sys.stdin: |
| 335 | + self.socket.close() |
| 336 | + |
| 337 | + def process(self): |
| 338 | + msg = self.socket.readline().sub('^\\s+|\\s+$', '') |
| 339 | + |
| 340 | + if (msg.startswith('/')): |
| 341 | + self.process_command( msg ) |
| 342 | + #else: |
| 343 | + # self.relay.broadcast_message( msg ) |
| 344 | + |
| 345 | + def process_command(self, command): |
| 346 | + self.relay.process_command( command ) |
| 347 | + |
| 348 | + def get_socket( self ): |
| 349 | + return self.socket |
| 350 | + |
| 351 | +class UdpConnection (Connection): |
| 352 | + def __init__( self, relay, buffer_size = 8192 ): |
| 353 | + super( UdpConnection, self ).__init__( relay ) |
| 354 | + self.buffer_size = buffer_size |
| 355 | + self.socket = None |
| 356 | + |
| 357 | + def close( self ): |
| 358 | + self.socket.close() |
| 359 | + |
| 360 | + def process(self): |
| 361 | + msg = socket.recvfrom( self.buffer_size ) |
| 362 | + |
| 363 | + self.process_rc_packet( msg ) |
| 364 | + |
| 365 | + def process_rc_packet(self, data): |
| 366 | + try: |
| 367 | + dom = xmpp.simplexml.simplexml( data ) |
| 368 | + except Exception, e: |
| 369 | + self.warn( "failed to parse RC packet: " + e ) |
| 370 | + return False |
| 371 | + |
| 372 | + self.relay.relay_rc_message( dom.item[0] ) |
| 373 | + |
| 374 | + def connect( self, port, interface = '0.0.0.0' ): |
| 375 | + self.socket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) |
| 376 | + self.socket.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) |
| 377 | + |
| 378 | + self.debug( "binding to UDP %s:%d" % (interface, port) ) |
| 379 | + |
| 380 | + if not self.socket.bind( (interface, port) ): |
| 381 | + self.warn( "failed to bind to UDP %s:%d" % (interface, port) ) |
| 382 | + return False |
| 383 | + |
| 384 | + self.info( "listening to UDP %s:%d" % (interface, port) ) |
| 385 | + return True |
| 386 | + |
| 387 | + def get_socket( self ): |
| 388 | + return self.socket |
| 389 | + |
| 390 | +################################################################################## |
| 391 | + |
| 392 | +class Channel(object): |
| 393 | + def __init__( self, connection ): |
| 394 | + self.connection = connection |
| 395 | + |
| 396 | + def join(self): |
| 397 | + pass |
| 398 | + |
| 399 | +class JabberChannel (Channel): |
| 400 | + def __init__( self, connection, jid ): |
| 401 | + super( JabberChannel, self ).__init__( connection ) |
| 402 | + self.connection = connection |
| 403 | + self.jid = jid |
| 404 | + self.message_type = 'chat' |
| 405 | + |
| 406 | + def compose_message( self, message, xml = None, mtype = None ): |
| 407 | + if type( message ) == unicode: |
| 408 | + message = message.encode( self.message_encoding ) |
| 409 | + |
| 410 | + if type( message ) == str: |
| 411 | + if mtype is None: |
| 412 | + mtype = self.message_type |
| 413 | + |
| 414 | + message = xmpp.protocol.Message( jid, body= message, type= mtype ) |
| 415 | + |
| 416 | + if xml: |
| 417 | + message.addChild( node = xml ) |
| 418 | + else: |
| 419 | + if xml: |
| 420 | + raise Exception("Message already composed, can't attach XML!") |
| 421 | + |
| 422 | + if mtype is not None and mtype != message.getType(): |
| 423 | + raise Exception("Message already composed with incompatible type! ( %s != %s )" % (mtype, message.getType()) ) |
| 424 | + |
| 425 | + return message |
| 426 | + |
| 427 | + def send_message( self, message, xml = None, mtype = None ): |
| 428 | + message = self.compose_message( message, mtype = mtype, xml = xml ) |
| 429 | + |
| 430 | + return self.connection.jabber.send( message ) |
| 431 | + |
| 432 | +class MucChannel (JabberChannel): |
| 433 | + def __init__( self, connection, room_jid, room_nick ): |
| 434 | + super( MucChannel, self ).__init__( connection, room_jid.getStripped() ) |
| 435 | + |
| 436 | + self.nick = room_nick |
| 437 | + self.message_type = 'groupchat' |
| 438 | + |
| 439 | + def join(self): |
| 440 | + # use our own desired nickname as resource part of the group's JID |
| 441 | + jid = self.jid.__str__( wresource= 0 ) + "/" + self.nick; |
| 442 | + |
| 443 | + #create presence stanza |
| 444 | + join = xmpp.Presence( to= jid ) |
| 445 | + |
| 446 | + #announce full MUC support |
| 447 | + join.addChild( name = 'x', namespace = 'http://jabber.org/protocol/muc' ) |
| 448 | + |
| 449 | + self.connection.jabber.send( join ) |
| 450 | + |
| 451 | + self.info('joined room %s' % room) |
| 452 | + |
| 453 | + return True |
| 454 | + |
| 455 | +################################################################################## |
| 456 | + |
| 457 | +if __name__ == '__main__': |
| 458 | + |
| 459 | + # find the location of this script |
| 460 | + bindir= os.path.dirname( os.path.realpath( sys.argv[0] ) ) |
| 461 | + extdir= os.path.dirname( bindir ) |
| 462 | + |
| 463 | + # set up command line options........ |
| 464 | + option_parser = optparse.OptionParser() |
| 465 | + option_parser.add_option("--config", dest="config_file", |
| 466 | + help="read config from FILE", metavar="FILE") |
| 467 | + |
| 468 | + option_parser.add_option("--wiki-info", dest="wiki_info_file", |
| 469 | + help="read wiki info from FILE", metavar="FILE") |
| 470 | + |
| 471 | + option_parser.add_option("--quiet", action="store_const", dest="loglevel", const=LOG_QUIET, default=LOG_VERBOSE, |
| 472 | + help="suppress informational messages, only print warnings and errors") |
| 473 | + |
| 474 | + option_parser.add_option("--debug", action="store_const", dest="loglevel", const=LOG_DEBUG, |
| 475 | + help="print debug messages") |
| 476 | + |
| 477 | + (options, args) = option_parser.parse_args() |
| 478 | + |
| 479 | + # find config file........ |
| 480 | + if options.config_file: |
| 481 | + cfg = options.config_file #take it from --config |
| 482 | + else: |
| 483 | + cfg = extdir + "/../../udp2xmpp.ini" #installation root |
| 484 | + |
| 485 | + if not os.path.exists( cfg ): |
| 486 | + cfg = extdir + "/../../phase3/udp2xmpp.ini" #installation root in dev environment |
| 487 | + |
| 488 | + if not os.path.exists( cfg ): |
| 489 | + cfg = bindir + "/udp2xmpp.ini" #extension dir |
| 490 | + |
| 491 | + # define config defaults........ |
| 492 | + config = ConfigParser.SafeConfigParser() |
| 493 | + |
| 494 | + config.add_section( 'UDP' ) |
| 495 | + config.set( 'UDP', 'buffer-size', '8192' ) |
| 496 | + config.set( 'UDP', 'port', '4455' ) |
| 497 | + config.set( 'UDP', 'interface', '0.0.0.0' ) |
| 498 | + |
| 499 | + config.add_section( 'XMPP' ) |
| 500 | + config.set( 'XMPP', 'message-encoding', 'utf-8' ) |
| 501 | + |
| 502 | + # read config file........ |
| 503 | + if not config.read( cfg ): |
| 504 | + sys.stderr.write( "failed to read config from %s\n" % cfg ) |
| 505 | + sys.exit(2) |
| 506 | + |
| 507 | + |
| 508 | + # find wiki info file........ |
| 509 | + wikis = None |
| 510 | + |
| 511 | + if options.wiki_info_file: |
| 512 | + w = options.wiki_info_file #take it from --wiki-info |
| 513 | + |
| 514 | + elif config.has_option( 'udp2xmpp', 'wiki-info-section' ): |
| 515 | + # if the config specifies a wiki-info section, there's only one wiki |
| 516 | + # with the wiki id equal to that section name. The wiki's properties |
| 517 | + # are then take from that section in the config file, no extra wiki |
| 518 | + # info file is needed. |
| 519 | + |
| 520 | + wikiid = config.get( 'udp2xmpp', 'wiki-info-section' ) |
| 521 | + info = config.options( wikiid ) |
| 522 | + |
| 523 | + wikis = ConfigParser.SafeConfigParser() |
| 524 | + wikis.add_section( wikiid ) |
| 525 | + |
| 526 | + for k in info: |
| 527 | + v = config.get( wikiid, k ) |
| 528 | + wikis.set( wikiid, k, v ) |
| 529 | + |
| 530 | + elif config.has_option( 'udp2xmpp', 'wiki-info-file' ): |
| 531 | + w = config.get( 'udp2xmpp', 'wiki-info-file' ) # config file says where to find the wiki info file |
| 532 | + |
| 533 | + else: |
| 534 | + w = extdir + "/../../udp2xmpp-wikis.ini" #installation root |
| 535 | + |
| 536 | + if not os.path.exists( cfg ): |
| 537 | + w = extdir + "/../../phase3/udp2xmpp-wikis.ini" #installation root in dev environment |
| 538 | + |
| 539 | + if not os.path.exists( cfg ): |
| 540 | + w = bindir + "/udp2xmpp-wikis.ini" #extension dir |
| 541 | + |
| 542 | + # load wiki info file, if no wiki info is yet known |
| 543 | + if not wikis: |
| 544 | + wikis = ConfigParser.SafeConfigParser() |
| 545 | + if not wikis.read( w ): |
| 546 | + sys.stderr.write( "failed to read wiki info from %s\n" % w ) |
| 547 | + sys.exit(2) |
| 548 | + |
| 549 | + # create wiki info wrapper and relay instance |
| 550 | + wiki_info = WikiInfo( wikis ) |
| 551 | + relay = Relay( wiki_info ) |
| 552 | + |
| 553 | + relay.loglevel = options.loglevel |
| 554 | + |
| 555 | + # create connections............ |
| 556 | + commands_con = CommandConnection( relay, sys.stdin ) |
| 557 | + udp_con = UdpConnection( relay, buffer_size = config.getint( 'UDP', 'buffer-size' ) ) |
| 558 | + xmpp_con = XmppConnection( relay, message_encoding = config.get( 'XMPP', 'message-encoding' ) ) |
| 559 | + |
| 560 | + # -- DO STUFF ----------------------------------------------------------------------------------- |
| 561 | + |
| 562 | + # connect................ |
| 563 | + if not xmpp_con.connect( jid = config.get( 'XMPP', 'jid' ), password = config.get( 'XMPP', 'password' ) ): |
| 564 | + sys.exit(1) |
| 565 | + |
| 566 | + if not udp_con.connect( port = config.getint( 'UDP', 'port' ), interface = config.get( 'UDP', 'interface' ) ): |
| 567 | + sys.exit(1) |
| 568 | + |
| 569 | + # create channels................ |
| 570 | + # note: Need to be connected to do this. Some channels need to be joined. |
| 571 | + relay.create_channels( wiki_info.get_wikis(), { |
| 572 | + 'jabber': xmpp_con.make_jabber_channel, |
| 573 | + 'muc': xmpp_con.make_muc_channel, |
| 574 | + } ) |
| 575 | + |
| 576 | + # run relay loop................ |
| 577 | + relay.service_loop( commands_con, udp_con, xmpp_con ) |
| 578 | + |
| 579 | + print "done." |
Property changes on: trunk/extensions/XMLRC/bridge/udp2xmpp.py |
___________________________________________________________________ |
Added: svn:mergeinfo |
Index: trunk/extensions/XMLRC/bridge/.htaccess |
— | — | @@ -0,0 +1 @@ |
| 2 | +Deny From All |
\ No newline at end of file |
Index: trunk/extensions/XMLRC/bridge/README |
— | — | @@ -0,0 +1,36 @@ |
| 2 | +This directory contains the UDP-to-XMPP bridge, implemented by udp2xmpp.py. |
| 3 | +This runs standalone as a python program, and does not interact wit hthe web |
| 4 | +server. |
| 5 | + |
| 6 | +You can place this directory anywhere on your system. In fact, for security |
| 7 | +reasons, it is recommended to move it to a location that is not accessible |
| 8 | +from the web or by the web server. |
| 9 | + |
| 10 | +To run udp2xmpp.py, you need python 2.5 and the xmpppy library from |
| 11 | +<http://xmpppy.sourceforge.net/>. You also need an account on an XMPP |
| 12 | +(Jabber) server for use by udp2xmpp.py. |
| 13 | + |
| 14 | +In order to configure udp2xmpp for use with your wiki, rename |
| 15 | +udp2xmpp.ini.sample to udp2xmpp.ini. Put the credentials for your XMPP |
| 16 | +account into udp2xmpp.ini and adjust it to your needs (see comments inside). |
| 17 | + |
| 18 | +If you use UDP-to-XMPP with a single wiki, you can provide the necessary |
| 19 | +information for this wiki directly in udp2xmpp.ini. Otherwise, you need to |
| 20 | +rename and udp2xmpp-wikis.ini.sample to udp2xmpp-wikis.ini and create one |
| 21 | +section for each wiki in this file (see comments). The secions' names |
| 22 | +must be the wiki IDs as returned by wfWikiID(). A wiki's ID is the name of |
| 23 | +the database it uses ( $wgDBname ) plus, if applicable, the table prefix |
| 24 | +( $wgDBname-$wgDBprefix ). |
| 25 | + |
| 26 | +When everything is configured, you can start udp2xmpp.py simply by typing: |
| 27 | + |
| 28 | + python udp2xmpp.py |
| 29 | + |
| 30 | +Since this is a long running process, it's advisable to put it into a |
| 31 | +screen session. |
| 32 | + |
| 33 | +udp2xmpp.py takes commands from standard input. Thes must start with "/": |
| 34 | + |
| 35 | + /quit terminates udp2xmpp |
| 36 | + /send ... sends a message to *all* channels |
| 37 | + /debug /verbose and /quiet set the noise level |
Index: trunk/extensions/XMLRC/bridge/udp2xmpp.ini.sample |
— | — | @@ -0,0 +1,26 @@ |
| 2 | +# Configuration file for udp2xmpp bridge |
| 3 | +# WARNING: make sure this file is not readable from the web! |
| 4 | + |
| 5 | +[udp2xmpp] |
| 6 | +# Location of the wiki list file: |
| 7 | +wiki-info-file: ./udp2xmpp-wikis.ini |
| 8 | + |
| 9 | +# Alternatively, for a single wiki, you can specify it inline: |
| 10 | +# wiki-info-section: acme |
| 11 | +# |
| 12 | +# [acme] |
| 13 | +# page-url: http://acme.com/wiki/$1 |
| 14 | +# channel-type: muc |
| 15 | +# channel: acme-rc@conference.jabber.example.com |
| 16 | + |
| 17 | +[UDP] |
| 18 | +port: 4455 |
| 19 | +# interface: 0.0.0.0 |
| 20 | +# buffer-size: 8192 |
| 21 | + |
| 22 | +[XMPP] |
| 23 | +# NOTE: please put your XMPP login info |
| 24 | +# WARNING: make sure this file is not readable from the web! |
| 25 | +jid: mediawiki@yourhost.jabber.com |
| 26 | +password: snoopy64 |
| 27 | +# message-encoding: utf-8 |
\ No newline at end of file |
Property changes on: trunk/extensions/XMLRC/bridge/udp2xmpp.ini.sample |
___________________________________________________________________ |
Added: svn:mergeinfo |