Skip to content

Commit bc65337

Browse files
authored
Add ctx.reply (#223)
1 parent 882cf25 commit bc65337

File tree

9 files changed

+77
-7
lines changed

9 files changed

+77
-7
lines changed

twitchio/abcs.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ def _fetch_channel(self):
6363
def _fetch_websocket(self):
6464
raise NotImplementedError
6565

66+
@abc.abstractmethod
67+
def _fetch_message(self):
68+
raise NotImplementedError
69+
6670
@abc.abstractmethod
6771
def _bot_is_mod(self):
6872
raise NotImplementedError
@@ -121,3 +125,39 @@ async def send(self, content: str):
121125
await ws.send(f"PRIVMSG #jtv :/w {name} {content}\r\n")
122126
else:
123127
await ws.send(f"PRIVMSG #{name} :{content}\r\n")
128+
129+
async def reply(self, content: str):
130+
"""|coro|
131+
132+
133+
Send a message in reply to the user who sent a message in the destination
134+
associated with the dataclass.
135+
136+
Destination will be the context of which the message/command was sent.
137+
138+
Parameters
139+
------------
140+
content: str
141+
The content you wish to send as a message. The content must be a string.
142+
143+
Raises
144+
--------
145+
InvalidContent
146+
Invalid content.
147+
"""
148+
entity = self._fetch_channel()
149+
ws = self._fetch_websocket()
150+
message = self._fetch_message()
151+
152+
self.check_content(content)
153+
self.check_bucket(channel=entity.name)
154+
155+
try:
156+
name = entity.channel.name
157+
except AttributeError:
158+
name = entity.name
159+
160+
if entity.__messageable_channel__:
161+
await ws.reply(message.id, f"PRIVMSG #{name} :{content}\r\n")
162+
else:
163+
await ws.send(f"PRIVMSG #jtv :/w {name} {content}\r\n")

twitchio/channel.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
class Channel(Messageable):
4141

42-
__slots__ = ("_name", "_ws")
42+
__slots__ = ("_name", "_ws", "_message")
4343

4444
__messageable_channel__ = True
4545

@@ -62,6 +62,9 @@ def _fetch_channel(self):
6262
def _fetch_websocket(self):
6363
return self._ws # Abstract method
6464

65+
def _fetch_message(self):
66+
return self._message # Abstract method
67+
6568
def _bot_is_mod(self):
6669
cache = self._ws._cache[self.name] # noqa
6770
for user in cache:

twitchio/chatter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(self, websocket, **kwargs):
4343
self._name = kwargs.get("name")
4444
self._ws = websocket
4545
self._channel = kwargs.get("channel", self._name)
46+
self._message = kwargs.get("message")
4647

4748
def __repr__(self):
4849
return f"<PartialChatter name: {self._name}, channel: {self._channel}>"
@@ -80,6 +81,9 @@ def _fetch_channel(self):
8081
def _fetch_websocket(self):
8182
return self._ws # Abstract method
8283

84+
def _fetch_message(self):
85+
return self._message # Abstract method
86+
8387
def _bot_is_mod(self):
8488
return False
8589

twitchio/ext/commands/bot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ async def get_context(self, message, *, cls=None):
232232
try:
233233
command_ = parsed.pop(0)
234234
except KeyError:
235-
raise CommandNotFound(f"No valid command was passed.")
235+
raise CommandNotFound("No valid command was passed.")
236236

237237
try:
238238
command_ = self._command_aliases[command_]

twitchio/ext/commands/core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ async def parse_args(self, context: Context, instance: Optional[Cog], parsed: di
154154
if instance:
155155
next(iterator)
156156
except StopIteration:
157-
raise TwitchCommandError(f"self or ctx is a required argument which is missing.")
157+
raise TwitchCommandError("self or ctx is a required argument which is missing.")
158158

159159
for _, param in iterator:
160160
index += 1
@@ -401,6 +401,9 @@ def _fetch_channel(self) -> Messageable:
401401
def _fetch_websocket(self):
402402
return self._ws # Abstract method
403403

404+
def _fetch_message(self):
405+
return self.message # Abstract method
406+
404407
def _bot_is_mod(self) -> bool:
405408
if not self.channel:
406409
return False

twitchio/message.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
class Message:
3535

36-
__slots__ = ("_raw_data", "content", "_author", "echo", "_timestamp", "_channel", "_tags")
36+
__slots__ = ("_raw_data", "content", "_author", "echo", "_timestamp", "_channel", "_tags", "_id")
3737

3838
def __init__(self, **kwargs):
3939
self._raw_data = kwargs.get("raw_data")
@@ -44,10 +44,17 @@ def __init__(self, **kwargs):
4444
self.echo = kwargs.get("echo", False)
4545

4646
try:
47+
self._id = self._tags["id"]
4748
self._timestamp = self._tags["tmi-sent-ts"]
4849
except KeyError:
50+
self._id = None
4951
self._timestamp = time.time()
5052

53+
@property
54+
def id(self) -> str:
55+
"""The Message ID."""
56+
return self._id
57+
5158
@property
5259
def author(self) -> Union["Chatter", "PartialChatter"]:
5360
"""The User object associated with the Message."""

twitchio/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,6 @@ def colour(self) -> str:
662662
def __repr__(self):
663663
return f"<PredictionOutcome outcome_id={self.outcome_id} title={self.title} channel_points={self.channel_points} color={self.color}>"
664664

665-
666665
class Schedule:
667666

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

twitchio/user.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,6 @@ async def fetch_schedule(
783783

784784
return Schedule(self._http, data)
785785

786-
787786
class BitLeaderboardUser(PartialUser):
788787

789788
__slots__ = "rank", "score"

twitchio/websocket.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,21 @@ async def send(self, message: str):
200200

201201
await self._websocket.send_str(message + "\r\n")
202202

203+
async def reply(self, msg_id: str, message: str):
204+
message = message.strip()
205+
log.debug(f" > {message}")
206+
207+
if message.startswith("PRIVMSG #"):
208+
data = message.replace("PRIVMSG #", "", 1).split(" ")
209+
channel = data.pop(0)
210+
content = " ".join(data)
211+
212+
dummy = f"> @reply-parent-msg-id={msg_id} :{self.nick}!{self.nick}@{self.nick}.tmi.twitch.tv PRIVMSG(ECHO) #{channel} {content}\r\n"
213+
task = asyncio.create_task(self._process_data(dummy))
214+
task.add_done_callback(partial(self._task_callback, dummy)) # Process our raw data
215+
216+
await self._websocket.send_str(f"@reply-parent-msg-id={msg_id} {message} \r\n")
217+
203218
async def authenticate(self, channels: Union[list, tuple]):
204219
"""|coro|
205220
@@ -435,7 +450,7 @@ async def _mode(self, parsed): # TODO
435450
pass
436451

437452
async def _reconnect(self, parsed):
438-
log.debug(f"ACTION: RECONNECT:: Twitch has gracefully closed the connection and will reconnect.")
453+
log.debug("ACTION: RECONNECT:: Twitch has gracefully closed the connection and will reconnect.")
439454
self._reconnect_requested = True
440455

441456
def dispatch(self, event: str, *args, **kwargs):

0 commit comments

Comments
 (0)