示例#1
0
    def on_usernotice(self, chatconn, event):
        tags = {
            tag["key"]: tag["value"] if tag["value"] is not None else ""
            for tag in event.tags
        }

        if event.target != self.channel:
            return

        id = tags["user-id"]
        login = tags["login"]
        name = tags["display-name"]

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            source = User.from_basics(db_session, UserBasics(id, login, name))
            if event.arguments and len(event.arguments) > 0:
                msg = event.arguments[0]
            else:
                msg = None  # e.g. user didn't type an extra message to share with the streamer

            with new_message_processing_scope(self):
                HandlerManager.trigger("on_usernotice",
                                       source=source,
                                       message=msg,
                                       tags=tags)

                if msg is not None:
                    self.parse_message(msg, source, event, tags)
示例#2
0
    def on_pubmsg(self, chatconn, event):
        tags = {
            tag["key"]: tag["value"] if tag["value"] is not None else ""
            for tag in event.tags
        }

        id = tags["user-id"]
        login = event.source.user
        name = tags["display-name"]

        if event.source.user == self.nickname:
            return False

        if self.streamer == "forsen":
            if "zonothene" in login:
                self._ban(login)
                return True

            raw_m = event.arguments[0].lower()
            if raw_m.startswith("!lastseen forsen"):
                if len(raw_m) > len("!lastseen forsen2"):
                    if raw_m[16] == " ":
                        return True
                else:
                    return True

            if raw_m.startswith("!lastseen @forsen"):
                if len(raw_m) > len("!lastseen @forsen2"):
                    if raw_m[17] == " ":
                        return True
                else:
                    return True

        if self.streamer == "nymn":
            if "hades_k" in login:
                self.timeout_login(login, 3600, reason="Bad username")
                return True

            if "hades_b" in login:
                self.timeout_login(login, 3600, reason="Bad username")
                return True

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            source = User.from_basics(db_session, UserBasics(id, login, name))

            with new_message_processing_scope(self):
                res = HandlerManager.trigger("on_pubmsg",
                                             source=source,
                                             message=event.arguments[0],
                                             tags=tags)
                if res is False:
                    return False

                self.parse_message(event.arguments[0],
                                   source,
                                   event,
                                   tags=tags)
示例#3
0
 def on_redeem(self, redeemer, redeemed_id, user_input):
     if user_input is not None and redeemed_id == self.settings[
             "redeemed_id"]:
         with DBManager.create_session_scope() as db_session:
             user = User.from_basics(db_session, redeemer)
             points = random.randint(self.settings["lower_points"],
                                     self.settings["upper_points"])
             user.points += points
             self.bot.whisper(user, f"You have been given {points}")
示例#4
0
文件: bot.py 项目: mikuski08/pajbot
    def on_whisper(self, chatconn, event):
        tags = {tag["key"]: tag["value"] if tag["value"] is not None else "" for tag in event.tags}

        id = tags["user-id"]
        login = event.source.user
        name = tags["display-name"]

        with DBManager.create_session_scope(expire_on_commit=False) as db_session:
            source = User.from_basics(db_session, UserBasics(id, login, name))
            self.parse_message(event.arguments[0], source, event, tags, whisper=True)
示例#5
0
    def command_masspoints(self, bot, source, message, **rest):
        if not message:
            return False

        pointsArgument = message.split(" ")[0]
        givePoints = 0

        try:
            givePoints = int(pointsArgument)
        except ValueError:
            bot.whisper(source, "Error: You must give an integer")
            return False

        currentChatters = bot.twitch_tmi_api.get_chatter_logins_by_login(
            bot.streamer)
        numUsers = len(currentChatters)
        if not currentChatters:
            bot.say("Error fetching chatters")
            return False

        userBasics = bot.twitch_helix_api.bulk_get_user_basics_by_login(
            currentChatters)

        # Filtering
        userBasics = [e for e in userBasics if e is not None]

        with DBManager.create_session_scope() as db_session:
            # Convert to models
            userModels = [User.from_basics(db_session, e) for e in userBasics]

            for userModel in userModels:
                if userModel.num_lines < 5:
                    continue

                if userModel.subscriber:
                    userModel.points = userModel.points + givePoints * self.settings[
                        "sub_points"]
                else:
                    userModel.points = userModel.points + givePoints

        bot.say(
            f"{source} just gave {numUsers} viewers {givePoints} points each! Enjoy FeelsGoodMan"
        )
