From 874e84af765ac0de51ba1698053e29f97877587a Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Fri, 4 Sep 2026 15:54:30 +0200 Subject: [PATCH 1/6] Render !rooms entries as blockquote cards (name/members, topic, join link); v2.1.6 --- .gitignore | 4 +++ maubot.yaml | 2 +- roomsoverview.mbp | Bin 5113 -> 5633 bytes roomsoverview.py | 87 ++++++++++++++++++++++++++++------------------ 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 8182bc4..e14b68d 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,10 @@ jspm_packages/ ehthumbs.db Thumbs.db +# Python +__pycache__/ +*.pyc + # Test files test/fixtures/ test/sample-pdfs/ diff --git a/maubot.yaml b/maubot.yaml index 3f17638..a45975e 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.1.2 +version: 2.1.6 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index fd48316e2afec3d6109f1de6aa5568776b0ed442..4eacc28a8db818fea4003ae2f3b32f658d957dc8 100644 GIT binary patch delta 5195 zcmZ|TWmweRx(4tehLrAZ1O^b1mXwl4dKhBp5Rh*86N5oZcMHPMNQbm^GXl~bk}4hN zdf$Eab@o2{ob_padG59D=i~3{aVRgKt%iX~4g!I2KrzAWrU=#Qj6@I!G!6oR@Imw- zXA3XOH=g`{7S2xkdbl7An$ZkLy3q_rFJF8R8s;A~5a{n={g_d_ZUJJ%@eTP6I>BuC zyS7m5SG@sq#SYK`twGM&j&{D3uo4Tqj(J?YRz0DH{UYO4GppY`CFD@{QU39t%hhGB zk+kCw543TVq8sg-?^K8x!(#;u%kYPKS^Ahi>;70&g_K z5&u6|aR|F}-w;W7FxLuobr&vrS1iTU8c-6mjk0=8`rV-%3d?aCa&u!QNf0Z*iNLgZ z&SQ8u?KCUZbq8E$xFd4dC_7Us??RnO^2PS&E2jubvfft)Q3<vr-1AKUyS`~uKD$#l7M0t&Cil$G;Z-9=U=Fzv4d#(2; z?h5`_f$bYyi1}zh_Ck`ZVKq#tJ@uXD&eBOQAamOQ91&eJU1RK?w)SW~)-AX~w+Xk? zf8|^~m9cS0%>u4wUrq1rP6;qKs%a-d&#Pe4@kkmnd_D(^>NI;py29qfpV2p!S# zsD6rUG1e!X2QgIs#m?vWGOfer73n&7 zhb5$snPRT3J%8IyJjUG_PO5^|63)lSo|Mi1Qln)XrNS^4;| z3+#xzhuFWsTnV7Q$(FDQLU|q30+Fg9(@R`sL6h!n_ zF^b$b01`+O?ODqMJkRSOw#xX@W2CNCvUnQZV(`X8k=yk~yB^i?(NEnwtrdF2#mM)| zS5LuDKepLq+mCFxzF^B$V$o>S#_hm+3|mNX61V*zvj*jL7q!=!WDenATyeH2$jGFe zD2le{3Dmx@Mnhh639CCUd1|^(0Rf)k)E{@^nt`0nNfX6`&A>~uCkuP9sfXdczTJY+ z>GIr_@COGFeB-2-hz|Ku90*t<;xh<4W7W#SHcFZy?k_cVF3_3(gt_5;EIpbzmw^lKE%1lpD z06b3X#oT=7BR@F&gSgTdy8rFLr?g5?rIcJ2LKQb&-f9!z%fyq%`*M81`$=}6h zUkwcR5^qhRS+$BWZ-1|Q(j_)Pv7V+EEZy~9i}Jmp{@NIxQL@d$HNXDL{NiaXxi zl#1PpKn^l88SOc4%3g;&8%^_-9p?AD-=EEw_bur7(&UD;XEd!aqE9PSQ&rjJ0H8mW z$xr+74Lw3E6fIhJwLPOx3AOr$k>n;0a5?(*~Nml^Dp`K3E=T(}^+OFqc zVhmYUpv;7)=citHwqa)UBs()0X-53Y>he`F7VM{ez3V%ZRhZorgTif9NT`Z1{ViZ=153u@#`zVY4 z^Q?@~SIPM1Lpa8CvrIfIqmyiZ0Fnk-q?E+y|Lfc~|6IfW4?zmRS IS;fQbGOXaq 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 delta 4691 zcmZ{oXD}QL*M?UY(aGwpUV;$Ods)4$NJQ^_iC)91(aRz_S)J%TN}`KyNpxZL&gvz? zH_tco%)B%2_uaqFnYpi-bN*hTytkY%T@5U3N&o$m`m==e4D#HN)z$5?wcnSaj zTr9n<-h1);Te?8sB#aY;3y?;f{Gt4ViF+XU9;xWa{cV)Ch<8(^A;x9pyE=H+aX#z4 zkJKk-yM*fYa-iQY>RffffS5|H=S!kc9{TV>S8EA3D3rAF)F&u3$=dzxE0Cnfjrn@Lv|`G@67IlX%v$;t<)>wy#v#OocrD zc;@hVdU623%F@%8&-%TqoumE#-r2~4(SZT`=NaVEYMDJL0H7od0FVLz0FU?YT|D3W z*n0Rl+WPXl`5)09xh^T6b>&-)UXV`1xh2(>B%2BL=QW*ktx6hlTigggvKWv*kF88e zgMvwsQD5!FQ284x#X25B9U30D%o=T_F2rIOBJ9V9hhv8yPVO4Tx4sV`qKJlHHTB? zX&yoiH_jg<$X^ITM{%HH93{?oa5NlPO@E&*u&HemVaR#{71N88Yr_WSNpd#X6Rhnu ztJ#Sto=xP66;L6$isoz^X*x$>7AZqrLR2wKnYpkfy3*t_Uc!nvO3~pG$pWE8HiL8b zO+POb;p?-fTqeN8`}oHlZB29Vp{w_ZxoC`^*|;=k!RoYyc9C-5vGJ;@VFp&9+r#v} zDUmAY)|?d42b3M4!9Gq&=iNGX9;b@(hV98%@XVi;{Uy_b=9x&E&kVva+Nbi_c_0_G zHv1b<+JwP59DJcd>v>ArJ;sFJLp#PlrzRAcT2qz0_I{w1g=kIq`-@mpSl%%nbUV)T zM{{knIB7s=HqkU~7R)Z9_#f`!^qhH#5dtr|4)RHO1rwQeW>4K=y~twUU#4vOlY{6Y z$3@3g37U5{ZfBj&@aTG&;aK`Bqq~b)ABJxASRpjUBtypHcNN@O60EZ?u!pnaN7mbp zOY26=5i)|Wag6t--W_j1NG9Fi5h>?!p_d6WU3gwf6o}zQ2HnW>_W+r2z7X2(cUQfr zRR_|akA^)`CztKB!vlC!WBIfn+M~NWrLMz1Cx`{hIKoSGv?@y-%p4%`6>t1CaS174Sr*Yuh<6At9Vm2HwK@4T5dY0>c3mN_i!_` zcU+8WMLkvyMCrxB8k|RdSs}X`9suJGuaGzksG3~yX8ZhnWJj^qI@|hp+<8K`FyR_>cZs+6-q5t&d@Y~FqMS)@!{4wqtk9K< zJ@!`UEP5q)2ZFnP?AwK&wjI9zMvdQ?8G1}J$x*;5<%te*QCQ8Gn;)eDP<#+I-!oED zAxq$FAz32j>nXGg#?K~yj!m@&F@WBhNR_ee^y}?%XPP zp$#(FS%%HHh1A+UIN^?A&=G#q2Pu<{Xwwj8N)CK@LrH8$75vsh&?X=b5HI1r?nsE5 z6Qxl#R1hGL0!sgOr|9we^j%Qv819iuFB=qKLkf&}A#7)&k-ayz^XekD(o>b#K{*ad zOg|>dD9VoeZdT<<=p>=nMtBW&$FUFzgGb$$_6Jxjig;*8#`s;p$Rl+Smn7re>0suW4PIwEW9lD3euTegp&@KKgbFQYi#oNj4=%9}jZBr@DC; zk>Zn`8RLEVjdZu;%U^2pyZVVa6Y{m)3)Iq^nn4bA6={eG$`-Il(BnFX9D%x`L$xtz z?p}pL@#pFEbc0mwE;2-l>968eW(Kk>zl_?iLUvIP5hZqkW!Pa*foCuopgj3JnHB}i zG#0a^mu4z4-PL5l67O5X2n4pqI?U9F5rTrZ9taQpTnTGaHsv5Hf=A=t| z)_G2ocdQ!8IG)bW8evK106FWMacM=3W(?e8@Ws72F8gA3bG$}~(s`ON=#$GwsDcr@ zahk=pWY!DUH@oGG#Ikz7!HLva^K!7)B2Q#5CQ8mD-rZxY*Q{rUJ9BlWsX~U&^7^H| zbElkgklhBT)a2l9)Te5K^q(mbuKKvx6(Y2h3O}vOk%hf*lU1#N>V`fWrznwMo@gfQ z{7Zx!TTn0H8ENjaI;zl@6_mv;dof%wRT=2zBpb9?s_|zZp5bCsjrj?ipc?~51{Sf)5IosKbosDwTye8JA@SsyJ zvYUt=KmP_l0-CVni538>@^F`M=s$x*lfM~Y9=wuc(Wi#v=jg$s>!p*6M`pA|xtVH} zg@-H*>xz5*Yyx__=ESzGppmfoDO*7j9TK)?&v2aKZL7uk#5RY0%)c0N@hjVnE(p4nPnUdKEP7fd{qzw7Dq^o|_KPBbJW)hZw$eO(8n?$apv>XaYp)Fc&rkDs=4 zcCok7^)T+h`jrln2y^Rgp7=bDwF;ixwEVF-@mU-rSbnS2k})#Z-MQBd^V-VFl1)}7 zSKPC~=AEGKOB@Oe3>`qk3q=xmgHp|wxVXY#Y5BFK`UG>`*6G-!z;Fc{*-sHy_Dro( zNa{u;em-6o`&*S{k2_*LVH511ij0mVFf*hB*o%aqbP4wO9M%nqAyrpmQKg$MNxff- zu`Rvn3S0&bBPbc?m~#%0pMkxy3oZpO5NwENAq=O#$J0uaY5oQ){H68;wZ~tNhSl|# zX}q$@Y><8t<-Mm@idiYX+4;5otpGRytsD_SepN!V1X<@?ecs-wy|cV5X^a25H1KZQ zNEq|&v@P6X4;Bb#x@`yIwYUkGXizP#?90S`7g8JuVncJEYd*X#&SC+N0GQv3#epz% zp$iqL7Wy#9LgBKDt?kg5c%SMa(o zYSGjf)^mn;5MfN{BBei%uopmMW>9|VR!Nrs^6HmPUak|#0s*-sRt!;9bqdQW%=xlf z6Txr>+Y@x)CpC<#Uy&>!5BkNOC$V1UCv&rO+ZN#c@kf+*m5EXKItIw?oSGo(XlTSf zRA4OzDvG);8(%kM>byHN`w&?q5%SRawsYwZ4+_6U(pNXx8N#uGNRN*ZAg)%)2e@olvcjO$Xc>0&#ba+_!sl;#I&*t{O>%&Tc z-ug?Hf7i#p&dE03ZQdqr4VQgOt?~AE+LPQ(zM$RQJZm~uHB`S@#1h=om3NQIX{xc?R52sTStcTg<{wi;_|( z93@_${uP<1mGUe^FpIxeH{D+lN@n_!>mI>hxHY~ewi?U=V3=eze{#6OOvm6cB@^hp z!i3bV@#h}?v%;%>$Zfg@$)TKS2#_m0glUb>JcTco8`r)m0vs0us+!cCEnC65SWr4A zM$^4=kG9OAB;tE7y+O+8ZW716uWumLBe)uG1=8kDIRebUpOWh>>EeW6dqj18G5`fS zacX0WHBP_2f-8Hd#pkurk#6mBvcuCc(~2QM#@WfrqSAUfAGgdtdpNW7+kv<(VKG!i za2TG0GVRpdz6V|JWEvsSEtl>OEifai`>eVGmR}pyn;%SKim?lg9wkb>>BNjqKNGxK z(rj&+#F*xsal}^vMZwQSL?vd6-8*K;z00!dX2WNV7N!zT8LL7cJTQredU~=`+e2R{ z^wOOjym$oBUn(y}{iM&zOk1x&!uqa*o`$J>FuxtyXJE{=OcQWF(3reEjk3x*w!t8e z`Rl8{;yWKOP~ah0JZx--NJR_8;G~p=%2g*YSF}l7fzy$8TpVu3g z)!r7;(SYO#;v+i=vB9Q&VRT4NkP5FQH2$imCkL(d?o3m?E_NqGE+bBc#i5mhF%6%x zJ0PGh*4%4I`)+{GU8uOV?Oa7?o-C)(rqWNbtcWON;sS7^3ye9n;yP^-3=_5mM(UXT z;Z|k2JMT>@&(qmM_nOPWs7u)7mK_aJs{c-hnD7n>=_3-$`h$hVhf)bcv~3G(oVFwO z6?nT5vuVA4MDo(IVo_W=7lzrGQtCounu91BIZuU(lMw8VX`jvI*h`UHga5XFN5dzT*~!qcfy5<0&+I;@(D>NI7&*yhW6 zX~-7OY`c-SZw&0Q#-r;v2+m@tRC2AoJt-kqL@rIsv#T=nReCUEMaVKpXxkhjq&l2i z`&4#(&$2g%;V`#}8~x74G5MBRaoy^f42iODm2siL7Kc+vZhYOUo2pcy)BNFkRmmO0 zdr5$C1K@QQAy4CT23LywJ)e~S0PXIHx+4m(sCZ($)B z34zVtCKWcLlL9MUK2ST+jGP7bExR@H28h}5rP}pKJB;nF3?Vu1-ors4qElylwzq@6 z#(8*CddY9G@xsh&QXcE&ACSE?OQ4kTo%p3z4*4;IbgoCycATC8$0u_^$Hj%vg`$H7 zm9njsWA)?0(bN`iqqOtNrp0okp?3U1|}ue|1V+wO-SfJ(|;(L{{)9Rm`x9w{m}Io z0Kgz79F6VuF9raFgti0zTa^4K{eS6_e-jd#h56s|L{|d`_n!t901MCz008(Y{@MNq DB5}Vt diff --git a/roomsoverview.py b/roomsoverview.py index 9e69e09..5290a3d 100644 --- a/roomsoverview.py +++ b/roomsoverview.py @@ -244,8 +244,37 @@ class RoomsOverviewPlugin(Plugin): pl = int(users.get(user_id, default) or 0) return pl >= MOD_POWER_LEVEL + async def _room_exists(self, room_id: str) -> bool: + """Bestaat de room nog? Alleen een duidelijke 404 telt als "weg". + + 200 en 403 (geen lid) betekenen "bestaat". Netwerk- en andere fouten + worden als "bestaat" behandeld, zodat er nooit per ongeluk data + verdwijnt. + """ + base = self._base_url() + h = self._headers() + rid = quote(room_id, safe="") + for path in ( + f"{base}/_matrix/client/v3/rooms/{rid}/state/m.room.create", + f"{base}/_matrix/client/v3/rooms/{rid}/joined_members", + ): + try: + async with self.http.get( + path, headers=h, timeout=_REQUEST_TIMEOUT + ) as r: + if r.status == 404: + continue + return True # 200, 403, … alles behalve 404 = bestaat + except Exception: + return True + return False + async def _validate_all(self) -> Tuple[int, int]: - """Valideer alle rooms in de index; verwijder verdwenen. (removed, total)""" + """Valideer: verwijder alleen rooms die niet meer bestaan (404). + + Werkt metadata NOOIT bij, alleen een duidelijke 404 op meerdere + endpoints verwijdert een rij. (removed, total) + """ async with self._refresh_lock: rows = await self.database.fetch( "SELECT room_id FROM roomsoverview_rooms" @@ -253,24 +282,11 @@ class RoomsOverviewPlugin(Plugin): removed = 0 for row in rows: rid = row["room_id"] - info = await self._get_room_info(rid) - if info is None: + if not await self._room_exists(rid): await self.database.execute( "DELETE FROM roomsoverview_rooms WHERE room_id = $1", rid ) removed += 1 - else: - await self.database.execute( - "UPDATE roomsoverview_rooms SET display_name = $2, " - "canonical_alias = $3, topic = $4, member_count = $5, " - "last_checked = $6 WHERE room_id = $1", - rid, - info["name"] or None, - info["alias"] or None, - info["topic"] or None, - info["members"], - int(time.time()), - ) if removed: self.log.info(f"validatie: {removed} room(s) verwijderd (bestaan niet meer)") return removed, len(rows) @@ -301,35 +317,40 @@ class RoomsOverviewPlugin(Plugin): rooms.sort(key=sort_key) - lines = [] + rooms_blocks = [] for r in rooms: display = r.get("display_name") or "" alias = r.get("canonical_alias") or "" room_id = r["room_id"] - link_target = quote(alias if alias else room_id, safe="!") member_count = r.get("member_count") topic = (r.get("topic") or "").strip().replace("\n", " ") - suffix = f" ({member_count} leden)" if member_count is not None else "" - if display: - label = _escape_md(display) - else: - label = room_id - link = f"[{_escape_md(alias) if alias else room_id}](https://matrix.to/#/{link_target})" - if display: - line = f"* **{label}**{suffix} — {link}" - else: - line = f"* {link}{suffix}" + # Elke room wordt een blockquote-kaart: een afgebakend blok met + # gekleurde kantlijn, naam + ledental als kop, omschrijving eronder + # en onderaan de join-link. + link_target = alias if alias else room_id + address = _escape_md(alias if alias else room_id) + lines = [] + title = _escape_md(display) if display else address + meta = f"**{title}**" + if member_count is not None: + meta += f" · {member_count} leden" + lines.append(meta) if topic: - line += f" — {_escape_md(topic)}" - lines.append(line) + lines.append(_escape_md(topic)) + # Linktekst = volledige matrix.to URL (fragment mag rauw # en : bevatten) + lines.append(f"[{address}](https://matrix.to/#/{link_target})") + rooms_blocks.append("\n".join(f"> {l}" for l in lines)) chunk_size = max(1, int(self.config.get("rooms_per_message", 50) or 50)) - chunks = [lines[i : i + chunk_size] for i in range(0, len(lines), chunk_size)] - total = len(lines) + chunks = [ + rooms_blocks[i : i + chunk_size] + for i in range(0, len(rooms_blocks), chunk_size) + ] + total = len(rooms_blocks) for idx, chunk in enumerate(chunks, start=1): header = f"**Rooms overzicht ({total} rooms getoond, deel {idx}/{len(chunks)}):**\n\n" - await evt.respond(header + "\n".join(chunk), markdown=True) + await evt.respond(header + "\n\n".join(chunk), markdown=True) @command.new( name="rooms", @@ -380,7 +401,7 @@ class RoomsOverviewPlugin(Plugin): target = info["alias"] or room_id await evt.reply( f"Room toegevoegd: **{info['name'] or room_id}** " - f"([{target}](https://matrix.to/#/{quote(target, safe='!')}))" + f"([{target}](https://matrix.to/#/{target}))" ) @handle_rooms.subcommand(name="remove", help="Verwijder een room (moderator+)") From efc68c6f063b0b59918924d4e2153e6ab06ce509 Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Fri, 4 Sep 2026 16:08:19 +0200 Subject: [PATCH 2/6] Fix blockquote card layout: hard-break (trailing spaces) between name/topic/link so lines render on separate rows; v2.1.7 --- maubot.yaml | 2 +- roomsoverview.mbp | Bin 5633 -> 5853 bytes roomsoverview.py | 14 ++++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/maubot.yaml b/maubot.yaml index a45975e..645e404 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.1.6 +version: 2.1.7 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index 4eacc28a8db818fea4003ae2f3b32f658d957dc8..9c1906b3c3c8ab7439d54eee6d147e33da1a7e01 100644 GIT binary patch delta 5147 zcmV+$6y)oHEZr?MP)h>@6aWAK2mtGVBwY-+fUS}M007Sb000XB8~|-$bz*OHE_q>X zY*kbV00WuOnrfQRku)HGi;y?)Ac}Yo+cYf;`6=0?+PAmGQt&kF%r`rMYL|+}R-IG} z+-rYZpnZ1BGph@-KdL^M9PX9tbP9xY92Of9EeM%-X@UgfRnjfMB778A|vDgIy4(Na+ zT^;fQ%B~Us09`_p>jE2pl+c=Lme87Nb$AN^0R;5{000CO0002(JMD7Y$dUi~6tnQA z49E~Dep7 z!Ub!kt5cuPg;=!|{E{YPE>g&MK{E40o4jRlOlPdOW}zs;Idd9%0gEzvCw#etM;VNW z{97jxe&9&{U2mL!LZ0w2WD}&=CKr=ATQZwmu4WPS*ocl(@=GWtJe>L&|F3Dx-bMWO z?nFMeiTb=;c~r1(S+HVJtK|-Rf4S^kjNH-L+uq=EM7k-z&Pm_{HzSZePlPGBph5Tf zk=1%TICb9*zUvL$e(!y+4>Nvy)M_P)G*=*P@T^FxX|-B^tv{6@oQTa}Q&?nrES^NZ z+?uX&7W%lIBw}knh9`#Al5!y)o01g4`l$(=hMkg#Vkx4I7L`Q~Uy*YjvQCS@%b)6T z((P&p=i)E@vr$itZe7 z5`5)Pc8o3Aa?B!k!jn*>W>ANso(EezzT1IB4c9fG4jIr`xFGJy0(SL5XkcnxH%@Ze zdvko*ACadInV!T^-$mneIC!Tw?d%Qd{q^i(bkSsK#!qj-|5l5iN-e6cDN~El@Ur($ z(2Q1p3tf;4?uxFMyYx)KZbgU0B9guaK9$bE;ynoi=|!~g_)T~i`?JuW!UlnGCvzIn ziNLJpX3j!_g7-YkypotjNS=Vi!AV(&m`I|qpg_1Pcu!D?3DE!HgJBc+A3k$N^Wl$b z^kd`Tff_t$9CX#7+cu6VQ}c=U!$dC$p5i zaaTO%%rgYp|%LAA+XD1%k`) zs4Fh~?JiXoy9;nhZI9sE4TfHS zDiEs-<)#bqk7ZBy)J8C~NZ*W! zd!%fWr!QU{S%gRGOM@9VlIGCWiiKWzbqOf}ohS+2fMUM!%+?rSSsx_?J~J?_pL8YI zb)S3akb}Fr>#qs!fc);L?)7wWX4a{HwHmTKSQW_wiMnGtSrVr?CIX4lD~R z2`WUSE!(K>2dK=|wbdz6jROvN+X)Lyz3!i+#Vwhbb1!onfZA1R>B?bq62t|6RND6{ zC-MLqZ|s&5T!{)9E0%IK)TJq{3?Xb`%v>n0bNOW9r7%0zQgJ4-*aXe=oph$>qT*n7;F410JRa_b#0A}ahw3Nk^h-unr(DIC0;WcL8#hP*A{%i!mY8b z?zD>nQVCBb{G18a;ynU87*`9%zLD#*}h0X(U@Pd9b&Yi6s&<2aZe851^4#NL zVM(iq?m9Kcq43`LVy-BZ%?tF=HAs9-jSaH5ZM#c~os+iO{r38KTgJBb2H4rQC5i0~ z(6}|~zDi0&$)P)M9u}%^(ycaF>d+Q&K%zQd_=2J@a*&oJ#hwndsc|74Fw+snNBNP%eL^Dt3rz$^sPvH*lbMk|PkZ4VlluhoeGLMJWxX zs)$0Bs6`xVY!4(#g%OFesUi|Jwg-|_xjN}4<>U3J1{o|5)=U|XPEX|b2FF~Etq?o< zJeJ|E z?EL+4|LoL#cRW0PTZn`|&h)E6v&jv3e#9n%M|aX8v^Oe$T1pCatbF{~Y6>-}OQ;0j zU&=wG#eCUGXeHI#ZhVn#Rxsz0rl6;O>@K-y*0U{Jwj9#Gq{5Cl4@!Fa7BncLAbVo2 z_zmbwz}74vukhT$!t~t3T3`kbmEsojEL5ZQ(`QdWc`i;~Jo}ITv^oeriI_wMqDNh`r@GL4!NFGTP>_^q)zf|S#zNYywA z7Kbv@9{!N9=#H2k6ihe&`(FW$YJ^ZC5=GjWt*Q8brze(~;{WV%TP1^9cv6R4sYAb5 z2>5F&dXt4w$`b@-3oGE zT-_*{qCsRMLPIJ0GOXPw@s;ltTVJJfuJ4^PhAE3ZeRHE40W0aUNU-+3$d7*9hyz@r)b7jGKy5r!|O>FdMwa$05nx4=8NRER5y*<9Y#P=Yh z{p%ZJvA?HkhV_O^| zJVj+ch#M`4+&H7i$Fwcb*XJ!?72 z3q;sOkf#IXy68R5V0d&&Pfg%E1R>5??17k!GLD6`~`CxE1 zB4huPog!=S15kq@PBh-!&4=C!v`oZ*IXOZVY5pgNm=Rm@HS@5YBWPeXT4YtxN+&QC zm(OqV=qZ7bdNc(`Uc0F4rg72h_fAIHaq^qt;BC_hv{9N_j>wt`Soo-Hg<$AF5!yKt z)}Rj3U%=Q^R=m_hDCl^|MUkhHPX`=3LUofA8V}%0@;C)~jCwoeK)(C7H|%A9;_8yG zpF&gvA{sF^XD+F{o^&fW640F0Se>Kt8OzkjPEtB#qbF@8S!QhIx(1bds^OW=TSj&J zqyc8Ch)pHjuiAl&8-T=>7;;<0%YD1s#$Tq86=k$bR~PK+ErqNHTDp5sR#fAGeN;CC z2E)_dki7oBI{9R9-0xkS^vnl;=+N|9EeKv@RbK*G9l-XhLM`Zbxq8eO-fno zgoUj|r*y}$;lN8VRg!dZyK-8zImySB!c9(v#pHo{!?~kzs`8;jd6q}~7!xB<7dCT9 zR_4cKk%VF9Z3Sm*4&{A_zO*7=iSy%E3Xpmpd8x2_hkSY{Mf>WbbHC$%LAmBmTb^$4 zNVp4jXF>!ht7ZKdtgtr?7i?4lTikl1ZqK-r3b57;Z-m|D=`65qNC3@3-(1SYL4Hi& zr<*cXg>c6h2P$Xl1r4k1v*rjrZxf5E!CBS}eaNtj(}pRzgWSn-$!s&^#itb+s^)GN zqGqwRTkXge?zV*3vLzLNERRSJkDO&rC>2$`%6q0oh0T!sk z6Sw)2lf&yXz^68Kfa-+#soxBft?2j$Qm*a@EUx6i5ka<{N zMNl0GypYDdJ7O|_sCs2;nB0JRphVGtdI4La%PnsJlHdv&fgYmMRjux+{({&4QUfU4 zk>^3;hL%*g1$NJ5tGXtum^r?LFXWZ)0!nSr7Gh1UUEU^cQVsN^DRb5@~e0{0X#Nxd=1h=YI z--j)O!aK$dn^6v~0K6 zCRT!Rr`^sLXg_Li3L3MmW?&NIFj-=RWu{!YA?J;g=@cxo3oZ}- zf!KWVQYLeMY63YVPn8xJ^)M_c$hmn%HbJ=ED2&@`zF3Gl=@j&}4mvASVzLTBYBEoN z^Vt8)^aZb-ce-jf;cR(E08)SttBg>JYbRM(KJ1$hJ~&^zL9{YH(s0JiqcrQyQGol) zC+CajXk^8v&tOXvbJpc5-mS(|-bKZQt?uP@pH}vNQ#A^-S;|{}1oPL=KAvir8@cvY zr&u674lx7UW#F)Zm8 zi2aa%_z`pSDP7q&P!LunkygL{DHJUGrP8@zkY}j2saMFKiwSm~r~`B=lj5pQPN=EG z0t_0q7RGx>jPol-kSP;$nWoWCQXi4Y7+gn!{z(}MAi?uHH=;M{l3pqsPj66J6tJwT zio<3xs2V13iIyeOP=f0-L8(4zwT6GFPPlb{e`R5OBNG7@E&Vvg3N~Y#aM(6JQwP)x zBA5(RPD2*z{mWgS61sS3We#H(#>F6EjG*(F1Ccc-GsXFWaxIyUc zM@aK@p$*vwmuGsaZTkQlSkqUExAK*@T0(&C9VbC7uE*hfsRT4YX!%M9NF9TEp(_S| z!e5Lbxw0OhJiC?rfOF4pFWcn#k@D*JS#ayR4%pc%Sks`nU2rnEJRg|{)*j8Pk^t3; zuev!CUsw=NjWbyZe+IBM55O^Jm{R8q4=jhg&svcnUC2N{&@rkyP&wyNLub?WDR;}{ z?a|*d&dx7-!x8>0W6;jPKgOXHvL@Ypr#Vb`ZsdtQh4NhnHsm1BT zy9^NtlO~%h+D-aHAb|dYB>z`zE~O*EN>*6@&C$LNS7j-9n==#<517;fRYa0QQaU|S zb!IgnPL2GGsd~1gOejBHDx@&h{{v7<0RkQa6aWAK2mtGVBwY-+fUS~~0~IL(nb4Ci z6*~dflWG+=ALxK2T^;fQ%B~Us09`@=01yBe00000009610DytglfV@i0+i5`+7%%N J$`t?r007vqv2p+a delta 4939 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 in + # de formatted_body (echte nieuwe regel in Element). link_target = alias if alias else room_id address = _escape_md(alias if alias else room_id) lines = [] @@ -337,9 +343,13 @@ class RoomsOverviewPlugin(Plugin): lines.append(meta) if topic: lines.append(_escape_md(topic)) - # Linktekst = volledige matrix.to URL (fragment mag rauw # en : bevatten) + # Linktekst = full matrix.to URI (fragment mag rauw # en : bevatten) lines.append(f"[{address}](https://matrix.to/#/{link_target})") - rooms_blocks.append("\n".join(f"> {l}" for l in lines)) + cards = [] + for i, ln in enumerate(lines): + suffix = " " if i < len(lines) - 1 else "" + cards.append(f"> {ln}{suffix}") + rooms_blocks.append("\n".join(cards)) chunk_size = max(1, int(self.config.get("rooms_per_message", 50) or 50)) chunks = [ From 11c8c5ca15597f83ffba721811ee0c9ddc8f619d Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Fri, 4 Sep 2026 17:23:31 +0200 Subject: [PATCH 3/6] Avatar icons in room cards: fetch m.room.avatar + matrixrooms.info enrichment, store in DB, render 32px icon; v2.2.1 --- maubot.yaml | 2 +- roomsoverview.mbp | Bin 5853 -> 7558 bytes roomsoverview.py | 264 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 210 insertions(+), 56 deletions(-) diff --git a/maubot.yaml b/maubot.yaml index 645e404..3548c09 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.1.7 +version: 2.2.1 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index 9c1906b3c3c8ab7439d54eee6d147e33da1a7e01..34ecd241db1c9d48655cbe69dc017c9567457ce3 100644 GIT binary patch delta 6907 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 None: await conn.execute("DROP TABLE IF EXISTS roomsoverview_cache") -def _escape_md(text: str) -> str: - """Escape markdown-significant characters when embedding user content.""" - return ( - text.replace("\\", "\\\\") - .replace("`", "\\`") - .replace("*", "\\*") - .replace("_", "\\_") - .replace("[", "\\[") - .replace("]", "\\]") - .replace("<", "\\<") - .replace(">", "\\>") - ) +@upgrade_table.register(description="Room avatars") +async def upgrade_v3(conn: Connection, scheme: Scheme) -> None: + # avatar = mxc:// URL van m.room.avatar (of NULL als room geen/ontoegankelijk + # icoon heeft). Alleen in de lijst getoond als de URL naar een echte + # thumbnail leidt. + if scheme == Scheme.SQLITE: + # SQLite kent geen ADD COLUMN IF NOT EXISTS; de kolom bestaat al zodra + # een eerdere (gedeeltelijk gefaalde) poging hem heeft aangemaakt. + try: + await conn.execute( + "ALTER TABLE roomsoverview_rooms ADD COLUMN avatar TEXT" + ) + except Exception: + pass # duplicate column -> al aanwezig, prima + else: + await conn.execute( + "ALTER TABLE roomsoverview_rooms " + "ADD COLUMN IF NOT EXISTS avatar TEXT" + ) class Config(BaseProxyConfig): @@ -159,10 +168,11 @@ class RoomsOverviewPlugin(Plugin): pass return None - name, topic, alias = await asyncio.gather( + name, topic, alias, avatar = await asyncio.gather( state("m.room.name"), state("m.room.topic"), state("m.room.canonical_alias"), + state("m.room.avatar"), ) members: Optional[int] = None @@ -184,6 +194,7 @@ class RoomsOverviewPlugin(Plugin): "name": ((name or {}).get("name", "") or "").strip(), "topic": ((topic or {}).get("topic", "") or "").strip(), "alias": ((alias or {}).get("alias", "") or "").strip(), + "avatar": ((avatar or {}).get("url", "") or "").strip() or None, "members": members, } @@ -272,12 +283,14 @@ class RoomsOverviewPlugin(Plugin): async def _validate_all(self) -> Tuple[int, int]: """Valideer: verwijder alleen rooms die niet meer bestaan (404). - Werkt metadata NOOIT bij, alleen een duidelijke 404 op meerdere - endpoints verwijdert een rij. (removed, total) + Vult daarnaast de avatar (m.room.avatar) in van rooms die er nog geen + hebben; rooms waar de bot geen state mag lezen (403) of die simpelweg + geen icoon hebben blijven op NULL. Metadata wordt verder nooit + overschreven. (removed, total) """ async with self._refresh_lock: rows = await self.database.fetch( - "SELECT room_id FROM roomsoverview_rooms" + "SELECT room_id, avatar FROM roomsoverview_rooms" ) removed = 0 for row in rows: @@ -287,13 +300,119 @@ class RoomsOverviewPlugin(Plugin): "DELETE FROM roomsoverview_rooms WHERE room_id = $1", rid ) removed += 1 + continue + if not row.get("avatar"): + mxc = await self._get_avatar_mxc(rid) + if mxc: + await self.database.execute( + "UPDATE roomsoverview_rooms SET avatar = $2 " + "WHERE room_id = $1", + rid, + mxc, + ) if removed: self.log.info(f"validatie: {removed} room(s) verwijderd (bestaan niet meer)") return removed, len(rows) + async def _get_avatar_mxc(self, room_id: str) -> Optional[str]: + """haal de mxc-URL van m.room.avatar op (None als afwezig/ontoegankelijk).""" + base = self._base_url() + h = self._headers() + rid = quote(room_id, safe="") + try: + async with self.http.get( + f"{base}/_matrix/client/v3/rooms/{rid}/state/m.room.avatar", + headers=h, + timeout=_REQUEST_TIMEOUT, + ) as r: + if r.status == 200: + d = await r.json() + url = str((d or {}).get("url", "") or "").strip() + return url if url.startswith("mxc://") else None + except Exception: + pass + return None + + async def _enrich_avatars_online(self) -> int: + """Vul avatars in via de publieke matrixrooms.info zoek-API. + + Alleen voor rooms met NULL avatar: query op alias-kern (of naam), + match op room_id/alias/naam, en sla de gevonden avatar-URL op. + Gebruikt de HTTP-URL direct (de matrixrooms-media wordt via de + homeserver-media-proxy van de kijker opgehaald); is daarna stabiel + genoeg voor het overzicht. Retourneert aantal gevulde rooms. + """ + rows = await self.database.fetch( + "SELECT room_id, display_name, canonical_alias FROM roomsoverview_rooms " + "WHERE alive AND (avatar IS NULL OR avatar = '')" + ) + base = "https://api.matrixrooms.info/search" + filled = 0 + headers = {"Accept": "application/json", "User-Agent": "maubot-roomsoverview"} + for row in rows: + rid = row["room_id"] + alias = (row.get("canonical_alias") or "").strip() + name = (row.get("display_name") or "").strip() + q = (alias.lstrip("#") if alias else name) + q = q.split(":")[0].strip() # zonder server-deel (API geeft 204 op 'alias:server') + if not q: + continue + try: + async with self.http.get( + f"{base}?q={quote(q)}", headers=headers, + timeout=_REQUEST_TIMEOUT, + ) as r: + if r.status != 200: + continue + res = await r.json() + except Exception: + continue + if not isinstance(res, list) or not res: + continue + hit = next((x for x in res if x.get("id") == rid), None) + if not hit: + hit = next((x for x in res if x.get("alias") == alias), None) + if not hit: + nl = (name or "").lower() + hit = next((x for x in res if (x.get("name") or "").lower() == nl), None) + if not hit: + continue + av = (str(hit.get("avatar_url") or hit.get("avatar") or "")).strip() + if not av: + continue + await self.database.execute( + "UPDATE roomsoverview_rooms SET avatar = $2 WHERE room_id = $1", + rid, av, + ) + filled += 1 + if filled: + self.log.info(f"avatars gevuld via matrixrooms.info: {filled}") + return filled + + def _mxc_to_http(self, mxc: Optional[str]) -> Optional[str]: + """mxc://server/media_id -> HTTPS-thumbnail-URL op de eigen homeserver. + + Element/federatie mogen media van vreemde servers niet zomaar laden; + via de eigen (bot)homeserver geproxied werkt het overal. 64px-crop is + klein genoeg voor een kaart-icoon. + """ + if not mxc or not mxc.startswith("mxc://"): + return None + try: + server, media = mxc[6:].split("/", 1) + except ValueError: + return None + if not server or not media: + return None + return ( + f"{self._base_url()}/_matrix/media/v3/thumbnail/" + f"{quote(server, safe='')}/{quote(media, safe='')}" + f"?width=64&height=64&method=crop" + ) + async def _list_rooms(self) -> List[Dict[str, Any]]: rows = await self.database.fetch( - "SELECT room_id, display_name, canonical_alias, topic, member_count " + "SELECT room_id, display_name, canonical_alias, topic, member_count, avatar " "FROM roomsoverview_rooms WHERE alive ORDER BY " "COALESCE(display_name, canonical_alias, room_id) ASC" ) @@ -317,50 +436,82 @@ class RoomsOverviewPlugin(Plugin): rooms.sort(key=sort_key) - rooms_blocks = [] + # Elke room wordt een blockquote-kaart: icoontje + naam/leden als kop, + # omschrijving eronder, onderaan de join-link. De formatted_body bouwen + # we zelf op (veilig ge-escaped), zodat we per kaart een klein avatar- + # icoon kunnen tonen — plain markdown kan geen width/height sturen en + # allow_html=True op user-content is een injectierisico. + # + # Avatar-bron: ofwel een al-publieke HTTP-URL (bijv. via de + # matrixrooms.info media-CDN, opgeslagen in de DB), ofwel een mxc:// + # die we via de eigen homeserver als thumbnail tonen (werkt ook voor + # remote media, mits die server publiek serveert). + cards = [] for r in rooms: - display = r.get("display_name") or "" - alias = r.get("canonical_alias") or "" + display = (r.get("display_name") or "").strip() + alias = (r.get("canonical_alias") or "").strip() room_id = r["room_id"] member_count = r.get("member_count") topic = (r.get("topic") or "").strip().replace("\n", " ") - # Elke room wordt een blockquote-kaart: een afgebakend blok met - # gekleurde kantlijn, naam + ledental als kop, omschrijving eronder - # en onderaan de join-link. - # - # Regels binnen de kaart scheiden met HARD BREAKS (twee trailing - # spaties), niet met een kale newline: mautrix rendert een soft - # newline binnen een paragraaf als spatie, en dan plakken naam, - # topic en link aan elkaar. Twee spaties voor \n geven
in - # de formatted_body (echte nieuwe regel in Element). + avatar = r.get("avatar") or "" + if avatar.startswith("https://") or avatar.startswith("http://"): + avatar_http = avatar + else: + avatar_http = self._mxc_to_http(avatar) if avatar.startswith("mxc://") else None + link_target = alias if alias else room_id - address = _escape_md(alias if alias else room_id) - lines = [] - title = _escape_md(display) if display else address - meta = f"**{title}**" + address = alias if alias else room_id + name = display if display else address + + # ---- plain text (body) ---- + plain_lines = [] + plain_title = name if member_count is not None: - meta += f" · {member_count} leden" - lines.append(meta) + plain_title += f" · {member_count} leden" + plain_lines.append(plain_title) if topic: - lines.append(_escape_md(topic)) - # Linktekst = full matrix.to URI (fragment mag rauw # en : bevatten) - lines.append(f"[{address}](https://matrix.to/#/{link_target})") - cards = [] - for i, ln in enumerate(lines): - suffix = " " if i < len(lines) - 1 else "" - cards.append(f"> {ln}{suffix}") - rooms_blocks.append("\n".join(cards)) + plain_lines.append(topic) + plain_lines.append(f"{address} (https://matrix.to/#/{link_target})") + plain = "\n".join(plain_lines) + + # ---- formatted HTML ---- + e = html.escape + avatar_html = "" + if avatar_http: + avatar_html = ( + f'' + ) + title_html = f"{e(name)}" + if member_count is not None: + title_html += f" · {member_count} leden" + body_parts = [f"

