Skip to content

Instantly share code, notes, and snippets.

Last active Aug 29, 2015
What would you like to do?
from __future__ import unicode_literals, division, absolute_import
import re
import socket
import threading
from flexget.entry import Entry
import xml.etree.ElementTree as ET
import logging
from flexget.config_schema import register_config_key, format_checker
from flexget.event import event
version = '0.0.2'
log = logging.getLogger('irc')
tracker_file_base_path = 'C:\Flexget\\flexget\\utils\\trackers\\'
ext = '.tracker'
Parses IRC feed.
# TODO hold all tracker names in a list for matching
schema = {
'type': 'object',
'properties': {
'tracker': {'type': 'string'},
'port': {'type': 'integer', 'default': 6667},
'timeout': {'type': 'integer', 'default': 180},
'nick': {'type': 'string'},
'join_channels': {'type': 'boolean', 'default': False},
'ident': {'type': 'string', 'default': 'Flexget'},
'realname': {'type': 'string', 'default': 'Flexget'},
'irckey': {'type': 'string'},
'rsskey': {'type': 'string'},
'nickserv': {'type': 'string'},
'message_recp': {'type': 'string'},
'message_body': {'type': 'string'}
'required': ['tracker', 'nick'],
'additionalProperties': False
class BackgroundIRC(object):
parser_info = {
'parser_var_list': [],
'torrent_url_var_list': [],
'torrent_url_string_list': []}
def __init__(self, manager):
config = manager.config['tasks']['bla3']['irc']
# TODO Find a way to dynamically get config of plugin
self.tracker = config['tracker']
self.port = config['port']
self.timeout = config['timeout']
self.nick = config['nick']
self.join_channels = config['join_channels']
# settings['ident'] = config['ident']
# settings['realname'] = config['realname']
# TODO get defaults
self.ident = config['nick']
self.realname = config['nick']
self.irckey = config['irckey']
self.rsskey = config['rsskey']
self.nickserv = config['nickserv']
self.message_recp = config['message_recp']
self.message_body = config['message_body']
# TODO Get all static setting here
# TODO support JINJA2 tags
thread = threading.Thread(, args=())
thread.daemon = True # Daemonize thread
def _parse_tracker_file(self):
tracker_file = ET.parse(tracker_file_base_path + self.tracker + ext).getroot()
except IOError as e:
log.error('Could not read tracker file. %s' % e)
# Parses connection and server settings
for node in tracker_file.findall('servers/server'):
self.network_names = node.attrib['network'].split(',')
self.server_list = node.attrib['serverNames'].split(',')
self.channel_list = node.attrib['channelNames'].split(',')
self.announcer_names = node.attrib['announcerNames'].split(',')
# Adds '.' to announcer name for regex matching in channel. This will disregard any mode bot has
for i in range(len(self.announcer_names)):
self.announcer_names[i] = '.' + self.announcer_names[i]
# Parses regex pattern(s)
# TODO support for multiple patterns
for i in tracker_file.iter('linepatterns'):
for l in i.iter('regex'):
self.parser_regex = l.attrib['value']
for d in i.iter('var'):
# Parses pattern match variables
# TODO support more stuff...
for i in tracker_file.iter('linematched'):
for k in i.iter('string'):
for d in i.iter('var'):
for d in i.iter('varenc'):
def _send_message(self, recipient, text):
# TODO make this more generic, only one function should output data
:param text: Body of message
:param recipient: Recipient of message
:return: None
message = ('PRIVMSG %s %s \r\n' % (recipient, text))
log.debug('Sending mesage: %s' % message)
def _connection_message(self, command, ident=None, realname=None, text=None):
if command == 'USER':
message = ('USER ' + ident + ' Flexget Flexget ' + realname + '\r\n')
log.debug('Sending USER command: ' + message)
message = ('%s %s \n\r' % (command, text))
log.debug('Sending %s command: %s' % (command, message))
def _send_pong(self, text):
self._connection_message(command='PONG', text=text)
log.debug('Sent PONG %s ' % text)
def _connect(self):
not_connected = True
server_number = 0
global connection
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while not_connected and server_number < len(self.server_list):
# TODO handle all of this section better: 3 retries per each server and handle connection immediately
try:'Trying to connect to server: %s using port:%s' %
(self.server_list[server_number], self.port))
connection.connect((self.server_list[server_number], self.port))'Successfully connected to server: %s.' % self.server_list[server_number])
not_connected = False
except socket.error as e:
'Could not connect to server %s:%s. Error is: %s' %
(self.server_list[server_number], self.port, e))
server_number += 1
# TODO assumes connection is successful, need to verify that
log.debug('Registering nick and user data.')
self._connection_message(command='NICK', text=self.nick)
self._connection_message(command='USER', ident=self.ident, realname=self.realname)
def _announcer_match(self, announcer_line):
matches = {}
announcer_string = self._clean_line(announcer_line)
m = re.match(self.parser_regex, announcer_string)
# TODO support multiple regex per tracker
for i in range(1, len(m.groups())+1):
matches[self.parser_info['parser_var_list'][i-1]] =
except AttributeError as e:
log.error('Could not parse the line: %s. %s' % (announcer_string, e))
matches['url'] = self._url_builder(matches)
return matches
def _url_builder(self, matches):
# TODO Redo this whole function better... shaky at best
torrent_url = ''
string_list = self.parser_info['torrent_url_string_list']
var_list = self.parser_info['torrent_url_var_list']
for i in range(len(string_list)-1):
torrent_url += string_list[i]
if var_list[i+1] in matches:
torrent_url += matches[var_list[i+1]]
elif var_list[i+1] == 'rsskey':
torrent_url += self.rsskey
torrent_url += string_list[-1]
return torrent_url
def _clean_line(self, line):
clean_line = ''
line[3] = line[3].lstrip(':')
for word in range(3, len(line)):
line[word] = "%r" % line[word]
clean_word = re.sub(r'\\', '', line[word])
clean_word = re.sub(r'x\d+', '', clean_word)
clean_word = re.sub(r',\d+', '', clean_word)
clean_word = re.sub(r'^u', '', clean_word)
clean_word = clean_word.lstrip(r"\'")
clean_word = clean_word.lstrip(r'\"')
clean_word = clean_word.rstrip(r"\'")
clean_word = clean_word.rstrip(r'\"')
if clean_word == '':
clean_line += clean_word + ' '
return clean_line
def run(self, manager):
# TODO Check parsing status
# TODO verify succesfull connection
motd_received = False
not_entry = True
while not_entry: # TODO think of a better way to verify that connection is alive
raw_data = connection.recv(512).strip('\n\r')
raw_data_lines = str(raw_data).split('\n')
for line in raw_data_lines:
line = line.split()
if re.match('PING', line[0]):
elif raw_data.find('End of /MOTD command') != -1 and not motd_received:
if self.nickserv:
self._send_message(r'NICKSERV IDENTIFY', self.nickserv)
if self.message_recp and self.message_body:
self._send_message(self.message_recp, self.message_body)
if self.join_channels:
for channel in self.channel_list:
self._connection_message('JOIN', channel)
motd_received = True
for word in line:
for announcer in self.announcer_names:
if re.match(announcer, word):
entries = []
matches = self._announcer_match(line)
entry = Entry(url=matches['url'],
if entry.isvalid():
except KeyError as e:
log.error('Invalid entry created: %s. Error: %s' % (entry, e))
if manager.options.test:"Test Mode:")' Name is: %s ' % entry['title'])' URL is: %s ' % entry['url'])
manager.execute(options={'dump_entries': True, 'inject': entries, 'tasks': ['bla4']}, priority=1)
not_entry = False
def irc_start(manager):
irc = BackgroundIRC(manager)
def register_plugin():
register_config_key('irc', schema)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment