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:
1 parent
67cfc6603d
commit
5e862f9543
4 files changed
+54
-22
No files matched your search
+10
-10
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
]
|
||||
|
||||
|
||||
@@ -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(' ', '-'))
|
||||
Reference in new issue
Block a user