commands to initialize a community from scratch, with sane default base-config to allow this action
This commit is contained in:
@@ -33,16 +33,38 @@ with this plugin's capabilities:
|
|||||||
to the space
|
to the space
|
||||||
|
|
||||||
by following this structure, you reduce the amount of surface area you have to spend time defending against spam and
|
by following this structure, you reduce the amount of surface area you have to spend time defending against spam and
|
||||||
implementing censorship rules.
|
implementing censorship rules. the handy `!community initialize <some name for your community>` command will get you
|
||||||
|
from zero to an opinionated community structured this way quickly and easily.
|
||||||
|
|
||||||
if that doesn't sound like how you want to structure your online community, you might be better off using something like
|
if that doesn't sound like how you want to structure your online community, you might be better off using something like
|
||||||
Draupnir or Mjolnir.
|
Draupnir, Meowlnir, or Mjolnir.
|
||||||
|
|
||||||
# features
|
# features
|
||||||
|
|
||||||
please read through the comments in the `base-config.yaml` for more thorough explanations, but this covers the high
|
please read through the comments in the `base-config.yaml` for more thorough explanations, but this covers the high
|
||||||
points.
|
points.
|
||||||
|
|
||||||
|
## initialize a community from scratch
|
||||||
|
|
||||||
|
just installed the plugin for the first time, and want to get started on the right foot? start a DM with your bot and run:
|
||||||
|
|
||||||
|
`!community initialize <your community name>`
|
||||||
|
|
||||||
|
this will perform several actions on your behalf:
|
||||||
|
|
||||||
|
1. create a space named for your community, with an appropriate alias on the homeserver, and save the config with this parent room ID
|
||||||
|
2. add you to the "invitee" list in the config to be invited to all new rooms
|
||||||
|
3. set the bot's power level to 1000, and invite you as an administrator with power level 100
|
||||||
|
4. create a room within the space for admins/moderators to execute bot commands, this room is invite only
|
||||||
|
5. create a publicly facing room called the waiting room to allow newcomers to join and ask for invitation to your space
|
||||||
|
6. enable basic keyword and file upload censorship only on the waiting room
|
||||||
|
7. all rooms will require moderator permissions to invite additional users, to prevent rogue invitations or unexpected guests
|
||||||
|
|
||||||
|
once these actions have been taken, you can manage moderators, change room avatars, etc as you like, and add more rooms with
|
||||||
|
other commands. happy community-managing!
|
||||||
|
|
||||||
|
attempts to run this command once a parent room has been set will fail.
|
||||||
|
|
||||||
## greet new users on joining a room
|
## greet new users on joining a room
|
||||||
|
|
||||||
configure your bot to send a custom greeting to users whenever they join a room! configuration file provides a greeting
|
configure your bot to send a custom greeting to users whenever they join a room! configuration file provides a greeting
|
||||||
@@ -70,7 +92,9 @@ purging admin accounts, backup accounts, rarely used bots, etc.
|
|||||||
members to the space automatically trigger a sync, as do most other commands. this command is mostly deprecated but you
|
members to the space automatically trigger a sync, as do most other commands. this command is mostly deprecated but you
|
||||||
may want to run it just to see what it does.
|
may want to run it just to see what it does.
|
||||||
|
|
||||||
generate a report with the `report` subcommand (i.e. `!community report`) to see your inactive users.
|
generate a report with the `report` subcommand (i.e. `!community report`) to see your inactive users. you can also
|
||||||
|
generate more specific reports using the `inactive`, `purgable`, and `ignored` commands to see users in those specific
|
||||||
|
categories.
|
||||||
|
|
||||||
## user management
|
## user management
|
||||||
|
|
||||||
|
|||||||
+20
-9
@@ -1,7 +1,9 @@
|
|||||||
# the room-id of the matrix room or space to use as your "full user list"
|
# the room-id of the matrix room or space to use as your "full user list"
|
||||||
# changes to user power levelsin this room will affect all rooms in the space
|
# changes to user power levelsin this room will affect all rooms in the space
|
||||||
# some features may not work if this is a regular room. use a space.
|
# some features may not work if this is a regular room. use a space.
|
||||||
parent_room: "!somerandomcharacters:server.tld"
|
# leave this empty to use the initialize command to create a new community to manage,
|
||||||
|
# based on opinionated defaults.
|
||||||
|
parent_room: ''
|
||||||
|
|
||||||
# sleep time between actions. you can drop this to 0 if your bot has no
|
# sleep time between actions. you can drop this to 0 if your bot has no
|
||||||
# ratelimits imposed on its homeserver, otherwise you may want to increase this
|
# ratelimits imposed on its homeserver, otherwise you may want to increase this
|
||||||
@@ -13,10 +15,19 @@ sleep: 5
|
|||||||
# when creating new rooms
|
# when creating new rooms
|
||||||
encrypt: False
|
encrypt: False
|
||||||
|
|
||||||
|
# when creating a new room, what power-level should be required to invite users?
|
||||||
|
# this is helpful to prevent malicious accounts from inviting spam bots by restricting
|
||||||
|
# room defaults to moderators being the only people who can invite new users from outside
|
||||||
|
# of your managed community. otherwise, you must be a space member to join the rooms.
|
||||||
|
invite_power_level: 50
|
||||||
|
|
||||||
# number of days of inactivity to be considered in the "warning zone"
|
# number of days of inactivity to be considered in the "warning zone"
|
||||||
|
# users in this category will appear in the report as inactive
|
||||||
warn_threshold_days: 30
|
warn_threshold_days: 30
|
||||||
|
|
||||||
# number of days of inactivity to be considered in the "danger zone"
|
# number of days of inactivity to be considered in the "danger zone"
|
||||||
|
# users in this category will appear in the purgable report and are
|
||||||
|
# subject to removal by the purge command.
|
||||||
kick_threshold_days: 60
|
kick_threshold_days: 60
|
||||||
|
|
||||||
# track users? if false, will disable all tracking and avoid writing anything to the database.
|
# track users? if false, will disable all tracking and avoid writing anything to the database.
|
||||||
@@ -39,9 +50,8 @@ admins: []
|
|||||||
moderators: []
|
moderators: []
|
||||||
|
|
||||||
# list of users who should be invited to new rooms immediately (other bots, moderators, perhaps)
|
# list of users who should be invited to new rooms immediately (other bots, moderators, perhaps)
|
||||||
invitees:
|
# use full matrix IDs here
|
||||||
- "@mybot:server.tld"
|
invitees: []
|
||||||
- "@secondaryadmin:server.tld"
|
|
||||||
|
|
||||||
# auto-greet users in rooms with these messages
|
# auto-greet users in rooms with these messages
|
||||||
# map greeting messages to a room
|
# map greeting messages to a room
|
||||||
@@ -66,7 +76,7 @@ greeting_rooms:
|
|||||||
'!someotherroom:server.tld': generic
|
'!someotherroom:server.tld': generic
|
||||||
'!myencryptedroomid:server.tld': encrypted
|
'!myencryptedroomid:server.tld': encrypted
|
||||||
|
|
||||||
# how long to wait (in seconds) before sending a greeting to a new
|
# how long to wait (in seconds) before sending a greeting to a new joiner
|
||||||
welcome_sleep: 0
|
welcome_sleep: 0
|
||||||
|
|
||||||
# add a room ID here to send a message to when someone joins the above rooms
|
# add a room ID here to send a message to when someone joins the above rooms
|
||||||
@@ -109,13 +119,14 @@ censor_wordlist_instaban: []
|
|||||||
banlists:
|
banlists:
|
||||||
- '#community-moderation-effort-bl:neko.dev'
|
- '#community-moderation-effort-bl:neko.dev'
|
||||||
|
|
||||||
# should we ban proactively? this will generate ban events across all rooms every time
|
# should we ban proactively? this will ban users in your rooms if a new ban event is added to
|
||||||
# the ban lists have a new policy added, which may be noisy. however, without this enabled,
|
# the banlist policy room for their account. however, without this enabled,
|
||||||
# an account may join your rooms, THEN get added to the banlist, and you will have to manually
|
# an account may join your rooms, THEN get added to the banlist, and you will have to manually
|
||||||
# ban them from your rooms.
|
# ban them from your rooms.
|
||||||
proactive_banning: true
|
proactive_banning: true
|
||||||
|
|
||||||
# should we redact messages when a user is banned?
|
# should we redact messages when a user is banned? limited to their last 100 messages in each room.
|
||||||
|
# redactions are processed every minute, they are not immediate.
|
||||||
redact_on_ban: true
|
redact_on_ban: true
|
||||||
|
|
||||||
# should we verify that users are human before allowing them to send messages?
|
# should we verify that users are human before allowing them to send messages?
|
||||||
@@ -146,4 +157,4 @@ verification_attempts: 3
|
|||||||
verification_message: |
|
verification_message: |
|
||||||
Thank you for joining {room}. As an anti-spam measure, you must demonstrate that you are a real person before you can send messages in its rooms.
|
Thank you for joining {room}. As an anti-spam measure, you must demonstrate that you are a real person before you can send messages in its rooms.
|
||||||
|
|
||||||
Please send a message to this chat with the phrase: "{phrase}"
|
Please send a message to this chat with the content: "{phrase}"
|
||||||
|
|||||||
+229
-41
@@ -78,6 +78,7 @@ class Config(BaseProxyConfig):
|
|||||||
helper.copy("verification_phrases")
|
helper.copy("verification_phrases")
|
||||||
helper.copy("verification_attempts")
|
helper.copy("verification_attempts")
|
||||||
helper.copy("verification_message")
|
helper.copy("verification_message")
|
||||||
|
helper.copy("invite_power_level")
|
||||||
|
|
||||||
|
|
||||||
class CommunityBot(Plugin):
|
class CommunityBot(Plugin):
|
||||||
@@ -1109,11 +1110,22 @@ class CommunityBot(Plugin):
|
|||||||
async def community(self) -> None:
|
async def community(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def check_parent_room(self, evt: MessageEvent) -> bool:
|
||||||
|
"""Check if parent room is configured and handle the response if not."""
|
||||||
|
if not self.config["parent_room"]:
|
||||||
|
await evt.reply(
|
||||||
|
"No parent room configured. Please use the 'initialize' command to set up your community space first."
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@community.subcommand(
|
@community.subcommand(
|
||||||
"bancheck", help="check subscribed banlists for a user's mxid"
|
"bancheck", help="check subscribed banlists for a user's mxid"
|
||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def check_banlists(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def check_banlists(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
ban_status = await self.check_if_banned(mxid)
|
ban_status = await self.check_if_banned(mxid)
|
||||||
await evt.reply(f"user on banlist: {ban_status}")
|
await evt.reply(f"user on banlist: {ban_status}")
|
||||||
|
|
||||||
@@ -1123,6 +1135,8 @@ class CommunityBot(Plugin):
|
|||||||
in case they are missing",
|
in case they are missing",
|
||||||
)
|
)
|
||||||
async def sync_space_members(self, evt: MessageEvent) -> None:
|
async def sync_space_members(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1189,6 +1203,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def ignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def ignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1214,6 +1230,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def unignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def unignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1238,6 +1256,8 @@ class CommunityBot(Plugin):
|
|||||||
"report", help="generate a full list of activity tracking status"
|
"report", help="generate a full list of activity tracking status"
|
||||||
)
|
)
|
||||||
async def get_report(self, evt: MessageEvent) -> None:
|
async def get_report(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1263,6 +1283,8 @@ class CommunityBot(Plugin):
|
|||||||
"inactive", help="generate a list of mxids who have been inactive"
|
"inactive", help="generate a list of mxids who have been inactive"
|
||||||
)
|
)
|
||||||
async def get_inactive_report(self, evt: MessageEvent) -> None:
|
async def get_inactive_report(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1284,6 +1306,8 @@ class CommunityBot(Plugin):
|
|||||||
"purgable", help="generate a list of matrix IDs that have been inactive long enough to be purged"
|
"purgable", help="generate a list of matrix IDs that have been inactive long enough to be purged"
|
||||||
)
|
)
|
||||||
async def get_purgable_report(self, evt: MessageEvent) -> None:
|
async def get_purgable_report(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1304,6 +1328,8 @@ class CommunityBot(Plugin):
|
|||||||
"ignored", help="generate a list of matrix IDs that have activity tracking disabled"
|
"ignored", help="generate a list of matrix IDs that have activity tracking disabled"
|
||||||
)
|
)
|
||||||
async def get_ignored_report(self, evt: MessageEvent) -> None:
|
async def get_ignored_report(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
return
|
return
|
||||||
@@ -1323,6 +1349,8 @@ class CommunityBot(Plugin):
|
|||||||
|
|
||||||
@community.subcommand("purge", help="kick users for excessive inactivity")
|
@community.subcommand("purge", help="kick users for excessive inactivity")
|
||||||
async def kick_users(self, evt: MessageEvent) -> None:
|
async def kick_users(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -1377,6 +1405,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def kick_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def kick_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -1427,6 +1457,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def ban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def ban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -1450,6 +1482,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("mxid", "full matrix ID", required=True)
|
@command.argument("mxid", "full matrix ID", required=True)
|
||||||
async def unban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
async def unban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -1504,6 +1538,8 @@ class CommunityBot(Plugin):
|
|||||||
async def mark_for_redaction(
|
async def mark_for_redaction(
|
||||||
self, evt: MessageEvent, mxid: UserID, room: str
|
self, evt: MessageEvent, mxid: UserID, room: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -1532,12 +1568,14 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
await evt.respond(f"Queued {len(messages)} messages for redaction in {room_id}")
|
await evt.respond(f"Queued {len(messages)} messages for redaction in {room_id}")
|
||||||
|
|
||||||
async def create_room(self, roomname: str, evt: MessageEvent = None) -> None:
|
async def create_room(self, roomname: str, evt: MessageEvent = None, power_level_override: Optional[PowerLevelStateEventContent] = None, creation_content: Optional[dict] = None) -> None:
|
||||||
"""Create a new room and add it to the parent space.
|
"""Create a new room and add it to the parent space.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
roomname: The name for the new room
|
roomname: The name for the new room
|
||||||
evt: Optional MessageEvent for progress updates. If provided, will send status messages.
|
evt: Optional MessageEvent for progress updates. If provided, will send status messages.
|
||||||
|
power_level_override: Optional power levels to use. If not provided, will try to get from parent room.
|
||||||
|
creation_content: Optional creation content to use when creating the room.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (room_id, room_alias) if successful, None if failed
|
tuple: (room_id, room_alias) if successful, None if failed
|
||||||
@@ -1552,16 +1590,25 @@ class CommunityBot(Plugin):
|
|||||||
parent_room = self.config["parent_room"]
|
parent_room = self.config["parent_room"]
|
||||||
server = self.client.parse_user_id(self.client.mxid)[1]
|
server = self.client.parse_user_id(self.client.mxid)[1]
|
||||||
|
|
||||||
# Get parent room power levels to use as template
|
# Get power levels from parent room if not provided
|
||||||
power_levels = await self.client.get_state_event(
|
if not power_level_override and parent_room:
|
||||||
self.config["parent_room"], EventType.ROOM_POWER_LEVELS
|
power_levels = await self.client.get_state_event(
|
||||||
)
|
parent_room, EventType.ROOM_POWER_LEVELS
|
||||||
|
)
|
||||||
user_power_levels = power_levels.users
|
user_power_levels = power_levels.users
|
||||||
|
# ensure bot has highest power
|
||||||
# ensure bot has highest power
|
user_power_levels[self.client.mxid] = 1000
|
||||||
user_power_levels[self.client.mxid] = 1000
|
power_levels.users = user_power_levels
|
||||||
self.log.debug(f"DEBUG user power levels: {user_power_levels}")
|
power_level_override = power_levels
|
||||||
|
elif not power_level_override:
|
||||||
|
# If no parent room and no override provided, create default power levels
|
||||||
|
power_levels = PowerLevelStateEventContent()
|
||||||
|
power_levels.users = {
|
||||||
|
self.client.mxid: 1000, # Bot gets highest power
|
||||||
|
}
|
||||||
|
# Set invite power level from config
|
||||||
|
power_levels.invite = self.config["invite_power_level"]
|
||||||
|
power_level_override = power_levels
|
||||||
|
|
||||||
if evt:
|
if evt:
|
||||||
mymsg = await evt.respond(
|
mymsg = await evt.respond(
|
||||||
@@ -1569,26 +1616,30 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Prepare initial state events
|
# Prepare initial state events
|
||||||
initial_state = [
|
initial_state = []
|
||||||
{
|
|
||||||
"type": str(EventType.SPACE_PARENT),
|
# Only add space parent state if we have a parent room
|
||||||
"state_key": parent_room,
|
if parent_room:
|
||||||
"content": {
|
initial_state.extend([
|
||||||
"via": [server],
|
{
|
||||||
"canonical": True
|
"type": str(EventType.SPACE_PARENT),
|
||||||
|
"state_key": parent_room,
|
||||||
|
"content": {
|
||||||
|
"via": [server],
|
||||||
|
"canonical": True
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": str(EventType.ROOM_JOIN_RULES),
|
||||||
|
"content": {
|
||||||
|
"join_rule": "restricted",
|
||||||
|
"allow": [{
|
||||||
|
"type": "m.room_membership",
|
||||||
|
"room_id": parent_room
|
||||||
|
}]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
])
|
||||||
{
|
|
||||||
"type": str(EventType.ROOM_JOIN_RULES),
|
|
||||||
"content": {
|
|
||||||
"join_rule": "restricted",
|
|
||||||
"allow": [{
|
|
||||||
"type": "m.room_membership",
|
|
||||||
"room_id": parent_room
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add encryption if needed
|
# Add encryption if needed
|
||||||
if self.config["encrypt"] or force_encryption:
|
if self.config["encrypt"] or force_encryption:
|
||||||
@@ -1605,20 +1656,22 @@ class CommunityBot(Plugin):
|
|||||||
name=roomname,
|
name=roomname,
|
||||||
invitees=invitees,
|
invitees=invitees,
|
||||||
initial_state=initial_state,
|
initial_state=initial_state,
|
||||||
power_level_override={"users": user_power_levels}
|
power_level_override=power_level_override,
|
||||||
|
creation_content=creation_content
|
||||||
)
|
)
|
||||||
|
|
||||||
# The space child relationship needs to be set in the parent room separately
|
# The space child relationship needs to be set in the parent room separately
|
||||||
await self.client.send_state_event(
|
if parent_room:
|
||||||
parent_room,
|
await self.client.send_state_event(
|
||||||
EventType.SPACE_CHILD,
|
parent_room,
|
||||||
{
|
EventType.SPACE_CHILD,
|
||||||
"via": [server],
|
{
|
||||||
"suggested": False
|
"via": [server],
|
||||||
},
|
"suggested": False
|
||||||
state_key=room_id
|
},
|
||||||
)
|
state_key=room_id
|
||||||
await asyncio.sleep(self.config["sleep"])
|
)
|
||||||
|
await asyncio.sleep(self.config["sleep"])
|
||||||
|
|
||||||
if evt:
|
if evt:
|
||||||
await evt.respond(
|
await evt.respond(
|
||||||
@@ -1643,6 +1696,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("roomname", pass_raw=True, required=True)
|
@command.argument("roomname", pass_raw=True, required=True)
|
||||||
async def create_that_room(self, evt: MessageEvent, roomname: str) -> None:
|
async def create_that_room(self, evt: MessageEvent, roomname: str) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
if (roomname == "help") or len(roomname) == 0:
|
if (roomname == "help") or len(roomname) == 0:
|
||||||
await evt.reply(
|
await evt.reply(
|
||||||
'pass me a room name (like "cool topic") and i will create it and add it to the space. \
|
'pass me a room name (like "cool topic") and i will create it and add it to the space. \
|
||||||
@@ -1662,6 +1717,8 @@ class CommunityBot(Plugin):
|
|||||||
@community.subcommand("archive", help="archive a room")
|
@community.subcommand("archive", help="archive a room")
|
||||||
@command.argument("room", required=False)
|
@command.argument("room", required=False)
|
||||||
async def archive_room(self, evt: MessageEvent, room: str) -> None:
|
async def archive_room(self, evt: MessageEvent, room: str) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
|
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
@@ -1697,6 +1754,8 @@ class CommunityBot(Plugin):
|
|||||||
@community.subcommand("replaceroom", help="replace a room with a new one")
|
@community.subcommand("replaceroom", help="replace a room with a new one")
|
||||||
@command.argument("room", required=False)
|
@command.argument("room", required=False)
|
||||||
async def replace_room(self, evt: MessageEvent, room: str) -> None:
|
async def replace_room(self, evt: MessageEvent, room: str) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
|
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
@@ -1857,6 +1916,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("room", required=False)
|
@command.argument("room", required=False)
|
||||||
async def get_guestlist(self, evt: MessageEvent, room: str) -> None:
|
async def get_guestlist(self, evt: MessageEvent, room: str) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
space_members_obj = await self.client.get_joined_members(
|
space_members_obj = await self.client.get_joined_members(
|
||||||
self.config["parent_room"]
|
self.config["parent_room"]
|
||||||
)
|
)
|
||||||
@@ -1895,6 +1956,8 @@ class CommunityBot(Plugin):
|
|||||||
)
|
)
|
||||||
@command.argument("room", required=False)
|
@command.argument("room", required=False)
|
||||||
async def get_roomid(self, evt: MessageEvent, room: str) -> None:
|
async def get_roomid(self, evt: MessageEvent, room: str) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
room_id = None
|
room_id = None
|
||||||
if room:
|
if room:
|
||||||
if room.startswith("#"):
|
if room.startswith("#"):
|
||||||
@@ -1922,6 +1985,8 @@ class CommunityBot(Plugin):
|
|||||||
evt: MessageEvent,
|
evt: MessageEvent,
|
||||||
target_room: str = None
|
target_room: str = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender, min_level=100):
|
if not await self.user_permitted(evt.sender, min_level=100):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -2021,6 +2086,8 @@ class CommunityBot(Plugin):
|
|||||||
help="migrate a room to a verification-based permission model, ensuring current members can still send messages while new joiners require verification",
|
help="migrate a room to a verification-based permission model, ensuring current members can still send messages while new joiners require verification",
|
||||||
)
|
)
|
||||||
async def verify_migrate(self, evt: MessageEvent) -> None:
|
async def verify_migrate(self, evt: MessageEvent) -> None:
|
||||||
|
if not await self.check_parent_room(evt):
|
||||||
|
return
|
||||||
await evt.mark_read()
|
await evt.mark_read()
|
||||||
if not await self.user_permitted(evt.sender):
|
if not await self.user_permitted(evt.sender):
|
||||||
await evt.reply("You don't have permission to use this command")
|
await evt.reply("You don't have permission to use this command")
|
||||||
@@ -2137,6 +2204,8 @@ class CommunityBot(Plugin):
|
|||||||
# If we can't check the state, assume it's stale
|
# If we can't check the state, assume it's stale
|
||||||
await self.delete_verification_state(state["dm_room_id"])
|
await self.delete_verification_state(state["dm_room_id"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_db_upgrade_table(cls) -> None:
|
def get_db_upgrade_table(cls) -> None:
|
||||||
return upgrade_table
|
return upgrade_table
|
||||||
@@ -2144,3 +2213,122 @@ class CommunityBot(Plugin):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_config_class(cls) -> Type[BaseProxyConfig]:
|
def get_config_class(cls) -> Type[BaseProxyConfig]:
|
||||||
return Config
|
return Config
|
||||||
|
|
||||||
|
@community.subcommand(
|
||||||
|
"initialize",
|
||||||
|
help="initialize a new community space with the given name. this command can only be used if no parent room is configured."
|
||||||
|
)
|
||||||
|
@command.argument("community_name", pass_raw=True, required=True)
|
||||||
|
async def initialize_community(self, evt: MessageEvent, community_name: str) -> None:
|
||||||
|
await evt.mark_read()
|
||||||
|
|
||||||
|
# Check if parent room is already configured
|
||||||
|
if self.config["parent_room"]:
|
||||||
|
await evt.reply("Cannot initialize: a parent room is already configured. Please remove the parent_room configuration first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Validate community name
|
||||||
|
if not community_name or community_name.isspace():
|
||||||
|
await evt.reply("Please provide a community name. Usage: !community initialize <community_name>")
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = await evt.respond("Initializing new community space...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Add initiator to invitees list if not already there
|
||||||
|
if evt.sender not in self.config["invitees"]:
|
||||||
|
self.config["invitees"].append(evt.sender)
|
||||||
|
# Save the updated config
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
# Create the space
|
||||||
|
server = self.client.parse_user_id(self.client.mxid)[1]
|
||||||
|
sanitized_name = re.sub(r"[^a-zA-Z0-9]", "", community_name).lower()
|
||||||
|
|
||||||
|
# Set up power levels for the space
|
||||||
|
power_levels = PowerLevelStateEventContent()
|
||||||
|
power_levels.users = {
|
||||||
|
self.client.mxid: 1000, # Bot gets highest power
|
||||||
|
evt.sender: 100 # Initiator gets admin power
|
||||||
|
}
|
||||||
|
# Set invite power level from config
|
||||||
|
power_levels.invite = self.config["invite_power_level"]
|
||||||
|
|
||||||
|
# Create the space with appropriate metadata and power levels
|
||||||
|
space_id, space_alias = await self.create_room(
|
||||||
|
community_name,
|
||||||
|
evt,
|
||||||
|
power_level_override=power_levels,
|
||||||
|
creation_content={"type": "m.space"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set the space as the parent room in config
|
||||||
|
self.config["parent_room"] = space_id
|
||||||
|
|
||||||
|
# Save the updated config
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
# Verify the space exists and has correct power levels
|
||||||
|
try:
|
||||||
|
space_power_levels = await self.client.get_state_event(space_id, EventType.ROOM_POWER_LEVELS)
|
||||||
|
if space_power_levels.users.get(self.client.mxid) != 1000:
|
||||||
|
raise Exception("Space power levels not set correctly")
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Failed to verify space setup: {e}"
|
||||||
|
self.log.error(error_msg)
|
||||||
|
await evt.respond(error_msg, edits=msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create moderators room
|
||||||
|
mod_room_id, mod_room_alias = await self.create_room(
|
||||||
|
f"{community_name} Moderators",
|
||||||
|
evt
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set moderators room to invite-only
|
||||||
|
await self.client.send_state_event(
|
||||||
|
mod_room_id,
|
||||||
|
EventType.ROOM_JOIN_RULES,
|
||||||
|
JoinRulesStateEventContent(join_rule=JoinRule.INVITE)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create waiting room
|
||||||
|
waiting_room_id, waiting_room_alias = await self.create_room(
|
||||||
|
f"{community_name} Waiting Room",
|
||||||
|
evt
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set waiting room to be joinable by anyone
|
||||||
|
await self.client.send_state_event(
|
||||||
|
waiting_room_id,
|
||||||
|
EventType.ROOM_JOIN_RULES,
|
||||||
|
JoinRulesStateEventContent(join_rule=JoinRule.PUBLIC)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update censor configuration based on current value
|
||||||
|
current_censor = self.config["censor"]
|
||||||
|
if current_censor is False:
|
||||||
|
# If censor is false, set it to a list with just the waiting room
|
||||||
|
self.config["censor"] = [waiting_room_id]
|
||||||
|
elif isinstance(current_censor, list) and waiting_room_id not in current_censor:
|
||||||
|
# If censor is already a list and waiting room isn't in it, append it
|
||||||
|
current_censor.append(waiting_room_id)
|
||||||
|
self.config["censor"] = current_censor
|
||||||
|
# If censor is True or waiting room is already in the list, leave it as is
|
||||||
|
|
||||||
|
# Save the updated config
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
await evt.respond(
|
||||||
|
f"Community space initialized successfully!\n\n"
|
||||||
|
f"Space: <a href='https://matrix.to/#/{space_alias}'>{space_alias}</a>\n"
|
||||||
|
f"Moderators Room: <a href='https://matrix.to/#/{mod_room_alias}'>{mod_room_alias}</a>\n"
|
||||||
|
f"Waiting Room: <a href='https://matrix.to/#/{waiting_room_alias}'>{waiting_room_alias}</a>",
|
||||||
|
edits=msg,
|
||||||
|
allow_html=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Failed to initialize community: {e}"
|
||||||
|
self.log.error(error_msg)
|
||||||
|
await evt.respond(error_msg, edits=msg)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
maubot: 0.1.0
|
maubot: 0.1.0
|
||||||
id: org.jobmachine.communitybot
|
id: org.jobmachine.communitybot
|
||||||
version: 0.2.7
|
version: 0.2.8
|
||||||
license: MIT
|
license: MIT
|
||||||
modules:
|
modules:
|
||||||
- community
|
- community
|
||||||
|
|||||||
Reference in New Issue
Block a user