Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The following extensions were made #20

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 39 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,54 @@
English | [中文](README_ZH.md)

# reactbot
A [maubot](https://github.com/maubot/maubot) that responds to messages that match predefined rules.
A [maubot](https://github.com/maubot/maubot) that responds to messages based on predefined rules.

## Samples
* The [base config](base-config.yaml) contains a cookie reaction for TWIM submissions
in [#thisweekinmatrix:matrix.org](https://matrix.to/#/#thisweekinmatrix:matrix.org)
and an image response for "alot".
## Examples
* [base config](base-config.yaml) contains a cookie reaction for TWIM submissions in [#thisweekinmatrix:matrix.org](https://matrix.to/#/#thisweekinmatrix:matrix.org) and an image response for "alot".
* [samples/jesari.yaml](samples/jesari.yaml) contains a replacement for [jesaribot](https://github.com/maubot/jesaribot).
* [samples/stallman.yaml](samples/stallman.yaml) contains a Stallman interject bot.
* [samples/random-reaction.yaml](samples/random-reaction.yaml) has an example of
a randomized reaction to matching messages.
* [samples/nitter.yaml](samples/nitter.yaml) has an example of matching tweet links
and responding with a corresponding nitter.net link.
* [samples/thread.yaml](samples/thread.yaml) has an example of replying in a thread.
* [samples/stallman.yaml](samples/stallman.yaml) contains a Stallman interjection bot.
* [samples/random-reaction.yaml](samples/random-reaction.yaml) has an example of a random reaction to matching messages.
* [samples/nitter.yaml](samples/nitter.yaml) has an example of matching tweet links and responding with corresponding nitter.net links.
* [samples/thread.yaml](samples/thread.yaml) has an example of replying in threads.

## Config format
## Configuration Format
### Templates
Templates contain the actual event type and content to be sent.
* `type` - The Matrix event type to send
* `content` - The event content. Either an object or jinja2 template that produces JSON.
* `variables` - A key-value map of variables.
* `type` - The Matrix event type to send.
* `content` - The event content. Can be an object or a jinja2 template that generates JSON.
* `variables` - Key-value mapping of variables.

Variables that start with `{{` are parsed as jinja2 templates and get the
maubot event object in `event`. As of v3, variables are parsed using jinja2's
[native types mode](https://jinja.palletsprojects.com/en/3.1.x/nativetypes/),
which means the output can be a non-string type.
Variables starting with `{{` will be parsed as jinja2 templates and will get the maubot event object in `event`. From v3 onwards, variables are parsed using jinja2's [native types mode](https://jinja.palletsprojects.com/en/3.1.x/nativetypes/), meaning the output can be non-string types.

If the content is a string, it'll be parsed as a jinja2 template and the output
will be parsed as JSON. The content jinja2 template will get `event` just like
variable templates, but it will also get all of the variables.
If the content is a string, it will be parsed as a jinja2 template, and the output will be parsed as JSON. Content jinja2 templates will get `event` like variable templates but will also get all variables.

If the content is an object, that object is what will be sent as the content.
The object can contain variables using a custom syntax: All instances of
`$${variablename}` will be replaced with the value matching `variablename`.
This works in object keys and values and list items. If a key/value/item only
consists of a variable insertion, the variable may be of any type. If there's
something else than the variable, the variable will be concatenated using `+`,
which means it should be a string.
If the content is an object, the object will be sent as the content. Objects can include variables using custom syntax: all instances of `$${variablename}` will be replaced with the value matching `variablename`. This applies to object keys and values as well as list items. If a key/value/item only contains a variable insertion, the variable can be of any type. If there is other content besides the variable, the variable will be concatenated using `+`, meaning it should be a string.

### Default flags
Default regex flags. Most Python regex flags are available.
See [docs](https://docs.python.org/3/library/re.html#re.A).
### Default Flags
Default regular expression flags. Most Python regular expression flags are available. See [documentation](https://docs.python.org/3/library/re.html#re.A).

Most relevant flags:
* `i` / `ignorecase` - Case-insensitive matching.
* `s` / `dotall` - Make `.` match any character at all, including newline.
* `x` / `verbose` - Ignore comments and whitespace in regex.
* `m` / `multiline` - When specified, `^` and `$` match the start and end of
line respectively instead of start and end of whole string.
* `s` / `dotall` - Makes `.` match any character, including newlines.
* `x` / `verbose` - Ignores whitespace and comments in the regex.
* `m` / `multiline` - When specified, `^` and `$` match the start and end of each line, not just the start and end of the whole string.

### Rules
Rules have five fields. Only `matches` and `template` are required.
* `rooms` - The list of rooms where the rule should apply.
If empty, the rule will apply to all rooms the bot is in.
* `matches` - The regex or list of regexes to match.
* `template` - The name of the template to use.
* `variables` - A key-value map of variables to extend or override template variables.
Like with template variables, the values are parsed as Jinja2 templates.

The regex(es) in `matches` can either be simple strings containing the pattern,
or objects containing additional info:
* `pattern` - The regex to match.
* `flags` - Regex flags (replaces default flags).
* `raw` - Whether or not the regex should be forced to be raw.

If `raw` is `true` OR the pattern contains no special regex characters other
than `^` at the start and/or `$` at the end, the pattern will be considered
"raw". Raw patterns don't use regex, but instead use faster string operators
(equality, starts/endwith, contains). Patterns with the `multiline` flag will
never be converted into raw patterns implicitly.
Rules only require `matches` and `template`.
* `rooms` - List of rooms (internal room IDs) the rule applies to. If empty, the rule applies to all rooms the bot is in.
* `not_rooms` - Exclude certain rooms (internal room IDs).
* `users` - List of users the rule applies to. If empty, the rule applies to all users.
* `not_users` - Exclude certain users.
* `matches` - Regular expression or list of regular expressions to match.
* `template` - Name of the template to use.
* `variables` - Key-value mapping to extend or override template variables. Like template variables, values will be parsed as Jinja2 templates.
* `only_text` - Should only respond to text-type messages (including notice-type events)? Defaults to false.
* `not_thread` - Should not respond to messages in threads? Defaults to false.
* `is_reedit` - Should not respond to edits? Defaults to false.

Regular expressions in `matches` can be simple strings containing the pattern or objects containing additional information:
* `pattern` - The regular expression to match.
* `flags` - Regular expression flags (override default flags).
* `raw` - Whether the regular expression should be forced to raw.

If `raw` is `true` or the pattern does not contain special regex characters other than leading `^` and/or trailing `$`, the pattern will be treated as "raw". Raw patterns do not use regular expressions but faster string operators (equals, starts/endswith, contains). Patterns with the `multiline` flag will never be implicitly converted to raw patterns.
54 changes: 54 additions & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[English](README.md) | 中文

# reactbot
一个[maubot](https://github.com/maubot/maubot) ,根据预定义规则响应消息。

## 示例
* [base config](base-config.yaml) 包含一个针对 [#thisweekinmatrix:matrix.org](https://matrix.to/#/#thisweekinmatrix:matrix.org) 中 TWIM 提交的 cookie 反应和一个针对 "alot" 的图片响应。
* [samples/jesari.yaml](samples/jesari.yaml) 包含一个 [jesaribot](https://github.com/maubot/jesaribot) 的替代品。
* [samples/stallman.yaml](samples/stallman.yaml) 包含一个 Stallman 插话机器人。
* [samples/random-reaction.yaml](samples/random-reaction.yaml) 有一个对匹配消息进行随机反应的示例。
* [samples/nitter.yaml](samples/nitter.yaml) 有一个匹配推文链接并回应相应 nitter.net 链接的示例。
* [samples/thread.yaml](samples/thread.yaml) 有一个在线程中回复的示例。

## 配置格式
### Templates
模板包含要发送的实际事件类型和内容。
* `type` - 要发送的 Matrix 事件类型
* `content` - 事件内容。可以是对象或生成 JSON 的 jinja2 模板。
* `variables` - 变量的键值对映射。

以 `{{` 开头的变量将被解析为 jinja2 模板,并在 `event` 中获取 maubot 事件对象。从 v3 开始,变量使用 jinja2 的[原生类型模式](https://jinja.palletsprojects.com/en/3.1.x/nativetypes/)进行解析 ,这意味着输出可以是非字符串类型。

如果内容是字符串,它将被解析为 jinja2 模板,输出将被解析为 JSON。内容 jinja2 模板将像变量模板一样获取 `event`,但它也将获取所有变量。

如果内容是对象,该对象将作为内容发送。对象可以使用自定义语法包含变量:所有 `$${variablename}` 实例将被替换为与 `variablename` 匹配的值。这适用于对象键和值以及列表项。如果键/值/项仅包含变量插入,则变量可以是任何类型。如果除了变量还有其他内容,变量将使用 `+` 连接,这意味着它应该是字符串。

### Default flags
默认正则表达式标志。大多数 Python 正则表达式标志可用。参见[文档](https://docs.python.org/3/library/re.html#re.A) 。

最相关的标志:
* `i` / `ignorecase` - 不区分大小写匹配。
* `s` / `dotall` - 使 `.` 匹配任何字符,包括换行符。
* `x` / `verbose` - 忽略正则表达式中的注释和空白。
* `m` / `multiline` - 指定时,`^` 和 `$` 分别匹配行的开始和结束,而不是整个字符串的开始和结束。

### Rules
规则只有 `matches` 和 `template` 是必需的。
* `rooms` - 规则适用的房间列表(内部房间ID)。如果为空,规则将适用于机器人所在的所有房间。
* `not_rooms` - 排除某些房间(内部房间ID)。
* `users` - 规则适用的用户列表。如果为空,规则将适用于所有用户。
* `not_users` - 排除某些用户。
* `matches` - 要匹配的正则表达式或正则表达式列表。
* `template` - 要使用的模板名称。
* `variables` - 扩展或覆盖模板变量的键值对映射。与模板变量一样,值将被解析为 Jinja2 模板。
* `only_text` - 是否只回应文本类消息(包括通知类型的事件)?默认为false
* `not_thread` - 是否不回应线程内的消息?默认为false
* `is_reedit` - 否不回应编辑?默认为false

`matches` 中的正则表达式可以是包含模式的简单字符串,也可以是包含附加信息的对象:
* `pattern` - 要匹配的正则表达式。
* `flags` - 正则表达式标志(替换默认标志)。
* `raw` - 正则表达式是否应被强制为原始。

如果 `raw` 为 `true` 或模式不包含除开头的 `^` 和/或结尾的 `$` 之外的特殊正则表达式字符,则模式将被视为“原始”。原始模式不使用正则表达式,而是使用更快的字符串操作符(相等、starts/endwith、contains)。带有 `multiline` 标志的模式将永远不会被隐式转换为原始模式。
149 changes: 107 additions & 42 deletions base-config.yaml
Original file line number Diff line number Diff line change
@@ -1,49 +1,114 @@
templates:
reaction:
type: m.reaction
variables:
react_to_event: "{{event.content.get_reply_to() or event.event_id}}"
content:
m.relates_to:
rel_type: m.annotation
event_id: $${react_to_event}
key: $${reaction}
alot:
type: m.room.message
content:
msgtype: m.image
body: image.png
url: "mxc://maunium.net/eFnyRdgJOHlKXCxzoKPQbwLV"
info:
mimetype: image/png
w: 680
h: 510
size: 247492
thumbnail_url: "mxc://maunium.net/PMxffxMfcUZeWeeYMDCdghBG"
thumbnail_info:
w: 680
h: 510
mimetype: image/png
size: 233763
text_reaction:
type: m.reaction
variables:
react_to_event: '{{event.event_id}}'
content:
m.relates_to:
rel_type: m.annotation
event_id: $${react_to_event}
key: $${reaction}

default_flags:
- ignorecase

antispam:
room:
max: 1
delay: 60
user:
max: 2
delay: 60
room:
max: 60
delay: 60
# max: 在指定的时间间隔(由 delay 定义)内,允许每个用户发送的最大消息数量。
# delay: 时间间隔,单位为秒。在这个时间间隔内,用户发送的消息数量不能超过 max 值。
user:
max: 30
delay: 60

rules:
twim_cookies:
rooms: ["!FPUfgzXYWTKgIrwKxW:matrix.org"]
matches: [^TWIM]
template: reaction
variables:
reaction: 🍪
alot:
matches: [alot]
template: alot
like_button:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: 👍点赞
to_aishow:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: 🤖转发
collect_button:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: ❤️收藏
voice_button:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: 🎤语音
rewrite_button:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: ♻️重制
rag_button:
users: ['@know:meiu.xyz', '@aibot1:meiu.xyz', '@aibot2:meiu.xyz', '@aibot3:meiu.xyz']
not_rooms: ["!erVdKitClvcjVzMjUw:meiu.xyz"]
only_text: true
matches: [.*]
template: text_reaction
variables:
reaction: 🌾采集


todo_idea:
users: ['@ming:meiu.xyz']
rooms: ['!WtMywSEpJiIxLlYxzZ:meiu.xyz']
not_thread: true
matches: [.*]
template: text_reaction
variables:
reaction: 💡想法
todo_plan:
users: ['@ming:meiu.xyz']
rooms: ['!WtMywSEpJiIxLlYxzZ:meiu.xyz']
not_thread: true
matches: [.*]
template: text_reaction
variables:
reaction: 📝计划
todo_doing:
users: ['@ming:meiu.xyz']
rooms: ['!WtMywSEpJiIxLlYxzZ:meiu.xyz']
not_thread: true
matches: [.*]
template: text_reaction
variables:
reaction: ⏳进行
todo_achieve:
users: ['@ming:meiu.xyz']
rooms: ['!WtMywSEpJiIxLlYxzZ:meiu.xyz']
not_thread: true
matches: [.*]
template: text_reaction
variables:
reaction: ✅完成
todo_pause:
users: ['@ming:meiu.xyz']
rooms: ['!WtMywSEpJiIxLlYxzZ:meiu.xyz']
not_thread: true
matches: [.*]
template: text_reaction
variables:
reaction: 💤搁置
4 changes: 2 additions & 2 deletions maubot.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
maubot: 0.1.0
id: xyz.maubot.reactbot
version: 2.2.0
version: 2.2.10
license: AGPL-3.0-or-later
modules:
- reactbot
main_class: ReactBot
extra_files:
- base-config.yaml
- base-config.yaml
8 changes: 5 additions & 3 deletions reactbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def bump(self) -> bool:


class ReactBot(Plugin):
allowed_msgtypes: Tuple[MessageType, ...] = (MessageType.TEXT, MessageType.EMOTE)
allowed_msgtypes: Tuple[MessageType, ...] = (MessageType.TEXT, MessageType.EMOTE, MessageType.NOTICE)
user_flood: Dict[UserID, FloodInfo]
room_flood: Dict[RoomID, FloodInfo]

Expand Down Expand Up @@ -95,15 +95,17 @@ def is_flood(self, evt: MessageEvent) -> bool:

@event.on(EventType.ROOM_MESSAGE)
async def event_handler(self, evt: MessageEvent) -> None:
if evt.sender == self.client.mxid or evt.content.msgtype not in self.allowed_msgtypes:
if evt.sender == evt.content.msgtype not in self.allowed_msgtypes:
return
for name, rule in self.config.rules.items():
match = rule.match(evt)
if match is not None:
if self.is_flood(evt):
# 去掉中间的 return 并使用 continue 可以确保即使某个规则被认为是“洪水攻击”,其他规则依然有机会被执行。
return
try:
await rule.execute(evt, match)
except Exception:
self.log.exception(f"Failed to execute {name} in {evt.room_id}")
return
# 去掉最后的 return 会导致所有匹配的规则都被执行。这种设计适用于需要同时处理多个规则的情况。
# return
5 changes: 5 additions & 0 deletions reactbot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ def _make_rule(self, name: str, rule: Dict[str, Any]) -> Rule:
return Rule(
rooms=set(rule.get("rooms", [])),
not_rooms=set(rule.get("not_rooms", [])),
users=set(rule.get("users", [])),
not_users=set(rule.get("not_users", [])),
only_text=rule.get("only_text", False),
not_thread=rule.get("not_thread", False),
is_reedit=rule.get("is_reedit", False),
matches=self._compile_all(rule["matches"]),
not_matches=self._compile_all(rule.get("not_matches", [])),
type=EventType.find(rule["type"]) if "type" in rule else None,
Expand Down
Loading