mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 21:06:31 +03:00
jsonrpc: add API to check if the message is sent by a bot
Co-Authored-By: Asiel Díaz Benítez <asieldbenitez@gmail.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
C interface is not changed.
|
C interface is not changed.
|
||||||
Rust and JSON-RPC API have `flags` integer argument
|
Rust and JSON-RPC API have `flags` integer argument
|
||||||
replaced with two boolean flags `info_only` and `add_daymarker`.
|
replaced with two boolean flags `info_only` and `add_daymarker`.
|
||||||
|
- jsonrpc: add API to check if the message is sent by a bot #3877
|
||||||
|
|
||||||
|
|
||||||
## 1.107.1
|
## 1.107.1
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ pub struct MessageObject {
|
|||||||
is_setupmessage: bool,
|
is_setupmessage: bool,
|
||||||
is_info: bool,
|
is_info: bool,
|
||||||
is_forwarded: bool,
|
is_forwarded: bool,
|
||||||
|
|
||||||
|
/// True if the message was sent by a bot.
|
||||||
|
is_bot: bool,
|
||||||
|
|
||||||
/// when is_info is true this describes what type of system message it is
|
/// when is_info is true this describes what type of system message it is
|
||||||
system_message_type: SystemMessageType,
|
system_message_type: SystemMessageType,
|
||||||
|
|
||||||
@@ -182,6 +186,7 @@ impl MessageObject {
|
|||||||
is_setupmessage: message.is_setupmessage(),
|
is_setupmessage: message.is_setupmessage(),
|
||||||
is_info: message.is_info(),
|
is_info: message.is_info(),
|
||||||
is_forwarded: message.is_forwarded(),
|
is_forwarded: message.is_forwarded(),
|
||||||
|
is_bot: message.is_bot(),
|
||||||
system_message_type: message.get_info_type().into(),
|
system_message_type: message.get_info_type().into(),
|
||||||
|
|
||||||
duration: message.get_duration(),
|
duration: message.get_duration(),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ async def main():
|
|||||||
async def process_messages():
|
async def process_messages():
|
||||||
for message in await account.get_fresh_messages_in_arrival_order():
|
for message in await account.get_fresh_messages_in_arrival_order():
|
||||||
snapshot = await message.get_snapshot()
|
snapshot = await message.get_snapshot()
|
||||||
if not snapshot.is_info:
|
if not snapshot.is_bot and not snapshot.is_info:
|
||||||
await snapshot.chat.send_text(snapshot.text)
|
await snapshot.chat.send_text(snapshot.text)
|
||||||
await snapshot.message.mark_seen()
|
await snapshot.message.mark_seen()
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ class NewMessage(EventFilter):
|
|||||||
content.
|
content.
|
||||||
:param command: If set, only match messages with the given command (ex. /help).
|
:param command: If set, only match messages with the given command (ex. /help).
|
||||||
Setting this property implies `is_info==False`.
|
Setting this property implies `is_info==False`.
|
||||||
|
:param is_bot: If set to True only match messages sent by bots, if set to None
|
||||||
|
match messages from bots and users. If omitted or set to False
|
||||||
|
only messages from users will be matched.
|
||||||
:param is_info: If set to True only match info/system messages, if set to False
|
:param is_info: If set to True only match info/system messages, if set to False
|
||||||
only match messages that are not info/system messages. If omitted
|
only match messages that are not info/system messages. If omitted
|
||||||
info/system messages as well as normal messages will be matched.
|
info/system messages as well as normal messages will be matched.
|
||||||
@@ -113,10 +116,12 @@ class NewMessage(EventFilter):
|
|||||||
re.Pattern,
|
re.Pattern,
|
||||||
] = None,
|
] = None,
|
||||||
command: Optional[str] = None,
|
command: Optional[str] = None,
|
||||||
|
is_bot: Optional[bool] = False,
|
||||||
is_info: Optional[bool] = None,
|
is_info: Optional[bool] = None,
|
||||||
func: Optional[Callable[[AttrDict], bool]] = None,
|
func: Optional[Callable[[AttrDict], bool]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(func=func)
|
super().__init__(func=func)
|
||||||
|
self.is_bot = is_bot
|
||||||
self.is_info = is_info
|
self.is_info = is_info
|
||||||
if command is not None and not isinstance(command, str):
|
if command is not None and not isinstance(command, str):
|
||||||
raise TypeError("Invalid command")
|
raise TypeError("Invalid command")
|
||||||
@@ -133,19 +138,28 @@ class NewMessage(EventFilter):
|
|||||||
raise TypeError("Invalid pattern type")
|
raise TypeError("Invalid pattern type")
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash((self.pattern, self.func))
|
return hash((self.pattern, self.command, self.is_bot, self.is_info, self.func))
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
if isinstance(other, NewMessage):
|
if isinstance(other, NewMessage):
|
||||||
return (self.pattern, self.command, self.is_info, self.func) == (
|
return (
|
||||||
|
self.pattern,
|
||||||
|
self.command,
|
||||||
|
self.is_bot,
|
||||||
|
self.is_info,
|
||||||
|
self.func,
|
||||||
|
) == (
|
||||||
other.pattern,
|
other.pattern,
|
||||||
other.command,
|
other.command,
|
||||||
|
other.is_bot,
|
||||||
other.is_info,
|
other.is_info,
|
||||||
other.func,
|
other.func,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def filter(self, event: AttrDict) -> bool:
|
async def filter(self, event: AttrDict) -> bool:
|
||||||
|
if self.is_bot is not None and self.is_bot != event.message_snapshot.is_bot:
|
||||||
|
return False
|
||||||
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
|
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
|
||||||
return False
|
return False
|
||||||
if self.command and self.command != event.command:
|
if self.command and self.command != event.command:
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ async def test_message(acfactory) -> None:
|
|||||||
snapshot = await message.get_snapshot()
|
snapshot = await message.get_snapshot()
|
||||||
assert snapshot.chat_id == chat_id
|
assert snapshot.chat_id == chat_id
|
||||||
assert snapshot.text == "Hello!"
|
assert snapshot.text == "Hello!"
|
||||||
|
assert not snapshot.is_bot
|
||||||
assert repr(message)
|
assert repr(message)
|
||||||
|
|
||||||
with pytest.raises(JsonRpcError): # chat is not accepted
|
with pytest.raises(JsonRpcError): # chat is not accepted
|
||||||
@@ -227,18 +228,46 @@ async def test_message(acfactory) -> None:
|
|||||||
await message.send_reaction("😎")
|
await message.send_reaction("😎")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_is_bot(acfactory) -> None:
|
||||||
|
"""Test that we can recognize messages submitted by bots."""
|
||||||
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
bob_addr = await bob.get_config("addr")
|
||||||
|
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||||
|
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||||
|
|
||||||
|
# Alice becomes a bot.
|
||||||
|
await alice.set_config("bot", "1")
|
||||||
|
await alice_chat_bob.send_text("Hello!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event = await bob.wait_for_event()
|
||||||
|
if event.type == EventType.INCOMING_MSG:
|
||||||
|
msg_id = event.msg_id
|
||||||
|
message = bob.get_message_by_id(msg_id)
|
||||||
|
snapshot = await message.get_snapshot()
|
||||||
|
assert snapshot.chat_id == event.chat_id
|
||||||
|
assert snapshot.text == "Hello!"
|
||||||
|
assert snapshot.is_bot
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio()
|
@pytest.mark.asyncio()
|
||||||
async def test_bot(acfactory) -> None:
|
async def test_bot(acfactory) -> None:
|
||||||
mock = MagicMock()
|
mock = MagicMock()
|
||||||
user = (await acfactory.get_online_accounts(1))[0]
|
user = (await acfactory.get_online_accounts(1))[0]
|
||||||
bot = await acfactory.new_configured_bot()
|
bot = await acfactory.new_configured_bot()
|
||||||
|
bot2 = await acfactory.new_configured_bot()
|
||||||
|
|
||||||
assert await bot.is_configured()
|
assert await bot.is_configured()
|
||||||
assert await bot.account.get_config("bot") == "1"
|
assert await bot.account.get_config("bot") == "1"
|
||||||
|
|
||||||
hook = lambda e: mock.hook(e.msg_id), events.RawEvent(EventType.INCOMING_MSG)
|
hook = lambda e: mock.hook(e.msg_id) and None, events.RawEvent(EventType.INCOMING_MSG)
|
||||||
bot.add_hook(*hook)
|
bot.add_hook(*hook)
|
||||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
|
||||||
|
snapshot = await bot.account.get_message_by_id(event.msg_id).get_snapshot()
|
||||||
|
assert not snapshot.is_bot
|
||||||
mock.hook.assert_called_once_with(event.msg_id)
|
mock.hook.assert_called_once_with(event.msg_id)
|
||||||
bot.remove_hook(*hook)
|
bot.remove_hook(*hook)
|
||||||
|
|
||||||
@@ -253,6 +282,8 @@ async def test_bot(acfactory) -> None:
|
|||||||
mock.hook.assert_called_with(event.msg_id)
|
mock.hook.assert_called_with(event.msg_id)
|
||||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!")
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!")
|
||||||
mock.hook.assert_called_with(event.msg_id)
|
mock.hook.assert_called_with(event.msg_id)
|
||||||
|
await acfactory.process_message(from_account=bot2.account, to_client=bot, text="hello")
|
||||||
|
assert len(mock.hook.mock_calls) == 2 # bot messages are ignored between bots
|
||||||
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
||||||
assert len(mock.hook.mock_calls) == 2
|
assert len(mock.hook.mock_calls) == 2
|
||||||
bot.remove_hook(*hook)
|
bot.remove_hook(*hook)
|
||||||
|
|||||||
Reference in New Issue
Block a user