Skip to content

Commit 6958aea

Browse files
authored
Additional methods + fix remove_cog (#195)
* Replace if statement with if expression * Simplify conditional into return statement * Inline variable that is immediately returned * Comparison moved to the right Use set when checking membership of a collection of literals * Inline variable that is immediately returned * remove none from dict.get returns none by default * Replace if statement with if expression Replace multiple comparisons of same variable with `in` operator Remove redundant conditional * Merge nested if conditions Remove none from dict.get returns none by default * Lift code into else after jump in control flow, Merge else clause's nested if statement into elif Remove redundant pass statements Simplify logical expression * Inline variable that is immediately returned * Merge nested if conditions Inline variable that is immediately returned * Merge nested if conditions * Swap if/else branches, Remove unnecessary else after guard condition * Replace unneeded comprehension with generator * added fetch_channel to client * formatting * remove typehint of User * Added fetch_follow method Removed for loop for fetch_channel * Update README.rst Added myself....just because * fix remove_cog * Fixed remove_cog * changed from list to set * Moved fetch_follow to user from client Fixed black formatting to 120 * Added ChannelInfo class in models Changed fetch_channel to return the ChannelInfo object * Removed except from remove_cog Issue was in unload_module Missing .items() for callsbacks Missing .__name__ for comparison * fixed reload_module * added created_at to User class * Routine fix where after task run completion iterations would set to 0 and run infinitely. * Add typehint for PartialUser in fetch_follow
1 parent c53700c commit 6958aea

File tree

16 files changed

+177
-79
lines changed

16 files changed

+177
-79
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,6 @@ Special thanks to:
111111

112112
`Scragly <https://github.com/scragly>`_
113113

114+
`Chillymosh <https://github.com/chillymosh>`_
114115

115116
If I have forgotten anyone please let me know <3: `EvieePy <https://github.com/EvieePy>`_

twitchio/abcs.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ def get_bucket(self, channel: str, method: str) -> RateBucket:
4242

4343
if bucket.method != method:
4444
bucket.method = method
45-
if method == "mod":
46-
bucket.limit = bucket.MODLIMIT
47-
else:
48-
bucket.limit = bucket.IRCLIMIT
49-
45+
bucket.limit = bucket.MODLIMIT if method == "mod" else bucket.IRCLIMIT
5046
self.buckets[channel] = bucket
5147

5248
return bucket

twitchio/chatter.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,7 @@ def is_mod(self) -> bool:
160160
"""A boolean indicating whether the User is a moderator of the current channel."""
161161
if self._mod == 1:
162162
return True
163-
if self.channel.name == self.display_name.lower():
164-
return True
165-
else:
166-
return False
163+
return self.channel.name == self.display_name.lower()
167164

168165
@property
169166
def is_turbo(self) -> Optional[bool]:

twitchio/client.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import sys
3131
from typing import Union, Callable, List, Optional, Tuple, Any
3232

33+
from twitchio.errors import HTTPException
3334
from . import models
3435
from .websocket import WSConnection
3536
from .http import TwitchHTTP
@@ -83,7 +84,11 @@ def __init__(
8384

8485
self._http = TwitchHTTP(self, api_token=token, client_secret=client_secret)
8586
self._connection = WSConnection(
86-
client=self, token=token, loop=self.loop, initial_channels=initial_channels, heartbeat=heartbeat
87+
client=self,
88+
token=token,
89+
loop=self.loop,
90+
initial_channels=initial_channels,
91+
heartbeat=heartbeat,
8792
)
8893

8994
self._events = {}
@@ -127,7 +132,10 @@ def from_client_credentials(
127132
self._http = TwitchHTTP(self, client_id=client_id, client_secret=client_secret)
128133
self._heartbeat = heartbeat
129134
self._connection = WSConnection(
130-
client=self, loop=self.loop, initial_channels=None, heartbeat=self._heartbeat
135+
client=self,
136+
loop=self.loop,
137+
initial_channels=None,
138+
heartbeat=self._heartbeat,
131139
) # The only reason we're even creating this is to avoid attribute errors
132140
self._events = {}
133141
self._waiting = []
@@ -192,7 +200,10 @@ async def wrapped(func):
192200
if inspect.iscoroutinefunction(inner_cb):
193201
self.loop.create_task(wrapped(inner_cb))
194202
else:
195-
warnings.warn(f"event '{name}' callback is not a coroutine", category=RuntimeWarning)
203+
warnings.warn(
204+
f"event '{name}' callback is not a coroutine",
205+
category=RuntimeWarning,
206+
)
196207

197208
if name in self._events:
198209
for event in self._events[name]:
@@ -239,7 +250,11 @@ def decorator(func: Callable) -> Callable:
239250
return decorator
240251

241252
async def wait_for(
242-
self, event: str, predicate: Callable[[], bool] = lambda *a: True, *, timeout=60.0
253+
self,
254+
event: str,
255+
predicate: Callable[[], bool] = lambda *a: True,
256+
*,
257+
timeout=60.0,
243258
) -> Tuple[Any]:
244259
"""|coro|
245260
@@ -285,8 +300,26 @@ def get_channel(self, name: str) -> Optional[Channel]:
285300
# With the associated users as a set.
286301
# We create a Channel here and return it only if the cache has that channel key.
287302

288-
channel = Channel(name=name, websocket=self._connection)
289-
return channel
303+
return Channel(name=name, websocket=self._connection)
304+
305+
async def fetch_channel(self, broadcaster: str) -> list:
306+
"""Retrieve a channel from the API.
307+
308+
Parameters
309+
-----------
310+
name: str
311+
The channel name or ID to request from API. Returns empty list if no channel was found.
312+
313+
Returns
314+
--------
315+
:class:`list`
316+
"""
317+
318+
if not broadcaster.isdigit():
319+
get_id = await self.fetch_users(names=[broadcaster.lower()])
320+
broadcaster = get_id[0].id
321+
322+
return await self._http.get_channels(broadcaster)
290323

291324
async def join_channels(self, channels: Union[List[str], Tuple[str]]):
292325
"""|coro|
@@ -334,7 +367,11 @@ def create_user(self, user_id: int, user_name: str) -> PartialUser:
334367

335368
@user_cache()
336369
async def fetch_users(
337-
self, names: List[str] = None, ids: List[int] = None, token: str = None, force=False
370+
self,
371+
names: List[str] = None,
372+
ids: List[int] = None,
373+
token: str = None,
374+
force=False,
338375
) -> List[User]:
339376
"""|coro|
340377
Fetches users from the helix API
@@ -378,6 +415,34 @@ async def fetch_clips(self, ids: List[str]):
378415
data = await self._http.get_clips(ids=ids)
379416
return [models.Clip(self._http, d) for d in data]
380417

418+
async def fetch_channel(self, broadcaster: str):
419+
"""Retrieve a channel from the API.
420+
421+
Parameters
422+
-----------
423+
name: str
424+
The channel name or ID to request from API. Returns empty dict if no channel was found.
425+
426+
Returns
427+
--------
428+
:class:`twitchio.ChannelInfo`
429+
"""
430+
431+
if not broadcaster.isdigit():
432+
get_id = await self.fetch_users(names=[broadcaster.lower()])
433+
if not get_id:
434+
raise IndexError("Invalid channel name.")
435+
broadcaster = get_id[0].id
436+
try:
437+
data = await self._http.get_channels(broadcaster)
438+
439+
from .models import ChannelInfo
440+
441+
return ChannelInfo(self._http, data=data[0])
442+
443+
except HTTPException:
444+
raise HTTPException("Incorrect channel ID.")
445+
381446
async def fetch_videos(
382447
self,
383448
ids: List[int] = None,
@@ -419,7 +484,13 @@ async def fetch_videos(
419484
from .models import Video
420485

421486
data = await self._http.get_videos(
422-
ids, user_id=user_id, game_id=game_id, period=period, sort=sort, type=type, language=language
487+
ids,
488+
user_id=user_id,
489+
game_id=game_id,
490+
period=period,
491+
sort=sort,
492+
type=type,
493+
language=language,
423494
)
424495
return [Video(self._http, x) for x in data]
425496

twitchio/ext/commands/bot.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,7 @@ def get_cog(self, name: str) -> Optional[Cog]:
192192
---------
193193
Optional[:class:`.Cog`]
194194
"""
195-
cog = self.cogs.get(name, None)
196-
197-
return cog
195+
return self.cogs.get(name, None)
198196

199197
async def get_context(self, message, *, cls=None):
200198
"""Get a Context object from a message.
@@ -302,11 +300,11 @@ def unload_module(self, name: str):
302300
except:
303301
pass
304302

305-
to_delete = [cog_name for cog_name, cog in self._cogs.items() if cog.__module__ == module]
303+
to_delete = [cog_name for cog_name, cog in self._cogs.items() if cog.__module__ == module.__name__]
306304
for name in to_delete:
307305
self.remove_cog(name)
308306

309-
to_delete = [name for name, cmd in self._commands if cmd._callback.__module__ == module]
307+
to_delete = [name for name, cmd in self._commands.items() if cmd._callback.__module__ == module.__name__]
310308
for name in to_delete:
311309
self.remove_command(name)
312310

@@ -330,7 +328,7 @@ def reload_module(self, name: str):
330328
if name not in self._modules:
331329
raise ValueError(f"Module <{name}> is not loaded")
332330

333-
module = self._modules.pop(name)
331+
module = self._modules[name]
334332

335333
modules = {
336334
name: m
@@ -370,7 +368,6 @@ def add_cog(self, cog: Cog):
370368

371369
def remove_cog(self, cog_name: str):
372370
"""Method which removes a cog from the bot.
373-
374371
Parameters
375372
----------
376373
cog_name: str
@@ -380,7 +377,6 @@ def remove_cog(self, cog_name: str):
380377
raise InvalidCog(f"Cog '{cog_name}' not found")
381378

382379
cog = self._cogs.pop(cog_name)
383-
384380
cog._unload_methods(self)
385381

386382
async def global_before_invoke(self, ctx):

twitchio/ext/commands/core.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,12 @@ def full_name(self):
102102
return f"{self.parent.full_name} {self._name}"
103103

104104
def _resolve_converter(self, converter: Union[Callable, Awaitable, type]) -> Callable:
105-
if isinstance(converter, type) and converter.__module__.startswith("twitchio"):
106-
if converter in builtin_converter._mapping:
107-
return builtin_converter._mapping[converter]
105+
if (
106+
isinstance(converter, type)
107+
and converter.__module__.startswith("twitchio")
108+
and converter in builtin_converter._mapping
109+
):
110+
return builtin_converter._mapping[converter]
108111

109112
return converter
110113

@@ -277,9 +280,8 @@ async def handle_checks(self, context):
277280
if not result:
278281
raise CheckFailure(f"The check {predicate} for command {self.name} failed.")
279282

280-
if self.cog:
281-
if not await self.cog.cog_check(context):
282-
raise CheckFailure(f"The cog check for command <{self.name}> failed.")
283+
if self.cog and not await self.cog.cog_check(context):
284+
raise CheckFailure(f"The cog check for command <{self.name}> failed.")
283285

284286
return True
285287
except Exception as e:
@@ -457,9 +459,12 @@ def command(
457459

458460
def decorator(func: Callable):
459461
fname = name or func.__name__
460-
cmd = cls(name=fname, func=func, aliases=aliases, no_global_checks=no_global_checks)
461-
462-
return cmd
462+
return cls(
463+
name=fname,
464+
func=func,
465+
aliases=aliases,
466+
no_global_checks=no_global_checks,
467+
)
463468

464469
return decorator
465470

@@ -477,16 +482,14 @@ def group(
477482

478483
def decorator(func: Callable):
479484
fname = name or func.__name__
480-
cmd = cls(
485+
return cls(
481486
name=fname,
482487
func=func,
483488
aliases=aliases,
484489
no_global_checks=no_global_checks,
485490
invoke_with_subcommand=invoke_with_subcommand,
486491
)
487492

488-
return cmd
489-
490493
return decorator
491494

492495

twitchio/ext/commands/meta.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ def __new__(mcs, *args, **kwargs):
4747
self._commands = {}
4848

4949
for name, mem in inspect.getmembers(self):
50-
if isinstance(mem, (CogEvent, Command)):
51-
if name.startswith(("cog_", "bot_")): # Invalid method prefixes
52-
raise RuntimeError(f'The event or command "{name}" starts with an invalid prefix (cog_ or bot_).')
50+
if isinstance(mem, (CogEvent, Command)) and name.startswith(
51+
("cog_", "bot_")
52+
): # Invalid method prefixes
53+
raise RuntimeError(f'The event or command "{name}" starts with an invalid prefix (cog_ or bot_).')
5354

5455
if isinstance(mem, CogEvent):
5556
try:

twitchio/ext/pubsub/pool.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,16 @@ async def unsubscribe_topics(self, topics: List[Topic]):
8787
self._pool.remove(node)
8888

8989
def _find_node(self, topics: List[Topic]) -> Optional[PubSubWebsocket]:
90-
if self._mode == "group":
91-
for p in self._pool:
92-
if len(p.max_topics) + len(topics) <= p.max_topics:
93-
return p
94-
95-
if len(self._pool) < self._max_size:
96-
return None
97-
else:
98-
raise models.PoolFull(
99-
f"The pubsub pool has reached maximum topics. Unable to allocate a group of {len(topics)} topics."
100-
)
90+
if self._mode != "group":
91+
raise ValueError("group is the only supported mode.")
92+
93+
for p in self._pool:
94+
if len(p.max_topics) + len(topics) <= p.max_topics:
95+
return p
10196

97+
if len(self._pool) < self._max_size:
98+
return None
10299
else:
103-
raise ValueError("group is the only supported mode.")
100+
raise models.PoolFull(
101+
f"The pubsub pool has reached maximum topics. Unable to allocate a group of {len(topics)} topics."
102+
)

twitchio/ext/pubsub/websocket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def subscribe_topics(self, topics: List[Topic]):
134134
await self._send_topics(topics)
135135

136136
async def unsubscribe_topic(self, topics: List[Topic]):
137-
if not all([t in self.topics for t in topics]):
137+
if any(t not in self.topics for t in topics):
138138
raise ValueError("Topics were given that have not been subscribed to")
139139

140140
await self._send_topics(topics, type="UNLISTEN")

twitchio/ext/routines/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ async def _routine(self, *args, **kwargs) -> None:
288288
if self._wait_first and not self._time:
289289
await asyncio.sleep(self._delta)
290290

291+
if self._remaining_iterations == 0:
292+
self._remaining_iterations = self._iterations
293+
291294
while True:
292295
start = datetime.datetime.now(datetime.timezone.utc)
293296

0 commit comments

Comments
 (0)