mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-11-04 08:32:21 +01:00
Fix clock filtering and contact lookup behavior bugs
This commit is contained in:
1 parent
2b681e1905
commit
00697e3c06
13 files changed
+133
-43
No files matched your search
+16
-8
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from meshcore import EventType
|
||||
|
||||
from app.models import Contact
|
||||
from app.models import CONTACT_TYPE_REPEATER, Contact
|
||||
from app.packet_processor import process_raw_packet
|
||||
from app.repository import ContactRepository, MessageRepository
|
||||
from app.websocket import broadcast_event
|
||||
@@ -71,11 +71,20 @@ async def on_contact_message(event: "Event") -> None:
|
||||
sender_pubkey = payload.get("public_key") or payload.get("pubkey_prefix", "")
|
||||
received_at = int(time.time())
|
||||
|
||||
# Look up full public key from contact database if we only have prefix
|
||||
if len(sender_pubkey) < 64:
|
||||
contact = await ContactRepository.get_by_key_prefix(sender_pubkey)
|
||||
if contact:
|
||||
sender_pubkey = contact.public_key
|
||||
# Look up contact from database - use prefix lookup only if needed
|
||||
# (get_by_key_or_prefix does exact match first, then prefix fallback)
|
||||
contact = await ContactRepository.get_by_key_or_prefix(sender_pubkey)
|
||||
if contact:
|
||||
sender_pubkey = contact.public_key
|
||||
|
||||
# Skip messages from repeaters - they only send CLI responses, not chat messages.
|
||||
# CLI responses are handled by the command endpoint and txt_type filter above.
|
||||
if contact.type == CONTACT_TYPE_REPEATER:
|
||||
logger.debug(
|
||||
"Skipping message from repeater %s (not stored in chat history)",
|
||||
sender_pubkey[:12],
|
||||
)
|
||||
return
|
||||
|
||||
# Try to create message - INSERT OR IGNORE handles duplicates atomically
|
||||
# If the packet processor already stored this message, this returns None
|
||||
@@ -121,8 +130,7 @@ async def on_contact_message(event: "Event") -> None:
|
||||
},
|
||||
)
|
||||
|
||||
# Update contact last_contacted
|
||||
contact = await ContactRepository.get_by_key_prefix(sender_pubkey)
|
||||
# Update contact last_contacted (contact was already fetched above)
|
||||
if contact:
|
||||
await ContactRepository.update_last_contacted(contact.public_key, received_at)
|
||||
|
||||
|
||||
@@ -204,6 +204,9 @@ class TelemetryResponse(BaseModel):
|
||||
default_factory=list, description="List of neighbors seen by repeater"
|
||||
)
|
||||
acl: list[AclEntry] = Field(default_factory=list, description="Access control list")
|
||||
clock_output: str | None = Field(
|
||||
default=None, description="Output from 'clock' command (or error message)"
|
||||
)
|
||||
|
||||
|
||||
class CommandRequest(BaseModel):
|
||||
|
||||
@@ -212,13 +212,14 @@ async def create_dm_message_from_decrypted(
|
||||
|
||||
Returns the message ID if created, None if duplicate.
|
||||
"""
|
||||
# Extract txt_type from flags (lower 4 bits)
|
||||
# txt_type=1 is CLI response - don't store these in chat history
|
||||
txt_type = decrypted.flags & 0x0F
|
||||
if txt_type == 1:
|
||||
# Check if sender is a repeater - repeaters only send CLI responses, not chat messages.
|
||||
# CLI responses are handled by the command endpoint, not stored in chat history.
|
||||
contact = await ContactRepository.get_by_key_or_prefix(their_public_key)
|
||||
if contact and contact.type == CONTACT_TYPE_REPEATER:
|
||||
logger.debug(
|
||||
"Skipping CLI response from %s (txt_type=1)",
|
||||
"Skipping message from repeater %s (CLI responses not stored): %s",
|
||||
their_public_key[:12],
|
||||
(decrypted.message or "")[:50],
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -361,8 +361,49 @@ async def request_telemetry(public_key: str, request: TelemetryRequest) -> Telem
|
||||
)
|
||||
)
|
||||
|
||||
# Fetch clock output (up to 2 attempts)
|
||||
# Must pause polling and stop auto-fetch to prevent race condition where
|
||||
# the CLI response is consumed before we can call get_msg()
|
||||
logger.info("Fetching clock from repeater %s", contact.public_key[:12])
|
||||
clock_output: str | None = None
|
||||
|
||||
async with pause_polling():
|
||||
await mc.stop_auto_message_fetching()
|
||||
try:
|
||||
for attempt in range(1, 3):
|
||||
logger.debug("Clock request attempt %d/2", attempt)
|
||||
try:
|
||||
send_result = await mc.commands.send_cmd(contact.public_key, "clock")
|
||||
if send_result.type == EventType.ERROR:
|
||||
logger.debug("Clock command send error: %s", send_result.payload)
|
||||
continue
|
||||
|
||||
# Wait for response
|
||||
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=5.0)
|
||||
if wait_result is None:
|
||||
logger.debug("Clock request timeout, retrying...")
|
||||
continue
|
||||
|
||||
response_event = await mc.commands.get_msg()
|
||||
if response_event.type == EventType.ERROR:
|
||||
logger.debug("Clock get_msg error: %s", response_event.payload)
|
||||
continue
|
||||
|
||||
clock_output = response_event.payload.get("text", "")
|
||||
logger.info("Received clock output: %s", clock_output)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug("Clock request exception: %s", e)
|
||||
continue
|
||||
finally:
|
||||
await mc.start_auto_message_fetching()
|
||||
|
||||
if clock_output is None:
|
||||
clock_output = "Unable to fetch `clock` output (repeater did not respond)"
|
||||
|
||||
# Convert raw telemetry to response format
|
||||
# bat is in mV, convert to V (e.g., 3775 -> 3.775)
|
||||
|
||||
return TelemetryResponse(
|
||||
pubkey_prefix=status.get("pubkey_pre", contact.public_key[:12]),
|
||||
battery_volts=status.get("bat", 0) / 1000.0,
|
||||
@@ -384,6 +425,7 @@ async def request_telemetry(public_key: str, request: TelemetryRequest) -> Telem
|
||||
full_events=status.get("full_evts", 0),
|
||||
neighbors=neighbors,
|
||||
acl=acl_entries,
|
||||
clock_output=clock_output,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ var Ey=Object.defineProperty;var ky=(n,s,i)=>s in n?Ey(n,s,{enumerable:!0,config
|
||||
`).replace(ee,"")}function he(e,r,a){if(r=le(r),le(e)!==r&&a)throw Error(i(425))}function be(){}var Me=null,et=null;function ht(e,r){return e==="textarea"||e==="noscript"||typeof r.children=="string"||typeof r.children=="number"||typeof r.dangerouslySetInnerHTML=="object"&&r.dangerouslySetInnerHTML!==null&&r.dangerouslySetInnerHTML.__html!=null}var dn=typeof setTimeout=="function"?setTimeout:void 0,ln=typeof clearTimeout=="function"?clearTimeout:void 0,On=typeof Promise=="function"?Promise:void 0,Tn=typeof queueMicrotask=="function"?queueMicrotask:typeof On<"u"?function(e){return On.resolve(null).then(e).catch(eo)}:dn;function eo(e){setTimeout(function(){throw e})}function za(e,r){var a=r,p=0;do{var y=a.nextSibling;if(e.removeChild(a),y&&y.nodeType===8)if(a=y.data,a==="/$"){if(p===0){e.removeChild(y),rr(r);return}p--}else a!=="$"&&a!=="$?"&&a!=="$!"||p++;a=y}while(a);rr(r)}function Jr(e){for(;e!=null;e=e.nextSibling){var r=e.nodeType;if(r===1||r===3)break;if(r===8){if(r=e.data,r==="$"||r==="$!"||r==="$?")break;if(r==="/$")return null}}return e}function ou(e){e=e.previousSibling;for(var r=0;e;){if(e.nodeType===8){var a=e.data;if(a==="$"||a==="$!"||a==="$?"){if(r===0)return e;r--}else a==="/$"&&r++}e=e.previousSibling}return null}var vi=Math.random().toString(36).slice(2),yr="__reactFiber$"+vi,to="__reactProps$"+vi,Ft="__reactContainer$"+vi,no="__reactEvents$"+vi,Gd="__reactListeners$"+vi,$d="__reactHandles$"+vi;function Ai(e){var r=e[yr];if(r)return r;for(var a=e.parentNode;a;){if(r=a[Ft]||a[yr]){if(a=r.alternate,r.child!==null||a!==null&&a.child!==null)for(e=ou(e);e!==null;){if(a=e[yr])return a;e=ou(e)}return r}e=a,a=e.parentNode}return null}function yi(e){return e=e[yr]||e[Ft],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function _i(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(i(33))}function Ls(e){return e[to]||null}var Kd=[],Ms=-1;function ro(e){return{current:e}}function qt(e){0>Ms||(e.current=Kd[Ms],Kd[Ms]=null,Ms--)}function Zt(e,r){Ms++,Kd[Ms]=e.current,e.current=r}var io={},Dn=ro(io),qn=ro(!1),Io=io;function Is(e,r){var a=e.type.contextTypes;if(!a)return io;var p=e.stateNode;if(p&&p.__reactInternalMemoizedUnmaskedChildContext===r)return p.__reactInternalMemoizedMaskedChildContext;var y={},S;for(S in a)y[S]=r[S];return p&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=r,e.__reactInternalMemoizedMaskedChildContext=y),y}function Gn(e){return e=e.childContextTypes,e!=null}function su(){qt(qn),qt(Dn)}function Ah(e,r,a){if(Dn.current!==io)throw Error(i(168));Zt(Dn,r),Zt(qn,a)}function yh(e,r,a){var p=e.stateNode;if(r=r.childContextTypes,typeof p.getChildContext!="function")return a;p=p.getChildContext();for(var y in p)if(!(y in r))throw Error(i(108,Ae(e)||"Unknown",y));return Z({},a,p)}function au(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||io,Io=Dn.current,Zt(Dn,e),Zt(qn,qn.current),!0}function _h(e,r,a){var p=e.stateNode;if(!p)throw Error(i(169));a?(e=yh(e,r,Io),p.__reactInternalMemoizedMergedChildContext=e,qt(qn),qt(Dn),Zt(Dn,e)):qt(qn),Zt(qn,a)}var wi=null,lu=!1,Qd=!1;function wh(e){wi===null?wi=[e]:wi.push(e)}function ZA(e){lu=!0,wh(e)}function oo(){if(!Qd&&wi!==null){Qd=!0;var e=0,r=Ct;try{var a=wi;for(Ct=1;e<a.length;e++){var p=a[e];do p=p(!0);while(p!==null)}wi=null,lu=!1}catch(y){throw wi!==null&&(wi=wi.slice(e+1)),Ll(ma,oo),y}finally{Ct=r,Qd=!1}}return null}var Os=[],js=0,uu=null,cu=0,_r=[],wr=0,Oo=null,bi=1,Ci="";function jo(e,r){Os[js++]=cu,Os[js++]=uu,uu=e,cu=r}function bh(e,r,a){_r[wr++]=bi,_r[wr++]=Ci,_r[wr++]=Oo,Oo=e;var p=bi;e=Ci;var y=32-ct(p)-1;p&=~(1<<y),a+=1;var S=32-ct(r)+y;if(30<S){var z=y-y%5;S=(p&(1<<z)-1).toString(32),p>>=z,y-=z,bi=1<<32-ct(r)+y|a<<y|p,Ci=S+e}else bi=1<<S|a<<y|p,Ci=e}function Yd(e){e.return!==null&&(jo(e,1),bh(e,1,0))}function Xd(e){for(;e===uu;)uu=Os[--js],Os[js]=null,cu=Os[--js],Os[js]=null;for(;e===Oo;)Oo=_r[--wr],_r[wr]=null,Ci=_r[--wr],_r[wr]=null,bi=_r[--wr],_r[wr]=null}var lr=null,ur=null,Kt=!1,Lr=null;function Ch(e,r){var a=kr(5,null,null,0);a.elementType="DELETED",a.stateNode=r,a.return=e,r=e.deletions,r===null?(e.deletions=[a],e.flags|=16):r.push(a)}function Eh(e,r){switch(e.tag){case 5:var a=e.type;return r=r.nodeType!==1||a.toLowerCase()!==r.nodeName.toLowerCase()?null:r,r!==null?(e.stateNode=r,lr=e,ur=Jr(r.firstChild),!0):!1;case 6:return r=e.pendingProps===""||r.nodeType!==3?null:r,r!==null?(e.stateNode=r,lr=e,ur=null,!0):!1;case 13:return r=r.nodeType!==8?null:r,r!==null?(a=Oo!==null?{id:bi,overflow:Ci}:null,e.memoizedState={dehydrated:r,treeContext:a,retryLane:1073741824},a=kr(18,null,null,0),a.stateNode=r,a.return=e,e.child=a,lr=e,ur=null,!0):!1;default:return!1}}function Jd(e){return(e.mode&1)!==0&&(e.flags&128)===0}function ef(e){if(Kt){var r=ur;if(r){var a=r;if(!Eh(e,r)){if(Jd(e))throw Error(i(418));r=Jr(a.nextSibling);var p=lr;r&&Eh(e,r)?Ch(p,a):(e.flags=e.flags&-4097|2,Kt=!1,lr=e)}}else{if(Jd(e))throw Error(i(418));e.flags=e.flags&-4097|2,Kt=!1,lr=e}}}function kh(e){for(e=e.
Line truncated
|
||||
Error generating stack: `+S.message+`
|
||||
`+S.stack}return{value:e,source:r,stack:y,digest:null}}function bf(e,r,a){return{value:e,source:null,stack:a??null,digest:r??null}}function Cf(e,r){try{console.error(r.value)}catch(a){setTimeout(function(){throw a})}}var JA=typeof WeakMap=="function"?WeakMap:Map;function sp(e,r,a){a=ki(-1,a),a.tag=3,a.payload={element:null};var p=r.value;return a.callback=function(){Tu||(Tu=!0,jf=p),Cf(e,r)},a}function ap(e,r,a){a=ki(-1,a),a.tag=3;var p=e.type.getDerivedStateFromError;if(typeof p=="function"){var y=r.value;a.payload=function(){return p(y)},a.callback=function(){Cf(e,r)}}var S=e.stateNode;return S!==null&&typeof S.componentDidCatch=="function"&&(a.callback=function(){Cf(e,r),typeof p!="function"&&(uo===null?uo=new Set([this]):uo.add(this));var z=r.stack;this.componentDidCatch(r.value,{componentStack:z!==null?z:""})}),a}function lp(e,r,a){var p=e.pingCache;if(p===null){p=e.pingCache=new JA;var y=new Set;p.set(r,y)}else y=p.get(r),y===void 0&&(y=new Set,p.set(r,y));y.has(a)||(y.add(a),e=hy.bind(null,e,r,a),r.then(e,e))}function up(e){do{var r;if((r=e.tag===13)&&(r=e.memoizedState,r=r!==null?r.dehydrated!==null:!0),r)return e;e=e.return}while(e!==null);return null}function cp(e,r,a,p,y){return(e.mode&1)===0?(e===r?e.flags|=65536:(e.flags|=128,a.flags|=131072,a.flags&=-52805,a.tag===1&&(a.alternate===null?a.tag=17:(r=ki(-1,1),r.tag=2,ao(a,r,1))),a.lanes|=1),e):(e.flags|=65536,e.lanes=y,e)}var ey=T.ReactCurrentOwner,$n=!1;function jn(e,r,a,p){r.child=e===null?Th(r,null,a,p):Hs(r,e.child,a,p)}function dp(e,r,a,p,y){a=a.render;var S=r.ref;return Us(r,y),p=mf(e,r,a,p,S,y),a=gf(),e!==null&&!$n?(r.updateQueue=e.updateQueue,r.flags&=-2053,e.lanes&=~y,Si(e,r,y)):(Kt&&a&&Yd(r),r.flags|=1,jn(e,r,p,y),r.child)}function fp(e,r,a,p,y){if(e===null){var S=a.type;return typeof S=="function"&&!qf(S)&&S.defaultProps===void 0&&a.compare===null&&a.defaultProps===void 0?(r.tag=15,r.type=S,hp(e,r,S,p,y)):(e=Mu(a.type,null,p,r,r.mode,y),e.ref=r.ref,e.return=r,r.child=e)}if(S=e.child,(e.lanes&y)===0){var z=S.memoizedProps;if(a=a.compare,a=a!==null?a:Qr,a(z,p)&&e.ref===r.ref)return Si(e,r,y)}return r.flags|=1,e=po(S,p),e.ref=r.ref,e.return=r,r.child=e}function hp(e,r,a,p,y){if(e!==null){var S=e.memoizedProps;if(Qr(S,p)&&e.ref===r.ref)if($n=!1,r.pendingProps=p=S,(e.lanes&y)!==0)(e.flags&131072)!==0&&($n=!0);else return r.lanes=e.lanes,Si(e,r,y)}return Ef(e,r,a,p,y)}function pp(e,r,a){var p=r.pendingProps,y=p.children,S=e!==null?e.memoizedState:null;if(p.mode==="hidden")if((r.mode&1)===0)r.memoizedState={baseLanes:0,cachePool:null,transitions:null},Zt(Gs,cr),cr|=a;else{if((a&1073741824)===0)return e=S!==null?S.baseLanes|a:a,r.lanes=r.childLanes=1073741824,r.memoizedState={baseLanes:e,cachePool:null,transitions:null},r.updateQueue=null,Zt(Gs,cr),cr|=e,null;r.memoizedState={baseLanes:0,cachePool:null,transitions:null},p=S!==null?S.baseLanes:a,Zt(Gs,cr),cr|=p}else S!==null?(p=S.baseLanes|a,r.memoizedState=null):p=a,Zt(Gs,cr),cr|=p;return jn(e,r,y,a),r.child}function mp(e,r){var a=r.ref;(e===null&&a!==null||e!==null&&e.ref!==a)&&(r.flags|=512,r.flags|=2097152)}function Ef(e,r,a,p,y){var S=Gn(a)?Io:Dn.current;return S=Is(r,S),Us(r,y),a=mf(e,r,a,p,S,y),p=gf(),e!==null&&!$n?(r.updateQueue=e.updateQueue,r.flags&=-2053,e.lanes&=~y,Si(e,r,y)):(Kt&&p&&Yd(r),r.flags|=1,jn(e,r,a,y),r.child)}function gp(e,r,a,p,y){if(Gn(a)){var S=!0;au(r)}else S=!1;if(Us(r,y),r.stateNode===null)Eu(e,r),ip(r,a,p),wf(r,a,p,y),p=!0;else if(e===null){var z=r.stateNode,X=r.memoizedProps;z.props=X;var ne=z.context,me=a.contextType;typeof me=="object"&&me!==null?me=br(me):(me=Gn(a)?Io:Dn.current,me=Is(r,me));var Se=a.getDerivedStateFromProps,Te=typeof Se=="function"||typeof z.getSnapshotBeforeUpdate=="function";Te||typeof z.UNSAFE_componentWillReceiveProps!="function"&&typeof z.componentWillReceiveProps!="function"||(X!==p||ne!==me)&&op(r,z,p,me),so=!1;var ke=r.memoizedState;z.state=ke,gu(r,p,z,y),ne=r.memoizedState,X!==p||ke!==ne||qn.current||so?(typeof Se=="function"&&(_f(r,a,Se,p),ne=r.memoizedState),(X=so||rp(r,a,X,p,ke,ne,me))?(Te||typeof z.UNSAFE_componentWillMount!="function"&&typeof z.componentWillMount!="function"||(typeof z.componentWillMount=="function"&&z.componentWillMount(),typeof z.UNSAFE_componentWillMount=="function"&&z.UNSAFE_componentWillMount()),typeof z.componentDidMount=="function"&&(r.flags|=4194308)):(typeof z.componentDidMount=="function"&&(r.flags|=4194308),r.memoizedProps=p,r.memoizedState=ne),z.props=p,z.state=ne,z.context=me,p=X):(typeof z.componentDidMount=="function"&&(r.flags|=4194308),p=!1)}else{z=r.stateNode,Rh(e,r),X=r.memoizedProps,me=r.type===r.elementType?X:Mr(r.type,X),z.props=me,Te=r.pendingProps,ke=z.context,ne=a.contextType,typeof ne=="object"&&ne!==null?ne=br(ne):(ne=Gn(a)?Io:Dn.current,ne=Is(r,ne));var Ie=a.getDerivedStateFromProps;(Se=typeof Ie=="function"||typeof z.getSnapshotBeforeUpdate=="function")||typeof z.UNSAFE_componentWillReceiveProps!="function"&&typeof z.componentWillReceiveProps!="function"||(X!==Te||ke!==ne)&&op(r,z,
Line truncated
|
||||
`)}function Qs(n,s,i){const l=Math.floor(Date.now()/1e3);return{id:-Date.now(),type:"PRIV",conversation_key:n,text:s,sender_timestamp:l,received_at:l,paths:null,txt_type:0,signature:null,outgoing:i,acked:1}}function Vy(n){const s=N.useRef(null),i=N.useRef(null),l=N.useCallback(()=>{i.current!==null&&(clearInterval(i.current),i.current=null),s.current=null},[]),u=N.useCallback(async()=>{const h=s.current;if(!h)return;let d=null,g=null;for(let m=0;m<2;m++)try{d=await ut.requestTelemetry(h.conversationId,"");break}catch(x){g=x instanceof Error?x:new Error("Unknown error"),m===0&&await new Promise(v=>setTimeout(v,2e3))}if(d){const m=Qs(h.conversationId,Hy(h,d),!1);n(x=>[...x,m])}else{const m=Qs(h.conversationId,`Duty cycle poll failed after retry: ${(g==null?void 0:g.message)??"Unknown error"}`,!1);n(x=>[...x,m])}},[n]),c=N.useCallback(async(h,d)=>{const g=h.trim().toLowerCase();if(g==="dutycycle_start"){l();let m=null,x=null;for(let P=0;P<2;P++)try{m=await ut.requestTelemetry(d,"");break}catch(M){x=M instanceof Error?M:new Error("Unknown error"),P===0&&await new Promise(k=>setTimeout(k,2e3))}if(!m){const P=Qs(d,`Failed to start duty cycle tracking after retry: ${(x==null?void 0:x.message)??"Unknown error"}`,!1);return n(M=>[...M,P]),!0}const v=Math.floor(Date.now()/1e3);s.current={startTime:v,uptime:m.uptime_seconds,txAirtime:m.airtime_seconds,rxAirtime:m.rx_airtime_seconds,sentFlood:m.sent_flood,sentDirect:m.sent_direct,recvFlood:m.recv_flood,recvDirect:m.recv_direct,conversationId:d};const C=Qs(d,`Airtime/duty cycle statistics gathering begins at ${v}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return n(P=>[...P,C]),i.current=window.setInterval(u,jy),!0}if(g==="dutycycle_stop"){if(s.current&&s.current.conversationId===d){await u(),l();const m=Qs(d,"Airtime/duty cycle statistics gathering stopped.",!1);n(x=>[...x,m])}else{const m=Qs(d,"Duty cycle tracking is not active.",!1);n(x=>[...x,m])}return!0}return!1},[n,l,u]);return N.useEffect(()=>()=>{i.current!==null&&clearInterval(i.current)},[]),{handleAirtimeCommand:c,stopTracking:l}}function oc(n){if(n<60)return`${n}s`;const s=Math.floor(n/86400),i=Math.floor(n%86400/3600),l=Math.floor(n%3600/60);return s>0?i>0&&l>0?`${s}d${i}h${l}m`:i>0?`${s}d${i}h`:l>0?`${s}d${l}m`:`${s}d`:i>0?l>0?`${i}h${l}m`:`${i}h`:`${l}m`}function Uy(n){return["Telemetry",`Battery Voltage: ${n.battery_volts.toFixed(3)}V`,`Uptime: ${oc(n.uptime_seconds)}`,`TX Airtime: ${oc(n.airtime_seconds)}`,`RX Airtime: ${oc(n.rx_airtime_seconds)}`,"",`Noise Floor: ${n.noise_floor_dbm} dBm`,`Last RSSI: ${n.last_rssi_dbm} dBm`,`Last SNR: ${n.last_snr_db.toFixed(1)} dB`,"",`Packets: ${n.packets_received.toLocaleString()} rx / ${n.packets_sent.toLocaleString()} tx`,`Flood: ${n.recv_flood.toLocaleString()} rx / ${n.sent_flood.toLocaleString()} tx`,`Direct: ${n.recv_direct.toLocaleString()} rx / ${n.sent_direct.toLocaleString()} tx`,`Duplicates: ${n.flood_dups.toLocaleString()} flood / ${n.direct_dups.toLocaleString()} direct`,"",`TX Queue: ${n.tx_queue_len}`,`Debug Flags: ${n.full_events}`].join(`
|
||||
`)}function Qs(n,s,i){const l=Math.floor(Date.now()/1e3);return{id:-Date.now(),type:"PRIV",conversation_key:n,text:s,sender_timestamp:l,received_at:l,paths:null,txt_type:0,signature:null,outgoing:i,acked:1}}function Vy(n){const s=N.useRef(null),i=N.useRef(null),l=N.useCallback(()=>{i.current!==null&&(clearInterval(i.current),i.current=null),s.current=null},[]),u=N.useCallback(async()=>{const h=s.current;if(!h)return;let d=null,g=null;for(let m=0;m<2;m++)try{d=await ut.requestTelemetry(h.conversationId,"");break}catch(x){g=x instanceof Error?x:new Error("Unknown error"),m===0&&await new Promise(v=>setTimeout(v,2e3))}if(d){const m=Qs(h.conversationId,Hy(h,d),!1);n(x=>[...x,m])}else{const m=Qs(h.conversationId,`Duty cycle poll failed after retry: ${(g==null?void 0:g.message)??"Unknown error"}`,!1);n(x=>[...x,m])}},[n]),c=N.useCallback(async(h,d)=>{const g=h.trim().toLowerCase();if(g==="dutycycle_start"){l();let m=null,x=null;for(let P=0;P<2;P++)try{m=await ut.requestTelemetry(d,"");break}catch(M){x=M instanceof Error?M:new Error("Unknown error"),P===0&&await new Promise(k=>setTimeout(k,2e3))}if(!m){const P=Qs(d,`Failed to start duty cycle tracking after retry: ${(x==null?void 0:x.message)??"Unknown error"}`,!1);return n(M=>[...M,P]),!0}const v=Math.floor(Date.now()/1e3);s.current={startTime:v,uptime:m.uptime_seconds,txAirtime:m.airtime_seconds,rxAirtime:m.rx_airtime_seconds,sentFlood:m.sent_flood,sentDirect:m.sent_direct,recvFlood:m.recv_flood,recvDirect:m.recv_direct,conversationId:d};const C=Qs(d,`Airtime/duty cycle statistics gathering begins at ${v}. Logs will follow every 5 minutes. To stop, run dutycycle_stop or navigate away from this conversation.`,!1);return n(P=>[...P,C]),i.current=window.setInterval(u,jy),!0}if(g==="dutycycle_stop"){if(s.current&&s.current.conversationId===d){await u(),l();const m=Qs(d,"Airtime/duty cycle statistics gathering stopped.",!1);n(x=>[...x,m])}else{const m=Qs(d,"Duty cycle tracking is not active.",!1);n(x=>[...x,m])}return!0}return!1},[n,l,u]);return N.useEffect(()=>()=>{i.current!==null&&clearInterval(i.current)},[]),{handleAirtimeCommand:c,stopTracking:l}}function oc(n){if(n<60)return`${n}s`;const s=Math.floor(n/86400),i=Math.floor(n%86400/3600),l=Math.floor(n%3600/60);return s>0?i>0&&l>0?`${s}d${i}h${l}m`:i>0?`${s}d${i}h`:l>0?`${s}d${l}m`:`${s}d`:i>0?l>0?`${i}h${l}m`:`${i}h`:`${l}m`}function Uy(n){return["Telemetry",`Battery Voltage: ${n.battery_volts.toFixed(3)}V`,`Uptime: ${oc(n.uptime_seconds)}`,...n.clock_output?[`Clock: ${n.clock_output}`]:[],`TX Airtime: ${oc(n.airtime_seconds)}`,`RX Airtime: ${oc(n.rx_airtime_seconds)}`,"",`Noise Floor: ${n.noise_floor_dbm} dBm`,`Last RSSI: ${n.last_rssi_dbm} dBm`,`Last SNR: ${n.last_snr_db.toFixed(1)} dB`,"",`Packets: ${n.packets_received.toLocaleString()} rx / ${n.packets_sent.toLocaleString()} tx`,`Flood: ${n.recv_flood.toLocaleString()} rx / ${n.sent_flood.toLocaleString()} tx`,`Direct: ${n.recv_direct.toLocaleString()} rx / ${n.sent_direct.toLocaleString()} tx`,`Duplicates: ${n.flood_dups.toLocaleString()} flood / ${n.direct_dups.toLocaleString()} direct`,"",`TX Queue: ${n.tx_queue_len}`,`Debug Flags: ${n.full_events}`].join(`
|
||||
`)}function Zy(n){if(n.length===0)return`Neighbors
|
||||
No neighbors reported`;const s=[...n].sort((l,u)=>u.snr-l.snr),i=[`Neighbors (${s.length})`];for(const l of s){const u=l.name||l.pubkey_prefix,c=l.snr>=0?`+${l.snr.toFixed(1)}`:l.snr.toFixed(1);i.push(`${u}, ${c} dB [${oc(l.last_heard_seconds)} ago]`)}return i.join(`
|
||||
`)}function Wy(n){if(n.length===0)return`ACL
|
||||
@@ -599,4 +599,4 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
`),this.noSleepTimer=window.setInterval(()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))},15e3),this.enabled=!0,Promise.resolve()):this.noSleepVideo.play().then(d=>(this.enabled=!0,d)).catch(d=>{throw this.enabled=!1,d})}disable(){l()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):i()?this.noSleepTimer&&(console.warn(`
|
||||
NoSleep now disabled for older iOS devices.
|
||||
`),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}return _0=u,_0}var CS=bS();const ES=ls(CS);function kS(n){if(n.length<4)return null;try{const i=parseInt(n.slice(0,2),16)&3;let l=2;if(i===0||i===3){if(n.length<l+8)return null;l+=8}if(n.length<l+2)return null;const u=parseInt(n.slice(l,l+2),16);l+=2;const c=u*2;return n.length<l+c?null:(l+=c,n.slice(l))}catch{return null}}function SS({packets:n,channels:s,onChannelCreate:i,onRunningChange:l,visible:u=!1}){const[c,h]=N.useState(!1),[d,g]=N.useState(6),[m,x]=N.useState(!1),[v,C]=N.useState(!0),[P,M]=N.useState(!1),[k,w]=N.useState(null),[B,b]=N.useState(new Map),[E,T]=N.useState([]),[F,I]=N.useState(!1),[j,O]=N.useState(null),[G,U]=N.useState(null),[K,J]=N.useState(0),Y=N.useRef(null),ie=N.useRef(null),ge=N.useRef(!1),$=N.useRef(!1),se=N.useRef(!1),W=N.useRef(new Map),te=N.useRef(!1),Z=N.useRef(6),D=N.useRef(!0),H=N.useRef(!1),q=N.useRef(new Set),Q=N.useRef(new Set);N.useEffect(()=>{const Ee=new _S;Y.current=Ee,O(Ee.isGpuAvailable());const je=new ES;return ie.current=je,()=>{Ee.destroy(),Y.current=null,je.disable(),ie.current=null}},[]),N.useEffect(()=>{!u||F||oS(async()=>{const{ENGLISH_WORDLIST:Ee}=await import("./wordlist-BtmChKSf.js");return{ENGLISH_WORDLIST:Ee}},[]).then(({ENGLISH_WORDLIST:Ee})=>{Y.current&&(Y.current.setWordlist(Ee),I(!0))}).catch(Ee=>{console.error("Failed to load wordlist:",Ee),At.error("Failed to load wordlist",{description:"Cracking will not be available"})})},[u,F]),N.useEffect(()=>{const Ee=()=>{ut.getUndecryptedPacketCount().then(({count:at})=>U(at)).catch(()=>U(null))};Ee();const je=setInterval(Ee,36e5);return()=>clearInterval(je)},[]);const ae=N.useMemo(()=>new Set(s.map(Ee=>Ee.key.toUpperCase())),[s]),ce=n.filter(Ee=>Ee.payload_type==="GROUP_TEXT"&&!Ee.decrypted);N.useEffect(()=>{let Ee=0;b(je=>{const at=new Map(je);let mt=!1;for(const ot of ce)if(!at.has(ot.id)){const qe=kS(ot.data);if(qe&&Q.current.has(qe)){Ee++;continue}qe&&Q.current.add(qe),at.set(ot.id,{packet:ot,attempts:0,lastAttemptLength:0,status:"pending"}),mt=!0}return mt?(W.current=at,at):je}),Ee>0&&J(je=>je+Ee)},[ce.length]),N.useEffect(()=>{W.current=B},[B]),N.useEffect(()=>{te.current=m},[m]),N.useEffect(()=>{Z.current=d},[d]),N.useEffect(()=>{D.current=v},[v]),N.useEffect(()=>{H.current=P},[P]),N.useEffect(()=>{q.current=new Set(ce.map(Ee=>Ee.id))},[ce]),N.useEffect(()=>{l==null||l(c)},[c,l]);const Ae=Array.from(B.values()).filter(Ee=>Ee.status==="pending").length,pe=Array.from(B.values()).filter(Ee=>Ee.status==="cracked").length,xe=Array.from(B.values()).filter(Ee=>Ee.status==="failed").length,Be=N.useCallback(async()=>{if(se.current||!Y.current||!ge.current)return;const Ee=W.current;let je=null,at=null;for(const[Ke,Qe]of Ee.entries())if(Qe.status==="pending"){je=Qe,at=Ke;break}if(!je&&te.current){const Ke=Array.from(Ee.entries()).filter(([,Qe])=>Qe.status==="failed"&&Qe.lastAttemptLength<10);Ke.length>0&&(Ke.sort((Qe,rt)=>Qe[1].lastAttemptLength-rt[1].lastAttemptLength),[at,je]=Ke[0])}if(!je||at===null){ge.current&&setTimeout(()=>Be(),1e3);return}if(!q.current.has(at)){b(Ke=>{const Qe=new Map(Ke);return Qe.delete(at),Qe}),ge.current&&setTimeout(()=>Be(),10);return}se.current=!0;const mt=Z.current,ot=je.lastAttemptLength>0,qe=ot?je.lastAttemptLength+1:mt;try{const Ke=await Y.current.crack(je.packet.data,{maxLength:qe,useSenderFilter:!0,useTimestampFilter:!0,useUtf8Filter:!0,...H.current&&{gpuDispatchMs:1e4},...ot&&{useDictionary:!1,startingLength:qe}},Qe=>{w(Qe)});if($.current){$.current=!1,se.current=!1,w(null);return}if(Ke.found&&Ke.roomName&&Ke.key){b(He=>{const Et=new Map(He),zt=Et.get(at);return zt&&Et.set(at,{...zt,status:"cracked",attempts:zt.attempts+1,lastAttemptLength:qe}),Et});const Qe={roomName:Ke.roomName,key:Ke.key,packetId:at,message:Ke.decryptedMessage||"",crackedAt:Date.now()};T(He=>[...He,Qe]);const rt=Ke.key.toUpperCase();if(!ae.has(rt))try{const He="#"+Ke.roomName;await i(He,Ke.key),D.current&&await ut.decryptHistoricalPackets({key_type:"channel",channel_name:He})}catch(He){console.error("Failed to create channel or decrypt historical:",He),At.error("Failed to save cracked channel",{description:He instanceof Error?He.message:"Channel discovered but could not be saved"})}}else b(Qe=>{const rt=new Map(Qe),He=rt.get(at);return He&&rt.set(at,{...He,status:"failed",attempts:He.attempts+1,lastAttemptLength:qe}),rt})}catch(Ke){console.error("Cracking error:",Ke),b(Qe=>{const rt=new Map(Qe),He=rt.get(at);return He&&rt.set(at,{...He,status:"failed",attempts:He.attempts+1,lastAttemptLength:qe}),rt})}se.current=!1,w(null),ge.current&&setTimeout(()=>Be(),100)},[ae,i]),Oe=()=>{var Ee;if(!j){At.error("WebGPU not available",{description:"Cracking requires Chrome 113+ or Edge 113+ with WebGPU support."});return}h(!0),ge.current=!0,$.current=!1,(Ee=ie.current)==null||Ee.enable(),Be()},Xe=()=>{var Ee,je;h(!1),ge.current=!1,$.current=!0,(Ee=Y.current)==null||Ee.abort(),(je=ie.current)==null||
Line truncated
|
||||
//# sourceMappingURL=index-D5ozmcKB.js.map
|
||||
//# sourceMappingURL=index-COA8MjNX.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"mappings":"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 truncated
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"mappings":"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 truncated
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-D5ozmcKB.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-COA8MjNX.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-H2C92sGV.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -37,6 +37,7 @@ export function formatTelemetry(telemetry: TelemetryResponse): string {
|
||||
`Telemetry`,
|
||||
`Battery Voltage: ${telemetry.battery_volts.toFixed(3)}V`,
|
||||
`Uptime: ${formatDuration(telemetry.uptime_seconds)}`,
|
||||
...(telemetry.clock_output ? [`Clock: ${telemetry.clock_output}`] : []),
|
||||
`TX Airtime: ${formatDuration(telemetry.airtime_seconds)}`,
|
||||
`RX Airtime: ${formatDuration(telemetry.rx_airtime_seconds)}`,
|
||||
'',
|
||||
|
||||
@@ -78,6 +78,7 @@ describe('formatTelemetry', () => {
|
||||
full_events: 0,
|
||||
neighbors: [],
|
||||
acl: [],
|
||||
clock_output: null,
|
||||
};
|
||||
|
||||
const result = formatTelemetry(telemetry);
|
||||
@@ -119,6 +120,7 @@ describe('formatTelemetry', () => {
|
||||
full_events: 0,
|
||||
neighbors: [],
|
||||
acl: [],
|
||||
clock_output: null,
|
||||
};
|
||||
|
||||
const result = formatTelemetry(telemetry);
|
||||
|
||||
@@ -197,6 +197,7 @@ export interface TelemetryResponse {
|
||||
full_events: number;
|
||||
neighbors: NeighborInfo[];
|
||||
acl: AclEntry[];
|
||||
clock_output: string | null;
|
||||
}
|
||||
|
||||
export interface CommandResponse {
|
||||
|
||||
@@ -195,7 +195,7 @@ class TestContactMessageCLIFiltering:
|
||||
patch("app.bot.run_bot_for_message", new_callable=AsyncMock),
|
||||
):
|
||||
mock_repo.create = AsyncMock(return_value=42)
|
||||
mock_contact_repo.get_by_key_prefix = AsyncMock(return_value=None)
|
||||
mock_contact_repo.get_by_key_or_prefix = AsyncMock(return_value=None)
|
||||
|
||||
class MockEvent:
|
||||
payload = {
|
||||
@@ -224,7 +224,7 @@ class TestContactMessageCLIFiltering:
|
||||
patch("app.bot.run_bot_for_message", new_callable=AsyncMock),
|
||||
):
|
||||
mock_repo.create = AsyncMock(return_value=42)
|
||||
mock_contact_repo.get_by_key_prefix = AsyncMock(return_value=None)
|
||||
mock_contact_repo.get_by_key_or_prefix = AsyncMock(return_value=None)
|
||||
|
||||
class MockEvent:
|
||||
payload = {
|
||||
@@ -259,7 +259,7 @@ class TestContactMessageCLIFiltering:
|
||||
patch("app.bot.run_bot_for_message", new_callable=AsyncMock),
|
||||
):
|
||||
mock_repo.create = AsyncMock(return_value=42)
|
||||
mock_contact_repo.get_by_key_prefix = AsyncMock(return_value=None)
|
||||
mock_contact_repo.get_by_key_or_prefix = AsyncMock(return_value=None)
|
||||
|
||||
class MockEvent:
|
||||
payload = {
|
||||
|
||||
@@ -943,26 +943,47 @@ class TestDMDecryptionFunction:
|
||||
assert packet_id not in [p.id for p in undecrypted]
|
||||
|
||||
|
||||
class TestCLIResponseFiltering:
|
||||
"""Test that CLI responses (txt_type=1) are not stored in chat history."""
|
||||
class TestRepeaterMessageFiltering:
|
||||
"""Test that messages from repeaters are not stored in chat history.
|
||||
|
||||
A1B2C3_PUB = "a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
|
||||
FACE12_PUB = "FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46"
|
||||
Repeaters only send CLI responses (not chat messages), and these are handled
|
||||
by the command endpoint. The packet processor filters them out based on
|
||||
contact type to prevent duplicate storage.
|
||||
"""
|
||||
|
||||
# A repeater contact
|
||||
REPEATER_PUB = "a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
|
||||
# A normal client contact
|
||||
CLIENT_PUB = "b2c3d4e4cb0a6fb9816ca956ff22dd7f12e2e5adbbf5e233bd8232774d6cffe8"
|
||||
# Our public key
|
||||
OUR_PUB = "FACE123334789E2B81519AFDBC39A3C9EB7EA3457AD367D3243597A484847E46"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cli_response_not_stored(self, test_db, captured_broadcasts):
|
||||
"""CLI responses (flags & 0x0F == 1) should not be stored in database."""
|
||||
async def test_repeater_message_not_stored(self, test_db, captured_broadcasts):
|
||||
"""Messages from repeaters should not be stored in database."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.models import CONTACT_TYPE_REPEATER
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
from app.repository import MessageRepository, RawPacketRepository
|
||||
from app.repository import ContactRepository, MessageRepository, RawPacketRepository
|
||||
|
||||
# Store a raw packet first
|
||||
# Create a repeater contact first
|
||||
await ContactRepository.upsert(
|
||||
{
|
||||
"public_key": self.REPEATER_PUB,
|
||||
"name": "Test Repeater",
|
||||
"type": CONTACT_TYPE_REPEATER, # type=2 is repeater
|
||||
"flags": 0,
|
||||
"on_radio": False,
|
||||
}
|
||||
)
|
||||
|
||||
# Store a raw packet
|
||||
packet_id, _ = await RawPacketRepository.create(b"\x09\x00test", 1700000000)
|
||||
|
||||
# Create a DecryptedDirectMessage with flags=1 (CLI response)
|
||||
# Create a DecryptedDirectMessage (simulating a CLI response from repeater)
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=1, # txt_type=1 (CLI response)
|
||||
flags=0, # flags don't matter - we filter by contact type
|
||||
message="cli response: version 1.0",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
@@ -974,13 +995,13 @@ class TestCLIResponseFiltering:
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
their_public_key=self.REPEATER_PUB,
|
||||
our_public_key=self.OUR_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
# Should return None (not stored)
|
||||
# Should return None (not stored because sender is a repeater)
|
||||
assert msg_id is None
|
||||
|
||||
# Should not broadcast
|
||||
@@ -988,25 +1009,36 @@ class TestCLIResponseFiltering:
|
||||
|
||||
# Should not be in database
|
||||
messages = await MessageRepository.get_all(
|
||||
msg_type="PRIV", conversation_key=self.A1B2C3_PUB.lower(), limit=10
|
||||
msg_type="PRIV", conversation_key=self.REPEATER_PUB.lower(), limit=10
|
||||
)
|
||||
assert len(messages) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_normal_message_still_stored(self, test_db, captured_broadcasts):
|
||||
"""Normal messages (flags & 0x0F == 0) should still be stored."""
|
||||
async def test_client_message_still_stored(self, test_db, captured_broadcasts):
|
||||
"""Messages from normal clients should still be stored."""
|
||||
from app.decoder import DecryptedDirectMessage
|
||||
from app.packet_processor import create_dm_message_from_decrypted
|
||||
from app.repository import MessageRepository, RawPacketRepository
|
||||
from app.repository import ContactRepository, MessageRepository, RawPacketRepository
|
||||
|
||||
# Create a normal client contact (type=1)
|
||||
await ContactRepository.upsert(
|
||||
{
|
||||
"public_key": self.CLIENT_PUB,
|
||||
"name": "Test Client",
|
||||
"type": 1, # type=1 is client
|
||||
"flags": 0,
|
||||
"on_radio": False,
|
||||
}
|
||||
)
|
||||
|
||||
packet_id, _ = await RawPacketRepository.create(b"\x09\x00test2", 1700000000)
|
||||
|
||||
decrypted = DecryptedDirectMessage(
|
||||
timestamp=1700000000,
|
||||
flags=0, # txt_type=0 (normal message)
|
||||
flags=0,
|
||||
message="Hello, world!",
|
||||
dest_hash="fa",
|
||||
src_hash="a1",
|
||||
src_hash="b2",
|
||||
)
|
||||
|
||||
broadcasts, mock_broadcast = captured_broadcasts
|
||||
@@ -1015,13 +1047,13 @@ class TestCLIResponseFiltering:
|
||||
msg_id = await create_dm_message_from_decrypted(
|
||||
packet_id=packet_id,
|
||||
decrypted=decrypted,
|
||||
their_public_key=self.A1B2C3_PUB,
|
||||
our_public_key=self.FACE12_PUB,
|
||||
their_public_key=self.CLIENT_PUB,
|
||||
our_public_key=self.OUR_PUB,
|
||||
received_at=1700000001,
|
||||
outgoing=False,
|
||||
)
|
||||
|
||||
# Should return message ID
|
||||
# Should return message ID (stored because sender is a client)
|
||||
assert msg_id is not None
|
||||
|
||||
# Should broadcast
|
||||
@@ -1030,6 +1062,6 @@ class TestCLIResponseFiltering:
|
||||
|
||||
# Should be in database
|
||||
messages = await MessageRepository.get_all(
|
||||
msg_type="PRIV", conversation_key=self.A1B2C3_PUB.lower(), limit=10
|
||||
msg_type="PRIV", conversation_key=self.CLIENT_PUB.lower(), limit=10
|
||||
)
|
||||
assert len(messages) == 1
|
||||
Reference in new issue
Block a user