Modular v1 API (REST + SignalR) and on-demand mobile app builds - #116
Merged
Conversation
…ds on-demand
Modular API v1 (/api/v1) exposing the full web feature set for a future mobile
app, plus live transfer progress over SignalR, plus complete English docs.
API
- Controllers under Controllers/Api/V1: Auth (phone + QR login), Channels,
Files, Transfers, LocalFiles, Playlists, Shares, Config, System.
- Consistent envelope {success,data,error,message,page} with stable error codes.
- SignalR hub /hubs/transfers (TransferHub) bridged from TransactionInfoService
by a hosted TransferBroadcastService (snapshot/summary/speed messages).
- DTOs in Models/Api; helpers in Services/Api (QrLoginSessionManager,
ChannelFolderResolver, TransferSnapshotBuilder, upload staging).
- API-key middleware now covers /api/v1 and /hubs, accepting X-Api-Key,
?apiKey=, ?access_token= (WebSocket) and Authorization: Bearer (negotiate).
- Second Swagger document "api-v1" at /swagger/api-v1/swagger.json (/api-docs),
built from XML doc comments (GenerateDocumentationFile enabled).
- Verified at runtime: OpenAPI generation, config clamping, full SignalR
handshake with snapshot-on-connect.
Release policy
- buildrelease.yml: a plain v* release now builds only the Server. The mobile /
desktop apps (Android/Windows/macOS) build on demand only, via an app-v*
release tag or the manual workflow_dispatch checkboxes.
- Documented in docs/releases.md.
Docs
- docs/api/* (getting-started, authentication, channels, files, transfers,
signalr, local-files, playlists, shares, system-and-config, reference).
mateof
added a commit
that referenced
this pull request
Jul 26, 2026
* fix: fix folder creation logic to ensure CurrentFolder is valid before proceeding * fix: solve upload to empty folder and upload to root folder * fix: solve folder problem * Sync develop with main after v3.6.3 (#93) * Develop to main (#90) * fix: fix folder creation logic to ensure CurrentFolder is valid before proceeding * fix: solve upload to empty folder and upload to root folder * fix: solve folder problem * Bump version to 3.6.3 --------- Co-authored-by: Mateo <mateof@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: update MongoDB.Driver and System.IO.Hashing package versions * feat: wire progressive download cache into tfm streaming endpoint - StreamAudioByTfmId now starts the background ProgressiveDownloadService download (previously injected but never invoked) so every streamed track is fetched from Telegram once and persisted to the disk cache - Serve ranges from the growing cache file, waiting briefly when the background download is close instead of opening duplicate fetches - Direct Telegram fetches (far seeks) now stream 512KB chunks to the response via new DownloadFileStreamChunks and are limited by a semaphore - Robust Range parsing: TryParse, suffix ranges (bytes=-N), 416 for unsatisfiable ranges, removed ambiguous to==0 sentinel - ProgressiveDownloadService: fix IsRangeAvailable null check, drop stale entries when cache files are evicted, cap retries with backoff, and trim the cache directory to 10GB (oldest first, throttled) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: configurable STRM streaming mode with progressive disk cache Add a 'Streaming Mode' setting (Direct streaming / Progressive cache / Full preload) selectable in the web Config page, used when generating STRM files for Emby/Kodi. - New GetFileStreamCached endpoint: streams from Telegram with Range support while a background download fills the local cache; later ranges and replays are served from disk. - ProgressiveDownloadService now supports split (multi-message) files, caching parts sequentially into a single file with resume support. - Direct seeks ahead of the cache locate the Telegram part containing the requested range and cap the response at the part boundary. - STRM generation picks the endpoint from the configured mode; files smaller than MaxPreloadFileSizeInMb are still fully preloaded. - Backwards compatible with the legacy PreloadFilesOnStream flag (kept in sync on save; used as fallback when the new setting is unset). * fix: show progress and honor cancel for progressive cache downloads The background cache download appeared in the downloads list but never updated its progress and could not be stopped: - Report progress through DownloadModel.ProgressCallback (updates percentage, transmitted/size strings, global speed stats and fires the UI events) instead of writing _transmitted directly. The task is now also marked Completed when the download finishes. - Check the task state on every chunk so pressing Cancel (or Pause) in the downloads list actually stops the background download. - A user cancel is remembered for 1 hour per file so ongoing playback range requests don't immediately restart the cache download; the stream keeps working through direct Telegram fetches. A later playback caches again. - Start the progress counter at the resume offset so resumed downloads don't inflate the global speed stats. - Incomplete downloads no longer linger as 'Working' in the list. * feat: audio transcoding endpoint for offline downloads (MP3/AAC) - GET api/mobile/stream/tfm/{channelId}/{tfmId}/transcoded?format=mp3|aac&bitrate=N downloads the original into the streaming cache if needed, transcodes it with FFmpeg and serves the result with Range support; transcodes are cached on disk under _temp/transcoded so repeat requests are instant - GET api/mobile/stream/transcode/info reports FFmpeg availability so clients can warn the user and fall back to original downloads - Returns 501 when FFmpeg is missing; per-target locks avoid duplicate transcodes and a semaphore caps concurrent FFmpeg processes at 2 - MP3 keeps embedded cover art and tags; AAC (m4a) keeps tags * fix: reliable live task updates in Tasks Manager Task, download and upload tables required a full page reload to show progress because per-model event subscriptions were only wired when the component's static cached list was empty, so newly created models never got subscribed. Static lists were also shared between the Active and Queue table instances, and grid refreshes were fired from background threads without dispatcher marshaling. TransactionInfoService now owns all per-model subscriptions (hooked when a model enters any list, unhooked on remove/clear) and exposes a single aggregated TransactionsChanged event, throttled to 250ms with a guaranteed trailing raise. Tables use instance lists and subscribe only to that event, refreshing via InvokeAsync. The Tasks Manager header stats now update live as well. * feat: configurable parallel chunk transfers for faster downloads/uploads Transfers were capped at ~5-7 MB/s because WTelegramClient requests 512KB file parts with only 2 requests in flight (its default since v4.x), making throughput latency-bound at roughly 1MB per round-trip to Telegram's data center. Add a ParallelTransfers setting (default 4, range 1-16) applied to the main client and, before each document download, to the media-DC client that WTelegram resolves internally - secondary DC clients do not inherit the main client's setting, so it must be propagated per instance. The applied value is tracked per client and adjusted by delta, which stays correct even while parts are in flight. Changes take effect on the next transfer without restarting. * feat: multi-connection downloads to bypass per-connection speed limit Telegram enforces its throughput limit per MTProto connection (~5-6 MB/s), so pipelining more chunks on one connection cannot go faster; pushing harder only triggers FLOOD_PREMIUM_WAIT penalties. Official clients reach 50+ MB/s by opening several sessions to the file DC and splitting the file between them. Add an experimental multi-connection download mode (off by default): a per-DC pool of extra authorized clients, bootstrapped once from the main client via auth.exportAuthorization/importAuthorization, persisted as session files and reused across restarts. Files >=32MB are split in 4MB blocks served concurrently by 2-8 connections (configurable), each part written at its absolute offset via RandomAccess. Progress reports the contiguous completed prefix so persistence keeps a safe resume offset, while speed accounting counts every received part. Any failure falls back transparently to the standard sequential download. * fix: enable multi-connection mode on the file-manager download path Multi-connection downloads were only hooked into DownloadFile, but file-manager download tasks go through DownloadFileNow, which calls DownloadFileAndReturn with a FileStream target, so the feature never triggered on the most common path. Hook DownloadFileAndReturn (and the offset==0 branch of DownloadFileAndReturnWithOffset) when the destination is a FileStream, which supports the positional writes multi-connection mode requires. Memory and non-seekable targets keep the sequential path. * fix: bootstrap download pool via neighbor DC to avoid DC_ID_INVALID Telegram rejects auth.exportAuthorization towards the DC the caller is already connected to, so the pool bootstrap failed with DC_ID_INVALID whenever the file lived on the account home DC (the common case) and downloads always fell back to a single connection. Home the pool clients on a neighbor DC instead: exporting from the main client to the neighbor is always cross-DC, and from there the pool client can export towards any file DC, including the account home DC - both hops are cross-DC and accepted. This also makes the pool global rather than per-DC (one set of sessions serves every DC through per-file-DC transfer clients resolved with GetClientForDC), and the bootstrap failure log now includes the error message. * fix: finalize pool client login and drop probe RPCs after import The pool bootstrap verified the imported authorization with users.getUsers, which answers AUTH_KEY_UNREGISTERED on an imported session even though the import succeeded and file requests work, so the bootstrap always aborted and downloads fell back to a single connection. Import the exported authorization as the first call on the fresh client, finalize it client-side with LoginAlreadyDone (recording the UserId in the session), and make no verification RPC - workers verify by use and fall back on error. With the UserId recorded, WTelegram's GetClientForDC now handles the per-file-DC authorization automatically, replacing the manual transfer-client export/import logic; persisted sessions skip the import on later runs via the recorded UserId. * fix: tolerate re-import on an already-authorized pool session key Session files persisted by earlier runs (where the import succeeded but the login was never finalized client-side) hold a key that already carries the account authorization; re-importing onto it is rejected by the server with AUTH_BYTES_INVALID and the bootstrap aborted. Treat AUTH_BYTES_INVALID on import as already-authorized: finalize the login client-side with the user id returned by exportAuthorization and let the workers verify the session by use, falling back to the sequential download if it turns out to be broken. * feat: pool clients clone the main session instead of importing auth The exportAuthorization/importAuthorization bootstrap turned out to be a dead end: an imported session is a limited authorization - file requests work, but probe RPCs and re-exports answer AUTH_KEY_UNREGISTERED - so the pool client could never authorize its per-file-DC transfer connections (GetClientForDC's automatic export failed with AUTH_KEY_UNREGISTERED) and downloads always fell back. Clone the main session file instead: each pool client loads a copy of WTelegram.session, sharing the fully-authorized main auth key, while WTelegram assigns a fresh transient MTProto session id per client instance. The server sees extra connections of the existing authorization - the exact model official clients use for parallel downloads. Telegram's throughput limit is per connection/session, not per auth key, so each clone gets its own allowance. No authorizations are created, nothing appears in the device list, and cross-DC files keep working because the clones are fully logged sessions. * fix: clone the session despite the live file lock The main client keeps WTelegram.session open (often exclusively), so File.Copy failed with a sharing violation and the pool bootstrap always fell back to single-connection downloads. Snapshot the session file at client creation, before WTelegram opens and locks it, and make the clone routine try a share-friendly stream copy of the live file first, falling back to the startup snapshot. Session state staleness is harmless: the auth key never changes and server salts are renegotiated automatically. * feat: pipeline file parts within each download connection (#107) * feat: pipeline file parts within each download connection Workers requested their block's 1MB parts sequentially, paying a full round-trip of dead time per part and capping each connection around 3-4 MB/s regardless of its server-side allowance - the multi-connection download barely improved on a single connection (~10 MB/s with dips). Request all parts of a block concurrently on the block's connection, keeping each connection's pipe full (the same pipelining official clients use), and log a completion summary with the effective average speed to make future tuning measurable. Part writes stay positional and block completion still gates the contiguous confirmed prefix used for progress and resume persistence. * feat: make multi-connection tuning configurable with documented defaults Expose the remaining hardcoded transfer knobs in General Config: chunk size (snapped to Telegram's allowed 128/256/512/1024 KB values, 512 being WTelegramClient's own default), block size per connection (which determines the requests in flight per connection) and the minimum file size for multi-connection mode. Values are captured once per download so a config change cannot desynchronize offsets mid-transfer. The Config page now states the library default, app default and recommended value for each transfer setting, so the stock WTelegramClient behavior (2 parallel chunks, 512KB parts, single connection) can be restored by configuration alone. * feat: real sea-wave effect on the top bar download/upload buttons The water-fill buttons showed a flat colored block with a horizontal shine sweep - no actual wave at the waterline. Replace the shine with two repeating SVG wave crests that ride exactly on the fill level (the .water-wave element's top edge), drifting horizontally at different speeds and in opposite directions, overlapping the fill by 1px so no seam shows. The fill itself now uses a vertical gradient (deeper at the bottom, lighter at the surface) per variant color, wave motion is disabled under prefers-reduced-motion, and the stylesheet link gets a version query so cached browsers pick up the change. No markup changes to the buttons themselves. * feat: auth guard before rendering with return to the requested page After an app restart the Telegram client exists but the user is not loaded, so pages rendered first and only then (on first render) the layout noticed the missing user and hard-redirected to the login page, losing the URL the user was visiting; after authenticating, login always landed on /fetchdata. Resolve the session in the layouts' OnInitializedAsync, before any page content renders: the body is gated behind the check (page components are not instantiated until it completes), a silent session restore via checkAuth(null) is attempted first, and only when interactive login is really needed does the guard bounce to the login page, passing the requested URL as ?returnUrl. The login page navigates back to that URL (local paths only, to avoid open redirects) after a successful login. ConfigLayout gets the same guard; the old after-render check is removed. * feat: QR code login option in the web login page The QR login plumbing (CallQrGenerator wrapping LoginWithQRCode, QR rendering in Index) existed but nothing invoked it, and it could not have worked from the web anyway: when the account has 2FA, LoginWithQRCode asks for the password through Config("password"), which with the convenience constructor prompts on the console - QR login only worked when driving the library from a terminal. Wire it end to end: - The main client is now built with a custom config callback that routes the "password" request to the web UI: a QrPasswordNeeded event switches the login form to the 2FA password step and the value is handed back to the waiting login task, which blocks on a TaskCompletionSource (5 minute timeout) on its background thread. - The login page offers "Log in with QR code" next to the phone step: it shows the tg://login QR (auto-refreshed by the library when each token expires), instructions and a back button; on success the shared post-login initialization runs and navigation proceeds as with the normal flow. - DoLogin's post-login tail is extracted into CompleteLogin, reused by the QR flow. - checkAuth: when a session exists but no user data file is saved (the QR case - there is no phone to save), attempt a silent session restore via DoLogin(null) instead of always bouncing to the phone step, so QR sessions survive app restarts. * feat: live refresh in the download/upload/task info modals The info modals held a live reference to their model but only rendered a snapshot taken when opened, so progress, state, duration and speed froze until the modal was reopened. Subscribe each modal to the aggregated throttled TransactionsChanged event while it is visible (subscribed on ShowModal, unsubscribed on HideModal and Dispose) and re-render on the UI thread, following the same pattern the transfer tables use. * Sync develop with main after v3.7.0 (#115) * Develop to main (#90) * fix: fix folder creation logic to ensure CurrentFolder is valid before proceeding * fix: solve upload to empty folder and upload to root folder * fix: solve folder problem * Bump version to 3.6.3 * Develop to main (#112) * fix: fix folder creation logic to ensure CurrentFolder is valid before proceeding * fix: solve upload to empty folder and upload to root folder * fix: solve folder problem * Sync develop with main after v3.6.3 (#93) * Develop to main (#90) * fix: fix folder creation logic to ensure CurrentFolder is valid before proceeding * fix: solve upload to empty folder and upload to root folder * fix: solve folder problem * Bump version to 3.6.3 --------- Co-authored-by: Mateo <mateof@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: update MongoDB.Driver and System.IO.Hashing package versions * feat: wire progressive download cache into tfm streaming endpoint - StreamAudioByTfmId now starts the background ProgressiveDownloadService download (previously injected but never invoked) so every streamed track is fetched from Telegram once and persisted to the disk cache - Serve ranges from the growing cache file, waiting briefly when the background download is close instead of opening duplicate fetches - Direct Telegram fetches (far seeks) now stream 512KB chunks to the response via new DownloadFileStreamChunks and are limited by a semaphore - Robust Range parsing: TryParse, suffix ranges (bytes=-N), 416 for unsatisfiable ranges, removed ambiguous to==0 sentinel - ProgressiveDownloadService: fix IsRangeAvailable null check, drop stale entries when cache files are evicted, cap retries with backoff, and trim the cache directory to 10GB (oldest first, throttled) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: configurable STRM streaming mode with progressive disk cache Add a 'Streaming Mode' setting (Direct streaming / Progressive cache / Full preload) selectable in the web Config page, used when generating STRM files for Emby/Kodi. - New GetFileStreamCached endpoint: streams from Telegram with Range support while a background download fills the local cache; later ranges and replays are served from disk. - ProgressiveDownloadService now supports split (multi-message) files, caching parts sequentially into a single file with resume support. - Direct seeks ahead of the cache locate the Telegram part containing the requested range and cap the response at the part boundary. - STRM generation picks the endpoint from the configured mode; files smaller than MaxPreloadFileSizeInMb are still fully preloaded. - Backwards compatible with the legacy PreloadFilesOnStream flag (kept in sync on save; used as fallback when the new setting is unset). * fix: show progress and honor cancel for progressive cache downloads The background cache download appeared in the downloads list but never updated its progress and could not be stopped: - Report progress through DownloadModel.ProgressCallback (updates percentage, transmitted/size strings, global speed stats and fires the UI events) instead of writing _transmitted directly. The task is now also marked Completed when the download finishes. - Check the task state on every chunk so pressing Cancel (or Pause) in the downloads list actually stops the background download. - A user cancel is remembered for 1 hour per file so ongoing playback range requests don't immediately restart the cache download; the stream keeps working through direct Telegram fetches. A later playback caches again. - Start the progress counter at the resume offset so resumed downloads don't inflate the global speed stats. - Incomplete downloads no longer linger as 'Working' in the list. * feat: audio transcoding endpoint for offline downloads (MP3/AAC) - GET api/mobile/stream/tfm/{channelId}/{tfmId}/transcoded?format=mp3|aac&bitrate=N downloads the original into the streaming cache if needed, transcodes it with FFmpeg and serves the result with Range support; transcodes are cached on disk under _temp/transcoded so repeat requests are instant - GET api/mobile/stream/transcode/info reports FFmpeg availability so clients can warn the user and fall back to original downloads - Returns 501 when FFmpeg is missing; per-target locks avoid duplicate transcodes and a semaphore caps concurrent FFmpeg processes at 2 - MP3 keeps embedded cover art and tags; AAC (m4a) keeps tags * fix: reliable live task updates in Tasks Manager Task, download and upload tables required a full page reload to show progress because per-model event subscriptions were only wired when the component's static cached list was empty, so newly created models never got subscribed. Static lists were also shared between the Active and Queue table instances, and grid refreshes were fired from background threads without dispatcher marshaling. TransactionInfoService now owns all per-model subscriptions (hooked when a model enters any list, unhooked on remove/clear) and exposes a single aggregated TransactionsChanged event, throttled to 250ms with a guaranteed trailing raise. Tables use instance lists and subscribe only to that event, refreshing via InvokeAsync. The Tasks Manager header stats now update live as well. * feat: configurable parallel chunk transfers for faster downloads/uploads Transfers were capped at ~5-7 MB/s because WTelegramClient requests 512KB file parts with only 2 requests in flight (its default since v4.x), making throughput latency-bound at roughly 1MB per round-trip to Telegram's data center. Add a ParallelTransfers setting (default 4, range 1-16) applied to the main client and, before each document download, to the media-DC client that WTelegram resolves internally - secondary DC clients do not inherit the main client's setting, so it must be propagated per instance. The applied value is tracked per client and adjusted by delta, which stays correct even while parts are in flight. Changes take effect on the next transfer without restarting. * feat: multi-connection downloads to bypass per-connection speed limit Telegram enforces its throughput limit per MTProto connection (~5-6 MB/s), so pipelining more chunks on one connection cannot go faster; pushing harder only triggers FLOOD_PREMIUM_WAIT penalties. Official clients reach 50+ MB/s by opening several sessions to the file DC and splitting the file between them. Add an experimental multi-connection download mode (off by default): a per-DC pool of extra authorized clients, bootstrapped once from the main client via auth.exportAuthorization/importAuthorization, persisted as session files and reused across restarts. Files >=32MB are split in 4MB blocks served concurrently by 2-8 connections (configurable), each part written at its absolute offset via RandomAccess. Progress reports the contiguous completed prefix so persistence keeps a safe resume offset, while speed accounting counts every received part. Any failure falls back transparently to the standard sequential download. * fix: enable multi-connection mode on the file-manager download path Multi-connection downloads were only hooked into DownloadFile, but file-manager download tasks go through DownloadFileNow, which calls DownloadFileAndReturn with a FileStream target, so the feature never triggered on the most common path. Hook DownloadFileAndReturn (and the offset==0 branch of DownloadFileAndReturnWithOffset) when the destination is a FileStream, which supports the positional writes multi-connection mode requires. Memory and non-seekable targets keep the sequential path. * fix: bootstrap download pool via neighbor DC to avoid DC_ID_INVALID Telegram rejects auth.exportAuthorization towards the DC the caller is already connected to, so the pool bootstrap failed with DC_ID_INVALID whenever the file lived on the account home DC (the common case) and downloads always fell back to a single connection. Home the pool clients on a neighbor DC instead: exporting from the main client to the neighbor is always cross-DC, and from there the pool client can export towards any file DC, including the account home DC - both hops are cross-DC and accepted. This also makes the pool global rather than per-DC (one set of sessions serves every DC through per-file-DC transfer clients resolved with GetClientForDC), and the bootstrap failure log now includes the error message. * fix: finalize pool client login and drop probe RPCs after import The pool bootstrap verified the imported authorization with users.getUsers, which answers AUTH_KEY_UNREGISTERED on an imported session even though the import succeeded and file requests work, so the bootstrap always aborted and downloads fell back to a single connection. Import the exported authorization as the first call on the fresh client, finalize it client-side with LoginAlreadyDone (recording the UserId in the session), and make no verification RPC - workers verify by use and fall back on error. With the UserId recorded, WTelegram's GetClientForDC now handles the per-file-DC authorization automatically, replacing the manual transfer-client export/import logic; persisted sessions skip the import on later runs via the recorded UserId. * fix: tolerate re-import on an already-authorized pool session key Session files persisted by earlier runs (where the import succeeded but the login was never finalized client-side) hold a key that already carries the account authorization; re-importing onto it is rejected by the server with AUTH_BYTES_INVALID and the bootstrap aborted. Treat AUTH_BYTES_INVALID on import as already-authorized: finalize the login client-side with the user id returned by exportAuthorization and let the workers verify the session by use, falling back to the sequential download if it turns out to be broken. * feat: pool clients clone the main session instead of importing auth The exportAuthorization/importAuthorization bootstrap turned out to be a dead end: an imported session is a limited authorization - file requests work, but probe RPCs and re-exports answer AUTH_KEY_UNREGISTERED - so the pool client could never authorize its per-file-DC transfer connections (GetClientForDC's automatic export failed with AUTH_KEY_UNREGISTERED) and downloads always fell back. Clone the main session file instead: each pool client loads a copy of WTelegram.session, sharing the fully-authorized main auth key, while WTelegram assigns a fresh transient MTProto session id per client instance. The server sees extra connections of the existing authorization - the exact model official clients use for parallel downloads. Telegram's throughput limit is per connection/session, not per auth key, so each clone gets its own allowance. No authorizations are created, nothing appears in the device list, and cross-DC files keep working because the clones are fully logged sessions. * fix: clone the session despite the live file lock The main client keeps WTelegram.session open (often exclusively), so File.Copy failed with a sharing violation and the pool bootstrap always fell back to single-connection downloads. Snapshot the session file at client creation, before WTelegram opens and locks it, and make the clone routine try a share-friendly stream copy of the live file first, falling back to the startup snapshot. Session state staleness is harmless: the auth key never changes and server salts are renegotiated automatically. * feat: pipeline file parts within each download connection (#107) * feat: pipeline file parts within each download connection Workers requested their block's 1MB parts sequentially, paying a full round-trip of dead time per part and capping each connection around 3-4 MB/s regardless of its server-side allowance - the multi-connection download barely improved on a single connection (~10 MB/s with dips). Request all parts of a block concurrently on the block's connection, keeping each connection's pipe full (the same pipelining official clients use), and log a completion summary with the effective average speed to make future tuning measurable. Part writes stay positional and block completion still gates the contiguous confirmed prefix used for progress and resume persistence. * feat: make multi-connection tuning configurable with documented defaults Expose the remaining hardcoded transfer knobs in General Config: chunk size (snapped to Telegram's allowed 128/256/512/1024 KB values, 512 being WTelegramClient's own default), block size per connection (which determines the requests in flight per connection) and the minimum file size for multi-connection mode. Values are captured once per download so a config change cannot desynchronize offsets mid-transfer. The Config page now states the library default, app default and recommended value for each transfer setting, so the stock WTelegramClient behavior (2 parallel chunks, 512KB parts, single connection) can be restored by configuration alone. * feat: real sea-wave effect on the top bar download/upload buttons The water-fill buttons showed a flat colored block with a horizontal shine sweep - no actual wave at the waterline. Replace the shine with two repeating SVG wave crests that ride exactly on the fill level (the .water-wave element's top edge), drifting horizontally at different speeds and in opposite directions, overlapping the fill by 1px so no seam shows. The fill itself now uses a vertical gradient (deeper at the bottom, lighter at the surface) per variant color, wave motion is disabled under prefers-reduced-motion, and the stylesheet link gets a version query so cached browsers pick up the change. No markup changes to the buttons themselves. * feat: auth guard before rendering with return to the requested page After an app restart the Telegram client exists but the user is not loaded, so pages rendered first and only then (on first render) the layout noticed the missing user and hard-redirected to the login page, losing the URL the user was visiting; after authenticating, login always landed on /fetchdata. Resolve the session in the layouts' OnInitializedAsync, before any page content renders: the body is gated behind the check (page components are not instantiated until it completes), a silent session restore via checkAuth(null) is attempted first, and only when interactive login is really needed does the guard bounce to the login page, passing the requested URL as ?returnUrl. The login page navigates back to that URL (local paths only, to avoid open redirects) after a successful login. ConfigLayout gets the same guard; the old after-render check is removed. * feat: QR code login option in the web login page The QR login plumbing (CallQrGenerator wrapping LoginWithQRCode, QR rendering in Index) existed but nothing invoked it, and it could not have worked from the web anyway: when the account has 2FA, LoginWithQRCode asks for the password through Config("password"), which with the convenience constructor prompts on the console - QR login only worked when driving the library from a terminal. Wire it end to end: - The main client is now built with a custom config callback that routes the "password" request to the web UI: a QrPasswordNeeded event switches the login form to the 2FA password step and the value is handed back to the waiting login task, which blocks on a TaskCompletionSource (5 minute timeout) on its background thread. - The login page offers "Log in with QR code" next to the phone step: it shows the tg://login QR (auto-refreshed by the library when each token expires), instructions and a back button; on success the shared post-login initialization runs and navigation proceeds as with the normal flow. - DoLogin's post-login tail is extracted into CompleteLogin, reused by the QR flow. - checkAuth: when a session exists but no user data file is saved (the QR case - there is no phone to save), attempt a silent session restore via DoLogin(null) instead of always bouncing to the phone step, so QR sessions survive app restarts. * feat: live refresh in the download/upload/task info modals The info modals held a live reference to their model but only rendered a snapshot taken when opened, so progress, state, duration and speed froze until the modal was reopened. Subscribe each modal to the aggregated throttled TransactionsChanged event while it is visible (subscribed on ShowModal, unsubscribed on HideModal and Dispose) and re-render on the UI thread, following the same pattern the transfer tables use. * Bump version to 3.7.0 --------- Co-authored-by: Mateo <mateof@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * feat(api): add modular v1 REST + SignalR API and make mobile app builds on-demand (#116) Modular API v1 (/api/v1) exposing the full web feature set for a future mobile app, plus live transfer progress over SignalR, plus complete English docs. API - Controllers under Controllers/Api/V1: Auth (phone + QR login), Channels, Files, Transfers, LocalFiles, Playlists, Shares, Config, System. - Consistent envelope {success,data,error,message,page} with stable error codes. - SignalR hub /hubs/transfers (TransferHub) bridged from TransactionInfoService by a hosted TransferBroadcastService (snapshot/summary/speed messages). - DTOs in Models/Api; helpers in Services/Api (QrLoginSessionManager, ChannelFolderResolver, TransferSnapshotBuilder, upload staging). - API-key middleware now covers /api/v1 and /hubs, accepting X-Api-Key, ?apiKey=, ?access_token= (WebSocket) and Authorization: Bearer (negotiate). - Second Swagger document "api-v1" at /swagger/api-v1/swagger.json (/api-docs), built from XML doc comments (GenerateDocumentationFile enabled). - Verified at runtime: OpenAPI generation, config clamping, full SignalR handshake with snapshot-on-connect. Release policy - buildrelease.yml: a plain v* release now builds only the Server. The mobile / desktop apps (Android/Windows/macOS) build on demand only, via an app-v* release tag or the manual workflow_dispatch checkboxes. - Documented in docs/releases.md. Docs - docs/api/* (getting-started, authentication, channels, files, transfers, signalr, local-files, playlists, shares, system-and-config, reference). * fix(api/v1/channels): honour onlySaved and populate HasDatabase TelegramService.getAllSavedChats only checks the in-memory chat cache; it never filters by whether the channel has a local Mongo index. As a result GET /api/v1/channels?onlySaved=true returned every visible chat, and every ApiChannelDto came back with HasDatabase=false because FromChatViewBase does not populate it. Cross-reference the response against IDbService.GetAllChannelDatabaseNames in List/Folders/Favorites so onlySaved actually restricts the set and HasDatabase is a reliable flag for clients (which no longer need to guess by making a second onlySaved=true call). * feat(webdav): native read/write WebDAV endpoint; retire Python proxy Add a native C# WebDAV endpoint at /webdav/{channel}/{**path} that replaces the Python proxy, so a channel can be mounted as a read/write share (e.g. a Synology Hyper Backup destination that stores encrypted backups in Telegram). - Read: OPTIONS (DAV: 1,2), PROPFIND (Depth 0/1), HEAD, GET (full 200 / range 206) backed by the download-once disk cache. Fixes the proxy read bugs: 6 MB-truncated GETs, no caching, and PROPFIND hiding single-child folders. - Write: PUT (reuses the single-upload queue + 2/4 GB split), MKCOL, DELETE, MOVE. Empty files are stored as index-only nodes (Telegram cannot hold a 0-byte message); all delete paths guarded against a null MessageId. - LOCK/UNLOCK: advisory class-2 locking (in-memory WebDavLockManager, 423 on conflict). - Auth: HTTP Basic, credentials managed from the Config page (persisted in Mongo) with fallback to config.json; the mobile API key is editable there too. - Remove the Python proxy: WebbDavService, WebDavModel, the /api/nodes controller, the config/webdav bridge endpoints and the WebDavInfo page. - Docs: new docs/api/webdav.md with usage and examples; drop the bridge from the API docs. * fix(docker): drop retired Python WebDAV proxy from the image The native WebDAV endpoint replaced the Python proxy, so the image no longer needs Python, the WebDav folder or the uvicorn venv. The `COPY ./WebDav` step was breaking the Docker build after that folder was removed. * fix(build): remove orphaned WebDavInfo.razor.css scoped stylesheet Its razor component was deleted with the Python proxy; the leftover scoped CSS triggered BLAZOR102 and broke the build. * feat(webdav): channel header button with the WebDAV URL of the current folder Adds a "WebDav" button to the channel file manager header. It opens a modal with the WebDAV URL of the channel, including the folder you've navigated into (e.g. .../webdav/{channelId}/Movies/2024/), ready to copy into a Synology Hyper Backup task or any WebDAV client. Also switch copyToClipboard to the legacy textarea + execCommand('copy') approach so the copy button works over plain HTTP, not only in secure contexts (navigator.clipboard is limited to HTTPS/localhost). This fixes copy for every button that goes through the shared helper (the MediaUrlModal copy button). * feat(channels): hide channels with a "show hidden channels" setting Users can hide channels from the channel lists; a new "Show hidden channels" config option (persisted in Mongo) reveals them again so they can be unhidden. - Config: HiddenChannels list + ShowHiddenChannels flag in GeneralConfig, with Add/DeleteHiddenChannel helpers and persisting TelegramService wrappers (AddHiddenChannel / RemoveHiddenChannel / GetHiddenChannels). - Web UI: an eye toggle per channel in the sidebar; hidden channels are excluded from every tab (Mine/Fav/Folders) unless "show hidden channels" is on; new Config toggle to reveal them. - v1 API: IsHidden on the channel DTOs; GET /channels/hidden, POST/DELETE /channels/{id}/hidden; the list respects the setting and gains hiddenOnly / includeHidden query params; ShowHiddenChannels in the config get/PATCH. - Mobile API: IsHidden on the DTO; channel and folder lists exclude hidden channels unless the setting is on. - Docs updated (channels, reference, system-and-config). * fix(channels): hide channels in the Folders tab and keep folders expanded - The Folders/Ungrouped views bypass sChats (they call GetFolderChatsFiltered with filterBySChats=false), so hidden channels stayed visible there. Exclude hidden channels in the shared FilterChats helper instead. - Hiding (or starring) a channel reloaded the folder list, which reset every folder to collapsed. Preserve the expanded folders across the reload so the folder the user is working in stays open. * Document the Android phone and TV clients in the README --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a modular, versioned API (
/api/v1) with a SignalR hub that exposes the full feature set of the web application for a future mobile app, and changes the release workflow so mobile/desktop apps are built on demand instead of on every release.API v1
New surface under
/api/v1, alongside the untouched legacy/api/mobileaudio API.Controllers/Api/V1/): Auth (phone + QR login with server-side pollable session, incl. 2FA), Channels, Files, Transfers, LocalFiles, Playlists, Shares, Config, System — 84 endpoints.{ success, data, error, message, page }with stable machine-readableerror.codes./hubs/transfers(Hubs/TransferHub.cs) pushingTransfersSnapshot/TransferSummary/SpeedHistoryPoint, bridged from the existingTransactionInfoServiceby a hostedTransferBroadcastService. Transfers are controlled via REST; the hub is read/subscribe-only and the REST snapshot returns the identical payload.Models/Api/; helpers inServices/Api/(QrLoginSessionManager,ChannelFolderResolver,TransferSnapshotBuilder, upload staging)./api/v1and/hubs, accepting the key viaX-Api-Key,?apiKey=,?access_token=(WebSocket) andAuthorization: Bearer(SignalR negotiate). Empty key = open (dev)./swagger/api-v1/swagger.json, browsable at/api-docs, built from XML doc comments (GenerateDocumentationFileenabled).Verified against a running instance: OpenAPI generates with all paths, config clamping works, and a full
@microsoft/signalrhandshake receives a snapshot on connect.Release policy change
buildrelease.yml: a plainv*release now builds only the Server. The mobile/desktop apps (Android/Windows/macOS) build on demand only — via anapp-v*release tag or the manualworkflow_dispatchcheckboxes. Thev*fallback was removed from the three app jobs; the server job is unchanged.server-v*/v*app-v*Documentation
docs/api/*— complete English API docs (getting-started, authentication, channels, files, transfers, signalr, local-files, playlists, shares, system-and-config, reference).docs/releases.md— the build/release policy.Notes
/api/mobileAPI and the web UI are unchanged.dotnet buildclean).