Last active
August 29, 2015 14:27
-
-
Save liiight/d1e8ea71d9891dac4269 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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