r96672 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r96671‎ | r96672 | r96673 >
Date:17:31, 9 September 2011
Author:awjrichards
Status:deferred
Tags:
Comment:
Adding better identica support including statusnet library; added support for multiple channels, fancy author and title maps, and more useful exception output with sys.exc_info in bot; updated documented dependencies to include simplejson; Most of this work was done by someone else (werdna?) - I just did some cleanup and am adding changes to svn
Modified paths:
  • /trunk/tools/adminlogbot/README (modified) (history)
  • /trunk/tools/adminlogbot/adminlog.py (modified) (history)
  • /trunk/tools/adminlogbot/adminlogbot.py (modified) (history)
  • /trunk/tools/adminlogbot/statusnet.py (added) (history)

Diff [purge]

Index: trunk/tools/adminlogbot/adminlog.py
@@ -3,9 +3,12 @@
44 user="More Bots"
55 password="..."
66 logname="Server admin log"
 7+identica_username="wikimediatech"
 8+identica_password="..."
79
810 import mwclient
911 import datetime
 12+import statusnet
1013 import re
1114
1215 months=["January","February","March","April","May","June","July","August","September","October","November","December"]
@@ -38,3 +41,9 @@
3942 else:
4043 lines.insert(position,logline)
4144 page.save('\n'.join(lines),"%s (%s)"%(message,author))
 45+
 46+ ## IDENTICA :D
 47+ snapi = statusnet.StatusNet( { 'user': identica_username, 'passwd': identica_password, 'api': 'https://identi.ca/api' } )
 48+ snupdate = "%s: %s" % (author, message)
 49+ snupdate = snupdate[:140] # Trim message
 50+ snapi.update(snupdate)
