Compare commits

..

13 Commits

Author SHA1 Message Date
d80d97bb9b Merge pull request '1.2.3' (#5) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 36s
Reviewed-on: #5
2026-03-08 18:05:57 +03:00
4f78653596 Фикс получения EventSub
All checks were successful
Verify Dev Build / publish (push) Successful in 38s
2026-03-08 18:04:33 +03:00
19b8d7fb49 Merge pull request '1.2.2' (#4) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 34s
Reviewed-on: #4
2026-03-08 07:32:09 +03:00
0e83d8eaa8 Исправлена работа клиента авторизации
Some checks failed
Verify Dev Build / publish (push) Has been cancelled
2026-03-08 07:30:55 +03:00
818affc74b Merge pull request '1.2.1' (#3) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 35s
Reviewed-on: #3
2026-03-08 00:27:16 +03:00
645f7029ad Фикс base_url
All checks were successful
Verify Dev Build / publish (push) Successful in 38s
2026-03-08 00:25:32 +03:00
63a874b94c Правки README
All checks were successful
Verify Dev Build / publish (push) Successful in 38s
2026-02-26 17:57:07 +03:00
93fe1beb2f Merge pull request '1.2.0' (#2) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 38s
Reviewed-on: #2
2026-02-26 15:23:58 +03:00
ab914bf58c Добавлен хелпер на пагинацию
All checks were successful
Verify Dev Build / publish (push) Successful in 39s
2026-02-26 15:17:40 +03:00
8cc2b53376 Добавлен хелпер на авторизацию
All checks were successful
Verify Dev Build / publish (push) Successful in 39s
2026-02-26 13:24:16 +03:00
8b81c68de3 Добавлено кеширование для GET запросов
All checks were successful
Verify Dev Build / publish (push) Successful in 39s
2026-02-26 13:15:29 +03:00
8dc0781e96 Merge pull request '1.1.0' (#1) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 38s
Reviewed-on: #1
2026-02-25 12:26:19 +03:00
2b53f309c2 Заменена логика с пагинацией на возврат генератора с ответами
All checks were successful
Verify Dev Build / publish (push) Successful in 38s
2026-02-25 12:22:37 +03:00
5 changed files with 750 additions and 2382 deletions

View File

@ -37,9 +37,10 @@ import asyncio
from oxidetwitch.api import TwitchAPIClient from oxidetwitch.api import TwitchAPIClient
async def main(): async def main():
async with TwitchClient( async with TwitchAPIClient(
client_id="your_id", client_id="your_id",
client_secret="your_client_secret", client_secret="your_client_secret",
redirect_uri="https://example.com",
redis_url="redis://localhost:6379", redis_url="redis://localhost:6379",
) as twitch: ) as twitch:
# Get user data (automatically cached if configured) # Get user data (automatically cached if configured)
@ -57,11 +58,10 @@ If you are polling 100+ streams, OxideTwitch spaces out the requests using the *
```python ```python
async def poll_streams(channels): async def poll_streams(channels):
async with TwitchClient(...) as twitch: async with TwitchAPIClient(...) as twitch:
# These will be executed as fast as the rate limiter allows # These will be executed as fast as the rate limiter allows
tasks = [twitch.get_stream(user_login=name) for name in channels] tasks = [twitch.get_streams(..., user_login=name) for name in channels]
streams = await asyncio.gather(*tasks) streams = await asyncio.gather(*tasks)
return [s for s in streams if s.is_live]
``` ```

View File

@ -1,6 +1,6 @@
[project] [project]
name = "oxidetwitch" name = "oxidetwitch"
version = "1.0.0" version = "1.2.3"
description = "Client for Twitch API" description = "Client for Twitch API"
readme = "README.md" readme = "README.md"
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }] authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
@ -99,6 +99,3 @@ ignore-one-line-docstrings = true
quote-style = "single" quote-style = "single"
indent-style = "space" indent-style = "space"
docstring-code-format = true docstring-code-format = true
[tool.uv.sources]
aiohttpx = { index = "Miwory" }

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ class TwitchAuthClient(OxideHTTP):
redis_url: str | None = None, redis_url: str | None = None,
proxy_url: str | None = None, proxy_url: str | None = None,
) -> None: ) -> None:
self.base_uri = 'https://id.twitch.tv/oauth2' self.base_uri = 'https://id.twitch.tv/oauth2/'
self.client_id = client_id self.client_id = client_id
self.client_secret = client_secret self.client_secret = client_secret
self.redirect_uri = redirect_uri self.redirect_uri = redirect_uri
@ -62,7 +62,7 @@ class TwitchAuthClient(OxideHTTP):
match req.status_code: match req.status_code:
case st.OK: case st.OK:
return s.AppAccessToken.model_validate(req.json()) return s.AppAccessToken.model_validate(await req.json())
case _: case _:
raise s.InternalError(req.status_code, 'Internal Server Error') raise s.InternalError(req.status_code, 'Internal Server Error')
@ -81,7 +81,7 @@ class TwitchAuthClient(OxideHTTP):
match req.status_code: match req.status_code:
case st.OK: case st.OK:
return s.UserAccessToken.model_validate(req.json()) return s.UserAccessToken.model_validate(await req.json())
case st.BAD_REQUEST: case st.BAD_REQUEST:
return None return None
@ -99,7 +99,7 @@ class TwitchAuthClient(OxideHTTP):
match req.status_code: match req.status_code:
case st.OK: case st.OK:
return s.AccessTokenValidation.model_validate(req.json()) return s.AccessTokenValidation.model_validate(await req.json())
case st.UNAUTHORIZED: case st.UNAUTHORIZED:
return None return None
@ -122,7 +122,7 @@ class TwitchAuthClient(OxideHTTP):
match req.status_code: match req.status_code:
case st.OK: case st.OK:
return s.UserAccessToken.model_validate(req.json()) return s.UserAccessToken.model_validate(await req.json())
case st.BAD_REQUEST: case st.BAD_REQUEST:
return None return None

View File

@ -33,6 +33,14 @@ class Pagination(BaseSchema):
cursor: str cursor: str
class BasePaginated(BaseSchema):
pagination: Pagination | dict[Any, Any] | None = None
class PaginatedSchema[T](BasePaginated):
data: list[T]
class AppAccessToken(BaseSchema): class AppAccessToken(BaseSchema):
access_token: str access_token: str
expires_in: int expires_in: int
@ -100,9 +108,8 @@ class ExtensionAnalyticsData(BaseSchema):
date_range: DateRange date_range: DateRange
class ExtensionAnalytics(BaseSchema): class ExtensionAnalytics(PaginatedSchema[ExtensionAnalyticsData]):
data: list[ExtensionAnalyticsData] data: list[ExtensionAnalyticsData]
pagination: Pagination | dict[Any, Any] | None = None
class GameAnalyticsData(BaseSchema): class GameAnalyticsData(BaseSchema):
@ -112,9 +119,8 @@ class GameAnalyticsData(BaseSchema):
date_range: DateRange date_range: DateRange
class GameAnalytics(BaseSchema): class GameAnalytics(PaginatedSchema[GameAnalyticsData]):
data: list[GameAnalyticsData] data: list[GameAnalyticsData]
pagination: Pagination | dict[Any, Any] | None = None
class BitsLeaderboardData(BaseSchema): class BitsLeaderboardData(BaseSchema):
@ -213,9 +219,8 @@ class ExtensionTransactionsData(BaseSchema):
product_data: ExtensionProductData product_data: ExtensionProductData
class ExtensionTransactions(BaseSchema): class ExtensionTransactions(PaginatedSchema[ExtensionTransactionsData]):
data: list[ExtensionTransactionsData] data: list[ExtensionTransactionsData]
pagination: Pagination | dict[Any, Any] | None = None
class ContentClassificationLabel(TypedDict): class ContentClassificationLabel(TypedDict):
@ -265,9 +270,8 @@ class FollowedChannel(BaseSchema):
followed_at: datetime followed_at: datetime
class FollowedChannels(BaseSchema): class FollowedChannels(PaginatedSchema[FollowedChannel]):
data: list[FollowedChannel] data: list[FollowedChannel]
pagination: Pagination | dict[Any, Any] | None = None
total: int total: int
@ -278,9 +282,8 @@ class ChannelFollower(BaseSchema):
followed_at: datetime followed_at: datetime
class ChannelFollowers(BaseSchema): class ChannelFollowers(PaginatedSchema[ChannelFollower]):
data: list[ChannelFollower] data: list[ChannelFollower]
pagination: Pagination | dict[Any, Any] | None = None
total: int total: int
@ -353,7 +356,7 @@ class CustomRewardRedemption(BaseSchema):
reward: CustomRewardRedemptionReward reward: CustomRewardRedemptionReward
class CustomRewardRedemptions(BaseSchema): class CustomRewardRedemptions(PaginatedSchema[CustomRewardRedemption]):
data: list[CustomRewardRedemption] data: list[CustomRewardRedemption]
@ -401,9 +404,8 @@ class CharityDonation(BaseSchema):
amount: CharityDonationAmount amount: CharityDonationAmount
class CharityDonations(BaseSchema): class CharityDonations(PaginatedSchema[CharityDonation]):
data: list[CharityDonation] data: list[CharityDonation]
pagination: Pagination | dict[Any, Any] | None = None
class ChattersData(BaseSchema): class ChattersData(BaseSchema):
@ -412,9 +414,8 @@ class ChattersData(BaseSchema):
user_name: str user_name: str
class Chatters(BaseSchema): class Chatters(PaginatedSchema[ChattersData]):
data: list[ChattersData] data: list[ChattersData]
pagination: Pagination | dict[Any, Any] | None = None
total: int total: int
@ -521,8 +522,8 @@ class SharedChatSession(BaseSchema):
data: list[SharedChatSessionData] data: list[SharedChatSessionData]
class UserEmotes(ChannelEmotes): class UserEmotes(PaginatedSchema[ChannelEmote], ChannelEmotes):
pagination: Pagination | dict[Any, Any] | None = None pass
class MessageDropReason(BaseSchema): class MessageDropReason(BaseSchema):
@ -571,9 +572,8 @@ class Clip(BaseSchema):
is_featured: bool is_featured: bool
class Clips(BaseSchema): class Clips(PaginatedSchema[Clip]):
data: list[Clip] data: list[Clip]
pagination: Pagination | dict[Any, Any] | None = None
class ClipDownload(BaseSchema): class ClipDownload(BaseSchema):
@ -625,9 +625,8 @@ class ConduitShard(BaseSchema):
transport: ConduitShardTransportWebhook | ConduitShardTransportWebsocket transport: ConduitShardTransportWebhook | ConduitShardTransportWebsocket
class ConduitShards(BaseSchema): class ConduitShards(PaginatedSchema[ConduitShard]):
data: list[ConduitShard] data: list[ConduitShard]
pagination: Pagination | dict[Any, Any] | None = None
class UpdateConduitShardTransportWebhook(TypedDict): class UpdateConduitShardTransportWebhook(TypedDict):
@ -687,9 +686,8 @@ class DropEntitlement(BaseSchema):
last_updated: datetime last_updated: datetime
class DropsEntitlements(BaseSchema): class DropsEntitlements(PaginatedSchema[DropEntitlement]):
data: list[DropEntitlement] data: list[DropEntitlement]
pagination: Pagination | dict[Any, Any] | None = None
class UpdateDropsEntitlementsData(BaseSchema): class UpdateDropsEntitlementsData(BaseSchema):
@ -722,9 +720,8 @@ class ExtensionLiveChannel(BaseSchema):
title: str title: str
class ExtensionLiveChannels(BaseSchema): class ExtensionLiveChannels(PaginatedSchema[ExtensionLiveChannel]):
data: list[ExtensionLiveChannel] data: list[ExtensionLiveChannel]
paginaiton: Pagination | dict[Any, Any] | None = None
class ExtensionSecret(BaseSchema): class ExtensionSecret(BaseSchema):
@ -837,12 +834,11 @@ class ExtensionBitsProducts(BaseSchema):
data: list[ExtensionBitsProduct] data: list[ExtensionBitsProduct]
class EventsubBaseSubscriptions(BaseSchema): class EventsubBaseSubscriptions(PaginatedSchema[sub.Any]):
data: list[sub.Any] data: list[sub.Any]
total: int total: int
total_cost: int total_cost: int
max_total_cost: int max_total_cost: int
pagination: Pagination | dict[Any, Any] | None = None
class Game(BaseSchema): class Game(BaseSchema):
@ -852,9 +848,8 @@ class Game(BaseSchema):
igdb_id: int | str igdb_id: int | str
class Games(BaseSchema): class Games(PaginatedSchema[Game]):
data: list[Game] data: list[Game]
paginaiton: Pagination | dict[Any, Any] | None = None
class CreatorGoal(BaseSchema): class CreatorGoal(BaseSchema):
@ -970,9 +965,8 @@ class BannedUser(BaseSchema):
moderator_name: str moderator_name: str
class BannedUsers(BaseSchema): class BannedUsers(PaginatedSchema[BannedUser]):
data: list[BannedUser] data: list[BannedUser]
pagination: Pagination | dict[Any, Any] | None = None
class BanUserData(BaseSchema): class BanUserData(BaseSchema):
@ -1007,9 +1001,8 @@ class UnbanRequest(BaseSchema):
resolution_text: str | None resolution_text: str | None
class UnbanRequests(BaseSchema): class UnbanRequests(PaginatedSchema[UnbanRequest]):
data: list[UnbanRequest] data: list[UnbanRequest]
pagination: Pagination | dict[Any, Any] | None = None
class BlockedTerm(BaseSchema): class BlockedTerm(BaseSchema):
@ -1022,9 +1015,8 @@ class BlockedTerm(BaseSchema):
expires_at: datetime | None expires_at: datetime | None
class BlockedTerms(BaseSchema): class BlockedTerms(PaginatedSchema[BlockedTerm]):
data: list[BlockedTerm] data: list[BlockedTerm]
pagination: Pagination | dict[Any, Any] | None = None
class ModeratedChannel(BaseSchema): class ModeratedChannel(BaseSchema):
@ -1033,9 +1025,8 @@ class ModeratedChannel(BaseSchema):
broadcaster_name: str broadcaster_name: str
class ModeratedChannels(BaseSchema): class ModeratedChannels(PaginatedSchema[ModeratedChannel]):
data: list[ModeratedChannel] data: list[ModeratedChannel]
pagination: Pagination | dict[Any, Any] | None = None
class Moderator(BaseSchema): class Moderator(BaseSchema):
@ -1044,9 +1035,8 @@ class Moderator(BaseSchema):
user_name: str user_name: str
class Moderators(BaseSchema): class Moderators(PaginatedSchema[Moderator]):
data: list[Moderator] data: list[Moderator]
pagination: Pagination | dict[Any, Any] | None = None
class VIP(BaseSchema): class VIP(BaseSchema):
@ -1055,9 +1045,8 @@ class VIP(BaseSchema):
user_name: str user_name: str
class VIPs(BaseSchema): class VIPs(PaginatedSchema[VIP]):
data: list[VIP] data: list[VIP]
pagination: Pagination | dict[Any, Any] | None = None
class ShieldModeStatusData(BaseSchema): class ShieldModeStatusData(BaseSchema):
@ -1107,9 +1096,8 @@ class Poll(BaseSchema):
ended_at: datetime | None ended_at: datetime | None
class Polls(BaseSchema): class Polls(PaginatedSchema[Poll]):
data: list[Poll] data: list[Poll]
pagination: Pagination | dict[Any, Any] | None = None
class PredictionTopPredictor(BaseSchema): class PredictionTopPredictor(BaseSchema):
@ -1144,9 +1132,8 @@ class Prediction(BaseSchema):
locked_at: datetime | None locked_at: datetime | None
class Predictions(BaseSchema): class Predictions(PaginatedSchema[Prediction]):
data: list[Prediction] data: list[Prediction]
pagination: Pagination | dict[Any, Any] | None = None
class Raid(BaseSchema): class Raid(BaseSchema):
@ -1186,9 +1173,8 @@ class Schedule(BaseSchema):
segments: list[ScheduleSegment] segments: list[ScheduleSegment]
class Schedules(BaseSchema): class Schedules(PaginatedSchema[Schedule]):
data: list[Schedule] data: list[Schedule]
pagination: Pagination | dict[Any, Any] | None = None
class Category(BaseSchema): class Category(BaseSchema):
@ -1197,9 +1183,8 @@ class Category(BaseSchema):
box_art_url: str box_art_url: str
class Categories(BaseSchema): class Categories(PaginatedSchema[Category]):
data: list[Category] data: list[Category]
pagination: Pagination | dict[Any, Any] | None = None
class Channel(BaseSchema): class Channel(BaseSchema):
@ -1216,9 +1201,8 @@ class Channel(BaseSchema):
started_at: datetime | None started_at: datetime | None
class Channels(BaseSchema): class Channels(PaginatedSchema[Channel]):
data: list[Channel] data: list[Channel]
pagination: Pagination | dict[Any, Any] | None = None
class StreamKey(BaseSchema): class StreamKey(BaseSchema):
@ -1247,9 +1231,8 @@ class Stream(BaseSchema):
is_mature: bool is_mature: bool
class Streams(BaseSchema): class Streams(PaginatedSchema[Stream]):
data: list[Stream] data: list[Stream]
pagination: Pagination | dict[Any, Any] | None = None
class BaseStreamMarker(BaseSchema): class BaseStreamMarker(BaseSchema):
@ -1279,9 +1262,8 @@ class StreamMarkersData(BaseSchema):
videos: list[StreamMarkerVideo] videos: list[StreamMarkerVideo]
class StreamMarkers(BaseSchema): class StreamMarkers(PaginatedSchema[StreamMarkersData]):
data: list[StreamMarkersData] data: list[StreamMarkersData]
pagination: Pagination | dict[Any, Any] | None = None
class Subscription(BaseSchema): class Subscription(BaseSchema):
@ -1302,9 +1284,8 @@ class BroadcasterSubscription(Subscription):
user_name: str user_name: str
class BroadcasterSubscriptions(BaseSchema): class BroadcasterSubscriptions(PaginatedSchema[BroadcasterSubscription]):
data: list[BroadcasterSubscription] data: list[BroadcasterSubscription]
pagination: Pagination | dict[Any, Any] | None = None
total: int total: int
points: int points: int
@ -1390,7 +1371,7 @@ class UserBlock(BaseSchema):
display_name: str display_name: str
class UserBlockList(BaseSchema): class UserBlockList(PaginatedSchema[UserBlock]):
data: list[UserBlock] data: list[UserBlock]
@ -1478,9 +1459,8 @@ class Video(BaseSchema):
muted_segments: list[VideoMutedSegment] muted_segments: list[VideoMutedSegment]
class Videos(BaseSchema): class Videos(PaginatedSchema[Video]):
data: list[Video] data: list[Video]
pagination: Pagination | dict[Any, Any] | None
class DeleteVideos(BaseSchema): class DeleteVideos(BaseSchema):