Remove processing of tags;

- Removes all logic related to stream tags processing
- Implements a new method of fetching live non-ACL channels, with drops enabled
- Minor logic cleanup changes
This commit is contained in:
DevilXD committed 2023-10-15 12:06:58 +02:00
1 parent 67cfc6603d
commit 5e862f9543
4 files changed
+54 -22

No files matched your search

+10 -10
View File
@@ -9,7 +9,7 @@ from typing import Any, SupportsInt, TYPE_CHECKING
from utils import invalidate_cache, json_minify, Game
from exceptions import MinerException, RequestException
from constants import BASE_URL, GQL_OPERATIONS, ONLINE_DELAY, DROPS_ENABLED_TAG, URLType
from constants import BASE_URL, GQL_OPERATIONS, ONLINE_DELAY, URLType
if TYPE_CHECKING:
from twitch import Twitch
@@ -31,12 +31,11 @@ class Stream:
game: JsonType | None,
viewers: int,
title: str,
tags: list[JsonType],
):
self.channel: Channel = channel
self.broadcast_id = int(id)
self.viewers: int = viewers
self.drops_enabled: bool = any(t["id"] == DROPS_ENABLED_TAG for t in tags)
self.drops_enabled: bool = False
self.game: Game | None = Game(game) if game else None
self.title: str = title
@@ -50,21 +49,20 @@ class Stream:
game=settings["game"],
viewers=stream["viewersCount"],
title=settings["title"],
tags=stream["tags"] or [],
)
@classmethod
def from_directory(cls, channel: Channel, data: JsonType) -> Stream:
def from_directory(
cls, channel: Channel, data: JsonType, *, drops_enabled: bool = False
) -> Stream:
self = cls(
channel,
id=data["id"],
game=data["game"], # has to be there since we searched with it
viewers=data["viewersCount"],
title=data["title"],
tags=[],
)
# HACK: we assume this is always the case for directory streams, since we search by the tag
# self.drops_enabled = True
self.drops_enabled = drops_enabled
return self
def __eq__(self, other: object) -> bool:
@@ -109,12 +107,14 @@ class Channel:
)
@classmethod
def from_directory(cls, twitch: Twitch, data: JsonType) -> Channel:
def from_directory(
cls, twitch: Twitch, data: JsonType, *, drops_enabled: bool = False
) -> Channel:
channel = data["broadcaster"]
self = cls(
twitch, id=channel["id"], login=channel["login"], display_name=channel["displayName"]
)
self._stream = Stream.from_directory(self, data)
self._stream = Stream.from_directory(self, data, drops_enabled=drops_enabled)
return self
@classmethod
+31 -5
View File
@@ -48,6 +48,30 @@ def _resource_path(relative_path: Path | str) -> Path:
return base_path.joinpath(relative_path)
def _merge_vars(base_vars: JsonType, vars: JsonType) -> None:
# NOTE: This modifies base in place
for k, v in vars.items():
if k not in base_vars:
base_vars[k] = v
elif isinstance(v, dict):
if isinstance(base_vars[k], dict):
_merge_vars(base_vars[k], v)
elif base_vars[k] is Ellipsis:
# unspecified base, use the passed in var
base_vars[k] = v
else:
raise RuntimeError(f"Var is a dict, base is not: '{k}'")
elif isinstance(base_vars[k], dict):
raise RuntimeError(f"Base is a dict, var is not: '{k}'")
else:
# simple overwrite
base_vars[k] = v
# ensure none of the vars are ellipsis (unset value)
for k, v in base_vars.items():
if v is Ellipsis:
raise RuntimeError(f"Unspecified variable: '{k}'")
# Base Paths
# NOTE: pyinstaller will set this to its own executable when building,
# detect this to use __file__ and main.py redirection instead
@@ -88,7 +112,6 @@ PING_TIMEOUT = timedelta(seconds=10)
ONLINE_DELAY = timedelta(seconds=120)
WATCH_INTERVAL = timedelta(seconds=59)
# Strings
DROPS_ENABLED_TAG = "c2542d6d-cd10-4532-919b-3d19f30a768b"
WINDOW_TITLE = f"Twitch Drops Miner v{__version__} (by DevilXD)"
# Logging
FILE_FORMATTER = logging.Formatter(
@@ -202,7 +225,7 @@ class GQLOperation(JsonType):
modified = deepcopy(self)
if "variables" in self:
existing_variables: JsonType = modified["variables"]
existing_variables.update(variables)
_merge_vars(existing_variables, variables)
else:
modified["variables"] = variables
return modified
@@ -284,15 +307,18 @@ GQL_OPERATIONS: dict[str, GQLOperation] = {
# returns live channels for a particular game
"GameDirectory": GQLOperation(
"DirectoryPage_Game",
"df4bb6cc45055237bfaf3ead608bbafb79815c7100b6ee126719fac3762ddf8b",
"3c9a94ee095c735e43ed3ad6ce6d4cbd03c4c6f754b31de54993e0d48fd54e30",
variables={
"limit": ..., # limit of channels returned
"name": ..., # game name
"slug": ..., # game slug
"imageWidth": 50,
"options": {
"broadcasterLanguages": [],
"freeformTags": None,
"includeRestricted": ["SUB_ONLY_LIVE"],
"recommendationsContext": {"platform": "web"},
"sort": "RELEVANCE",
"tags": [], # list of tag IDs
"tags": [],
"requestID": "JIRA-VXP-2397",
},
"sortTypeIsRecency": False,
+5 -7
View File
@@ -65,7 +65,6 @@ from constants import (
GQL_OPERATIONS,
MAX_CHANNELS,
WATCH_INTERVAL,
# DROPS_ENABLED_TAG,
State,
ClientType,
WebsocketTopic,
@@ -1537,7 +1536,7 @@ class Twitch:
if (
"message" in error_dict
and error_dict["message"] in (
"service error",
# "service error",
"service unavailable",
"service timeout",
"context deadline exceeded",
@@ -1558,8 +1557,7 @@ class Twitch:
merged = {}
for key in set(chain(primary_data.keys(), secondary_data.keys())):
in_primary = key in primary_data
in_secondary = key in secondary_data
if in_primary and in_secondary:
if in_primary and key in secondary_data:
vp = primary_data[key]
vs = secondary_data[key]
if not isinstance(vp, type(vs)) or not isinstance(vs, type(vp)):
@@ -1693,15 +1691,15 @@ class Twitch:
response = await self.gql_request(
GQL_OPERATIONS["GameDirectory"].with_variables({
"limit": limit,
"name": game.name,
"slug": game.slug,
"options": {
"includeRestricted": ["SUB_ONLY_LIVE"],
# "tags": [DROPS_ENABLED_TAG],
"systemFilters": ["DROPS_ENABLED"],
},
})
)
return [
Channel.from_directory(self, stream_channel_data["node"])
Channel.from_directory(self, stream_channel_data["node"], drops_enabled=True)
for stream_channel_data in response["data"]["game"]["streams"]["edges"]
]
+8
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import io
import os
import re
import sys
import json
import random
@@ -402,3 +403,10 @@ class Game:
def __hash__(self) -> int:
return self.id
@property
def slug(self) -> str:
"""
Converts the game name into a slug, useable for the GQL API.
"""
return re.sub(r'(?![- ])\W', '', self.name.lower().replace(' ', '-'))