Index: trunk/tools/adminlogbot/statusnet.py
@@ -0,0 +1,217 @@
 2+# LICENCE: GPL3
 3+import os.path
 4+import simplejson as json
 5+from select import select
 6+from subprocess import Popen, PIPE
 7+from sys import version_info
 8+
 9+if version_info[0] < 3:
 10+ from urllib import urlencode
 11+else:
 12+ from urllib.parse import urlencode
 13+
 14+def p_ready(p):
 15+ if p.poll() is not None or len(select([p.stdout], [], [], 0)[0]) > 0:
 16+ return True
 17+ else:
 18+ return False
 19+
 20+def success(data):
 21+ if 'error' in data:
 22+ return False
 23+ else:
 24+ return True
 25+
 26+def to_json(data):
 27+ try:
 28+ data = json.loads(str(data.decode('utf8')))
 29+ return data
 30+ except:
 31+ errstr = "<div class='error'>"
 32+
 33+ if version_info[0] == 3:
 34+ errstr = bytes(errstr, 'utf8')
 35+
 36+ if errstr in data:
 37+ error = data.replace(errstr, '')[:-6]
 38+ return {'error':error}
 39+ else:
 40+ return {'error':"unknown error"}
 41+
 42+class StatusNet:
 43+ def __init__(self, acc):
 44+ if type(acc) is dict:
 45+ self.acc = acc
 46+ else:
 47+ return None
 48+ self.source = 'python-statusnet'
 49+ self.ext = '.json'
 50+
 51+ def _request(self, path, params=None, post=None):
 52+ auth_str = ':'.join( (self.acc['user'], self.acc['passwd']) )
 53+
 54+ url = os.path.join(self.acc['api'], path)
 55+ url += self.ext
 56+ if params is not None:
 57+ url += "?"+urlencode(params)
 58+
 59+ cmd = ['curl', '-s', '-u', auth_str, url]
 60+
 61+ if post is not None and type(post) is dict:
 62+ cmd.append('-d')
 63+ cmd.append(urlencode(post))
 64+
 65+ return Popen(cmd, stdout=PIPE)
 66+
 67+ # account verification
 68+ def verify_creds(self):
 69+ return self._request("account/verify_credentials")
 70+
 71+ # notices
 72+ def get(self, notice_id):
 73+ return self._request("statuses/show/"+str(notice_id))
 74+
 75+ def update(self, status, replyid=None):
 76+ data = { 'status': status,
 77+ 'in_reply_to_status_id': replyid,
 78+ 'source': self.source }
 79+ if replyid is not None:
 80+ data['in_reply_to_status_id'] = replyid
 81+ return self._request("statuses/update", post=data)
 82+
 83+ def repeat(self, nid):
 84+ data = {'source': self.source}
 85+ return self._request("statuses/retweet/{0}".format(nid), post=data)
 86+
 87+ def delete(self, notice_id):
 88+ data = { 'id': notice_id }
 89+ return self._request("statuses/destroy/{0}".format(notice_id), post=data)
 90+
 91+ # direct messages
 92+ def direct(self, user, msg):
 93+ data = { 'screen_name': user
 94+ , 'text': msg }
 95+ return self._request("direct_messages/new", post=data)
 96+
 97+# def delete_direct(self, acc, id):
 98+# return self._request(acc, "direct_messages/destroy/"+str(id))
 99+
 100+ # timelines
 101+ def home(self, page=1, count=20):
 102+ data = { 'page': page
 103+ , 'count': count }
 104+ return self._request("statuses/friends_timeline", data)
 105+
 106+ def mentions(self, page=1, count=20, show_repeats=0):
 107+ data = { 'page': page
 108+ , 'count': count
 109+ , 'include_rts': show_repeats }
 110+ return self._request("statuses/mentions", data)
 111+
 112+ def user_tl(self, user, page=1, count=20, show_repeats=0):
 113+ data = { 'screen_name': user
 114+ , 'page': page
 115+ , 'count': count
 116+ , 'include_rts': show_repeats }
 117+ return self._request("statuses/user_timeline", data)
 118+
 119+ def inbox(self, page=1, count=20):
 120+ data = { 'page': page
 121+ , 'count': count }
 122+ return self._request("direct_messages", data)
 123+
 124+ def outbox(self, page=1, count=20):
 125+ data = { 'page': page
 126+ , 'count': count }
 127+ return self._request("direct_messages/sent", data)
 128+
 129+ def public(self, page=1, count=20):
 130+ data = { 'page': page
 131+ , 'count': count }
 132+ return self._request("statuses/public_timeline", data)
 133+
 134+ # friends + followers
 135+ def friends(self, user):
 136+ data = { 'screen_name': user }
 137+ return self._request("statuses/friends", data)
 138+
 139+ def followers(self, user):
 140+ data = { 'screen_name': user }
 141+ return self._request("statuses/followers", data)
 142+
 143+ # groups
 144+ def groups(self, user=None, page=1):
 145+ data = { 'page': page }
 146+ if user is not None:
 147+ data['screen_name'] = user
 148+ else:
 149+ data['screen_name'] = self.acc['user']
 150+ return self._request("statusnet/groups/list", data)
 151+
 152+ def group_tl(self, group, page=1, count=20):
 153+ data = { 'page': page
 154+ , 'count': count }
 155+ return self._request("statusnet/groups/timeline/"+group, data)
 156+
 157+ def group_join(self, group):
 158+ return self._request("statusnet/groups/join/"+group)
 159+
 160+ def group_leave(self, group):
 161+ return self._request("statusnet/groups/leave/"+group)
 162+
 163+ # subscriptions
 164+ def follow(self, user_id):
 165+ data = { 'user_id': user_id }
 166+ return self._request("friendships/create", post=data)
 167+
 168+ def unfollow(self, user_id):
 169+ data = { 'user_id': user_id }
 170+ return self._request("friendships/destroy", post=data)
 171+
 172+ # favorites
 173+ def favorites(self, user=None, page=1):
 174+ data = { 'page': page }
 175+ if user is not None:
 176+ return self._request("favorites/"+user, data)
 177+ else:
 178+ return self._request("favorites", data)
 179+
 180+ def favorite(self, notice_id):
 181+ data = { 'id': notice_id }
 182+ return self._request("favorites/create/"+str(notice_id), post=data)
 183+
 184+ def unfavorite(self, notice_id):
 185+ data = { 'id': notice_id }
 186+ return self._request("favorites/destroy/"+str(notice_id), post=data)
 187+
 188+ # blocks
 189+ def block(self, user_id):
 190+ data = { 'user_id': user_id }
 191+ return self._request("blocks/create", post=data)
 192+
 193+ def unblock(self, user_id):
 194+ data = { 'user_id': user_id }
 195+ return self._request("blocks/destroy", post=data)
 196+
 197+# def get_blocks(self, page=1):
 198+# data = { "page": page }
 199+# return self._request("blocks/blocking", data)
 200+
 201+ # searches
 202+ def search(self, query, page=1, count=20):
 203+ data = { 'q': query
 204+ , 'page': page
 205+ , 'count': count }
 206+ return self._request("search", data)
 207+
 208+ def tag_tl(self, tag, page=1, count=20):
 209+ data = { 'page': page
 210+ , 'count': count }
 211+ return self._request("statusnet/tags/timeline/"+tag, data)
 212+
 213+ # profile / config
 214+ def update_profile(self, data):
 215+ return self._request("account/update_profile", data)
 216+
 217+ def config(self):
 218+ return self._request("statusnet/config")
