class TyggBot: """ Main class for the twitch bot """ version = '0.9.5.2' date_fmt = '%H:%M' #date_fmt = '%A %B ' commands = {} filters = [] settings = {} emotes = {} twitchapi = False silent = False dev = False """ Singleton instance of TyggBot, one instance of the script should never have two active classes.""" instance = None default_settings = { 'broadcaster': 'test_broadcaster', 'ban_ascii': True, 'ban_msg_length': True, } def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--config', '-c', default='config.ini', help='Specify which config file to use (default: config.ini)') parser.add_argument('--silent', action='count', help='Decides whether the bot should be silent or not') # TODO: Add a log level argument. return parser.parse_args() def init_twitter(self): try: self.twitter_auth = tweepy.OAuthHandler(self.config['twitter']['consumer_key'], self.config['twitter']['consumer_secret']) self.twitter_auth.set_access_token(self.config['twitter']['access_token'], self.config['twitter']['access_token_secret']) self.twitter = tweepy.API(self.twitter_auth) if self.use_twitter_stream: self.connect_to_twitter_stream() except: log.exception('Twitter authentication failed.') self.twitter = False def connect_to_twitter_stream(self): try: class MyStreamListener(tweepy.StreamListener): relevant_users = [ 'tyggbar', 'forsensc2', 'pajtest', 'rubarthasdf' ] def on_status(self, tweet): if tweet.user.screen_name.lower() in self.relevant_users: if not tweet.text.startswith('RT ') and tweet.in_reply_to_screen_name is None: TyggBot.instance.say('Volcania New tweet from {0}: {1}'.format(tweet.user.screen_name, tweet.text.replace("\n", " "))) def on_error(self, status): log.warning('Unhandled in twitter stream: {0}'.format(status)) if not self.twitter_stream: listener = MyStreamListener() self.twitter_stream = tweepy.Stream(self.twitter_auth, listener, retry_420=3*60, daemonize_thread=True) self.twitter_stream.userstream(_with='followings', replies='all', async=True) except: log.exception('Exception caught while trying to connect to the twitter stream') def load_default_phrases(self): default_phrases = { 'welcome': False, 'quit': False, 'nl': '{username} has typed {num_lines} messages in this channel!', 'nl_0': '{username} has not typed any messages in this channel BibleThump', 'new_sub': 'Sub hype! {username} just subscribed PogChamp', 'resub': 'Resub hype! {username} just subscribed, {num_months} months in a row PogChamp <3 PogChamp', } if 'phrases' in self.config: self.phrases = {} for phrase_key, phrase_value in self.config['phrases'].items(): log.debug('Including from config {0}: {1}'.format(phrase_key, phrase_value)) if len(phrase_value.strip()) <= 0: self.phrases[phrase_key] = False else: self.phrases[phrase_key] = phrase_value for phrase_key, phrase_value in default_phrases.items(): if phrase_key not in self.phrases: log.debug('Overriding from default {0}: {1}'.format(phrase_key, phrase_value)) self.phrases[phrase_key] = phrase_value else: self.phrases = default_phrases def __init__(self, config, args): self.config = config self.sqlconn = pymysql.connect(unix_socket=config['sql']['unix_socket'], user=config['sql']['user'], passwd=config['sql']['passwd'], db=config['sql']['db'], charset='utf8') self.sqlconn.autocommit(True) update_database(self.sqlconn) self.load_default_phrases() self.reactor = irc.client.Reactor() self.connection = self.reactor.server() self.twitchapi = TwitchAPI(type='api') self.reactor.add_global_handler('all_events', self._dispatcher, -10) if 'wolfram' in config['main']: Dispatch.wolfram = wolframalpha.Client(config['main']['wolfram']) else: wolfram = None self.whisper_conn = None TyggBot.instance = self self.base_path = os.path.dirname(os.path.realpath(__file__)) self.data = {} self.data_cb = {} self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['time_norway'] = self.c_time_norway self.data_cb['bot_uptime'] = self.c_uptime self.data_cb['time_since_latest_deck'] = self.c_time_since_latest_deck self.ignores = [] self.start_time = datetime.now() if 'streamer' in config['main']: self.streamer = config['main']['streamer'] self.channel = '#' + self.streamer elif 'target' in config['main']: self.channel = config['main']['target'] self.streamer = self.channel[1:] self.kvi = KVIData(self.sqlconn) self.tbm = TBMath() self.last_sync = time.time() self.users = UserManager(self.sqlconn) if 'flags' in config: self.silent = True if 'silent' in config['flags'] and config['flags']['silent'] == '1' else self.silent self.dev = True if 'dev' in config['flags'] and config['flags']['dev'] == '1' else self.dev self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') self.sync_from() self.nickname = config['main']['nickname'] self.password = config['main']['password'] self.reconnection_interval = 5 self.load_all() self.whisper_conn = WhisperConn(self.streamer, self.nickname, self.password, self.reactor) self.whisper_conn.connect() self.num_commands_sent = 0 self.connection.execute_every(30, self.reset_command_throttle) self.twitter_stream = False if 'twitter' in config: self.use_twitter_stream = 'streaming' in config['twitter'] and config['twitter']['streaming'] == '1' self.init_twitter() else: self.twitter = None self.connection.execute_every(1, self.shift_emotes) self.ws_clients = [] if 'websocket' in config and config['websocket']['enabled'] == '1': self.init_websocket_server() self.execute_every(1, self.refresh_emote_data) self.urls_to_check = queue.Queue() self.connection.execute_every(1, self.check_urls) def refresh_emote_data(self): if len(self.ws_clients) > 0: emote_data = {} for emote in self.emotes: emote_data[emote.code] = { 'code': emote.code, 'pm': emote.pm, 'tm': emote.tm, 'count': emote.count, } payload = json.dumps(emote_data, separators=(',',':')).encode('utf8') for client in self.ws_clients: client.sendMessage(payload, False) def init_websocket_server(self): import twisted from twisted.internet import reactor twisted.python.log.startLogging(sys.stdout) from autobahn.twisted.websocket import WebSocketServerFactory, \ WebSocketServerProtocol class MyServerProtocol(WebSocketServerProtocol): def onConnect(self, request): log.info('Client connecting: {0}'.format(request.peer)) def onOpen(self): log.info('WebSocket connection open. {0}'.format(self)) TyggBot.instance.ws_clients.append(self) def onMessage(self, payload, isBinary): if isBinary: log.info('Binary message received: {0} bytes'.format(len(payload))) else: TyggBot.instance.me('Recieved message: {0}'.format(payload.decode('utf8'))) log.info('Text message received: {0}'.format(payload.decode('utf8'))) def onClose(self, wasClean, code, reason): log.info('WebSocket connection closed: {0}'.format(reason)) TyggBot.instance.ws_clients.remove(self) factory = WebSocketServerFactory() factory.protocol = MyServerProtocol def reactor_run(reactor, factory, port): log.info(reactor) log.info(factory) log.info(port) reactor.listenTCP(port, factory) reactor.run(installSignalHandlers=0) reactor_thread = threading.Thread(target=reactor_run, args=(reactor, factory, int(self.config['websocket']['port']))) reactor_thread.daemon = True reactor_thread.start() self.ws_factory = factory def shift_emotes(self): for emote in self.emotes: emote.shift() def reset_command_throttle(self): self.num_commands_sent = 0 def _dispatcher(self, connection, event): do_nothing = lambda c, e: None method = getattr(self, "on_" + event.type, do_nothing) method(connection, event) def start(self): """Start the IRC client.""" self.reactor.process_forever() def get_kvi_value(self, key, extra={}): return self.kvi.get(key) def get_last_tweet(self, key, extra={}): if self.twitter: try: public_tweets = self.twitter.user_timeline(key) for tweet in public_tweets: if not tweet.text.startswith('RT ') and tweet.in_reply_to_screen_name is None: return '{0} ({1} ago)'.format(tweet.text.replace("\n", " "), time_since(datetime.now().timestamp(), tweet.created_at.timestamp(), format='short')) except Exception as e: log.error('Exception caught {0}'.format(e)) return 'FeelsBadMan' else: return 'Twitter not set up FeelsBadMan' return 'FeelsBadMan' def get_emote_pm(self, key, extra={}): for emote in self.emotes: if key == emote.code: return emote.pm return 0 def get_emote_tm(self, key, extra={}): for emote in self.emotes: if key == emote.code: return emote.tm return 0 def get_emote_count(self, key, extra={}): for emote in self.emotes: if key == emote.code: return emote.count return 0 def get_emote_pm_record(self, key, extra={}): for emote in self.emotes: if key == emote.code: return emote.pm_record return 0 def get_emote_tm_record(self, key, extra={}): for emote in self.emotes: if key == emote.code: return emote.tm_record return 0 def get_value(self, key, extra={}): if key in extra: return extra[key] elif key in self.data: return self.data[key] elif key in self.data_cb: return self.data_cb[key]() elif key in self.settings: return self.settings[key] log.warning('Unknown key passed to get_value: {0}'.format(key)) return '???' def get_cursor(self): self.sqlconn.ping() return self.sqlconn.cursor() def get_dictcursor(self): self.sqlconn.ping() return self.sqlconn.cursor(pymysql.cursors.DictCursor) def reload(self): self.sync_to() self.load_all() def privmsg(self, message, priority=False, channel=None): # Non-prioritized messages are allowed 50% of the message limit if (not priority and self.num_commands_sent > TMI.message_limit/2) or (priority and self.num_commands_sent > TMI.message_limit): log.error('Skipping this say, because we are sending too many messages.') return False try: if channel is None: channel = self.channel self.connection.privmsg(channel, message) self.num_commands_sent += 1 except Exception as e: log.error('Exception caught while sending privmsg: {0}'.format(e)) def c_time_norway(self): return datetime.now(timezone('Europe/Oslo')).strftime(TyggBot.date_fmt) def c_uptime(self): return time_since(datetime.now().timestamp(), self.start_time.timestamp()) def c_stream_status(self): if self.kvi.get('stream_status') == 1: return 'online' else: return 'offline' def c_status_length(self): stream_status = self.kvi.get('stream_status') if stream_status == 1: return time_since(time.time(), self.kvi.get('last_offline')) else: return time_since(time.time(), self.kvi.get('last_online')) def c_time_since_latest_deck(self): return time_since(time.time(), self.kvi.get('latest_deck_time')) def _ban(self, username): self.privmsg('.ban {0}'.format(username), True) def execute_at(self, at, function, arguments=()): self.reactor.execute_at(at, function, arguments) def execute_delayed(self, delay, function, arguments=()): self.reactor.execute_delayed(delay, function, arguments) def execute_every(self, period, function, arguments=()): self.reactor.execute_every(period, function, arguments) def ban(self, username): self._timeout(username, 30) self.execute_delayed(1, self._ban, (username, )) def unban(self, username): self.privmsg('.unban {0}'.format(username), True) def _timeout(self, username, duration): self.privmsg('.timeout {0} {1}'.format(username, duration), True) def timeout(self, username, duration): self._timeout(username, duration) self.execute_delayed(1, self._timeout, (username, duration)) def whisper(self, username, message): if self.whisper_conn: log.debug('Sending whisper {0} to {1}'.format(message, username)) self.whisper_conn.whisper(username, message) else: log.debug('No whisper conn set up.') def say(self, message, force=False): if force or not self.silent: message = message.strip() if len(message) >= 1: if (message[0] == '.' or message[0] == '/') and not message[:3] == '.me': log.warning('Message we attempted to send started with . or /, skipping.') return log.info('Sending message: {0}'.format(message)) self.privmsg(message[:400]) else: log.warning('Message too short, skipping...') def me(self, message, force=False): if force or not self.silent: message = message.strip() if len(message) >= 1: if message[0] == '.' or message[0] == '/': log.warning('Message we attempted to send started with . or /, skipping.') return log.info('Sending message: {0}'.format(message)) self.privmsg('.me ' + message[:400]) else: log.warning('Message too short, skipping...') def sync_to(self): self.sqlconn.ping() cursor = self.sqlconn.cursor() log.debug('Syncing data from TyggBot to the database...') self.users.sync() for trigger, command in self.commands.items(): if not command.synced: command.sync(cursor) command.synced = True for filter in self.filters: if not filter.synced: filter.sync(cursor) filter.synced = True for emote in self.emotes: emote.sync(cursor) cursor.close() def sync_from(self): pass def load_all(self): self._load_commands() self._load_filters() self._load_settings() self._load_ignores() self._load_emotes() def _load_commands(self): cursor = self.sqlconn.cursor(pymysql.cursors.DictCursor) from command import Command cursor.execute('SELECT * FROM `tb_commands`') self.commands = {} self.commands['reload'] = Command.admin_command(self.reload) self.commands['quit'] = Command.admin_command(self.quit) self.commands['ignore'] = Command.admin_command(Dispatch.ignore, type='func') self.commands['unignore'] = Command.admin_command(Dispatch.unignore, type='func') self.commands['add'] = Command() self.commands['add'].load_from_db({ 'id': -1, 'level': 500, 'action': '{ "type":"multi", "default":"nothing", "args": [ { "level":500, "command":"banphrase", "action": { "type":"func", "cb":"add_banphrase" } }, { "level":500, "command":"win", "action": { "type":"func", "cb":"add_win" } }, { "level":500, "command":"command", "action": { "type":"func", "cb":"add_command" } }, { "level":2000, "command":"funccommand", "action": { "type":"func", "cb":"add_funccommand" } }, { "level":500, "command":"nothing", "action": { "type":"say", "message":"" } } ] }', 'do_sync': False, 'delay_all': 5, 'delay_user': 15, 'enabled': True, 'num_uses': 0, 'extra_args': None, }) self.commands['remove'] = Command() self.commands['remove'].load_from_db({ 'id': -1, 'level': 500, 'action': '{ "type":"multi", "default":"nothing", "args": [ { "level":500, "command":"banphrase", "action": { "type":"func", "cb":"remove_banphrase" } }, { "level":500, "command":"win", "action": { "type":"func", "cb":"remove_win" } }, { "level":500, "command":"command", "action": { "type":"func", "cb":"remove_command" } }, { "level":500, "command":"nothing", "action": { "type":"say", "message":"" } } ] }', 'do_sync': False, 'delay_all': 5, 'delay_user': 15, 'enabled': True, 'num_uses': 0, 'extra_args': None, }) self.commands['rem'] = self.commands['remove'] self.commands['del'] = self.commands['remove'] self.commands['delete'] = self.commands['remove'] self.commands['debug'] = Command() self.commands['debug'].load_from_db({ 'id': -1, 'level': 250, 'action': '{ "type":"multi", "default":"nothing", "args": [ { "level":250, "command":"command", "action": { "type":"func", "cb":"debug_command" } }, { "level":250, "command":"user", "action": { "type":"func", "cb":"debug_user" } }, { "level":250, "command":"nothing", "action": { "type":"say", "message":"" } } ] }', 'do_sync': False, 'delay_all': 5, 'delay_user': 15, 'enabled': True, 'num_uses': 0, 'extra_args': None, }) self.commands['level'] = Command.admin_command(Dispatch.level, type='func') self.commands['eval'] = Command.admin_command(Dispatch.eval, type='func', level=2000) num_commands = 0 num_aliases = 0 for row in cursor: try: cmd = Command() cmd.load_from_db(row) if cmd.is_enabled(): for alias in row['command'].split('|'): if alias not in self.commands: self.commands[alias] = cmd num_aliases += 1 else: log.error('Command !{0} is already in use'.format(alias)) num_commands += 1 except Exception as e: log.error('Exception caught when loading command: {0}'.format(e)) continue log.debug('Loaded {0} commands ({1} aliases)'.format(num_commands, num_aliases)) cursor.close() def _load_filters(self): cursor = self.sqlconn.cursor(pymysql.cursors.DictCursor) cursor.execute('SELECT * FROM `tb_filters`') self.filters = [] num_filters = 0 for row in cursor: try: filter = Filter(row) if filter.is_enabled(): self.filters.append(filter) num_filters += 1 except Exception as e: log.error('Exception caught when loading filter: {0}'.format(e)) continue log.debug('Loaded {0} filters'.format(num_filters)) cursor.close() def _load_settings(self): cursor = self.sqlconn.cursor(pymysql.cursors.DictCursor) cursor.execute('SELECT * FROM `tb_settings`') self.settings = {} for row in cursor: self.settings[row['setting']] = Setting.parse(row['type'], row['value']) if self.settings[row['setting']] is None: log.error('ERROR LOADING SETTING {0}'.format(row['setting'])) for setting in self.default_settings: if setting not in self.settings: self.settings[setting] = self.default_settings[setting] cursor.close() def _load_ignores(self): cursor = self.sqlconn.cursor(pymysql.cursors.DictCursor) cursor.execute('SELECT * FROM `tb_ignores`') self.ignores = [] for row in cursor: self.ignores.append(row['username']) cursor.close() def _load_emotes(self): cursor = self.sqlconn.cursor(pymysql.cursors.DictCursor) cursor.execute('SELECT * FROM `tb_emote`') self.emotes = [] for row in cursor: self.emotes.append(Emote.load_from_row(row)) cursor.close() def on_welcome(self, chatconn, event): if chatconn == self.connection: log.debug('Connected to IRC server.') if irc.client.is_channel(self.channel): chatconn.join(self.channel) if self.phrases['welcome']: phrase_data = { 'nickname': self.nickname, 'version': self.version, } try: self.say(self.phrases['welcome'].format(**phrase_data)) except Exception as e: log.error(e) elif chatconn == self.whisper_conn: log.debug('Connected to Whisper server.') def _connected_checker(self): if not self.connection.is_connected(): self.connection.execute_delayed(self.reconnection_interval, self._connected_checker) self.connect() def connect(self): log.debug('Fetching random IRC server...') data = self.twitchapi.get(['channels', self.streamer, 'chat_properties']) if data and len(data['chat_servers']) > 0: server = random.choice(data['chat_servers']) ip, port = server.split(':') port = int(port) log.debug('Fetched {0}:{1}'.format(ip, port)) try: irc.client.SimpleIRCClient.connect(self, ip, port, self.nickname, self.password, self.nickname) self.connection.cap('REQ', 'twitch.tv/membership') self.connection.cap('REQ', 'twitch.tv/commands') self.connection.cap('REQ', 'twitch.tv/tags') return True except irc.client.ServerConnectionError: pass log.debug('Connecting to IRC server...') self.connection.execute_delayed(self.reconnection_interval, self._connected_checker) return False def on_disconnect(self, chatconn, event): if chatconn == self.connection: log.debug('Disconnected from IRC server') self.sync_to() self.connection.execute_delayed(self.reconnection_interval, self._connected_checker) elif chatconn == self.whisper_conn: log.debug('Disconnecting from Whisper server') self.whisper_conn.execute_delayed(self.whisper_conn.reconnection_interval, self.whisper_conn._connected_checker) def check_msg_content(self, source, msg_raw, event): for f in self.filters: if f.type == 'regex': m = f.search(source, msg_lower) if m: log.debug('Matched regex filter \'{0}\''.format(f.name)) f.run(self, source, msg_raw, event, {'match':m}) return True elif f.type == 'banphrase': if f.filter in msg_lower: log.debug('Matched banphrase filter \'{0}\''.format(f.name)) f.run(self, source, msg_raw, event) return True return False # message was ok def check_link_content(self, source, content, event): return self.check_msg_content(source, content, event) # same check as for normal chat messages, probably should be changed def check_url(self, _url): try: r = requests.get(_url['url']) except: return self.check_url_content(_url['source'], r.text, _url['event']) return def _check_urls(self): while True: try: _url = self.urls_to_check.get(False) except queue.Empty: return self.check_url(_url) def check_urls(self): t = threading.Thread(self._check_urls) t.start() def parse_message(self, msg_raw, source=None, event=None, pretend=False, force=False, tags={}): msg_lower = msg_raw.lower() for tag in tags: if tag['key'] == 'subscriber': if source.subscriber and tag['value'] == '0': source.subscriber = False source.needs_sync = True elif not source.subscriber and tag['value'] == '1': source.subscriber = True source.needs_sync = True for emote in self.emotes: num = len(emote.regex.findall(msg_raw)) if num > 0: emote.add(num) if source is None and not event: log.error('No nick or event passed to parse_message') return False log.debug('{0}: {1}'.format(source.username, msg_raw)) if not force: if source.level < 500: if self.check_msg_content(source, msg_raw, event): return # If we've matched a filter, we should not have to run a command. regex = r'((http:\/\/)|\b)(\w|\.)*\.(((aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-zA-Z]{2})\/\S*)|(aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-zA-Z]{2}))' #probably shit regex, but kind of works urls = re.finditer(regex, msg_raw) for i in urls: url = i.group(0) if not (url.startswith('http://') or url.startswith('https://')): url = 'http://' + url _url['url'] = url _url['source'] = source _url['event'] = event self.urls_to_check.put(_url) # TODO: Change to if source.ignored if source.username in self.ignores: return if msg_lower[:1] == '!': msg_lower_parts = msg_lower.split(' ') command = msg_lower_parts[0][1:] msg_raw_parts = msg_raw.split(' ') extra_msg = ' '.join(msg_raw_parts[1:]) if len(msg_raw_parts) > 1 else None if command in self.commands: if source.level >= self.commands[command].level: self.commands[command].run(self, source, extra_msg, event) return source.num_lines += 1 source.needs_sync = True def on_whisper(self, chatconn, event): # We use .lower() in case twitch ever starts sending non-lowercased usernames source = self.users[event.source.user.lower()] if source.level >= 500: # Only moderators and above can send commands through whispers self.parse_message(event.arguments[0], source, event) def on_action(self, chatconn, event): self.on_pubmsg(chatconn, event) def on_pubmsg(self, chatconn, event): if event.source.user == self.nickname: return False # We use .lower() in case twitch ever starts sending non-lowercased usernames source = self.users[event.source.user.lower()] cur_time = time.time() msg = event.arguments[0] msg_len = len(msg) if msg_len > 70: non_alnum = sum(not c.isalnum() for c in msg) ratio = non_alnum/msg_len log.debug('Ascii ratio: {0}'.format(ratio)) if self.settings['ban_ascii']: if (msg_len > 140 and ratio > 0.8) or ratio > 0.91: log.debug('Timeouting {0} because of a high ascii ratio ({1}). Message length: {2}'.format(source.username, ratio, msg_len)) self.timeout(source.username, 120) self.whisper(source.username, 'You have been timed out for 120 seconds because your message contained too many ascii characters.') return if self.settings['ban_msg_length']: if msg_len > 450: log.debug('Timeouting {0} because of a message length: {1}'.format(source.username, msg_len)) self.timeout(source.username, 20) self.whisper(source.username, 'You have been timed out for 20 seconds because your message was too long.') return if cur_time - self.last_sync >= 60: self.sync_to() self.last_sync = cur_time self.parse_message(event.arguments[0], source, event, tags=event.tags) def quit(self): self.sync_to() if self.phrases['quit']: phrase_data = { 'nickname': self.nickname, 'version': self.version, } try: self.say(self.phrases['quit'].format(**phrase_data)) except Exception as e: log.error(e) if self.twitter_stream: self.twitter_stream.disconnect() self.connection.quit('bye') if self.whisper_conn: self.whisper_conn.connection.quit('bye') sys.exit(0)
def __init__(self, config, args): self.config = config self.sqlconn = pymysql.connect(unix_socket=config['sql']['unix_socket'], user=config['sql']['user'], passwd=config['sql']['passwd'], db=config['sql']['db'], charset='utf8') self.sqlconn.autocommit(True) update_database(self.sqlconn) self.load_default_phrases() self.reactor = irc.client.Reactor() self.connection = self.reactor.server() self.twitchapi = TwitchAPI(type='api') self.reactor.add_global_handler('all_events', self._dispatcher, -10) if 'wolfram' in config['main']: Dispatch.wolfram = wolframalpha.Client(config['main']['wolfram']) else: wolfram = None self.whisper_conn = None TyggBot.instance = self self.base_path = os.path.dirname(os.path.realpath(__file__)) self.data = {} self.data_cb = {} self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['time_norway'] = self.c_time_norway self.data_cb['bot_uptime'] = self.c_uptime self.data_cb['time_since_latest_deck'] = self.c_time_since_latest_deck self.ignores = [] self.start_time = datetime.now() if 'streamer' in config['main']: self.streamer = config['main']['streamer'] self.channel = '#' + self.streamer elif 'target' in config['main']: self.channel = config['main']['target'] self.streamer = self.channel[1:] self.kvi = KVIData(self.sqlconn) self.tbm = TBMath() self.last_sync = time.time() self.users = UserManager(self.sqlconn) if 'flags' in config: self.silent = True if 'silent' in config['flags'] and config['flags']['silent'] == '1' else self.silent self.dev = True if 'dev' in config['flags'] and config['flags']['dev'] == '1' else self.dev self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') self.sync_from() self.nickname = config['main']['nickname'] self.password = config['main']['password'] self.reconnection_interval = 5 self.load_all() self.whisper_conn = WhisperConn(self.streamer, self.nickname, self.password, self.reactor) self.whisper_conn.connect() self.num_commands_sent = 0 self.connection.execute_every(30, self.reset_command_throttle) self.twitter_stream = False if 'twitter' in config: self.use_twitter_stream = 'streaming' in config['twitter'] and config['twitter']['streaming'] == '1' self.init_twitter() else: self.twitter = None self.connection.execute_every(1, self.shift_emotes) self.ws_clients = [] if 'websocket' in config and config['websocket']['enabled'] == '1': self.init_websocket_server() self.execute_every(1, self.refresh_emote_data) self.urls_to_check = queue.Queue() self.connection.execute_every(1, self.check_urls)