From 1bfc35024cb3450d64d2e43f9d70b03209331c9b Mon Sep 17 00:00:00 2001 From: Eddy de Vink Date: Tue, 1 Sep 2026 18:35:44 +0200 Subject: [PATCH] Periodic background indexing via scheduler; !rooms reads cache; v1.0.8 --- base-config.yaml | 7 ++--- maubot.yaml | 2 +- roomsoverview.mbp | Bin 4289 -> 4571 bytes roomsoverview.py | 78 +++++++++++++++++++++++++++++----------------- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/base-config.yaml b/base-config.yaml index 7f77244..42c30ed 100644 --- a/base-config.yaml +++ b/base-config.yaml @@ -14,7 +14,6 @@ rooms_per_message: 50 # Voorkomt dat een enorme (gefedereerde) space-boom leidt tot duizenden berichten. max_rooms: 300 -# Hoe lang (minuten) de opgehaalde lijst wordt gecached in de plugin-database. -# Binnen deze periode gebruikt !rooms de cache i.p.v. de hele boom opnieuw op te -# halen (minder traffic naar de homeserver en federatie). 0 = caching uit. -cache_ttl_minutes: 15 \ No newline at end of file +# Hoe vaak (minuten) de bot de ruimte-hierarchy opnieuw indexeert op de +# achtergrond. !rooms toont altijd het laatste geindexeerde resultaat. +refresh_interval_minutes: 15 \ No newline at end of file diff --git a/maubot.yaml b/maubot.yaml index 15a4b0c..4c0c7d0 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: nl.roomsoverview -version: 1.0.7 +version: 1.0.8 license: MIT modules: - roomsoverview diff --git a/roomsoverview.mbp b/roomsoverview.mbp index 96b334194d36ef892308de81f31ba29ac78fd259..9900091d5e080d5b47cfc757d477067d5e8c7415 100644 GIT binary patch literal 4571 zcmZ{oXHXM>mW2~S@6wwT=}4~v(xtc1ODGWx5J5sGp$MT$lPX9r(i8$xlopU8Ac*v; z5Q_9(11jLo+nqQ2_U-QZb??kQGv~+sb3U*kh>!sQ0FVO2vv?e-TH}?{0RX@l005u> z007S(2f82v#8Ho*d0C}=BkxtGj5$IjVj~SZhq}-tW}id#Ti|5-rVIPlfAAk|$&2Df z!ZOJ$J1j-zH@(=>a4zQpRSdG<3fxx}YTsp)^}mv2K3;nu0>L^u)0#W0)DV59znvq{ z_bL>$N*WVuI~wYnGrMLli1hhkaEL-DN^x%je){d)1hf7cY~rJ007)T001oj0C0Ki4-<1mc)P*f|1-Wyh8p6BByE3iL}F4SpL%(_ zSR-;ftBh*ggs7cO@JzdGM2!HIu|#>ZuQC}#f~G}<9R(jDO(jYg-XNTvJvP*d&pkIm zUu?$-mFxr=Y|4X^jvsXv&c4i3KBOT=^m$bM^iKRL%nLUb9F@%v{SEAtIr301W(pN8 zlP5I?ELq8T7M;_g4;So4r=OYszP%iRXy zfQ&XTtW$WMirkVwPEDw1P$DMTUbPu#qFk^rM(t>%)nt}>)F7|I{9V-m4Q8+A-KeM+ z$$dx@FkN}4J!R_*2hH}?9kW3*`phWN(|!AWp@)}DG|*4(85b6K6RvjsL?6Gno5F3E ze;B$XmQXE1CBhRW$S7whUOKgG`s2W=O;4{hAZS<py-${@AvT+m};PTKP=Fu~F1e zx^Vbr_Q&9Mg?07-t;=aiJP;v-B-%-TTKB9zZyjnZH&uC{tU0t3L6Os1bNNyg(bAHD z8PN?nmKU`@N9Grb|31c++!dp%>bufajPCcFY-1~!=}hx1-SzR+V+)a7+CaOBOig+f z&JRt~ax`VNNUjKgjnbF}L(1CLjQx`!Pk|U7^lB zAvQKT9x^GS_uN8|36D+AHQZ0mHQaj9dJr&UEoWoBF5;z(ytYe&?f#|s7n=mjyzu~D zHwS!c_;s3r+4XE#e~3T7&6A>p+-zwRebp>*g<{GlMfFXZ^ry$a+NZC?mG) z4*LXL<2hBoQKF*6<=^DW{gjO0{H@Ioru#RJbY|b4yZQE7)-yX%MW7VIl*x6s>f>^b zbe@3s6_A|%^WR935;6x*-@+dv{njo*ax3ml>k%7On{V_Yj|SNumCO@djb*Vn1XQ-B z_jW5XbMWgjK+YI&51hu{M?Ug0At&6kV3z0qE$3Yx$9_|ApA&VSh0Tt-2qhF(9UB~i zrfCr(BHA8sQ5`&RI?wiFh@}N~xHcKdv~cu&^)1-4>1&!uOCBw9q)f>0do?U);W_Hv z$4XRNt_rG`EtO$9eUTg&!Je*QvsFJ@_nbF^^>(vGol2A9@;BWr&e5S6t!|}mD95{1 zuiIA~XG5aCWdt%d)6>}9QUivOK|mK!*A0rXYZu?)TLkOb{^_X;691dV-KaiZi6!de zWK3+$mzjN&OZ|7R%;Z}k**&$TXrPRxLOPZ))x3)~Y}DNnz?EY;k*l-!$x&Kk{^@WT zsfuN`bgb7b6spni(M8FktXIu}FFonqF7J58o`t9i57PG80Lsl=<1SnAPuJ zESkc*7yO_l&b9k1L3FW^e4Rx#EVldmRU$6-j^X*KD&S2m&%^lpd$%-?rMoX(T>Sed zVP7C>=zX6J1)T2L-f$v&J`*(@W_s` zPJ8N{IQT(P7PqtJ_fl^AI|H8+2Us$+38}5n^gi_c-f!(ustH0?QFLICIPa);RG%lI ztp4j4ySFpyY~AXxd&$BB3xm&Hbq8cRh;l@*VNghM;}etH?8@k~Ot?@-j_e)TEKlfoU4M;YAq>lvlUqOBa4S81IwTv|x> zM9IOgvN}&pL6L-*%_oiHbj;FRDl~aQT`31P5REot6 zIr=k%rFzyU3TiCrjq)h9TMWtcn=5qg4-okEL-_$YQDJ|s#zJp_)O2TzbHW;O$?E(W zEc?`EKd|t2o-GN|LSZr>Xch)n$3=}+@YCs7VR_Q19G!DCc7Er1=x^IcNu)Hs5-!bN zh|)M^kb=FAjj3Sla$vQq{47U$EzNt@Me?~Hw6Yi zjoMu28<)wzs@LY`Ju)5n8d)2A<}bh$Sue_AZs9g*x#%26DG?vT2i}3SOvxJlY3++g zgjUZM{5ej}^>eakZoM;_59pNK zqw7E6MK*UWsbB+iSj#Z z%kEZ(mas$89+NyV=ooaMl;MQ9VKn_UG0rj5VpgPG8ps{G|N8MD3;pd=H^hKeS?#-{ zBYI{h`)A)JP8ugjEOhR3P5n`4Jd7Ej2pApP;h4{N7CPhp^rzCDUq;14-d3q0M4f0~ z#JF95L@^HVdUaQ{f0LLn{!h<|$o%x=)MlUrB5z)vr%>yAG@j%`bP$#HuWZbM>+$!8 zN?Jm-R3>L@E6rNy8fK--N14v>f(9O?>#>ThV`50$B{%*TPS$um{D~3qy?6?*zeRk0 zXen5J6LRZ9Or`QhX#Y<^tK%+*!-1r0@;rv6I75)ef1@M`z1{96s`E{8+dgkjtF98Y zAa8D_Rw3n@K(WvOStTbXIyxSn=R#-wzO7&rIO&gkczD`A<0kfZc*ik@D(RG^;s;RWs;b9TWI7^aUZmL8XitDFQWE7_N~i#rCc7wfv0?{G9dCU z$9q+66p5o^S!tWbxsJ{3YQ)KJKApTUnyZ0BS9|8B-F|i?N37e6+F=MKN?`I`KBeZ= z7C7`vh(&cPcnLt~kHEUp5X?%pEvt0E?vgQmA_! zi1m^~v(YZfqNpYHN26z_aEg#~`xFP8K(u(PZ5vT)&`)dkZR6fy9FIw$H9FVP=A-_> z+jcl9G*K=Dq6*J0KfqZnoaQRO!jIYy*t<~edtb)ou*YmQhbWf9i@x`J-mQpJ%9VO! z0lT>bpY>W=W*Sgv4gF}xPI$e)CQDv?-0Ev|7KD@vQ+?XlThAGf#kPmdpq^W7b)C%> z$J_KhRlBO0(s6Tc?0VCm%`O3+pNYWabpKLLcB_;rjH2`&Y}+eBW-H(w8+gyyzM7r| zeNsj)XV_RlsL4juEH9687Zld_Di0I9l-vgYAu2m zrd_(y-$L`0*h!~*-oqdXe}^7U+*rDke1;}tN>OTB^iz?JTxl2~KQ7)wSrLsua`4S6 zwEMG+0sX|Vdy#_@Aa_0~XLPgR@|%lY3O9CEb$SakT*22jlTM}kLtQDpD2~UTOZDO8 z(-OcfW#6Z)Pmq@BG}pA^f=WP48<|j=K-|v@ek`OL#|MSH zkGPp41km~y*D_X6JZyX`eWV@1%y!G@<(*t>+-J>y$@0TfS$$Zst4G4;yYO3jf8KMsO&35l zKLqUMCL=$0Ot@(a{UgdKr>kJTa~$`~LCAPls`OT0{jP5AamiW3Y{943W;{fkd9kva zvfl;=VB&Ho+{8F(s}DpFac+Kbu(XigJN3^F?+5IjmI1em@KVTkx%V~z}18`^Ngp`S555dHT^1B&Xt!h&$ns}0$-rX zkkp5@I`t=Cz9VHj>;~bdFIbaxpJ26thXUew>8ab3hJ#cRd(H?H79;7u^-N5-LrGk< zCEt4v;`Rzbx%&6^kJ@_`8fb?+Kg=$P#wwy~1bUGb@q{LvVf=S%ui)~6Ys}HRLf+`{ zZLD3;h<>rpsWGfHp#kXa1y}_7n}%7~vm?K7U8doj_C~h6BLivXYjPP<4^MMH>I_?N zR*!sGzrwR8_rQ72zgxe3HJSdatdE26`8tc^BE~MjCPYg}mvCE}n7J{QD0r|^y1@{s zTptl|?u2C)n*a*o<4ui+VwEg5YsGvw3DN3>Zc2cj==WdHyG literal 4289 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 diff --git a/roomsoverview.py b/roomsoverview.py index 3dad5df..8d7c217 100644 --- a/roomsoverview.py +++ b/roomsoverview.py @@ -1,6 +1,7 @@ import asyncio import json import time +from functools import partial from typing import Any, Dict, List, Optional, Tuple from urllib.parse import quote @@ -58,12 +59,36 @@ class Config(BaseProxyConfig): helper.copy("max_depth") helper.copy("rooms_per_message") helper.copy("max_rooms") - helper.copy("cache_ttl_minutes") + helper.copy("refresh_interval_minutes") class RoomsOverviewPlugin(Plugin): async def start(self) -> None: self.config.load_and_update() + space_id = str(self.config.get("space_id", "") or "") + interval_minutes = max(1, int(self.config.get("refresh_interval_minutes", 15) or 15)) + if not space_id or space_id.startswith("!vervang-mij"): + return + self._refresh_lock = asyncio.Lock() + self._scheduled_tasks = [] + # Direct eenmalig indexeren (prewarm), daarna periodiek. + self._scheduled_tasks.append( + self.sched.run_later(2, self._refresh_cache(space_id)) + ) + self._scheduled_tasks.append( + self.sched.run_periodically( + interval_minutes * 60, + partial(self._refresh_cache, space_id), + run_task_in_background=True, + ) + ) + self.log.info( + f"start: indexering gepland elke {interval_minutes} minuten" + ) + + async def stop(self) -> None: + for task in getattr(self, "_scheduled_tasks", []): + task.cancel() @classmethod def get_config_class(cls): @@ -155,6 +180,19 @@ class RoomsOverviewPlugin(Plugin): break return rooms, True + async def _refresh_cache(self, space_id: str) -> None: + """Live ophalen en in de cache opslaan (gedeeld via lock, ook achtergrond).""" + async with self._refresh_lock: + entries, complete = await self._fetch_hierarchy(space_id) + rooms = [ + r + for r in entries + if r.get("room_id") != space_id + and (r.get("room_type") or "") != "m.space" + ] + await self._store_cache(space_id, rooms, complete) + self.log.info(f"indexering voltooid ({len(rooms)} rooms)") + async def _load_cache( self, space_id: str ) -> Optional[Tuple[List[Dict[str, Any]], bool, int]]: @@ -191,35 +229,19 @@ class RoomsOverviewPlugin(Plugin): await evt.mark_read() - ttl_minutes = max(0, int(self.config.get("cache_ttl_minutes", 15) or 15)) cached = await self._load_cache(space_id) - now = int(time.time()) - if cached and ttl_minutes > 0 and now - cached[2] < ttl_minutes * 60: - rooms, complete = cached[0], cached[1] - self.log.info( - f"rooms: cache hit voor {space_id} (age {now - cached[2]}s)" - ) - else: - entries, complete = await self._fetch_hierarchy(space_id) - rooms = [ - r - for r in entries - if r.get("room_id") != space_id - and (r.get("room_type") or "") != "m.space" - ] - await self._store_cache(space_id, rooms, complete) + if cached is None: + # Nog nooit geïndexeerd: vraag een indexering aan en wacht erop + # (lock deelt met de achtergrondtaak, dus geen dubbele fetches). + await self._refresh_cache(space_id) + cached = await self._load_cache(space_id) + rooms, complete = cached[0], cached[1] if cached else ([], True) if not rooms: - if not complete: - await evt.reply( - "De ruimte-hierarchy kon niet worden opgehaald. Controleer de " - "space_id en of de bot lid is van (of toegang heeft tot) de space." - ) - else: - await evt.reply( - "Geen rooms gevonden. Controleer of de space_id klopt en of de bot " - "lid is van (of toegang heeft tot) de space." - ) + await evt.reply( + "Er zijn (nog) geen rooms gevonden. Controleer de space_id en of de bot " + "lid is van (of toegang heeft tot) de space." + ) return def sort_key(r: Dict[str, Any]) -> str: @@ -248,5 +270,5 @@ class RoomsOverviewPlugin(Plugin): for idx, chunk in enumerate(chunks, start=1): header = f"**Rooms overzicht ({total} rooms getoond, deel {idx}/{len(chunks)}):**\n\n" if idx == 1 and not complete: - header += "*De lijst is mogelijk onvolledig: de ruimte-hierarchy kon niet volledig worden opgehaald.*\n\n" + header += "*De lijst is mogelijk onvolledig: de laatste indexering was niet compleet.*\n\n" await evt.respond(header + "\n".join(chunk), markdown=True) \ No newline at end of file