Property changes on: trunk/tools/adminlogbot/statusnet.py
___________________________________________________________________
Added: svn:eol-style
1219 + native
Index: trunk/tools/adminlogbot/adminlogbot.py
@@ -1,30 +1,44 @@
22 import irclib
33 import time
44 import adminlog
 5+import sys
56
6 -target="#wikimedia-tech"
 7+targets=("#wikimedia-tech", "#wikimedia-operations")
78 nickserv="nickserv"
8 -nickpassword="..."
 9+nick="nick"
 10+nickpassword="password"
 11+network="irc.freenode.net"
 12+port=6667
913
 14+authormap = { "TimStarling": "Tim", "_mary_kate_": "river", "yksinaisyyteni": "river", "flyingparchment": "river", "RobH": "RobH", "werdnum": "Andrew", "werdna": "Andrew", "werdnus": "Andrew", "aZaFred" : "Fred" }
 15+titlemap = { "Andrew": "junior", "RoanKattouw": "Mr. Obvious", "RobH": "RobH", "notpeter": "and now dispaching a T1000 to your position to terminate you.", "domas": "o lord of the trolls, my master, my love. I can't live with out you; oh please log to me some more!" }
 16+
1017 def on_connect(con, event):
1118 con.privmsg(nickserv,"identify "+nickpassword)
1219 time.sleep(1)
13 - con.join(target)
 20+ for target in targets:
 21+ con.join(target)
1422
1523 def on_msg(con, event):
16 - if event.target() != target: return
 24+ if event.target() not in targets: return
1725 author,rest=event.source().split('!')
 26+ if author in authormap: author=authormap[author]
1827 line=event.arguments()[0]
1928 if line.startswith("!log "):
2029 undef,message=line.split(" ",1);
21 - try: adminlog.log(message,author)
22 - except: server.privmsg(target,"I failed :(")
 30+ try:
 31+ adminlog.log(message,author)
 32+ if author in titlemap: title = titlemap[author]
 33+ else: title = "Master"
 34+ server.privmsg(event.target(),"Logged the message, %s" % title)
 35+ except: print sys.exc_info()
2336
2437
2538 irc = irclib.IRC()
2639 server = irc.server()
27 -server.connect("irc.freenode.net",6667,"morebots")
 40+server.connect(network,port,nick)
2841 server.add_global_handler("welcome", on_connect)
2942 server.add_global_handler("pubmsg",on_msg)
3043
3144 irc.process_forever()
 45+
Index: trunk/tools/adminlogbot/README
@@ -1,5 +1,5 @@
22 Dependencies:
33 mwclient: svn co http://mwclient.svn.sourceforge.net/viewvc/mwclient/trunk/mwclient/
44 irclib: http://downloads.sourceforge.net/python-irclib/python-irclib-0.4.6.tar.gz?modtime=1135442433&big_mirror=0
 5+ simplejson: http://pypi.python.org/pypi/simplejson/
56
6 -

Status & tagging log