Difference between revisions of "User:WindPower/RCNotify.py"
(New release; includes schema checking and a new http timeout setting. No deprecation of old clients this time, but still recommended to upgrade if you want to report schema changes and have timeouts.) |
(New release, now with rate limiting for submission) |
||
Line 9: | Line 9: | ||
* First way: Download Python and run the script as is. | * First way: Download Python and run the script as is. | ||
** [http://python.org/download/releases/2.6.6/ Download Python] and install it if you haven't got it yet. | ** [http://python.org/download/releases/2.6.6/ Download Python] and install it if you haven't got it yet. | ||
− | ** [http://upload.gaiatools.com/files/ | + | ** [http://upload.gaiatools.com/files/rcNotify_1.7z Download the archive file] and extract it somewhere. You should have a file named <code>rcNotify.py</code> (the Python script), a file named <code>rcConfig.py</code> (configuration file) and a folder called <code>wikitools</code> (Wiki API library). |
** Edit <code>rcConfig.py</code> to your liking. Read the comments next to each line. Do not use Notepad to edit the file, because it cannot read Unix-style linebreaks (<code>\n</code>). Use WordPad or whatever code editor you have. | ** Edit <code>rcConfig.py</code> to your liking. Read the comments next to each line. Do not use Notepad to edit the file, because it cannot read Unix-style linebreaks (<code>\n</code>). Use WordPad or whatever code editor you have. | ||
** Open a command prompt or terminal window, and run Python with the script's path as argument. | ** Open a command prompt or terminal window, and run Python with the script's path as argument. | ||
Line 197: | Line 197: | ||
return u'&'.join(s) | return u'&'.join(s) | ||
def checkForRCs(): | def checkForRCs(): | ||
+ | global config | ||
rcs = wikitools.api.APIRequest(wiki, { | rcs = wikitools.api.APIRequest(wiki, { | ||
'action': 'query', | 'action': 'query', | ||
Line 209: | Line 210: | ||
if rc is not None: | if rc is not None: | ||
allParams.append(rc) | allParams.append(rc) | ||
+ | allParams = allParams[:min(config['rcSubmitLimit'], len(allParams))] | ||
response = getNotifyResponse(multiUrlEncode(allParams)) | response = getNotifyResponse(multiUrlEncode(allParams)) | ||
updateLastRC(response) | updateLastRC(response) |
Revision as of 16:05, 23 January 2012
About this script
This is a Python script that:
- Fetches the recent changes log every 30 seconds
- Fetches the TF2 blog to look for updates
- Fetches the TF2 news feed to look for updates
- Sends the results back to the IRC bot (#tfwiki on irc.freenode.net), and the bot notifies everyone in the channel about it.
Running this script
- First way: Download Python and run the script as is.
- Download Python and install it if you haven't got it yet.
- Download the archive file and extract it somewhere. You should have a file named
rcNotify.py
(the Python script), a file namedrcConfig.py
(configuration file) and a folder calledwikitools
(Wiki API library). - Edit
rcConfig.py
to your liking. Read the comments next to each line. Do not use Notepad to edit the file, because it cannot read Unix-style linebreaks (\n
). Use WordPad or whatever code editor you have. - Open a command prompt or terminal window, and run Python with the script's path as argument.
- On Windows:
C:\Python26\python.exe C:\path\to\script.py
- On Windows: Double-click the file
- You can also rename it to .pyw and double-click it if you don't want the command prompt window to appear.
- On Unix (Mac/Linux):
python /path/to/script.py
- On Unix (Mac/Linux), you can also type simply
/path/to/script.py
if you set the executable bit on the script file beforehand (chmod +x /path/to/script.py
).
- On Windows:
- Second way (Windows only): Download the exe.
- Coming soon if there is demand.
Source
#!/usr/bin/env python # -*- coding: utf-8 -*- # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import time #, Dr. Freeman? import urllib, urllib2 # Series of tubes import re # Dem regexes import hashlib # Yummy import pprint # prettyPrint import wikitools # Wiki bindings import sys # Command-line arguments parsing import traceback # Print error stack traces # Config: from rcConfig import * # Constants: wiki = wikitools.wiki.Wiki(config['wikiUrl']) refreshRatePage = config['refreshRatePage'] notifyUrl = config['notifyUrl'] httpTimeout = config['httpTimeout'] tf2Blog = 'http://www.teamfortress.com/' tf2UpdatesFeed = 'http://store.steampowered.com/news/posts/?feed=steam_updates&appids=440' tf2schemaFeed = 'http://git.optf2.com/schema-tracking/atom/?h=master' # Globals: refreshRate = config['refreshRate'] # 3 minutes by default tf2BlogHash = None # Will be populated later tf2UpdateID = None # Will be populated later tf2SchemaID = None # Will be populated later lastRC = -1 # Will be populated later def u(s): if type(s) is type(u''): return s if type(s) is type(''): try: return unicode(s) except: try: return unicode(s.decode('utf8')) except: try: return unicode(s.decode('windows-1252')) except: return unicode(s, errors='ignore') try: return unicode(s) except: try: return u(str(s)) except: return s def urlEncode(s): return urllib2.quote(urllib2.unquote(eval(u(s).encode('utf8').__repr__().replace('\\x', '%')))) def getNotifyResponse(params): global notifyUrl, config, httpTimeout if type(params) in (type(''), type(u'')): params = u(params) else: params = u(urllib.urlencode(params)) if config['via'] is not None: params += '&via=' + u(urlEncode(config['via'])) response = urllib2.urlopen(notifyUrl, params, timeout=httpTimeout).read(-1) print 'Response:', response return response def updateRefreshRate(): global wiki, refreshRate, refreshRatePage, config if type(config['refreshRate']) is type(0): return try: refreshRate = int(wikitools.page.Page(wiki, refreshRatePage).getWikiText()) except: refreshRate = config['refreshRate'] if type(refreshRate) is not type(0): print 'Error while grabbing refresh rate; defaulting to 180s.' refreshRate = 180 def updateLastRC(last=None): global lastRC try: if last is None: lastRC = int(getNotifyResponse('requestrcid=1')) else: lastRC = int(last) except: lastRC = -1 def updateTf2Blog(): global tf2Blog, tf2BlogHash, httpTimeout newHash = hashlib.md5() for i in urllib2.urlopen(tf2Blog, timeout=httpTimeout): newHash.update(i) newHash = newHash.hexdigest().lower() print 'Old hash is', tf2BlogHash, '; New one is', newHash if tf2BlogHash is None or tf2BlogHash != newHash: tf2BlogHash = newHash print 'TF2 hash response:', getNotifyResponse('newblog=' + tf2BlogHash) def checkTf2Update(): global tf2UpdatesFeed, tf2UpdateID, httpTimeout idRegex = re.compile(r'<div[^<>]*class="posttitle"[^<>]*><a[^<>]*href="http://store.steampowered.com/news/(\d+)', re.IGNORECASE) try: webfeed = urllib2.urlopen(tf2UpdatesFeed, timeout=httpTimeout).read() idResult = int(idRegex.search(webfeed).group(1)) except: print 'Couldn\'t grab latest news ID' return if idResult != tf2UpdateID: tf2UpdateID = idResult getNotifyResponse('newnews=' + str(idResult)) def checkSchemaUpdate(): global tf2schemaFeed, tf2SchemaID, httpTimeout idRegex = re.compile(r'<id>\s*(.*?)\s*</id>', re.IGNORECASE) try: webfeed = urllib2.urlopen(tf2schemaFeed, timeout=httpTimeout).read() idResult = idRegex.search(webfeed).group(1) except: print 'Couldn\'t grab latest schema' return if idResult != tf2SchemaID: tf2SchemaID = idResult getNotifyResponse('newschema=' + str(idResult)) def reviewRC(rc): global lastRC if rc['rcid'] <= lastRC: return None pprint.PrettyPrinter(indent=4).pprint(rc) flag = '' if 'redirect' in rc: flag += 'R' if rc['type'] == u'new': flag += 'N' elif rc['type'] == u'log': flag += 'L' if 'minor' in rc: flag += 'm' if 'bot' in rc: flag += 'b' if not flag: flag = '-' params = { 'rcid': rc['rcid'], 'user': rc['user'], 'title': rc['title'], 'pageid': rc['pageid'], 'namespace': rc['ns'], 'newrevid': rc['revid'], 'oldrevid': rc['old_revid'], 'newsize': rc['newlen'], 'oldsize': rc['oldlen'], 'flags': flag, 'comment': rc['comment'], 'timestamp': rc['timestamp'] } optionalstuff = ('logtype', 'logid', 'logaction') for o in optionalstuff: if o in rc: params[o] = rc[o] return params def multiUrlEncode(allParams): s = [] c = 0 for p in allParams: for k in p.keys(): s.append(urlEncode(k) + u'_' + u(c) + u'=' + urlEncode(p[k])) c += 1 return u'&'.join(s) def checkForRCs(): global config rcs = wikitools.api.APIRequest(wiki, { 'action': 'query', 'list': 'recentchanges', 'rclimit': str(config['rcLimit']), 'rcprop': 'user|comment|title|ids|timestamp|sizes|redirect|flags|loginfo' }).query(querycontinue=False, timeout=15)['query']['recentchanges'] rcs.reverse() # Chronological order allParams = [] for rc in rcs: rc = reviewRC(rc) if rc is not None: allParams.append(rc) allParams = allParams[:min(config['rcSubmitLimit'], len(allParams))] response = getNotifyResponse(multiUrlEncode(allParams)) updateLastRC(response) def main(once=False): global refreshRate, config updateRefreshRate() updateLastRC() updateTf2Blog() print 'Started with last RCID =', lastRC print 'TF2 blog hash =', tf2BlogHash print 'Refresh rate =', refreshRate tf2BlogCountdown = 1 # Start at 1 to ensure the blog gets refreshed on the first run once = once or '--once' in sys.argv[1:] while True: try: try: print 'Checking for RCs.' checkForRCs() except: print 'Error while checking for RCs.' traceback.print_exc() try: tf2BlogCountdown -= 1 if tf2BlogCountdown <= 0: tf2BlogCountdown = config['blogRefresh'] print 'Checking for updates to TF2\'s blog.' updateTf2Blog() print 'Checking for news updates' checkTf2Update() print 'Checking for schema updates' checkSchemaUpdate() except KeyboardInterrupt: raise KeyboardInterrupt except: print 'Error while checking for updates to TF2\'s blog.' traceback.print_exc() if once: print 'Exitting after only one run.' break print 'Sleeping for', refreshRate, 'seconds.' time.sleep(refreshRate) except KeyboardInterrupt: print 'End.' break if __name__ == '__main__': main()