Compare commits
8
Commits
869f8291fb
...
2512a7988d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2512a7988d | ||
|
|
56146e4e8d | ||
|
|
5fb8f8a534 | ||
|
|
98ab8a579b | ||
|
|
a5d10146fd | ||
|
|
5f1f18b343 | ||
|
|
8eb31ed4d3 | ||
|
|
e67dbdb325 |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
_commit: v0.4.0
|
_commit: v0.6.0
|
||||||
_src_path: https://git.kotikot.com/templates/backend-python.git
|
_src_path: https://git.kotikot.com/templates/backend-python.git
|
||||||
author_email: h@kotikot.com
|
author_email: h@kotikot.com
|
||||||
author_name: h
|
author_name: h
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
_commit: v0.1.2
|
_commit: v0.1.4
|
||||||
_src_path: https://git.kotikot.com/templates/caddy.git
|
_src_path: https://git.kotikot.com/templates/caddy.git
|
||||||
project_slug: beavergram
|
project_slug: beavergram
|
||||||
routes:
|
routes:
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
_commit: v0.4.2
|
_commit: v0.6.0
|
||||||
_src_path: https://git.kotikot.com/templates/frontend-svelte.git
|
_src_path: https://git.kotikot.com/templates/frontend-svelte.git
|
||||||
install_deps: false
|
install_deps: false
|
||||||
layout: part
|
layout: part
|
||||||
|
linter: biome
|
||||||
project_name: beavergram
|
project_name: beavergram
|
||||||
project_slug: beavergram
|
project_slug: beavergram
|
||||||
serve: api
|
serve: api
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
_commit: v0.5.0
|
_commit: v0.6.0
|
||||||
_src_path: https://git.kotikot.com/templates/infra.git
|
_src_path: https://git.kotikot.com/templates/infra.git
|
||||||
init_git: false
|
init_git: false
|
||||||
layout: parts
|
layout: parts
|
||||||
|
|||||||
+1
-1
@@ -8,4 +8,4 @@ docs
|
|||||||
storage
|
storage
|
||||||
|
|
||||||
# Local playground
|
# Local playground
|
||||||
t/
|
/t/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
PARTS := $(patsubst %/Makefile,%,$(wildcard */Makefile))
|
PARTS := $(patsubst %/Makefile,%,$(wildcard */Makefile))
|
||||||
|
|
||||||
.PHONY: env recreate rebuild restart logs down deploy fmt check build pre-deploy post-deploy
|
.PHONY: env recreate rebuild restart logs down fmt check build
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@fresh=; test -f .env || { awk 'FNR == 1 && NR > 1 { print "" } 1' .env.example $(addsuffix /.env.example,$(PARTS)) > .env; fresh=1; }; \
|
@fresh=; test -f .env || { awk 'FNR == 1 && NR > 1 { print "" } 1' .env.example $(addsuffix /.env.example,$(PARTS)) > .env; fresh=1; }; \
|
||||||
@@ -30,9 +30,5 @@ logs:
|
|||||||
down:
|
down:
|
||||||
docker compose down
|
docker compose down
|
||||||
|
|
||||||
deploy: pre-deploy
|
fmt check build:
|
||||||
docker compose up -d
|
|
||||||
$(MAKE) post-deploy
|
|
||||||
|
|
||||||
fmt check build pre-deploy post-deploy:
|
|
||||||
@for part in $(PARTS); do $(MAKE) -C $$part $@ || exit 1; done
|
@for part in $(PARTS); do $(MAKE) -C $$part $@ || exit 1; done
|
||||||
@@ -31,7 +31,8 @@ Read-only: `list_watched_bots`, `get_bot_stats`, `get_bot_updates`, `search_mess
|
|||||||
```sh
|
```sh
|
||||||
make env # .env and docker-compose.override.yml from the examples
|
make env # .env and docker-compose.override.yml from the examples
|
||||||
make -C backend session # log the account in once; the session file stays in backend/sessions
|
make -C backend session # log the account in once; the session file stays in backend/sessions
|
||||||
make deploy # build what changed, migrate, start the containers
|
make -C backend migrate # the schema
|
||||||
|
make recreate # build and start the services in COMPOSE_PROFILES
|
||||||
```
|
```
|
||||||
|
|
||||||
`COMPOSE_PROFILES` in `.env` selects what runs on the machine, `docker-compose.override.yml` in each part holds the published ports and the `caddy` network aliases. The web client and the MCP endpoint are served by `api`; the bearer for both is `AUTH__TOKEN`.
|
`COMPOSE_PROFILES` in `.env` selects what runs on the machine, `docker-compose.override.yml` in each part holds the published ports and the `caddy` network aliases. The web client and the MCP endpoint are served by `api`; the bearer for both is `AUTH__TOKEN`.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ TZ=UTC
|
|||||||
LOG__LEVEL=INFO
|
LOG__LEVEL=INFO
|
||||||
LOG__LEVEL_EXTERNAL=WARNING
|
LOG__LEVEL_EXTERNAL=WARNING
|
||||||
LOG__SHOW_TIME=false
|
LOG__SHOW_TIME=false
|
||||||
|
LOG__SHOW_LOCALS=false
|
||||||
LOG__CONSOLE_WIDTH=150
|
LOG__CONSOLE_WIDTH=150
|
||||||
|
|
||||||
DB__HOST=postgres
|
DB__HOST=postgres
|
||||||
|
|||||||
+4
-3
@@ -1,7 +1,8 @@
|
|||||||
## Checking commands
|
## Checking commands
|
||||||
After writing code, always run from `backend/`:
|
After writing code, always run from `backend/`:
|
||||||
```shell
|
```shell
|
||||||
ruff format
|
make fmt
|
||||||
ruff check --fix
|
make check
|
||||||
ty check
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Mind startup imports: `make check` runs `scripts/check_imports.py`.
|
||||||
+2
-21
@@ -1,12 +1,6 @@
|
|||||||
COMPOSE := cd .. && docker compose
|
COMPOSE := cd .. && docker compose
|
||||||
IMAGE := beavergram/backend
|
|
||||||
IMAGE_INPUTS := pyproject.toml uv.lock Dockerfile
|
|
||||||
CODE := src scripts ../frontend
|
|
||||||
SERVICES := api userbot watcher
|
|
||||||
changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
|
|
||||||
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
|
|
||||||
|
|
||||||
.PHONY: fmt check build pre-deploy post-deploy migrate revision session
|
.PHONY: fmt check build migrate revision session
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
uv run ruff format
|
uv run ruff format
|
||||||
@@ -16,24 +10,11 @@ check:
|
|||||||
uv run ruff format --check
|
uv run ruff format --check
|
||||||
uv run ruff check
|
uv run ruff check
|
||||||
uv run ty check
|
uv run ty check
|
||||||
|
uv run python scripts/check_imports.py
|
||||||
|
|
||||||
build:
|
build:
|
||||||
$(COMPOSE) build api
|
$(COMPOSE) build api
|
||||||
|
|
||||||
pre-deploy:
|
|
||||||
@if $(call changed,refs/deploy/backend,$(IMAGE_INPUTS)) || ! docker image inspect $(IMAGE) >/dev/null 2>&1; then \
|
|
||||||
$(MAKE) build && $(call stamp,refs/deploy/backend) && $(call stamp,refs/deploy/backend-code); \
|
|
||||||
fi
|
|
||||||
@$(COMPOSE) config --services | grep -qx postgres && $(COMPOSE) up -d postgres || true
|
|
||||||
$(MAKE) migrate
|
|
||||||
|
|
||||||
post-deploy:
|
|
||||||
@if $(call changed,refs/deploy/backend-code,$(CODE)); then \
|
|
||||||
running=$$($(COMPOSE) ps --services --status running | grep -x $(addprefix -e ,$(SERVICES))); \
|
|
||||||
[ -z "$$running" ] || $(COMPOSE) restart $$running; \
|
|
||||||
$(call stamp,refs/deploy/backend-code); \
|
|
||||||
fi
|
|
||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
$(COMPOSE) run --rm migrator $(or $(filter-out $@,$(MAKECMDGOALS)),upgrade head)
|
$(COMPOSE) run --rm migrator $(or $(filter-out $@,$(MAKECMDGOALS)),upgrade head)
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,15 @@ def _async_url() -> str:
|
|||||||
return env.db.connection_url.replace("postgresql://", "postgresql+asyncpg://", 1)
|
return env.db.connection_url.replace("postgresql://", "postgresql+asyncpg://", 1)
|
||||||
|
|
||||||
|
|
||||||
|
def include_name(name: str | None, type_: str, _parents: object) -> bool:
|
||||||
|
return not (type_ == "table" and name is not None and name.startswith("taskiq_"))
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
def run_migrations_offline() -> None:
|
||||||
context.configure(
|
context.configure(
|
||||||
url=_async_url(),
|
url=_async_url(),
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
|
include_name=include_name,
|
||||||
literal_binds=True,
|
literal_binds=True,
|
||||||
dialect_opts={"paramstyle": "named"},
|
dialect_opts={"paramstyle": "named"},
|
||||||
)
|
)
|
||||||
@@ -32,7 +37,11 @@ def run_migrations_offline() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _do_run_migrations(connection) -> None: # noqa: ANN001
|
def _do_run_migrations(connection) -> None: # noqa: ANN001
|
||||||
context.configure(connection=connection, target_metadata=target_metadata)
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
include_name=include_name,
|
||||||
|
)
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ unfixable = ["F401"]
|
|||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"scripts/*" = ["INP"]
|
"scripts/*" = ["INP"]
|
||||||
|
"scripts/check_imports.py" = ["T201"]
|
||||||
"t/*" = ["ALL"]
|
"t/*" = ["ALL"]
|
||||||
"*.lock" = ["ALL"]
|
"*.lock" = ["ALL"]
|
||||||
"migrations/*" = ["INP", "D", "E501"]
|
"migrations/*" = ["INP", "D", "E501"]
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HEAVY = ["aiogram", "pyrogram", "fastapi", "google.genai"]
|
||||||
|
SERVICES = {
|
||||||
|
"api": (["api.app"], {"fastapi", "pyrogram"}),
|
||||||
|
"userbot": (["userbot.runner", "userbot.handlers"], {"pyrogram"}),
|
||||||
|
"watcher": (["watcher.runner"], {"pyrogram"}),
|
||||||
|
}
|
||||||
|
PROBE = """
|
||||||
|
import importlib, sys
|
||||||
|
for module in sys.argv[1].split(","):
|
||||||
|
importlib.import_module(module)
|
||||||
|
print(" ".join(name for name in sys.argv[2:] if name in sys.modules))
|
||||||
|
"""
|
||||||
|
ENV = {**os.environ, "AUTH__TOKEN": "check"}
|
||||||
|
|
||||||
|
|
||||||
|
def loaded(modules: list[str]) -> tuple[set[str], str]:
|
||||||
|
result = subprocess.run( # noqa: S603
|
||||||
|
[sys.executable, "-c", PROBE, ",".join(modules), *HEAVY],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd="src",
|
||||||
|
env=ENV,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
error = (result.stderr.strip().splitlines() or ["exit code"])[-1]
|
||||||
|
return set(result.stdout.split()) & set(HEAVY), error if result.returncode else ""
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
failed = False
|
||||||
|
for service, (modules, allowed) in SERVICES.items():
|
||||||
|
found, error = loaded(modules)
|
||||||
|
extra = sorted(found - allowed)
|
||||||
|
status = error or ("imports " + ", ".join(extra) if extra else "")
|
||||||
|
print(f"{service:10s} {'FAIL: ' + status if status else 'ok'}")
|
||||||
|
failed |= bool(status)
|
||||||
|
return int(failed)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -9,7 +9,7 @@ from pyrogram.errors import AuthTokenExpired, SessionPasswordNeeded
|
|||||||
from pyrogram.qrlogin import QRLogin
|
from pyrogram.qrlogin import QRLogin
|
||||||
from pyrogram.types import User
|
from pyrogram.types import User
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
from utils.env import env
|
from utils.env import env
|
||||||
|
|
||||||
LOGIN_TTL_SECONDS = 900
|
LOGIN_TTL_SECONDS = 900
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from pyrogram.errors import FloodWait, RPCError
|
|||||||
from pyrogram.types import User
|
from pyrogram.types import User
|
||||||
|
|
||||||
from api.login import LoginError, login_manager
|
from api.login import LoginError, login_manager
|
||||||
from userbot import DEVICE_MODEL_LIMIT
|
from userbot.modules.client import DEVICE_MODEL_LIMIT
|
||||||
from utils.read import accounts
|
from utils.read import accounts
|
||||||
from utils.read.models import AccountView
|
from utils.read.models import AccountView
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from userbot.modules.client import DEVICE_MODEL, DEVICE_MODEL_LIMIT, PyroClient
|
|
||||||
|
|
||||||
__all__ = ["DEVICE_MODEL", "DEVICE_MODEL_LIMIT", "PyroClient"]
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from pyrogram.types import Message
|
from pyrogram.types import Message
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import repository
|
from userbot.modules.capture import repository
|
||||||
from userbot.modules.capture.repository import CHANNEL_ID_THRESHOLD
|
from userbot.modules.capture.repository import CHANNEL_ID_THRESHOLD
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from pyrogram.types import Message
|
from pyrogram.types import Message
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import repository
|
from userbot.modules.capture import repository
|
||||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||||
from userbot.modules.capture.message import sender_id
|
from userbot.modules.capture.message import sender_id
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.media import capture_media, media_unique_id, self_destruct_ttl
|
from userbot.modules.media import capture_media, media_unique_id, self_destruct_ttl
|
||||||
from userbot.modules.raw_json import raw_json
|
from userbot.modules.raw_json import raw_json
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from pyrogram.types import Message
|
from pyrogram.types import Message
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import capture_message
|
from userbot.modules.capture import capture_message
|
||||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.stt import is_transcribable
|
from userbot.modules.stt import is_transcribable
|
||||||
from userbot.modules.stt.gate import safe_transcribe
|
from userbot.modules.stt.gate import safe_transcribe
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import UTC, datetime
|
|||||||
|
|
||||||
from pyrogram.types import User
|
from pyrogram.types import User
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.presence import repository
|
from userbot.modules.presence import repository
|
||||||
from userbot.modules.raw_json import raw_json
|
from userbot.modules.raw_json import raw_json
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pyrogram import raw
|
from pyrogram import raw
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
|
|
||||||
HANDLES = (raw.types.UpdateContactsReset, raw.types.UpdateUser)
|
HANDLES = (raw.types.UpdateContactsReset, raw.types.UpdateUser)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pyrogram import raw
|
from pyrogram import raw
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
|
|
||||||
HANDLES = (
|
HANDLES = (
|
||||||
raw.types.UpdateDialogFilter,
|
raw.types.UpdateDialogFilter,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from collections.abc import Awaitable, Callable
|
|||||||
|
|
||||||
from pyrogram import raw
|
from pyrogram import raw
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.handlers.raw import (
|
from userbot.handlers.raw import (
|
||||||
contacts,
|
contacts,
|
||||||
dialog_filters,
|
dialog_filters,
|
||||||
@@ -12,6 +11,7 @@ from userbot.handlers.raw import (
|
|||||||
read_receipts,
|
read_receipts,
|
||||||
transcribed,
|
transcribed,
|
||||||
)
|
)
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
|
|
||||||
RawHandler = Callable[..., Awaitable[None]]
|
RawHandler = Callable[..., Awaitable[None]]
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from dataclasses import replace
|
|||||||
|
|
||||||
from pyrogram import raw
|
from pyrogram import raw
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.avatars import capture_avatar
|
from userbot.modules.avatars import capture_avatar
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.profiles import active_username, snapshot_from_user
|
from userbot.modules.profiles import active_username, snapshot_from_user
|
||||||
from userbot.modules.profiles.parse import ProfileFields
|
from userbot.modules.profiles.parse import ProfileFields
|
||||||
from userbot.modules.profiles.repository import get_peer, write_profile
|
from userbot.modules.profiles.repository import get_peer, write_profile
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from pyrogram import raw, utils
|
from pyrogram import raw, utils
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import repository
|
from userbot.modules.capture import repository
|
||||||
from userbot.modules.capture.chat_meta import meta_from_peer
|
from userbot.modules.capture.chat_meta import meta_from_peer
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|
||||||
HANDLES = (raw.types.UpdateMessageReactions,)
|
HANDLES = (raw.types.UpdateMessageReactions,)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from datetime import UTC, datetime
|
|||||||
|
|
||||||
from pyrogram import raw, utils
|
from pyrogram import raw, utils
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture.chat_meta import meta_from_chat_id
|
from userbot.modules.capture.chat_meta import meta_from_chat_id
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.raw_json import raw_json
|
from userbot.modules.raw_json import raw_json
|
||||||
from userbot.modules.read_receipts import repository as receipts
|
from userbot.modules.read_receipts import repository as receipts
|
||||||
from userbot.modules.stt import repository
|
from userbot.modules.stt import repository
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import UTC, datetime
|
|||||||
|
|
||||||
from pyrogram import raw, utils
|
from pyrogram import raw, utils
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.raw_json import raw_json
|
from userbot.modules.raw_json import raw_json
|
||||||
from userbot.modules.read_receipts import repository
|
from userbot.modules.read_receipts import repository
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pyrogram import raw
|
from pyrogram import raw
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture.chat_meta import meta_from_peer
|
from userbot.modules.capture.chat_meta import meta_from_peer
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.stt import repository
|
from userbot.modules.stt import repository
|
||||||
|
|
||||||
HANDLES = (raw.types.UpdateTranscribedAudio,)
|
HANDLES = (raw.types.UpdateTranscribedAudio,)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pyrogram.types import Story
|
from pyrogram.types import Story
|
||||||
|
|
||||||
from userbot import PyroClient
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.stories.service import save_story
|
from userbot.modules.stories.service import save_story
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ class ChatMetaCache:
|
|||||||
chat_id: int,
|
chat_id: int,
|
||||||
meta: tuple[str | None, str | None],
|
meta: tuple[str | None, str | None],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
from userbot.modules.groups.repository import ( # noqa: PLC0415
|
from userbot.modules.groups.repository import get_latest_chat_meta # noqa: PLC0415
|
||||||
get_latest_chat_meta,
|
|
||||||
)
|
|
||||||
|
|
||||||
if chat_id not in self._cache:
|
if chat_id not in self._cache:
|
||||||
self._cache[chat_id] = await get_latest_chat_meta(pool, account_id, chat_id)
|
self._cache[chat_id] = await get_latest_chat_meta(pool, account_id, chat_id)
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import asyncpg
|
|||||||
import uvloop
|
import uvloop
|
||||||
|
|
||||||
from dependencies.container import container
|
from dependencies.container import container
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import CaptureContext, build_capture_context
|
from userbot.modules.capture import CaptureContext, build_capture_context
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.jobs import JobConsumer
|
from userbot.modules.jobs import JobConsumer
|
||||||
from utils.env import env
|
from utils.env import env
|
||||||
from utils.jobs import enqueue
|
from utils.jobs import enqueue
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class LogSettings(BaseSettings):
|
|||||||
level: str = "INFO"
|
level: str = "INFO"
|
||||||
level_external: str = "WARNING"
|
level_external: str = "WARNING"
|
||||||
show_time: bool = False
|
show_time: bool = False
|
||||||
|
show_locals: bool = False
|
||||||
console_width: int = 150
|
console_width: int = 150
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import asyncio
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
|
||||||
|
async def preload(module: str) -> None:
|
||||||
|
await asyncio.to_thread(import_module, module)
|
||||||
@@ -20,13 +20,13 @@ def setup_logging() -> None:
|
|||||||
markup=True,
|
markup=True,
|
||||||
rich_tracebacks=True,
|
rich_tracebacks=True,
|
||||||
enable_link_path=False,
|
enable_link_path=False,
|
||||||
tracebacks_show_locals=True,
|
tracebacks_show_locals=env.log.show_locals,
|
||||||
omit_repeated_times=False,
|
omit_repeated_times=False,
|
||||||
show_time=env.log.show_time,
|
show_time=env.log.show_time,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
install(console=console, show_locals=True)
|
install(console=console, show_locals=env.log.show_locals)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("beavergram")
|
logger = logging.getLogger("beavergram")
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ from dataclasses import dataclass
|
|||||||
from pyrogram import raw, types
|
from pyrogram import raw, types
|
||||||
from pyrogram.errors import RPCError
|
from pyrogram.errors import RPCError
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import capture_message
|
from userbot.modules.capture import capture_message
|
||||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||||
from userbot.modules.capture.context import CaptureContext
|
from userbot.modules.capture.context import CaptureContext
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from utils.logging import logger
|
from utils.logging import logger
|
||||||
|
|
||||||
EARLIEST_PTS = 1
|
EARLIEST_PTS = 1
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ from pyrogram.handlers import (
|
|||||||
MessageHandler,
|
MessageHandler,
|
||||||
)
|
)
|
||||||
|
|
||||||
from userbot import PyroClient
|
|
||||||
from userbot.modules.capture import capture_message
|
from userbot.modules.capture import capture_message
|
||||||
from userbot.modules.capture.chat_meta import meta_from_chat
|
from userbot.modules.capture.chat_meta import meta_from_chat
|
||||||
|
from userbot.modules.client import PyroClient
|
||||||
from userbot.modules.jobs import JobConsumer
|
from userbot.modules.jobs import JobConsumer
|
||||||
from utils.events import notify_bg_event
|
from utils.events import notify_bg_event
|
||||||
from utils.jobs import enqueue
|
from utils.jobs import enqueue
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
.env
|
.env
|
||||||
Caddyfile
|
/Caddyfile
|
||||||
+1
-20
@@ -1,22 +1,3 @@
|
|||||||
# caddy
|
# caddy
|
||||||
|
|
||||||
`site.caddy` routes `{$BEAVERGRAM_DOMAIN}` to the project's services over the external `caddy` docker network. It works standalone and inside a shared Caddy that fronts several projects.
|
`site.caddy` routes `{$BEAVERGRAM_DOMAIN}` to the project over the external `caddy` network. In a shared Caddy, mount this directory as `/etc/caddy/projects.d/beavergram:ro` and set `BEAVERGRAM_DOMAIN`. Keep it portable: domain only from the env, no `tls`, snippets prefixed with `beavergram_`.
|
||||||
|
|
||||||
Standalone:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker network create caddy
|
|
||||||
cp Caddyfile.example Caddyfile
|
|
||||||
cp .env.example .env
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Shared Caddy: mount this directory as `/etc/caddy/projects.d/beavergram:ro`, keep `import /etc/caddy/projects.d/*/site.caddy` in its Caddyfile and put `BEAVERGRAM_DOMAIN` in its environment.
|
|
||||||
|
|
||||||
Rules that keep `site.caddy` portable:
|
|
||||||
|
|
||||||
- the domain comes only from the environment variable
|
|
||||||
- no `tls` block, certificates are configured by the host Caddy (`acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}` in the global block)
|
|
||||||
- snippets are prefixed with the project slug because Caddy snippets are global
|
|
||||||
- mount the directory, not the file: a single-file bind mount goes stale when git replaces the inode
|
|
||||||
- keep `admin localhost:2019`, `admin off` makes `caddy reload` impossible
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
build
|
build
|
||||||
.env
|
# .env goes into the build: $env/static/public (and static/private) are
|
||||||
|
# inlined at build time from it. It stays in the builder stage, the runtime
|
||||||
|
# image gets build/ only. Local overrides (.env.local and the like) stay out.
|
||||||
.env.*
|
.env.*
|
||||||
+1
-11
@@ -1,8 +1,6 @@
|
|||||||
COMPOSE := cd .. && docker compose
|
COMPOSE := cd .. && docker compose
|
||||||
changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
|
|
||||||
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
|
|
||||||
|
|
||||||
.PHONY: fmt check build pre-deploy post-deploy dev
|
.PHONY: fmt check build dev
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
bun run fix
|
bun run fix
|
||||||
@@ -14,13 +12,5 @@ check:
|
|||||||
build:
|
build:
|
||||||
$(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
|
$(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
|
||||||
|
|
||||||
pre-deploy:
|
|
||||||
@if $(call changed,refs/deploy/frontend,.) || ! test -d build; then \
|
|
||||||
$(MAKE) build && $(call stamp,refs/deploy/frontend); \
|
|
||||||
fi
|
|
||||||
|
|
||||||
post-deploy:
|
|
||||||
@:
|
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
bun run dev
|
bun run dev
|
||||||
+12
-12
@@ -11,7 +11,7 @@
|
|||||||
"uqr": "^0.1.3",
|
"uqr": "^0.1.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.14",
|
"@biomejs/biome": "2.5.15",
|
||||||
"@sveltejs/adapter-auto": "^7.0.1",
|
"@sveltejs/adapter-auto": "^7.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.57.0",
|
"@sveltejs/kit": "^2.57.0",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"svelte-check": "^4.7.6",
|
"svelte-check": "^4.7.6",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"ultracite": "7.12.2",
|
"ultracite": "7.12.4",
|
||||||
"vite": "^8.0.7",
|
"vite": "^8.0.7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -39,23 +39,23 @@
|
|||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
|
"@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
|
||||||
|
|
||||||
"@biomejs/biome": ["@biomejs/biome@2.5.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.14", "@biomejs/cli-darwin-x64": "2.5.14", "@biomejs/cli-linux-arm64": "2.5.14", "@biomejs/cli-linux-arm64-musl": "2.5.14", "@biomejs/cli-linux-x64": "2.5.14", "@biomejs/cli-linux-x64-musl": "2.5.14", "@biomejs/cli-win32-arm64": "2.5.14", "@biomejs/cli-win32-x64": "2.5.14" }, "bin": { "biome": "bin/biome" } }, "sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw=="],
|
"@biomejs/biome": ["@biomejs/biome@2.5.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.15", "@biomejs/cli-darwin-x64": "2.5.15", "@biomejs/cli-linux-arm64": "2.5.15", "@biomejs/cli-linux-arm64-musl": "2.5.15", "@biomejs/cli-linux-x64": "2.5.15", "@biomejs/cli-linux-x64-musl": "2.5.15", "@biomejs/cli-win32-arm64": "2.5.15", "@biomejs/cli-win32-x64": "2.5.15" }, "bin": { "biome": "bin/biome" } }, "sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ=="],
|
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BZVzFhJ/mUvTLMYbc9x6el0o2Uv5zP7bACYyhFb6fSc1giroXm7PL0a5KqMJ9F+/BnkYRjagD/ROSDVOZ6eapg=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA=="],
|
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-V5Kw63V+fVGNFhFrizDxMbGXAzZCh8kYzJpy3GI6gTapWDg7bZK9oYC9CbVgQSEdTnupz0U5Efz8Ev+5vdBBXw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q=="],
|
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-XaG7P7eeSLYETD3K9grfB0mQmpLQZjygdbsxekWBJAg4am79fGAtRpfRyNnTvXB2fZFNnJZG3nXm7HBat0+VUw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ=="],
|
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-tGzZUTcJCV7tj5Adh/Gn6nR4MycqA3iohq2LEcrarAgRBkU0h0OKd2UzCRkOsOYOuUGaUJb4YmqRqyy2feGGSw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA=="],
|
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-xE4iEW/3LqlYj9GFgGrFsFSH51dEEpUbYWBfeOv0q87WkUDxK2o/HhreSb7qMJqck70RVM6Lg96hgedcAYcOkA=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg=="],
|
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-IlxUcyxilVGPsE008x13pWdkTbU3nQpP2i9b5UrbOYj6goBkKsRX1XB7yJLcV+O1H9LBchIIm4adOaVLZBX0ZQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg=="],
|
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-2kPKzhNlm8C+Ru3GcO0Me2ODkCBLrVOUNuyi84RJyWDVKAA4+Kjj3jMjL938lF6BRfvltC4vB2nPY9SvN51Ovg=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.15", "", { "os": "win32", "cpu": "x64" }, "sha512-yAzh4UqEImV6Hcy0zjUqNfJAsUhoD64FBQCdTG8Md/Z2wQeVP/ZmJ3XRYAs1g8J9fjTFyl2fCtc5FB5til47+g=="],
|
||||||
|
|
||||||
"@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="],
|
"@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="],
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@
|
|||||||
|
|
||||||
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
|
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
|
||||||
|
|
||||||
"ultracite": ["ultracite@7.12.2", "", { "dependencies": { "@clack/prompts": "^1.8.1", "cli-truncate": "^6.1.1", "commander": "^15.0.0", "deepmerge": "^4.3.1", "empathic": "^2.1.0", "execa": "^10.0.1", "fast-glob": "^3.3.3", "find-workspaces": "^0.3.1", "jsonc-parser": "^3.3.1", "log-update": "^8.0.0", "magicast": "^0.5.5", "nypm": "^0.6.10", "resolve.exports": "^2.0.3", "semver": "^7.8.5", "string-width": "^8.3.0", "yaml": "^2.9.1", "zod": "^4.6.5" }, "peerDependencies": { "@biomejs/biome": "^2.5.0", "eslint": "^10.0.0", "oxfmt": ">=0.59.0", "oxlint": "^1.82.0", "prettier": "^3.0.0", "stylelint": "^17.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "oxfmt", "oxlint", "prettier", "stylelint"], "bin": { "ultracite": "dist/index.js" } }, "sha512-0FQE7EuVFnX1FjtlIBklkfBTiZ5bJxknXSxcsjKos5Y73FlkasubWH7uVLS8jgOAI204B7xaIq+buE3vUybuwQ=="],
|
"ultracite": ["ultracite@7.12.4", "", { "dependencies": { "@clack/prompts": "^1.8.1", "cli-truncate": "^6.1.1", "commander": "^15.0.0", "deepmerge": "^4.3.1", "empathic": "^2.1.0", "execa": "^10.0.1", "fast-glob": "^3.3.3", "find-workspaces": "^0.3.1", "jsonc-parser": "^3.3.1", "log-update": "^8.0.0", "magicast": "^0.5.5", "npm-run-path": "^6.0.0", "nypm": "^0.6.10", "resolve.exports": "^2.0.3", "semver": "^7.8.5", "string-width": "^8.3.0", "which-command": "^0.1.0", "yaml": "^2.9.1", "zod": "^4.6.5" }, "peerDependencies": { "@biomejs/biome": "^2.5.0", "eslint": "^10.9.0", "oxfmt": ">=0.59.0", "oxlint": "^1.86.0", "prettier": "^3.0.0", "stylelint": "^17.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "oxfmt", "oxlint", "prettier", "stylelint"], "bin": { "ultracite": "dist/index.js" } }, "sha512-6jVEwaZBaytFm6XRoI3WGS2ZydrkrNvHTD0qTf7983ti2FlEWL1YcxnPaMaYuS6PkXqMhIK2XGr0cbsL4x5pQA=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"uqr": "^0.1.3"
|
"uqr": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.14",
|
"@biomejs/biome": "2.5.15",
|
||||||
"@sveltejs/adapter-auto": "^7.0.1",
|
"@sveltejs/adapter-auto": "^7.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.57.0",
|
"@sveltejs/kit": "^2.57.0",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"svelte-check": "^4.7.6",
|
"svelte-check": "^4.7.6",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"ultracite": "7.12.2",
|
"ultracite": "7.12.4",
|
||||||
"vite": "^8.0.7"
|
"vite": "^8.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,9 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
aria-label="Открыть профиль"
|
aria-label="Открыть профиль"
|
||||||
|
|||||||
@@ -59,7 +59,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
class="Chat ListItem-button"
|
class="Chat ListItem-button"
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet tree(items: EntityTreeNode[])}
|
{#snippet tree(
|
||||||
|
items: EntityTreeNode[]
|
||||||
|
)}
|
||||||
{#each items as node, i (i)}
|
{#each items as node, i (i)}
|
||||||
{#if node.kind === "text"}
|
{#if node.kind === "text"}
|
||||||
{node.text}
|
{node.text}
|
||||||
@@ -37,7 +39,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet entity(node: EntityNode)}
|
{#snippet entity(
|
||||||
|
node: EntityNode
|
||||||
|
)}
|
||||||
{@const type = node.entity.type}
|
{@const type = node.entity.type}
|
||||||
{#if type === "bold"}
|
{#if type === "bold"}
|
||||||
<strong>{@render tree(node.children)}</strong>
|
<strong>{@render tree(node.children)}</strong>
|
||||||
@@ -66,7 +70,10 @@
|
|||||||
>{#if node.entity.language}<span class="pre-lang">{node.entity.language}</span>{/if}<code>{@render tree(node.children)}</code></pre>
|
>{#if node.entity.language}<span class="pre-lang">{node.entity.language}</span>{/if}<code>{@render tree(node.children)}</code></pre>
|
||||||
{:else if type === "blockquote"}
|
{:else if type === "blockquote"}
|
||||||
<blockquote class="blockquote">{@render tree(node.children)}</blockquote>
|
<blockquote class="blockquote">{@render tree(node.children)}</blockquote>
|
||||||
{:else if type === "url" || type === "text_link" || type === "email" || type === "phone_number"}
|
{:else if type === "url" ||
|
||||||
|
type === "text_link" ||
|
||||||
|
type === "email" ||
|
||||||
|
type === "phone_number"}
|
||||||
<a
|
<a
|
||||||
class="link"
|
class="link"
|
||||||
href={linkHref(node)}
|
href={linkHref(node)}
|
||||||
@@ -75,7 +82,11 @@
|
|||||||
class:own
|
class:own
|
||||||
>{@render tree(node.children)}</a
|
>{@render tree(node.children)}</a
|
||||||
>
|
>
|
||||||
{:else if type === "mention" || type === "text_mention" || type === "hashtag" || type === "cashtag" || type === "bot_command"}
|
{:else if type === "mention" ||
|
||||||
|
type === "text_mention" ||
|
||||||
|
type === "hashtag" ||
|
||||||
|
type === "cashtag" ||
|
||||||
|
type === "bot_command"}
|
||||||
<span class="link" class:own>{@render tree(node.children)}</span>
|
<span class="link" class:own>{@render tree(node.children)}</span>
|
||||||
{:else}
|
{:else}
|
||||||
{@render tree(node.children)}
|
{@render tree(node.children)}
|
||||||
|
|||||||
@@ -50,7 +50,9 @@
|
|||||||
{:else if result.state === "ready"}
|
{:else if result.state === "ready"}
|
||||||
<a
|
<a
|
||||||
class="file"
|
class="file"
|
||||||
download={isActiveContent(result.mime) ? `media_${version.id}` : undefined}
|
download={isActiveContent(result.mime)
|
||||||
|
? `media_${version.id}`
|
||||||
|
: undefined}
|
||||||
href={result.url}
|
href={result.url}
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -184,7 +184,9 @@
|
|||||||
<div class="media-actions">
|
<div class="media-actions">
|
||||||
{#if canSave}
|
{#if canSave}
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
aria-label="Скачать файл"
|
aria-label="Скачать файл"
|
||||||
|
|||||||
@@ -131,7 +131,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
class="Message"
|
class="Message"
|
||||||
|
|||||||
@@ -137,7 +137,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<div {...props} class="message-media" use:visible={start}>
|
<div {...props} class="message-media" use:visible={start}>
|
||||||
{#if message.is_self_destruct}
|
{#if message.is_self_destruct}
|
||||||
<button class="media-chip self-destruct" onclick={onopen} type="button">
|
<button class="media-chip self-destruct" onclick={onopen} type="button">
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="Reactions" class:own>
|
<div class="Reactions" class:own>
|
||||||
{#each reactions as reaction, index (reaction.custom_emoji_id ?? reaction.emoji ?? index)}
|
{#each reactions as reaction, index (reaction.custom_emoji_id ??
|
||||||
|
reaction.emoji ??
|
||||||
|
index)}
|
||||||
<button
|
<button
|
||||||
class="reaction"
|
class="reaction"
|
||||||
{onclick}
|
{onclick}
|
||||||
|
|||||||
@@ -72,7 +72,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<span class="badge">
|
<span class="badge">
|
||||||
<Icon name="microphone" size="0.875rem" />
|
<Icon name="microphone" size="0.875rem" />
|
||||||
{formatDuration(paused && currentTime <= POSTER_TIME ? duration : remaining)}
|
{formatDuration(
|
||||||
|
paused && currentTime <= POSTER_TIME ? duration : remaining
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{#if transcript}
|
{#if transcript}
|
||||||
|
|||||||
@@ -172,7 +172,11 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet card(record: PolicyRecord, title: string, onremove?: () => void)}
|
{#snippet card(
|
||||||
|
record: PolicyRecord,
|
||||||
|
title: string,
|
||||||
|
onremove?: () => void
|
||||||
|
)}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<span class="card-title">{title}</span>
|
<span class="card-title">{title}</span>
|
||||||
|
|||||||
@@ -181,7 +181,9 @@
|
|||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#each items as item, index (item.id)}
|
{#each items as item, index (item.id)}
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
class="tile"
|
class="tile"
|
||||||
@@ -237,7 +239,9 @@
|
|||||||
{#each items as item, index (item.id)}
|
{#each items as item, index (item.id)}
|
||||||
<li>
|
<li>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
onclick={() => open(index)}
|
onclick={() => open(index)}
|
||||||
|
|||||||
@@ -178,7 +178,9 @@
|
|||||||
{@const state = shareState(share)}
|
{@const state = shareState(share)}
|
||||||
<li class="row" class:inactive={state !== "active"}>
|
<li class="row" class:inactive={state !== "active"}>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
class="head"
|
class="head"
|
||||||
|
|||||||
@@ -45,7 +45,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
class="tile"
|
class="tile"
|
||||||
|
|||||||
@@ -184,7 +184,7 @@
|
|||||||
<Dialog.Root bind:open>
|
<Dialog.Root bind:open>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay class="story-overlay" />
|
<Dialog.Overlay class="story-overlay" />
|
||||||
<Dialog.Content class="story-content {held ? 'held' : ''}">
|
<Dialog.Content class="story-content {held ? "held" : ""}">
|
||||||
<Dialog.Title class="story-a11y-title">Сторис</Dialog.Title>
|
<Dialog.Title class="story-a11y-title">Сторис</Dialog.Title>
|
||||||
<div class="bars">
|
<div class="bars">
|
||||||
{#each items as item, i (item.story_id)}
|
{#each items as item, i (item.story_id)}
|
||||||
@@ -226,7 +226,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if canSave}
|
{#if canSave}
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
{#snippet children({ props })}
|
{#snippet children({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
aria-label="Скачать сторис"
|
aria-label="Скачать сторис"
|
||||||
|
|||||||
@@ -48,7 +48,9 @@
|
|||||||
|
|
||||||
<ContextMenu.Root bind:open>
|
<ContextMenu.Root bind:open>
|
||||||
<ContextMenu.Trigger>
|
<ContextMenu.Trigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({
|
||||||
|
props,
|
||||||
|
})}
|
||||||
{@render children({ props: triggerProps(props) })}
|
{@render children({ props: triggerProps(props) })}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
|
|||||||
Reference in new issue
Block a user