mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-14 08:04:32 +00:00
OpenWorker: initial import
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Parked unauthorized messages — what an unallowed sender said, kept instead of lost.
|
||||
|
||||
The gateway drops inbound messages from senders not on the allow-list (closed by default).
|
||||
Dropping silently made the first-contact flow clumsy: the sender had to message once just to
|
||||
appear under "Recent senders", get allowed, then message AGAIN. Parking the dropped message
|
||||
lets the owner see it on the connector page and resolve it in one step — dismiss it, allow
|
||||
the sender, or allow AND deliver the original message (no re-send needed).
|
||||
|
||||
JSON-backed and capped like UnroutedStore. This IS a queue (unlike Unrouted): allow-and-deliver
|
||||
re-injects the parked message through the normal inbound path.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ParkedMessage:
|
||||
platform: str # "slack" | "telegram" | …
|
||||
chat_id: str # channel/DM id, e.g. "C0BD7KZ1AH5"
|
||||
user_id: str # sender id, e.g. "U07JK68S4BH"
|
||||
text: str
|
||||
chat_name: Optional[str] = None # resolved display name (falls back to chat_id)
|
||||
user_name: Optional[str] = None # resolved display name (falls back to user_id)
|
||||
chat_type: str = "channel" # "channel" | "group" | "dm"
|
||||
thread_id: Optional[str] = None
|
||||
team_id: Optional[str] = None # workspace id (managed relay); None for socket mode
|
||||
ts: float = field(default_factory=time.time)
|
||||
id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
||||
|
||||
|
||||
class ParkedStore:
|
||||
def __init__(self, path: Optional[str | Path] = None, *, cap: int = 100) -> None:
|
||||
self.path = Path(path) if path else None
|
||||
self._cap = cap
|
||||
self._lock = threading.Lock()
|
||||
self._items: list[ParkedMessage] = []
|
||||
self._load()
|
||||
|
||||
def _load(self) -> None:
|
||||
if self.path and self.path.is_file():
|
||||
try:
|
||||
data = json.loads(self.path.read_text(encoding="utf-8"))
|
||||
self._items = [ParkedMessage(**raw) for raw in data.get("items", [])]
|
||||
except (OSError, ValueError, TypeError):
|
||||
self._items = [] # a corrupt file must never block startup
|
||||
|
||||
def _save(self) -> None:
|
||||
if not self.path:
|
||||
return
|
||||
try:
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.path.write_text(
|
||||
json.dumps({"items": [asdict(i) for i in self._items]}, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except OSError:
|
||||
pass # persistence is best-effort; memory stays authoritative
|
||||
|
||||
def park(self, **fields) -> ParkedMessage:
|
||||
item = ParkedMessage(**fields)
|
||||
with self._lock:
|
||||
self._items.append(item)
|
||||
if len(self._items) > self._cap:
|
||||
self._items = self._items[-self._cap :]
|
||||
self._save()
|
||||
return item
|
||||
|
||||
def list(self, platform: Optional[str] = None) -> list[dict]:
|
||||
with self._lock:
|
||||
return [
|
||||
asdict(i)
|
||||
for i in reversed(self._items) # newest first
|
||||
if platform is None or i.platform == platform
|
||||
]
|
||||
|
||||
def pop(self, item_id: str) -> Optional[ParkedMessage]:
|
||||
with self._lock:
|
||||
for i, item in enumerate(self._items):
|
||||
if item.id == item_id:
|
||||
del self._items[i]
|
||||
self._save()
|
||||
return item
|
||||
return None
|
||||
Reference in New Issue
Block a user