import sys
import json
unique = {}
start = ['a','f','l','q','u']
end = ['e','k','p','t','z']

if(len(sys.argv) != 4):
    print("========================================================================================================")
    print("SORRY!! Please provide the path to the INPUT json file, the OUTPUT file, alphabet selection number [0-5]")
    print("========================================================================================================")
    print("Example: python3 Fan_in.py  ./dummy.json ./output.txt 2                                                 ")
    print("========================================================================================================")
    sys.exit()

f = open(sys.argv[1])
index = int(sys.argv[3])
if(index < 0 or index > 5):
    print("INDEX should be between 0 and 5 only")
    sys.exit()

for line in f:
    data = json.loads(line)
    try:

        if(data is None or data['created_time'] is None):
            continue
        if(data['message'] is None):
            continue
        if('actor' not in data or 'username' not in data['actor'] or 'transactions' not in data or data['transactions'] is None  or 'target' not in data['transactions'][0] or 'username' not in data['transactions'][0]['target']):
            continue
        tusername = data['transactions'][0]['target']['username']
        username = data['actor']['username']

        ltuser = tusername[0].lower()
        if(index != 5 and (ltuser < start[index] or ltuser > end[index])):
            continue
        if(index == 5 and (ltuser >= 'a' or ltuser <= 'z')):
            continue
        if(tusername not in unique):
            unique[tusername] = {'T':0,'users':set()}
        if(username not in unique[tusername]):
            unique[tusername]['users'].add(username.strip())
        unique[tusername]['T'] += 1

    except Exception as e:
        continue
f.close()

outputfile1 = open(sys.argv[2] + str(index),"w")

for k,v in unique.items():
    s = str(len(v['users']))+ " " + str(v['T'])
    outputfile1.write(s + "\n")
outputfile1.close()


from __future__ import annotations
import datetime
from typing import Optional, Union, TYPE_CHECKING, List, Dict

from . import enums
from .utils import parse_timestamp
from .user import BitLeaderboardUser, PartialUser, User

if TYPE_CHECKING:
    from .http import TwitchHTTP
__all__ = (
    "BitsLeaderboard",
    "Clip",
    "CheerEmote",
    "CheerEmoteTier",
    "GlobalEmote",
    "ChannelEmote",
    "HypeTrainContribution",
    "HypeTrainEvent",
    "BanEvent",
    "FollowEvent",
    "SubscriptionEvent",
    "Marker",
    "VideoMarkers",
    "Game",
    "ModEvent",
    "AutomodCheckMessage",
    "AutomodCheckResponse",
    "Extension",
    "MaybeActiveExtension",
    "ActiveExtension",
    "ExtensionBuilder",
    "Video",
    "Tag",
    "WebhookSubscription",
    "Prediction",
    "Predictor",
    "PredictionOutcome",
    "Schedule",
    "ScheduleSegment",
    "ScheduleCategory",
    "ScheduleVacation",
    "Stream",
    "Team",
    "ChannelTeams",
    "ChannelInfo",
    "Poll",
    "PollChoice",
    "Goal",
    "ChatSettings",
    "Raid",
    "ChatterColor",
    "Timeout",
    "Ban",
    "ShieldStatus",
    "ChatBadge",
    "ChatBadgeVersions",
    "ContentClassificationLabel",
    "CharityValues",
    "CharityCampaign",
    "ChannelFollowerEvent",
    "ChannelFollowingEvent",
)

class BitsLeaderboard:
    """
    Represents a Bits leaderboard from the twitch API.

    Attributes
    ------------
    started_at: Optional[:class:`datetime.datetime`]
        The time the leaderboard started.
    ended_at: Optional[:class:`datetime.datetime`]
        The time the leaderboard ended.
    leaders: List[:class:`BitLeaderboardUser`]
        The current leaders of the Leaderboard.
    """

    __slots__ = "_http", "leaders", "started_at", "ended_at"

    def __init__(self, http: "TwitchHTTP", data: dict):
        self._http = http
        self.started_at = (
            parse_timestamp(data["date_range"]["started_at"]) if data["date_range"]["started_at"] else None
        )
        self.ended_at = parse_timestamp(data["date_range"]["ended_at"]) if data["date_range"]["ended_at"] else None
        self.leaders = [BitLeaderboardUser(http, x) for x in data["data"]]

    def __repr__(self):
        return f"<BitsLeaderboard started_at={self.started_at} ended_at={self.ended_at}>"

class CheerEmoteTier:
    """
    Represents a Cheer Emote tier.

    Attributes
    -----------
    min_bits: :class:`int`
        The minimum bits for the tier
    id: :class:`str`
        The ID of the tier
    colour: :class:`str`
        The colour of the tier
    images: :class:`dict`
        contains two dicts, ``light`` and ``dark``. Each item will have an ``animated`` and ``static`` item,
        which will contain yet another dict, with sizes ``1``, ``1.5``, ``2``, ``3``, and ``4``.
        Ex. ``cheeremotetier.images["light"]["animated"]["1"]``
    can_cheer: :class:`bool`
        Indicates whether emote information is accessible to users.
    show_in_bits_card: :class`bool`
        Indicates whether twitch hides the emote from the bits card.
    """

    __slots__ = "min_bits", "id", "color", "images", "can_cheer", "show_in_bits_card"

    def __init__(self, data: dict):
        self.min_bits: int = data["min_bits"]
        self.id: str = data["id"]
        self.color: str = data["color"]
        self.images = data["images"]  # TODO types
        self.can_cheer: bool = data["can_cheer"]
        self.show_in_bits_card: bool = data["show_in_bits_card"]

    def __repr__(self):
        return f"<CheerEmoteTier id={self.id} min_bits={self.min_bits}>"

class CheerEmote:
    """
    Represents a Cheer Emote

    Attributes
    -----------
    prefix: :class:`str`
        The string used to Cheer that precedes the Bits amount.
    tiers: :class:`~CheerEmoteTier`
        The tiers this Cheer Emote has
    type: :class:`str`
        Shows whether the emote is ``global_first_party``, ``global_third_party``, ``channel_custom``, ``display_only``, or ``sponsored``.
    order: :class:`str`
        Order of the emotes as shown in the bits card, in ascending order.
    last_updated :class:`datetime.datetime`
        The date this cheermote was last updated.
    charitable: :class:`bool`
        Indicates whether this emote provides a charity contribution match during charity campaigns.
    """

    __slots__ = "_http", "prefix", "tiers", "type", "order", "last_updated", "charitable"

    def __init__(self, http: "TwitchHTTP", data: dict):
        self._http = http
        self.prefix: str = data["prefix"]
        self.tiers = [CheerEmoteTier(x) for x in data["tiers"]]
        self.type: str = data["type"]
        self.order: str = data["order"]
        self.last_updated = parse_timestamp(data["last_updated"])
        self.charitable: bool = data["is_charitable"]

    def __repr__(self):
        return f"<CheerEmote prefix={self.prefix} type={self.type} order={self.order}>"

class GlobalEmote:
    """
    Represents a Global Emote

    Attributes
    -----------
    id: :class:`str`
        The ID of the emote.
    name: :class:`str`
        The name of the emote.
    images: :class:`dict`
        Contains the image URLs for the emote. These image URLs will always provide a static (i.e., non-animated) emote image with a light background.
    format: List[:class:`str`]
        The formats that the emote is available in.
    scale: List[:class:`str`]
        The sizes that the emote is available in.
    theme_mode: List[:class:`str`]
        The background themes that the emote is available in.
    """

    __slots__ = ("id", "name", "images", "format", "scale", "theme_mode", "template")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: str = data["id"]
        self.name: str = data["name"]
        self.images: dict = data["images"]
        self.format: List[str] = data["format"]
        self.scale: List[str] = data["scale"]
        self.theme_mode: List[str] = data["theme_mode"]

    def __repr__(self):
        return f"<GlobalEmote id={self.id} name={self.name}"

class ChannelEmote(GlobalEmote):
    """
    Represents a Channel Emote

    Attributes
    -----------
    id: :class:`str`
        The ID of the emote.
    name: :class:`str`
        The name of the emote.
    images: :class:`dict`
        Contains the image URLs for the emote. These image URLs will always provide a static (i.e., non-animated) emote image with a light background.
    tier: :class:`str`
        The subscriber tier at which the emote is unlocked.
    type: :class:`str`
        The type of emote.
    set_id: :class:`str`
        An ID that identifies the emote set that the emote belongs to.
    format: List[:class:`str`]
        The formats that the emote is available in.
    scale: List[:class:`str`]
        The sizes that the emote is available in.
    theme_mode: List[:class:`str`]
        The background themes that the emote is available in.
    """

    __slots__ = ("tier", "type", "set_id")

    def __init__(self, http: "TwitchHTTP", data: dict):
        super().__init__(http, data)
        self.tier: str = data["tier"]
        self.type: str = data["emote_type"]
        self.set_id: str = data["emote_set_id"]

    def __repr__(self):
        return f"<ChannelEmote id={self.id} name={self.name} type={self.type}>"

