Difference between revisions of "User:WindPower/RCNotify.py"

From Team Fortress Wiki
Jump to: navigation, search
(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/rcNotify_0.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).
+
** [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 named rcConfig.py (configuration file) and a folder called wikitools (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).
  • 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()