{avatar_html}{title_html}
"] + if topic: + body_parts.append(f"{e(topic)}
") + body_parts.append( + f'' + f"{e(address)}

" + ) + cards.append(("".join(body_parts), plain)) chunk_size = max(1, int(self.config.get("rooms_per_message", 50) or 50)) - chunks = [ - rooms_blocks[i : i + chunk_size] - for i in range(0, len(rooms_blocks), chunk_size) - ] - total = len(rooms_blocks) + chunks = [cards[i : i + chunk_size] for i in range(0, len(cards), chunk_size)] + total = len(cards) for idx, chunk in enumerate(chunks, start=1): - header = f"**Rooms overzicht ({total} rooms getoond, deel {idx}/{len(chunks)}):**\n\n" - await evt.respond(header + "\n\n".join(chunk), markdown=True) + header = f"Rooms overzicht ({total} rooms getoond, deel {idx}/{len(chunks)}):" + html_body = ( + f'

{html.escape(header)}

' + + "".join(html_part for html_part, _ in chunk) + ) + plain_body = header + "\n\n" + "\n\n".join(plain for _, plain in chunk) + content = TextMessageEventContent( + msgtype=MessageType.TEXT, + body=plain_body, + format=Format.HTML, + formatted_body=html_body, + ) + await evt.respond(content) @command.new( name="rooms", @@ -396,15 +547,16 @@ class RoomsOverviewPlugin(Plugin): return await self.database.execute( "INSERT INTO roomsoverview_rooms " - "(room_id, display_name, canonical_alias, topic, member_count, added_by, added_at, last_checked) " - "VALUES ($1, $2, $3, $4, $5, $6, $7, $7) " + "(room_id, display_name, canonical_alias, topic, member_count, avatar, added_by, added_at, last_checked) " + "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8) " "ON CONFLICT (room_id) DO UPDATE SET display_name = $2, canonical_alias = $3, " - "topic = $4, member_count = $5, last_checked = $7", + "topic = $4, member_count = $5, avatar = $6, last_checked = $8", room_id, info["name"] or None, info["alias"] or None, info["topic"] or None, info["members"], + info["avatar"], evt.sender, int(time.time()), ) @@ -432,13 +584,15 @@ class RoomsOverviewPlugin(Plugin): else: await evt.reply(f"Room `{room_id}` stond niet in de index.") - @handle_rooms.subcommand(name="refresh", help="Valideer de index nu (moderator+)") + @handle_rooms.subcommand(name="refresh", help="Valideer de index en vul avatars aan (moderator+)") async def refresh_index(self, evt: MessageEvent) -> None: if not await self._is_mod(evt.room_id, evt.sender): await evt.reply("Geen permissie: moderator of hoger vereist.") return removed, total = await self._validate_all() + filled = await self._enrich_avatars_online() await evt.reply( f"Index gecontroleerd: {total - removed} rooms over, " - f"{removed} verwijderd (bestonden niet meer)." + f"{removed} verwijderd (bestonden niet meer). " + f"{filled} avatar(s) aangevuld." ) \ No newline at end of file From 812f30d3332c77f4a7309767a3210e9badb40e5b Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Sat, 5 Sep 2026 00:17:39 +0200 Subject: [PATCH 4/6] Add !rooms edit subcommand: moderators can change name/topic/alias of a room record; v2.3.0 --- maubot.yaml | 2 +- roomsoverview.mbp | Bin 7558 -> 8256 bytes roomsoverview.py | 71 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/maubot.yaml b/maubot.yaml index 3548c09..c38062b 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.2.1 +version: 2.3.0 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index 34ecd241db1c9d48655cbe69dc017c9567457ce3..bdbe11f2174c903d52e79fa580d5110c55d52635 100644 GIT binary patch delta 7644 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 delta 6903 zcmZYERa6uVfCb>8yBq25mhK!F8itfc9J*6*=tf!z=@1yYySuwf5G03gg+2d1?%vmX z?!$fldSxRepxPiLWMTjSfDYJ-Wio38*)x9y001)p000X>1#q_Vws!U6@wakz0_$S{ zkaYI49CY`x9K8Lo00_u;2mrwUj=>rr%rHLutr_-1L{#t0cV0RsY7YX#Y)DT)mbriz z4w3@c$jfJrph-%{r7}vtS@Iuh`~fj>8dw{8BtFDniXZZSaEaQnF_DGMH3hN+XdW%Lj>-;un+XllsY=6I+W{C1hp)!$^FFz?^B7q87&f3 z3Z9h~mACuqmQfLjR%4mW7L5i)O(_5X0y6*rA3&9gKt}|{03ZSPvK-X@|B15?ryd(> z9@p7;r4A<;lmllft3;WmJI!2cd1(+RIrI4+g);Uao`^GY9Q`~4+R zW0})J@54wp?@!@bRmJ6-8*#NEO=0oxa|{}W5*0zcD+_E>CfzqHo)q`1F+f6Tvi$_~ z1rLeOeF`&V?$b5zP!Kku;~T|wo%JEE$t`|FLkL=)DMWs#*m1B%$zeI-(u0ENN%8b< z4F>N{du3dc$o`?2ht;+Yd<5+dqbVDB3`U-vZdsCBksFIAfA?XGADN2b3Yu5P$l^|1 z`iS0D*QFuD_L}?jFtn3APwU6n+}Pu-b-!}v>KT#+;!h%@en<{9%RbEmdh0Z^E+!`B zwVm^7A9zTWxRaPzFd_p#KdU5h)Qs)y>k@#KHZ?TdM5$p(ka`-K5@hEBH;v{o6XT1t z3k_)eIL=?ydRB)smjc-PyH1@l6b)mqr~CYIMQp&rglG}n58^y% z6_x8qHMT=$zkrDv!_eGX-%l#6FP~&;p%ibkjzY|#60 zy}6FIXHn4l&~$pau#=0+DJJqA^J2%fL^887@XL8P)#~6O_vzpOTN=*;H5`=d)u>jK z?j@0n!jKWW>R#lXVP`k%G$e)h%`ad}PEpFmLAIe}CUp_Py#r*lBw;#GEUd9_8?`U; zLN-El|CrA z<*r5M6ImVS>W4X7vE?uIx$I_r=@vkaYzTMbvn2a8;xwQ@x;hH0qRpODSB*r{Wg-(N zW1s>+Vh4XjfqqSBfi!g5b%^UD@EaEE*SaqhK$pobQ7e2W z)=d;TB_%eq$}s!Ul9@pIuEp+_j;)VwD)N)Yh}sOAYp=iPB}ApVMg8MoAy`!gzfnAD z;{!A1^qJVvNRTpan6ItP8B~76tP=5uPqtl#U+QHE;ODu+Khv#%g;XlBvCG>8rw7{5 zMJ=EX(_`9ur&dY@a1R7OikA^0Hn_R}Q*D(K+S#hVaI&d0xFn-gHRKxgre~Vp$mxW7BtDo^386 zSAtzJeA39+P!E?m?W>@_rD^WH%9`6Pp@bsUTbN}lbJ^7aNxz0uF>D9dVB4bKXkx>P zq^1`_E;RFXld2;TdUWOaWBm)rfSOIC`t7zAG660T7J#6ydRb-@*q6=I>P|9yyJiZ! zMA2a916!h7M&Fps=y8E_o5yuX-12c%klPBo);A2Hw@ZFk-S7C%)L+2hBvvUWJLtD3 zq{KRFA!TA#Sn%_{X@fDAD72x~fS9ZXrOwtevgPEc^TsXL~XmP$^r1X@P zS+@?j;$D_BN^RR&t{3y7h{(jU-!44zn5G=J7y(xguzba zu5JN6^tQ^~)WRMOr8wV7sc$jXAzote>X@J|eW^&A`0sht3M~j5G4O?E@BLnarEHnE zY^x8T0)d|<1aCM%UUY7JdkiX(i(nU0j(`qSJr}r?=T>0X?QP@h!dA#&#Z~0^Gj%+) zp)kRNmj_iY2z$}{mQ`cMj{h4^@QQ54I{1MR3>Iu+vt)l%kB@iyb>b7|BGqM{JP2p! zs*e@7WIr+=_bL!At|xvzQU-O}4dZBD`0OH6Ia(t%J%>#-APhD4;Xu{@_{ehMx&)7y z79GQY3~IPVN-z8R;6dfPdE#!3huy(BnPVi+}fpM2AmewmB;$eu!vj6S6qhwC!e z^KxwdeS6FBPiJ7AX?hilHsmaB_Lew6w;`<8*VFNRhn#@k!LO(78y_0AlNfu%r=H2v zWLlv)0%@iVHX(W}+9l-RMekAd)Nx|`-&Uv&nzzXP9dfI9uh3PFz$LFa?UQ&W{enVu z1*r}+f|5g^B3OQ0ak1eAUnf#lKkez;m@ELo9BQGn``$XU6t{tdH&-=ei~PVgJQ7KQKaRp&le09U;q>m~}Ezf)s2N7J<-1f%H?yqb9c&-g7lFa^B+ zc{7C6sdOX;7(o$_W+LACXV?nfT;xi>oLanY1?Vt5G9z+P*f>u9q3@9H!F|GHD65Wz zRD{3B!R}FU-MOHpr53-qpSjnnbf$Pz_zRRWX?Y^n8t&I9dd%va*j0|OKW}6Q$^_Up z4Yf}8EEc}j95^?=&jKeFfx)~TS9A(BIy~|pRJA_tAw!ct30s*bR>|emyB60juw8Qo z1RzopdnQQ<8qP5v#qkRFf2wigoSc7O)|N4#QPTP`ILe{X|N54nZRV-(=I7h(%rbs< zM;%>gMJXdvWo&+Sm`YOHc<{?3Lb_?V5^(>fkD(v;H{nw6=$CoC$5>|ub#N`57{GXl zm^I5+m@Z9pWUUOeK>I{ zsjXUZ#fSsFI&dq* z1ggs_RcEeTx2>e*q@+{qGgydGKkFI8nOUp~LUv3vu6ge_9fvXZcf)A~`OV+)c+Nab zRvY*0rdIi*<%ifd9Zbvo-}s^VQ-Yg?!CJxLT$d~5MzQ$al$0g!gg^vCcDI&K)PDs5&GVyyvMbyF*0YZa6Mq^qLhn!mmM)$W}u7J=@?d!Y|M`t?H z^7?OAV#y(`oTJfmUZ>#kC^Tf}{;oXvGQqCO75caPSS-y}!|Upt?OWIKcrUwXzc!tW zaks5_HNT}m6gC&UrREX9ZP@q|13fs*DMIK=+QE0c%F49Ja?=J6Z|~D8;)fd(Rw%Xe zKnk)mNdKnrVO3$bl%Q1fn#h{DTHWycBmK7uLo_)8D-(k;ubm>+8@i&;e8ei+7tRMfY}?1qKZX0x&CDg6?C zAqq>&niKNg2f(LRhf&M8$P}!wPqf#86%1+qDqse$lB7eR3n$HbvwN9H1-@3y?V1k zcQ!ZlWWQG-#q2w5U>`q6_gS@YXrk56H~NqNI@4$AT;lgzJ2F-0VIpiz+=I(Bd>iLD zmg8GE6`}n``380D#V6CdlhDzJx5zVP-*)EisX>mvr6ZG*Qir$;0wfv=>WKfe0B38_x-b|E0 zgh|~9E=L&&1!XsRaEO6p^hpvMU(?^lL))I<+oAfNH+UJcp_hj|nCqY&k+93S#hT@uO?f5=mtY2eUB5myzE@q81?e&D#`Hz}4YV#;5=H+5wVgfh z_Ut{bw-4&ex(*kK$71H#zm_PjjR8?{Ll`u`0J&Brv9dWe1-gz!c@#@ni!F($z0xMf z;CIFo3m3$oG#oNqAL;FSXS1V(B@j8e%jpflsk9G{Of(2Y0Ilytp%jpwaHo9vSg)*O zu!^|`4|i0{VzfI1l+=ORr;27@4cdJDmzsV5#s7n#Z>*?@`V^pvG7S;=(=sib-LFN; zF~tiDRpNc7h2BX0v|do!K+X-YWobq1Lb@UL5aioZaUfHDAFvpV5xpWUvgO4&_!1k1 z)BR6!{vB_gdre8S>d6JKcc`W5U9%qc8cCc)MXK+g-m2TXic!VLUb`d2Qhbbj_EA>g z7`k=bx4@G0b$M$N9%93Cd-G%jv*f2;EF(dm5+MrTSSX(@;~rdx$`MSQzeIIsj%0g? zVlz8}mPp7zI@*#kHyv2{+JgM={BMR~NtZoL8EXyeAKJ|_hi|uBBte&Ko`-mNt8T)F z&q)mnaD{qBPfrU}niOOyrmHqXJ0e(d*J(b3xww=5yn04)yIGE{)Tc50l-NmR;u9Ur zdJ3iEWhg{HZYgXYLp(S{lTz%c!%HBvw>B$yGdYEgseJP+_!2`6t(|U9?BY+5mi5=r^dhL{L+2o`<^3kT;5A&QaKbze;6(0wOr zvV$nm9t^zxHxDaZhmaJ}aoQd?s@1i@HMgev8R~+)IgWV76C}qfiiI2j@x<6K4dPI2 zVq~i7&bW1?MWr>Z^E;g$x*t$>;F02{2{7Ip5E)1`KBC&C_&sd!_!(X^B>Gk2UX6p% z0p2uOkFg8!eV7tHfsOZ^*CG-O|K?fA3emZ!D*PmT^>|}m}3Er`z@ExH&7JR2AetuiE9_Ciuf|Ekx~A&eqE8`T+i$S zG%3R74^dX;u3C&hk_Zo5dl1Uh(%shq)`-sQ-$v%u%GbW=xfu>02 z1M^m^{qn=d*2XK~TKx8Jtt+Lz(GI%>NRaXR!)gRzpgWjD*V%=!nAiPNF)!iw8st6A zCP$1ax1N*?a6ts(*J|#x(Q-4qDyW5h_}kkZ@7u6tP`-cfehHbGwI${f3x-%{$hA-H zwnaI_ zdLpsrhn2-16a?W6D{mN?UqYNExErt6&)6ECueY+sU`16Z3eVWS2{<0jRJzjp7Ukmo z4*DKcG%X)-?*iqds)8#kp|wTrUP6!%tD)WS6#nMf7KeUQ|ApGFY)s{ky*U@T%>tXc z1y%9E+an?ObhyIfxLF1#903S0k6TF~dMeuO>{6aO>jBpr&`hY0!?mLl@B2NF-L^Qo zyUQ`o#c*P5b+B*VZAv;es$Bh;Og<_3S>CWQnZeye0ol2c?k0-@de-xrx|Q~_BY=sn zOa@(>@L*O;P)twjaJmR$?lAk zgk8Do;f0SV85Tp(&g84J@;;%-pEHT#qaL)B$av{$ zr|FMIfX%hd!BG}n^+4M&9KU~=*0Vv6G7bCD5MF& z>RV-gDJC~e5!=3@zht+@9M|BohUu%bTXv}bFw7n@!BL7396mY8VVu!ngcKI@m*F;h zD2JCw#JmqaN8uiAd^u7Obwxs^KoBk~lmKyAh06L2yGpG!5t1`jz4AO?UsSADDxad7-$H%$MC zu>DR5@V9WV(PE_d6_AWtmTTLE%JMP2Tf}g*sf>(jJ5nYDNJPd_-G60cUT1uSsr_~b z+uuL6(0IDN4yH9Jfzvvc&g|JGm3^^s#on~9CxxJ~MRz}K!m-m6+rMyDvPaTo?$_<; z9Dg4^yWbI%Gf+WU;qHl0ys|3((lI$qKDWdIic)?ebcV%zYk=FLa9E!BAJ$01cJvw) z+>O=obB=*)>gB|7GGIOdmJBBU1O0y}pk+OcPtm<4KNFpHEyVl541{qrjucowj|2)A z{41alU`~TF3n(W^6x^_y%`U+{n z13ciE#vdFDuG>j2w(0zT3#YS9H((;ezM@da%by|YgpwzzTJI7`c?#PCaMXis&zJS2 z(#6B46SN~+vm<|Mj3@o`r7)?XiiLIVMh2O{wEX~(Q}wh9TbXUk(+%1+fqB-HYd!Q6 zw_ECk{P=eN=SsgKDcD|mX0c{4FbiVK`CS&3ysA3_L-)Jt!5KR`RO}!LeXGy^wFt`S z8$P=?q8f0vmK&S%*g`DneZ6k{eao$;v#dk6V>)q?Y+d@+ITNpA-nHkV`{k&&z(z8*LjL)^ld=o7d0Zl~cA;kZVQCb%Z zb@(Z|)%o)bDEc>RE9(~O;JK>vOM*|W0p{IR;dl~Is+mY40^eP1@9O*y3T+`CSS5G| zxZ?m&mA!EOt3-jzPScn zP~yR}PU#$juj;m}lx0}gjk}TS+4Of88)QL0VqyH$^b}qfe1;#1HOx@W#OBBP^X~|F zBLqold2J&zzkM+mz+#5%6`QtX@=fL`$ZW6M(poh~K5VHp8IKhy9)(J^;?wly0nn?z ztR&+g?cPFD6y5=nmNq!%6T~se73he#8MH?Dn^Ii0K?sO6NdHHsQsrsc|1+pm7g}+I oh17Cd5!(OnJmi4?f&c&jf None: ) +def _parse_edit_args(args: str) -> Optional[Tuple[str, str, str]]: + """Parse `!rooms edit =`. + + Retourneert (room_ref, field, value) of None bij ongeldige vorm. + field is genormaliseerd naar lowercase; value mag leeg zijn (wissen). + """ + parts = args.strip().split(" ", 1) + if len(parts) != 2: + return None + room_ref, kv = parts + if "=" not in kv: + return None + field, _, value = kv.partition("=") + field = field.strip().lower() + return room_ref, field, value.strip() + + class Config(BaseProxyConfig): def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("refresh_interval_minutes") @@ -515,7 +532,7 @@ class RoomsOverviewPlugin(Plugin): @command.new( name="rooms", - help="Toon de room-index; subcommand: add, remove, refresh", + help="Toon de room-index; subcommand: add, edit, remove, refresh", require_subcommand=False, ) async def handle_rooms(self, evt: MessageEvent) -> None: @@ -566,6 +583,58 @@ class RoomsOverviewPlugin(Plugin): f"([{target}](https://matrix.to/#/{target}))" ) + @handle_rooms.subcommand( + name="edit", + help="Wijzig naam/topic/alias van een room (moderator+)", + ) + @command.argument("args", pass_raw=True, required=True) + async def edit_room(self, evt: MessageEvent, args: str) -> None: + if not await self._is_mod(evt.room_id, evt.sender): + await evt.reply("Geen permissie: moderator of hoger vereist.") + return + parsed = _parse_edit_args(args) + if parsed is None: + await evt.reply( + "Gebruik: `!rooms edit =`\n" + "Lege waarde wist het veld (bijv. `alias=`)." + ) + return + room_ref, field, value = parsed + field_ok = {"name": "display_name", "topic": "topic", "alias": "canonical_alias"} + if field not in field_ok: + await evt.reply("Veld moet `name`, `topic` of `alias` zijn.") + return + if field == "alias" and value and not value.startswith("#"): + await evt.reply("Alias moet beginnen met `#` (bijv. `#mijn-room:server.nl`).") + return + room_id = room_ref if room_ref.startswith("!") else await self._resolve_alias(room_ref) + if not room_id: + await evt.reply(f"Room `{room_ref}` kon niet gevonden worden.") + return + new_value = value or None # lege waarde wist het veld (NULL) + column = field_ok[field] + result = await self.database.execute( + f"UPDATE roomsoverview_rooms SET {column} = $2, last_checked = $3 " + "WHERE room_id = $1", + room_id, + new_value, + int(time.time()), + ) + if result != "UPDATE 1": + await evt.reply(f"Room `{room_id}` stond niet in de index.") + return + row = await self.database.fetchrow( + "SELECT display_name, canonical_alias FROM roomsoverview_rooms " + "WHERE room_id = $1", + room_id, + ) + label = row["display_name"] or row["canonical_alias"] or room_id + shown = new_value if new_value is not None else "(leeg)" + field_name = {"name": "naam", "topic": "omschrijving", "alias": "alias"}[field] + await evt.reply( + f"Room bijgewerkt: **{label}** — {field_name} is nu `{shown}`." + ) + @handle_rooms.subcommand(name="remove", help="Verwijder een room (moderator+)") @command.argument("room", pass_raw=True, required=True) async def remove_room(self, evt: MessageEvent, room: str) -> None: From c73b5e53c7ce0161bfaad43f9427d13041c7b4dd Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Sat, 5 Sep 2026 00:26:33 +0200 Subject: [PATCH 5/6] Extend !rooms help: dedicated help subcommand with full NL guide, richer help strings on all subcommands; v2.3.1 --- maubot.yaml | 2 +- roomsoverview.mbp | Bin 8256 -> 8762 bytes roomsoverview.py | 53 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/maubot.yaml b/maubot.yaml index c38062b..b114188 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.3.0 +version: 2.3.1 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index bdbe11f2174c903d52e79fa580d5110c55d52635..35e6db815435335eca7fa6e391b6739910a333dc 100644 GIT binary patch delta 7437 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 Jr1{VGe*kP{ACLe5 diff --git a/roomsoverview.py b/roomsoverview.py index 3f9ed25..7f1d0bb 100644 --- a/roomsoverview.py +++ b/roomsoverview.py @@ -530,16 +530,52 @@ class RoomsOverviewPlugin(Plugin): ) await evt.respond(content) + async def _send_help(self, evt: MessageEvent) -> None: + """Uitgebreide Nederlandstalige handleiding voor !rooms.""" + help_text = ( + "**!rooms** — overzicht van de room-index\n\n" + "Toont alle rooms als kaarten met naam, ledental, omschrijving " + "en een directe join-link. Ruimtes met een eigen icoon tonen die " + "naast de naam.\n\n" + "**Basis:**\n" + "* `!rooms` — toon de volledige lijst (bij veel rooms verdeeld " + "over meerdere berichten)\n\n" + "**Subcommando's (moderator of hoger vereist):**\n" + "* `!rooms add ` — voeg een room toe aan de index\n" + "* `!rooms edit =` — wijzig een " + "veld van een room\n" + "* `!rooms remove ` — verwijder een room uit de index\n" + "* `!rooms refresh` — controleer of rooms nog bestaan en vul " + "ontbrekende avatars aan\n\n" + "**Veld bij edit:** `name` (naam), `topic` (omschrijving) of " + "`alias` (adres). Een lege waarde wist het veld.\n\n" + "**Voorbeelden:**\n" + "* `!rooms add #algemeen:mtux.nl`\n" + "* `!rooms edit #algemeen:mtux.nl name=Algemeen Overleg`\n" + "* `!rooms edit #algemeen:mtux.nl topic=` (wist de omschrijving)\n" + "* `!rooms remove #algemeen:mtux.nl`\n\n" + "Alleen moderators (macht ≥ 50) mogen rooms toevoegen, wijzigen " + "of verwijderen. Iedereen kan de lijst bekijken." + ) + await evt.respond(help_text, markdown=True) + @command.new( name="rooms", - help="Toon de room-index; subcommand: add, edit, remove, refresh", + help="Toon de room-index als kaarten; subcommands: add, edit, remove, refresh, help", require_subcommand=False, ) async def handle_rooms(self, evt: MessageEvent) -> None: await evt.mark_read() await self._send_list(evt) - @handle_rooms.subcommand(name="add", help="Voeg een room toe (moderator+)") + @handle_rooms.subcommand(name="help", help="Toon deze handleiding") + async def rooms_help(self, evt: MessageEvent) -> None: + await self._send_help(evt) + + @handle_rooms.subcommand( + name="add", + help="Voeg een room toe aan de index: !rooms add (moderator+)", + ) @command.argument("room", pass_raw=True, required=True) async def add_room(self, evt: MessageEvent, room: str) -> None: if not await self._is_mod(evt.room_id, evt.sender): @@ -585,7 +621,8 @@ class RoomsOverviewPlugin(Plugin): @handle_rooms.subcommand( name="edit", - help="Wijzig naam/topic/alias van een room (moderator+)", + help="Wijzig een veld van een room: !rooms edit " + "= (moderator+)", ) @command.argument("args", pass_raw=True, required=True) async def edit_room(self, evt: MessageEvent, args: str) -> None: @@ -635,7 +672,10 @@ class RoomsOverviewPlugin(Plugin): f"Room bijgewerkt: **{label}** — {field_name} is nu `{shown}`." ) - @handle_rooms.subcommand(name="remove", help="Verwijder een room (moderator+)") + @handle_rooms.subcommand( + name="remove", + help="Verwijder een room uit de index: !rooms remove (moderator+)", + ) @command.argument("room", pass_raw=True, required=True) async def remove_room(self, evt: MessageEvent, room: str) -> None: if not await self._is_mod(evt.room_id, evt.sender): @@ -653,7 +693,10 @@ class RoomsOverviewPlugin(Plugin): else: await evt.reply(f"Room `{room_id}` stond niet in de index.") - @handle_rooms.subcommand(name="refresh", help="Valideer de index en vul avatars aan (moderator+)") + @handle_rooms.subcommand( + name="refresh", + help="Controleer of rooms bestaan en vul ontbrekende avatars aan (moderator+)", + ) async def refresh_index(self, evt: MessageEvent) -> None: if not await self._is_mod(evt.room_id, evt.sender): await evt.reply("Geen permissie: moderator of hoger vereist.") From d6c9fd8004941626af2b99d2c600863ee7631454 Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Sun, 6 Sep 2026 14:34:04 +0200 Subject: [PATCH 6/6] Add !rooms over search: filter rooms by name/alias/topic (case-insensitive, all terms must match); v2.4.0 --- maubot.yaml | 2 +- roomsoverview.mbp | Bin 8762 -> 9148 bytes roomsoverview.py | 97 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/maubot.yaml b/maubot.yaml index b114188..a41b9dd 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 2.3.1 +version: 2.4.0 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index 35e6db815435335eca7fa6e391b6739910a333dc..1b34b3da6809baf0df29e146c5e62585a5218019 100644 GIT binary patch delta 8743 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 delta 8353 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 diff --git a/roomsoverview.py b/roomsoverview.py index 7f1d0bb..677cf8c 100644 --- a/roomsoverview.py +++ b/roomsoverview.py @@ -435,23 +435,16 @@ class RoomsOverviewPlugin(Plugin): ) return [dict(r) for r in rows] - async def _send_list(self, evt: MessageEvent) -> None: - rooms = await self._list_rooms() - if not rooms: - await evt.reply( - "De room-index is nog leeg. Moderators kunnen rooms toevoegen met " - "`!rooms add `." - ) - return + def _sort_rooms(self, rooms: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return sorted( + rooms, + key=lambda r: ( + r.get("display_name") or r.get("canonical_alias") or r["room_id"] + ).lower(), + ) - def sort_key(r: Dict[str, Any]) -> str: - return ( - r.get("display_name") - or r.get("canonical_alias") - or r["room_id"] - ).lower() - - rooms.sort(key=sort_key) + def _build_cards(self, rooms: List[Dict[str, Any]]) -> List[Tuple[str, str]]: + """Bouw per room een blockquote-kaart (html, plain).""" # Elke room wordt een blockquote-kaart: icoontje + naam/leden als kop, # omschrijving eronder, onderaan de join-link. De formatted_body bouwen @@ -510,13 +503,21 @@ class RoomsOverviewPlugin(Plugin): f"{e(address)}

" ) cards.append(("".join(body_parts), plain)) + return cards + async def _send_cards( + self, + evt: MessageEvent, + cards: List[Tuple[str, str]], + subject: str, + count_label: str, + ) -> None: chunk_size = max(1, int(self.config.get("rooms_per_message", 50) or 50)) chunks = [cards[i : i + chunk_size] for i in range(0, len(cards), chunk_size)] total = len(cards) for idx, chunk in enumerate(chunks, start=1): - header = f"Rooms overzicht ({total} rooms getoond, deel {idx}/{len(chunks)}):" + header = f"{subject} ({total} {count_label}, deel {idx}/{len(chunks)}):" html_body = ( f'

{html.escape(header)}

' + "".join(html_part for html_part, _ in chunk) @@ -530,6 +531,47 @@ class RoomsOverviewPlugin(Plugin): ) await evt.respond(content) + async def _send_list(self, evt: MessageEvent) -> None: + rooms = self._sort_rooms(await self._list_rooms()) + if not rooms: + await evt.reply( + "De room-index is nog leeg. Moderators kunnen rooms toevoegen met " + "`!rooms add `." + ) + return + await self._send_cards( + evt, self._build_cards(rooms), "Rooms overzicht", "rooms getoond" + ) + + async def _search_rooms(self, terms: str) -> List[Dict[str, Any]]: + """Zoek rooms: alle termen moeten matchen (case-insensitive, substring) + in naam, alias of omschrijving.""" + needles = [t for t in (part.lower() for part in terms.split()) if t] + if not needles: + return [] + found = [] + for r in await self._list_rooms(): + hay = " ".join( + str(r.get(field) or "").lower() + for field in ("display_name", "canonical_alias", "topic") + ) + if all(n in hay for n in needles): + found.append(r) + return self._sort_rooms(found) + + async def _send_search(self, evt: MessageEvent, query: str) -> None: + found = await self._search_rooms(query) + query_label = " ".join(query.split()) + if not found: + await evt.reply(f"Geen rooms gevonden voor `{query_label}`.") + return + await self._send_cards( + evt, + self._build_cards(found), + f"Zoekresultaten voor '{query_label}'", + "gevonden", + ) + async def _send_help(self, evt: MessageEvent) -> None: """Uitgebreide Nederlandstalige handleiding voor !rooms.""" help_text = ( @@ -539,7 +581,10 @@ class RoomsOverviewPlugin(Plugin): "naast de naam.\n\n" "**Basis:**\n" "* `!rooms` — toon de volledige lijst (bij veel rooms verdeeld " - "over meerdere berichten)\n\n" + "over meerdere berichten)\n" + "* `!rooms over ` — zoek rooms op naam, alias en/of " + "omschrijving (bijv. `!rooms over linux`); alle termen moeten " + "matchen\n\n" "**Subcommando's (moderator of hoger vereist):**\n" "* `!rooms add ` — voeg een room toe aan de index\n" "* `!rooms edit =` — wijzig een " @@ -561,7 +606,7 @@ class RoomsOverviewPlugin(Plugin): @command.new( name="rooms", - help="Toon de room-index als kaarten; subcommands: add, edit, remove, refresh, help", + help="Toon de room-index als kaarten; subcommands: over (zoeken), add, edit, remove, refresh, help", require_subcommand=False, ) async def handle_rooms(self, evt: MessageEvent) -> None: @@ -572,6 +617,20 @@ class RoomsOverviewPlugin(Plugin): async def rooms_help(self, evt: MessageEvent) -> None: await self._send_help(evt) + @handle_rooms.subcommand( + name="over", + help="Zoek rooms op naam, alias of omschrijving: !rooms over ", + ) + @command.argument("terms", pass_raw=True, required=True) + async def search_over(self, evt: MessageEvent, terms: str) -> None: + if not terms.strip(): + await evt.reply( + "Gebruik: `!rooms over ` (bijv. `!rooms over linux`).\n" + "Zoekt op naam, alias en omschrijving; alle termen moeten matchen." + ) + return + await self._send_search(evt, terms) + @handle_rooms.subcommand( name="add", help="Voeg een room toe aan de index: !rooms add (moderator+)",