def format_set_channel_mode(self, settings: ChannelSettings, old_settings=ChannelSettings()) -> typing.List: """ Generate commands needed to change :param settings: Settings to apply. :param old_settings: Settings from before. Used to not execute commands that are not needed. :return: A list of ChannelMessages containing commands. """ commands = [] for num, val in enumerate(settings): if val != old_settings[num]: c_entry = COMMANDS[settings._fields[num]] if isinstance(settings[num], bool): replacement = '' else: replacement = str(settings[num]) text = (c_entry[settings[num] is not False] .replace('{}', replacement)) msg = twitchirc.ChannelMessage(text=text, user='******', outgoing=True, channel=self.target_channel, parent=self.parent) commands.append(msg) return commands
def command_part(msg: twitchirc.ChannelMessage): p = twitchirc.ArgumentParser(prog='!part', add_help=False) p.add_argument('channel', metavar='CHANNEL', nargs='?', const=msg.channel, default=msg.channel) p.add_argument('-h', '--help', action='store_true', dest='help') args = p.parse_args(msg.text.split(' ')[1:]) if args is None or args.help: usage = msg.reply(f'@{msg.user} {p.format_usage()}') bot.send(usage) return if args.channel == '': args.channel = msg.channel channel = args.channel.lower() if channel != msg.channel.lower() and bot.check_permissions( msg, [twitchirc.PERMISSION_COMMAND_PART_OTHER], enable_local_bypass=False): bot.send( msg.reply( f"Cannot part from channel {channel}: your permissions are not valid in that channel." )) return if channel not in bot.channels_connected: bot.send(msg.reply(f'Not in {channel}')) return else: bot.send(msg.reply(f'Parting from {channel}')) m = twitchirc.ChannelMessage('Bye.', 'OUTGOING', channel) m.outgoing = True bot.send(m) bot.flush_single_queue(msg.channel) bot.part(channel)
def format_clear_channel(self): """ Run the clear command on the targeted channel. :return: ChannelMessage containing generated command. """ return twitchirc.ChannelMessage(user='******', text=f'/clear', channel=self.target_channel, outgoing=True)
def create_msg(text, channel) -> twitchirc.ChannelMessage: """ Create a ChannelMessage with the provided text and channel. :return: Newly created ChannelMessage object. """ msg = twitchirc.ChannelMessage(text=text, channel=channel, user='******') msg.outgoing = True return msg
def format_delete(self): """ Create a message with the /delete command in it. :return: ChannelMessage containing generated command. """ if self.target_message_id is not None: return twitchirc.ChannelMessage(user='******', text=f'/delete {self.target_message_id}', channel=self.target_channel, outgoing=True) else: raise RuntimeError("This ModerationContainer doesn't target a message.")
def remind(reminder, channel): msg = twitchirc.ChannelMessage( user='******', channel=channel, text= f'@{reminder["user"]} As promised I\'m reminding you: {reminder["text"]}' ) msg.outgoing = True main.bot.send(msg) if reminder['nr']: reminder['timestamp'] = time.time() + reminder['seconds']
def format_set_vip(self, status): """ Create a message with the /[un]vip command in it. :param status: New status for the user. True for vip, False for unvip. :return: ChannelMessage containing generated command. """ if self.target_user is not None: return twitchirc.ChannelMessage(user='******', text=f'/{"un" if not status else ""}vip {self.target_user}', channel=self.target_channel, outgoing=True) else: raise RuntimeError("This ModerationContainer doesn't target a user.")
def format_set_mod(self, status): """ Run the [un]mod command on the targeted user. :param status: New status for the user. True for mod, False for unmod. :return: ChannelMessage containing generated command. """ if self.target_user is not None: return twitchirc.ChannelMessage(user='******', text=f'/{"un" if not status else ""}mod {self.target_user}', channel=self.target_channel, outgoing=True) else: raise RuntimeError("This ModerationContainer doesn't target a user.")
def format_permaban(self, reason: typing.Optional[str] = None): """ Create a message with the /ban command in it. :param reason: Reason for this action. :return: ChannelMessage containing generated command. """ if self.target_user is not None: return twitchirc.ChannelMessage(user='******', text=f'/ban {self.target_user}' f'{f" {reason}" if reason is not None else ""}', channel=self.target_channel, outgoing=True) else: raise RuntimeError("This ModerationContainer doesn't target a user.")
async def command_debug(msg: twitchirc.ChannelMessage): if isinstance(msg, twitchirc.ChannelMessage): return f'@{msg.user}, This command is only available in whispers :)' argv = arg_parser.parse_args( main.delete_spammer_chrs(msg.text).rstrip(' '), { 0: str, 1: str }) if argv[0] is ...: return f'debug what?' debugee_type = argv[0] print(repr(debugee_type), debugee_type == 'command', debugee_type == 'user', debugee_type == 'me', debugee_type in ('user', 'me')) if debugee_type == 'command': if argv[1] is ...: return f'debug which command?' cmd_name = argv[1] matches = list( filter(lambda c: c.chat_command == cmd_name, main.bot.commands)) if not matches: fake_msg = twitchirc.ChannelMessage(cmd_name, msg.user, msg.channel, parent=main.bot) matches = list( filter( lambda c: c.matcher_function and c.matcher_function( fake_msg, c), main.bot.commands)) if not matches: return f'Unknown command {cmd_name}' if len(matches) == 1: return _debug_command(matches[0]) else: return f'@{msg.user}, {len(matches)} found.' elif debugee_type in ('user', 'me'): if argv[1] is ... and debugee_type != 'me': return f'@{msg.user}, how do I debug?' if debugee_type == 'me': argv[1] = msg.user users = main.User.get_by_name(argv[1]) if users: return _debug_user(users[0]) else: return f'@{msg.user}, couldn\'t find the target user.' else: return f'@{msg.user}, how to debug {debugee_type!r}?'
def test_compose_whisper_via_reply_to_privmsg(self): orig = twitchirc.ChannelMessage('original', 'some_user', 'some_channel') msg = orig.reply_directly('Here is some text') self.assertEqual('[email protected]', msg.source) self.assertEqual('WHISPER', msg.action) self.assertEqual(True, msg.outgoing) self.assertEqual(None, msg.raw_data) self.assertEqual(':Here is some text', msg.args) self.assertEqual(['Here is some text'], msg.new_args) self.assertEqual(b'PRIVMSG #jtv :/w some_user Here is some text\r\n', bytes(msg)) self.assertEqual({}, msg.flags)
def test_compose_via_reply_to_privmsg(self): orig = twitchirc.ChannelMessage('original', 'some_user', 'some_channel') msg = orig.reply('Here is some text') self.assertEqual('[email protected]', msg.source) self.assertEqual('PRIVMSG', msg.action) self.assertEqual(True, msg.outgoing) self.assertEqual(None, msg.raw_data) self.assertEqual('#some_channel :Here is some text', msg.args) self.assertEqual(['#some_channel', 'Here is some text'], msg.new_args) self.assertEqual({}, msg.flags) orig = twitchirc.ChannelMessage('original', 'some_user', 'some_channel') msg = orig.reply('/help') self.assertEqual('[email protected]', msg.source) self.assertEqual('PRIVMSG', msg.action) self.assertEqual(True, msg.outgoing) self.assertEqual(None, msg.raw_data) self.assertEqual('#some_channel :/ /help', msg.args) self.assertEqual(['#some_channel', '/ /help'], msg.new_args) self.assertEqual({}, msg.flags) orig = twitchirc.ChannelMessage('original', 'some_user', 'some_channel') msg = orig.reply('/help', True) self.assertEqual('[email protected]', msg.source) self.assertEqual('PRIVMSG', msg.action) self.assertEqual(True, msg.outgoing) self.assertEqual(None, msg.raw_data) self.assertEqual('#some_channel :/help', msg.args) self.assertEqual(['#some_channel', '/help'], msg.new_args) self.assertEqual({}, msg.flags)
def format_timeout(self, time: typing.Union[int, str], reason: typing.Optional[str] = None): """ Create a message with the /timeout command in it. :param time: Time to block the user for. :param reason: Reason for this action. :return: ChannelMessage containing generated command. """ if isinstance(time, str) and ((not time[-1].isalpha()) or time.isalpha()): raise ValueError('Time needs to be an int or string with the last character indicating the unit.') if self.target_user is not None: return twitchirc.ChannelMessage(user='******', text=f'/timeout {self.target_user} {time}' f'{f" {reason}" if reason is not None else ""}', channel=self.target_channel, outgoing=True) else: raise RuntimeError("This ModerationContainer doesn't target a user.")
def _acommand_error_handler(exception, command, message): msg = twitchirc.ChannelMessage( text=f'Errors monkaS {chr(128073)} ALERT: {exception!r}', user='******', channel=error_notification_channel) msg.outgoing = True main.bot.force_send(msg) log('err', f'Error while running command {command.chat_command}') log('info', f'{message.user}@{message.channel}: {message.text}') for i in traceback.format_exc(30).split('\n'): log('err', i) msg2 = message.reply( f'@{message.user}, an error was raised during the execution of your command: ' f'{command.chat_command}') main.bot.send(msg2)
def _command_run(sock: socket.socket, msg: str, socket_id): text = msg.split(' ', 1)[1] message = twitchirc.ChannelMessage(channel=f'__IPC {socket_id}', text=text, user=f'__IPC {socket_id}') message.flags = { 'badge-info': '', 'badges': ['broadcaster/1'], 'color': '#ffffff', 'display-name': f'__IPC_Socket_id_{socket_id}', 'id': str(uuid.uuid4()), 'room-id': socket_id + 1000, 'user-id': socket_id + 1000, 'turbo': '0', 'subscriber': '0', 'emotes': '' } # noinspection PyProtectedMember main.bot._call_command_handlers(message) return f'~Sent: {text} as {message.user!r}\r\n'.encode('utf-8')
def fire(self, event: Event) -> None: print('FIRE!!!!! WAYTOODANK') if self.fire_triggered: return self.fire_triggered = True pinged = '' if isinstance(event.source, twitchirc.Bot): for user, perms in event.source.permissions.users.items(): print(user, perms) if ((twitchirc.permission_names.GLOBAL_BYPASS_PERMISSION in perms or 'util.fire_ping' in perms or 'parent.bot_admin' in perms) and 'util.fire_ping_disable' not in perms): pinged += f' @{user}' e = event.data['exception'] m = twitchirc.ChannelMessage( text=f'The bot\'s breaking!!! {pinged} WAYTOODANK {e!r}', user='******', channel=error_notification_channel, outgoing=True, parent=None) event.source.send(m) event.source.flush_queue()
def run_commands_from_file(self, file_object): lines = file_object.readlines() user = '******' channel = 'rcfile' self._in_rc_mode = True for num, i in enumerate(lines): i: str = i.replace('\n', '') if i.startswith('@'): if i.startswith('@at'): channel = i.replace('@at ', '') elif i.startswith('@as'): user = i.replace('@as ', '') continue m = twitchirc.ChannelMessage(user=user, channel=channel, text=i) m.flags = { 'badge-info': '', 'badges': 'moderator/1', 'display-name': 'RCFile', 'id': '00000000-0000-0000-0000-{:0>12}'.format(num), 'user-id': 'rcfile', 'emotes': '' } self._call_command_handlers(m) self._in_rc_mode = False
def test_compose_privmsg(self): msg = twitchirc.ChannelMessage('Testing123 456', 'OUTGOING', 'test', outgoing=True) self.assertEqual(b'PRIVMSG #test :Testing123 456\r\n', bytes(msg))
subs = bot.storage['subs'] bot.twitch_mode() bot.join(bot.username.lower()) if 'channels' in bot.storage.data: for i in bot.storage['channels']: if i in bot.channels_connected: print(f'Skipping joining channel: {i}: Already connected.') continue bot.join(i) if prog_args.restart_from: msg = twitchirc.ChannelMessage( user='******', channel=prog_args.restart_from[1], text=(f'@{prog_args.restart_from[0]} Restart OK. (debug)' if prog_args.debug else f'@{prog_args.restart_from[0]} Restart OK.')) msg.outgoing = True def _send_restart_message(*a): del a bot.send(msg, queue='misc') bot.flush_queue(1000) bot.schedule_event(1, 15, _send_restart_message, (), {}) try: bot.run() except BaseException as e: traceback.print_exc() bot.call_middleware('fire', {'exception': e}, False)
async def main(): global pubsub languages.load_data() auth = { Platform.TWITCH: (bot.storage['self_twitch_name'], 'oauth:' + util_bot.twitch_auth.json_data['access_token']) } wait = False for plat in Platform: if plat in auth: continue if plat.name.casefold() in util_bot.other_platform_auth: auth[plat] = util_bot.other_platform_auth[plat.name.casefold()] else: log( 'warn', f'Failed to load auth for {plat.name} from auth file. Key: {plat.name.casefold()!r}' ) wait = True if not auth: log('err', 'No platform authentication found') log('err', 'Exiting') return if wait: print('Please wait 5 seconds') time.sleep(5) await bot.init_clients(auth) bot.clients[Platform.TWITCH].connection.middleware.append( UserStateCapturingMiddleware()) bot.username = bot.storage['self_twitch_name'] if 'self_id' in bot.storage.data: uid = bot.storage['self_id'] else: try: uid = util_bot.twitch_auth.new_api.get_users( login=bot.username.lower())[0].json()['data'][0]['id'] except KeyError: util_bot.twitch_auth.refresh() util_bot.twitch_auth.save() uid = util_bot.twitch_auth.new_api.get_users( login=bot.username.lower())[0].json()['data'][0]['id'] bot.storage['self_id'] = uid await init_server(grpc_listen_addresses) await bot.aconnect() bot.cap_reqs(False) for i in bot.storage['channels']: if i in bot.channels_connected: log('info', f'Skipping joining channel: {i}: Already connected.') continue await bot.join(i) if prog_args.restart_from: msg = twitchirc.ChannelMessage( user='******', channel=prog_args.restart_from[1], text=(f'@{prog_args.restart_from[0]} Restart OK. (debug)' if prog_args.debug else f'@{prog_args.restart_from[0]} Restart OK.')) msg.outgoing = True def _send_restart_message(*a): del a bot.send(msg) bot.flush_queue(1000) bot.schedule_event(1, 15, _send_restart_message, (), {}) pubsub = await init_pubsub(util_bot.twitch_auth.new_api.auth.token) temp = util_bot.make_log_function('pubsub') pubsub.log_function = lambda *text, **kwargs: temp('debug', *text, **kwargs ) pubsub.listen([f'chat_moderator_actions.{uid}.{uid}']) bot.pubsub = pubsub bot.middleware.append(pubsub_middleware) for i in bot.channels_connected: pubsub_middleware.join( twitchirc.Event('join', {'channel': i}, bot, cancelable=False, has_result=False)) await bot.join(bot.username.lower()) futures = [] log('info', 'Spam async inits') for plugin in util_bot.plugins.values(): log('info', f'Call async init for {plugin}') futures.append(asyncio.create_task(plugin.async_init())) async_init_time = 5.0 _, pending = await asyncio.wait(futures, timeout=async_init_time, return_when=asyncio.ALL_COMPLETED) # wait for every plugin to initialize before proceeding if pending: log( 'err', f'Waiting for async inits timed out after {async_init_time} seconds. ' f'Assuming waiting longer will not help, exiting.') plugin_objects = list(util_bot.plugins.values()) table_data = [('State', 'Plugin name', 'Path')] for index, fut in enumerate(futures): fut: asyncio.Future plugin = plugin_objects[index] # order should be the same. done = 'done' if fut.done() else 'TIMED OUT' table_data.append((done, plugin.name, plugin.source)) table = '' cols = 3 # calculate maximum length of elements for each row col_max = [ len(max(table_data, key=lambda o: len(o[col_id]))[col_id]) for col_id in range(cols) ] for row in table_data: for col_id, col in enumerate(row): table += col _print(col_max[col_id], repr(col), len(col)) table += (col_max[col_id] - len(col) + 1) * ' ' table += '\n' _print(table) log('warn', table) raise TimeoutError('Waiting for async inits timed out') log('warn', 'async inits done') try: done, pending = await asyncio.wait({bot.arun(), pubsub.task}, return_when=asyncio.FIRST_COMPLETED) except KeyboardInterrupt: await bot.stop() return for j in done: await j # retrieve any exceptions
async def nuke(self, args, msg, users, force_nuke=False, reasons=None, disable_hastebinning=False, show_nuked_by=False): if reasons is None: reasons = {} if msg.user in users: users.remove( msg.user) # make the executor not get hit by the fallout. if main.bot.username.lower() in users: users.remove(main.bot.username.lower()) for u in users.copy(): m = twitchirc.ChannelMessage(text='', user=u, channel=msg.channel, parent=main.bot) missing_permissions = main.bot.check_permissions( m, ['util.nuke.no_fallout'], enable_local_bypass=False) if not missing_permissions: users.remove(u) if disable_hastebinning: url = '(disabled)' else: url = plugin_hastebin.hastebin_addr + await plugin_hastebin.upload( "\n".join(users)) if len(users) > self.max_nuke and not force_nuke: return ( f'@{msg.user}, {"(dry run)" if args["dry-run"] else ""}' f'refusing to nuke {len(users)} users. Add the +force flag or force nuke the list.' f'Full list here: {url}') if args['dry-run'] is True: return ( f'@{msg.user}, (dry run) {"timing out" if not args["perma"] else "banning (!!)"} {len(users)} ' f'users. Full list here: ' f'{url}') timeouts = [] if not args['perma']: t_o_length = int(args['timeout'].total_seconds()) for u in users: if show_nuked_by: reason = f'nuked by {msg.user} - ' else: reason = '' if u in reasons: reason += reasons[u] timeouts.append( msg.reply(f'/timeout {u} {t_o_length}s {reason}', force_slash=True)) else: for u in users: if show_nuked_by: reason = f'nuked by {msg.user} - ' else: reason = '' if u in reasons: reason += reasons[u] timeouts.append( msg.reply(f'/ban {u} {reason}', force_slash=True)) number_of_needed_connections = self._calculate_number_of_connections( timeouts) main.bot.send( msg.reply_directly( self._make_nuke_notification_whisper( args, msg, number_of_needed_connections, timeouts, users))) main.bot.flush_queue(1) num_of_timeouts = len(timeouts) db_log_level_bkup = plugin_logs.db_log_level log_level_bkup = plugin_logs.log_level try: if plugin_logs: plugin_logs.db_log_level = plugin_logs.log_levels[ 'warn'] # don't spam the logs plugin_logs.log_level = plugin_logs.log_levels['warn'] # time_taken = await self._send_using_multiple_connections(msg, timeouts, msg.channel, # number_of_needed_connections) time_taken = await self._send_using_new_connection_thread( msg, timeouts, msg.channel, number_of_needed_connections) finally: plugin_logs.db_log_level = db_log_level_bkup plugin_logs.log_level = log_level_bkup return self._make_nuke_end_message(msg, args, users, url, time_taken, timeouts)