Difference between revisions of "User:WindPower/RCNotify.py"
m (→Running this script: Check if blog refresh rate is None) |
m (→Source) |
||
| (2 intermediate revisions by the same user not shown) | |||
| Line 53: | Line 53: | ||
notifyUrl = config['notifyUrl'] | notifyUrl = config['notifyUrl'] | ||
httpTimeout = config['httpTimeout'] | httpTimeout = config['httpTimeout'] | ||
| − | tf2Blog = ' | + | tf2Blog = 'https://www.teamfortress.com/' |
| − | tf2UpdatesFeed = ' | + | tf2UpdatesFeed = 'https://store.steampowered.com/news/posts/?feed=steam_updates&appids=440' |
| − | tf2schemaFeed = ' | + | tf2schemaFeed = 'https://git.optf2.com/schema-tracking/atom/?h=teamfortress2' |
# Globals: | # Globals: | ||
refreshRate = config['refreshRate'] # 3 minutes by default | refreshRate = config['refreshRate'] # 3 minutes by default | ||
| Line 85: | Line 85: | ||
return s | return s | ||
def urlEncode(s): | def urlEncode(s): | ||
| − | + | encoded = urllib2.quote(urllib2.unquote(eval(u(s).encode('utf8').__repr__().replace('\\x', '%')))) | |
| + | encoded = encoded.replace('head%20', 'head_') | ||
| + | encoded = encoded.replace('%7B', '').replace('%7D', '') | ||
| + | return encoded | ||
def getNotifyResponse(params): | def getNotifyResponse(params): | ||
global notifyUrl, config, httpTimeout | global notifyUrl, config, httpTimeout | ||
| Line 94: | Line 97: | ||
if config['via'] is not None: | if config['via'] is not None: | ||
params += '&via=' + u(urlEncode(config['via'])) | params += '&via=' + u(urlEncode(config['via'])) | ||
| + | print('Hitting URL:' , notifyUrl, 'with params:', params) | ||
response = urllib2.urlopen(notifyUrl, params, timeout=httpTimeout).read(-1) | response = urllib2.urlopen(notifyUrl, params, timeout=httpTimeout).read(-1) | ||
print 'Response:', response | print 'Response:', response | ||
| Line 171: | Line 175: | ||
params = { | params = { | ||
'rcid': rc['rcid'], | 'rcid': rc['rcid'], | ||
| − | 'user': rc | + | 'user': rc.get('user', '<UNKNOWN>'), |
'title': rc['title'], | 'title': rc['title'], | ||
'pageid': rc['pageid'], | 'pageid': rc['pageid'], | ||
| Line 180: | Line 184: | ||
'oldsize': rc['oldlen'], | 'oldsize': rc['oldlen'], | ||
'flags': flag, | 'flags': flag, | ||
| − | 'comment': rc | + | 'comment': rc.get('comment', ''), |
'timestamp': rc['timestamp'] | 'timestamp': rc['timestamp'] | ||
} | } | ||
| Line 217: | Line 221: | ||
updateRefreshRate() | updateRefreshRate() | ||
updateLastRC() | updateLastRC() | ||
| − | updateTf2Blog() | + | #updateTf2Blog() |
print 'Started with last RCID =', lastRC | print 'Started with last RCID =', lastRC | ||
print 'TF2 blog hash =', tf2BlogHash | print 'TF2 blog hash =', tf2BlogHash | ||
| Line 231: | Line 235: | ||
print 'Error while checking for RCs.' | print 'Error while checking for RCs.' | ||
traceback.print_exc() | traceback.print_exc() | ||
| − | try: | + | if config['blogRefresh'] is not None: |
| − | + | 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: | if once: | ||
print 'Exitting after only one run.' | print 'Exitting after only one run.' | ||
Latest revision as of 05:21, 30 March 2025
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.pyto 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.pyif 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 = 'https://www.teamfortress.com/'
tf2UpdatesFeed = 'https://store.steampowered.com/news/posts/?feed=steam_updates&appids=440'
tf2schemaFeed = 'https://git.optf2.com/schema-tracking/atom/?h=teamfortress2'
# 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):
encoded = urllib2.quote(urllib2.unquote(eval(u(s).encode('utf8').__repr__().replace('\\x', '%'))))
encoded = encoded.replace('head%20', 'head_')
encoded = encoded.replace('%7B', '').replace('%7D', '')
return encoded
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']))
print('Hitting URL:' , notifyUrl, 'with params:', params)
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.get('user', '<UNKNOWN>'),
'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.get('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()
if config['blogRefresh'] is not None:
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()