Change to Nyx bot
This commit is contained in:
parent
e3c1de3a84
commit
aec546e372
12
README.md
12
README.md
|
@ -1,4 +1,10 @@
|
||||||
# Nio Template [![Built with matrix-nio](https://img.shields.io/badge/built%20with-matrix--nio-brightgreen)](https://github.com/poljar/matrix-nio) <a href="https://matrix.to/#/#nio-template:matrix.org"><img src="https://img.shields.io/matrix/nio-template:matrix.org?color=blue&label=Join%20the%20Matrix%20Room&server_fqdn=matrix-client.matrix.org" /></a>
|
# Nyx Bot [![Built with matrix-nio](https://img.shields.io/badge/built%20with-matrix--nio-brightgreen)](https://github.com/poljar/matrix-nio) <a href="https://matrix.to/#/#nio-template:matrix.org"><img src="https://img.shields.io/matrix/nio-template:matrix.org?color=blue&label=Join%20the%20Matrix%20Room&server_fqdn=matrix-client.matrix.org" /></a>
|
||||||
|
|
||||||
|
Matrix bot named after [Nyx](https://megamitensei.fandom.com/wiki/Nyx_Avatar).
|
||||||
|
|
||||||
|
The rest are from the original template.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
A template for creating bots with
|
A template for creating bots with
|
||||||
[matrix-nio](https://github.com/poljar/matrix-nio). The documentation for
|
[matrix-nio](https://github.com/poljar/matrix-nio). The documentation for
|
||||||
|
@ -46,7 +52,7 @@ See [SETUP.md](SETUP.md) for how to setup and run the template project.
|
||||||
*A reference of each file included in the template repository, its purpose and
|
*A reference of each file included in the template repository, its purpose and
|
||||||
what it does.*
|
what it does.*
|
||||||
|
|
||||||
The majority of the code is kept inside of the `my_project_name` folder, which
|
The majority of the code is kept inside of the `nyx_bot` folder, which
|
||||||
is in itself a [python package](https://docs.python.org/3/tutorial/modules.html),
|
is in itself a [python package](https://docs.python.org/3/tutorial/modules.html),
|
||||||
the `__init__.py` file inside declaring it as such.
|
the `__init__.py` file inside declaring it as such.
|
||||||
|
|
||||||
|
@ -65,7 +71,7 @@ their needs. Be sure never to check the edited `config.yaml` into source control
|
||||||
since it'll likely contain sensitive details such as passwords!
|
since it'll likely contain sensitive details such as passwords!
|
||||||
|
|
||||||
Below is a detailed description of each of the source code files contained within
|
Below is a detailed description of each of the source code files contained within
|
||||||
the `my_project_name` directory:
|
the `nyx_bot` directory:
|
||||||
|
|
||||||
### `main.py`
|
### `main.py`
|
||||||
|
|
||||||
|
|
2
SETUP.md
2
SETUP.md
|
@ -1,5 +1,7 @@
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
|
_(This requires changing.)_
|
||||||
|
|
||||||
nio-template is a sample repository of a working Matrix bot that can be taken
|
nio-template is a sample repository of a working Matrix bot that can be taken
|
||||||
and transformed into one's own bot, service or whatever else may be necessary.
|
and transformed into one's own bot, service or whatever else may be necessary.
|
||||||
Below is a quick setup guide to running the existing bot.
|
Below is a quick setup guide to running the existing bot.
|
||||||
|
|
|
@ -58,8 +58,8 @@ RUN apk add --no-cache \
|
||||||
# Install python runtime modules. We do this before copying the source code
|
# Install python runtime modules. We do this before copying the source code
|
||||||
# such that these dependencies can be cached
|
# such that these dependencies can be cached
|
||||||
# This speeds up subsequent image builds when the source code is changed
|
# This speeds up subsequent image builds when the source code is changed
|
||||||
RUN mkdir -p /src/my_project_name
|
RUN mkdir -p /src/nyx_bot
|
||||||
COPY my_project_name/__init__.py /src/my_project_name/
|
COPY nyx_bot/__init__.py /src/nyx_bot/
|
||||||
COPY README.md my-project-name /src/
|
COPY README.md my-project-name /src/
|
||||||
|
|
||||||
# Build the dependencies
|
# Build the dependencies
|
||||||
|
@ -68,7 +68,7 @@ RUN pip install --prefix="/python-libs" --no-warn-script-location "/src/.[postgr
|
||||||
|
|
||||||
# Now copy the source code
|
# Now copy the source code
|
||||||
COPY *.py *.md /src/
|
COPY *.py *.md /src/
|
||||||
COPY my_project_name/*.py /src/my_project_name/
|
COPY nyx_bot/*.py /src/nyx_bot/
|
||||||
|
|
||||||
# And build the final module
|
# And build the final module
|
||||||
RUN pip install --prefix="/python-libs" --no-warn-script-location "/src/.[postgres]"
|
RUN pip install --prefix="/python-libs" --no-warn-script-location "/src/.[postgres]"
|
||||||
|
|
|
@ -53,14 +53,14 @@ RUN apk add --no-cache \
|
||||||
|
|
||||||
# Install python runtime modules. We do this before copying the source code
|
# Install python runtime modules. We do this before copying the source code
|
||||||
# such that these dependencies can be cached
|
# such that these dependencies can be cached
|
||||||
RUN mkdir -p /src/my_project_name
|
RUN mkdir -p /src/nyx_bot
|
||||||
COPY my_project_name/__init__.py /src/my_project_name/
|
COPY nyx_bot/__init__.py /src/nyx_bot/
|
||||||
COPY README.md my-project-name /src/
|
COPY README.md my-project-name /src/
|
||||||
COPY setup.py /src/setup.py
|
COPY setup.py /src/setup.py
|
||||||
RUN pip install -e "/src/.[postgres]"
|
RUN pip install -e "/src/.[postgres]"
|
||||||
|
|
||||||
# Now copy the source code
|
# Now copy the source code
|
||||||
COPY my_project_name/*.py /src/my_project_name/
|
COPY nyx_bot/*.py /src/nyx_bot/
|
||||||
COPY *.py /src/
|
COPY *.py /src/
|
||||||
|
|
||||||
# Specify a volume that holds the config file, SQLite3 database,
|
# Specify a volume that holds the config file, SQLite3 database,
|
||||||
|
|
|
@ -1,211 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from nio import (
|
|
||||||
AsyncClient,
|
|
||||||
InviteMemberEvent,
|
|
||||||
JoinError,
|
|
||||||
MatrixRoom,
|
|
||||||
MegolmEvent,
|
|
||||||
RoomGetEventError,
|
|
||||||
RoomMessageText,
|
|
||||||
UnknownEvent,
|
|
||||||
)
|
|
||||||
|
|
||||||
from my_project_name.bot_commands import Command
|
|
||||||
from my_project_name.chat_functions import make_pill, react_to_event, send_text_to_room
|
|
||||||
from my_project_name.config import Config
|
|
||||||
from my_project_name.message_responses import Message
|
|
||||||
from my_project_name.storage import Storage
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Callbacks:
|
|
||||||
def __init__(self, client: AsyncClient, store: Storage, config: Config):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
client: nio client used to interact with matrix.
|
|
||||||
|
|
||||||
store: Bot storage.
|
|
||||||
|
|
||||||
config: Bot configuration parameters.
|
|
||||||
"""
|
|
||||||
self.client = client
|
|
||||||
self.store = store
|
|
||||||
self.config = config
|
|
||||||
self.command_prefix = config.command_prefix
|
|
||||||
|
|
||||||
async def message(self, room: MatrixRoom, event: RoomMessageText) -> None:
|
|
||||||
"""Callback for when a message event is received
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room: The room the event came from.
|
|
||||||
|
|
||||||
event: The event defining the message.
|
|
||||||
"""
|
|
||||||
# Extract the message text
|
|
||||||
msg = event.body
|
|
||||||
|
|
||||||
# Ignore messages from ourselves
|
|
||||||
if event.sender == self.client.user:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"Bot message received for room {room.display_name} | "
|
|
||||||
f"{room.user_name(event.sender)}: {msg}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process as message if in a public room without command prefix
|
|
||||||
has_command_prefix = msg.startswith(self.command_prefix)
|
|
||||||
|
|
||||||
# room.is_group is often a DM, but not always.
|
|
||||||
# room.is_group does not allow room aliases
|
|
||||||
# room.member_count > 2 ... we assume a public room
|
|
||||||
# room.member_count <= 2 ... we assume a DM
|
|
||||||
if not has_command_prefix and room.member_count > 2:
|
|
||||||
# General message listener
|
|
||||||
message = Message(self.client, self.store, self.config, msg, room, event)
|
|
||||||
await message.process()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Otherwise if this is in a 1-1 with the bot or features a command prefix,
|
|
||||||
# treat it as a command
|
|
||||||
if has_command_prefix:
|
|
||||||
# Remove the command prefix
|
|
||||||
msg = msg[len(self.command_prefix) :]
|
|
||||||
|
|
||||||
command = Command(self.client, self.store, self.config, msg, room, event)
|
|
||||||
await command.process()
|
|
||||||
|
|
||||||
async def invite(self, room: MatrixRoom, event: InviteMemberEvent) -> None:
|
|
||||||
"""Callback for when an invite is received. Join the room specified in the invite.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room: The room that we are invited to.
|
|
||||||
|
|
||||||
event: The invite event.
|
|
||||||
"""
|
|
||||||
logger.debug(f"Got invite to {room.room_id} from {event.sender}.")
|
|
||||||
|
|
||||||
# Attempt to join 3 times before giving up
|
|
||||||
for attempt in range(3):
|
|
||||||
result = await self.client.join(room.room_id)
|
|
||||||
if type(result) == JoinError:
|
|
||||||
logger.error(
|
|
||||||
f"Error joining room {room.room_id} (attempt %d): %s",
|
|
||||||
attempt,
|
|
||||||
result.message,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.error("Unable to join room: %s", room.room_id)
|
|
||||||
|
|
||||||
# Successfully joined room
|
|
||||||
logger.info(f"Joined {room.room_id}")
|
|
||||||
|
|
||||||
async def invite_event_filtered_callback(
|
|
||||||
self, room: MatrixRoom, event: InviteMemberEvent
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Since the InviteMemberEvent is fired for every m.room.member state received
|
|
||||||
in a sync response's `rooms.invite` section, we will receive some that are
|
|
||||||
not actually our own invite event (such as the inviter's membership).
|
|
||||||
This makes sure we only call `callbacks.invite` with our own invite events.
|
|
||||||
"""
|
|
||||||
if event.state_key == self.client.user_id:
|
|
||||||
# This is our own membership (invite) event
|
|
||||||
await self.invite(room, event)
|
|
||||||
|
|
||||||
async def _reaction(
|
|
||||||
self, room: MatrixRoom, event: UnknownEvent, reacted_to_id: str
|
|
||||||
) -> None:
|
|
||||||
"""A reaction was sent to one of our messages. Let's send a reply acknowledging it.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room: The room the reaction was sent in.
|
|
||||||
|
|
||||||
event: The reaction event.
|
|
||||||
|
|
||||||
reacted_to_id: The event ID that the reaction points to.
|
|
||||||
"""
|
|
||||||
logger.debug(f"Got reaction to {room.room_id} from {event.sender}.")
|
|
||||||
|
|
||||||
# Get the original event that was reacted to
|
|
||||||
event_response = await self.client.room_get_event(room.room_id, reacted_to_id)
|
|
||||||
if isinstance(event_response, RoomGetEventError):
|
|
||||||
logger.warning(
|
|
||||||
"Error getting event that was reacted to (%s)", reacted_to_id
|
|
||||||
)
|
|
||||||
return
|
|
||||||
reacted_to_event = event_response.event
|
|
||||||
|
|
||||||
# Only acknowledge reactions to events that we sent
|
|
||||||
if reacted_to_event.sender != self.config.user_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Send a message acknowledging the reaction
|
|
||||||
reaction_sender_pill = make_pill(event.sender)
|
|
||||||
reaction_content = (
|
|
||||||
event.source.get("content", {}).get("m.relates_to", {}).get("key")
|
|
||||||
)
|
|
||||||
message = (
|
|
||||||
f"{reaction_sender_pill} reacted to this event with `{reaction_content}`!"
|
|
||||||
)
|
|
||||||
await send_text_to_room(
|
|
||||||
self.client,
|
|
||||||
room.room_id,
|
|
||||||
message,
|
|
||||||
reply_to_event_id=reacted_to_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def decryption_failure(self, room: MatrixRoom, event: MegolmEvent) -> None:
|
|
||||||
"""Callback for when an event fails to decrypt. Inform the user.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room: The room that the event that we were unable to decrypt is in.
|
|
||||||
|
|
||||||
event: The encrypted event that we were unable to decrypt.
|
|
||||||
"""
|
|
||||||
logger.error(
|
|
||||||
f"Failed to decrypt event '{event.event_id}' in room '{room.room_id}'!"
|
|
||||||
f"\n\n"
|
|
||||||
f"Tip: try using a different device ID in your config file and restart."
|
|
||||||
f"\n\n"
|
|
||||||
f"If all else fails, delete your store directory and let the bot recreate "
|
|
||||||
f"it (your reminders will NOT be deleted, but the bot may respond to existing "
|
|
||||||
f"commands a second time)."
|
|
||||||
)
|
|
||||||
|
|
||||||
red_x_and_lock_emoji = "❌ 🔐"
|
|
||||||
|
|
||||||
# React to the undecryptable event with some emoji
|
|
||||||
await react_to_event(
|
|
||||||
self.client,
|
|
||||||
room.room_id,
|
|
||||||
event.event_id,
|
|
||||||
red_x_and_lock_emoji,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def unknown(self, room: MatrixRoom, event: UnknownEvent) -> None:
|
|
||||||
"""Callback for when an event with a type that is unknown to matrix-nio is received.
|
|
||||||
Currently this is used for reaction events, which are not yet part of a released
|
|
||||||
matrix spec (and are thus unknown to nio).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
room: The room the reaction was sent in.
|
|
||||||
|
|
||||||
event: The event itself.
|
|
||||||
"""
|
|
||||||
if event.type == "m.reaction":
|
|
||||||
# Get the ID of the event this was a reaction to
|
|
||||||
relation_dict = event.source.get("content", {}).get("m.relates_to", {})
|
|
||||||
|
|
||||||
reacted_to = relation_dict.get("event_id")
|
|
||||||
if reacted_to and relation_dict.get("rel_type") == "m.annotation":
|
|
||||||
await self._reaction(room, event, reacted_to)
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"Got unknown event with type to {event.type} from {event.sender} in {room.room_id}."
|
|
||||||
)
|
|
|
@ -2,9 +2,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from my_project_name import main
|
from nyx_bot import main
|
||||||
|
|
||||||
# Run the main function of the bot
|
# Run the main function of the bot
|
||||||
asyncio.get_event_loop().run_until_complete(main.main())
|
asyncio.get_event_loop().run_until_complete(main.main())
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print("Unable to import my_project_name.main:", e)
|
print("Unable to import nyx_box.main:", e)
|
|
@ -2,7 +2,7 @@ import sys
|
||||||
|
|
||||||
# Check that we're not running on an unsupported Python version.
|
# Check that we're not running on an unsupported Python version.
|
||||||
if sys.version_info < (3, 5):
|
if sys.version_info < (3, 5):
|
||||||
print("my_project_name requires Python 3.5 or above.")
|
print("nyx_bot requires Python 3.5 or above.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
__version__ = "0.0.1"
|
__version__ = "0.0.1"
|
|
@ -1,8 +1,8 @@
|
||||||
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
||||||
|
|
||||||
from my_project_name.chat_functions import react_to_event, send_text_to_room
|
from nyx_bot.chat_functions import react_to_event, send_text_to_room
|
||||||
from my_project_name.config import Config
|
from nyx_bot.config import Config
|
||||||
from my_project_name.storage import Storage
|
from nyx_bot.storage import Storage
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
|
@ -72,7 +72,7 @@ class Command:
|
||||||
"""Show the help text"""
|
"""Show the help text"""
|
||||||
if not self.args:
|
if not self.args:
|
||||||
text = (
|
text = (
|
||||||
"Hello, I am a bot made with matrix-nio! Use `help commands` to view "
|
"matrix-nio\nUse `help commands` to view "
|
||||||
"available commands."
|
"available commands."
|
||||||
)
|
)
|
||||||
await send_text_to_room(self.client, self.room.room_id, text)
|
await send_text_to_room(self.client, self.room.room_id, text)
|
|
@ -0,0 +1,144 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from nio import (
|
||||||
|
AsyncClient,
|
||||||
|
InviteMemberEvent,
|
||||||
|
JoinError,
|
||||||
|
MatrixRoom,
|
||||||
|
RoomMessageText,
|
||||||
|
UnknownEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# from nyx_bot.bot_commands import Command
|
||||||
|
from nyx_bot.chat_functions import send_jerryxiao
|
||||||
|
from nyx_bot.config import Config
|
||||||
|
|
||||||
|
# from nyx_bot.message_responses import Message
|
||||||
|
from nyx_bot.storage import Storage
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Callbacks:
|
||||||
|
def __init__(self, client: AsyncClient, store: Storage, config: Config):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
client: nio client used to interact with matrix.
|
||||||
|
|
||||||
|
store: Bot storage.
|
||||||
|
|
||||||
|
config: Bot configuration parameters.
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
self.store = store
|
||||||
|
self.config = config
|
||||||
|
self.command_prefix = config.command_prefix
|
||||||
|
|
||||||
|
async def message(self, room: MatrixRoom, event: RoomMessageText) -> None:
|
||||||
|
"""Callback for when a message event is received
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room: The room the event came from.
|
||||||
|
|
||||||
|
event: The event defining the message.
|
||||||
|
"""
|
||||||
|
# Extract the message text
|
||||||
|
msg = event.body
|
||||||
|
|
||||||
|
# Ignore messages from ourselves
|
||||||
|
if event.sender == self.client.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Bot message received for room {room.display_name} | "
|
||||||
|
f"{room.user_name(event.sender)}: {msg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
content = event.source.get("content")
|
||||||
|
reply_to = ((content.get("m.relates_to") or {}).get("m.in_reply_to") or {}).get(
|
||||||
|
"event_id"
|
||||||
|
)
|
||||||
|
logger.debug(f"In-Reply-To: {reply_to}")
|
||||||
|
|
||||||
|
has_jerryxiao_prefix = False
|
||||||
|
for i in msg.splitlines():
|
||||||
|
if i.startswith("/") or i.startswith("!!"):
|
||||||
|
has_jerryxiao_prefix = True
|
||||||
|
msg = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_jerryxiao_prefix and reply_to:
|
||||||
|
if msg.startswith("/"):
|
||||||
|
await send_jerryxiao(self.client, room, event, "/", reply_to, msg)
|
||||||
|
elif msg.startswith("!!"):
|
||||||
|
await send_jerryxiao(self.client, room, event, "!!", reply_to, msg)
|
||||||
|
|
||||||
|
# # Treat it as a command only if it has a prefix
|
||||||
|
# if has_command_prefix:
|
||||||
|
# # Remove the command prefix
|
||||||
|
# msg = msg[len(self.command_prefix) :]
|
||||||
|
|
||||||
|
# command = Command(self.client, self.store, self.config, msg, room, event)
|
||||||
|
# await command.process()
|
||||||
|
|
||||||
|
async def unknown(self, room: MatrixRoom, event: UnknownEvent) -> None:
|
||||||
|
"""Callback for when an event with a type that is unknown to matrix-nio is received.
|
||||||
|
Currently this is used for reaction events, which are not yet part of a released
|
||||||
|
matrix spec (and are thus unknown to nio).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room: The room the reaction was sent in.
|
||||||
|
|
||||||
|
event: The event itself.
|
||||||
|
"""
|
||||||
|
if event.type == "m.reaction":
|
||||||
|
# Get the ID of the event this was a reaction to
|
||||||
|
relation_dict = event.source.get("content", {}).get("m.relates_to", {})
|
||||||
|
|
||||||
|
reacted_to = relation_dict.get("event_id")
|
||||||
|
if reacted_to and relation_dict.get("rel_type") == "m.annotation":
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Got unknown event with type to {event.type} from {event.sender} in {room.room_id}."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def invite(self, room: MatrixRoom, event: InviteMemberEvent) -> None:
|
||||||
|
"""Callback for when an invite is received. Join the room specified in the invite.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room: The room that we are invited to.
|
||||||
|
|
||||||
|
event: The invite event.
|
||||||
|
"""
|
||||||
|
logger.debug(f"Got invite to {room.room_id} from {event.sender}.")
|
||||||
|
|
||||||
|
# Attempt to join 3 times before giving up
|
||||||
|
for attempt in range(3):
|
||||||
|
result = await self.client.join(room.room_id)
|
||||||
|
if type(result) == JoinError:
|
||||||
|
logger.error(
|
||||||
|
f"Error joining room {room.room_id} (attempt %d): %s",
|
||||||
|
attempt,
|
||||||
|
result.message,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.error("Unable to join room: %s", room.room_id)
|
||||||
|
|
||||||
|
# Successfully joined room
|
||||||
|
logger.info(f"Joined {room.room_id}")
|
||||||
|
|
||||||
|
async def invite_event_filtered_callback(
|
||||||
|
self, room: MatrixRoom, event: InviteMemberEvent
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Since the InviteMemberEvent is fired for every m.room.member state received
|
||||||
|
in a sync response's `rooms.invite` section, we will receive some that are
|
||||||
|
not actually our own invite event (such as the inviter's membership).
|
||||||
|
This makes sure we only call `callbacks.invite` with our own invite events.
|
||||||
|
"""
|
||||||
|
if event.state_key == self.client.user_id:
|
||||||
|
# This is our own membership (invite) event
|
||||||
|
await self.invite(room, event)
|
|
@ -8,6 +8,7 @@ from nio import (
|
||||||
MatrixRoom,
|
MatrixRoom,
|
||||||
MegolmEvent,
|
MegolmEvent,
|
||||||
Response,
|
Response,
|
||||||
|
RoomMessageText,
|
||||||
RoomSendResponse,
|
RoomSendResponse,
|
||||||
SendRetryError,
|
SendRetryError,
|
||||||
)
|
)
|
||||||
|
@ -90,6 +91,61 @@ def make_pill(user_id: str, displayname: str = None) -> str:
|
||||||
return f'<a href="https://matrix.to/#/{user_id}">{displayname}</a>'
|
return f'<a href="https://matrix.to/#/{user_id}">{displayname}</a>'
|
||||||
|
|
||||||
|
|
||||||
|
def make_jerryxiao_reply(
|
||||||
|
from_sender: str, to_sender: str, action: str, room: MatrixRoom
|
||||||
|
):
|
||||||
|
from_pill = make_pill(from_sender, room.user_name(from_sender))
|
||||||
|
to_pill = make_pill(to_sender, room.user_name(to_sender))
|
||||||
|
return f"{from_pill} {action}了 {to_pill}"
|
||||||
|
|
||||||
|
|
||||||
|
async def send_in_reply_to(
|
||||||
|
client: AsyncClient,
|
||||||
|
room_id: str,
|
||||||
|
event: RoomMessageText,
|
||||||
|
body: str,
|
||||||
|
formatted_body: str,
|
||||||
|
) -> Union[RoomSendResponse, ErrorResponse]:
|
||||||
|
content = {
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"body": body,
|
||||||
|
"formatted_body": formatted_body,
|
||||||
|
}
|
||||||
|
content["m.relates_to"] = {"m.in_reply_to": {"event_id": event.event_id}}
|
||||||
|
try:
|
||||||
|
return await client.room_send(
|
||||||
|
room_id,
|
||||||
|
"m.room.message",
|
||||||
|
content,
|
||||||
|
ignore_unverified_devices=True,
|
||||||
|
)
|
||||||
|
except SendRetryError:
|
||||||
|
logger.exception(f"Unable to send message response to {room_id}")
|
||||||
|
|
||||||
|
|
||||||
|
async def send_jerryxiao(
|
||||||
|
client: AsyncClient,
|
||||||
|
room: MatrixRoom,
|
||||||
|
event: RoomMessageText,
|
||||||
|
prefix: str,
|
||||||
|
reply_to: str,
|
||||||
|
reference_text: str,
|
||||||
|
):
|
||||||
|
from_sender = event.sender
|
||||||
|
target_event = await client.room_get_event(room.room_id, reply_to)
|
||||||
|
to_sender = target_event.event.sender
|
||||||
|
action = reference_text[len(prefix) :]
|
||||||
|
if action != "":
|
||||||
|
send_text_formatted = make_jerryxiao_reply(from_sender, to_sender, action, room)
|
||||||
|
send_text = (
|
||||||
|
f"{room.user_name(from_sender)} {action}了 {room.user_name(to_sender)}"
|
||||||
|
)
|
||||||
|
await send_in_reply_to(
|
||||||
|
client, room.room_id, event, send_text, send_text_formatted
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def react_to_event(
|
async def react_to_event(
|
||||||
client: AsyncClient,
|
client: AsyncClient,
|
||||||
room_id: str,
|
room_id: str,
|
|
@ -6,7 +6,7 @@ from typing import Any, List, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from my_project_name.errors import ConfigError
|
from nyx_bot.errors import ConfigError
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logging.getLogger("peewee").setLevel(
|
logging.getLogger("peewee").setLevel(
|
|
@ -11,14 +11,13 @@ from nio import (
|
||||||
InviteMemberEvent,
|
InviteMemberEvent,
|
||||||
LocalProtocolError,
|
LocalProtocolError,
|
||||||
LoginError,
|
LoginError,
|
||||||
MegolmEvent,
|
|
||||||
RoomMessageText,
|
RoomMessageText,
|
||||||
UnknownEvent,
|
UnknownEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
from my_project_name.callbacks import Callbacks
|
from nyx_bot.callbacks import Callbacks
|
||||||
from my_project_name.config import Config
|
from nyx_bot.config import Config
|
||||||
from my_project_name.storage import Storage
|
from nyx_bot.storage import Storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ async def main():
|
||||||
max_limit_exceeded=0,
|
max_limit_exceeded=0,
|
||||||
max_timeouts=0,
|
max_timeouts=0,
|
||||||
store_sync_tokens=True,
|
store_sync_tokens=True,
|
||||||
encryption_enabled=True,
|
encryption_enabled=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the matrix client
|
# Initialize the matrix client
|
||||||
|
@ -63,11 +62,10 @@ async def main():
|
||||||
# Set up event callbacks
|
# Set up event callbacks
|
||||||
callbacks = Callbacks(client, store, config)
|
callbacks = Callbacks(client, store, config)
|
||||||
client.add_event_callback(callbacks.message, (RoomMessageText,))
|
client.add_event_callback(callbacks.message, (RoomMessageText,))
|
||||||
|
client.add_event_callback(callbacks.unknown, (UnknownEvent,))
|
||||||
client.add_event_callback(
|
client.add_event_callback(
|
||||||
callbacks.invite_event_filtered_callback, (InviteMemberEvent,)
|
callbacks.invite_event_filtered_callback, (InviteMemberEvent,)
|
||||||
)
|
)
|
||||||
client.add_event_callback(callbacks.decryption_failure, (MegolmEvent,))
|
|
||||||
client.add_event_callback(callbacks.unknown, (UnknownEvent,))
|
|
||||||
|
|
||||||
# Keep trying to reconnect on failure (with some time in-between)
|
# Keep trying to reconnect on failure (with some time in-between)
|
||||||
while True:
|
while True:
|
|
@ -2,9 +2,9 @@ import logging
|
||||||
|
|
||||||
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
||||||
|
|
||||||
from my_project_name.chat_functions import send_text_to_room
|
from nyx_bot.chat_functions import send_text_to_room
|
||||||
from my_project_name.config import Config
|
from nyx_bot.config import Config
|
||||||
from my_project_name.storage import Storage
|
from nyx_bot.storage import Storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -11,7 +11,7 @@ if [ $# -ge 1 ]
|
||||||
then
|
then
|
||||||
files=$*
|
files=$*
|
||||||
else
|
else
|
||||||
files="my_project_name my-project-name tests"
|
files="nyx_bot nyx-bot tests"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Linting these locations: $files"
|
echo "Linting these locations: $files"
|
||||||
|
|
|
@ -12,7 +12,7 @@ ignore=W503,W504,E203,E731,E501
|
||||||
line_length = 88
|
line_length = 88
|
||||||
sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,TESTS,LOCALFOLDER
|
sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,TESTS,LOCALFOLDER
|
||||||
default_section=THIRDPARTY
|
default_section=THIRDPARTY
|
||||||
known_first_party=my_project_name
|
known_first_party=nyx_bot
|
||||||
known_tests=tests
|
known_tests=tests
|
||||||
multi_line_output=3
|
multi_line_output=3
|
||||||
include_trailing_comma=true
|
include_trailing_comma=true
|
||||||
|
|
12
setup.py
12
setup.py
|
@ -20,18 +20,18 @@ def read_file(path_segments):
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
version = exec_file(("my_project_name", "__init__.py"))["__version__"]
|
version = exec_file(("nyx_bot", "__init__.py"))["__version__"]
|
||||||
long_description = read_file(("README.md",))
|
long_description = read_file(("README.md",))
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="my-project-name",
|
name="nyx-bot",
|
||||||
version=version,
|
version=version,
|
||||||
url="https://github.com/anoadragon453/nio-template",
|
url="https://github.com/ShadowRZ/nyx-bot",
|
||||||
description="A matrix bot to do amazing things!",
|
description="A matrix bot to do amazing things!",
|
||||||
packages=find_packages(exclude=["tests", "tests.*"]),
|
packages=find_packages(exclude=["tests", "tests.*"]),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"matrix-nio[e2e]>=0.10.0",
|
"matrix-nio>=0.10.0",
|
||||||
"Markdown>=3.1.1",
|
"Markdown>=3.1.1",
|
||||||
"PyYAML>=5.1.2",
|
"PyYAML>=5.1.2",
|
||||||
],
|
],
|
||||||
|
@ -54,6 +54,6 @@ setup(
|
||||||
],
|
],
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
# Allow the user to run the bot with `my-project-name ...`
|
# Allow the user to run the bot with `nyx-bot ...`
|
||||||
scripts=["my-project-name"],
|
scripts=["nyx-bot"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,8 +3,8 @@ from unittest.mock import Mock
|
||||||
|
|
||||||
import nio
|
import nio
|
||||||
|
|
||||||
from my_project_name.callbacks import Callbacks
|
from nyx_bot.callbacks import Callbacks
|
||||||
from my_project_name.storage import Storage
|
from nyx_bot.storage import Storage
|
||||||
|
|
||||||
from tests.utils import make_awaitable, run_coroutine
|
from tests.utils import make_awaitable, run_coroutine
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from my_project_name.config import Config
|
from nyx_bot.config import Config
|
||||||
from my_project_name.errors import ConfigError
|
from nyx_bot.errors import ConfigError
|
||||||
|
|
||||||
|
|
||||||
class ConfigTestCase(unittest.TestCase):
|
class ConfigTestCase(unittest.TestCase):
|
||||||
|
|
Loading…
Reference in New Issue