chatwire 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chatwire-0.2.2/chatwire.egg-info → chatwire-0.3.0}/PKG-INFO +17 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/README.md +16 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/_version.py +1 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/bridge.py +1 -1
- {chatwire-0.2.2 → chatwire-0.3.0/chatwire.egg-info}/PKG-INFO +17 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire.egg-info/SOURCES.txt +1 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire_cli.py +12 -2
- {chatwire-0.2.2 → chatwire-0.3.0}/config.py +55 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/echo_log.py +4 -3
- chatwire-0.3.0/migrations/0003_state_dir_paths.py +30 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/main.py +5 -4
- {chatwire-0.2.2 → chatwire-0.3.0}/web/static/update-check.js +6 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/web/templates/index.html +1 -1
- {chatwire-0.2.2 → chatwire-0.3.0}/whitelist.py +3 -2
- {chatwire-0.2.2 → chatwire-0.3.0}/LICENSE +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chat_db.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chat_send.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire.egg-info/dependency_links.txt +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire.egg-info/entry_points.txt +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire.egg-info/requires.txt +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/chatwire.egg-info/top_level.txt +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/contacts.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/integrations/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/integrations/base.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/integrations/telegram/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/integrations/web/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/integrations/webhook/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/migrations/0001_initial.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/migrations/0002_integration_split.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/migrations/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/prefix.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/pyproject.toml +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/setup.cfg +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/templates/__init__.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/templates/launchd/bridge.plist.template +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/templates/launchd/keepawake.plist.template +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/templates/launchd/web.plist.template +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/setup_wizard.py +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/static/favicon.svg +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/static/style.css +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/static/sw.js +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/templates/_conversation.html +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/templates/_conversations.html +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/templates/_settings.html +0 -0
- {chatwire-0.2.2 → chatwire-0.3.0}/web/templates/_whitelist_rows.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatwire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: macOS chat-relay bridge: iMessage <-> Telegram, web UI, and pluggable integrations
|
|
5
5
|
Author: Allen Bina
|
|
6
6
|
License: MIT License
|
|
@@ -184,6 +184,22 @@ the first install, and it's the same on every Mac.
|
|
|
184
184
|
`scripts/check-permissions.sh` (or `chatwire doctor`) will tell you
|
|
185
185
|
which prompts you still need to click.
|
|
186
186
|
|
|
187
|
+
## Privacy
|
|
188
|
+
|
|
189
|
+
**No telemetry.** chatwire collects no analytics, sends no usage data, and
|
|
190
|
+
includes no third-party SDKs that report back. Your messages, contacts,
|
|
191
|
+
and `chat.db` stay on your Mac; outbound traffic only goes to integrations
|
|
192
|
+
you configure (e.g. Telegram via your own bot token).
|
|
193
|
+
|
|
194
|
+
Two narrow third-party requests the web UI does make, neither carrying any
|
|
195
|
+
of your data:
|
|
196
|
+
|
|
197
|
+
- The update-check banner fetches `api.github.com/repos/<repo>/releases/latest`
|
|
198
|
+
once a day to surface new-version notices. Disable by setting
|
|
199
|
+
`UPDATE_CHECK_REPO=""` in the launchd agent's environment.
|
|
200
|
+
- Static assets (htmx, emoji-picker-element) load from `unpkg.com` and
|
|
201
|
+
`cdn.jsdelivr.net`.
|
|
202
|
+
|
|
187
203
|
## Repo layout
|
|
188
204
|
|
|
189
205
|
```
|
|
@@ -128,6 +128,22 @@ the first install, and it's the same on every Mac.
|
|
|
128
128
|
`scripts/check-permissions.sh` (or `chatwire doctor`) will tell you
|
|
129
129
|
which prompts you still need to click.
|
|
130
130
|
|
|
131
|
+
## Privacy
|
|
132
|
+
|
|
133
|
+
**No telemetry.** chatwire collects no analytics, sends no usage data, and
|
|
134
|
+
includes no third-party SDKs that report back. Your messages, contacts,
|
|
135
|
+
and `chat.db` stay on your Mac; outbound traffic only goes to integrations
|
|
136
|
+
you configure (e.g. Telegram via your own bot token).
|
|
137
|
+
|
|
138
|
+
Two narrow third-party requests the web UI does make, neither carrying any
|
|
139
|
+
of your data:
|
|
140
|
+
|
|
141
|
+
- The update-check banner fetches `api.github.com/repos/<repo>/releases/latest`
|
|
142
|
+
once a day to surface new-version notices. Disable by setting
|
|
143
|
+
`UPDATE_CHECK_REPO=""` in the launchd agent's environment.
|
|
144
|
+
- Static assets (htmx, emoji-picker-element) load from `unpkg.com` and
|
|
145
|
+
`cdn.jsdelivr.net`.
|
|
146
|
+
|
|
131
147
|
## Repo layout
|
|
132
148
|
|
|
133
149
|
```
|
|
@@ -30,6 +30,7 @@ import inspect
|
|
|
30
30
|
|
|
31
31
|
import config as _bridge_config # noqa: E402 — must run before env-consuming imports
|
|
32
32
|
CFG = _bridge_config.apply_to_environ()
|
|
33
|
+
from config import STATE_DIR # noqa: E402
|
|
33
34
|
|
|
34
35
|
from chat_db import ChatDBReader, InboundMessage
|
|
35
36
|
from contacts import load_lookup as load_contacts
|
|
@@ -66,7 +67,6 @@ POLL_INTERVAL_S = float(os.environ.get("POLL_INTERVAL_S", "2"))
|
|
|
66
67
|
# normal operation; safe to `tail -f` from a separate SSH session.
|
|
67
68
|
DEBUG_MIRROR_FILE = os.environ.get("DEBUG_MIRROR_FILE", "").strip() or None
|
|
68
69
|
|
|
69
|
-
STATE_DIR = Path.home() / ".imessage-tg"
|
|
70
70
|
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
71
71
|
STATE_PATH = STATE_DIR / "state.json"
|
|
72
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatwire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: macOS chat-relay bridge: iMessage <-> Telegram, web UI, and pluggable integrations
|
|
5
5
|
Author: Allen Bina
|
|
6
6
|
License: MIT License
|
|
@@ -184,6 +184,22 @@ the first install, and it's the same on every Mac.
|
|
|
184
184
|
`scripts/check-permissions.sh` (or `chatwire doctor`) will tell you
|
|
185
185
|
which prompts you still need to click.
|
|
186
186
|
|
|
187
|
+
## Privacy
|
|
188
|
+
|
|
189
|
+
**No telemetry.** chatwire collects no analytics, sends no usage data, and
|
|
190
|
+
includes no third-party SDKs that report back. Your messages, contacts,
|
|
191
|
+
and `chat.db` stay on your Mac; outbound traffic only goes to integrations
|
|
192
|
+
you configure (e.g. Telegram via your own bot token).
|
|
193
|
+
|
|
194
|
+
Two narrow third-party requests the web UI does make, neither carrying any
|
|
195
|
+
of your data:
|
|
196
|
+
|
|
197
|
+
- The update-check banner fetches `api.github.com/repos/<repo>/releases/latest`
|
|
198
|
+
once a day to surface new-version notices. Disable by setting
|
|
199
|
+
`UPDATE_CHECK_REPO=""` in the launchd agent's environment.
|
|
200
|
+
- Static assets (htmx, emoji-picker-element) load from `unpkg.com` and
|
|
201
|
+
`cdn.jsdelivr.net`.
|
|
202
|
+
|
|
187
203
|
## Repo layout
|
|
188
204
|
|
|
189
205
|
```
|
|
@@ -24,6 +24,7 @@ integrations/web/__init__.py
|
|
|
24
24
|
integrations/webhook/__init__.py
|
|
25
25
|
migrations/0001_initial.py
|
|
26
26
|
migrations/0002_integration_split.py
|
|
27
|
+
migrations/0003_state_dir_paths.py
|
|
27
28
|
migrations/__init__.py
|
|
28
29
|
templates/__init__.py
|
|
29
30
|
templates/launchd/bridge.plist.template
|
|
@@ -276,10 +276,20 @@ def cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
276
276
|
|
|
277
277
|
|
|
278
278
|
def cmd_migrate(args: argparse.Namespace) -> int:
|
|
279
|
-
|
|
280
|
-
if
|
|
279
|
+
env_ran = config.migrate_legacy_env()
|
|
280
|
+
if env_ran:
|
|
281
281
|
print(f"migrated {config.LEGACY_ENV_PATH} → {config.CONFIG_PATH}")
|
|
282
282
|
print(f"chmod 600 enforced. legacy file left in place — delete after verification.")
|
|
283
|
+
|
|
284
|
+
state_copied = config.migrate_state_dir()
|
|
285
|
+
if state_copied:
|
|
286
|
+
print(f"copied {len(state_copied)} state file(s) "
|
|
287
|
+
f"{config.LEGACY_STATE_DIR} → {config.STATE_DIR}: "
|
|
288
|
+
f"{', '.join(state_copied)}")
|
|
289
|
+
print(f"legacy dir left in place — `rm -rf {config.LEGACY_STATE_DIR}` "
|
|
290
|
+
f"after verifying agents are healthy.")
|
|
291
|
+
|
|
292
|
+
if env_ran or state_copied:
|
|
283
293
|
return 0
|
|
284
294
|
if config.CONFIG_PATH.exists():
|
|
285
295
|
print(f"already migrated: {config.CONFIG_PATH} exists. nothing to do.")
|
|
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|
|
22
22
|
import json
|
|
23
23
|
import logging
|
|
24
24
|
import os
|
|
25
|
+
import shutil
|
|
25
26
|
import stat
|
|
26
27
|
from pathlib import Path
|
|
27
28
|
|
|
@@ -30,6 +31,13 @@ log = logging.getLogger("chatwire.config")
|
|
|
30
31
|
CONFIG_DIR = Path.home() / ".chatwire"
|
|
31
32
|
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
32
33
|
|
|
34
|
+
# Runtime state files (state.json, whitelist.json, echo_log.jsonl, mirror.jsonl,
|
|
35
|
+
# push_subs.json, thumb_cache/) live alongside config under the same dir.
|
|
36
|
+
# Same path as CONFIG_DIR by design — exposing it as its own name lets state
|
|
37
|
+
# consumers (bridge.py, whitelist.py, echo_log.py, web/main.py) import the
|
|
38
|
+
# concept without pretending they're reading config.
|
|
39
|
+
STATE_DIR = CONFIG_DIR
|
|
40
|
+
|
|
33
41
|
# Read-only fallbacks for prior config locations. First save under the new
|
|
34
42
|
# CONFIG_PATH migrates a user off the legacy path. Order matters: most
|
|
35
43
|
# recent first, so a user mid-rename gets the freshest snapshot.
|
|
@@ -41,7 +49,19 @@ LEGACY_CONFIG_PATHS: list[Path] = [
|
|
|
41
49
|
LEGACY_ENV_DIR = Path.home() / ".imessage-tg"
|
|
42
50
|
LEGACY_ENV_PATH = LEGACY_ENV_DIR / ".env"
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
# Pre-0.3.0 the runtime state lived alongside the legacy .env in this dir.
|
|
53
|
+
# `migrate_state_dir()` copies these into STATE_DIR on first 0.3.0 boot.
|
|
54
|
+
LEGACY_STATE_DIR = LEGACY_ENV_DIR
|
|
55
|
+
STATE_FILES = (
|
|
56
|
+
"state.json",
|
|
57
|
+
"whitelist.json",
|
|
58
|
+
"echo_log.jsonl",
|
|
59
|
+
"mirror.jsonl",
|
|
60
|
+
"push_subs.json",
|
|
61
|
+
"thumb_cache", # directory
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
CURRENT_VERSION = 3
|
|
45
65
|
|
|
46
66
|
|
|
47
67
|
def _read_legacy_env() -> dict[str, str]:
|
|
@@ -122,6 +142,7 @@ def apply_to_environ() -> dict:
|
|
|
122
142
|
if path.exists():
|
|
123
143
|
_ensure_secure(path)
|
|
124
144
|
break
|
|
145
|
+
migrate_state_dir()
|
|
125
146
|
cfg = load_config()
|
|
126
147
|
cfg = _run_migrations(cfg)
|
|
127
148
|
for k, v in _flatten_v2_to_env(cfg).items():
|
|
@@ -217,6 +238,39 @@ def save_config(cfg: dict) -> None:
|
|
|
217
238
|
tmp.replace(CONFIG_PATH)
|
|
218
239
|
|
|
219
240
|
|
|
241
|
+
def migrate_state_dir() -> list[str]:
|
|
242
|
+
"""Copy any pre-0.3.0 state files from LEGACY_STATE_DIR into STATE_DIR.
|
|
243
|
+
|
|
244
|
+
Idempotent by file-existence check: a file is only copied when the
|
|
245
|
+
destination doesn't already exist. The legacy dir is left in place so
|
|
246
|
+
the operator can verify and remove it manually.
|
|
247
|
+
|
|
248
|
+
Returns the list of file/dir names that were copied (empty list = no-op).
|
|
249
|
+
"""
|
|
250
|
+
if not LEGACY_STATE_DIR.exists() or LEGACY_STATE_DIR == STATE_DIR:
|
|
251
|
+
return []
|
|
252
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
253
|
+
copied: list[str] = []
|
|
254
|
+
for name in STATE_FILES:
|
|
255
|
+
src = LEGACY_STATE_DIR / name
|
|
256
|
+
dst = STATE_DIR / name
|
|
257
|
+
if not src.exists() or dst.exists():
|
|
258
|
+
continue
|
|
259
|
+
try:
|
|
260
|
+
if src.is_dir():
|
|
261
|
+
shutil.copytree(src, dst)
|
|
262
|
+
else:
|
|
263
|
+
shutil.copy2(src, dst)
|
|
264
|
+
except OSError:
|
|
265
|
+
log.exception("failed to copy %s → %s", src, dst)
|
|
266
|
+
continue
|
|
267
|
+
copied.append(name)
|
|
268
|
+
if copied:
|
|
269
|
+
log.info("migrated %d state file(s) from %s → %s: %s",
|
|
270
|
+
len(copied), LEGACY_STATE_DIR, STATE_DIR, ", ".join(copied))
|
|
271
|
+
return copied
|
|
272
|
+
|
|
273
|
+
|
|
220
274
|
def migrate_legacy_env() -> bool:
|
|
221
275
|
"""If config.json is absent and a legacy .env exists, write config.json
|
|
222
276
|
from it. Returns True if a migration ran. Idempotent: a no-op once
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
agree on "what we just sent" — used to suppress chat.db echoes of bridge-
|
|
3
3
|
originated outbound. Both processes append; the bridge consults on each poll.
|
|
4
4
|
|
|
5
|
-
Format: JSONL at ~/.
|
|
5
|
+
Format: JSONL at ~/.chatwire/echo_log.jsonl
|
|
6
6
|
{"t": <epoch>, "h": <handle_lc>, "k": "text"|"photo", "b": <body or null>}
|
|
7
7
|
|
|
8
8
|
Tail-only consumer: `seen_recently` reads the last ~200 lines and checks
|
|
@@ -12,9 +12,10 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
import time
|
|
15
|
-
from pathlib import Path
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
from config import STATE_DIR
|
|
17
|
+
|
|
18
|
+
LOG = STATE_DIR / "echo_log.jsonl"
|
|
18
19
|
LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
19
20
|
TAIL_LINES = 200
|
|
20
21
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Repoint `debug.mirror_file` from the legacy state dir to the new one.
|
|
2
|
+
|
|
3
|
+
0.3.0 moves runtime state from `~/.imessage-tg/` to `~/.chatwire/` (see
|
|
4
|
+
`config.migrate_state_dir`). The mirror file is the one piece of state
|
|
5
|
+
whose location the user can override via config, so an explicit user
|
|
6
|
+
value at the legacy *default* gets bumped here. Anything pointed
|
|
7
|
+
elsewhere on purpose is left alone.
|
|
8
|
+
|
|
9
|
+
Pairs with the filesystem-level copy in `migrate_state_dir()` — that
|
|
10
|
+
moves the file's bytes; this updates the pointer in config.json.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
target_version = 3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def migrate(cfg: dict) -> dict:
|
|
20
|
+
debug = cfg.get("debug")
|
|
21
|
+
if not isinstance(debug, dict):
|
|
22
|
+
return cfg
|
|
23
|
+
current = debug.get("mirror_file")
|
|
24
|
+
if not isinstance(current, str):
|
|
25
|
+
return cfg
|
|
26
|
+
legacy_default = str(Path.home() / ".imessage-tg" / "mirror.jsonl")
|
|
27
|
+
new_default = str(Path.home() / ".chatwire" / "mirror.jsonl")
|
|
28
|
+
if current == legacy_default:
|
|
29
|
+
debug["mirror_file"] = new_default
|
|
30
|
+
return cfg
|
|
@@ -26,7 +26,7 @@ import logging
|
|
|
26
26
|
from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile
|
|
27
27
|
|
|
28
28
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
|
|
29
|
-
log = logging.getLogger("
|
|
29
|
+
log = logging.getLogger("chatwire.web")
|
|
30
30
|
from fastapi.responses import HTMLResponse, StreamingResponse, FileResponse
|
|
31
31
|
from fastapi.staticfiles import StaticFiles
|
|
32
32
|
from fastapi.templating import Jinja2Templates
|
|
@@ -49,6 +49,7 @@ from whitelist import ( # noqa: E402
|
|
|
49
49
|
# Load config from ~/.chatwire/config.json (or legacy fallbacks).
|
|
50
50
|
import config as _bridge_config # noqa: E402
|
|
51
51
|
_bridge_config.apply_to_environ()
|
|
52
|
+
from config import STATE_DIR # noqa: E402
|
|
52
53
|
|
|
53
54
|
SELF_HANDLES = {h.strip().lower() for h in os.environ.get("SELF_HANDLES", "").split(",") if h.strip()}
|
|
54
55
|
|
|
@@ -57,7 +58,7 @@ def relay_handles() -> set[str]:
|
|
|
57
58
|
return SELF_HANDLES | wl_all()
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
MIRROR_FILE = Path(os.environ.get("DEBUG_MIRROR_FILE", str(
|
|
61
|
+
MIRROR_FILE = Path(os.environ.get("DEBUG_MIRROR_FILE", str(STATE_DIR / "mirror.jsonl")))
|
|
61
62
|
|
|
62
63
|
WEB_PORT = int(os.environ.get("WEB_PORT", "8723"))
|
|
63
64
|
HISTORY_LIMIT = 100
|
|
@@ -66,7 +67,7 @@ CONVO_LIMIT = 50
|
|
|
66
67
|
VAPID_PRIVATE_KEY = os.environ.get("VAPID_PRIVATE_KEY", "")
|
|
67
68
|
VAPID_PUBLIC_KEY = os.environ.get("VAPID_PUBLIC_KEY", "")
|
|
68
69
|
VAPID_CONTACT = os.environ.get("VAPID_CONTACT", "mailto:admin@example.com")
|
|
69
|
-
PUSH_SUBS_FILE =
|
|
70
|
+
PUSH_SUBS_FILE = STATE_DIR / "push_subs.json"
|
|
70
71
|
|
|
71
72
|
CONTACTS = load_contacts()
|
|
72
73
|
IMAGE_INDEX = load_image_index()
|
|
@@ -232,7 +233,7 @@ ATTACHMENTS_BASE = (Path.home() / "Library" / "Messages" / "Attachments").resolv
|
|
|
232
233
|
# On-disk thumb cache. Lives outside the Messages.app attachments dir so we
|
|
233
234
|
# never risk Messages noticing extra files alongside the originals. Keyed by
|
|
234
235
|
# (path, mtime) so renamed/replaced originals invalidate cleanly.
|
|
235
|
-
THUMB_CACHE_DIR = (
|
|
236
|
+
THUMB_CACHE_DIR = (STATE_DIR / "thumb_cache").resolve()
|
|
236
237
|
THUMB_MAX_EDGE = 720 # px; covers retina at the chat's ~280–360 displayed size
|
|
237
238
|
THUMB_TTL_DAYS = 180
|
|
238
239
|
|
|
@@ -58,12 +58,17 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function showBanner(latestTag, releaseUrl) {
|
|
61
|
+
if (!latestTag) return;
|
|
61
62
|
const banner = document.getElementById('update-banner');
|
|
62
63
|
if (!banner) return;
|
|
63
64
|
const v = banner.querySelector('.update-banner-version');
|
|
64
65
|
const a = banner.querySelector('.update-banner-link');
|
|
65
66
|
if (v) v.textContent = latestTag;
|
|
66
|
-
if (a
|
|
67
|
+
if (a) {
|
|
68
|
+
const repo = meta('update-check-repo');
|
|
69
|
+
const fallback = repo ? 'https://github.com/' + repo + '/releases' : '#';
|
|
70
|
+
a.setAttribute('href', releaseUrl || fallback);
|
|
71
|
+
}
|
|
67
72
|
banner.hidden = false;
|
|
68
73
|
document.body.classList.add('has-update-banner');
|
|
69
74
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<strong>Update available:</strong>
|
|
21
21
|
<span class="update-banner-version"></span>
|
|
22
22
|
—
|
|
23
|
-
<a class="update-banner-link" href="" target="_blank" rel="noopener">release notes</a>
|
|
23
|
+
<a class="update-banner-link" href="#" target="_blank" rel="noopener">release notes</a>
|
|
24
24
|
</span>
|
|
25
25
|
<button type="button" class="update-banner-dismiss" aria-label="Dismiss">×</button>
|
|
26
26
|
</div>
|
|
@@ -20,11 +20,12 @@ import json
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
22
|
import threading
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
from config import STATE_DIR
|
|
24
25
|
|
|
25
26
|
log = logging.getLogger("whitelist")
|
|
26
27
|
|
|
27
|
-
WHITELIST_FILE =
|
|
28
|
+
WHITELIST_FILE = STATE_DIR / "whitelist.json"
|
|
28
29
|
|
|
29
30
|
_lock = threading.Lock()
|
|
30
31
|
_cached_handles: set[str] = set()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|