class Queue_Manager(threading.Thread):
def __init__(self, connection, delay=network['announce_delay']):
threading.Thread.__init__(self)
self.setDaemon(1)
self.connection = connection
self.delay = delay
self.event = threading.Event()
self.queue = []
def run(self):
while 1:
self.event.wait()
while self.queue:
(msg, target) = self.queue.pop(0)
try:
self.connection.privmsg(target, msg)
except irclib.ServerNotConnectedError as error:
print(error)
time.sleep(self.delay)
self.event.clear()
def send(self, msg, target):
self.queue.append((msg.strip(), target))
self.event.set()
class ReconnectStrategy(object):
min_interval = 60
max_interval = 300
def __init__(self, **attrs):
vars(self).update(attrs)
assert 0 <= self.min_interval <= self.max_interval
self._check_scheduled = False
self.attempt_count = itertools.count(1)
def run(self, bot):
self.bot = bot
if self._check_scheduled:
return
# calculate interval in seconds based on connection attempts
intvl = 2**next(self.attempt_count) - 1
# limit the max interval
intvl = min(intvl, self.max_interval)
# add jitter and truncate to integer seconds
intvl = int(intvl * random.random())
# limit the min interval
intvl = max(intvl, self.min_interval)
self.bot.reactor.scheduler.execute_after(intvl, self.check)
self._check_scheduled = True
def check(self):
self._check_scheduled = False
if not self.bot.connection.is_connected():
self.run(self.bot)
self.bot.jump_server()
class _feedie(SimpleIRCClient):
def __init__(self):
irclib.SimpleIRCClient.__init__(self)
self.start_time = time.time()
self.queue = Queue_Manager(self.connection)
self.reconnection_interval = 10
self.channels = utils.IrcDict()
self.recon = ReconnectStrategy(min_interval=self.reconnection_interval)
self.urlShorter = utils.URLShortener(feedie['shorten_service'])
self.lastRequest = {}
self.cachedFeeds = {}
def on_welcome(self, serv, ev):
if network['password']:
serv.privmsg("nickserv", "IDENTIFY {}".format(network['password']))
serv.privmsg("chanserv", "SET irc_auto_rejoin ON")
serv.privmsg("chanserv", "SET irc_join_delay 0")
for name in feeds[0]:
if not feeds[0][name]['enabled']:
continue
try:
serv.join(feeds[0][name]['channel'], key=feeds[0][name]['channel_key'])
except:
serv.join(feeds[0][name]['channel'])
try:
self.history_manager()
time.sleep(2)
self.queue.start()
self.initFeedRefreshTimers()
except (OSError, IOError) as error:
serv.disconnect()
print(error)
sys.exit(1)
def on_rss_entry(self, chan=None, text=''):
if chan:
self.queue.send(text, chan)
else:
for name in feeds[0]:
self.queue.send(text, feeds[0][name]['channel'])
def mircColor(self, s, fg=None, bg=None):
"""Returns s with the appropriate mIRC color codes applied."""
if fg is None and bg is None:
return s
elif bg is None:
fg = mircColors[str(fg)]
return '\x03%s%s\x03' % (fg.zfill(2), s)
elif fg is None:
bg = mircColors[str(bg)]
# According to the mirc color doc, a fg color MUST be specified if a
# background color is specified. So, we'll specify 00 (white) if the
# user doesn't specify one.
return '\x0300,%s%s\x03' % (bg.zfill(2), s)
else:
fg = mircColors[str(fg)]
bg = mircColors[str(bg)]
# No need to zfill fg because the comma delimits.
return '\x03%s,%s%s\x03' % (fg, bg.zfill(2), s)
def _getConverter(self, feed):
toText = utils.htmlToText
if 'encoding' in feed:
return lambda s: toText(s).strip().encode(feed['encoding'], 'replace')
else:
return lambda s: toText(s).strip()
def getHeadlines(self, feed):
headlines = []
conv = self._getConverter(feed)
for d in feed['items']:
if 'title' in d:
title = conv(d['title'])
link = d.get('link')
if link:
headlines.append((title, link))
else:
headlines.append((title, None))
return headlines
def getFeed(self, url):
def error(s):
return {'items': [{'title': s}]}
try:
#print('Downloading new feed from %u' % url)
results = feedparser.parse(url)
if 'bozo_exception' in results:
raise results['bozo_exception']
except sgmllib.SGMLParseError:
return error('Invalid (unparsable) RSS feed.')
except socket.timeout:
return error('Timeout downloading feed.')
except Exception, e:
# These seem mostly harmless. We'll need reports of a kind that isn't.
print('Allowing bozo_exception "%r" through.' % e)
if results.get('feed', {}):
self.cachedFeeds[url] = results
self.lastRequest[url] = time.time()
else:
print('Not caching results; feed is empty.')
try:
return self.cachedFeeds[url]
except KeyError:
# If there's a problem retrieving the feed, we should back off
# for a little bit before retrying so that there is time for
# the error to be resolved.
self.lastRequest[url] = time.time() - .5 * 180
return error('Unable to download feed.')
def initFeedRefreshTimers(self):
for feed in feeds:
for name in feed:
if not feed[name]['enabled']:
continue
try:
refresh_time = feed[name]['refresh_delay']
except KeyError:
refresh_time = network['default_refresh_delay']
threading.Timer(refresh_time, self.feed_refresh, (feed,name,refresh_time,)).start()
def feed_refresh(self, feed, name, refresh_time):
url = feed[name]['url']
try:
oldresults = self.cachedFeeds[url]
oldheadlines = self.getHeadlines(oldresults)
except KeyError:
oldheadlines = []
if not network['startup_announces'] and not oldheadlines:
newresults = self.getFeed(url)
threading.Timer(refresh_time, self.feed_refresh, (feed,name,refresh_time,)).start()
return
else:
newresults = self.getFeed(url)
newheadlines = self.getHeadlines(newresults)
if len(newheadlines) == 1:
s = newheadlines[0][0]
if s in ('Timeout downloading feed.', 'Unable to download feed.'):
print('%s %u', s, url)
threading.Timer(refresh_time, self.feed_refresh, (feed,name,refresh_time,)).start()
return
def canonize(headline):
return (tuple(headline[0].lower().split()), headline[1])
oldheadlines = set(map(canonize, oldheadlines))
for (i, headline) in enumerate(newheadlines):
if canonize(headline) in oldheadlines:
newheadlines[i] = None
newheadlines = filter(None, newheadlines) # Removes Nones.
if newheadlines:
for headline in newheadlines:
if headline[1]:
title = headline[0]
short_url = self.urlShorter.shorten_url(headline[1])
if not short_url or short_url == 'Error': short_url = url
feedName = self.mircColor(name, feed[name]['color'])
feedTitle = self.mircColor(title, 'blue')
try:
chan = feed[name]['channel']
except KeyError:
# send to all channels
chan = None
self.on_rss_entry(chan=chan, text='{0} {1} {2}'.format(feedName, feedTitle, self.underline(short_url)))
threading.Timer(refresh_time, self.feed_refresh, (feed,name,refresh_time,)).start()