Skip to content

Instantly share code, notes, and snippets.

@liiight
Last active August 29, 2015 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save liiight/d1e8ea71d9891dac4269 to your computer and use it in GitHub Desktop.
Save liiight/d1e8ea71d9891dac4269 to your computer and use it in GitHub Desktop.
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(target=self.run(manager), args=())
thread.daemon = True # Daemonize thread
thread.start()
def _parse_tracker_file(self):
try:
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)
raise
# 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'):
self.parser_info['parser_var_list'].append(d.attrib['name'])
# Parses pattern match variables
# TODO support more stuff...
for i in tracker_file.iter('linematched'):
for k in i.iter('string'):
self.parser_info['torrent_url_string_list'].append(k.attrib['value'])
for d in i.iter('var'):
self.parser_info['torrent_url_var_list'].append(d.attrib['name'])
for d in i.iter('varenc'):
self.parser_info['torrent_url_var_list'].append(d.attrib['name'])
def _send_message(self, recipient, text):
# TODO make this more generic, only one function should output data
"""
Sends IRC PRIVMSG
:param text: Body of message
:param recipient: Recipient of message
:return: None
"""
message = ('PRIVMSG %s %s \r\n' % (recipient, text))
connection.send(message)
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')
connection.send(message)
log.debug('Sending USER command: ' + message)
else:
message = ('%s %s \n\r' % (command, text))
connection.send(message)
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)
connection.settimeout(self.timeout)
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:
log.info('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))
log.info('Successfully connected to server: %s.' % self.server_list[server_number])
not_connected = False
except socket.error as e:
log.error(
'Could not connect to server %s:%s. Error is: %s' %
(self.server_list[server_number], self.port, e))
server_number += 1
continue
# 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
try:
for i in range(1, len(m.groups())+1):
matches[self.parser_info['parser_var_list'][i-1]] = m.group(i)
except AttributeError as e:
log.error('Could not parse the line: %s. %s' % (announcer_string, e))
return
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 == '':
continue
else:
clean_line += clean_word + ' '
return clean_line
def run(self, manager):
self._parse_tracker_file()
# TODO Check parsing status
self._connect()
# 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()
log.debug(line)
if re.match('PING', line[0]):
self._send_pong(line[1])
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'],
title=matches['torrentName'])
try:
if entry.isvalid():
entries.append(entry)
except KeyError as e:
log.error('Invalid entry created: %s. Error: %s' % (entry, e))
if manager.options.test:
log.info("Test Mode:")
log.info(' Name is: %s ' % entry['title'])
log.info(' URL is: %s ' % entry['url'])
manager.execute(options={'dump_entries': True, 'inject': entries, 'tasks': ['bla4']}, priority=1)
not_entry = False
@event('manager.daemon.started')
def irc_start(manager):
irc = BackgroundIRC(manager)
@event('config.register')
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