-
Notifications
You must be signed in to change notification settings - Fork 4
/
monitor_websocket.py
187 lines (160 loc) · 5.86 KB
/
monitor_websocket.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# some code from Twisted Matrix's irc_test.py
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from autobahn.websocket import (WebSocketServerFactory,
WebSocketServerProtocol,
listenWS)
import re
from json import dumps
CHANNEL = 'en.wikipedia' # use language.project
COLOR_RE = re.compile(r"\x03(?:\d{1,2}(?:,\d{1,2})?)?",
re.UNICODE) # remove IRC color codes
PARSE_EDIT_RE = re.compile(r'(\[\[(?P<page_title>.*?)\]\])'
r' +((?P<flags>[A-Z\!]+) )?'
r'(?P<url>\S*)'
r' +\* (?P<user>.*?)'
r' \* (\((?P<change_size>[\+\-][0-9]+)\))?'
r' ?(?P<summary>.+)?')
NON_MAIN_NS = ['Talk',
'User',
'User talk',
'Wikipedia',
'Wikipedia talk',
'File',
'File talk',
'MediaWiki',
'MediaWiki talk',
'Template',
'Template talk',
'Help',
'Help talk',
'Category',
'Category talk',
'Portal',
'Portal talk',
'Book',
'Book talk',
'Education Program',
'Education Program talk',
'TimedText',
'TimedText talk',
'Module',
'Module talk',
'Special',
'Media']
def process_message(message):
no_color = COLOR_RE.sub('', message)
ret = PARSE_EDIT_RE.match(no_color)
if ret:
return ret.groupdict()
return {}
class Monitor(irc.IRCClient):
def __init__(self, bsf):
self.broadcaster = bsf
print 'created IRC ...'
def connectionMade(self):
irc.IRCClient.connectionMade(self)
print 'connected to IRC server...'
def signedOn(self):
self.join(self.factory.channel)
print 'joined', self.factory.channel, '...'
def privmsg(self, user, channel, msg):
rc = process_message(msg)
rc['is_new'] = False
rc['is_bot'] = False
rc['is_minor'] = False
rc['is_unpatrolled'] = False
rc['is_anon'] = False
'''
Special logs:
- Special:Log/abusefilter
- Special:Log/block
- Special:Log/newusers
- Special:Log/move
- Special:Log/pagetriage-curation
- Special:Log/delete
- Special:Log/upload
- Special:Log/patrol
'''
ns, _, title = rc['page_title'].partition(':')
if not title or ns not in NON_MAIN_NS:
rc['ns'] = 'Main'
else:
rc['ns'] = ns
if rc['flags'] and 'N' in rc['flags']:
rc['is_new'] = True
if rc['flags'] and 'B' in rc['flags']:
rc['is_bot'] = True
if rc['flags'] and 'M' in rc['flags']:
rc['is_minor'] = True
if rc['flags'] and '!' in rc['flags']:
rc['is_unpatrolled'] = True
if rc['user'] and sum([a.isdigit() for a in rc['user'].split('.')]) == 4:
rc['is_anon'] = True
# Which revisions to broadcast?
if rc['is_anon'] and rc['ns'] == 'Main':
self.broadcaster.broadcast(dumps(rc))
class MonitorFactory(protocol.ClientFactory):
def __init__(self, channel, bsf):
self.channel = channel
self.bsf = bsf
def buildProtocol(self, addr):
p = Monitor(self.bsf)
p.factory = self
return p
class BroadcastServerProtocol(WebSocketServerProtocol):
def onOpen(self):
self.factory.register(self)
def onMessage(self, msg, binary):
if not binary:
self.factory.broadcast("'%s' from %s" % (msg, self.peerstr))
def connectionLost(self, reason):
WebSocketServerProtocol.connectionLost(self, reason)
self.factory.unregister(self)
class BroadcastServerFactory(WebSocketServerFactory):
def __init__(self, url, debug=False, debugCodePaths=False):
WebSocketServerFactory.__init__(self,
url,
debug=debug,
debugCodePaths=debugCodePaths)
self.clients = []
self.tickcount = 0
start_monitor(self)
def tick(self):
self.tickcount += 1
self.broadcast("'tick %d' from server" % self.tickcount)
reactor.callLater(1, self.tick)
def register(self, client):
if not client in self.clients:
print "registered client " + client.peerstr
self.clients.append(client)
def unregister(self, client):
if client in self.clients:
print "unregistered client " + client.peerstr
self.clients.remove(client)
def broadcast(self, msg):
print "broadcasting message '%s' .." % msg
for c in self.clients:
c.sendMessage(msg)
print "message sent to " + c.peerstr
class BroadcastPreparedServerFactory(BroadcastServerFactory):
def broadcast(self, msg):
# print "broadcasting prepared message '%s' .." % msg
preparedMsg = self.prepareMessage(msg)
for c in self.clients:
c.sendPreparedMessage(preparedMsg)
print "prepared message sent to " + c.peerstr
def start_monitor(bsf):
f = MonitorFactory(CHANNEL, bsf)
reactor.connectTCP("irc.wikimedia.org", 6667, f)
if __name__ == '__main__':
debug = False
# start_monitor()
ServerFactory = BroadcastServerFactory
factory = ServerFactory("ws://localhost:9000",
debug=debug,
debugCodePaths=debug)
factory.protocol = BroadcastServerProtocol
factory.setProtocolOptions(allowHixie76=True)
listenWS(factory)
reactor.run()