def command_checkalias(**extra_args): json_data = request.get_json() if not json_data: return {"error": "No input data provided"}, 400 try: data = CheckAliasSchema().load(json_data) except ValidationError as err: return { "error": f"Did not match schema: {json.dumps(err.messages)}" }, 400 request_alias = data.alias.lower() command_manager = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) command_aliases = [] for alias, command in command_manager.items(): command_aliases.append(alias) if command.command and len(command.command) > 0: command_aliases.extend(command.command.split("|")) command_aliases = set(command_aliases) if request_alias in command_aliases: return {"error": "Alias already in use"} else: return {"success": "good job"}
def get_cached_commands() -> List[Dict[str, Any]]: CACHE_TIME = 30 # seconds redis = RedisManager.get() commands_key = f"{StreamHelper.get_streamer()}:cache:commands" commands = redis.get(commands_key) if commands is not None: cached_bot_command_list = json.loads(commands) if not isinstance(cached_bot_command_list, list): return [] return cached_bot_command_list log.debug("Updating commands...") bot_commands = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(load_examples=True) bot_commands_list = bot_commands.parse_for_web() bot_commands_list.sort(key=lambda x: (x.id or -1, x.main_alias)) jsonified_bot_commands_list = [c.jsonify() for c in bot_commands_list] redis.setex(commands_key, value=json.dumps(jsonified_bot_commands_list, separators=(",", ":")), time=CACHE_TIME) return jsonified_bot_commands_list
def commands(**options): from pajbot.models.command import CommandManager from pajbot.models.module import ModuleManager bot_commands = CommandManager(socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) bot_commands_list = bot_commands.parse_for_web() custom_commands = [] point_commands = [] moderator_commands = [] for command in bot_commands_list: if command.id is None: continue if command.level > 100 or command.mod_only: moderator_commands.append(command) elif command.cost > 0: point_commands.append(command) else: custom_commands.append(command) return render_template('admin/commands.html', custom_commands=sorted(custom_commands, key=lambda f: f.command), point_commands=sorted(point_commands, key=lambda a: (a.cost, a.command)), moderator_commands=sorted( moderator_commands, key=lambda c: (c.level if c.mod_only is False else 500, c.command)), created=session.pop('command_created_id', None), edited=session.pop('command_edited_id', None))
def modules_edit(module_id, **options): module_manager = ModuleManager(None).load(do_reload=False) current_module = find(lambda m: m.ID == module_id, module_manager.all_modules) if current_module is None: return render_template('admin/module_404.html'), 404 sub_modules = [] for module in module_manager.all_modules: module.db_module = None with DBManager.create_session_scope() as db_session: for db_module in db_session.query(Module): module = find(lambda m: m.ID == db_module.id, module_manager.all_modules) if module: module.db_module = db_module if module.PARENT_MODULE == current_module.__class__: sub_modules.append(module) if current_module.db_module is None: return render_template('admin/module_404.html'), 404 if request.method == 'POST': form_values = {key: value for key, value in request.form.items()} res = current_module.parse_settings(**form_values) if res is False: return render_template('admin/module_404.html'), 404 current_module.db_module.settings = json.dumps(res) db_session.commit() settings = None try: settings = json.loads(current_module.db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) SocketClientManager.send( 'module.update', {'module_id': current_module.db_module.id}) return render_template('admin/configure_module.html', module=current_module, sub_modules=sub_modules) else: settings = None try: settings = json.loads(current_module.db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) return render_template('admin/configure_module.html', module=current_module, sub_modules=sub_modules)
def modules_edit(module_id, **options): module_manager = ModuleManager(None).load(do_reload=False) current_module = find(lambda m: m.ID == module_id, module_manager.all_modules) if current_module is None: return render_template('admin/module_404.html'), 404 if request.method == 'POST': form_values = {key: value for key, value in request.form.items()} res = current_module.parse_settings(**form_values) if res is False: return render_template('admin/module_404.html'), 404 with DBManager.create_session_scope() as db_session: db_module = db_session.query(Module).filter_by( id=module_id).one_or_none() if db_module is None: return render_template('admin/module_404.html'), 404 db_module.settings = json.dumps(res) db_session.commit() current_module.db_module = db_module settings = None try: settings = json.loads(db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) SocketClientManager.send('module.update', {'module_id': db_module.id}) return render_template('admin/configure_module.html', module=current_module) pass else: with DBManager.create_session_scope() as db_session: db_module = db_session.query(Module).filter_by( id=module_id).one_or_none() if db_module is None: return render_template('admin/module_404.html'), 404 current_module.db_module = db_module settings = None try: settings = json.loads(db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) return render_template('admin/configure_module.html', module=current_module)
def modules(**options) -> ResponseReturnValue: module_manager = ModuleManager(None).load(do_reload=False) with DBManager.create_session_scope() as db_session: for db_module in db_session.query(Module): module = find(lambda m: m.ID == db_module.id, module_manager.all_modules) if module: module.db_module = db_module return render_template("admin/modules.html", modules=module_manager.all_modules)
def update_commands(signal_id): global bot_commands_list from pajbot.models.command import CommandManager bot_commands = CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(load_examples=True) bot_commands_list = bot_commands.parse_for_web() bot_commands_list = sorted(bot_commands_list, key=lambda x: (x.id or -1, x.main_alias)) del bot_commands
def update_commands(signal_id): log.debug('Updating commands...') from pajbot.models.command import CommandManager bot_commands = CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(load_examples=True) app.bot_commands_list = bot_commands.parse_for_web() app.bot_commands_list.sort(key=lambda x: (x.id or -1, x.main_alias)) del bot_commands
def commands(**options) -> ResponseReturnValue: from pajbot.models.module import ModuleManager bot_commands = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) bot_commands_list = bot_commands.parse_for_web() custom_commands = [] point_commands = [] moderator_commands = [] for command in bot_commands_list: if command.id is None: continue if command.level > 100 or command.mod_only: moderator_commands.append(command) elif command.cost > 0: point_commands.append(command) else: custom_commands.append(command) with DBManager.create_session_scope() as db_session: commands_data = (db_session.query(CommandData).options( joinedload(CommandData.user), joinedload(CommandData.user2)).all()) return render_template( "admin/commands.html", commands_data=commands_data, custom_commands=sorted(custom_commands, key=lambda f: f.command), point_commands=sorted(point_commands, key=lambda a: (a.cost, a.command)), moderator_commands=sorted( moderator_commands, key=lambda c: (c.level if c.mod_only is False else 500, c.command)), created=session.pop("command_created_id", None), edited=session.pop("command_edited_id", None), )
def get_cached_commands(): CACHE_TIME = 30 # seconds redis = RedisManager.get() commands_key = '{streamer}:cache:commands'.format(streamer=StreamHelper.get_streamer()) commands = redis.get(commands_key) if commands is None: log.debug('Updating commands...') bot_commands = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(load_examples=True) bot_commands_list = bot_commands.parse_for_web() bot_commands_list.sort(key=lambda x: (x.id or -1, x.main_alias)) bot_commands_list = [c.jsonify() for c in bot_commands_list] redis.setex(commands_key, json.dumps(bot_commands_list, separators=(',', ':')), CACHE_TIME) else: bot_commands_list = json.loads(commands) return bot_commands_list
def post(self, **extra_args): args = pajbot.utils.remove_none_values(self.post_parser.parse_args()) request_alias = args['alias'].lower() command_manager = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) command_aliases = [] for alias, command in command_manager.items(): command_aliases.append(alias) if command.command and len(command.command) > 0: command_aliases.extend(command.command.split('|')) command_aliases = set(command_aliases) if request_alias in command_aliases: return {'error': 'Alias already in use'} else: return {'success': 'good job'}
def get_cached_enabled_modules() -> Set[str]: CACHE_TIME = 30 # seconds CACHE_KEY = f"{StreamHelper.get_streamer()}:cache:enabled_modules" enabled_modules: Set[str] = set() redis = RedisManager.get() redis_enabled_modules = redis.get(CACHE_KEY) if redis_enabled_modules is not None: cached_enabled_modules = json.loads(redis_enabled_modules) if not isinstance(cached_enabled_modules, list): log.warning("Poorly cached module states") return enabled_modules return set(cached_enabled_modules) log.debug("Updating enabled modules...") module_manager = ModuleManager(None).load() for module in module_manager.modules: enabled_modules.add(module.ID) redis.setex(CACHE_KEY, value=json.dumps(list(enabled_modules)), time=CACHE_TIME) return enabled_modules
def command_checkalias(**options): if 'alias' not in request.form: return make_response(jsonify({'error': 'Missing `alias` parameter.'}), 400) request_alias = request.form['alias'].lower() command_manager = CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) command_aliases = [] for alias, command in command_manager.items(): command_aliases.append(alias) if command.command and len(command.command) > 0: command_aliases.extend(command.command.split('|')) command_aliases = set(command_aliases) if request_alias in command_aliases: return make_response(jsonify({'error': 'Alias already in use'})) else: return make_response(jsonify({'success': 'good job'}))
def __init__(self, config, args): self.config = config self.args = args ScheduleManager.init() DBManager.init(self.config["main"]["db"]) # redis redis_options = {} if "redis" in config: redis_options = dict(config.items("redis")) RedisManager.init(**redis_options) utils.wait_for_redis_data_loaded(RedisManager.get()) self.nickname = config["main"].get("nickname", "pajbot") if config["main"].getboolean("verified", False): self.tmi_rate_limits = TMIRateLimits.VERIFIED elif config["main"].getboolean("known", False): self.tmi_rate_limits = TMIRateLimits.KNOWN else: self.tmi_rate_limits = TMIRateLimits.BASE self.whisper_output_mode = WhisperOutputMode.from_config_value( config["main"].get("whisper_output_mode", "normal") ) # phrases self.phrases = { "welcome": ["{nickname} {version} running! HeyGuys"], "quit": ["{nickname} {version} shutting down... BibleThump"], } if "phrases" in config: phrases = config["phrases"] if "welcome" in phrases: self.phrases["welcome"] = phrases["welcome"].splitlines() if "quit" in phrases: self.phrases["quit"] = phrases["quit"].splitlines() # Remembers whether the "welcome" phrases have already been said. We don't want to send the # welcome messages to chat again on a reconnect. self.welcome_messages_sent = False # streamer 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.bot_domain = self.config["web"]["domain"] self.streamer_display = self.config["web"]["streamer_name"] log.debug("Loaded config") # do this earlier since schema upgrade can depend on the helix api self.api_client_credentials = ClientCredentials( self.config["twitchapi"]["client_id"], self.config["twitchapi"]["client_secret"], self.config["twitchapi"]["redirect_uri"], ) self.twitch_id_api = TwitchIDAPI(self.api_client_credentials) self.twitch_tmi_api = TwitchTMIAPI() self.app_token_manager = AppAccessTokenManager(self.twitch_id_api, RedisManager.get()) self.twitch_helix_api = TwitchHelixAPI(RedisManager.get(), self.app_token_manager) self.twitch_v5_api = TwitchKrakenV5API(self.api_client_credentials, RedisManager.get()) self.bot_user_id = self.twitch_helix_api.get_user_id(self.nickname) if self.bot_user_id is None: raise ValueError("The bot login name you entered under [main] does not exist on twitch.") self.streamer_user_id = self.twitch_helix_api.get_user_id(self.streamer) if self.streamer_user_id is None: raise ValueError("The streamer login name you entered under [main] does not exist on twitch.") self.streamer_access_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.streamer, user_id=self.streamer_user_id ) StreamHelper.init_streamer(self.streamer, self.streamer_user_id, self.streamer_display) # SQL migrations with DBManager.create_dbapi_connection_scope() as sql_conn: sql_migratable = DatabaseMigratable(sql_conn) sql_migration = Migration(sql_migratable, pajbot.migration_revisions.db, self) sql_migration.run() # Redis migrations redis_migratable = RedisMigratable(redis_options=redis_options, namespace=self.streamer) redis_migration = Migration(redis_migratable, pajbot.migration_revisions.redis, self) redis_migration.run() # Thread pool executor for async actions self.action_queue = ActionQueue() # refresh points_rank and num_lines_rank regularly UserRanksRefreshManager.start(self.action_queue) self.reactor = irc.client.Reactor() # SafeDefaultScheduler makes the bot not exit on exception in the main thread # e.g. on actions via bot.execute_now, etc. self.reactor.scheduler_class = SafeDefaultScheduler self.reactor.scheduler = SafeDefaultScheduler() self.start_time = utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer, self.execute_now) self.stream_manager = StreamManager(self) StreamHelper.init_stream_manager(self.stream_manager) self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() # bot access token if "password" in self.config["main"]: log.warning( "DEPRECATED - Using bot password/oauth token from file. " "You should authenticate in web gui using route /bot_login " "and remove password from config file" ) access_token = self.config["main"]["password"] if access_token.startswith("oauth:"): access_token = access_token[6:] self.bot_token_manager = UserAccessTokenManager( api=None, redis=None, username=self.nickname, user_id=self.bot_user_id, token=UserAccessToken.from_implicit_auth_flow_token(access_token), ) else: self.bot_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.nickname, user_id=self.bot_user_id ) self.emote_manager = EmoteManager(self.twitch_v5_api, self.action_queue) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() if "twitter" in self.config and self.config["twitter"].get("streaming_type", "twitter") == "tweet-provider": self.twitter_manager = PBTwitterManager(self) else: self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager( socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self ).load() self.websocket_manager = WebSocketManager(self) HandlerManager.trigger("on_managers_loaded") # Commitable managers self.commitable = {"commands": self.commands, "banphrases": self.banphrase_manager} self.execute_every(60, self.commit_all) self.execute_every(1, self.do_tick) # promote the admin to level 2000 self.admin = self.config["main"].get("admin", None) if self.admin is None: log.warning("No admin user specified. See the [main] section in the example config for its usage.") else: with DBManager.create_session_scope() as db_session: admin_user = User.find_or_create_from_login(db_session, self.twitch_helix_api, self.admin) if admin_user is None: log.warning( "The login name you entered for the admin user does not exist on twitch. " "No admin user has been created." ) else: admin_user.level = 2000 # silent mode self.silent = ( "flags" in config and "silent" in config["flags"] and config["flags"]["silent"] == "1" ) or args.silent if self.silent: log.info("Silent mode enabled") # dev mode self.dev = "flags" in config and "dev" in config["flags"] and config["flags"]["dev"] == "1" if self.dev: self.version_long = utils.extend_version_if_possible(VERSION) else: self.version_long = VERSION self.irc = IRCManager(self) relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is not None or relay_password is not None: log.warning( "DEPRECATED - Relaybroker support is no longer implemented. relay_host and relay_password are ignored" ) self.data = { "broadcaster": self.streamer, "version": self.version_long, "version_brief": VERSION, "bot_name": self.nickname, "bot_domain": self.bot_domain, "streamer_display": self.streamer_display, } self.data_cb = { "status_length": self.c_status_length, "stream_status": self.c_stream_status, "bot_uptime": self.c_uptime, "current_time": self.c_current_time, "molly_age_in_years": self.c_molly_age_in_years, } self.user_agent = f"pajbot1/{VERSION} ({self.nickname})"
def __init__(self, config, args=None): # Load various configuration variables from the given config object # The config object that should be passed through should # come from pajbot.utils.load_config self.load_config(config) # Update the database scheme if necessary using alembic # In case of errors, i.e. if the database is out of sync or the alembic # binary can't be called, we will shut down the bot. pajbot.utils.alembic_upgrade() # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = datetime.datetime.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager( socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.filters = FilterManager().reload() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() self.emotes = EmoteManager(self) self.twitter_manager = TwitterManager(self) HandlerManager.trigger('on_managers_loaded') # Reloadable managers self.reloadable = { 'filters': self.filters, } # Commitable managers self.commitable = { 'commands': self.commands, 'filters': self.filters, 'banphrases': self.banphrase_manager, } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) try: self.admin = self.config['main']['admin'] except KeyError: log.warning('No admin user specified. See the [main] section in config.example.ini for its usage.') if self.admin: with self.users.get_user_context(self.admin) as user: pass # user.level = 2000 self.parse_version() relay_host = self.config['main'].get('relay_host', None) relay_password = self.config['main'].get('relay_password', None) if relay_host is None or relay_password is None: self.irc = MultiIRCManager(self) else: self.irc = SingleIRCManager(self) self.reactor.add_global_handler('all_events', self.irc._dispatcher, -10) twitch_client_id = None twitch_oauth = None if 'twitchapi' in self.config: twitch_client_id = self.config['twitchapi'].get('client_id', None) twitch_oauth = self.config['twitchapi'].get('oauth', None) # A client ID is required for the bot to work properly now, give an error for now if twitch_client_id is None: log.error('MISSING CLIENT ID, SET "client_id" VALUE UNDER [twitchapi] SECTION IN CONFIG FILE') self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data['broadcaster'] = self.streamer self.data['version'] = self.version self.data['version_brief'] = self.version_brief self.data['bot_name'] = self.nickname self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['bot_uptime'] = self.c_uptime self.data_cb['current_time'] = self.c_current_time self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) try: if self.config['twitchapi']['update_subscribers'] == '1': self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass # XXX: TEMPORARY UGLY CODE HandlerManager.add_handler('on_user_gain_tokens', self.on_user_gain_tokens) HandlerManager.add_handler('send_whisper', self.whisper)
def init(args): import subprocess import sys from flask import request from flask import session import pajbot.utils import pajbot.web.common import pajbot.web.routes from pajbot.managers.db import DBManager from pajbot.managers.redis import RedisManager from pajbot.models.module import ModuleManager from pajbot.models.sock import SocketClientManager from pajbot.streamhelper import StreamHelper from pajbot.utils import load_config from pajbot.web.models import errors from pajbot.web.utils import download_logo from pajbot.web.utils import download_sub_badge config = load_config(args.config) api_client_credentials = ClientCredentials( config["twitchapi"]["client_id"], config["twitchapi"]["client_secret"], config["twitchapi"].get( "redirect_uri", f"https://{config['web']['domain']}/login/authorized"), ) redis_options = {} if "redis" in config: redis_options = dict(config["redis"]) RedisManager.init(redis_options) twitch_id_api = TwitchIDAPI(api_client_credentials) app_token_manager = AppAccessTokenManager(twitch_id_api, RedisManager.get()) twitch_helix_api = TwitchHelixAPI(RedisManager.get(), app_token_manager) twitch_badges_api = TwitchBadgesAPI(RedisManager.get()) app.api_client_credentials = api_client_credentials app.twitch_id_api = twitch_id_api app.twitch_helix_api = twitch_helix_api if "web" not in config: log.error("Missing [web] section in config.ini") sys.exit(1) app.streamer = cfg.load_streamer(config, twitch_helix_api) app.streamer_display = app.streamer.name if "streamer_name" in config["web"]: app.streamer_display = config["web"]["streamer_name"] app.bot_user = cfg.load_bot(config, twitch_helix_api) StreamHelper.init_streamer(app.streamer.login, app.streamer.id, app.streamer.name) try: download_logo(twitch_helix_api, app.streamer) except: log.exception("Error downloading the streamers profile picture") subscriber_badge_version = config["web"].get("subscriber_badge_version", "0") # Specifying a value of -1 in the config will disable sub badge downloading. Useful if you want to keep a custom version of a sub badge for a streamer if subscriber_badge_version != "-1": try: download_sub_badge(twitch_badges_api, app.streamer, subscriber_badge_version) except: log.exception("Error downloading the streamers subscriber badge") SocketClientManager.init(app.streamer.login) app.bot_modules = config["web"].get("modules", "").split() app.bot_commands_list = [] app.bot_config = config # https://flask.palletsprojects.com/en/1.1.x/quickstart/#sessions # https://flask.palletsprojects.com/en/1.1.x/api/#sessions # https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.secret_key app.secret_key = _load_secret_key(app.bot_user.id, app.streamer.id) app.bot_dev = "flags" in config and "dev" in config["flags"] and config[ "flags"]["dev"] == "1" DBManager.init(config["main"]["db"]) app.module_manager = ModuleManager(None).load() pajbot.web.routes.admin.init(app) pajbot.web.routes.api.init(app) pajbot.web.routes.base.init(app) # Make a CSRF exemption for the /api/v1/banphrases/test endpoint csrf.exempt("pajbot.web.routes.api.banphrases.apibanphrasetest") pajbot.web.common.filters.init(app) pajbot.web.common.assets.init(app) pajbot.web.common.menu.init(app) app.register_blueprint(pajbot.web.routes.clr.page) errors.init(app, config) pajbot.web.routes.clr.config = config version = VERSION last_commit = None if app.bot_dev: version = extend_version_if_possible(VERSION) try: last_commit = subprocess.check_output( ["git", "log", "-1", "--format=%cd"]).decode("utf8").strip() except: log.exception( "Failed to get last_commit, will not show last commit") default_variables = { "version": version, "last_commit": last_commit, "bot": { "name": app.bot_user.login }, "site": { "domain": config["web"]["domain"], "deck_tab_images": cfg.get_boolean(config["web"], "deck_tab_images", False), "websocket": { "host": config["websocket"].get( "host", f"wss://{config['web']['domain']}/clrsocket") }, }, "streamer": { "name": app.streamer_display, "full_name": app.streamer.login, "id": app.streamer.id }, "modules": app.bot_modules, "request": request, "session": session, "google_analytics": config["web"].get("google_analytics", None), } @app.context_processor def current_time(): current_time = {} current_time["current_time"] = pajbot.utils.now() return current_time @app.context_processor def inject_default_variables(): return default_variables
def init(args): import configparser import logging import subprocess import sys from flask import request from flask import session from flask_scrypt import generate_random_salt import pajbot.utils import pajbot.web.common import pajbot.web.routes from pajbot import constants from pajbot.managers.db import DBManager from pajbot.managers.redis import RedisManager from pajbot.managers.time import TimeManager from pajbot.models.module import ModuleManager from pajbot.models.sock import SocketClientManager from pajbot.streamhelper import StreamHelper from pajbot.utils import load_config from pajbot.web.models import errors from pajbot.web.utils import download_logo log = logging.getLogger(__name__) config = configparser.ConfigParser() config = load_config(args.config) config.read("webconfig.ini") api_client_credentials = ClientCredentials( config["twitchapi"]["client_id"], config["twitchapi"]["client_secret"], config["twitchapi"]["redirect_uri"] ) redis_options = {} if "redis" in config: redis_options = dict(config["redis"]) RedisManager.init(**redis_options) id_api = TwitchIDAPI(api_client_credentials) app_token_manager = AppAccessTokenManager(id_api, RedisManager.get()) twitch_helix_api = TwitchHelixAPI(RedisManager.get(), app_token_manager) if "web" not in config: log.error("Missing [web] section in config.ini") sys.exit(1) if "pleblist_password_salt" not in config["web"]: salt = generate_random_salt() config.set("web", "pleblist_password_salt", salt.decode("utf-8")) if "pleblist_password" not in config["web"]: salt = generate_random_salt() config.set("web", "pleblist_password", salt.decode("utf-8")) if "secret_key" not in config["web"]: salt = generate_random_salt() config.set("web", "secret_key", salt.decode("utf-8")) if "logo" not in config["web"]: try: download_logo(twitch_helix_api, config["main"]["streamer"]) config.set("web", "logo", "set") except: log.exception("Error downloading logo") StreamHelper.init_web(config["main"]["streamer"]) SocketClientManager.init(config["main"]["streamer"]) with open(args.config, "w") as configfile: config.write(configfile) app.bot_modules = config["web"].get("modules", "").split() app.bot_commands_list = [] app.bot_config = config app.secret_key = config["web"]["secret_key"] DBManager.init(config["main"]["db"]) TimeManager.init_timezone(config["main"].get("timezone", "UTC")) app.module_manager = ModuleManager(None).load() pajbot.web.routes.admin.init(app) pajbot.web.routes.api.init(app) pajbot.web.routes.base.init(app) pajbot.web.common.filters.init(app) pajbot.web.common.assets.init(app) pajbot.web.common.menu.init(app) app.register_blueprint(pajbot.web.routes.clr.page) errors.init(app, config) pajbot.web.routes.clr.config = config version = extend_version_if_possible(VERSION) try: last_commit = subprocess.check_output(["git", "log", "-1", "--format=%cd"]).decode("utf8").strip() except: log.exception("Failed to get last_commit, will not show last commit") last_commit = None default_variables = { "version": version, "last_commit": last_commit, "bot": {"name": config["main"]["nickname"]}, "site": { "domain": config["web"]["domain"], "deck_tab_images": config.getboolean("web", "deck_tab_images"), "websocket": { "host": config["websocket"].get("host", "wss://{}/clrsocket".format(config["web"]["domain"])) }, }, "streamer": {"name": config["web"]["streamer_name"], "full_name": config["main"]["streamer"]}, "modules": app.bot_modules, "request": request, "session": session, "google_analytics": config["web"].get("google_analytics", None), } @app.context_processor def current_time(): current_time = {} current_time["current_time"] = pajbot.utils.now() return current_time @app.context_processor def inject_default_variables(): return default_variables
def commands_create(**options) -> ResponseReturnValue: session.pop("command_created_id", None) session.pop("command_edited_id", None) if request.method != "POST": return render_template("admin/create_command.html") if "aliases" not in request.form: abort(403) alias_str = request.form.get("aliases", "").replace("!", "").lower() delay_all = request.form.get("cd", Command.DEFAULT_CD_ALL) delay_user = request.form.get("usercd", Command.DEFAULT_CD_USER) level = request.form.get("level", Command.DEFAULT_LEVEL) cost = request.form.get("cost", 0) can_execute_with_whisper = request.form.get("whisperable", "off") == "on" sub_only = request.form.get("subonly", "off") == "on" mod_only = request.form.get("modonly", "off") == "on" run_through_banphrases = request.form.get("checkmsg", "off") == "on" try: delay_all = int(delay_all) delay_user = int(delay_user) level = int(level) cost = int(cost) except ValueError: abort(403) if not alias_str: abort(403) if delay_all < 0 or delay_all > 9999: abort(403) if delay_user < 0 or delay_user > 9999: abort(403) if level < 0 or level > 2000: abort(403) if cost < 0 or cost > 9999999: abort(403) user = options.get("user", None) if user is None: abort(403) options = { "delay_all": delay_all, "delay_user": delay_user, "level": level, "cost": cost, "added_by": user.id, "can_execute_with_whisper": can_execute_with_whisper, "sub_only": sub_only, "mod_only": mod_only, "run_through_banphrases": run_through_banphrases, } valid_action_types = ["say", "me", "announce", "whisper", "reply"] action_type = request.form.get("reply", "say").lower() if action_type not in valid_action_types: abort(403) response = request.form.get("response", "") if not response: abort(403) action = {"type": action_type, "message": response} options["action"] = action command_manager = pajbot.managers.command.CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) command_aliases_list = [] for alias, command in command_manager.items(): command_aliases_list.append(alias) if command.command and len(command.command) > 0: command_aliases_list.extend(command.command.split("|")) command_aliases = set(command_aliases_list) alias_str = alias_str.replace(" ", "").replace("!", "").lower() alias_list = alias_str.split("|") alias_list = [alias for alias in alias_list if len(alias) > 0] if not alias_list: return render_template("admin/create_command_fail.html") for alias in alias_list: if alias in command_aliases: return render_template("admin/create_command_fail.html") alias_str = "|".join(alias_list) command = Command(command=alias_str, **options) command.data = CommandData(command.id, **options) log_msg = f"The !{command.command.split('|')[0]} command has been created" AdminLogManager.add_entry("Command created", user, log_msg) with DBManager.create_session_scope( expire_on_commit=False) as db_session: db_session.add(command) db_session.add(command.data) db_session.commit() db_session.expunge(command) db_session.expunge(command.data) SocketClientManager.send("command.update", {"command_id": command.id}) session["command_created_id"] = command.id return redirect("/admin/commands", 303)
def __init__(self, config, args=None): # Load various configuration variables from the given config object # The config object that should be passed through should # come from pajbot.utils.load_config self.load_config(config) log.debug("Loaded config") # streamer is additionally initialized here so streamer can be accessed by the DB migrations # before StreamHelper.init_bot() is called later (which depends on an upgraded DB because # StreamManager accesses the DB) StreamHelper.init_streamer(self.streamer) # Update the database (and partially redis) scheme if necessary using alembic # In case of errors, i.e. if the database is out of sync or the alembic # binary can't be called, we will shut down the bot. pajbot.utils.alembic_upgrade() log.debug("ran db upgrade") # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = pajbot.utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() twitch_client_id = None twitch_oauth = None if "twitchapi" in self.config: twitch_client_id = self.config["twitchapi"].get("client_id", None) twitch_oauth = self.config["twitchapi"].get("oauth", None) # A client ID is required for the bot to work properly now, give an error for now if twitch_client_id is None: log.error( 'MISSING CLIENT ID, SET "client_id" VALUE UNDER [twitchapi] SECTION IN CONFIG FILE' ) self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.emote_manager = EmoteManager(twitch_client_id) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager(socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() HandlerManager.trigger("on_managers_loaded") # Reloadable managers self.reloadable = {} # Commitable managers self.commitable = { "commands": self.commands, "banphrases": self.banphrase_manager } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) try: self.admin = self.config["main"]["admin"] except KeyError: log.warning( "No admin user specified. See the [main] section in config.example.ini for its usage." ) if self.admin: with self.users.get_user_context(self.admin) as user: user.level = 2000 self.parse_version() relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is None or relay_password is None: self.irc = MultiIRCManager(self) else: self.irc = SingleIRCManager(self) self.reactor.add_global_handler("all_events", self.irc._dispatcher, -10) self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data["broadcaster"] = self.streamer self.data["version"] = self.version self.data["version_brief"] = self.version_brief self.data["bot_name"] = self.nickname self.data_cb["status_length"] = self.c_status_length self.data_cb["stream_status"] = self.c_stream_status self.data_cb["bot_uptime"] = self.c_uptime self.data_cb["current_time"] = self.c_current_time self.silent = True if args.silent else self.silent if self.silent: log.info("Silent mode enabled") """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) try: if self.config["twitchapi"]["update_subscribers"] == "1": self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass
def init(args): import configparser import logging import subprocess import sys from flask import request from flask import session from flask_scrypt import generate_random_salt import pajbot.utils import pajbot.web.common import pajbot.web.routes from pajbot.bot import Bot from pajbot.managers.db import DBManager from pajbot.managers.redis import RedisManager from pajbot.managers.time import TimeManager from pajbot.models.module import ModuleManager from pajbot.models.sock import SocketClientManager from pajbot.models.sock import SocketManager from pajbot.streamhelper import StreamHelper from pajbot.utils import load_config from pajbot.web.models import errors from pajbot.web.utils import download_logo log = logging.getLogger(__name__) config = configparser.ConfigParser() config = load_config(args.config) config.read("webconfig.ini") if "web" not in config: log.error("Missing [web] section in config.ini") sys.exit(1) if "pleblist_password_salt" not in config["web"]: salt = generate_random_salt() config.set("web", "pleblist_password_salt", salt.decode("utf-8")) if "pleblist_password" not in config["web"]: salt = generate_random_salt() config.set("web", "pleblist_password", salt.decode("utf-8")) if "secret_key" not in config["web"]: salt = generate_random_salt() config.set("web", "secret_key", salt.decode("utf-8")) if "logo" not in config["web"]: res = download_logo(config["webtwitchapi"]["client_id"], config["main"]["streamer"]) if res: config.set("web", "logo", "set") StreamHelper.init_web(config["main"]["streamer"]) SocketClientManager.init(config["main"]["streamer"]) redis_options = {} if "redis" in config: redis_options = dict(config["redis"]) RedisManager.init(**redis_options) with open(args.config, "w") as configfile: config.write(configfile) app.bot_modules = config["web"].get("modules", "").split() app.bot_commands_list = [] app.bot_config = config app.secret_key = config["web"]["secret_key"] DBManager.init(config["main"]["db"]) TimeManager.init_timezone(config["main"].get("timezone", "UTC")) app.module_manager = ModuleManager(None).load() pajbot.web.routes.admin.init(app) pajbot.web.routes.api.init(app) pajbot.web.routes.base.init(app) pajbot.web.common.filters.init(app) pajbot.web.common.assets.init(app) pajbot.web.common.menu.init(app) app.register_blueprint(pajbot.web.routes.clr.page) errors.init(app, config) pajbot.web.routes.clr.config = config version = Bot.version last_commit = "" commit_number = 0 try: current_branch = subprocess.check_output( ["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf8").strip() latest_commit = subprocess.check_output(["git", "rev-parse", "HEAD" ]).decode("utf8").strip()[:8] commit_number = subprocess.check_output( ["git", "rev-list", "HEAD", "--count"]).decode("utf8").strip() last_commit = subprocess.check_output( ["git", "log", "-1", "--format=%cd"]).decode("utf8").strip() version = "{0} DEV ({1}, {2}, commit {3})".format( version, current_branch, latest_commit, commit_number) except: pass default_variables = { "version": version, "last_commit": last_commit, "commit_number": commit_number, "bot": { "name": config["main"]["nickname"] }, "site": { "domain": config["web"]["domain"], "deck_tab_images": config.getboolean("web", "deck_tab_images"), "websocket": { "host": config["websocket"].get( "host", "wss://{}/clrsocket".format(config["web"]["domain"])) }, }, "streamer": { "name": config["web"]["streamer_name"], "full_name": config["main"]["streamer"] }, "modules": app.bot_modules, "request": request, "session": session, "google_analytics": config["web"].get("google_analytics", None), } @app.context_processor def current_time(): current_time = {} current_time["current_time"] = pajbot.utils.now() return current_time @app.context_processor def inject_default_variables(): return default_variables
def __init__(self, config, args): self.config = config self.args = args self.last_ping = utils.now() self.last_pong = utils.now() DBManager.init(self.config["main"]["db"]) # redis redis_options = {} if "redis" in config: redis_options = dict(config.items("redis")) RedisManager.init(**redis_options) wait_for_redis_data_loaded(RedisManager.get()) # Pepega SE points sync pajbot.models.user.Config.se_sync_token = config["main"].get( "se_sync_token", None) pajbot.models.user.Config.se_channel = config["main"].get( "se_channel", None) self.nickname = config["main"].get("nickname", "pajbot") self.timezone = config["main"].get("timezone", "UTC") if config["main"].getboolean("verified", False): TMI.promote_to_verified() # phrases self.phrases = { "welcome": ["{nickname} {version} running!"], "quit": ["{nickname} {version} shutting down..."] } if "phrases" in config: phrases = config["phrases"] if "welcome" in phrases: self.phrases["welcome"] = phrases["welcome"].splitlines() if "quit" in phrases: self.phrases["quit"] = phrases["quit"].splitlines() TimeManager.init_timezone(self.timezone) # streamer 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:] StreamHelper.init_streamer(self.streamer) log.debug("Loaded config") # do this earlier since schema upgrade can depend on the helix api self.api_client_credentials = ClientCredentials( self.config["twitchapi"]["client_id"], self.config["twitchapi"]["client_secret"], self.config["twitchapi"]["redirect_uri"], ) self.twitch_id_api = TwitchIDAPI(self.api_client_credentials) self.app_token_manager = AppAccessTokenManager(self.twitch_id_api, RedisManager.get()) self.twitch_helix_api = TwitchHelixAPI(RedisManager.get(), self.app_token_manager) self.twitch_v5_api = TwitchKrakenV5API(self.api_client_credentials, RedisManager.get()) self.twitch_legacy_api = TwitchLegacyAPI(self.api_client_credentials, RedisManager.get()) self.twitch_tmi_api = TwitchTMIAPI() self.bot_user_id = self.twitch_helix_api.get_user_id(self.nickname) if self.bot_user_id is None: raise ValueError( "The bot login name you entered under [main] does not exist on twitch." ) self.streamer_user_id = self.twitch_helix_api.get_user_id( self.streamer) if self.streamer_user_id is None: raise ValueError( "The streamer login name you entered under [main] does not exist on twitch." ) # SQL migrations sql_conn = DBManager.engine.connect().connection sql_migratable = DatabaseMigratable(sql_conn) sql_migration = Migration(sql_migratable, pajbot.migration_revisions.db, self) sql_migration.run() # Redis migrations redis_migratable = RedisMigratable(redis_options=redis_options, namespace=self.streamer) redis_migration = Migration(redis_migratable, pajbot.migration_revisions.redis, self) redis_migration.run() # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor(self.on_connect) self.start_time = utils.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self.streamer, self.execute_now) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) ScheduleManager.init() self.users = UserManager() self.decks = DeckManager() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager() # bot access token if "password" in self.config["main"]: log.warning( "DEPRECATED - Using bot password/oauth token from file. " "You should authenticate in web gui using route /bot_login " "and remove password from config file") access_token = self.config["main"]["password"] if access_token.startswith("oauth:"): access_token = access_token[6:] self.bot_token_manager = UserAccessTokenManager( api=None, redis=None, username=self.nickname, user_id=self.bot_user_id, token=UserAccessToken.from_implicit_auth_flow_token( access_token), ) else: self.bot_token_manager = UserAccessTokenManager( api=self.twitch_id_api, redis=RedisManager.get(), username=self.nickname, user_id=self.bot_user_id) self.emote_manager = EmoteManager(self.twitch_v5_api, self.twitch_legacy_api, self.action_queue) self.epm_manager = EpmManager() self.ecount_manager = EcountManager() self.twitter_manager = TwitterManager(self) self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager(socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.websocket_manager = WebSocketManager(self) HandlerManager.trigger("on_managers_loaded") # Commitable managers self.commitable = { "commands": self.commands, "banphrases": self.banphrase_manager } self.execute_every(10 * 60, self.commit_all) self.execute_every(1, self.do_tick) # promote the admin to level 2000 admin = None try: admin = self.config["main"]["admin"] except KeyError: log.warning( "No admin user specified. See the [main] section in the example config for its usage." ) if admin is not None: with self.users.get_user_context(admin) as user: user.level = 2000 # silent mode self.silent = ("flags" in config and "silent" in config["flags"] and config["flags"]["silent"] == "1") or args.silent if self.silent: log.info("Silent mode enabled") # dev mode self.dev = "flags" in config and "dev" in config["flags"] and config[ "flags"]["dev"] == "1" if self.dev: self.version_long = extend_version_if_possible(VERSION) else: self.version_long = VERSION self.irc = IRCManager(self) relay_host = self.config["main"].get("relay_host", None) relay_password = self.config["main"].get("relay_password", None) if relay_host is not None or relay_password is not None: log.warning( "DEPRECATED - Relaybroker support is no longer implemented. relay_host and relay_password are ignored" ) self.reactor.add_global_handler("all_events", self.irc._dispatcher, -10) self.data = { "broadcaster": self.streamer, "version": self.version_long, "version_brief": VERSION, "bot_name": self.nickname, } self.data_cb = { "status_length": self.c_status_length, "stream_status": self.c_stream_status, "bot_uptime": self.c_uptime, "current_time": self.c_current_time, "molly_age_in_years": self.c_molly_age_in_years, }
def __init__(self, config, args=None): self.load_config(config) self.last_ping = datetime.datetime.now() self.last_pong = datetime.datetime.now() self.load_default_phrases() self.db_session = DBManager.create_session() try: subprocess.check_call( ['alembic', 'upgrade', 'head'] + ['--tag="{0}"'.format(' '.join(sys.argv[1:]))]) except subprocess.CalledProcessError: log.exception('aaaa') log.error( 'Unable to call `alembic upgrade head`, this means the database could be out of date. Quitting.' ) sys.exit(1) except PermissionError: log.error( 'No permission to run `alembic upgrade head`. This means your user probably doesn\'t have execution rights on the `alembic` binary.' ) log.error( 'The error can also occur if it can\'t find `alembic` in your PATH, and instead tries to execute the alembic folder.' ) sys.exit(1) except FileNotFoundError: log.error( 'Could not found an installation of alembic. Please install alembic to continue.' ) sys.exit(1) except: log.exception('Unhandled exception when calling db update') sys.exit(1) # Actions in this queue are run in a separate thread. # This means actions should NOT access any database-related stuff. self.action_queue = ActionQueue() self.action_queue.start() self.reactor = irc.client.Reactor() self.start_time = datetime.datetime.now() ActionParser.bot = self HandlerManager.init_handlers() self.socket_manager = SocketManager(self) self.stream_manager = StreamManager(self) StreamHelper.init_bot(self, self.stream_manager) self.users = UserManager() self.decks = DeckManager().reload() self.module_manager = ModuleManager(self.socket_manager, bot=self).load() self.commands = CommandManager(socket_manager=self.socket_manager, module_manager=self.module_manager, bot=self).load() self.filters = FilterManager().reload() self.banphrase_manager = BanphraseManager(self).load() self.timer_manager = TimerManager(self).load() self.kvi = KVIManager().reload() self.emotes = EmoteManager(self).reload() self.twitter_manager = TwitterManager(self).reload() self.duel_manager = DuelManager(self) HandlerManager.trigger('on_managers_loaded') # Reloadable managers self.reloadable = { 'filters': self.filters, 'kvi': self.kvi, 'emotes': self.emotes, 'twitter': self.twitter_manager, 'decks': self.decks, } # Commitable managers self.commitable = { 'commands': self.commands, 'filters': self.filters, 'kvi': self.kvi, 'emotes': self.emotes, 'twitter': self.twitter_manager, 'decks': self.decks, 'users': self.users, 'banphrases': self.banphrase_manager, } self.execute_every(10 * 60, self.commit_all) self.execute_every( 30, lambda: self.connection_manager.get_main_conn().ping( 'tmi.twitch.tv')) try: self.admin = self.config['main']['admin'] except KeyError: log.warning( 'No admin user specified. See the [main] section in config.example.ini for its usage.' ) if self.admin: self.users[self.admin].level = 2000 self.parse_version() self.connection_manager = ConnectionManager(self.reactor, self, TMI.message_limit, streamer=self.streamer) chub = self.config['main'].get('control_hub', None) if chub is not None: self.control_hub = ConnectionManager(self.reactor, self, TMI.message_limit, streamer=chub, backup_conns=1) log.info('start pls') else: self.control_hub = None twitch_client_id = None twitch_oauth = None if 'twitchapi' in self.config: twitch_client_id = self.config['twitchapi'].get('client_id', None) twitch_oauth = self.config['twitchapi'].get('oauth', None) self.twitchapi = TwitchAPI(twitch_client_id, twitch_oauth) self.reactor.add_global_handler('all_events', self._dispatcher, -10) self.whisper_manager = WhisperConnectionManager( self.reactor, self, self.streamer, TMI.whispers_message_limit, TMI.whispers_limit_interval) self.whisper_manager.start(accounts=[{ 'username': self.nickname, 'oauth': self.password, 'can_send_whispers': self.config.getboolean('main', 'add_self_as_whisper_account') }]) self.ascii_timeout_duration = 120 self.msg_length_timeout_duration = 120 self.data = {} self.data_cb = {} self.url_regex = re.compile(self.url_regex_str, re.IGNORECASE) self.data['broadcaster'] = self.streamer self.data['version'] = self.version self.data_cb['status_length'] = self.c_status_length self.data_cb['stream_status'] = self.c_stream_status self.data_cb['bot_uptime'] = self.c_uptime self.silent = True if args.silent else self.silent if self.silent: log.info('Silent mode enabled') self.reconnection_interval = 5 """ For actions that need to access the main thread, we can use the mainthread_queue. """ self.mainthread_queue = ActionQueue() self.execute_every(1, self.mainthread_queue.parse_action) self.websocket_manager = WebSocketManager(self) """ Update chatters every `update_chatters_interval' minutes. By default, this is set to run every 5 minutes. """ self.execute_every(self.update_chatters_interval * 60, self.action_queue.add, (self.update_chatters_stage1, )) try: if self.config['twitchapi']['update_subscribers'] == '1': self.execute_every(30 * 60, self.action_queue.add, (self.update_subscribers_stage1, )) except: pass # XXX: TEMPORARY UGLY CODE HandlerManager.add_handler('on_user_gain_tokens', self.on_user_gain_tokens)
def init(args): import configparser import datetime import logging import subprocess import sys from flask import request from flask import session from flask.ext.scrypt import generate_random_salt import pajbot.web.common import pajbot.web.routes from pajbot.bot import Bot from pajbot.managers.db import DBManager from pajbot.managers.redis import RedisManager from pajbot.managers.time import TimeManager from pajbot.models.module import ModuleManager from pajbot.models.sock import SocketClientManager from pajbot.streamhelper import StreamHelper from pajbot.tbutil import load_config from pajbot.web.models import errors from pajbot.web.utils import download_logo log = logging.getLogger(__name__) log.info('XD') config = configparser.ConfigParser() config = load_config(args.config) config.read('webconfig.ini') if 'web' not in config: log.error('Missing [web] section in config.ini') sys.exit(1) if 'pleblist_password_salt' not in config['web']: salt = generate_random_salt() config.set('web', 'pleblist_password_salt', salt.decode('utf-8')) if 'secret_key' not in config['web']: salt = generate_random_salt() config.set('web', 'secret_key', salt.decode('utf-8')) if 'logo' not in config['web']: res = download_logo(config['main']['streamer']) if res: config.set('web', 'logo', 'set') StreamHelper.init_web(config['main']['streamer']) redis_options = {} if 'redis' in config: redis_options = config._sections['redis'] RedisManager.init(**redis_options) with open(args.config, 'w') as configfile: config.write(configfile) app.bot_modules = config['web'].get('modules', '').split() app.bot_commands_list = [] app.bot_config = config app.secret_key = config['web']['secret_key'] if 'sock' in config and 'sock_file' in config['sock']: SocketClientManager.init(config['sock']['sock_file']) DBManager.init(config['main']['db']) TimeManager.init_timezone(config['main'].get('timezone', 'UTC')) app.module_manager = ModuleManager(None).load() pajbot.web.routes.admin.init(app) pajbot.web.routes.api.init(app) pajbot.web.routes.base.init(app) pajbot.web.common.filters.init(app) pajbot.web.common.assets.init(app) pajbot.web.common.tasks.init(app) pajbot.web.common.menu.init(app) app.register_blueprint(pajbot.web.routes.clr.page) errors.init(app) pajbot.web.routes.clr.config = config version = Bot.version last_commit = '' commit_number = 0 try: current_branch = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode('utf8').strip() latest_commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf8').strip()[:8] commit_number = subprocess.check_output(['git', 'rev-list', 'HEAD', '--count']).decode('utf8').strip() last_commit = subprocess.check_output(['git', 'log', '-1', '--format=%cd']).decode('utf8').strip() version = '{0} DEV ({1}, {2}, commit {3})'.format(version, current_branch, latest_commit, commit_number) except: pass default_variables = { 'version': version, 'last_commit': last_commit, 'commit_number': commit_number, 'bot': { 'name': config['main']['nickname'], }, 'site': { 'domain': config['web']['domain'], 'deck_tab_images': config.getboolean('web', 'deck_tab_images'), 'websocket': { 'host': config['websocket'].get('host', config['web']['domain']), 'port': config['websocket']['port'], 'ssl': config.getboolean('websocket', 'ssl') } }, 'streamer': { 'name': config['web']['streamer_name'], 'full_name': config['main']['streamer'] }, 'modules': app.bot_modules, 'request': request, 'session': session, 'google_analytics': config['web'].get('google_analytics', None), } if 'streamtip' in config: default_variables['streamtip_data'] = { 'client_id': config['streamtip']['client_id'], 'redirect_uri': config['streamtip']['redirect_uri'], } else: default_variables['streamtip_data'] = { 'client_id': 'MISSING', 'redirect_uri': 'MISSING', } if 'twitchalerts' in config: default_variables['twitchalerts_data'] = { 'client_id': config['twitchalerts']['client_id'], 'redirect_uri': config['twitchalerts']['redirect_uri'], } else: default_variables['twitchalerts_data'] = { 'client_id': 'MISSING', 'redirect_uri': 'MISSING', } @app.context_processor def current_time(): current_time = {} current_time['current_time'] = datetime.datetime.now() return current_time @app.context_processor def inject_default_variables(): return default_variables
else: cnt = s.rfind(dsep) if cnt > 0: rhs = dsep + s[cnt + 1:] s = s[:cnt] else: rhs = '' splt = '' while s != '': splt = s[-3:] + tsep + splt s = s[:-3] return lhs + splt[:-1] + rhs module_manager = ModuleManager(None).load() nav_bar_header = [] nav_bar_header.append(('/', 'home', 'Home')) nav_bar_header.append(('/commands/', 'commands', 'Commands')) if 'deck' in module_manager: nav_bar_header.append(('/decks/', 'decks', 'Decks')) if config['main']['nickname'] not in ['scamazbot']: nav_bar_header.append(('/points/', 'points', 'Points')) nav_bar_header.append(('/stats/', 'stats', 'Stats')) nav_bar_header.append(('/highlights/', 'highlights', 'Highlights')) if 'pleblist' in modules: nav_bar_header.append(('/pleblist/history/', 'pleblist', 'Pleblist')) nav_bar_admin_header = [] nav_bar_admin_header.append(('/', 'home', 'Home'))
def modules_edit(module_id, **options): module_manager = ModuleManager(None).load(do_reload=False) current_module = find(lambda m: m.ID == module_id, module_manager.all_modules) user = options["user"] if user.level < current_module.CONFIGURE_LEVEL: return ( render_template( "errors/403.html", extra_message= "You do not have permission to configure this module."), 403, ) if current_module is None: return render_template("admin/module_404.html"), 404 sub_modules = [] for module in module_manager.all_modules: module.db_module = None with DBManager.create_session_scope() as db_session: for db_module in db_session.query(Module): module = find(lambda m: m.ID == db_module.id, module_manager.all_modules) if module: module.db_module = db_module if module.PARENT_MODULE == current_module.__class__: sub_modules.append(module) if current_module.db_module is None: return render_template("admin/module_404.html"), 404 if request.method != "POST": settings = None try: settings = json.loads(current_module.db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) return render_template("admin/configure_module.html", module=current_module, sub_modules=sub_modules) form_values = {key: value for key, value in request.form.items()} res = current_module.parse_settings(**form_values) if res is False: return render_template("admin/module_404.html"), 404 current_module.db_module.settings = json.dumps(res) db_session.commit() settings = None try: settings = json.loads(current_module.db_module.settings) except (TypeError, ValueError): pass current_module.load(settings=settings) payload = {"id": current_module.db_module.id} SocketClientManager.send("module.update", payload) AdminLogManager.post("Module edited", user, current_module.NAME) return render_template("admin/configure_module.html", module=current_module, sub_modules=sub_modules)
def init(args): import subprocess import sys from flask import request from flask import session from flask import g from flask_scrypt import generate_random_salt import pajbot.utils import pajbot.web.common import pajbot.web.routes from pajbot.managers.db import DBManager from pajbot.managers.redis import RedisManager from pajbot.managers.schedule import ScheduleManager from pajbot.managers.songrequest_queue_manager import SongRequestQueueManager from pajbot.models.module import ModuleManager from pajbot.models.sock import SocketClientManager from pajbot.streamhelper import StreamHelper from pajbot.utils import load_config from pajbot.web.models import errors from pajbot.web.utils import download_logo from pajbot.web.utils import download_sub_badge ScheduleManager.init() config = load_config(args.config) # ScheduleManager.init() api_client_credentials = ClientCredentials( config["twitchapi"]["client_id"], config["twitchapi"]["client_secret"], config["twitchapi"]["redirect_uri"] ) redis_options = {} if "redis" in config: redis_options = dict(config["redis"]) RedisManager.init(**redis_options) id_api = TwitchIDAPI(api_client_credentials) app_token_manager = AppAccessTokenManager(id_api, RedisManager.get()) twitch_helix_api = TwitchHelixAPI(RedisManager.get(), app_token_manager) twitch_badges_api = TwitchBadgesAPI(RedisManager.get()) if "web" not in config: log.error("Missing [web] section in config.ini") sys.exit(1) if "secret_key" not in config["web"]: salt = generate_random_salt() config.set("web", "secret_key", salt.decode("utf-8")) with open(args.config, "w") as configfile: config.write(configfile) streamer = config["main"]["streamer"] SongRequestQueueManager.init(streamer) streamer_user_id = twitch_helix_api.get_user_id(streamer) if streamer_user_id is None: raise ValueError("The streamer login name you entered under [main] does not exist on twitch.") StreamHelper.init_streamer(streamer, streamer_user_id) try: download_logo(twitch_helix_api, streamer, streamer_user_id) except: log.exception("Error downloading the streamers profile picture") subscriber_badge_version = config["web"].get("subscriber_badge_version", "0") # Specifying a value of -1 in the config will disable sub badge downloading. Useful if you want to keep a custom version of a sub badge for a streamer if subscriber_badge_version != "-1": try: download_sub_badge(twitch_badges_api, streamer, streamer_user_id, subscriber_badge_version) except: log.exception("Error downloading the streamers subscriber badge") SocketClientManager.init(streamer) app.bot_modules = config["web"].get("modules", "").split() app.bot_commands_list = [] app.bot_config = config app.secret_key = config["web"]["secret_key"] app.bot_dev = "flags" in config and "dev" in config["flags"] and config["flags"]["dev"] == "1" DBManager.init(config["main"]["db"]) app.module_manager = ModuleManager(None).load() pajbot.web.routes.admin.init(app) pajbot.web.routes.api.init(app) pajbot.web.routes.base.init(app) pajbot.web.common.filters.init(app) pajbot.web.common.assets.init(app) pajbot.web.common.menu.init(app) app.register_blueprint(pajbot.web.routes.clr.page) errors.init(app, config) pajbot.web.routes.clr.config = config version = VERSION last_commit = None if app.bot_dev: version = extend_version_if_possible(VERSION) try: last_commit = subprocess.check_output(["git", "log", "-1", "--format=%cd"]).decode("utf8").strip() except: log.exception("Failed to get last_commit, will not show last commit") default_variables = { "version": version, "last_commit": last_commit, "bot": {"name": config["main"]["nickname"]}, "site": { "domain": config["web"]["domain"], "deck_tab_images": config.getboolean("web", "deck_tab_images"), "websocket": {"host": config["websocket"].get("host", f"wss://{config['web']['domain']}/clrsocket")}, "songrequestWS": { "host": config["songrequest-websocket"].get( "host", f"wss://{config['web']['domain']}/songrequest_websocket" ) }, }, "streamer": {"name": config["web"]["streamer_name"], "full_name": config["main"]["streamer"]}, "modules": app.bot_modules, "request": request, "session": session, "google_analytics": config["web"].get("google_analytics", None), } @app.context_processor def current_time(): current_time = {} current_time["current_time"] = pajbot.utils.now() return current_time @app.context_processor def inject_default_variables(): return default_variables
def commands_create(**options): session.pop('command_created_id', None) session.pop('command_edited_id', None) if request.method == 'POST': if 'aliases' not in request.form: abort(403) alias_str = request.form.get('aliases', '').replace('!', '').lower() delay_all = request.form.get('cd', Command.DEFAULT_CD_ALL) delay_user = request.form.get('usercd', Command.DEFAULT_CD_USER) level = request.form.get('level', Command.DEFAULT_LEVEL) cost = request.form.get('cost', 0) try: delay_all = int(delay_all) delay_user = int(delay_user) level = int(level) cost = int(cost) except ValueError: abort(403) if len(alias_str) == 0: abort(403) if delay_all < 0 or delay_all > 9999: abort(403) if delay_user < 0 or delay_user > 9999: abort(403) if level < 0 or level > 2000: abort(403) if cost < 0 or cost > 9999999: abort(403) options = { 'delay_all': delay_all, 'delay_user': delay_user, 'level': level, 'cost': cost, } valid_action_types = ['say', 'me', 'whisper', 'reply'] action_type = request.form.get('reply', 'say').lower() if action_type not in valid_action_types: abort(403) response = request.form.get('response', '') if len(response) == 0: abort(403) action = {'type': action_type, 'message': response} options['action'] = action command_manager = CommandManager( socket_manager=None, module_manager=ModuleManager(None).load(), bot=None).load(enabled=None) command_aliases = [] for alias, command in command_manager.items(): command_aliases.append(alias) if command.command and len(command.command) > 0: command_aliases.extend(command.command.split('|')) command_aliases = set(command_aliases) alias_str = alias_str.replace(' ', '').replace('!', '').lower() alias_list = alias_str.split('|') alias_list = [alias for alias in alias_list if len(alias) > 0] if len(alias_list) == 0: return render_template('admin/create_command_fail.html') for alias in alias_list: if alias in command_aliases: return render_template('admin/create_command_fail.html') alias_str = '|'.join(alias_list) command = Command(command=alias_str, **options) command.data = CommandData(command.id) with DBManager.create_session_scope( expire_on_commit=False) as db_session: db_session.add(command) db_session.add(command.data) db_session.commit() db_session.expunge(command) db_session.expunge(command.data) SocketClientManager.send('command.update', {'command_id': command.id}) session['command_created_id'] = command.id return redirect('/admin/commands/', 303) else: return render_template('admin/create_command.html')