示例#6
0
文件: bot.py 项目: yustinuspr/bullbot
    def on_pubmsg(self, chatconn, event):
        tags = {
            tag["key"]: tag["value"] if tag["value"] is not None else ""
            for tag in event.tags
        }

        id = tags["user-id"]
        login = event.source.user
        name = tags["display-name"]

        if event.source.user == self.nickname:
            return False

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            source = User.from_basics(db_session, UserBasics(id, login, name))
            res = HandlerManager.trigger("on_pubmsg",
                                         source=source,
                                         message=event.arguments[0])
            if res is False:
                return False

            self.parse_message(event.arguments[0], source, event, tags=tags)
示例#7
0
    def on_usernotice(self, source, tags, **rest):
        if "msg-id" not in tags:
            return

        if tags["msg-id"] == "resub":
            num_months = -1
            substreak_count = 0
            if "msg-param-months" in tags:
                num_months = int(tags["msg-param-months"])
            if "msg-param-cumulative-months" in tags:
                num_months = int(tags["msg-param-cumulative-months"])
            if "msg-param-streak-months" in tags:
                substreak_count = int(tags["msg-param-streak-months"])
            if "msg-param-should-share-streak" in tags:
                should_share = bool(tags["msg-param-should-share-streak"])
                if not should_share:
                    substreak_count = 0

            if "msg-param-sub-plan" not in tags:
                log.debug(
                    f"subalert msg-id is resub, but missing msg-param-sub-plan: {tags}"
                )
                return

            # log.debug('msg-id resub tags: {}'.format(tags))

            # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead
            self.on_resub(source, num_months, tags["msg-param-sub-plan"], None,
                          substreak_count)
            HandlerManager.trigger("on_user_resub",
                                   user=source,
                                   num_months=num_months)
        elif tags["msg-id"] == "subgift":
            num_months = 0
            substreak_count = 0
            if "msg-param-months" in tags:
                num_months = int(tags["msg-param-months"])
            if "msg-param-cumulative-months" in tags:
                num_months = int(tags["msg-param-cumulative-months"])
            if "msg-param-streak-months" in tags:
                substreak_count = int(tags["msg-param-streak-months"])
            if "msg-param-should-share-streak" in tags:
                should_share = bool(tags["msg-param-should-share-streak"])
                if not should_share:
                    substreak_count = 0

            if "display-name" not in tags:
                log.debug(
                    f"subalert msg-id is subgift, but missing display-name: {tags}"
                )
                return

            with DBManager.create_session_scope() as db_session:
                receiver_id = tags["msg-param-recipient-id"]
                receiver_login = tags["msg-param-recipient-user-name"]
                receiver_name = tags["msg-param-recipient-display-name"]
                receiver = User.from_basics(
                    db_session,
                    UserBasics(receiver_id, receiver_login, receiver_name))

                if num_months > 1:
                    # Resub
                    self.on_resub(receiver, num_months,
                                  tags["msg-param-sub-plan"],
                                  tags["display-name"], substreak_count)
                    HandlerManager.trigger("on_user_resub",
                                           user=receiver,
                                           num_months=num_months)
                else:
                    # New sub
                    self.on_new_sub(receiver, tags["msg-param-sub-plan"],
                                    tags["display-name"])
                    HandlerManager.trigger("on_user_sub", user=receiver)
        elif tags["msg-id"] == "sub":
            if "msg-param-sub-plan" not in tags:
                log.debug(
                    f"subalert msg-id is sub, but missing msg-param-sub-plan: {tags}"
                )
                return

            self.on_new_sub(source, tags["msg-param-sub-plan"])
            HandlerManager.trigger("on_user_sub", user=source)
        elif tags["msg-id"] == "giftpaidupgrade":
            self.on_gift_upgrade(source)
        elif tags["msg-id"] == "extendsub":
            self.on_extend_sub(source)
        else:
            log.debug(f"Unhandled msg-id: {tags['msg-id']} - tags: {tags}")
示例#8
0
文件: login.py 项目: sgaweda/troybot
    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)
