def spotify_auth(): try: resp = spotify.authorized_response(spotify=True) except OAuthException as e: log.error(e) log.exception("An exception was caught while authorizing") next_url = get_next_url(request, "state") return redirect(next_url) except Exception as e: log.error(e) log.exception("Unhandled exception while authorizing") return render_template("login_error.html") session["spotify_token"] = (resp["access_token"], ) if resp is None: if "error" in request.args and "error_description" in request.args: log.warning( f"Access denied: reason={request.args['error']}, error={request.args['error_description']}" ) next_url = get_next_url(request, "state") return redirect(next_url) elif type(resp) is OAuthException: log.warning(resp.message) log.warning(resp.data) log.warning(resp.type) next_url = get_next_url(request, "state") return redirect(next_url) data = f'{app.bot_config["spotify"]["client_id"]}:{app.bot_config["spotify"]["client_secret"]}' encoded = str(base64.b64encode(data.encode("utf-8")), "utf-8") headers = {"Authorization": f"Basic {encoded}"} me_api_response = spotify.get("me", headers=headers) redis = RedisManager.get() token_json = UserAccessToken.from_api_response(resp).jsonify() redis.set( f"authentication:spotify-access-token:{me_api_response.data['id']}", json.dumps(token_json)) redis.set( f"authentication:user-refresh-token:{me_api_response.data['id']}", token_json["refresh_token"]) log.info( f"Successfully updated spotify token in redis for user {me_api_response.data['id']}" ) next_url = get_next_url(request, "state") return redirect(next_url)
def refresh_user_access_token(self, refresh_token): response = self.post( "/oauth2/token", { "client_id": self.client_credentials.client_id, "client_secret": self.client_credentials.client_secret, "grant_type": "refresh_token", "refresh_token": refresh_token, }, ) # response = # { # "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxx", # "expires_in": 14346, # "refresh_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", # "scope": [ # "user:read:email" # ], # "token_type": "bearer" # } return UserAccessToken.from_api_response(response)
def get_user_access_token(self, code): response = self.post( "/oauth2/token", { "client_id": self.client_credentials.client_id, "client_secret": self.client_credentials.client_secret, "code": code, "redirect_uri": self.client_credentials.redirect_uri, "grant_type": "authorization_code", }, ) # response = # { # "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxx" # "expires_in": 14310, # "refresh_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", # "scope": [ # "user:read:email" # ], # "token_type": "bearer" # } return UserAccessToken.from_api_response(response)
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): 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 authorized(): try: resp = twitch.authorized_response() except OAuthException as e: log.error(e) log.exception("An exception was caught while authorizing") next_url = get_next_url(request, "state") return redirect(next_url) except Exception as e: log.error(e) log.exception("Unhandled exception while authorizing") return render_template("login_error.html") if resp is None: if "error" in request.args and "error_description" in request.args: log.warning(f"Access denied: reason={request.args['error']}, error={request.args['error_description']}") next_url = get_next_url(request, "state") return redirect(next_url) elif type(resp) is OAuthException: log.warning(resp.message) log.warning(resp.data) log.warning(resp.type) next_url = get_next_url(request, "state") return redirect(next_url) session["twitch_token"] = (resp["access_token"],) session["twitch_token_expire"] = time.time() + resp["expires_in"] * 0.75 me_api_response = twitch.get("users") if len(me_api_response.data["data"]) < 1: return render_template("login_error.html") with DBManager.create_session_scope(expire_on_commit=False) as db_session: me = User.from_basics( db_session, UserBasics( me_api_response.data["data"][0]["id"], me_api_response.data["data"][0]["login"], me_api_response.data["data"][0]["display_name"], ), ) session["user"] = me.jsonify() # bot login if me.login == app.bot_config["main"]["nickname"].lower(): redis = RedisManager.get() token_json = UserAccessToken.from_api_response(resp).jsonify() redis.set(f"authentication:user-access-token:{me.id}", json.dumps(token_json)) log.info("Successfully updated bot token in redis") # streamer login if me.login == app.bot_config["main"]["streamer"].lower(): # there's a good chance the streamer will later log in using the normal login button. # we only update their access token if the returned scope containes the special scopes requested # in /streamer_login # We use < to say "if the granted scope is a proper subset of the required scopes", this can be case # for example when the bot is running in its own channel and you use /bot_login, # then the granted scopes will be a superset of the scopes needed for the streamer. # By doing this, both the streamer and bot token will be set if you complete /bot_login with the bot # account, and if the bot is running in its own channel. if set(resp["scope"]) < set(streamer_scopes): log.info("Streamer logged in but not all scopes present, will not update streamer token") else: redis = RedisManager.get() token_json = UserAccessToken.from_api_response(resp).jsonify() redis.set(f"authentication:user-access-token:{me.id}", json.dumps(token_json)) log.info("Successfully updated streamer token in redis") next_url = get_next_url(request, "state") return redirect(next_url)
def authorized(): try: resp = twitch.authorized_response() except OAuthException: log.exception("An exception was caught while authorizing") next_url = get_next_url(request, "state") return redirect(next_url) except: log.exception("Unhandled exception while authorizing") return render_template("login_error.html") if resp is None: if "error" in request.args and "error_description" in request.args: log.warning("Access denied: reason={}, error={}".format( request.args["error"], request.args["error_description"])) next_url = get_next_url(request, "state") return redirect(next_url) elif type(resp) is OAuthException: log.warning(resp.message) log.warning(resp.data) log.warning(resp.type) next_url = get_next_url(request, "state") return redirect(next_url) session["twitch_token"] = (resp["access_token"], ) me = twitch.get("user", headers={"Accept": "application/vnd.twitchtv.v5+json"}) level = 100 with DBManager.create_session_scope() as db_session: db_user = db_session.query(User).filter_by( username=me.data["name"].lower()).one_or_none() if db_user: level = db_user.level session["user"] = { "username": me.data["name"], "username_raw": me.data["display_name"], "level": level } if me.data["name"].lower() == app.bot_config["main"]["nickname"].lower( ): redis = RedisManager.get() bot_id = me.data["_id"] token_json = UserAccessToken.from_api_response(resp).jsonify() redis.set("authentication:user-access-token:{}".format(bot_id), json.dumps(token_json)) log.info("Successfully updated bot token in redis") # streamer login if me.data["name"].lower() == app.bot_config["main"]["streamer"].lower( ): # there's a good chance the streamer will later log in using the normal login button. # we only update their access token if the returned scope containes the special scopes requested # in /streamer_login if set(resp["scope"]) != set(streamer_scopes): log.info( "Streamer logged in but not all scopes present, will not update streamer token" ) else: redis = RedisManager.get() streamer_id = me.data["_id"] token_json = UserAccessToken.from_api_response(resp).jsonify() redis.set( "authentication:user-access-token:{}".format(streamer_id), json.dumps(token_json)) log.info("Successfully updated streamer token in redis") next_url = get_next_url(request, "state") return redirect(next_url)