forked from Rockachopa/Timmy-time-dashboard
ruff (#169)
* polish: streamline nav, extract inline styles, improve tablet UX - Restructure desktop nav from 8+ flat links + overflow dropdown into 5 grouped dropdowns (Core, Agents, Intel, System, More) matching the mobile menu structure to reduce decision fatigue - Extract all inline styles from mission_control.html and base.html notification elements into mission-control.css with semantic classes - Replace JS-built innerHTML with secure DOM construction in notification loader and chat history - Add CONNECTING state to connection indicator (amber) instead of showing OFFLINE before WebSocket connects - Add tablet breakpoint (1024px) with larger touch targets for Apple Pencil / stylus use and safe-area padding for iPad toolbar - Add active-link highlighting in desktop dropdown menus - Rename "Mission Control" page title to "System Overview" to disambiguate from the chat home page - Add "Home — Timmy Time" page title to index.html https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h * fix(security): move auth-gate credentials to environment variables Hardcoded username, password, and HMAC secret in auth-gate.py replaced with os.environ lookups. Startup now refuses to run if any variable is unset. Added AUTH_GATE_SECRET/USER/PASS to .env.example. https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h * refactor(tooling): migrate from black+isort+bandit to ruff Replace three separate linting/formatting tools with a single ruff invocation. Updates tox.ini (lint, format, pre-push, pre-commit envs), .pre-commit-config.yaml, and CI workflow. Fixes all ruff errors including unused imports, missing raise-from, and undefined names. Ruff config maps existing bandit skips to equivalent S-rules. https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
708c8a2477
commit
9d78eb31d1
@@ -14,9 +14,9 @@ Architecture:
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from enum import Enum, auto
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
|
||||
class PlatformState(Enum):
|
||||
@@ -36,9 +36,9 @@ class ChatMessage:
|
||||
author: str
|
||||
channel_id: str
|
||||
platform: str
|
||||
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||
message_id: Optional[str] = None
|
||||
thread_id: Optional[str] = None
|
||||
timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
|
||||
message_id: str | None = None
|
||||
thread_id: str | None = None
|
||||
attachments: list[str] = field(default_factory=list)
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@@ -51,7 +51,7 @@ class ChatThread:
|
||||
title: str
|
||||
channel_id: str
|
||||
platform: str
|
||||
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||
created_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
|
||||
archived: bool = False
|
||||
message_count: int = 0
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
@@ -64,7 +64,7 @@ class InviteInfo:
|
||||
url: str
|
||||
code: str
|
||||
platform: str
|
||||
guild_name: Optional[str] = None
|
||||
guild_name: str | None = None
|
||||
source: str = "unknown" # "qr", "vision", "text"
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class PlatformStatus:
|
||||
token_set: bool
|
||||
guild_count: int = 0
|
||||
thread_count: int = 0
|
||||
error: Optional[str] = None
|
||||
error: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -112,7 +112,7 @@ class ChatPlatform(ABC):
|
||||
"""Current connection state."""
|
||||
|
||||
@abstractmethod
|
||||
async def start(self, token: Optional[str] = None) -> bool:
|
||||
async def start(self, token: str | None = None) -> bool:
|
||||
"""Start the platform connection. Returns True on success."""
|
||||
|
||||
@abstractmethod
|
||||
@@ -121,14 +121,14 @@ class ChatPlatform(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def send_message(
|
||||
self, channel_id: str, content: str, thread_id: Optional[str] = None
|
||||
) -> Optional[ChatMessage]:
|
||||
self, channel_id: str, content: str, thread_id: str | None = None
|
||||
) -> ChatMessage | None:
|
||||
"""Send a message. Optionally within a thread."""
|
||||
|
||||
@abstractmethod
|
||||
async def create_thread(
|
||||
self, channel_id: str, title: str, initial_message: Optional[str] = None
|
||||
) -> Optional[ChatThread]:
|
||||
self, channel_id: str, title: str, initial_message: str | None = None
|
||||
) -> ChatThread | None:
|
||||
"""Create a new thread in a channel."""
|
||||
|
||||
@abstractmethod
|
||||
@@ -144,5 +144,5 @@ class ChatPlatform(ABC):
|
||||
"""Persist token for restarts."""
|
||||
|
||||
@abstractmethod
|
||||
def load_token(self) -> Optional[str]:
|
||||
def load_token(self) -> str | None:
|
||||
"""Load persisted token."""
|
||||
|
||||
@@ -23,7 +23,6 @@ Usage:
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from integrations.chat_bridge.base import InviteInfo
|
||||
|
||||
@@ -36,7 +35,7 @@ _DISCORD_PATTERNS = [
|
||||
]
|
||||
|
||||
|
||||
def _extract_discord_code(text: str) -> Optional[str]:
|
||||
def _extract_discord_code(text: str) -> str | None:
|
||||
"""Extract a Discord invite code from text."""
|
||||
for pattern in _DISCORD_PATTERNS:
|
||||
match = pattern.search(text)
|
||||
@@ -52,7 +51,7 @@ class InviteParser:
|
||||
then regex on raw text. All local, no cloud.
|
||||
"""
|
||||
|
||||
async def parse_image(self, image_data: bytes) -> Optional[InviteInfo]:
|
||||
async def parse_image(self, image_data: bytes) -> InviteInfo | None:
|
||||
"""Extract an invite from image bytes (screenshot or QR photo).
|
||||
|
||||
Tries strategies in order:
|
||||
@@ -70,7 +69,7 @@ class InviteParser:
|
||||
logger.info("No invite found in image via any strategy.")
|
||||
return None
|
||||
|
||||
def parse_text(self, text: str) -> Optional[InviteInfo]:
|
||||
def parse_text(self, text: str) -> InviteInfo | None:
|
||||
"""Extract an invite from plain text."""
|
||||
code = _extract_discord_code(text)
|
||||
if code:
|
||||
@@ -82,7 +81,7 @@ class InviteParser:
|
||||
)
|
||||
return None
|
||||
|
||||
def _try_qr_decode(self, image_data: bytes) -> Optional[InviteInfo]:
|
||||
def _try_qr_decode(self, image_data: bytes) -> InviteInfo | None:
|
||||
"""Strategy 1: Decode QR codes from image using pyzbar."""
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -111,7 +110,7 @@ class InviteParser:
|
||||
|
||||
return None
|
||||
|
||||
async def _try_ollama_vision(self, image_data: bytes) -> Optional[InviteInfo]:
|
||||
async def _try_ollama_vision(self, image_data: bytes) -> InviteInfo | None:
|
||||
"""Strategy 2: Use Ollama vision model for local OCR."""
|
||||
try:
|
||||
import base64
|
||||
|
||||
@@ -13,7 +13,6 @@ Usage:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from integrations.chat_bridge.base import ChatPlatform, PlatformStatus
|
||||
|
||||
@@ -42,7 +41,7 @@ class PlatformRegistry:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get(self, name: str) -> Optional[ChatPlatform]:
|
||||
def get(self, name: str) -> ChatPlatform | None:
|
||||
"""Get a platform by name."""
|
||||
return self._platforms.get(name)
|
||||
|
||||
|
||||
27
src/integrations/chat_bridge/vendors/discord.py
vendored
27
src/integrations/chat_bridge/vendors/discord.py
vendored
@@ -18,13 +18,12 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from integrations.chat_bridge.base import (
|
||||
ChatMessage,
|
||||
ChatPlatform,
|
||||
ChatThread,
|
||||
InviteInfo,
|
||||
PlatformState,
|
||||
PlatformStatus,
|
||||
)
|
||||
@@ -108,9 +107,9 @@ class DiscordVendor(ChatPlatform):
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._client = None
|
||||
self._token: Optional[str] = None
|
||||
self._token: str | None = None
|
||||
self._state: PlatformState = PlatformState.DISCONNECTED
|
||||
self._task: Optional[asyncio.Task] = None
|
||||
self._task: asyncio.Task | None = None
|
||||
self._guild_count: int = 0
|
||||
self._active_threads: dict[str, str] = {} # channel_id -> thread_id
|
||||
self._pending_actions: dict[str, dict] = {} # approval_id -> action details
|
||||
@@ -125,7 +124,7 @@ class DiscordVendor(ChatPlatform):
|
||||
def state(self) -> PlatformState:
|
||||
return self._state
|
||||
|
||||
async def start(self, token: Optional[str] = None) -> bool:
|
||||
async def start(self, token: str | None = None) -> bool:
|
||||
"""Start the Discord bot. Returns True on success."""
|
||||
if self._state == PlatformState.CONNECTED:
|
||||
return True
|
||||
@@ -198,15 +197,13 @@ class DiscordVendor(ChatPlatform):
|
||||
self._task = None
|
||||
|
||||
async def send_message(
|
||||
self, channel_id: str, content: str, thread_id: Optional[str] = None
|
||||
) -> Optional[ChatMessage]:
|
||||
self, channel_id: str, content: str, thread_id: str | None = None
|
||||
) -> ChatMessage | None:
|
||||
"""Send a message to a Discord channel or thread."""
|
||||
if not self._client or self._state != PlatformState.CONNECTED:
|
||||
return None
|
||||
|
||||
try:
|
||||
import discord
|
||||
|
||||
target_id = int(thread_id) if thread_id else int(channel_id)
|
||||
channel = self._client.get_channel(target_id)
|
||||
|
||||
@@ -228,8 +225,8 @@ class DiscordVendor(ChatPlatform):
|
||||
return None
|
||||
|
||||
async def create_thread(
|
||||
self, channel_id: str, title: str, initial_message: Optional[str] = None
|
||||
) -> Optional[ChatThread]:
|
||||
self, channel_id: str, title: str, initial_message: str | None = None
|
||||
) -> ChatThread | None:
|
||||
"""Create a new thread in a Discord channel."""
|
||||
if not self._client or self._state != PlatformState.CONNECTED:
|
||||
return None
|
||||
@@ -272,8 +269,6 @@ class DiscordVendor(ChatPlatform):
|
||||
return False
|
||||
|
||||
try:
|
||||
import discord
|
||||
|
||||
invite = await self._client.fetch_invite(invite_code)
|
||||
logger.info(
|
||||
"Validated invite for server '%s' (code: %s)",
|
||||
@@ -301,7 +296,7 @@ class DiscordVendor(ChatPlatform):
|
||||
except Exception as exc:
|
||||
logger.error("Failed to save Discord token: %s", exc)
|
||||
|
||||
def load_token(self) -> Optional[str]:
|
||||
def load_token(self) -> str | None:
|
||||
"""Load token from state file or config."""
|
||||
try:
|
||||
if _STATE_FILE.exists():
|
||||
@@ -321,7 +316,7 @@ class DiscordVendor(ChatPlatform):
|
||||
|
||||
# ── OAuth2 URL generation ──────────────────────────────────────────────
|
||||
|
||||
def get_oauth2_url(self) -> Optional[str]:
|
||||
def get_oauth2_url(self) -> str | None:
|
||||
"""Generate the OAuth2 URL for adding this bot to a server.
|
||||
|
||||
Requires the bot to be connected to read its application ID.
|
||||
@@ -514,7 +509,7 @@ class DiscordVendor(ChatPlatform):
|
||||
asyncio.to_thread(chat_with_tools, content, session_id),
|
||||
timeout=300,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.error("Discord: chat_with_tools() timed out after 300s")
|
||||
response = "Sorry, that took too long. Please try a simpler request."
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user