示例#9
0
    def authorized():
        # First, validate state with CSRF token
        # (CSRF token from request parameter must match token from session)
        state_str = request.args.get("state", None)

        if state_str is None:
            return render_template("login_error.html", return_to="/", detail_msg="State parameter missing"), 400

        try:
            state = json.loads(state_str)
        except JSONDecodeError:
            return render_template("login_error.html", return_to="/", detail_msg="State parameter not valid JSON"), 400

        # we now have a valid state object, we can send the user back to the place they came from
        return_to = state.get("return_to", None)
        if return_to is None:
            # either not present in the JSON at all, or { "return_to": null } (which is the case when you
            # e.g. access /bot_login or /streamer_login directly)
            return_to = "/"

        def login_error(code, detail_msg=None):
            return render_template("login_error.html", return_to=return_to, detail_msg=detail_msg), code

        csrf_token = state.get("csrf_token", None)
        if csrf_token is None:
            return login_error(400, "CSRF token missing from state")

        csrf_token_in_session = session.pop("csrf_token", None)
        if csrf_token_in_session is None:
            return login_error(400, "No CSRF token in session cookie")

        if csrf_token != csrf_token_in_session:
            return login_error(403, "CSRF tokens don't match")

        # determine if we got ?code= or ?error= (success or not)
        # https://tools.ietf.org/html/rfc6749#section-4.1.2
        if "error" in request.args:
            # user was sent back with an error condition
            error_code = request.args["error"]
            optional_error_description = request.args.get("error_description", None)

            if error_code == "access_denied":
                # User pressed "Cancel" button. We don't want to show an error page, instead we will just
                # redirect them to where they were coming from.
                # See also https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for error codes and documentation for them
                return redirect(return_to)

            # All other error conditions, we show an error page.
            if optional_error_description is not None:
                user_detail_msg = f"Error returned from Twitch: {optional_error_description} (code: {error_code})"
            else:
                user_detail_msg = f"Error returned from Twitch (code: {error_code})"

            return login_error(400, user_detail_msg)

        if "code" not in request.args:
            return login_error(400, "No ?code or ?error present on the request")

        # successful authorization
        code = request.args["code"]

        try:
            # gets us an UserAccessToken object
            access_token = app.twitch_id_api.get_user_access_token(code)
        except:
            log.exception("Could not exchange given code for access token with Twitch")
            return login_error(500, "Could not exchange the given code for an access token.")

        user_basics = app.twitch_helix_api.fetch_user_basics_from_authorization(
            (app.api_client_credentials, access_token)
        )

        with DBManager.create_session_scope(expire_on_commit=False) as db_session:
            me = User.from_basics(db_session, user_basics)
            session["user"] = me.jsonify()

        # bot login
        if me.login == app.bot_config["main"]["nickname"].lower():
            redis = RedisManager.get()
            redis.set(f"authentication:user-access-token:{me.id}", json.dumps(access_token.jsonify()))
            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(access_token.scope) < set(streamer_scopes):
                log.info("Streamer logged in but not all scopes present, will not update streamer token")
            else:
                redis = RedisManager.get()
                redis.set(f"authentication:user-access-token:{me.id}", json.dumps(access_token.jsonify()))
                log.info("Successfully updated streamer token in redis")

        return redirect(return_to)
示例#10
0
文件: login.py 项目: MrBean355/pajbot
    def authorized():
        # First, validate state with CSRF token
        # (CSRF token from request parameter must match token from session)
        state_str = request.args.get("state", None)

        if state_str is None:
            return render_template("login_error.html",
                                   return_to="/",
                                   detail_msg="State parameter missing"), 400

        try:
            state = json.loads(state_str)
        except JSONDecodeError:
            return render_template(
                "login_error.html",
                return_to="/",
                detail_msg="State parameter not valid JSON"), 400

        # we now have a valid state object, we can send the user back to the place they came from
        return_to = state.get("return_to", None)
        if return_to is None:
            # either not present in the JSON at all, or { "return_to": null } (which is the case when you
            # e.g. access /bot_login or /streamer_login directly)
            return_to = "/"

        def login_error(code, detail_msg=None):
            return render_template("login_error.html",
                                   return_to=return_to,
                                   detail_msg=detail_msg), code

        csrf_token = state.get("csrf_token", None)
        if csrf_token is None:
            return login_error(400, "CSRF token missing from state")

        csrf_token_in_session = session.pop("csrf_token", None)
        if csrf_token_in_session is None:
            return login_error(400, "No CSRF token in session cookie")

        if csrf_token != csrf_token_in_session:
            return login_error(403, "CSRF tokens don't match")

        # determine if we got ?code= or ?error= (success or not)
        # https://tools.ietf.org/html/rfc6749#section-4.1.2
        if "error" in request.args:
            # user was sent back with an error condition
            error_code = request.args["error"]
            optional_error_description = request.args.get(
                "error_description", None)

            if error_code == "access_denied":
                # User pressed "Cancel" button. We don't want to show an error page, instead we will just
                # redirect them to where they were coming from.
                # See also https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for error codes and documentation for them
                return redirect(return_to)

            # All other error conditions, we show an error page.
            if optional_error_description is not None:
                user_detail_msg = f"Error returned from Twitch: {optional_error_description} (code: {error_code})"
            else:
                user_detail_msg = f"Error returned from Twitch (code: {error_code})"

            return login_error(400, user_detail_msg)

        if "code" not in request.args:
            return login_error(400,
                               "No ?code or ?error present on the request")

        # successful authorization
        code = request.args["code"]

        try:
            # gets us an UserAccessToken object
            access_token = app.twitch_id_api.get_user_access_token(code)
        except:
            log.exception(
                "Could not exchange given code for access token with Twitch")
            return login_error(
                500, "Could not exchange the given code for an access token.")

        user_basics = app.twitch_helix_api.fetch_user_basics_from_authorization(
            (app.api_client_credentials, access_token))

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            me = User.from_basics(db_session, user_basics)
            session["user"] = me.jsonify()

        required_user_scopes = {
            # Bot
            app.bot_user.login.lower(): set(bot_scopes),
            # Streamer
            app.streamer.login.lower(): set(streamer_scopes),
        }

        if me.login in required_user_scopes:
            # Stop the user from accidentally downgrading their scopes when logging in with
            # the Streamer or Bot account on the normal /login page
            required_scopes = required_user_scopes[me.login]

            if set(access_token.scope) < required_scopes:
                log.info(
                    f"User {me.login} logged in but not all of their required scopes are present, will not update redis token"
                )
            else:
                redis = RedisManager.get()
                redis.set(f"authentication:user-access-token:{me.id}",
                          json.dumps(access_token.jsonify()))
                log.info(
                    f"Successfully updated {me.login}'s token in redis to {access_token.scope}"
                )

        return redirect(return_to)
