@@ -11,7 +11,9 @@ import (
1111 "strings"
1212 "time"
1313
14+ "maunium.net/go/mautrix"
1415 "maunium.net/go/mautrix/bridgev2/commands"
16+ bridgeMatrix "maunium.net/go/mautrix/bridgev2/matrix"
1517 "maunium.net/go/mautrix/bridgev2/status"
1618 "maunium.net/go/mautrix/event"
1719 "maunium.net/go/mautrix/id"
@@ -25,6 +27,7 @@ var AllCommands = []commands.CommandHandler{
2527 GhostsCommand ,
2628 MessagesCommand ,
2729 FileCommand ,
30+ KickMeCommand ,
2831 CatCommand ,
2932 CatAvatarCommand ,
3033}
@@ -212,6 +215,74 @@ var FileCommand = &commands.FullHandler{
212215 },
213216}
214217
218+ var KickMeCommand = & commands.FullHandler {
219+ Func : func (e * commands.Event ) {
220+ portal := e .Portal
221+ args := e .Args
222+
223+ // Allow using this from the management room by specifying a room ID.
224+ if portal == nil {
225+ if len (args ) == 0 {
226+ e .Reply ("Usage: `$cmdprefix kick-me [reason...]` (in a portal room) or `$cmdprefix kick-me <room_id> [reason...]`" )
227+ return
228+ }
229+ if ! strings .HasPrefix (args [0 ], "!" ) {
230+ e .Reply ("Usage: `$cmdprefix kick-me [reason...]` (in a portal room) or `$cmdprefix kick-me <room_id> [reason...]`" )
231+ return
232+ }
233+ candidateRoomID := id .RoomID (args [0 ])
234+ var err error
235+ portal , err = e .Bridge .GetPortalByMXID (e .Ctx , candidateRoomID )
236+ if err != nil {
237+ e .Reply ("Failed to get portal for room: %s" , err )
238+ return
239+ } else if portal == nil {
240+ e .Reply ("Room %s is not a portal room" , candidateRoomID )
241+ return
242+ }
243+ args = args [1 :]
244+ }
245+
246+ kickerRemoteID := stablePortalUserIDByIndex (portal .ID , 0 )
247+ ghost , err := e .Bridge .GetGhostByID (e .Ctx , kickerRemoteID )
248+ if err != nil {
249+ e .Reply ("Failed to get ghost kicker: %s" , err )
250+ return
251+ }
252+
253+ if err := ghost .Intent .EnsureJoined (e .Ctx , portal .MXID ); err != nil {
254+ e .Reply ("Failed to join ghost kicker to room: %s" , err )
255+ return
256+ }
257+
258+ asIntent , ok := ghost .Intent .(* bridgeMatrix.ASIntent )
259+ if ! ok {
260+ e .Reply ("Unsupported ghost intent type: %T" , ghost .Intent )
261+ return
262+ }
263+
264+ // Best-effort: ensure the ghost has high enough PL to kick.
265+ // The kick itself is sent as a custom membership event so this generally isn't required,
266+ // but it makes behavior closer to a real kick flow.
267+ _ , _ = asIntent .Matrix .SetPowerLevel (e .Ctx , portal .MXID , asIntent .GetMXID (), 100 )
268+
269+ reason := strings .TrimSpace (strings .Join (args , " " ))
270+ req := & mautrix.ReqKickUser {UserID : e .User .MXID , Reason : reason }
271+ _ , err = asIntent .Matrix .KickUser (e .Ctx , portal .MXID , req , map [string ]interface {}{"com.beeper.dummybridge" : true })
272+ if err != nil {
273+ e .Reply ("Failed to kick you: %s" , err )
274+ return
275+ }
276+ },
277+ Name : "kick-me" ,
278+ Help : commands.HelpMeta {
279+ Description : "Simulate being kicked from a portal room" ,
280+ Args : "[room_id] [reason...]" ,
281+ Section : DummyHelpsection ,
282+ },
283+ RequiresLogin : true ,
284+ }
285+
215286var catpions []string = []string {
216287 "You’ve cat to be kitten me!" ,
217288 "I’m feline fine!" ,
0 commit comments