mirror of
https://github.com/by-sonic/tglock.git
synced 2026-09-05 18:16:09 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cb72e4acc | |||
| 60ae065938 | |||
| 1a56e1c96e |
@@ -64,13 +64,20 @@ jobs:
|
||||
with:
|
||||
tagName: v__VERSION__
|
||||
releaseName: TGLock v__VERSION__
|
||||
# Только инструкция по скачиванию: она одна и та же для любой версии.
|
||||
# Список изменений дописывается в release notes при выпуске — иначе
|
||||
# выходит как в бетах 1-7, где каждая несла один и тот же текст «новый
|
||||
# TGLock, полностью обновлённый интерфейс» и понять, что поменялось,
|
||||
# было невозможно.
|
||||
releaseBody: |
|
||||
Новый TGLock: адаптивный MTProto/WebSocket-транспорт и полностью обновлённый интерфейс на Tauri 2.
|
||||
## Что скачивать
|
||||
|
||||
- macOS: скачайте универсальный `.dmg` или `.app.tar.gz`
|
||||
- Windows: скачайте `.exe` установщик
|
||||
- Linux: скачайте `.AppImage` или `.deb`
|
||||
- Сервер или машина без монитора: скачайте `tglock-cli-*` — там нет графического интерфейса
|
||||
- **Windows** — `.exe`, установщик
|
||||
- **macOS** — универсальный `.dmg` (или `.app.tar.gz`)
|
||||
- **Linux** — `.AppImage` или `.deb`
|
||||
- **Сервер, контейнер, машина без монитора** — `tglock-cli-*`: то же самое, но без графического интерфейса
|
||||
|
||||
Если Windows ругается на файл — [почему так и что с этим делать](https://github.com/by-sonic/tglock#-антивирус-ругается-smartscreen-предупреждает-virustotal-показывает-детекты).
|
||||
releaseDraft: false
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
|
||||
Generated
+1
-1
@@ -3553,7 +3553,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tglock"
|
||||
version = "2.0.0-beta.7"
|
||||
version = "2.0.0-beta.8"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cipher",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tglock"
|
||||
version = "2.0.0-beta.7"
|
||||
version = "2.0.0-beta.8"
|
||||
edition = "2021"
|
||||
rust-version = "1.88"
|
||||
description = "Telegram unblock via local WebSocket tunnel"
|
||||
|
||||
@@ -155,7 +155,11 @@ Telegram → Настройки → **Продвинутые** → Тип сое
|
||||
|
||||
В окне TGLock включи галочку **LAN** — приложение начнёт слушать на `0.0.0.0`. Все устройства в домашней сети (телефон, планшет, ноутбук, телевизор) смогут подключиться к `<IP-компьютера>:1080` и тоже получить рабочий Telegram.
|
||||
|
||||
Ссылку `tg://proxy` с уже подставленным адресом TGLock открывает сам при включении — её достаточно переслать себе в Telegram и открыть на телефоне. Если нужен адрес руками: `ipconfig` на Windows, `ip a` на Linux, `ifconfig` на macOS.
|
||||
Когда LAN-режим включён, TGLock показывает в окне **готовый адрес** вида `192.168.1.7:1080` — нажми на него, чтобы скопировать. Плюс он сам открывает ссылку `tg://proxy`, где уже подставлены адрес, порт и секрет: её достаточно переслать себе в Telegram и открыть на телефоне.
|
||||
|
||||
> ⚠️ На другом устройстве **не вводи `127.0.0.1`** — для него это оно само, а не твой компьютер. Нужен именно тот адрес, который показан в окне TGLock. Это же касается телефона в эмуляторе (MuMuPlayer, BlueStacks и подобных): внутри эмулятора Android — отдельная машина со своей сетью.
|
||||
|
||||
Если адрес нужен руками: `ipconfig` на Windows, `ip a` на Linux, `ifconfig` на macOS.
|
||||
|
||||
Удобно, если дома один компьютер всегда включён — он становится «домашним Telegram-роутером».
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tglock-ui",
|
||||
"private": true,
|
||||
"version": "2.0.0-beta.7",
|
||||
"version": "2.0.0-beta.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 1420",
|
||||
|
||||
+57
@@ -49,6 +49,13 @@ struct StatusSnapshot {
|
||||
route_failures: u32,
|
||||
uptime_seconds: u64,
|
||||
port: u16,
|
||||
/// Адрес, который нужно вписать в Telegram на другом устройстве.
|
||||
///
|
||||
/// В LAN-режиме это адрес этого компьютера в локальной сети. Люди искали
|
||||
/// его в интерфейсе и не находили: вписывали `127.0.0.1`, который на
|
||||
/// телефоне или в эмуляторе означает само устройство, и подключение не
|
||||
/// работало (by-sonic/tglock#36).
|
||||
share_address: Option<String>,
|
||||
logs: Vec<LogLine>,
|
||||
}
|
||||
|
||||
@@ -56,6 +63,10 @@ struct AppState {
|
||||
stats: Arc<proxy::Stats>,
|
||||
settings: Mutex<Settings>,
|
||||
active_port: Mutex<u16>,
|
||||
/// Слушатель работающего прокси. Нужен, чтобы показать адрес для других
|
||||
/// устройств именно тот, на котором прокси реально поднят, а не тот, что
|
||||
/// сейчас выбран в настройках.
|
||||
active_listen: Mutex<Option<ListenConfig>>,
|
||||
started_at: Mutex<Option<Instant>>,
|
||||
logs: Arc<Mutex<Vec<LogLine>>>,
|
||||
settings_path: PathBuf,
|
||||
@@ -71,6 +82,7 @@ impl AppState {
|
||||
stats: proxy::Stats::new(),
|
||||
settings: Mutex::new(settings),
|
||||
active_port: Mutex::new(proxy::DEFAULT_PORT),
|
||||
active_listen: Mutex::new(None),
|
||||
started_at: Mutex::new(None),
|
||||
logs: Arc::new(Mutex::new(Vec::new())),
|
||||
settings_path,
|
||||
@@ -106,6 +118,7 @@ impl AppState {
|
||||
.unwrap()
|
||||
.map_or(0, |started| started.elapsed().as_secs()),
|
||||
port: *self.active_port.lock().unwrap(),
|
||||
share_address: share_address(*self.active_listen.lock().unwrap()),
|
||||
logs: self.logs.lock().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
@@ -122,6 +135,18 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Адрес, который нужно вписать в Telegram на другом устройстве.
|
||||
///
|
||||
/// Только для слушателя на `0.0.0.0`: на loopback делиться нечем, туда никто
|
||||
/// извне не достучится. Возвращается адрес этой машины в сети, а не `0.0.0.0`
|
||||
/// и не `127.0.0.1` — последний на телефоне или в эмуляторе означает само
|
||||
/// устройство, и именно на этом спотыкались (by-sonic/tglock#36).
|
||||
fn share_address(listen: Option<ListenConfig>) -> Option<String> {
|
||||
listen
|
||||
.filter(|listen| listen.addr.ip().is_unspecified())
|
||||
.map(|listen| format!("{}:{}", listen.advertised_host(), listen.addr.port()))
|
||||
}
|
||||
|
||||
fn current_time() -> String {
|
||||
let seconds = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -177,6 +202,8 @@ fn start_proxy(state: State<'_, AppState>) -> Result<StatusSnapshot, String> {
|
||||
ListenConfig::loopback(settings.port)
|
||||
};
|
||||
|
||||
*state.active_listen.lock().unwrap() = Some(listen);
|
||||
|
||||
let stats = state.stats.clone();
|
||||
let logs = state.logs.clone();
|
||||
std::thread::spawn(move || {
|
||||
@@ -194,6 +221,7 @@ fn start_proxy(state: State<'_, AppState>) -> Result<StatusSnapshot, String> {
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(220));
|
||||
if !state.stats.running.load(Ordering::SeqCst) {
|
||||
*state.active_listen.lock().unwrap() = None;
|
||||
*state.started_at.lock().unwrap() = None;
|
||||
return Err(state
|
||||
.logs
|
||||
@@ -213,6 +241,7 @@ fn start_proxy(state: State<'_, AppState>) -> Result<StatusSnapshot, String> {
|
||||
#[tauri::command]
|
||||
fn stop_proxy(state: State<'_, AppState>) -> StatusSnapshot {
|
||||
state.stats.stop();
|
||||
*state.active_listen.lock().unwrap() = None;
|
||||
*state.started_at.lock().unwrap() = None;
|
||||
state.log("Защита выключена", false);
|
||||
state.snapshot()
|
||||
@@ -311,6 +340,34 @@ fn main() {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn nothing_to_share_when_the_proxy_is_off_or_local() {
|
||||
assert_eq!(
|
||||
share_address(None),
|
||||
None,
|
||||
"выключенный прокси нечего делить"
|
||||
);
|
||||
assert_eq!(
|
||||
share_address(Some(ListenConfig::loopback(1080))),
|
||||
None,
|
||||
"на loopback снаружи никто не подключится"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lan_mode_shares_a_reachable_address() {
|
||||
let shown = share_address(Some(ListenConfig::lan(1443))).expect("в LAN-режиме адрес нужен");
|
||||
assert!(shown.ends_with(":1443"), "порт должен быть виден: {shown}");
|
||||
assert!(
|
||||
!shown.starts_with("0.0.0.0"),
|
||||
"0.0.0.0 нельзя вписать в Telegram: {shown}"
|
||||
);
|
||||
assert!(
|
||||
!shown.starts_with("127.0.0.1"),
|
||||
"127.0.0.1 на другом устройстве означает само устройство: {shown}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn software_rendering_is_requested_by_default() {
|
||||
let vars = software_rendering_vars(false, |_| false);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "TGLock",
|
||||
"version": "2.0.0-beta.7",
|
||||
"version": "2.0.0-beta.8",
|
||||
"identifier": "com.bysonic.tglock",
|
||||
"mainBinaryName": "tglock",
|
||||
"build": {
|
||||
|
||||
+28
@@ -13,6 +13,8 @@ type Status = {
|
||||
routeFailures: number;
|
||||
uptimeSeconds: number;
|
||||
port: number;
|
||||
/// Адрес для других устройств. Приходит только в LAN-режиме.
|
||||
shareAddress: string | null;
|
||||
logs: LogLine[];
|
||||
};
|
||||
|
||||
@@ -41,6 +43,7 @@ let status: Status = {
|
||||
routeFailures: 0,
|
||||
uptimeSeconds: 0,
|
||||
port: 1080,
|
||||
shareAddress: null,
|
||||
logs: [],
|
||||
};
|
||||
let settings: Settings = { lanMode: false, port: 1080, workerDomain: "" };
|
||||
@@ -147,6 +150,18 @@ function renderHome(): void {
|
||||
<span>порт ${status.port}</span>
|
||||
</div>
|
||||
|
||||
${status.shareAddress ? `
|
||||
<div class="share-card">
|
||||
<div class="share-label">Адрес для других устройств</div>
|
||||
<button id="copy-address" class="share-address" title="Нажмите, чтобы скопировать">
|
||||
${escapeHtml(status.shareAddress)}
|
||||
</button>
|
||||
<div class="share-hint">
|
||||
Впишите его в Telegram на телефоне. Не <code>127.0.0.1</code> — на другом
|
||||
устройстве это означает само устройство.
|
||||
</div>
|
||||
</div>` : ""}
|
||||
|
||||
<nav class="bottom-nav" aria-label="Разделы">
|
||||
<button id="settings-nav" class="nav-button">
|
||||
<span class="nav-icon">${icons.settings}</span>
|
||||
@@ -162,6 +177,7 @@ function renderHome(): void {
|
||||
`, "home-page");
|
||||
|
||||
document.querySelector("#power")?.addEventListener("click", toggleProtection);
|
||||
document.querySelector("#copy-address")?.addEventListener("click", copyShareAddress);
|
||||
document.querySelector("#settings-nav")?.addEventListener("click", () => navigate("settings"));
|
||||
document.querySelector("#diagnostics-nav")?.addEventListener("click", () => navigate("diagnostics"));
|
||||
}
|
||||
@@ -368,6 +384,18 @@ async function saveSettings(event: Event): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyShareAddress(): Promise<void> {
|
||||
const address = status.shareAddress;
|
||||
if (!address) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
showToast("Адрес скопирован");
|
||||
} catch {
|
||||
// Буфер обмена может быть недоступен — адрес и так виден на экране.
|
||||
showToast("Скопируйте адрес вручную", true);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message: string, error = false): void {
|
||||
window.clearTimeout(toastTimer);
|
||||
window.requestAnimationFrame(() => {
|
||||
|
||||
@@ -847,3 +847,53 @@ input:disabled {
|
||||
0 19px 52px rgba(9, 182, 133, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* Адрес для подключения других устройств. Показывается только в LAN-режиме:
|
||||
люди искали его в интерфейсе, не находили и вписывали 127.0.0.1, который на
|
||||
телефоне означает сам телефон (by-sonic/tglock#36). */
|
||||
.share-card {
|
||||
margin-top: 14px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: rgba(42, 171, 238, 0.08);
|
||||
border: 1px solid rgba(42, 171, 238, 0.24);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.share-label {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--text-muted, #8b949e);
|
||||
}
|
||||
|
||||
.share-address {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 6px 0 8px;
|
||||
padding: 6px 8px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: var(--accent, #2aabee);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
|
||||
.share-address:hover,
|
||||
.share-address:focus-visible {
|
||||
background: rgba(42, 171, 238, 0.14);
|
||||
}
|
||||
|
||||
.share-hint {
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-muted, #8b949e);
|
||||
}
|
||||
|
||||
.share-hint code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user