r70830 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r70829‎ | r70830 | r70831 >
Date:15:45, 10 August 2010
Author:daniel
Status:deferred
Tags:
Comment:
udp2xmpp bridge - doesn't work yet.
Modified paths:
  • /trunk/extensions/XMLRC/bridge (added) (history)
  • /trunk/extensions/XMLRC/bridge/.htaccess (added) (history)
  • /trunk/extensions/XMLRC/bridge/README (added) (history)
  • /trunk/extensions/XMLRC/bridge/udp2xmpp-wikis.ini.sample (added) (history)
  • /trunk/extensions/XMLRC/bridge/udp2xmpp.ini.sample (added) (history)
  • /trunk/extensions/XMLRC/bridge/udp2xmpp.py (added) (history)
  • /trunk/extensions/XMLRC/udp2xmpp.py (deleted) (history)

Diff [purge]

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

Status & tagging log