示例#11
0
    def __init__(self, config: cfg.Config, args: argparse.Namespace) -> None:
        self.args = args
        self.config = config

        ScheduleManager.init()

        DBManager.init(config["main"]["db"])

        # redis
        redis_options = config.get("redis", {})
        RedisManager.init(redis_options)
        utils.wait_for_redis_data_loaded(RedisManager.get())

        if cfg.get_boolean(config["main"], "verified", False):
            self.tmi_rate_limits = TMIRateLimits.VERIFIED
        elif cfg.get_boolean(config["main"], "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

        self.bot_domain = config["web"]["domain"]

        # do this earlier since schema upgrade can depend on the helix api
        self.api_client_credentials = ClientCredentials(
            config["twitchapi"]["client_id"],
            config["twitchapi"]["client_secret"],
            config["twitchapi"].get(
                "redirect_uri",
                f"https://{config['web']['domain']}/login/authorized"),
        )

        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 = TwitchHelixAPI(
            RedisManager.get(), self.app_token_manager)

        self.streamer: UserBasics = cfg.load_streamer(config,
                                                      self.twitch_helix_api)
        self.channel = f"#{self.streamer.login}"

        self.streamer_display: str = self.streamer.name
        if "streamer_name" in config["web"]:
            # Override the streamer display name
            self.streamer_display = config["web"]["streamer_name"]

        self.bot_user: UserBasics = cfg.load_bot(config, self.twitch_helix_api)

        self.control_hub_user: Optional[UserBasics] = self._load_control_hub(
            config)
        self.control_hub_channel: Optional[str] = None
        if self.control_hub_user:
            self.control_hub_channel = f"#{self.control_hub_user.login}"

        log.debug("Loaded config")

        self.streamer_access_token_manager = UserAccessTokenManager(
            api=self.twitch_id_api,
            redis=RedisManager.get(),
            username=self.streamer.login,
            user_id=self.streamer.id,
        )

        StreamHelper.init_streamer(self.streamer.login, self.streamer.id,
                                   self.streamer.name)

        # 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.login)
        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.login,
                                            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 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 = config["main"]["password"]

            if access_token.startswith("oauth:"):
                access_token = access_token[6:]

            self.bot_token_manager = UserAccessTokenManager(
                api=None,
                redis=None,
                username=self.bot_user.login,
                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.bot_user.login,
                user_id=self.bot_user.id,
            )

        self.emote_manager = EmoteManager(self.twitch_helix_api,
                                          self.action_queue)
        self.epm_manager = EpmManager()
        self.ecount_manager = EcountManager()
        self.twitter_manager = cfg.load_twitter_manager(config)(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)

        admin: Optional[UserBasics] = self._load_admin(config)

        if 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.from_basics(db_session, admin)
                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 = config["main"].get("relay_host", None)
        relay_password = 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.login,
            "version": self.version_long,
            "version_brief": VERSION,
            "bot_name": self.bot_user.login,
            "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.bot_user.login})"

        self.thread_locals = threading.local()

        self.subs_only = False