class Clip:
    """
    Represents a Twitch Clip

    Attributes
    -----------
    id: :class:`str`
        The ID of the clip.
    url: :class:`str`
        The URL of the clip.
    embed_url: :class:`str`
        The URL to embed the clip with.
    broadcaster: :class:`~twitchio.PartialUser`
        The user whose channel the clip was created on.
    creator: :class:`~twitchio.PartialUser`
        The user who created the clip.
    video_id: :class:`str`
        The ID of the video the clip is sourced from.
    game_id: :class:`str`
        The ID of the game that was being played when the clip was created.
    language: :class:`str`
        The language, in an `ISO 639-1 <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>`_ format, of the stream when the clip was created.
    title: :class:`str`
        The title of the clip.
    views: :class:`int`
        The amount of views this clip has.
    created_at: :class:`datetime.datetime`
        When the clip was created.
    thumbnail_url: :class:`str`
        The url of the clip thumbnail.
    duration: :class:`float`
        Duration of the Clip in seconds (up to 0.1 precision).
    vod_offset: Optional[:class:`int`]
        The zero-based offset, in seconds, to where the clip starts in the video (VOD) or stream.
        This can be None if the parent no longer exists
    is_featured: :class:`bool`
        Indicates if the clip is featured or not.
    """

    __slots__ = (
        "id",
        "url",
        "embed_url",
        "broadcaster",
        "creator",
        "video_id",
        "game_id",
        "language",
        "title",
        "views",
        "created_at",
        "thumbnail_url",
        "duration",
        "vod_offset",
        "is_featured",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: str = data["id"]
        self.url: str = data["url"]
        self.embed_url: str = data["embed_url"]
        self.broadcaster = PartialUser(http, data["broadcaster_id"], data["broadcaster_name"])
        self.creator = PartialUser(http, data["creator_id"], data["creator_name"])
        self.video_id: str = data["video_id"]
        self.game_id: str = data["game_id"]
        self.language: str = data["language"]
        self.title: str = data["title"]
        self.views: int = data["view_count"]
        self.created_at = parse_timestamp(data["created_at"])
        self.thumbnail_url: str = data["thumbnail_url"]
        self.duration: float = data["duration"]
        self.vod_offset: Optional[int] = data["vod_offset"]
        self.is_featured: bool = data["is_featured"]

    def __repr__(self):
        return f"<Clip id={self.id} broadcaster={self.broadcaster} creator={self.creator}>"

class HypeTrainContribution:
    """
    A Contribution to a Hype Train

    Attributes
    -----------
    total: :class:`int`
        Total aggregated amount of all contributions by the top contributor. If type is ``BITS``, total represents aggregate amount of bits used.
        If type is ``SUBS``, aggregate total where 500, 1000, or 2500 represent tier 1, 2, or 3 subscriptions respectively.
        For example, if top contributor has gifted a tier 1, 2, and 3 subscription, total would be 4000.
    type: :class:`str`
        Identifies the contribution method, either BITS, SUBS or OTHER.
    user: :class:`~twitchio.PartialUser`
        The user making the contribution.
    """

    __slots__ = "total", "type", "user"

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.total: int = data["total"]
        self.type: str = data["type"]
        self.user = PartialUser(http, id=data["user"], name=None)  # we'll see how this goes

    def __repr__(self):
        return f"<HypeTrainContribution total={self.total} type={self.type} user={self.user}>"

class HypeTrainEvent:
    """
    Represents a Hype Train Event (progression)

    Attributes
    -----------
    id: :class:`str`
        The ID of the event.
    event_id: :class:`str`
        The ID of the Hype Train.
    type: :class:`str`
        The type of the event. Currently only ``hypetrain.progression``.
    version: :class:`str`
        The version of the endpoint.
    broadcaster: :class:`~twitchio.PartialUser`
        The user whose channel the Hype Train is occurring on.
    timestamp: :class:`datetime.datetime`
        The time the event happened at.
    cooldown_end_time: :class:`datetime.datetime`
        The time that another Hype Train can happen at.
    expiry: :class:`datetime.datetime`
        The time that this Hype Train expires at.
    started_at: :class:`datetime.datetime`
        The time that this Hype Train started at.
    last_contribution: :class:`HypeTrainContribution`
        The last contribution to this Hype Train.
    level: :class:`int`
        The level reached on this Hype Train (1-5).
    top_contributions: List[:class:`HypeTrainContribution`]
        The top contributors to the Hype Train.
    contributions_total: :class:`int`
        The total score towards completing the goal.
    goal: :class:`int`
        The goal for the next Hype Train level
    """

    __slots__ = (
        "id",
        "type",
        "timestamp",
        "version",
        "broadcaster",
        "expiry",
        "event_id",
        "goal",
        "level",
        "started_at",
        "top_contributions",
        "contributions_total",
        "cooldown_end_time",
        "last_contribution",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: str = data["id"]
        self.event_id: str = data["event_data"]["id"]
        self.type: str = data["event_type"]
        self.version: str = data["version"]
        self.broadcaster = PartialUser(http, id=data["event_data"]["broadcaster_id"], name=None)
        self.timestamp = parse_timestamp(data["event_timestamp"])
        self.cooldown_end_time = parse_timestamp(data["event_data"]["cooldown_end_time"])
        self.expiry = parse_timestamp(data["expires_at"])
        self.started_at = parse_timestamp(data["event_data"]["started_at"])
        self.last_contribution = HypeTrainContribution(http, data["event_data"]["last_contribution"])
        self.level: int = data["event_data"]["level"]
        self.top_contributions = [HypeTrainContribution(http, x) for x in data["event_data"]["top_contributions"]]
        self.contributions_total: int = data["event_data"]["total"]
        self.goal: int = data["event_data"]["goal"]

    def __repr__(self):
        return f"<HypeTrainEvent id={self.id} type={self.type} level={self.level} broadcaster={self.broadcaster}>"

class BanEvent:
    """
    This has been deprecated.

    Represents a user being banned from a channel.

    Attributes
    -----------
    id: :class:`str`
        The event ID.
    type: :class:`str`
        Type of ban event. Either ``moderation.user.ban`` or ``moderation.user.unban``.
    timestamp: :class:`datetime.datetime`
        The time the action occurred at.
    version: :class:`float`
        The version of the endpoint.
    broadcaster: :class:`~twitchio.PartialUser`
        The user whose channel the ban/unban occurred on.
    user: :class:`~twichio.PartialUser`
        The user who was banned/unbanned.
    moderator: :class:`~twitchio.PartialUser`
        The user who performed the action.
    expires_at: Optional[:class:`datetime.datetime`]
        When the ban expires.
    reason: :class:`str`
        The reason the moderator banned/unbanned the user.
    """

    __slots__ = "id", "type", "timestamp", "version", "broadcaster", "user", "expires_at", "moderator", "reason"

    def __init__(self, http: "TwitchHTTP", data: dict, broadcaster: Optional[Union[PartialUser, User]]):
        self.id: str = data["id"]
        self.type: str = data["event_type"]
        self.timestamp = parse_timestamp(data["event_timestamp"])
        self.version: float = float(data["version"])
        self.reason: str = data["event_data"]["reason"]
        self.broadcaster = broadcaster or PartialUser(
            http, data["event_data"]["broadcaster_id"], data["event_data"]["broadcaster_name"]
        )
        self.user = PartialUser(http, data["event_data"]["user_id"], data["event_data"]["user_name"])
        self.moderator = PartialUser(http, data["event_data"]["moderator_id"], data["event_data"]["moderator_name"])
        self.expires_at = (
            parse_timestamp(data["event_data"]["expires_at"]) if data["event_data"]["expires_at"] else None
        )

    def __repr__(self):
        return f"<BanEvent id={self.id} type={self.type} broadcaster={self.broadcaster} user={self.user}>"

class FollowEvent:
    """
    Represents a Follow Event.

    Attributes
    -----------
    from_user: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user that followed another user.
    to_user: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user that was followed.
    followed_at: :class:`datetime.datetime`
        When the follow happened.
    """

    __slots__ = "from_user", "to_user", "followed_at"

    def __init__(
        self,
        http: "TwitchHTTP",
        data: dict,
        from_: Union[User, PartialUser] = None,
        to: Union[User, PartialUser] = None,
    ):
        self.from_user: Union[User, PartialUser] = from_ or PartialUser(http, data["from_id"], data["from_name"])
        self.to_user: Union[User, PartialUser] = to or PartialUser(http, data["to_id"], data["to_name"])
        self.followed_at = parse_timestamp(data["followed_at"])

    def __repr__(self):
        return f"<FollowEvent from_user={self.from_user} to_user={self.to_user} followed_at={self.followed_at}>"

class ChannelFollowerEvent:
    """
    Represents a ChannelFollowEvent Event.

    Attributes
    -----------
    user: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user that followed another user.
    followed_at: :class:`datetime.datetime`
        When the follow happened.
    """

    __slots__ = "user", "followed_at"

    def __init__(
        self,
        http: "TwitchHTTP",
        data: dict,
    ):
        self.user: Union[User, PartialUser] = PartialUser(http, data["user_id"], data["user_login"])
        self.followed_at = parse_timestamp(data["followed_at"])

    def __repr__(self):
        return f"<ChannelFollowerEvent user={self.user} followed_at={self.followed_at}>"

class ChannelFollowingEvent:
    """
    Represents a ChannelFollowEvent Event.

    Attributes
    -----------
    broadcaster: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user that is following another user.
    followed_at: :class:`datetime.datetime`
        When the follow happened.
    """

    __slots__ = "broadcaster", "followed_at"

    def __init__(
        self,
        http: "TwitchHTTP",
        data: dict,
    ):
        self.broadcaster: Union[User, PartialUser] = PartialUser(
            http, data["broadcaster_id"], data["broadcaster_login"]
        )
        self.followed_at = parse_timestamp(data["followed_at"])

    def __repr__(self):
        return f"<ChannelFollowerEvent user={self.broadcaster} followed_at={self.followed_at}>"

class SubscriptionEvent:
    """
    Represents a Subscription Event

    Attributes
    -----------
    broadcaster: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user that was subscribed to.
    user: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
        The user who subscribed.
    tier: :class:`int`
        The tier at which the user subscribed. Could be ``1``, ``2``, or ``3``.
    plan_name: :class:`str`
        Name of the description. (twitch docs aren't helpful, if you know what this is specifically please PR :) ).
    gift: :class:`bool`
        Whether the subscription is a gift.
    """

    __slots__ = "broadcaster", "gift", "tier", "plan_name", "user"

    def __init__(
        self,
        http: "TwitchHTTP",
        data: dict,
        broadcaster: Union[User, PartialUser] = None,
        user: Union[User, PartialUser] = None,
    ):
        self.broadcaster: Union[User, PartialUser] = broadcaster or PartialUser(
            http, data["broadcaster_id"], data["broadcaster_name"]
        )
        self.user: Union[User, PartialUser] = user or PartialUser(http, data["user_id"], data["user_name"])
        self.tier: int = round(int(data["tier"]) / 1000)
        self.plan_name: str = data["plan_name"]
        self.gift: bool = data["is_gift"]

    def __repr__(self):
        return (
            f"<SubscriptionEvent broadcaster={self.broadcaster} user={self.user} tier={self.tier} "
            f"plan_name={self.plan_name} gift={self.gift}>"
        )

class Marker:
    """
    Represents a stream Marker

    Attributes
    -----------
    id: :class:`str`
        The ID of the marker.
    created_at: :class:`datetime.datetime`
        When the marker was created.
    description: :class:`str`
        The description of the marker.
    position: :class:`int`
        The position of the marker, in seconds.
    url: Optional[:class:`str`]
        The url that leads to the marker.
    """

    __slots__ = "id", "created_at", "description", "position", "url"

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.created_at = parse_timestamp(data["created_at"])
        self.description: str = data["description"]
        self.position: int = data["position_seconds"]
        self.url: Optional[str] = data.get("URL")

    def __repr__(self):
        return f"<Marker id={self.id} created_at={self.created_at} position={self.position} url={self.url}>"

class VideoMarkers:
    """
    Represents markers contained in a video

    Attributes
    -----------
    id: :class:`str`
        The video id.
    markers: List[:class:`Marker`]
        The markers contained in the video.
    """

    __slots__ = "id", "markers"

    def __init__(self, data: dict):
        self.id: str = data["video_id"]
        self.markers = [Marker(d) for d in data["markers"]]

    def __repr__(self):
        return f"<VideoMarkers id={self.id}>"

class Game:
    """
    Represents a Game on twitch

    Attributes
    -----------
    id: :class:`int`
        Game ID.
    name: :class:`str`
        Game name.
    box_art_url: :class:`str`
        Template URL for the game's box art.
    igdb_id: Optional[:class:`int`]
        The IGDB ID of the game. If this is not available to Twitch it will return None
    """

    __slots__ = "id", "name", "box_art_url", "igdb_id"

    def __init__(self, data: dict):
        self.id: int = int(data["id"])
        self.name: str = data["name"]
        self.box_art_url: str = data["box_art_url"]
        self.igdb_id: Optional[int] = data.get("igdb_id") and int(data["igdb_id"])

    def __repr__(self):
        return f"<Game id={self.id} name={self.name}>"

    def art_url(self, width: int, height: int) -> str:
        """
        Adds width and height into the box art url

        Parameters
        -----------
        width: :class:`int`
            The width of the image
        height: :class:`int`
            The height of the image

        Returns
        --------
            :class:`str`
        """
        return self.box_art_url.format(width=width, height=height)

class ModEvent:
    """
    Represents a mod add/remove action

    Attributes
    -----------
    id: :class:`str`
        The ID of the event.
    type: :class:`~twitchio.ModEventEnum`
        The type of the event.
    timestamp: :class:`datetime.datetime`
        The timestamp of the event.
    version: :class:`str`
        The version of the endpoint.
    broadcaster: Union[:class:`~twitchio.PartialUser`, :class:`~twitchio.User`]
        The user whose channel the event happened on.
    user: :class:`~twitchio.PartialUser`
        The user being removed or added as a moderator.
    """

    __slots__ = "id", "type", "timestamp", "version", "broadcaster", "user"

    def __init__(self, http: "TwitchHTTP", data: dict, broadcaster: Union[PartialUser, User]):
        self.id: str = data["id"]
        self.type = enums.ModEventEnum(value=data["event_type"])
        self.timestamp = parse_timestamp(data["event_timestamp"])
        self.version: str = data["version"]
        self.broadcaster = broadcaster
        self.user = PartialUser(http, data["event_data"]["user_id"], data["event_data"]["user_name"])

    def __repr__(self):
        return f"<ModEvent id={self.id} type={self.type} broadcaster={self.broadcaster} user={self.user}>"

class AutomodCheckMessage:
    """
    Represents the message to check with automod

    Attributes
    -----------
    id: :class:`str`
        Developer-generated identifier for mapping messages to results.
    text: :class:`str`
        Message text.
    user_id: :class:`int`
        User ID of the sender.
    """

    __slots__ = "id", "text", "user_id"

    def __init__(self, id: str, text: str, user: Union[PartialUser, int]):
        self.id = id
        self.text = text
        self.user_id = user.id if isinstance(user, PartialUser) else user

    def _to_dict(self):
        return {"msg_id": self.id, "msg_text": self.text, "user_id": str(self.user_id)}

    def __repr__(self):
        return f"<AutomodCheckMessage id={self.id} user_id={self.user_id}>"

class AutomodCheckResponse:
    """
    Represents the response to a message check with automod

    Attributes
    -----------
    id: :class:`str`
        The message ID passed in the body of the check
    permitted: :class:`bool`
        Indicates if this message meets AutoMod requirements.
    """

    __slots__ = "id", "permitted"

    def __init__(self, data: dict):
        self.id: str = data["msg_id"]
        self.permitted: bool = data["is_permitted"]

    def __repr__(self):
        return f"<AutomodCheckResponse id={self.id} permitted={self.permitted}>"

class Extension:
    """
    Represents an extension for a specified user

    Attributes
    -----------
    id: :class:`str`
        ID of the extension.
    version: :class:`str`
        Version of the extension.
    active: :class:`bool`
        Activation state of the extension, for each extension type (component, overlay, mobile, panel).
    """

    __slots__ = "id", "active", "version", "_x", "_y"

    def __init__(self, data):
        self.id: str = data["id"]
        self.version: str = data["version"]
        self.active: bool = data["active"]
        self._x = None
        self._y = None

    def __repr__(self):
        return f"<Extension id={self.id} version={self.version} active={self.active}>"

    @classmethod
    def new(cls, active: bool, version: str, id: str, x: int = None, y: int = None) -> "Extension":
        self = cls.__new__(cls)
        self.active = active
        self.version = version
        self.id = id
        self._x = x
        self._y = y
        return self

    def _to_dict(self):
        v = {"active": self.active, "id": self.id, "version": self.version}
        if self._x is not None:
            v["x"] = self._x
        if self._y is not None:
            v["y"] = self._y
        return v

class MaybeActiveExtension(Extension):
    """
    Represents an extension for a specified user that could be may be activated

    Attributes
    -----------
    id: :class:`str`
        ID of the extension.
    version: :class:`str`
        Version of the extension.
    name: :class:`str`
        Name of the extension.
    can_activate: :class:`bool`
        Indicates whether the extension is configured such that it can be activated.
    types: List[:class:`str`]
        Types for which the extension can be activated.
    """

    __slots__ = "id", "version", "name", "can_activate", "types"

    def __init__(self, data):
        self.id: str = data["id"]
        self.version: str = data["version"]
        self.name: str = data["name"]
        self.can_activate: bool = data["can_activate"]
        self.types: List[str] = data["type"]

    def __repr__(self):
        return f"<MaybeActiveExtension id={self.id} version={self.version} name={self.name}>"

class ActiveExtension(Extension):
    """
    Represents an active extension for a specified user

    Attributes
    -----------
    id: :class:`str`
        ID of the extension.
    version: :class:`str`
        Version of the extension.
    active: :class:`bool`
        Activation state of the extension.
    name: :class:`str`
        Name of the extension.
    x: :class:`int`
        (Video-component Extensions only) X-coordinate of the placement of the extension. Could be None.
    y: :class:`int`
        (Video-component Extensions only) Y-coordinate of the placement of the extension. Could be None.
    """

    __slots__ = "id", "active", "name", "version", "x", "y"

    def __init__(self, data):
        self.active: bool = data["active"]
        self.id: Optional[str] = data.get("id", None)
        self.version: Optional[str] = data.get("version", None)
        self.name: Optional[str] = data.get("name", None)
        self.x: Optional[int] = data.get("x", None)  # x and y only show for component extensions.
        self.y: Optional[int] = data.get("y", None)

    def __repr__(self):
        return f"<ActiveExtension id={self.id} version={self.version} name={self.name}>"

class ExtensionBuilder:
    """
    Represents an extension to be updated for a specific user

    Attributes
    -----------
    panels: List[:class:`~twitchio.Extension`]
        List of panels to update for an extension.
    overlays: List[:class:`~twitchio.Extension`]
        List of overlays to update for an extension.
    components: List[:class:`~twitchio.Extension`]
        List of components to update for an extension.
    """

    __slots__ = "panels", "overlays", "components"

    def __init__(
        self, panels: List[Extension] = None, overlays: List[Extension] = None, components: List[Extension] = None
    ):
        self.panels = panels or []
        self.overlays = overlays or []
        self.components = components or []

    def _to_dict(self):
        return {
            "panel": {str(x): y._to_dict() for x, y in enumerate(self.panels)},
            "overlay": {str(x): y._to_dict() for x, y in enumerate(self.overlays)},
            "component": {str(x): y._to_dict() for x, y in enumerate(self.components)},
        }

class Video:
    """
    Represents video information

    Attributes
    -----------
    id: :class:`int`
        The ID of the video.
    user: :class:`~twitchio.PartialUser`
        User who owns the video.
    title: :class:`str`
        Title of the video
    description: :class:`str`
        Description of the video.
    created_at: :class:`datetime.datetime`
        Date when the video was created.
    published_at: :class:`datetime.datetime`
       Date when the video was published.
    url: :class:`str`
        URL of the video.
    thumbnail_url: :class:`str`
        Template URL for the thumbnail of the video.
    viewable: :class:`str`
        Indicates whether the video is public or private.
    view_count: :class:`int`
        Number of times the video has been viewed.
    language: :class:`str`
        Language of the video.
    type: :class:`str`
        The type of video.
    duration: :class:`str`
        Length of the video.
    """

    __slots__ = (
        "_http",
        "id",
        "user",
        "title",
        "description",
        "created_at",
        "published_at",
        "url",
        "thumbnail_url",
        "viewable",
        "view_count",
        "language",
        "type",
        "duration",
    )

    def __init__(self, http: "TwitchHTTP", data: dict, user: Union[PartialUser, User] = None):
        self._http = http
        self.id: int = int(data["id"])
        self.user = user or PartialUser(http, data["user_id"], data["user_name"])
        self.title: str = data["title"]
        self.description: str = data["description"]
        self.created_at = parse_timestamp(data["created_at"])
        self.published_at = parse_timestamp(data["published_at"])
        self.url: str = data["url"]
        self.thumbnail_url: str = data["thumbnail_url"]
        self.viewable: str = data["viewable"]
        self.view_count: int = data["view_count"]
        self.language: str = data["language"]
        self.type: str = data["type"]
        self.duration: str = data["duration"]

    def __repr__(self):
        return f"<Video id={self.id} title={self.title} url={self.url}>"

    async def delete(self, token: str):
        """|coro|

        Deletes the video. For bulk deletion see :func:`Client.delete_videos`

        Parameters
        -----------
        token: :class:`str`
            The users oauth token with the channel:manage:videos
        """
        await self._http.delete_videos(token, ids=[str(self.id)])

class Tag:
    """
    Represents a stream tag

    Attributes
    -----------
    id: :class:`str`
        An ID that identifies the tag.
    auto: :class:`bool`
        Indicates whether the tag is an automatic tag.
    localization_names: Dict[:class:`str`, :class:`str`]
        A dictionary that contains the localized names of the tag.
    localization_descriptions: :class:`str`
        A dictionary that contains the localized descriptions of the tag.
    """

    __slots__ = "id", "auto", "localization_names", "localization_descriptions"

    def __init__(self, data: dict):
        self.id: str = data["tag_id"]
        self.auto: bool = data["is_auto"]
        self.localization_names: Dict[str, str] = data["localization_names"]
        self.localization_descriptions: Dict[str, str] = data["localization_descriptions"]

    def __repr__(self):
        return f"<Tag id={self.id}>"

class WebhookSubscription:
    __slots__ = "callback", "expires_at", "topic"

    def __init__(self, data: dict):
        self.callback: str = data["callback"]
        self.expires_at = parse_timestamp(data["expired_at"])
        self.topic: str = data["topic"]

    def __repr__(self):
        return f"<WebhookSubscription callback={self.callback} topic={self.topic} expires_at={self.expires_at}>"

class Stream:
    """
    Represents a Stream

    Attributes
    -----------
    id: :class:`int`
        The current stream ID.
    user: :class:`~twitchio.PartialUser`
        The user who is streaming.
    game_id: :class:`int`
        Current game ID being played on the channel.
    game_name: :class:`str`
        Name of the game being played on the channel.
    type: :class:`str`
        Whether the stream is "live" or not.
    title: :class:`str`
        Title of the stream.
    viewer_count: :class:`int`
        Current viewer count of the stream
    started_at: :class:`datetime.datetime`
        UTC timestamp of when the stream started.
    language: :class:`str`
        Language of the channel.
    thumbnail_url: :class:`str`
        Thumbnail URL of the stream.
    tag_ids: List[:class:`str`]
        Tag IDs that apply to the stream.

        .. warning::

            This field will be deprecated by twitch in 2023.

    is_mature: :class:`bool`
        Indicates whether the stream is intended for mature audience.
    tags: List[:class:`str`]
        The tags applied to the channel.
    """

    __slots__ = (
        "id",
        "user",
        "game_id",
        "game_name",
        "type",
        "title",
        "viewer_count",
        "started_at",
        "language",
        "thumbnail_url",
        "tag_ids",
        "is_mature",
        "tags",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: int = data["id"]
        self.user = PartialUser(http, data["user_id"], data["user_name"])
        self.game_id: int = data["game_id"]
        self.game_name: str = data["game_name"]
        self.type: str = data["type"]
        self.title: str = data["title"]
        self.viewer_count: int = data["viewer_count"]
        self.started_at = parse_timestamp(data["started_at"])
        self.language: str = data["language"]
        self.thumbnail_url: str = data["thumbnail_url"]
        self.tag_ids: List[str] = data["tag_ids"] or []
        self.is_mature: bool = data["is_mature"]
        self.tags: List[str] = data["tags"]

    def __repr__(self):
        return f"<Stream id={self.id} user={self.user} title={self.title} started_at={self.started_at}>"

class ChannelInfo:
    """
    Represents a channel's current information

    Attributes
    -----------
    user: :class:`~twitchio.PartialUser`
        The user whose channel information was requested.
    game_id: :class:`int`
        Current game ID being played on the channel.
    game_name: :class:`str`
        Name of the game being played on the channel.
    title: :class:`str`
        Title of the stream.
    language: :class:`str`
        Language of the channel.
    delay: :class:`int`
        Stream delay in seconds.
        This defaults to 0 if the broadcaster_id does not match the user access token.
    tags: List[:class:`str`]
        The tags applied to the channel.
    content_classification_labels: List[:class:`str`]
        The CCLs applied to the channel.
    is_branded_content: :class:`bool`
        Boolean flag indicating if the channel has branded content.
    """

    __slots__ = (
        "user",
        "game_id",
        "game_name",
        "title",
        "language",
        "delay",
        "tags",
        "content_classification_labels",
        "is_branded_content",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.user = PartialUser(http, data["broadcaster_id"], data["broadcaster_name"])
        self.game_id: int = data["game_id"]
        self.game_name: str = data["game_name"]
        self.title: str = data["title"]
        self.language: str = data["broadcaster_language"]
        self.delay: int = data["delay"]
        self.tags: List[str] = data["tags"]
        self.content_classification_labels: List[str] = data["content_classification_labels"]
        self.is_branded_content: bool = data["is_branded_content"]

    def __repr__(self):
        return f"<ChannelInfo user={self.user} game_id={self.game_id} game_name={self.game_name} title={self.title} language={self.language} delay={self.delay}>"

class Prediction:
    """
    Represents channel point predictions

    Attributes
    -----------
    user: :class:`~twitchio.PartialUser`
        The user who is streaming.
    prediction_id: :class:`str`
        ID of the Prediction.
    title: :class:`str`
        Title for the Prediction.
    winning_outcome_id: :class:`str`
        ID of the winning outcome
    outcomes: List[:class:`~twitchio.PredictionOutcome`]
        List of possible outcomes for the Prediction.
    prediction_window: :class:`int`
        Total duration for the Prediction (in seconds).
    prediction_status: :class:`str`
        Status of the Prediction.
    created_at: :class:`datetime.datetime`
        Time for when the Prediction was created.
    ended_at: :class:`datetime.datetime`
        Time for when the Prediction ended.
    locked_at: :class:`datetime.datetime`
        Time for when the Prediction was locked.
    """

    __slots__ = (
        "user",
        "prediction_id",
        "title",
        "winning_outcome_id",
        "outcomes",
        "prediction_window",
        "prediction_status",
        "created_at",
        "ended_at",
        "locked_at",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.user = PartialUser(http, data["broadcaster_id"], data["broadcaster_name"])
        self.prediction_id: str = data["id"]
        self.title: str = data["title"]
        self.winning_outcome_id: str = data["winning_outcome_id"]
        self.outcomes: List[PredictionOutcome] = [PredictionOutcome(http, x) for x in data["outcomes"]]
        self.prediction_window: int = data["prediction_window"]
        self.prediction_status: str = data["status"]
        self.created_at = self._parse_time(data, "created_at")
        self.ended_at = self._parse_time(data, "ended_at")
        self.locked_at = self._parse_time(data, "locked_at")

    def _parse_time(self, data, field) -> Optional["Datetime"]:
        if field not in data or data[field] is None:
            return None
        time = data[field].split(".")[0]
        return datetime.datetime.fromisoformat(time)

    def __repr__(self):
        return f"<Prediction user={self.user} prediction_id={self.prediction_id} winning_outcome_id={self.winning_outcome_id} title={self.title}>"

class Predictor:
    """
    Represents a predictor

    Attributes
    -----------
    user: :class:`~twitchio.PartialUser`
        The user who is streaming.
    channel_points_used: :class:`int`
        Number of Channel Points used by the user.
    channel_points_won: :class:`int`
        Number of Channel Points won by the user.
    """

    __slots__ = ("channel_points_used", "channel_points_won", "user")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.channel_points_used: int = data["channel_points_used"]
        self.channel_points_won: int = data["channel_points_won"]
        self.user = PartialUser(http, data["user_id"], data["user_login"])

    def __repr__(self):
        return f"<Predictor user={self.user} channel_points_used={self.channel_points_used} channel_points_won={self.channel_points_won}>"

class PredictionOutcome:
    """
    Represents a prediction outcome

    Attributes
    -----------
    outcome_id: :class:`str`
        ID for the outcome.
    title: :class:`str`
        Text displayed for outcome.
    channel_points: :class:`int`
        Number of Channel Points used for the outcome.
    color: :class:`str`
        Color for the outcome.
    users: :class:`int`
        Number of unique uesrs that chose the outcome.
    top_predictors: List[:class:`~twitchio.Predictor`]
        List of the top predictors. Could be None.
    """

    __slots__ = ("outcome_id", "title", "channel_points", "color", "users", "top_predictors")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.outcome_id: str = data["id"]
        self.title: str = data["title"]
        self.channel_points: int = data["channel_points"]
        self.color: str = data["color"]
        self.users: int = data["users"]
        if data["top_predictors"]:
            self.top_predictors: List[Predictor] = [Predictor(http, x) for x in data["top_predictors"]]
        else:
            self.top_predictors: List[Predictor] = None

    def __repr__(self):
        return f"<PredictionOutcome outcome_id={self.outcome_id} title={self.title} channel_points={self.channel_points} color={self.color} users={self.users}>"

    @property
    def colour(self) -> str:
        """The colour of the prediction. Alias to color."""
        return self.color

    def __repr__(self):
        return f"<PredictionOutcome outcome_id={self.outcome_id} title={self.title} channel_points={self.channel_points} color={self.color}>"

class Schedule:
    """
    Represents a channel's stream schedule

    Attributes
    -----------
    segments: List[:class:`~twitchio.ScheduleSegment`]
        List of segments of a channel's stream schedule.
    user: :class:`~twitchio.PartialUser`
        The user of the channel associated to the schedule.
    vacation: :class:`~twitchio.ScheduleVacation`
        Vacation details of stream schedule.
    """

    __slots__ = ("segments", "user", "vacation")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.segments = [ScheduleSegment(d) for d in data["data"]["segments"]] if data["data"]["segments"] else []
        self.user = PartialUser(http, data["data"]["broadcaster_id"], data["data"]["broadcaster_login"])
        self.vacation = ScheduleVacation(data["data"]["vacation"]) if data["data"]["vacation"] else None

    def __repr__(self):
        return f"<Schedule segments={self.segments} user={self.user} vacation={self.vacation}>"

class ScheduleSegment:
    """
    Represents a list segments of a channel's stream schedule

    Attributes
    -----------
    id: :class:`str`
        The ID for the scheduled broadcast.
    start_time: :class:`datetime.datetime`
        Scheduled start time for the scheduled broadcast
    end_time: Optional[:class:`datetime.datetime`]
        Scheduled end time for the scheduled broadcast
    title: :class:`str`
        Title for the scheduled broadcast.
    canceled_until: :class:`datetime.datetime`
        Used with recurring scheduled broadcasts. Specifies the date of the next recurring broadcast.
    category: :class:`~twitchio.ScheduleCategory`
        The game or category details for the scheduled broadcast.
    is_recurring: :class:`bool`
        Indicates if the scheduled broadcast is recurring weekly.
    """

    __slots__ = ("id", "start_time", "end_time", "title", "canceled_until", "category", "is_recurring")

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.start_time = parse_timestamp(data["start_time"])
        self.end_time = parse_timestamp(data["end_time"]) if data["end_time"] else None
        self.title: str = data["title"]
        self.canceled_until = parse_timestamp(data["canceled_until"]) if data["canceled_until"] else None
        self.category = ScheduleCategory(data["category"]) if data["category"] else None
        self.is_recurring: bool = data["is_recurring"]

    def __repr__(self):
        return f"<ScheduleSegment id={self.id} start_time={self.start_time} end_time={self.end_time} title={self.title} canceled_until={self.canceled_until} category={self.category} is_recurring={self.is_recurring}>"

class ScheduleCategory:
    """
    Game or category details of a stream's schedule

    Attributes
    -----------
    id: :class:`str`
        The game or category ID.
    name: :class:`str`
        The game or category name.
    """

    __slots__ = ("id", "name")

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.name: str = data["name"]

    def __repr__(self):
        return f"<ScheduleCategory id={self.id} name={self.name}>"

class ScheduleVacation:
    """
    A schedule's vacation details

    Attributes
    -----------
    start_time: :class:`datetime.datetime`
        Start date of stream schedule vaction.
    end_time: :class:`datetime.datetime`
        End date of stream schedule vaction.
    """

    __slots__ = ("start_time", "end_time")

    def __init__(self, data: dict):
        self.start_time = parse_timestamp(data["start_time"])
        self.end_time = parse_timestamp(data["end_time"])

    def __repr__(self):
        return f"<ScheduleVacation start_time={self.start_time} end_time={self.end_time}>"

class Team:
    """
    Represents information for a specific Twitch Team

    Attributes
    -----------
    users: List[:class:`~twitchio.PartialUser`]
        List of users in the specified Team.
    background_image_url: :class:`str`
        URL for the Team background image.
    banner: :class:`str`
        URL for the Team banner.
    created_at: :class:`datetime.datetime`
        Date and time the Team was created.
    updated_at: :class:`datetime.datetime`
        Date and time the Team was last updated.
    info: :class:`str`
        Team description.
    thumbnail_url: :class:`str`
        Image URL for the Team logo.
    team_name: :class:`str`
        Team name.
    team_display_name: :class:`str`
        Team display name.
    id: :class:`str`
        Team ID.
    """

    __slots__ = (
        "users",
        "background_image_url",
        "banner",
        "created_at",
        "updated_at",
        "info",
        "thumbnail_url",
        "team_name",
        "team_display_name",
        "id",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.users: List[PartialUser] = [PartialUser(http, x["user_id"], x["user_login"]) for x in data["users"]]
        self.background_image_url: str = data["background_image_url"]
        self.banner: str = data["banner"]
        self.created_at = parse_timestamp(data["created_at"].split(" ")[0])
        self.updated_at = parse_timestamp(data["updated_at"].split(" ")[0])
        self.info: str = data["info"]
        self.thumbnail_url: str = data["thumbnail_url"]
        self.team_name: str = data["team_name"]
        self.team_display_name: str = data["team_display_name"]
        self.id = data["id"]

    def __repr__(self):
        return f"<Team users={self.users} team_name={self.team_name} team_display_name={self.team_display_name} id={self.id} created_at={self.created_at}>"

class ChannelTeams:
    """
    Represents the Twitch Teams of which the specified channel/broadcaster is a member

    Attributes
    -----------
    broadcaster: :class:`~twitchio.PartialUser`
        User of the broadcaster.
    background_image_url: :class:`str`
        URL for the Team background image.
    banner: :class:`str`
        URL for the Team banner.
    created_at: :class:`datetime.datetime`
        Date and time the Team was created.
    updated_at: :class:`datetime.datetime`
        Date and time the Team was last updated.
    info: :class:`str`
        Team description.
    thumbnail_url: :class:`str`
        Image URL for the Team logo.
    team_name: :class:`str`
        Team name.
    team_display_name: :class:`str`
        Team display name.
    id: :class:`str`
        Team ID.
    """

    __slots__ = (
        "broadcaster",
        "background_image_url",
        "banner",
        "created_at",
        "updated_at",
        "info",
        "thumbnail_url",
        "team_name",
        "team_display_name",
        "id",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.broadcaster: PartialUser = PartialUser(http, data["broadcaster_id"], data["broadcaster_login"])
        self.background_image_url: str = data["background_image_url"]
        self.banner: str = data["banner"]
        self.created_at = parse_timestamp(data["created_at"].split(" ")[0])
        self.updated_at = parse_timestamp(data["updated_at"].split(" ")[0])
        self.info: str = data["info"]
        self.thumbnail_url: str = data["thumbnail_url"]
        self.team_name: str = data["team_name"]
        self.team_display_name: str = data["team_display_name"]
        self.id = data["id"]

    def __repr__(self):
        return f"<ChannelTeams user={self.broadcaster} team_name={self.team_name} team_display_name={self.team_display_name} id={self.id} created_at={self.created_at}>"

class Poll:
    """
    Represents a list of Polls for a broadcaster / channel

    .. note::

        Twitch have removed support for voting with bits.
        By default bits_votes, bits_voting_enabled and bits_per_vote will be received as either 0 or False.

    Attributes
    -----------
    id: :class:`str`
        ID of a poll.
    broadcaster: :class:`~twitchio.PartialUser`
        User of the broadcaster.
    title: :class:`str`
        Question displayed for the poll.
    choices: List[:class:`~twitchio.PollChoice`]
        The poll choices.
    bits_voting_enabled: :class:`bool`
        Indicates if Bits can be used for voting.

        .. warning::

            Twitch have removed support for voting with bits.
            This will return as False

    bits_per_vote: :class:`int`
        Number of Bits required to vote once with Bits.

        .. warning::

            Twitch have removed support for voting with bits.
            This will return as 0

    channel_points_voting_enabled: :class:`bool`
        Indicates if Channel Points can be used for voting.
    channel_points_per_vote: :class:`int`
        Number of Channel Points required to vote once with Channel Points.
    status: :class:`str`
        Poll status. Valid values: ACTIVE, COMPLETED, TERMINATED, ARCHIVED, MODERATED, INVALID
    duration: :class:`int`
        Total duration for the poll (in seconds).
    started_at: :class:`datetime.datetime`
        Date and time the poll was started.
    ended_at: :class:`datetime.datetime`
        Date and time the poll was ended.
    """

    __slots__ = (
        "id",
        "broadcaster",
        "title",
        "choices",
        "channel_points_voting_enabled",
        "channel_points_per_vote",
        "status",
        "duration",
        "started_at",
        "ended_at",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: str = data["id"]
        self.broadcaster = PartialUser(http, data["broadcaster_id"], data["broadcaster_login"])
        self.title: str = data["title"]
        self.choices: List[PollChoice] = [PollChoice(d) for d in data["choices"]] if data["choices"] else []
        self.channel_points_voting_enabled: bool = data["channel_points_voting_enabled"]
        self.channel_points_per_vote: int = data["channel_points_per_vote"]
        self.status: str = data["status"]
        self.duration: int = data["duration"]
        self.started_at: datetime.datetime = parse_timestamp(data["started_at"])
        try:
            self.ended_at: Optional[datetime.datetime] = parse_timestamp(data["ended_at"])
        except KeyError:
            self.ended_at = None

    def __repr__(self):
        return f"<Polls id={self.id} broadcaster={self.broadcaster} title={self.title} status={self.status} duration={self.duration} started_at={self.started_at} ended_at={self.ended_at}>"

class PollChoice:
    """
    Represents a polls choices

    Attributes
    -----------
    id: :class:`str`
        ID for the choice.
    title: :class:`str`
        Text displayed for the choice.
    votes: :class:`int`
        Total number of votes received for the choice across all methods of voting.
    channel_points_votes: :class:`int`
        Number of votes received via Channel Points.
    bits_votes: :class:`int`
        Number of votes received via Bits.

        .. warning::

            Twitch have removed support for voting with bits.
            This will return as 0

    """

    __slots__ = ("id", "title", "votes", "channel_points_votes", "bits_votes")

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.title: str = data["title"]
        self.votes: int = data["votes"]
        self.channel_points_votes: int = data["channel_points_votes"]
        self.bits_votes: int = data["bits_votes"]

    def __repr__(self):
        return f"<PollChoice id={self.id} title={self.title} votes={self.votes} channel_points_votes={self.channel_points_votes} bits_votes={self.bits_votes}>"

class Goal:
    """
    Represents a list of Goals for a broadcaster / channel

    Attributes
    -----------
    id: :class:`str`
        An ID that uniquely identifies this goal.
    broadcaster: :class:`~twitchio.PartialUser`
        User of the broadcaster.
    type: :class:`str`
        The type of goal.
        Valid values: follower, subscription, subscription_count, new_subscription and new_subscription_count.
    description: :class:`str`
        A description of the goal, if specified.
    current_amount: :class:`int`
        The current value.
    target_amount: :class:`int`
        Number of Bits required to vote once with Bits.
    created_at: :class:`datetime.datetime`
        Date and time of when the broadcaster created the goal.
    """

    __slots__ = (
        "id",
        "broadcaster",
        "type",
        "description",
        "current_amount",
        "target_amount",
        "created_at",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.id: str = data["id"]
        self.broadcaster = PartialUser(http, data["broadcaster_id"], data["broadcaster_login"])
        self.type: str = data["type"]
        self.description: str = data["description"]
        self.current_amount: int = data["current_amount"]
        self.target_amount: int = data["target_amount"]
        self.created_at: datetime.datetime = parse_timestamp(data["created_at"])

    def __repr__(self):
        return f"<Goal id={self.id} broadcaster={self.broadcaster} description={self.description} current_amount={self.current_amount} target_amount={self.target_amount} created_at={self.created_at}>"

class ChatSettings:
    """
    Represents current chat settings of a broadcaster / channel

    Attributes
    -----------
    broadcaster: :class:`~twitchio.PartialUser`
        User of the broadcaster. Only returns the ID.
    emote_mode: :class:`bool`
        Indicates whether emote only mode is enabled.
    follower_mode: :class:`bool`
        Indicates whether follower only chat is enabled.
    follower_mode_duration: Optional[:class:`int`]
        The length of time, in minutes, that the followers must have followed the broadcaster to participate in chat.
    slow_mode: :class:`bool`
        Indicates whether the chat is in slow mode.
    slow_mode_wait_time: Optional[:class:`int`]
        The amount of time, in seconds, that users need to wait between sending messages.
    subscriber_mode: :class:`bool`
        Indicates whether only users that subscribe to the broadcaster's channel can talk in chat.
    unique_chat_mode: :class:`bool`
        Indicates whether the broadcaster requires users to post only unique messages in the chat room.
    moderator: Optional[:class:`~twitchio.PartialUser`]
        The User of the moderator, if provided. Only returns the ID.
    non_moderator_chat_delay: Optional[:class:`bool`]
        Indicates whether the broadcaster adds a short delay before chat messages appear in the chat room.
    non_moderator_chat_delay_duration: Optional[:class:`int`]
        The amount of time, in seconds, that messages are delayed from appearing in chat.
    """

    __slots__ = (
        "broadcaster",
        "emote_mode",
        "follower_mode",
        "follower_mode_duration",
        "slow_mode",
        "slow_mode_wait_time",
        "subscriber_mode",
        "unique_chat_mode",
        "moderator",
        "non_moderator_chat_delay",
        "non_moderator_chat_delay_duration",
    )

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.broadcaster = PartialUser(http, data["broadcaster_id"], None)
        self.emote_mode: bool = data["emote_mode"]
        self.follower_mode: bool = data["follower_mode"]
        self.follower_mode_duration: Optional[int] = data.get("follower_mode_duration")
        self.slow_mode: bool = data["slow_mode"]
        self.slow_mode_wait_time: Optional[int] = data.get("slow_mode_wait_time")
        self.subscriber_mode: bool = data["subscriber_mode"]
        self.unique_chat_mode: bool = data["unique_chat_mode"]
        self.non_moderator_chat_delay: Optional[bool] = data.get("non_moderator_chat_delay")
        self.non_moderator_chat_delay_duration: Optional[int] = data.get("non_moderator_chat_delay_duration")
        try:
            self.moderator = PartialUser(http, data["moderator_id"], None)
        except KeyError:
            self.moderator = None

    def __repr__(self):
        return f"<ChatSettings broadcaster={self.broadcaster} emote_mode={self.emote_mode} follower_mode={self.follower_mode} slow_mode={self.slow_mode} subscriber_mode={self.subscriber_mode} unique_chat_mode={self.unique_chat_mode}>"

class ChatterColor:
    """
    Represents chatters current name color.

    Attributes
    -----------
    user: :class:`~twitchio.PartialUser`
        PartialUser of the chatter.
    color: :class:`str`
        The color of the chatter's name.
    """

    __slots__ = ("user", "color")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.user = PartialUser(http, data["user_id"], data["user_login"])
        self.color: str = data["color"]

    def __repr__(self):
        return f"<ChatterColor user={self.user} color={self.color}>"

class Raid:
    """
    Represents a raid for a broadcaster / channel

    Attributes
    -----------
    created_at: :class:`datetime.datetime`
        Date and time of when the raid started.
    is_mature: :class:`bool`
        Indicates whether the stream being raided is marked as mature.
    """

    __slots__ = ("created_at", "is_mature")

    def __init__(self, data: dict):
        self.created_at: datetime.datetime = parse_timestamp(data["created_at"])
        self.is_mature: bool = data["is_mature"]

    def __repr__(self):
        return f"<Raid created_at={self.created_at} is_mature={self.is_mature}>"

class Ban:
    """
    Represents a ban for a broadcaster / channel

    Attributes
    -----------
    broadcaster: :class:`~twitchio.PartialUser`
        The broadcaster whose chat room the user was banned from chatting in.
    moderator: :class:`~twitchio.PartialUser`
        The moderator that banned the user.
    user: :class:`~twitchio.PartialUser`
        The user that was banned.
    created_at: :class:`datetime.datetime`
        Date and time of when the ban was created.
    """

    __slots__ = ("broadcaster", "moderator", "user", "created_at")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.broadcaster = PartialUser(http, data["broadcaster_id"], None)
        self.moderator = PartialUser(http, data["moderator_id"], None)
        self.user = PartialUser(http, data["user_id"], None)
        self.created_at: datetime.datetime = parse_timestamp(data["created_at"])

    def __repr__(self):
        return f"<Ban broadcaster={self.broadcaster} user={self.user} created_at={self.created_at}>"

class Timeout:
    """
    Represents a timeout for a broadcaster / channel

    Attributes
    -----------
    broadcaster: :class:`~twitchio.PartialUser`
        The broadcaster whose chat room the user was timed out from chatting in.
    moderator: :class:`~twitchio.PartialUser`
        The moderator that timed the user out.
    user: :class:`~twitchio.PartialUser`
        The user that was timed out.
    created_at: :class:`datetime.datetime`
        Date and time of when the timeout was created.
    end_time: :class:`datetime.datetime`
        Date and time of when the timeout will end.
    """

    __slots__ = ("broadcaster", "moderator", "user", "created_at", "end_time")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.broadcaster = PartialUser(http, data["broadcaster_id"], None)
        self.moderator = PartialUser(http, data["moderator_id"], None)
        self.user = PartialUser(http, data["user_id"], None)
        self.created_at: datetime.datetime = parse_timestamp(data["created_at"])
        self.end_time: datetime.datetime = parse_timestamp(data["end_time"])

    def __repr__(self):
        return f"<Timeout broadcaster={self.broadcaster} user={self.user} created_at={self.created_at} end_time={self.end_time}>"

class ShieldStatus:
    """
    Represents a Shield Mode activation status.

    Attributes
    -----------
    moderator: :class:`~twitchio.PartialUser`
        The moderator that last activated Shield Mode.
    display_name: :class:`str`
        The moderator's display name. Is an empty string if Shield Mode hasn't been previously activated.
    last_activated_at: :class:`datetime.datetime`
        The UTC datetime of when Shield Mode was last activated.
        Is an empty string if Shield Mode hasn't been previously activated.
    is_active: :class:`bool`
        A Boolean value that determines whether Shield Mode is active.
        Is true if the broadcaster activated Shield Mode; otherwise, false.
    """

    __slots__ = ("moderator", "display_name", "last_activated_at", "is_active")

    def __init__(self, http: "TwitchHTTP", data: dict):
        self.moderator: Optional[PartialUser] = (
            PartialUser(http, data["moderator_id"], data["moderator_login"]) if data["moderator_id"] else None
        )
        self.display_name: Optional[str] = data.get("moderator_name")
        self.is_active: bool = data["is_active"]
        self.last_activated_at: Optional[datetime.datetime] = (
            parse_timestamp(data["last_activated_at"]) if data["last_activated_at"] else None
        )

    def __repr__(self):
        return f"<ShieldStatus moderator={self.moderator} is_active={self.is_active} last_activated_at={self.last_activated_at}>"

class ChatBadge:
    """
    Represents chat badges.

    Attributes
    -----------
    set_id: :class:`str`
        An ID that identifies this set of chat badges. For example, Bits or Subscriber.
    versions: List[:class:`~twitchio.ChatBadgeVersions`]
        The list of chat badges in this set.
    """

    __slots__ = ("set_id", "versions")

    def __init__(self, data: dict):
        self.set_id: str = data["set_id"]
        self.versions: List[ChatBadgeVersions] = [ChatBadgeVersions(version_data) for version_data in data["versions"]]

    def __repr__(self):
        return f"<ChatBadge set_id={self.set_id} versions={self.versions}>"

class ChatBadgeVersions:
    """
    Represents the different versions of the chat badge.

    Attributes
    -----------
    id: :class:`str`
        An ID that identifies this version of the badge. The ID can be any value.
    image_url_1x: :class:`str`
        URL to the small version (18px x 18px) of the badge.
    image_url_2x: :class:`str`
        URL to the medium version (36px x 36px) of the badge.
    image_url_4x: :class:`str`
        URL to the large version (72px x 72px) of the badge.
    title: :class:`str`
        The title of the badge.
    description: :class:`str`
        The description of the badge.
    click_action: Optional[:class:`str`]
        The action to take when clicking on the badge. This can be None if no action is specified
    click_url: Optional[:class:`str`]
        The URL to navigate to when clicking on the badge. This can be None if no URL is specified.
    """

    __slots__ = (
        "id",
        "image_url_1x",
        "image_url_2x",
        "image_url_4x",
        "title",
        "description",
        "click_url",
        "click_action",
    )

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.image_url_1x: str = data["image_url_1x"]
        self.image_url_2x: str = data["image_url_2x"]
        self.image_url_4x: str = data["image_url_4x"]
        self.title: str = data["title"]
        self.description: str = data["description"]
        self.click_action: Optional[str] = data.get("click_action")
        self.click_url: Optional[str] = data.get("click_url")

    def __repr__(self):
        return f"<ChatBadgeVersions id={self.id} title={self.title}>"

class ContentClassificationLabel:
    """
    Represents a Content Classification Label.

    Attributes
    -----------
    id: :class:`str`
        Unique identifier for the CCL.
    description: :class:`str`
        Localized description of the CCL.
    name: :class:`str`
        Localized name of the CCL.
    """

    __slots__ = ("id", "description", "name")

    def __init__(self, data: dict):
        self.id: str = data["id"]
        self.description: str = data["description"]
        self.name: str = data["name"]

    def __repr__(self):
        return f"<ContentClassificationLabel id={self.id}>"

class CharityValues:
    """
    Represents the current/target funds of a charity campaign.

    Attributes
    -----------
    value: :class:`int`
        The value of the campaign (either so far, or the target value).
    decimal_places: :class:`int`
        The decimal places to be inserted into :attr:`.value`.
    currency: :class:`str`
        The currency this charity is raising funds in. eg ``USD``, ``GBP``, ``EUR``.
    """

    __slots__ = ("value", "decimal_places", "currency")

    def __init__(self, data: dict) -> None:
        self.value: int = data["value"]
        self.decimal_places: int = data["decimal_places"]
        self.currency: str = data["currency"]

    def __repr__(self) -> str:
        return f"<CharityValues value={self.value} decimal_places={self.decimal_places} currency={self.currency}>"

class CharityCampaign:
    """
    Represents a Charity Campaign on a channel.

    Attributes
    -----------
    campaign_id: :class:`str`
        The ID of the running charity campaign.
    broadcaster: :class:`~twitchio.PartialUser`
        The broadcaster running the campaign.
    user: :class:`~twitchio.PartialUser`
        The user who donated.
    charity_name: :class:`str`
        The name of the charity.
    charity_description: :class:`str`
        The description of the charity.
    charity_logo: :class:`str`
        The logo of the charity.
    charity_website: :class:`str`
        The websiet of the charity.
    current: :class:`CharityValues`
        The current funds raised by this campaign.
    target: :class:`CharityValues`
        The target funds to be raised for this campaign.
    """

    __slots__ = (
        "campaign_id",
        "broadcaster",
        "charity_name",
        "charity_description",
        "charity_logo",
        "charity_website",
        "current",
        "target",
    )

    def __init__(self, data: dict, http: TwitchHTTP, broadcaster: PartialUser | None = None) -> None:
        self.campaign_id: str = data["campaign_id"]
        self.broadcaster: PartialUser = broadcaster or PartialUser(
            http, data["broadcaster_id"], data["broadcaster_name"]
        )
        self.charity_name: str = data["charity_name"]
        self.charity_description: str = data["charity_description"]
        self.charity_logo: str = data["charity_logo"]
        self.charity_website: str = data["charity_website"]
        self.current: CharityValues = CharityValues(data["current_amount"])
        self.target: CharityValues = CharityValues(data["target_amount"])

    def __repr__(self) -> str:
        return f"<CharityCampaign broadcaster={self.broadcaster} campaign_id={self.campaign_id} charity_name={self.charity_name}>"


import numpy as np
from tabulate import tabulate

from clustering.external_evaluation import calculate_purity
from clustering.k_means import KMeans
from data_preparation.inverted_index import InvertedIndex
from data_preparation.pre_processing import parse_corpus, pre_process_corpus

corpus, y_true, titles = parse_corpus()
preprocessed_corpus = pre_process_corpus(corpus)

y_true = [y_true.index(l) for l in y_true]

def generate_matrix(preprocessed_corpus):
    inverted_index = InvertedIndex()

    for i in range(len(preprocessed_corpus)):
        for term in preprocessed_corpus[i].split():
            inverted_index.parse_term(term, i)
    document_term_matrix = np.array(inverted_index.make_document_by_term_matrix())
    return document_term_matrix

matrix = generate_matrix(preprocessed_corpus)
k = KMeans(5, 1000)

document_clusters = k.assign_documents_to_cluster(matrix)

y_pred = document_clusters[0]

clusters = document_clusters[1]

cluster_tightness = document_clusters[2]

top_documents = document_clusters[3]

def write_clusters():
    with open("clusters.txt", "w") as f:
        for i in range(len(clusters)):
            data = []

            f.write(
                "Cluster #%d contains the following %d documents: "
                % (i, len(clusters[i]))
            )
            f.write("\n\n")
            for j in range(len(clusters[i])):
                id = clusters[i][j]
                data.append([id, titles[id]])
            f.write(tabulate(data, headers=["Document ID", "Document Title"]))
            f.write("\n\n")

def sort_tuples(tuples):

    # sort tuples in ascending order by the second element
    # (distance from the centroid), which acts as the key

    tuples.sort(key=lambda x: x[1])
    return tuples

def show_summary():
    for i in range(len(top_documents)):
        data = []
        print("The top 3 documents in cluster #%d are:\n " % i)
        sortedTuples = sort_tuples(top_documents[i])[:3]
        for j in sortedTuples:
            data.append([j[0], titles[j[0]]])
        print(tabulate(data, headers=["Document ID", "Document Title"]))
        print()

def show_RSS():
    data = []
    for i in range(len(cluster_tightness)):
        data.append([i, cluster_tightness[i]])

    print(tabulate(data, headers=["Cluster ID", "RSS"]))

    print("\nThe total RSS is %.2f." % sum(cluster_tightness))

def show_purity():
    purity = calculate_purity(y_pred, y_true)
    print("The purity is %.2f." % (100 * purity))

def display_menu():

    # display menu shown to user
    print("")
    print(60 * "-", "Menu", 60 * "-")
    print("1. Show Cluster Summary")
    print("2. Calculate RSS")
    print("3. Calculate Purity")
    print("4. Write Clusters")
    print("5. Exit")
    print(127 * "-")
    print("")

def wait_for_input():
    input("\nPlease press Enter to continue...")

status = True

# main loop to display the menu
while status:
    display_menu()
    selection = input("Please enter your selection (1-4): ")
    print()
    if selection == "1":
        show_summary()
        wait_for_input()

    elif selection == "2":
        show_RSS()
        wait_for_input()

    elif selection == "3":
        show_purity()
        wait_for_input()

    elif selection == "4":
        write_clusters()
        wait_for_input()

    elif selection == "5":
        print("\nThe program will now terminate.")
        status = False

    else:

        # prompt user for a valid selection
        input("Please select a valid option from the menu.\n")


from bs4 import BeautifulSoup
import requests, os

#Configuration Variables
search_refs = True
build_path = "API"
API_URL = "https://pythonapi.upbge.org/"

#Further addons
headers = {"bge" + os.sep + "types.py" : """
import mathutils

inf = 0
class CListValue:
    def __init__(self, ctype):
        self.__ret__ = ctype
        self.__i__ = None
        self.__itf__ = False

    def __instanceme__(self):
        if self.__i__ == None:
            self.__i__ = self.__ret__()
        return self.__i__

    def __getitem__(self, key): return self.__instanceme__()
    def __setitem__(self, key, val): return self.__instanceme__()
    def get(self, key): return self.__instanceme__()

    def __iter__(self): return self
    def __next__(self):
        self.__itf__ = not self.__itf__
        if self.__itf__: return self.__instanceme__()
        else: raise StopIteration
""",
"bge" + os.sep + "logic.py" :
"""globalDict = {}
keyboard = None
mouse = None
joysticks = []
"""}

erase = {"bge" + os.sep + "logic.py" : [
"""globalDict = None
keyboard = None
mouse = None
joysticks = None"""]}

fixes = {
"RandomMusic": [(", transition=(5)", ", transition=(5,0,0))")]
}

def dataToPath(dp):
    i = dp.rfind(".")
    return os.path.normpath(dp[:len(dp) if i == -1 else i].replace(".", "/") + ".py")

class File:
    done_files = []
    done_urls = []
    registred_class = {}

    def __init__(self, url, recursive=False, prefix=""):
        self.current_class = ""
        self.current_module = ""
        self.recursive = recursive

        self.makePage(url, recursive=recursive, prefix=prefix)

    def getType(self, dl, noerror=False):
        if dl==None: raise Exception("dl should not be None")
        if type(dl)!=str:
            try: t = dl.dd.table.tbody.tr.td.get_text()
            except Exception: return "None"
        else: t=dl
        t=t.replace("\t", "")
        t=t.replace("‘s", "")

        #Correctors
        if t == "MeshProxy": t = "KX_MeshProxy"
        if t == "boolen": t = "bool"

        #Registred
        if t == self.current_class: return "self"
        if t in File.registred_class.keys():
            m = File.registred_class[t]
            if self.current_module == m: return t + "()"
            else: return m + '.' + t +"()"
        for k, v in File.registred_class.items():
            m = v+'.'+k
            if m == t: return m + "()"

        #Direct addressing
        if t in ["float", "int", "bool"]: return t + "()"
        if t in ["boolean", "boolean.", "bool"]: return "bool()"
        if t == "double": return "float()"
        if t in ["integer", "bitfield"]: return "int()"
        if t in ["string", "str"]: return "str()"
        if t in ["matrix", "Matrix", "mathutils.Matrix"]:
            if self.current_module != "mathutils": return "mathutils.Matrix()"
            else: return "Matrix()"
        if t in ["vector", "Vector", "mathutils.Vector"]:
            if self.current_module != "mathutils": return "mathutils.Vector()"
            else: return "Vector()"
        if t == "list" and not noerror: return "list()"
        if t == "dict" and not noerror: return "dict()"
        if t == "tuple" and not noerror: return "tuple()"
        if t == "Quaternion":
            if self.current_module != "mathutils": return "mathutils.Quaternion()"
            else: return "Quaternion()"

        #Special cases
        if t == "list of functions and/or methods": return "list()"
        if t == "3d vector.": return "mathutils.Vector()"
        if t == "3-tuple (float, 3-tuple (x, y, z), 3-tuple (x, y, z))": return "(float, (0,0,0), (0,0,0))"
        if t.startswith("\n3-tuple (KX_GameObject, 3-tuple (x, y, z), 3-tuple (nx, ny, nz))"):
            return "(KX_GameObject, (0,0,0), (0,0,0), KX_PolyProxy, (0,0))"
        if t == "list [x, y]": return "[0,0]"
        if t in ["(integer, integer)", "(int,int)", "(int, int)"]: return "(0,0)"
        if t == "list [str]": return "[str()]"
        if t == "list [r, g, b]": return "[0,0,0]"
        if t == "list[x, y, z]": return "[0,0,0]"
        if t == "(Vector, float) pair": return "(Vector(), float())"
        if t == "Matrix4x4 (read only)": return "mathutils.Matrix()"
        if t == "tuple of two ints": return "(0,0)"
        if t == "sequence of two ints": return "[0,0]"
        if t == "sequence of two floats": return "[0.0,0.0]"
        if t == "sequence of three ints": return "[0,0,0]"
        if t == "sequence of four sequences of two ints": return "[[0,0],[0,0],[0,0],[0,0]]"
        if t == "sequence of four sequences of five ints": return "[[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]"
        if t == "Buffer\n": return "bgl.Buffer()"
        if t == "sequence supporting index/string lookups and iteration.": return "dict()"

        #Addressing of containers
        for st in ["list of ", "CListValue of "]:
            if t.startswith(st):
                h=self.getType(t[len(st):], True)
                if h != "None":
                    if h.endswith("()"): h=h[:-2]
                    if h=="self": h=self.current_class
                    if self.current_module == "bge.types":
                        return "CListValue(" + h + ")"
                    else: return "bge.types.CListValue(" + h + ")"

        if t.startswith("Vector"):
            if self.current_module != "mathutils": return "mathutils.Vector()"
            else: return "Vector()"

        #Last chances to get it right
        for ch in ['\n', ' ', ',']:
            if ch in t:
                for x in t.split(ch):
                    h=self.getType(x, True)
                    if h!="None": return h

        for x in ["non-negative", "None"]:
            if x in t: return "None"

        if not noerror:
            if type(dl) != str and search_refs:
                links = dl.dd.table.tbody.tr.td.find_all("a")
                url = File.done_urls[-1]
                base_url = url[:url.rfind("/")+1]
                for l in links:
                    link = l["href"]
                    if not "CListValue" in link:
                        link = base_url + link[:link.rfind("#")]
                        File(link, recursive=True)
                        return self.getType(dl, noerror)

            print("Unknown type:", t)

        return "None"

    def getReturnType(self, o):
        if o.dd.table == None: return "None"
        for tr in o.dd.table.tbody.find_all("tr"):
            if tr.th.string=="Return type:":
                return self.getType(tr.td.get_text())
        return "None"

    def makePage(self, url, tab='', recursive=False, prefix=""):
        if url in File.done_urls: return
        else: File.done_urls.append(url)

        if not url.endswith(".html"):
            print("Skipped:", url)
            return

        print("Building page: ", url)

        r = requests.get(url).text
        soup = BeautifulSoup(r, "html.parser")

        body = soup.body.find("h1").parent
        if body.p.get_text().startswith("base class"):
            link = body.p.a["href"]
            link = url[:url.rfind("/")+1] + link[:link.rfind("#")]
            if recursive==True:
                File(link, recursive)

        #Get current module, autodetect class vs module using case sensitive.
        self.current_module = prefix + url[url.rfind('/')+1:url.rfind(".html")]
        i = self.current_module.rfind(".")
        if i != -1:
            if not self.current_module.split(".")[-1][0:1].islower():
                self.current_module = self.current_module[:i]
                dest = url[url.rfind('/')+1:url.rfind(".html")]
            else: dest = self.current_module + "."
        else: dest = self.current_module + "."

        #Identify Class or Module level data
        code = ""
        for dl in soup.find_all("dl"):
            dtype=dl.get("class")
            if dtype[0]=="class":
                code += '\n' + self.makePythonClass(dl) + '\n'

            if dtype[0]=="data":
                name = dl.dt["id"]
                #Make sure it's at module level
                if len(name.split('.')) == len(self.current_module.split('.'))+1:
                    value = "None"
                    for th in dl.find_all("th"):
                        if th.get_text() == "Value:":
                            value = th.parent.td.get_text()
                    code += name.split('.')[-1] + " = " + value + "\n"

            if dtype[0]=="function":
                name = dl.dt["id"]
                if len(name.split('.')) == len(self.current_module.split('.'))+1:
                    code += self.writeFunction(dl, False, '')

        #Write the file
        odest =  dataToPath(dest)
        dest = build_path + os.sep + odest

        if os.sep in dest:
            os.makedirs(os.path.dirname(dest), exist_ok=True)
        if dest in File.done_files:
            with open(dest, "a+", encoding="utf-8") as out: out.write(code)
        else:
            try: code = headers[odest] + code
            except KeyError: pass

            try:
                for x in erase[odest]: code=code.replace(x, "")
            except KeyError: pass

            with open(dest, "w", encoding="utf-8") as out: out.write(code)
            File.done_files.append(dest)

    def makePythonClassTitle(self, dt):
        cn = dt["id"]
        self.current_class = cn[cn.rfind(".")+1:]
        File.registred_class[self.current_class] = self.current_module
        code = "class " + self.current_class + '('
        for x in dt.find_all("em"):
            if x.get("class"): continue
            if not x.string[0].isupper(): continue
            if x.string in ["A", "B", "C", "D", "E", "F"]: continue
            code += x.string + ','
        if code.endswith(","): return code[:-1] + '):\n'
        else: return code [:-1]+ ":\n"

    def makePythonClass(self, dl, tab=''):
        tab+='\t'
        docstring = '"""' + dl.dd.p.get_text() + '"""'
        code = self.makePythonClassTitle(dl.dt) + tab + docstring + '\n\n'

        temp_code = tab + "def __init__(self, "

        for x in dl.dt.find_all("em"):
            if x.get("class"): continue
            if not x.string[0].islower() and not x.string in ["A", "B", "C", "D", "E", "F"]: continue
            if not "=" in x.string: temp_code += x.string+"=None, "
            else:
                if x.string.split("=")[1][0]== '<':
                    temp_code += x.string.split("=")[0] + "=None, "
                else:
                    temp_code += x.string + ', '

        temp_code = temp_code[:-2] + "):\n"
        tab+='\t'

        for o in dl.dd.find_all("dl"):
            if o["class"][0]=="data":
                temp_code += tab + "self." + o.dt.code.string + " = int()\n"
            if o["class"][0]=="attribute":
                temp_code += tab + "self." + o.dt.code.string + " = " + self.getType(o) + '\n'

        if not temp_code.endswith(":\n"): code += temp_code

        tab=tab[:-1]
        for o in dl.dd.find_all("dl"):
            if o["class"][0]=="method":
                code += self.writeFunction(o, True, tab)

        if self.current_class in fixes:
            for el in fixes[self.current_class]:
                x, y = el
                code = code.replace(x, y)

        return code

    def writeFunction(self, o, is_method=True, tab='\t'):
        if is_method:
            code = '\n' + tab + "def " + o.dt.code.string + "(self, "
        else:
            code = '\n' + tab + "def " + o.dt.find_all("code")[-1].string + "("

        for arg in o.dt.find_all("em"):
            m = arg.string.split("=")
            if len(m)>1 and any([m[1].startswith(x) for x in ["KX_", "IMB_"]]):
                 code += m[0] + '=None, '
            else: code += arg.string + ', '
        if code.endswith("("): code += "):"
        else: code = code[:-2]+"):"

        try:
            docstring = '"""' + o.dd.p.get_text() + '"""'
            code += '\n' + tab + '\t' + docstring + '\n'
        except Exception: code += " pass\n"

        rt = self.getReturnType(o)
        if rt != "None":
            if code.endswith(" pass\n"): code=code[:-len(" pass\n")]+"\n"
            tab+='\t'
            if "bge." in rt: code += tab + "import bge\n"
            code += tab + "return " + rt + '\n'
            tab=tab[:-1]

        if "deprecated" in code or "Deprecated" in code: return ""
        return code

def build(url): File(url, recursive=True, prefix="core." if "api/" in url else "")
def build_bge(url):
    build(url + "mathutils.html")
    build(url + "bge.types.KX_MeshProxy.html")
    build(url + "bge.types.KX_CharacterWrapper.html")
    build(url + "bge.types.KX_VehicleWrapper.html")
    build(url + "bge.types.SCA_PythonController.html")
    build(url + "bge.types.KX_Scene.html")
    build(url + "bge.logic.html")
    build(url + "bge.texture.html")
    build(url + "bge.events.html")
    build(url + "bge.app.html")
    build(url + "bge.constraints.html")

    init="from . import logic, types, texture, events, app, constraints"
    init_path = build_path + os.sep + "bge" + os.sep + "__init__.py"
    with open(init_path, "w", encoding="utf-8") as out: out.write(init)

def build_core(url):
    build(url + "api/media.html")
    build(url + "api/event.html")
    build(url + "api/sequencer.html")
    build(url + "api/utils.html")

    init="from . import media, event, utils, sequencer\nmedia.music=media.AudioFile()"
    init_path = build_path + os.sep + "core" + os.sep + "__init__.py"
    with open(init_path, "w", encoding="utf-8") as out: out.write(init)

def test():
    test_bge()
    test_core()

def test_bge():
    import traceback
    sys.path.append(build_path)

    try:
        import mathutils, bge
        v=mathutils.Vector()
        m=mathutils.Matrix()
        scn = bge.logic.getCurrentScene()
        o = scn.objects["some"]
        a=o.isPlayingAction()
        b=o.parent.addDebugProperty("LOL")
        o.endObject()

        print("Test BGE: OK")
    except Exception: traceback.print_exc()

def test_core():
    import traceback
    sys.path.append(build_path)

    try:
        import core
        core.media.music.filepath = ""

        print("Test CORE: OK")
    except Exception: traceback.print_exc()

build_path = os.path.normpath(build_path)

import sys
if len(sys.argv) == 1:
    build_bge(API_URL)
    build_core("http://coredoc.royalwebhosting.net/")
    test()
    print("Done.")

if len(sys.argv) == 2:
    if sys.argv[1] == "-test": test()


# exclude from patching
DONT_PATCH_MY_STAR_IMPORTS = True
from mods.RiftOptimizer.Patcher import *

import threading
import queue
import Level
import LevelGen
import inspect
import logging
import SteamAdapter
import Game
import os
import pygame
import dill as pickle

import mods.RiftOptimizer.RiftOptimizer as RiftOptimizer

####################################################
# Importing RiftWizard.py                          |
# Credit to trung on discord                       |
#                                                  |
#----------------------------------------------    |
import inspect #                                   |
def get_RiftWizard(): #                            |
    # Returns the RiftWizard.py module object      |
    for f in inspect.stack()[::-1]: #              |
        if "file 'RiftWizard.py'" in str(f): #     |
            return inspect.getmodule(f[0]) #       |
     #                                             |
    return inspect.getmodule(f[0]) #               |
#                                                  |
RiftWizard = get_RiftWizard() #                    |
#                                                  |
#                                                  |
####################################################

import sys

need_to_setup_print_logs = False
if 'print' in sys.argv:
    need_to_setup_print_logs = True

# Level.py calls both logging.debug and Logger.debug which are distinct apparently
original_logging_debug = logging.debug
def logging_debug(self, *args, **kwargs):
    channel.put((original_logging_debug, (self, *args, *kwargs)))

Level.logging.debug = logging_debug
logging.debug = logging_debug

original_debug = logging.Logger.debug
def log_debug(self, *args, **kwargs):
    channel.put((original_debug, (self, *args, *kwargs)))

def local_setup_logging(self):
    # Clear handlers if they exist
    for h in list(self.combat_log.handlers):
        self.combat_log.removeHandler(h)

    if need_to_setup_print_logs:
        self.combat_log.addHandler(logging.StreamHandler(sys.stdout))

    self.combat_log.addHandler(logging.FileHandler(os.path.join(self.logdir if self.logdir else '.', 'combat_log.txt'), mode='a'))

LevelGen.level_logger.debug = log_debug.__get__(LevelGen.level_logger,logging.Logger)
RiftWizard.mem_log.debug = log_debug.__get__(RiftWizard.mem_log,logging.Logger)
SteamAdapter.stats_log.debug = log_debug.__get__(SteamAdapter.stats_log,logging.Logger)

def setup_logging(self, logdir, level_num):
    self.combat_log = logging.getLogger("damage")
    self.combat_log.setLevel(logging.DEBUG)
    self.combat_log.propagate = False
    self.combat_log.debug = log_debug.__get__(self.combat_log,logging.Logger)

    self.logdir = logdir
    self.level_no = level_num

    channel.put((local_setup_logging, (self)))

Level.Level.setup_logging = setup_logging

original_next_log_turn = Level.Level.next_log_turn
def next_log_turn(self, *args, **kwargs):
    channel.put((original_next_log_turn, (self, *args, *kwargs)))
Level.Level.next_log_turn = next_log_turn

def write_finalize_level(stats, run_number, level_number):
    filename = os.path.join('saves', str(run_number), 'stats.level_%d.txt' % level_number)

    dirname = os.path.dirname(filename)
    if not os.path.exists(dirname):
        os.makedirs(dirname)

    with open(filename, 'w') as outfile:
        outfile.write(''.join(stats))

def finalize_level(self, victory):
    self.total_turns += self.cur_level.turn_no

    stats = []

    stats.append("Realm %d\n" % self.level_num)
    if self.trial_name:
        stats.append(self.trial_name + "\n")
    stats.append("Outcome: %s\n" % ("VICTORY" if victory else "DEFEAT"))
    stats.append("\nTurns taken:\n")
    stats.append("%d (L)\n" % self.cur_level.turn_no)
    stats.append("%d (G)\n" % self.total_turns)

    counts = sorted(self.cur_level.spell_counts.items(), key=lambda t: -t[1])

    spell_counts = [(s, c) for (s, c) in counts if not s.item]
    if spell_counts:
        stats.append("\nSpell Casts:\n")
        for s, c in spell_counts:
            stats.append("%s: %d\n" % (s.name, c))

    dealers = sorted(self.cur_level.damage_dealt_sources.items(), key=lambda t: -t[1])
    if dealers:
        stats.append("\nDamage to Enemies:\n")
        for s, d in dealers[:5]:
            stats.append("%d %s\n" % (d, s))
        if len(dealers) > 6:
            total_other = sum(d for s,d in dealers[5:])
            stats.append("%d Other\n" % total_other)

    sources = sorted(self.cur_level.damage_taken_sources.items(), key=lambda t: -t[1])
    if sources:
        stats.append("\nDamage to Wizard:\n")
        for s, d in sources[:5]:
            stats.append("%d %s\n" % (d, s))
        if len(sources) > 6:
            total_other = sum(d for s,d in sources[5:])
            stats.append("%d Other\n" % total_other)

    item_counts = [(s, c) for (s, c) in counts if s.item]
    if item_counts:
        stats.append("\nItems Used:\n")
        for s, c in item_counts:
            stats.append("%s: %d\n" % (s.name, c))

    if self.recent_upgrades:
        stats.append("\nPurchases:\n")
        for u in self.recent_upgrades:
            fmt = u.name
            if getattr(u, 'prereq', None):
                fmt = "%s %s" % (u.prereq.name, u.name)
            stats.append("%s\n" % fmt)

    self.recent_upgrades.clear()

    channel.put((write_finalize_level, (stats, self.run_number, self.level_num)))

RiftOptimizer.replace_only_vanilla_code(Game.Game.finalize_level,finalize_level)

def threaded_screenshot(surface, filename, run_number, level_number):
    filename = os.path.join('saves', str(run_number), filename % level_number)

    dirname = os.path.dirname(filename)
    if not os.path.exists(dirname):
        os.makedirs(dirname)

    pygame.image.save(surface, filename)

def make_level_screenshot(self):
    self.draw_level()
    self.draw_character()
    fake_portal = Level.Portal(self.game.cur_level.gen_params)
    self.examine_target = fake_portal
    self.draw_examine()

    channel.put((threaded_screenshot, (self.screen.copy(), 'level_%d_begin.png', self.game.run_number, self.game.level_num)))

    self.examine_target = None
    self.draw_examine()

RiftOptimizer.replace_only_vanilla_code(RiftWizard.PyGameView.make_level_screenshot,make_level_screenshot)

def make_level_end_screenshot(self):
    self.draw_level()
    self.draw_character()

    self.examine_display.fill((0, 0, 0))
    self.draw_panel(self.examine_display)

    self.draw_level_stats()

    self.screen.blit(self.examine_display, (self.screen.get_width() - self.h_margin, 0))

    channel.put((threaded_screenshot, (self.screen.copy(), 'level_%d_finish.png', self.game.run_number, self.game.level_num)))

RiftOptimizer.replace_only_vanilla_code(RiftWizard.PyGameView.make_level_end_screenshot,make_level_end_screenshot)

def setup_logger_thread(channel):
    try:
        # let's wait for the first message
        try:
            msg = channel.get(timeout=1)
        except queue.Empty:
            print("\nthe ThreadedIO queue was empty after 1 second. the main thread might have crashed. will give up in 10 more seconds")
            # TODO - should this be configurable?
            giveup_timer = 10

            while giveup_timer > 0:
                try:
                    msg = channel.get(timeout=1)

                    print("communication reestablished\n")

                    break
                except queue.Empty:
                    giveup_timer -= 1
                    if giveup_timer <= 3 and giveup_timer > 0:
                        print(giveup_timer)

            if giveup_timer <= 0:
                # TODO - revert to default functions first?
                return

        if not handle_message(msg):
            return

        # messages arrive and are executed sequentially in the same order as the main thread sent them
        while True:
            msg = channel.get()

            if not handle_message(msg):
                return
    except:
        # just crash the whole game if the io thread crashes
        if not root_window:
            back_channel.put("crash")

        root_window.running = False
        raise

def handle_message(msg):
    if msg == "quit":
        back_channel.put("quitting")
        return False
    elif hasattr(msg, '__len__') and len(msg) == 2 and callable(msg[0]):
        if hasattr(msg[1], '__iter__'):
            msg[0](*msg[1])
        else:
            msg[0](msg[1])
    elif isinstance(msg, RiftWizard.PyGameView):
        root_window = msg
    else:
        print("unknown message to IO thread:")
        print(msg)

    return True

channel = queue.Queue()

back_channel = queue.Queue()

original_run = RiftWizard.PyGameView.run

io_thread = threading.Thread(target=setup_logger_thread, args=(channel,), name="WriterThread")
io_thread.start()

# override RiftWizard.run() in order to close thread, handle crashes, etc
def run(self):
    try:
        try:
            channel.put(self)
            back_channel.get(False)

            print("closing main thread due to ThreadedIO crash")

            return
        except queue.Empty:
            pass
        except:
            raise

        original_run(self)
    except:
        # make sure thread is killed if any error occurs
        channel.put("quit")

        io_thread.join()
        raise

    channel.put("quit")

    # give the io thread time to close
    try:
        back_channel.get(timeout=2)
    except queue.Empty:
        pass

    io_thread.join()

RiftWizard.PyGameView.run = run