<commit_before>import logging

from requests import HTTPError
from sqlalchemy import text

from pajbot.apiwrappers.authentication.token_manager import UserAccessTokenManager, NoTokenError
from pajbot.managers.db import DBManager
from pajbot.managers.redis import RedisManager
from pajbot.managers.schedule import ScheduleManager
from pajbot.models.command import Command, CommandExample
from pajbot.modules import BaseModule
from pajbot.utils import time_method

log = logging.getLogger(__name__)


class SubscriberFetchModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Subscriber fetch"
    DESCRIPTION = "Fetches a list of subscribers and updates the database"
    ENABLED_DEFAULT = True
    CATEGORY = "Internal"
    HIDDEN = True
    SETTINGS = []

    def update_subs_cmd(self, bot, source, **rest):
        # TODO if you wanted to improve this: Provide the user with feedback
        #   whether the update succeeded, and if yes, how many users were updated
        bot.whisper(source, "Reloading list of subscribers...")
        bot.action_queue.submit(self._update_subscribers)

    @time_method
    def _update_subscribers(self):
        access_token_manager = UserAccessTokenManager(
            api=self.bot.twitch_id_api,
            redis=RedisManager.get(),
            username=self.bot.streamer,
            user_id=self.bot.streamer_user_id,
        )

        try:
            subscriber_ids = self.bot.twitch_helix_api.fetch_all_subscribers(
                self.bot.streamer_user_id, access_token_manager
            )
        except NoTokenError:
            log.warning(
                "Cannot fetch subscribers because no streamer token is present. "
                "Have the streamer log in with the /streamer_login web route to enable subscriber fetch."
            )
            return
        except HTTPError as e:
            if e.response.status_code == 401:
                log.warning(
                    "Cannot fetch subscribers because the streamer token does not grant access to the "
                    "subscribers list. Have the streamer log in again with the /streamer_login route"
                )
                return
            else:
                raise

        user_basics = self.bot.twitch_helix_api.bulk_get_user_basics_by_id(subscriber_ids)
        # filter out deleted/invalid users
        user_basics = [e for e in user_basics if e is not None]

        # remove broadcaster from sub count
        self.bot.kvi["active_subs"].set(len(user_basics) - 1)

        with DBManager.create_session_scope() as db_session:
            db_session.execute(
                text(
                    """
CREATE TEMPORARY TABLE subscribers(
    id TEXT PRIMARY KEY NOT NULL,
    login TEXT NOT NULL,
    name TEXT NOT NULL
)
ON COMMIT DROP"""
                )
            )

            db_session.execute(
                text("INSERT INTO subscribers(id, login, name) VALUES (:id, :login, :name)"),
                [basics.jsonify() for basics in user_basics],
            )
<commit_msg>Fix subscriber refresh for case when broadcaster has no sub program<commit_after>import logging

from requests import HTTPError
from sqlalchemy import text

from pajbot.apiwrappers.authentication.token_manager import UserAccessTokenManager, NoTokenError
from pajbot.managers.db import DBManager
from pajbot.managers.redis import RedisManager
from pajbot.managers.schedule import ScheduleManager
from pajbot.models.command import Command, CommandExample
from pajbot.modules import BaseModule
from pajbot.streamhelper import StreamHelper
from pajbot.utils import time_method

log = logging.getLogger(__name__)


class SubscriberFetchModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Subscriber fetch"
    DESCRIPTION = "Fetches a list of subscribers and updates the database"
    ENABLED_DEFAULT = True
    CATEGORY = "Internal"
    HIDDEN = True
    SETTINGS = []

    def update_subs_cmd(self, bot, source, **rest):
        # TODO if you wanted to improve this: Provide the user with feedback
        #   whether the update succeeded, and if yes, how many users were updated
        bot.whisper(source, "Reloading list of subscribers...")
        bot.action_queue.submit(self._update_subscribers)

    @time_method
    def _update_subscribers(self):
        access_token_manager = UserAccessTokenManager(
            api=self.bot.twitch_id_api,
            redis=RedisManager.get(),
            username=self.bot.streamer,
            user_id=self.bot.streamer_user_id,
        )

        try:
            subscriber_ids = self.bot.twitch_helix_api.fetch_all_subscribers(
                self.bot.streamer_user_id, access_token_manager
            )
        except NoTokenError:
            log.warning(
                "Cannot fetch subscribers because no streamer token is present. "
                "Have the streamer log in with the /streamer_login web route to enable subscriber fetch."
            )
            return
        except HTTPError as e:
            if e.response.status_code == 401:
                log.warning(
                    "Cannot fetch subscribers because the streamer token does not grant access to the "
                    "subscribers list. Have the streamer log in again with the /streamer_login route"
                )
                return
            else:
                raise

        user_basics = self.bot.twitch_helix_api.bulk_get_user_basics_by_id(subscriber_ids)
        # filter out deleted/invalid users
        user_basics = [e for e in user_basics if e is not None]

        # count how many subs we have (we don't want to count the broadcaster with his permasub)
        sub_count = sum(1 for basics in user_basics if basics.id != StreamHelper.get_streamer_id())
        self.bot.kvi["active_subs"].set(sub_count)

        with DBManager.create_session_scope() as db_session:
            db_session.execute(
                text(
                    """
CREATE TEMPORARY TABLE subscribers(
    id TEXT PRIMARY KEY NOT NULL,
    login TEXT NOT NULL,
    name TEXT NOT NULL
)
ON COMMIT DROP"""
                )
            )

            if len(user_basics) > 0:
                # The precondition check is to prevent an exception,
                # if len(user_basics) was 0, then we would try to execute this SQL without any values,
                # which would then fail.
                # len(user_basics) can be 0 if the broadcaster does not have a subscription program.
                db_session.execute(
                    text("INSERT INTO subscribers(id, login, name) VALUES (:id, :login, :name)"),
                    [basics.jsonify() for basics in user_basics],
                )

{'fraction_non_alphanumeric': 0.06508787607983318, 'fraction_numerical': 0.0019362526064938934, 'mean_word_length': 2.270823185582075, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1346239', 'n_tokens_mistral': 1920, 'n_tokens_neox': 1813, 'n_words': 568}