diff --git a/.agents/shared/test-loop.md b/.agents/shared/test-loop.md new file mode 100644 index 00000000000000..a94d6a489a4dd0 --- /dev/null +++ b/.agents/shared/test-loop.md @@ -0,0 +1,310 @@ +# Test Loop Protocol (harness-neutral) + +The portable core of autonomous, tested implementation. Both `/implement` (Claude Code) +and `$implement` (Codex) read and follow THIS file verbatim for the testing phase, so the +impl⇄test loop behaves identically across harnesses. The harness-specific wrappers own +project setup, task splitting, and the spawn/wait mechanics; this file owns everything from +"a single task's implementation is committed" onward. + +## Vocabulary + +- **task-runner** — the per-task agent (one spawn per task). Owns the loop below. Its context + is disposable: only its compact final summary propagates up to the orchestrator. +- **impl agent / impl-fix agent** — sub-agents the task-runner spawns to write or fix the + implementation. They never write test code. +- **test-author agent** — sub-agent that writes the ad-hoc test overlay and builds. +- **overlay** — the throwaway `#ifdef _DEBUG` test code for the current task. Never part of an + implementation commit. Lives as a patch under the task folder between rounds. +- **golden tdata** — a read-only backup of the authed test account. Tests only ever copy FROM + it; they never write to it. + +## Inputs the wrapper passes in + +- `TASK_DIR` — `.ai///` for this task. +- `TASK_ID` — stable id used in commit trailers (e.g. the project + letter). +- **TASK SPEC** — the task's full description block (from `implementing.md`) and its referenced + images (`images/` design mockups / screenshots / graphic resources for this isolated task). + This is half of what the tests are designed against (the diff is the other half); the design READS + the images — they show what the result should look like. +- Config: `BUILD` (build command), `EXE` (built binary path), `MAX_ATTEMPTS` (default 4). The test + account lives in `out/Debug/` as the portable-data folders described under "Test account" below; + the wrapper has already confirmed the golden one exists (launch gate). All paths are relative to + the current checkout — no worktrees are created; the run happens in whatever repository slot it + was launched from. + +## State machine (run by the task-runner) + +Precondition: the implementation for this task is committed in the current checkout (impl agents +commit; they do not stash). Record that commit's SHA as **IMPL_SHA** — the reset after each test run +returns the checkout to exactly it. The runner tracks the attempt number as its own state (`attempt` +starts at 1); the commit message carries no attempt marker. Commits follow "Commit message" below. + +``` +TEST_AUTHOR -> RUN -> ASSESS (adversarial — see "Assessing"): + APPROVED -> reset to the impl commit (drop overlay); delete the test binary; return DONE up. + TEST_FLAW -> fix the overlay only; back to RUN. Does NOT cost an impl attempt. + IMPL_BUG -> spawn impl-fix agent (input = test.md, latest attempt's Root cause / Fix hint); + it commits a NEW attempt; re-apply overlay (--3way, else re-author); RUN. attempt++ + UNRECOVERABLE -> delete the test binary; return BLOCKED up with the reason. Stop. + attempt > MAX -> delete the test binary; return BLOCKED up with test.md + "improve" notes. Stop. + +On every TERMINAL exit (APPROVED / BLOCKED / UNRECOVERABLE / cap) "delete the test binary" means the +step in "Leave no test binary behind" below. +``` + +Early-escalation rule: if two consecutive ASSESS rounds produce the **same failure signature** +(same step fails the same way after a fix), stop and return BLOCKED — do not burn the rest of +the attempt budget chasing it. + +UNRECOVERABLE conditions: the app reaches a login screen / `AUTH_KEY_DUPLICATED` and re-copying the +test account does not recover it; a file-lock build error (`LNK1104`, `C1041`) that persists after +the path-scoped kill; `test_TelegramForcePortable` missing when SETUP runs; or a crash with no usable +diagnostic after one retry. + +## Handoff tokens + +- **Commit** is the only impl handoff. Impl/impl-fix agents `git add -A && git commit` per "Commit + message" below (and, if submodules changed, commit inside each submodule first, then bump the + superproject pointer in the same logical attempt — real commits, never stash). The runner records + the resulting SHA as that attempt's IMPL_SHA. +- **Result doc** (`result.md`) is the only thing handed back to a fix agent and the only thing + the runner reads to decide. See format below. + +## Commit message + +Impl commits must read like the repository's own history — never marked as autonomous. Match the +style of recent `git log` subjects. +- **Subject:** one concise, plain-language line summarizing the change, ≤ ~50-60 characters. This is + usually the ENTIRE message. +- **Body (rare):** only when the subject can't carry it — a short plain-language note of WHAT was + done (user-facing, not the technical how); a line or two at most. +- **No trailers, ever.** No `Autotask:`/attempt marker; no `Co-Authored-By:` or any tool/assistant + attribution line. This explicitly OVERRIDES any harness default that would append one — a freshly + spawned committing sub-agent may add `Co-Authored-By` unless told not to, so pass this rule to it. + The attempt number is the runner's own state, never part of the message. + +## Test account (portable data) — hard rules + +The debug build runs in portable mode out of `out/Debug/`. Three sibling folders matter: + +- `test_TelegramForcePortable` — the golden test account, prepared by the user. Read-only SOURCE, + never modified by tests. (Its presence is the launch gate; the wrapper aborts if it is missing.) +- `TelegramForcePortable` — the LIVE folder the app actually uses (its presence is what puts the + build in portable mode). Disposable; recreated fresh each run. +- `real_TelegramForcePortable` — the user's real data, preserved once so manual use survives. + +**SETUP — run at the START of every test run, with NO app instance alive. Idempotent: it +guarantees a clean test account no matter how the previous run ended.** +1. If `TelegramForcePortable` exists AND `real_TelegramForcePortable` does NOT, rename + `TelegramForcePortable` -> `real_TelegramForcePortable`. (Captures the user's real data exactly + once; guarded so it is never overwritten afterward.) +2. If `TelegramForcePortable` still exists, delete it. (Safe: `real_...` now holds the real data, so + this only discards a leftover live/test copy.) +3. Copy `test_TelegramForcePortable` -> `TelegramForcePortable`. The live folder is now a fresh copy + of the golden test account — ready to launch. + +**CLEANUP — optional, after a run.** The SETUP steps already self-heal, so cleanup exists only to +leave the user's real data live for manual use: +1. Delete `TelegramForcePortable`. +2. Copy `real_TelegramForcePortable` -> `TelegramForcePortable`. + +Why this is safe: `real_...` is written exactly once (step 1 is guarded by "real does not exist") +and `test_...` is only ever a copy source, so both the user's real data and the golden test account +are structurally protected — only `TelegramForcePortable` is ever destroyed. Use `robocopy /MIR` +(or `Copy-Item -Recurse` / `Remove-Item -Recurse -Force`) for the folder ops. + +**Serialize app runs.** Never have two `Telegram.exe` instances alive against this account at once — +concurrent reuse of one auth key can trigger a server-side session reset. Before SETUP, launching, or +rebuilding, kill any straggler **of THIS checkout's binary only** — the one whose full executable +path is `EXE` (`out/Debug/Telegram.exe` in this checkout). Match on the full path; do NOT blanket-kill +every `Telegram.exe` on the machine. The user may be running a system-installed client or another +checkout's build against unrelated accounts — those use different auth keys, never conflict with this +account, and MUST be left alive. On Windows, scope the kill by path: + + $exe = (Resolve-Path "$EXE").Path + Get-CimInstance Win32_Process -Filter "Name = 'Telegram.exe'" | + Where-Object { $_.ExecutablePath -eq $exe } | + ForEach-Object { Stop-Process -Id $_.ProcessId -Force } + +`taskkill /IM Telegram.exe /F` is forbidden here and anywhere else in this loop — it is image-name-wide +and takes down the user's unrelated clients. Every "kill stragglers" / "taskkill" step below means +this path-scoped kill. + +**Avoid destructive calls.** The overlay must never trigger logout / session-termination / +account-deletion. Tests that genuinely need those use a separate burner account, not this one. (If a +permanent destructive-call fuse is later added to the debug build, this is enforced in code; until +then it is the test-author's responsibility.) + +## Design the tests from THIS task (the crux) + +The single most important rule: **tests are derived from what THIS task changed — not from generic +project navigation, and not reused from a previous task.** Different change → different checks. If +two tasks produce the same screenshots and the same assertions, the second test is a no-op. Before +writing any overlay: + +1. **Read both sides of the task.** (a) The TASK SPEC — the task's full description block from + `implementing.md` and its referenced design-mockup images (`images/`); READ the images, + they are the source of truth for what the result should look like. (b) The change under test — + `git show ` (the actual diff) and `/plan.md`. List every concrete thing the + diff changed and every surface the task (description + "Observable result") says it affects. +2. **Turn each into a falsifiable check with an ORACLE** — something that can come out FAIL. A check + with no way to fail is not a test. By change type: + - **String / text** → assert the EXACT expected text is present at runtime (dump the label/widget + text to the log and compare) AND the old text is gone. Not "the screen opened". + - **Visual / asset (icon, image, color, layout)** → the rendered target must MATCH the intended + new artwork and DIFFER from the old. Both are available as files (intended art under + `.ai//...`; the committed new file is `git show :`; the old is + `git show ^:`). Render those references to PNG and compare the tight crop + against both. **If the rendered target matches the OLD art — or you cannot tell them apart — + that is a FAIL, not a pass.** (This is the check that catches a change that never took effect, + e.g. an asset that wasn't rebuilt into the binary.) + - **Behavior** → drive the specific action and observe the concrete state/log/screenshot the + change should produce, and confirm the pre-change behavior no longer happens. +3. **Cover every surface the task names.** If the Observable result lists a settings row, a balance + header, a gift field, and a suggestion bar, each must be observed (or explicitly marked N/A with + a reason). Do not stop at one or two. +4. **Write the checks into `/test.md` BEFORE running** (format under "Test report"), so the + design is explicit and Actual/Result can be filled in per check afterward. + +## Overlay mechanics + +The overlay is ad-hoc, authored fresh against the CURRENT implementation, injected at the +highest level that still exercises the change (often a direct data-layer call like +`item->applyEdition(...)` rather than a faked MTP response). It must: + +- Live entirely inside `#ifdef _DEBUG` blocks. +- Pick a **test strategy** and record it in the spec: + `live-data` (use real account data) · `live-mutate` (really create an entity — prefer a + throwaway target, clean up after) · `inject` (build fake local state without the network) · + `mock-api` (intercept specific requests, return canned responses — for payments/destructive). + Prefer `inject` over `live-mutate` to avoid account/server accumulation and flake. +- Drive the scenario on the Qt event loop, preferring **condition-waits over fixed timers** + (wait until the target widget/data actually exists, with a timeout fallback). Fixed sleeps are + the main source of screenshot flake. +- Write a flushed log to `/test_log.txt` (open Append|Text, flush after each write) and + save screenshots to `/screenshots/`. Delete the old log at the first step. +- **Capture the target tightly.** Grab the specific widget / row / glyph (or crop the saved PNG to + it) so the target is unambiguously in frame at usable resolution. A full-window grab that leaves + the target clipped, off-screen, or thumbnail-sized is NOT acceptable evidence — if the target + isn't clearly captured, that is a TEST_FLAW (re-frame), never a pass. +- **Lay down the oracle's reference.** For an asset/visual check, also save the OLD and intended-NEW + art as PNGs beside the crop (`screenshots/_old.png`, `_new.png`) so the assessment is + a direct three-way comparison, not a memory test. +- Emit these markers, one per line: + `TEST_STEP: ` · `TEST_RESULT: PASS: ` / `TEST_RESULT: FAIL: -
` · + `SCREENSHOT: ` · `TEST_COMPLETE` (immediately before quit). +- Prefer asserting on **logged state** (log the actual value, assert on text — deterministic); + reserve screenshots for genuinely visual checks where an eye is the right judge. +- **Watchdog:** install a `QTimer` at scenario start that force-quits (`Core::Quit()`, and if + needed `std::abort` after a flush) at a hard wall-clock cap (default 120s). This guarantees the + app never hangs holding a lock on the exe — independent of the runner's own timeout. +- End every path (success or assertion failure) by logging `TEST_COMPLETE` then `Core::Quit()`. + +### Git mechanics for the overlay (no stash) + +- After building, save the overlay as a patch: `git diff > /test-overlay.patch`. + Then **reset the checkout back to the implementation commit** so it stays impl-only: + `git reset --hard ` (and `git submodule update --init --recursive` if the overlay + touched submodules). The overlay never enters an impl commit. +- Next round, re-apply on top of the new implementation: `git apply --3way + /test-overlay.patch`. This succeeds ~90% of the time when the tail change was small. +- On conflict, **re-author the conflicting hunk from the spec** in `test.md` (which records + intent: injection point, fake values, assertions) rather than fighting the conflict markers. + Scenario steps that only call public APIs should live in their own block so they never conflict; + only true in-situ injections land inside impl files. + +## Build & run discipline + +- Build with `BUILD`. A single changed TU compiles fast; only the overlay-touched files + link + rebuild between rounds. On `LNK1104`/`C1041`, run the path-scoped kill (Test account → "Serialize + app runs"), wait, retry once; if it persists -> UNRECOVERABLE. +- **Codegen does not track resource mtimes.** If the task changed only a resource the style codegen + consumes (an icon `.svg`, etc.) without touching a `.style`, an incremental build will NOT re-pack + it and the binary keeps the OLD asset. Before building such a task force regeneration — touch the + referencing `.style` (or clean the codegen output) — so the change actually ships. A render that + shows no difference from before is the symptom of skipping this. +- Run: run the SETUP steps (Test account) -> launch `EXE` in the background -> poll `test_log.txt` + every ~5s -> on each `SCREENSHOT:` read the image and judge it -> detect `TEST_COMPLETE` (success) + or process death (crash) or no new output for the watchdog cap (hang) -> path-scoped kill of any + straggler (Test account → "Serialize app runs") -> optional CLEANUP -> save the overlay + (`git diff > /test-overlay.patch`) -> THEN `git reset --hard ` (back to + impl-only — the patch must be saved before this reset). + +### Leave no test binary behind + +The on-disk `EXE` (`out/Debug/Telegram.exe`) always contains the compiled overlay after a test run — +`git reset --hard` only reverts the source, not the built binary. So when the loop reaches a TERMINAL +verdict (APPROVED, BLOCKED, UNRECOVERABLE, or attempt cap), after the final path-scoped kill and +`git reset --hard `, **delete the built `EXE`** so no overlay-laden test binary is left for +the user to launch by mistake: + + Remove-Item -Force "$EXE" + +A clean, feature-ready binary is one `BUILD` away on demand. (Delete only on terminal exit — between +attempts the next round rebuilds the overlay, so the binary is reused there.) + +## Assessing (adversarial) + +ASSESS decides APPROVED / TEST_FLAW / IMPL_BUG. Default to **not approved**; a check passes only on +positive, specific evidence — in the captured pixels or the log — that the change is present AND +correct. + +- **No pass by inference.** "Same asset so it's fine", "probably", "looks like" are not evidence. + Missing, clipped, or ambiguous evidence for a check → **TEST_FLAW**: re-frame/re-capture and run + again. Never turn missing evidence into a PASS. +- **Judge the actual artifact.** State what is literally visible in the crop / present in the log, + then compare to the oracle reference (`_old.png` vs `_new.png`). Do not narrate expectations. +- **Judge visually, never by hash.** Do NOT pixel-diff or hash images. Desktop renders differ from + the mobile (iOS/Android) design mockups by platform, DPI, theme and antialiasing — the mockups + convey the intended look, they are NOT pixel targets, so never fail a check merely for not matching + a mockup pixel-for-pixel. The falsifiable signal is the on-screen crop against the OLD vs + intended-NEW render (does it match the new and differ from the old?); the mockup informs what + "correct" means. Read the images and decide like a designer reviewing the build. +- **No-difference = IMPL_BUG.** If a check detects no difference from the pre-change state (the glyph + matches the OLD art; the string still shows the old word), the change did not take effect — return + IMPL_BUG; do not approve. +- **A visual check with no baseline/target comparison cannot APPROVE** — with no oracle you have + tested nothing. +- APPROVED requires every derived check to PASS with evidence; else IMPL_BUG (real defect) or + TEST_FLAW (the test was wrong, not the code). + +## Test report (`/test.md`) — human-readable, append per attempt + +The file the human opens to see how testing went. The test-author writes the checks (Expected / +Oracle / Observed via) BEFORE running; ASSESS fills Actual / Result and the verdict. Append a new +`## Attempt` section each round — never overwrite prior attempts. + +``` +# Test report — /: + +## Attempt <n> — commit <sha> — strategy <...> — verdict: <APPROVED|TEST_FLAW|IMPL_BUG|UNRECOVERABLE> + +### Test 1 — <aspect of THIS change> +- Expected: <observable effect the change should produce> +- Oracle: <what would make this check FAIL> +- Observed via: <surface + how captured: tight crop of widget X; refs _old/_new> +- Actual: <what is literally visible / logged> +- Screenshots: screenshots/<after>.png (refs: _old.png, _new.png) +- Result: PASS | FAIL + +### Test 2 — ... + +### Verdict reasoning +<1-3 lines tying the checks to the verdict> +### Root cause / Fix hint (only if IMPL_BUG — the impl-fix agent reads this) +### Failure signature (one line, for early-escalation comparison) +``` + +## Compact summary the task-runner returns up + +``` +TASK: <TASK_ID> +STATUS: <DONE|BLOCKED> +VERDICT: <APPROVED|reason if blocked> +ATTEMPTS: <n> +TOUCHED: <repo paths or none> +DISCOVERED: <new follow-up tasks to append to implementing.md, or none> +NOTES: <one or two lines, or none> +``` + +Detailed reasoning stays in `.ai/` artifacts. The chat reply is only this block. diff --git a/.agents/skills/implement/SKILL.md b/.agents/skills/implement/SKILL.md new file mode 100644 index 00000000000000..8158a1f058993d --- /dev/null +++ b/.agents/skills/implement/SKILL.md @@ -0,0 +1,311 @@ +--- +name: implement +description: Autonomously implement a request on this repository (Telegram Desktop). Accept an inline description, a task-list file, or just a project name with a prepared .ai/<project>/tasks/about.md default task source, normalize it into a project with a testability-split task list, then drive each task to test-approval through an isolated per-task runner subagent that does context, planning, implementation, build, a single review pass, and an in-app test loop. Use when the user wants one prompt — ideally under Goal run mode — to carry work all the way to a tested, approved state with persistent .ai/ artifacts and a thin parent thread. Reuses task-think's phase prompts and the shared test-loop protocol; prefers spawn_agent/wait_agent and keeps the main thread lean. +--- + +# Implement Pipeline + +You are the top orchestrator. Take a request -- an inline description OR a task-list file -- +normalize it into a project with a testability-split task list, and drive each task to +test-approval through an isolated per-task **task-runner** subagent. Keep your context lean: hold +only the task list and one compact summary per task. All heavy work (planning, coding, building, +testing) happens inside subagents whose context is discarded. + +This tested superset of `task-think` does not re-specify the implementation phases or the test +loop. It reuses: +- `.agents/skills/task-think/PROMPTS.md` — Phase 0-6 prompt templates and the Codex execution-mode + / wait-ladder / progress-heartbeat / compact-reply rules. +- `.agents/shared/test-loop.md` — the harness-neutral impl⇄test loop (state machine, commit + handoff, overlay mechanics, `--3way`/re-author, test-account swap, watchdog, escalation). + +Read both before orchestrating. + +## Inputs + +`$ARGUMENTS` = ONE of: +- an inline task description (e.g. `add a dark-mode toggle to settings`) +- a path to a task-list file (e.g. `.ai/communities/tasks.txt` -- a rough list of tasks to refine) +- an existing project name to resume, optionally followed by extra work +- **just a project name with a prepared `.ai/<project>/tasks/about.md`** -- the default task source + (see Artifacts). With no other input, `implement <project>` plans and implements straight from that + file, so `implement communities` alone fires the full pipeline off `.ai/communities/tasks/about.md`. + +May also reference attached images. + +## Config + +Runs in the **current checkout** — wherever the skill is invoked. No worktrees are created; paths +below are relative to that repository root. + +``` +BUILD = cmake --build ./out --config Debug --target Telegram +EXE = ./out/Debug/Telegram.exe (or ./out/Debug/Telegram on WSL/Linux — verify the tree) +TEST_ACCOUNT = ./out/Debug/test_TelegramForcePortable (user-prepared golden; launch gate aborts if absent) +MAX_ATTEMPTS = 4 +``` + +`EXE` is also the process-cleanup scope. Any autonomous kill step must match the full executable +path for THIS checkout's built binary only; never blanket-kill all `Telegram.exe` processes. + +Tasks run **sequentially** in this one checkout (the build cache stays warm; app runs must serialize +against the account anyway). To parallelize, run the skill in a different checkout/slot (e.g. +`C:\Telegram\tdesktop`, `D:\Telegram\tdesktop`, `D:\Telegram\twin`). Each run is independent and +single-tree. Never run the **test phase** in two slots against the same account at once; concurrent +clients on one auth key can trigger a session reset, so give parallel slots separate test accounts. + +## Artifacts (per project) + +- `.ai/<project>/tasks/about.md` — the **default task source**: a human-prepared description (or + rough list) of the batch to implement, with any mockups dropped beside it in `.ai/<project>/tasks/`. + This is the file the user prepares; the planner reads it as SOURCE when `implement <project>` is + invoked with no other input. It is **distinct** from the project blueprint `.ai/<project>/about.md` + (the `tasks/` subdir is what disambiguates them). +- `.ai/<project>/implementing.md` — the canonical, final, testability-split task list (descriptions + + status); the main thread is its only writer. +- `.ai/<project>/images/` — illustrations referenced by tasks (`images/01.png`, ...). +- `.ai/<project>/<letter>/` — per-task artifacts (context, plan, review, test, result, overlay, logs). +- `.ai/<project>/about.md` — project blueprint (task-think convention). + +## Done (for Goal run mode) + +The run is **done** when every task in `implementing.md` has `Status: approved` or +`Status: blocked: <reason>`. Under Goal run mode this is the stop condition. The run is +**resumable**: re-invoking with the project name reads `implementing.md` and continues from the +first unfinished task. + +## Phase A: Setup & input resolution (main thread) + +1. Record `$START_TIME` (for example with `Get-Date`). +2. **Test-account gate (hard precondition — before any work).** If + `out/Debug/test_TelegramForcePortable` does not exist, STOP the entire skill immediately and tell + the user that the test account is not prepared: create `out/Debug/test_TelegramForcePortable` + (a portable-data folder authed to a throwaway test account) before `implement` can run, because + autonomous testing is impossible without it. Do no implementation work. +3. **Resolve `$ARGUMENTS` into (project, SOURCE, mode) — without reading task files or images.** + The main thread never loads task prose or assets; resolving needs only paths and existence checks. + SOURCE ends as EITHER inline text OR a confirmed file path that the planner will read. + - **File input** — if the first token is a path: confirm it exists (for example with `Test-Path`, + do NOT read it) and set SOURCE = that path. If the path is under `.ai/<name>/`, project = + `<name>`; else derive a short kebab name from the filename. Mode = **extend** if that project + already has `implementing.md`, else new. + - **Existing project** — else if `.ai/<FIRST_TOKEN>/` exists: project = `FIRST_TOKEN`. + - If there is a **remainder** → mode = **extend**: if the remainder is itself a path to an + existing file, SOURCE = that path (confirm it exists, do NOT read it); otherwise SOURCE = the + remainder text. + - If the remainder is **empty**, resolve SOURCE in this priority order (existence checks only, + do NOT read): + 1. If `.ai/<project>/tasks/about.md` exists → SOURCE = that file (the **default task + source**); mode = **extend** if `implementing.md` already exists, else **new**. This is the + `implement <project>` with a prepared task source path — it fires the full pipeline. + 2. Else if `implementing.md` exists → mode = **resume** (no SOURCE; Phase C finishes the + still-unfinished tasks). + 3. Else there is nothing to implement — tell the user to prepare `.ai/<project>/tasks/about.md` + (or pass a description / task-file path) and stop. + - **New inline** — else SOURCE = all of `$ARGUMENTS`; pick a unique short kebab-case project name + after consulting `.ai/`. + After this step you always have a project name and either a SOURCE (inline text or a confirmed + path) or mode = **resume** — and you have read neither the file nor any image. +4. Create `.ai/<project>/` and `.ai/<project>/images/` if new. +5. **Images must be on disk.** The planner reads images as files, and subagents cannot see chat + attachments, so every image a task needs must exist as a file (referenced by the SOURCE file, or + under `.ai/<project>/images/`). The main thread usually cannot save a pasted/inline chat image to + disk from text-only tools. If the user only pasted an image into chat, either ask them to drop it + into `.ai/<project>/images/` as a file, or, as a lossy fallback, write a textual description for + the planner. Do not claim to have saved it. Images the SOURCE file *references by path* are the + planner's job, not handled here. +6. If mode = **resume**, skip Phase B and go to Phase C. + +## Phase B: Planning & testability split (delegate) + +Spawn one planner subagent (`fork_context: false`) with this prompt shape: + +``` +You are a planning/splitting agent for a large C++ codebase (Telegram Desktop). + +SOURCE — EITHER an inline request OR a path to a task-list file. If it is a PATH, READ it yourself +(and any task files it points to); the main thread has NOT read it. If it is inline text, use it as +the request: +<the inline description, or the file path> +PROJECT: <project> MODE: <new | extend> + +IMAGES — the SOURCE and/or its task file may reference images by path (resolve them relative to the +SOURCE file's directory, or use absolute paths; when SOURCE is `.ai/<project>/tasks/about.md`, its +sibling files in `.ai/<project>/tasks/` — e.g. the mockup PNGs there — are those images). READ every +referenced image yourself, then COPY +each into `.ai/<project>/images/` with a descriptive kebab-case name, and reference it from the +specific task(s) it pertains to (see "Images per task" below). The main thread did NOT read or move +these — that is your job. If an image exists only as a textual description because the user pasted it +into chat and it could not be saved to a file, it is provided here; treat that description as the +visual spec: <description(s) or none> + +Read AGENTS.md. Briefly scan the codebase to gauge scope. Produce the FINAL ordered task list that +satisfies BOTH constraints for every task: + +- **Implementable in one pass**: a single agent with a ~200k-token budget must be able to implement + the task fully on its own WITHOUT triggering context compaction — i.e. a bounded change it can + read and edit across a handful of files, not a sweep across dozens. If a unit is too big, split it. +- **Independently testable**: each task must yield an observable behavior the test agent can drive + from an in-app debug overlay and verify via log/screenshot. Split on testable seams, so each task + ends at a point where something concrete can be exercised and checked. + +Use the minimal number of tasks subject to both constraints; preserve dependency order (a task comes +before any task that depends on it). If the SOURCE is already a list, respect its intended breakdown +and refine only as needed: split entries that are too big or not independently testable; you may +merge trivially tiny adjacent entries if the result is still one testable unit. + +Write `.ai/<project>/implementing.md` in EXACTLY this format: + +# Implementing: <project> + +## Goal +<one-line overall goal> + +## Tasks + +### a: <imperative title> +Status: todo +<2-4 line self-contained description: what to implement and the observable, testable result. Enough +that a fresh agent can act on it.> +Images: images/<file> — <caption> (this line only if the task uses an image) + +### b: <imperative title> +Status: todo +<...> + +**Images per task (required).** Every provided image is a design/resource the work must satisfy. +Attach each to the task(s) it pertains to via the `Images:` line, with a caption stating what that +task must match in it (the exact wording on a mockup, the glyph/shape of a resource, etc.). A task +that changes UI / visual / asset behavior MUST cite the specific mockups/resources it has to match; +do not leave such a task without its images, and do not leave a provided image referenced by no task +(if one genuinely applies to none, note why). These per-task references are the oracle the test +phase verifies against — be specific and per-task, not one shared dump on the first task. + +Use letters a, b, c... as task ids. Do not plan internals or implement. When done, reply with ONLY a +compact confirmation — `ready — <N> tasks` (extend: `ready — appended <letters>`); do NOT echo the +task list or image contents back, the main thread reads `implementing.md` itself. +``` + +For **extend** mode, instead instruct the planner to FIRST read the existing `implementing.md`, then +rewrite it as: (1) a TRIMMED completed-history — keep only the **three most recent** `Status: approved` +task blocks (the three nearest the bottom of the file) and drop all earlier approved ones; (2) every +still-unfinished task left untouched, in place and with its status — that is all `todo`, `in-progress`, +and `blocked` blocks (never drop these); then (3) APPEND new lettered tasks (continuing the letter +sequence from the highest letter still present after the trim) after them. The trim only removes +already-approved entries from the list — it never touches the per-task `.ai/<project>/<letter>/` +artifacts on disk, so a follow-up letter can still read an earlier letter's `context.md` even after its +block was trimmed out of `implementing.md`. It must append ONLY tasks from SOURCE not already +represented in `implementing.md` — so re-running `implement <project>` against an unchanged default +`tasks/about.md` appends nothing (the planner replies `ready — appended (none)`, still applying the +completed-history trim) and Phase C just finishes whatever is still unfinished. (Any +`todo`/`in-progress` leftovers from an interrupted run are picked up by Phase C regardless, so +defaulting to extend never loses an in-flight batch — it is a superset of resume.) + +After the planner replies `ready`, read `implementing.md` back ONCE (your first and only load of the +task prose; you never read the images). Initialize a progress list mirroring the tasks so progress +is visible. + +## Phase C: Per-task loop (main thread orchestrates) + +For each task whose `Status` is not `approved`/`blocked`, in order: + +1. Set `Status: in-progress` and mark the corresponding progress item in progress. Spawn ONE + **task-runner** worker (`fork_context: false`, request `model: gpt-5.4`, + `reasoning_effort: xhigh` when supported) with the prompt below. Apply task-think's wait ladder + (5-min waits while in progress, 1-2 min near completion; inspect the task's progress/result + artifacts on timeout; one follow-up then one fresh retry before escalating). +2. Read only its compact reply block. Detail is in `.ai/`. +3. Update the task's `Status:` — `approved` (STATUS DONE) or `blocked: <reason>`. +4. Append any `DISCOVERED` tasks as new lettered `### <letter>:` blocks (`Status: todo`) after the + remaining ones, and add them to the progress list. The main thread is the only writer of + `implementing.md`. +5. On BLOCKED, stop and report — do not start the next task. Under Goal run mode, surfacing the + blocker is the correct stop; the loop should not spin on a blocked task. + +### task-runner prompt + +``` +You are a task-runner for ONE task in an autonomous implement-and-test workflow on Telegram +Desktop (C++ / Qt). You own this task end to end and isolate its context from the orchestrator. +You MUST use subagents (spawn_agent/wait_agent) for each phase, keeping the parent thread lean. + +PROJECT: <project> TASK: <letter> — <title> +TASK DESCRIPTION: +<the task's full description block from implementing.md> +IMAGES: <referenced .ai/<project>/images/* paths, or none — Read them if present> +TASK_DIR: .ai/<project>/<letter>/ TASK_ID: <project>-<letter> +Config (paths relative to this checkout): BUILD/EXE/MAX_ATTEMPTS = <values>. Test account = the +out/Debug/ portable-data folders (see test-loop.md "Test account"). + +Read first: AGENTS.md; REVIEW.md; `.agents/skills/task-think/PROMPTS.md` (Phase 1-6 templates + +execution rules); `.agents/shared/test-loop.md` (testing). Read any IMAGES listed above. For a +follow-up letter, also read `.ai/<project>/about.md` and the previous letter's `context.md`. +Create `<TASK_DIR>/` and `<TASK_DIR>/logs/`. + +Pipeline for THIS task only, spawning a fresh subagent per phase (so each phase's output stays in +YOUR context, not the orchestrator's), writing prompt/progress/result logs per task-think: +1. CONTEXT — task-think Phase 1 (or 1F) -> context.md (+ about.md). +2. PLAN — Phase 2 -> plan.md. +3. ASSESS — Phase 3. +4. IMPLEMENT— Phase 4, one subagent per plan phase. Implementation agents do NOT commit yet; you + commit after build passes. +5. BUILD — Phase 5 (prefer same-thread build; fix errors). On file-lock errors, run the + path-scoped kill of THIS checkout's binary only (see test-loop.md "Serialize app runs") and retry + once; if the lock persists, return BLOCKED/UNRECOVERABLE with the lock reason. This overrides the + generic task-think stop-on-lock rule for this autonomous implement workflow. +6. REVIEW — Phase 6 but a SINGLE pass (one 6a, one 6b if NEEDS_CHANGES, rebuild). +7. COMMIT — stage the task's intended changes and git commit with a concise plain-language subject + (≤ ~50-60 chars, matching recent `git log` style; usually the whole message — add a short plain + body only if the subject can't carry it). NO `Autotask:`/attempt trailer and NO + `Co-Authored-By:`/attribution line (overrides the default; see test-loop.md "Commit message"). + Commit submodules first if dirty, then bump the pointer. Record the commit SHA as IMPL_SHA (track + the attempt number yourself). +8. TEST — run `.agents/shared/test-loop.md` to APPROVED / BLOCKED / attempt cap. Spawn a + test-author subagent and feed it BOTH sides per test-loop.md "Design the tests from THIS task": + (1) the TASK SPEC — this task's full description block above PLUS its referenced IMAGES (have it + READ the mockups; they show the intended result), and (2) the implementation — `git show + <IMPL_SHA>` + touched files. It designs a falsifiable oracle per change and writes the plan into + `<TASK_DIR>/test.md` BEFORE running (visual/asset changes compare the tight crop vs old vs + intended-new art — judged VISUALLY, never by hash/byte; mobile mockups are not pixel targets), + covers every surface the task names, and never reuses another task's navigate+screenshot. You + drive RUN/ASSESS yourself, ADVERSARIALLY (no pass-by-inference; missing evidence = TEST_FLAW; + no-difference-from-before = IMPL_BUG), and keep the human-readable `<TASK_DIR>/test.md` report. + Spawn an impl-fix subagent on IMPL_BUG (it commits the next attempt → new IMPL_SHA). After each + run, save the overlay patch into TASK_DIR and `git reset --hard <IMPL_SHA>` so the checkout + returns to impl-only. Run the test-account SETUP before each launch and honor every test-account + hard rule (serialize app runs; avoid destructive calls). + +Skip TEST only for docs/config-only tasks (say so). On Windows, after approval run task-think +Phase 7 (CRLF / no-BOM) on the task's touched source/config files. + +Reply with only the compact summary block from test-loop.md +(TASK/STATUS/VERDICT/ATTEMPTS/TOUCHED/DISCOVERED/NOTES). +``` + +## Completion + +When the loop ends (all tasks approved/blocked, or a blocked task stopped it): +1. Summarize per task: approved vs blocked, attempts, files touched, key test evidence. +2. List any discovered tasks that were added. +3. Note the project name for `implement <project> <follow-up>`. +4. Show total elapsed time (`Xh Ym Zs`, omit zero components). +5. Remind that test overlays are saved as `.ai/<project>/<letter>/test-overlay.patch` and the + checkout is left at each task's implementation commit (overlays reset away). + +## Error handling + +- Follow task-think's retry ladder for stuck phases; a task-runner returning BLOCKED stops the loop. +- If `implementing.md` or any artifact is malformed, re-spawn that step with tighter instructions. +- For file-lock build errors, run the autonomous path-scoped kill from test-loop.md and retry once. + Kill only the resolved `EXE` for this checkout; `taskkill /IM Telegram.exe /F` and other + image-name-wide kills are forbidden. If the lock persists after the scoped retry, return BLOCKED + with the lock reason instead of asking for user input. +- The launch gate (Phase A) guarantees the test account exists before any work begins. +- Keep `.ai/` artifacts and edited text files LF/no-BOM on WSL; run CRLF normalization only on a + native Windows checkout. + +## User invocation + +`Use local implement skill: <request or path to a task file>` — ideally under Goal run mode so it +loops to a tested state. Resume/extend: `Use local implement skill: <project> [additional change]`. +Default task source: `Use local implement skill: <project>` with a prepared `.ai/<project>/tasks/about.md` +runs the whole pipeline from that file with no other input. diff --git a/.agents/skills/task-think/PROMPTS.md b/.agents/skills/task-think/PROMPTS.md new file mode 100644 index 00000000000000..aef21013b6212a --- /dev/null +++ b/.agents/skills/task-think/PROMPTS.md @@ -0,0 +1,559 @@ +# Phase Prompts + +Use these templates as Codex subagent messages. Use them as same-session checklists only for Phase 0, intentional main-session build work, Phase 7, or when delegation is unavailable from the start. Replace `<TASK>`, `<PROJECT>`, `<LETTER>`, and `<REPO_ROOT>`. + +## Orchestration Rules + +- Phase 0 runs in the main session. +- When delegation is available, use a fresh subagent for Phase 1, Phase 2, Phase 3, each Phase 4 implementation unit, and each Phase 6 pass. Do not switch those phases to same-session midstream because of a timeout or missing artifact. +- Phase 7 runs in the main session on Windows because it depends on the final local diff and touched-file set. +- Write each phase prompt to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.prompt.md` before execution. +- If you delegate a phase, send the prompt file contents as the initial `spawn_agent` message. +- When writing the phase prompt file, append the standard progress file contract and the standard compact reply block below so the subagent knows how to surface progress before the final artifact. +- After each phase completes, write `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.result.md` summarizing the status, files touched, and any follow-up notes. +- Use `fork_context: false` by default. If the phase depends on thread-only context or UI attachments, pass that context explicitly or enable `fork_context` only for that phase. +- Prefer `worker` for phases that write files. Use `default` for plan or review passes if that fits the host better. Use `explorer` only for narrow read-only questions. +- When supported, request `model: gpt-5.4` and `reasoning_effort: xhigh` for delegated phases. +- Default wait budget for delegated phases is 5 minutes while the phase is clearly still in progress. Successful completion may wake earlier, so this does not delay finished work. +- When a phase appears close to landing, use 1-2 minute waits until it finishes. +- A `wait_agent` timeout is not failure. On timeout, inspect both the expected artifact and the matching progress file before deciding anything. +- If the expected artifact exists and shows progress, wait again. +- If the expected artifact is not ready but the progress file mtime moved or its heartbeat counter increased since the previous check, wait again. Prefer mtime checks first and avoid rereading the file unless you need detail. Do not count that as a failed wait. +- If neither the expected artifact nor the progress file moved since the previous blocked check, send one short follow-up asking the same agent to refresh the progress file, finish the required artifact, and return the standard compact reply block, then wait again. +- If the same agent still produces no usable artifact and no meaningful progress-file movement after two full default waits and one follow-up, close it and retry the phase in a fresh subagent. +- For Phase 1, Phase 2, Phase 3, Phase 4, and Phase 6, if delegated retries still fail, stop and ask the user rather than rerunning the phase locally. +- Never use `codex exec`, background shell child processes, or JSONL child-session logging from this skill. + +## Standard Progress File Contract + +Append this verbatim to every delegated phase prompt: + +```text +Before deep work, create or update the matching progress file in `.ai/<PROJECT>/<LETTER>/logs/`. + +Use `<phase-name>.progress.md` as a concise heartbeat with: +- `Heartbeat: <N>` on the first line, incremented on each meaningful update +- Current step +- Files being read or edited +- Concrete findings or decisions so far +- Blocker or next checkpoint + +Update it sparingly: preferably at natural milestones, and otherwise only after a longer quiet stretch such as roughly 5-10 minutes. +Keep it tiny so the parent can usually rely on file mtime or the heartbeat counter instead of rereading the whole file. +Do not wait until the final artifact to write progress. +``` + +## Standard Compact Reply Block + +Append this verbatim to every delegated phase prompt: + +```text +Before replying in chat, write the required artifact(s) to disk. + +Reply in 8 lines or fewer using exactly these keys: +STATUS: <DONE|BLOCKED|APPROVED|NEEDS_CHANGES> +ARTIFACTS: <paths> +TOUCHED: <repo paths or none> +BLOCKER: <none or one short line> + +Do not restate the full context, plan, diff, or long reasoning in the chat reply. +``` + +## Artifact-Based Completion Checks + +- Phase 1 is complete only when `about.md` and `context.md` both exist and are non-empty. +- Phase 2 is complete only when `plan.md` exists, contains a `## Status` section, and no unintended source edits were made. +- Phase 3 is complete only when `plan.md` contains both `Phases:` in the Status section and `Assessed: yes`. +- Phase 4 is complete only when the target phase checkbox changed to checked and the touched-file list matches the owned write set, or the blocker explains any mismatch. +- Phase 5 is complete only when the build outcome is known and the build checkbox is updated on success. +- Phase 6a is complete only when `review<R>.md` exists and contains a verdict line. +- Phase 6b is complete only when the requested fixes were applied and the post-fix build outcome is known. + +## Phase 0: Setup + +Record the current time now and store it as `$START_TIME`. You will use this at the end to display total elapsed time. + +Before running any phase prompts, determine whether this is a new project or a follow-up task. + +Follow-up detection: +1. Extract the first word or token from the task description. Call it `FIRST_TOKEN`. +2. Check `.ai/` to see existing project names. +3. Check whether `.ai/<FIRST_TOKEN>/about.md` exists. +4. If the file exists, this is a follow-up task. The project name is `FIRST_TOKEN`. The task description is everything after `FIRST_TOKEN`. +5. If the file does not exist, this is a new project. The full input is the task description. + +Do not proceed until you have determined follow-up vs new. + +For new projects: +- Using the list of existing projects, pick a unique short name (1-2 lowercase words, hyphen-separated) that does not collide. +- Create `.ai/<PROJECT>/`, `.ai/<PROJECT>/a/`, and `.ai/<PROJECT>/a/logs/`. +- Set `<LETTER>` = `a`. + +For follow-up tasks: +- Scan `.ai/<PROJECT>/` for existing task folders (`a/`, `b/`, ...). Find the latest one (highest letter). +- The previous task letter = that highest letter. +- The new task letter = next letter in sequence. +- Create `.ai/<PROJECT>/<LETTER>/` and `.ai/<PROJECT>/<LETTER>/logs/`. + +Then proceed to Phase 1. Follow-up tasks do not skip context gathering. They use a modified Phase 1F prompt. + +## Phase 1: Context (New Project, letter = `a`) + +```text +You are a context-gathering agent for a large C++ codebase (Telegram Desktop). + +TASK: <TASK> + +YOUR JOB: Read AGENTS.md, inspect the codebase, find all files and code relevant to this task, and write two documents. + +Steps: +1. Read AGENTS.md for project conventions and build instructions. +2. Search the codebase for files, classes, functions, and patterns related to the task. +3. Read all potentially relevant files. Be thorough and prefer reading more rather than less. +4. For each relevant file, note: + - file path + - relevant line ranges + - what the code does and how it relates to the task + - key data structures, function signatures, and patterns used +5. Look for similar existing features that could serve as a reference implementation. +6. Check api.tl if the task involves Telegram API. +7. Check .style files if the task involves UI. +8. Check lang.strings if the task involves user-visible text. + +Write two files. + +File 1: .ai/<PROJECT>/about.md + +This file is not used by any agent in the current task. It exists solely as a starting point for a future follow-up task's context gatherer. No planning, implementation, or review phase should rely on it during the current task. + +Write it as if the project is already fully implemented and working. It should contain: +- Project: What this project does (feature description, goals, scope) +- Architecture: High-level architectural decisions, which modules are involved, how they interact +- Key Design Decisions: Important choices made about the approach +- Relevant Codebase Areas: Which parts of the codebase this project touches, key types and APIs involved + +Do not include temporal state like "Current State", "Pending Changes", "Not yet implemented", or "TODO". Describe the project as a complete, coherent whole. + +File 2: .ai/<PROJECT>/a/context.md + +This is the primary task-specific implementation context. All downstream phases should be able to work from this file plus the referenced source files. It must be self-contained. Include: +- Task Description: The full task restated clearly +- Relevant Files: Every file path with line ranges and descriptions +- Key Code Patterns: How similar things are done in the codebase, with snippets when useful +- Data Structures: Relevant types, structs, classes +- API Methods: Any TL schema methods involved, copied from api.tl when useful +- UI Styles: Any relevant style definitions +- Localization: Any relevant string keys +- Build Info: Build command and any special notes +- Reference Implementations: Similar features that can serve as templates + +Be extremely thorough. Another agent with no prior context will rely on this file. + +Do not implement code in this phase. +``` + +## Phase 1F: Context (Follow-up Task, letter = `b`, `c`, ...) + +```text +You are a context-gathering agent for a follow-up task on an existing project in a large C++ codebase (Telegram Desktop). + +NEW TASK: <TASK> + +YOUR JOB: Read the existing project state, gather any additional context needed, and produce fresh documents for the new task. + +Steps: +1. Read AGENTS.md for project conventions and build instructions. +2. Read .ai/<PROJECT>/about.md. This is the project-level blueprint describing everything done so far. +3. Read .ai/<PROJECT>/<PREV_LETTER>/context.md. This is the previous task's gathered context. +4. Understand what has already been implemented by reading the actual source files referenced in about.md and the previous context. +5. Based on the new task description, search the codebase for any additional files, classes, functions, and patterns that are relevant to the new task but not already covered. +6. Read all newly relevant files thoroughly. + +Write two files. + +File 1: .ai/<PROJECT>/about.md (rewrite) + +Rewrite this file instead of appending to it. The new about.md must be a single coherent document that describes the project as if everything, including this new task's changes, is already fully implemented and working. + +It should incorporate: +- everything from the old about.md that is still accurate and relevant +- the new task's functionality described as part of the project, not as a pending change +- any changed design decisions or architectural updates from the new task requirements + +It should not contain: +- temporal state such as "Current State", "Pending Changes", or "TODO" +- history of how requirements changed between tasks +- references to "the old approach" versus "the new approach" +- task-by-task changelog or timeline +- information that contradicts the new task requirements + +File 2: .ai/<PROJECT>/<LETTER>/context.md + +This is the primary document for the new task. It must be self-contained and should include: +- Task Description: The new task restated clearly, with enough project background that an implementation agent can understand it without reading any other .ai files +- Relevant Files: Every file path with line ranges relevant to this task +- Key Code Patterns: How similar things are done in the codebase +- Data Structures: Relevant types, structs, classes +- API Methods: Any TL schema methods involved +- UI Styles: Any relevant style definitions +- Localization: Any relevant string keys +- Build Info: Build command and any special notes +- Reference Implementations: Similar features that can serve as templates + +Be extremely thorough. Another agent with no prior context should be able to work from this file alone. + +Do not implement code in this phase. +``` + +## Phase 2: Plan + +```text +You are a planning agent. You must create a detailed implementation plan. + +Read these files: +- .ai/<PROJECT>/<LETTER>/context.md +- Then read the specific source files referenced in context.md to understand the code deeply. + +Create a detailed plan in: .ai/<PROJECT>/<LETTER>/plan.md + +The plan.md should contain: + +## Task +<one-line summary> + +## Approach +<high-level description of the implementation approach> + +## Files to Modify +<list of files that will be created or modified> + +## Files to Create +<list of new files, if any> + +## Implementation Steps + +Each step must be specific enough that an agent can execute it without ambiguity: +- exact file paths +- exact function names +- what code to add, modify, or remove +- where exactly in the file (after which function, in which class, and so on) + +Number every step. Group steps into phases if there are more than about eight steps. + +### Phase 1: <name> +1. <specific step> +2. <specific step> + +### Phase 2: <name> (if needed) +1. <specific step> + +## Build Verification +- build command to run +- expected outcome + +## Status +- [ ] Phase 1: <name> +- [ ] Phase 2: <name> (if applicable) +- [ ] Build verification +- [ ] Code review + +Do not implement code in this phase. +``` + +## Phase 3: Plan Assessment + +```text +You are a plan assessment agent. Review and refine an implementation plan. + +Read these files: +- .ai/<PROJECT>/<LETTER>/context.md +- .ai/<PROJECT>/<LETTER>/plan.md +- Then read the actual source files referenced to verify the plan makes sense. + +Assess the plan: + +1. Correctness: Are the file paths and line references accurate? Does the plan reference real functions and types? +2. Completeness: Are there missing steps? Edge cases not handled? +3. Code quality: Will the plan minimize code duplication? Does it follow existing codebase patterns from AGENTS.md? +4. Design: Could the approach be improved? Are there better patterns already used in the codebase? +5. Phase sizing: Each phase should be implementable by a single agent in one session. If a phase has more than about 8-10 substantive code changes, split it further. + +Update plan.md with your refinements. Keep the same structure but: +- fix any inaccuracies +- add missing steps +- improve the approach if you found better patterns +- ensure phases are properly sized for single-agent execution +- add a line at the top of the Status section: `Phases: <N>` +- add `Assessed: yes` at the bottom of the file + +If the plan is small enough for a single agent (roughly 8 steps or fewer), mark it as a single phase. + +Do not implement code in this phase. +``` + +## Phase 4: Implementation + +Run one implementation unit per plan phase. Keep implementation phases sequential by default. Parallelize only if their write sets are disjoint and the plan makes that safe. + +For each phase in the plan that is not yet marked as done, use this prompt: + +```text +You are an implementation agent working on phase <N> of an implementation plan. + +Read these files first: +- .ai/<PROJECT>/<LETTER>/context.md +- .ai/<PROJECT>/<LETTER>/plan.md + +Then read the source files you will be modifying. + +Your owned write set for this phase: +<OWNED_WRITE_SET> + +YOUR TASK: Implement only Phase <N> from the plan: +<paste the specific phase steps here> + +Rules: +- Follow the plan precisely. +- Follow AGENTS.md coding conventions. +- You are not alone in the codebase. Respect existing changes and do not revert unrelated work. +- Do not modify .ai/ files except to update the Status section in plan.md. +- When done, update plan.md Status section: change `- [ ] Phase <N>: ...` to `- [x] Phase <N>: ...` +- Do not work on other phases. + +When finished, report what you did, which files you changed, and any issues encountered. +``` + +After each implementation phase: +1. Use a narrow read or search to confirm the status line was updated. +2. Verify the owned write set and touched files with a small diff summary such as `git diff --name-only`. +3. If more phases remain, run the next implementation phase. +4. If all phases are done, proceed to build verification. + +## Phase 5: Build Verification + +Only run this phase if the task modified project source code. + +Prefer running the build in the main session because it is critical-path work. If you delegate it, use a worker subagent and wait immediately for the result. + +```text +You are a build verification agent. + +Read these files: +- .ai/<PROJECT>/<LETTER>/context.md +- .ai/<PROJECT>/<LETTER>/plan.md + +The implementation is complete. Your job is to build the project and fix any build errors that block the planned work. + +Steps: +1. Run (from repository root): cmake --build ./out --config Debug --target Telegram +2. If the build succeeds, update plan.md: change `- [ ] Build verification` to `- [x] Build verification` +3. If the build fails: + a. Read the error messages carefully + b. Read the relevant source files + c. Fix the errors in accordance with the plan and AGENTS.md conventions + d. Rebuild and repeat until the build passes + e. Update plan.md status when done + +Rules: +- Only fix build errors. Do not refactor or improve code beyond what is needed for a passing build. +- Follow AGENTS.md conventions. +- If build fails with file-locked errors (C1041, LNK1104, "cannot open output file", or similar access-denied lock issues), stop and report the lock. Do not retry. +- You are not alone in the codebase. Respect existing changes and do not revert unrelated work. + +When finished, report the build result and which files, if any, you changed. +``` + +## Phase 6: Code Review Loop + +After build verification passes, run up to 3 review-fix iterations. Set iteration counter `R = 1`. + +Review loop: + +```text +LOOP: + 1. Run review phase 6a with iteration R. + 2. Read review<R>.md verdict: + - "APPROVED" -> go to FINISH + - "NEEDS_CHANGES" -> run fix phase 6b + 3. After fix work completes and build passes: + R = R + 1 + If R > 3 -> go to FINISH + Otherwise -> go to step 1 + +FINISH: + - Update plan.md: change `- [ ] Code review` to `- [x] Code review` + - Proceed to Phase 7 on Windows, otherwise proceed to Completion +``` + +### Step 6a: Code Review + +```text +You are a code review agent for Telegram Desktop (C++ / Qt). + +Read these files: +- .ai/<PROJECT>/<LETTER>/context.md +- .ai/<PROJECT>/<LETTER>/plan.md +- REVIEW.md +- If R > 1, also read .ai/<PROJECT>/<LETTER>/review<R-1>.md + +Then run `git diff` to see the current uncommitted changes for this task. + +Read the modified source files in full to understand the changes in context. + +Perform a focused code review using these criteria, in order: + +1. Correctness and safety: Obvious logic errors, missing null checks at API boundaries, potential crashes, use-after-free, dangling references, race conditions. +2. Dead code: Added or left-behind code that is never used within the scope of the changes. +3. Redundant changes: Diff hunks that have no functional effect. +4. Code duplication: Repeated logic that should be shared. +5. Wrong placement: Code added to a module where it does not logically belong. +6. Function decomposition: Whether an extracted helper would clearly improve readability. +7. Module structure: Only in exceptional cases where a large new chunk of code clearly belongs elsewhere. +8. Style compliance: REVIEW.md rules and AGENTS.md conventions. + +Important guidelines: +- Review only the changes made, not pre-existing code outside the scope of the task. +- Be pragmatic. Each suggestion should have a clear, concrete benefit. +- Do not suggest comments, docstrings, or over-engineering. + +Write your review to: .ai/<PROJECT>/<LETTER>/review<R>.md + +The review document should contain: + +## Code Review - Iteration <R> + +## Summary +<1-2 sentence overall assessment> + +## Verdict: <APPROVED or NEEDS_CHANGES> + +If the verdict is NEEDS_CHANGES, continue with: + +## Changes Required + +### <Issue 1 title> +- Category: <dead code | duplication | wrong placement | function decomposition | module structure | style | correctness> +- File(s): <file paths> +- Problem: <clear description> +- Fix: <specific description of what to change> + +Keep the list focused. Prioritize the most impactful issues. + +When finished, report your verdict clearly as: APPROVED or NEEDS_CHANGES. +``` + +### Step 6b: Review Fix + +```text +You are a review fix agent. You implement improvements identified during code review. + +Read these files: +- .ai/<PROJECT>/<LETTER>/context.md +- .ai/<PROJECT>/<LETTER>/plan.md +- .ai/<PROJECT>/<LETTER>/review<R>.md + +Then read the source files mentioned in the review. + +YOUR TASK: Implement all changes listed in review<R>.md. + +Rules: +- Implement exactly the review changes, nothing more. +- Follow AGENTS.md coding conventions. +- You are not alone in the codebase. Respect existing changes and do not revert unrelated work. +- Do not modify .ai/ files except where the review process explicitly requires it. + +After all changes are made: +1. Build (from repository root): cmake --build ./out --config Debug --target Telegram +2. If the build fails, fix build errors and rebuild until it passes. +3. If build fails with file-locked errors (C1041, LNK1104, "cannot open output file", or similar access-denied lock issues), stop and report the lock. Do not retry. + +When finished, report what changes were made and which files you touched. +``` + +## Phase 7: Windows Text Normalization + +Run this phase only on Windows hosts and only after the review loop has finished. + +Use the current task's result logs as the source of truth for what Codex touched. Do not sweep the whole repo and do not rewrite unrelated files from a dirty worktree. + +```text +You are performing the final Windows-only text normalization phase for task-think. + +Read these files: +- .ai/<PROJECT>/<LETTER>/plan.md +- .ai/<PROJECT>/<LETTER>/logs/phase-4*.result.md +- .ai/<PROJECT>/<LETTER>/logs/phase-5*.result.md +- .ai/<PROJECT>/<LETTER>/logs/phase-6*.result.md + +Your job: +- Collect the union of repo file paths listed under "Touched files" in those result logs. +- Keep only files inside the repository that currently exist and are textual project files: source, headers, build/config files, localization files, style files, and similar text assets. +- Exclude `.ai/`, `out/`, binary files, and unrelated user files that were not touched by Codex in this task. +- Rewrite each kept file so all line endings are CRLF. +- If a kept file is UTF-8 or ASCII text, write it back as UTF-8 without BOM. Never add a UTF-8 BOM to source/config/project text files. +- Preserve file content otherwise. Preserve whether the file ended with a trailing newline. + +Rules: +- Run this phase in the main session on Windows. +- Do not modify files outside the touched-file set for the current task. +- Do not rewrite binary files. +- When scripting this phase, do not use writer APIs or defaults that emit UTF-8 with BOM. +- If a file cannot be normalized safely, record it as a failure instead of silently skipping it. + +When finished: +1. Write `.ai/<PROJECT>/<LETTER>/logs/phase-7-line-endings.result.md` +2. Include: + - whether the phase completed + - which files were normalized + - which files were skipped and why + - whether any UTF-8 BOMs were removed or verified absent + - any failures that need to be mentioned in the final summary +``` + +## Completion + +When all phases, including build verification, code review, and Windows line ending normalization when applicable, are done: +1. Read the final `plan.md` and report the summary to the user. +2. Show which files were modified or created. +3. Note any issues encountered during implementation. +4. Summarize the code review iterations: how many rounds, what was found and fixed, or whether it was approved on the first pass. +5. On Windows, mention the text-normalization result briefly: which project files were normalized, whether any BOMs were removed, or whether nothing needed changes. +6. Calculate and display the total elapsed time since `$START_TIME` (format as `Xh Ym Zs`, omitting zero components). +7. Remind the user of the project name so they can request follow-up tasks within the same project. + +## Error Handling + +- If any phase fails or gets stuck, follow the timeout and retry rules above. Do not close an agent solely because the final artifact is missing while its progress file is still advancing. For Phase 1, Phase 2, Phase 3, Phase 4, and Phase 6, do not rerun locally after delegated retries fail; ask the user instead. +- If `context.md` or `plan.md` is not written properly by a phase, rerun that phase in a fresh subagent with more specific instructions. +- If build errors persist after the build phase's attempts, report the remaining errors to the user. +- If a review-fix phase introduces new build errors that it cannot resolve, report to the user. + +## Prompt Delivery And Logs + +For each phase: +1. Write the full prompt to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.prompt.md` +2. Delegate by sending that prompt text to a fresh subagent, or use it as a same-session checklist only for the designated main-session phases or when delegation was unavailable from the start +3. For delegated phases, expect a matching `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.progress.md` heartbeat while work is in flight +4. Save a concise completion note to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.result.md` + +For review iterations, include the iteration in the file name, for example: +- `phase-6a-review-1.prompt.md` +- `phase-6a-review-1.result.md` +- `phase-6b-fix-1.prompt.md` +- `phase-6b-fix-1.result.md` + +## Subagent Pattern + +Use this pattern conceptually for delegated phases: + +1. Write the phase prompt file. +2. Spawn a fresh subagent with the phase prompt, usually with `fork_context: false`. +3. Require the agent to create the matching progress file early and refresh it sparingly: at natural milestones when possible, otherwise only after a longer quiet stretch such as roughly 5-10 minutes. +4. Wait in 5-minute intervals when the next step is blocked on that phase, checking both the final artifact and the progress file on timeout. +5. When the phase looks close to finishing, switch to 1-2 minute waits. +6. Prefer filesystem mtime checks on the progress file first. If its mtime moved or the heartbeat counter increased, keep waiting; do not treat that as a stall. +7. If neither the artifact nor the progress file moves, send one short follow-up to the same agent, then retry once with a fresh subagent before involving the user. +8. Validate the expected artifact or code changes with small shell summaries and the completion checks above. +9. Write the result log from the validated outcome and the compact reply block. + +Do not replace this pattern with shell-launched `codex exec`. diff --git a/.agents/skills/task-think/SKILL.md b/.agents/skills/task-think/SKILL.md new file mode 100644 index 00000000000000..939e78a42789ca --- /dev/null +++ b/.agents/skills/task-think/SKILL.md @@ -0,0 +1,145 @@ +--- +name: task-think +description: Orchestrate a multi-phase implementation workflow for this repository with artifact files under .ai/<project-name>/<letter>/ using Codex subagents instead of shell-spawned child processes. Use when the user wants one prompt to drive context gathering, planning, plan assessment, implementation, build verification, and review with persistent artifacts, clear phase handoffs, and a thin parent thread. Prefer spawn_agent/send_input/wait_agent, keep heavy pre-build work delegated when possible, and avoid pulling timed-out phases back into the main session. +--- + +# Task Pipeline + +Run a full implementation workflow with repository artifacts and clear phase boundaries. + +## Inputs + +Collect: +- task description +- optional project name (if missing, derive a short kebab-case name) +- optional constraints (files, architecture, risk tolerance) +- optional screenshot paths + +If screenshots are attached in UI but not present as files, write a brief textual summary into the task artifacts before spawning fresh subagents so later phases can read the requirements without inheriting the whole parent thread. + +## Overview + +The workflow is organized around projects. Each project lives in `.ai/<project-name>/` and can contain multiple sequential tasks (labeled `a`, `b`, `c`, ... `z`). + +Project structure: +```text +.ai/<project-name>/ + about.md # Single source of truth for the entire project + a/ # First task + context.md # Gathered codebase context for this task + plan.md # Implementation plan + review1.md # Code review documents (up to 3 iterations) + review2.md + review3.md + logs/ + phase-*.prompt.md + phase-*.progress.md + phase-*.result.md + b/ # Follow-up task + context.md + plan.md + review1.md + logs/ + ... + c/ # Another follow-up task + ... +``` + +- `about.md` is the project-level blueprint: a single comprehensive document describing what this project does and how it works, written as if everything is already fully implemented. It contains no temporal state ("current state", "pending changes", "not yet implemented"). It is rewritten, not appended to, each time a new task starts, incorporating the new task's changes as if they were always part of the design. +- Each task folder (`a/`, `b/`, ...) contains self-contained files for that task. The task's `context.md` carries all task-specific information: what specifically needs to change, the delta from the current codebase, gathered file references, and code patterns. Planning, implementation, and review phases should rely on the current task folder. + +## Artifacts + +Create and maintain: +- `.ai/<project-name>/about.md` +- `.ai/<project-name>/<letter>/context.md` +- `.ai/<project-name>/<letter>/plan.md` +- `.ai/<project-name>/<letter>/review<R>.md` (up to 3 review iterations) +- `.ai/<project-name>/<letter>/logs/phase-<name>.prompt.md` +- `.ai/<project-name>/<letter>/logs/phase-<name>.progress.md` for delegated phases +- `.ai/<project-name>/<letter>/logs/phase-<name>.result.md` + +Each `phase-<name>.result.md` should capture a concise outcome summary: whether the phase completed, which files it touched, and any follow-up notes or blockers. +Each delegated `phase-<name>.progress.md` should act as a heartbeat: a tiny monotonic counter plus current step, files being read or edited, concrete findings so far, and the next checkpoint. It is not a final artifact; it exists so the parent can distinguish active research from a truly stuck subagent without rereading large context. + +## Phases + +Run these phases sequentially: + +1. Phase 0: Setup - Record start time, detect follow-up vs new project, create directories. +2. Phase 1: Context Gathering - Read codebase, write `about.md` and `context.md`. Use Phase 1F for follow-up tasks. +3. Phase 2: Planning - Read context, write detailed `plan.md` with numbered steps grouped into phases. +4. Phase 3: Plan Assessment - Review and refine the plan for correctness, completeness, code quality, and phase sizing. +5. Phase 4: Implementation - Execute one implementation unit per plan phase. +6. Phase 5: Build Verification - Build the project, fix any build errors. Skip if no source code was modified. +7. Phase 6: Code Review Loop - Run review and fix iterations until approved or the iteration limit is reached. +8. Phase 7: Windows Text Normalization - On Windows only, after review passes and before the final summary, normalize LF to CRLF for the text source/config files Codex edited in this task and ensure rewritten UTF-8 project files are saved without BOM. + +Use the phase prompt templates in `PROMPTS.md`. + +## Execution Mode + +Use Codex subagents as the primary orchestration mechanism. + +- When delegation is available, Phase 1, Phase 2, Phase 3, each Phase 4 implementation unit, and each Phase 6 review or review-fix pass must run in fresh subagents. Do not rerun those phases in the main session midstream just because a wait timed out or an artifact is missing. +- Run Phase 7 in the main session on Windows because it depends on the final local file state and the exact touched-file set for the current task. +- When any same-session helper rewrites Windows project text files, preserve CRLF and write UTF-8 without BOM. Avoid writer APIs or defaults that silently inject a UTF-8 BOM. +- The main session may read `context.md` once after Phase 1 and `plan.md` once after Phase 3. After that, prefer narrow shell checks, file existence checks, and status-line reads instead of rereading full documents or diffs. +- Prefer `worker` for phases that write files. Use `explorer` only for narrow read-only questions that unblock your next local step. +- Keep `fork_context` off by default. Pass the phase prompt and explicit file paths instead of the whole thread unless the phase truly needs prior conversational context or thread-only attachments. +- When the platform supports it, request `model: gpt-5.4` and `reasoning_effort: xhigh` for spawned phase agents. If overrides are unavailable, inherit the current session settings. +- Write the exact phase prompt to the matching `logs/phase-<name>.prompt.md` file before you delegate. Use the same prompt file as a checklist if you later need to fall back to same-session execution. +- For delegated phases, require an early `logs/phase-<name>.progress.md` heartbeat before deep work. The subagent should create or update it early, keep it tiny, and refresh it sparingly: preferably at natural milestones, and otherwise only after a longer quiet stretch such as roughly 5-10 minutes. +- In every delegated prompt, require a compact final reply with only status, artifact paths, touched files, and blocker or `none`. Detailed reasoning belongs in `.ai/` artifacts, not in the chat reply. +- After a subagent finishes, verify that the expected artifacts or code changes exist, then write a short result log in `logs/phase-<name>.result.md`. +- For delegated phases, use `wait_agent` with a 5-minute timeout by default while a phase is still clearly in progress. Successful completion may wake earlier, so this does not add latency to finished phases. +- When a phase looks close to completion — for example the final artifact has appeared, a build is in its final pass, or the agent said it is wrapping up — switch to 1-2 minute waits until it lands. +- A timeout is not a failure; it only means no final status arrived yet. Do not treat short waits as stall detection for research-heavy phases. +- On timeout, inspect the expected artifact, the phase progress file mtime, and the worktree for movement. Prefer mtime checks first; only reread the progress file when you need detail. +- If the progress file mtime moved or its heartbeat counter increased since the previous check, treat that as active progress and wait again. +- If no usable final artifact exists yet but the progress file is appearing or advancing, keep the same subagent alive. Progress-file movement does not count toward the retry limit. +- If no usable final artifact exists yet and neither the expected artifact nor the progress file has moved since the previous blocked check, send one short follow-up asking the same subagent to refresh the progress file, finish the artifact, and return the compact status block, then wait again. +- Only if the same subagent still shows no meaningful movement in either the expected artifact or the progress file after two full default waits and one follow-up should you close it and rerun that phase in a fresh subagent. +- Use `wait_agent` only when the next step is blocked on the result. While the delegated phase runs, do small non-overlapping local tasks such as validating directory structure or preparing the next prompt file. +- Build verification is critical-path work. Prefer running the build in the main session, and only delegate a bounded build-fix phase when there is a concrete reason. +- If subagents are unavailable in the current environment, or current policy does not allow delegation from the start, run the phase in the main session using the same prompt files. Otherwise, do not switch a pre-build phase to same-session midstream. Never fall back to shell-spawned `codex exec` child processes from this skill. + +## Verification Rules + +- If build or test commands fail due to file locks or access-denied outputs (C1041, LNK1104), stop and ask the user to close locking processes before retrying. +- Treat a delegated phase as complete only when the required artifact or status update exists on disk and matches the phase goals; do not rely on the chat reply alone. +- Never claim completion without: + - implemented code changes present + - build attempt results recorded + - review pass documented with any follow-up fixes + - on Windows, if the task edited project source/config text files, a CRLF / no-BOM normalization pass recorded after review + +## Completion Criteria + +Mark complete only when: +- All plan phases are done +- Build verification is recorded +- Review issues are addressed or explicitly deferred with rationale +- On Windows, Codex-edited project source/config text files have been normalized to CRLF, any UTF-8 rewrites were saved without BOM, and the result is logged +- Display total elapsed time since start (format: `Xh Ym Zs`, omitting zero components) +- Remind the user of the project name so they can request follow-up tasks within the same project + +## Error Handling + +- If any phase fails, times out, or gets stuck, follow the retry ladder from Execution Mode. Do not close an agent solely because the final artifact is missing while its progress file is still moving. After two delegated attempts remain blocked with no meaningful progress, report the issue to the user. Do not absorb the phase into the main session before build unless delegation was unavailable from the start. +- If `context.md` or `plan.md` is not written properly by a phase, rerun that phase in a fresh subagent with more specific instructions. Do not repair it locally before build unless delegation was unavailable from the start. +- If build errors persist after the build phase's attempts, report the remaining errors to the user. +- If a review-fix phase introduces new build errors that it cannot resolve, report to the user. +- If Phase 7 cannot safely normalize a touched file on Windows or remove an introduced UTF-8 BOM from a touched project text file, record the failure in the result log and report it in the final summary instead of silently skipping it. + +## User Invocation + +Use plain language with the skill name in the request, for example: + +`Use local task-think skill with subagents: make sure FileLoadTask::process does not create or read QPixmap on background threads; use QImage with ARGB32_Premultiplied instead.` + +For follow-up tasks on an existing project: + +`Use local task-think skill with subagents: my-project also handle the case where the file is already cached` + +If screenshots are relevant, include file paths in the same prompt when possible. diff --git a/.claude/commands/icon.md b/.claude/commands/icon.md new file mode 100644 index 00000000000000..479e23eceb0194 --- /dev/null +++ b/.claude/commands/icon.md @@ -0,0 +1,306 @@ +--- +description: Generate an SVG icon from a design mockup using vectosolve vectorization +allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion, TodoWrite, mcp__vectosolve__vectorize +--- + +# Icon - SVG Icon Generation from Design Mockup + +You generate production-quality SVG icons for Telegram Desktop by vectorizing design mockup screenshots using the vectosolve MCP service, then post-processing the result to match the Telegram icon format. + +**Arguments:** `$ARGUMENTS` = "$ARGUMENTS" + +If `$ARGUMENTS` is empty, ask the user to describe the icon they want and paste a cropped screenshot of it. + +## Overview + +The workflow takes a cropped screenshot of an icon from a design mockup (grabbed from the clipboard), vectorizes it via the vectosolve MCP, then post-processes the SVG (recolor to white-on-transparent, restructure to minimal format, set 24x24 output size). + +Working directory: `.ai/icon_{name}/` with iterations labeled by letter (`a/`, `b/`, ...), each containing `source.png`. Output SVGs are in the icon root: `a.svg`, `b.svg`, etc. + +Follow-ups are supported: `/icon {icon_name} <description>` continues from where the previous run left off. + +## Phase 0: Setup + +**Record the current time** (using `date` or equivalent) as `$START_TIME`. + +### Step 0a: Clipboard grab (MUST be the VERY FIRST action) + +If there is an image attached to the user's message: + +1. Generate a random 8-character hex string for `HASH` (use `openssl rand -hex 4` or similar). +2. **IMMEDIATELY** — before any other processing — run this Bash command to save the clipboard image: + ```bash + HASH=$(openssl rand -hex 4) && if [[ "$OSTYPE" == darwin* ]]; then bash .claude/grab_clipboard.sh ".ai/icon_${HASH}.png"; else powershell -ExecutionPolicy Bypass -File .claude/grab_clipboard.ps1 ".ai/icon_${HASH}.png"; fi + ``` + On macOS `.claude/grab_clipboard.sh` is used; on Windows `.claude/grab_clipboard.ps1`. Both grab the current clipboard image and save it to the specified path. + +3. If the command fails (exit 1 / no image on clipboard): + - Tell the user: **"Clipboard doesn't contain an image. Please copy the icon area first, then retry."** (On macOS: Cmd+Ctrl+Shift+4 to snip to clipboard; on Windows: Win+Shift+S.) + - **STOP IMMEDIATELY. Do NOT continue.** You cannot use the image pasted in the conversation — it exists only as pixels in the chat, not as a file you can send to vectosolve. The clipboard grab is the ONLY way to get the image to disk. Do not attempt any workaround. + +4. Read back the saved `.ai/icon_HASH.png` using the Read tool. +5. Compare it visually with the image pasted in the conversation. They should depict the same thing. + - If they look **completely different**: delete `.ai/icon_HASH.png` and fail: + > "The clipboard image doesn't match what you pasted. Please re-copy and retry." + - If they look the same (or close enough): proceed. Store the temp path. + +If NO image is attached to the message, skip this step entirely. + +### Step 0b: Fail-fast — verify vectosolve MCP + +Check that the `mcp__vectosolve__vectorize` tool is available by looking at your available tools list. If it is NOT available, fail immediately with: + +> vectosolve MCP is not configured. Set it up with: +> ``` +> claude mcp add vectosolve --scope user -e VECTOSOLVE_API_KEY=vs_xxx -- npx @vectosolve/mcp +> ``` +> Then restart Claude Code. + +### Step 0c: Follow-up detection + +Extract the first word/token from `$ARGUMENTS` (everything before the first space or newline). Call it `FIRST_TOKEN`. + +Run these TWO commands using the Bash tool, **IN PARALLEL**: +1. `ls .ai/` — to see all existing icon project names +2. `ls .ai/icon_{FIRST_TOKEN}/context.md` — to check if this specific icon project exists + +**Evaluate the results:** +- If command 2 **succeeds** (context.md exists): this is a **follow-up**. The icon name is `FIRST_TOKEN`. The follow-up description is everything in `$ARGUMENTS` after `FIRST_TOKEN`. +- If command 2 **fails** (not found): this is a **new icon**. The full `$ARGUMENTS` is the icon description. + +### Step 0d: New icon setup + +1. Parse `$ARGUMENTS` to determine: + - **Icon description**: what the icon should depict + - **Icon type**: default is `menu` (24x24 menu/button icon). User may specify otherwise. + - **Target subfolder**: `menu/` by default, or another subfolder if specified. + +2. Choose an icon file name: + - Lowercase letters and underscores only — **NO hyphens** + - Match existing naming conventions (check `Telegram/Resources/icons/{subfolder}/`) + - Must NOT conflict with existing icons + - Must NOT collide with existing `.ai/icon_{name}/` directories + +3. Create `.ai/icon_{name}/` and `.ai/icon_{name}/a/`. + +4. Write `.ai/icon_{name}/context.md` with: + ``` + ## Icon: {icon_name} + Type: {menu/other} + Target: Telegram/Resources/icons/{subfolder}/{icon_name}.svg + + ## Original Request + {full $ARGUMENTS text} + + ## Follow-ups + (none yet) + ``` + +5. Set `LETTER` to `a`. + +### Step 0e: Follow-up setup + +1. Read `.ai/icon_{name}/context.md` to get the icon type, subfolder, and full history. +2. Find the latest existing letter folder in `.ai/icon_{name}/` (highest letter). +3. Set `LETTER` to the next letter after the latest. +4. Create `.ai/icon_{name}/{LETTER}/`. +5. Update `.ai/icon_{name}/context.md` — append the follow-up description to the `## Follow-ups` section: + ``` + ### Follow-up (starting at letter {LETTER}) + {follow-up description} + ``` + +### Step 0f: Place source image + +If a clipboard image was grabbed in Step 0a: +1. Copy (or move) `.ai/icon_HASH.png` → `.ai/icon_{name}/source.png` (overwrite if exists — this is always the latest source). +2. Copy it to `.ai/icon_{name}/{LETTER}/source.png` (archive per-iteration source). +3. Delete the temp `.ai/icon_HASH.png` if it was copied (not moved). + +If NO image was grabbed: +- **New icon with no image**: Ask the user to provide a screenshot. STOP. +- **Follow-up with no image**: The existing `source.png` in the icon root carries forward. Copy it to `.ai/icon_{name}/{LETTER}/source.png`. If no source.png exists at all, ask the user for an image. + +### Step 0g: Verify renderer + +Locate the render tool (`codegen_style` with `--render-svg` mode): + +```bash +if [[ "$OSTYPE" == darwin* ]]; then + ls out/Telegram/codegen/codegen/style/Debug/codegen_style +else + ls out/Telegram/codegen/codegen/style/Debug/codegen_style.exe +fi +``` + +If missing, build it: `cmake --build out --config Debug --target codegen_style` + +Test on a known good SVG (use the appropriate binary path for the OS): +```bash +CODEGEN=$(if [[ "$OSTYPE" == darwin* ]]; then echo out/Telegram/codegen/codegen/style/Debug/codegen_style; else echo out/Telegram/codegen/codegen/style/Debug/codegen_style.exe; fi) +$CODEGEN --render-svg Telegram/Resources/icons/menu/tag_add.svg .ai/icon_{name}/test_render.png 512 +``` + +If works → delete test render, set `RENDER_AVAILABLE = true`. If fails → `RENDER_AVAILABLE = false`. + +## Phase 1: Vectorize & Post-process + +### Step 1a: Call vectosolve + +Use the `mcp__vectosolve__vectorize` tool with `file_path` set to the **absolute path** of `.ai/icon_{name}/{LETTER}/source.png`. + +**If this fails, STOP IMMEDIATELY.** Do NOT try to generate the SVG manually or by any other means. Report the error to the user and let them fix the issue (bad API key, no credits, network error, etc.). + +Save the returned SVG content to `.ai/icon_{name}/{LETTER}/raw_vectosolve.svg`. + +The MCP tool calls the vectosolve API ($0.20/call). The API key is stored in `~/.claude.json` MCP config (never in the repository). + +### Step 1b: Post-process the SVG + +The vectosolve SVG will have colors from the mockup, arbitrary dimensions, and possibly a non-square aspect ratio from a non-square screenshot crop. Post-processing fixes this by adjusting the **viewBox** — leave path coordinates untouched. + +**Do NOT transform path coordinates.** Vectosolve's paths are correct — the only thing wrong is the framing. All geometry adjustments are done by manipulating the `viewBox` and the `width`/`height` attributes. + +#### Sub-step 1: Read the request and determine parameters + +Before touching the SVG, determine these from the user's request and context.md: + +1. **Output size** (`OUT_W × OUT_H`): default is `24px × 24px` for menu icons. The user may request different dimensions (e.g., 36×36, 48×48, or non-square). Always check the request. +2. **Content padding**: default is ~2px equivalent on each side at the output scale (so content fills roughly (OUT_W-4) × (OUT_H-4)). The user may request different padding or edge-to-edge. +3. **Centering**: default is centered both horizontally and vertically. The user may request specific alignment (e.g., "align to bottom"). + +#### Sub-step 2: Parse the raw SVG + +1. Extract the `viewBox`: `viewBox="VB_X VB_Y VB_W VB_H"` (typically `0 0 W H`). +2. Identify ALL paths. Classify each: + - **Background**: a rect or path spanning the full viewBox (first path that's a simple rectangle matching the viewBox bounds). **Remove it entirely.** + - **Content**: the actual icon shapes. **Keep these, paths unchanged.** +3. If paths have `transform="translate(TX,TY)"` attributes, that's fine — keep them as-is. The viewBox framing will work regardless. + +#### Sub-step 3: Compute the content bounding box + +Estimate the bounding box of the content paths (after removing the background). You can either: +- Eyeball it from the path coordinates (look at first/last M commands and extremes of curves) +- Or for precision, write a quick script to parse the paths and find min/max X/Y + +Call the result: `CX_MIN, CY_MIN, CX_MAX, CY_MAX`. Content dimensions: `CW = CX_MAX - CX_MIN`, `CH = CY_MAX - CY_MIN`. + +#### Sub-step 4: Compute the new viewBox + +The viewBox determines what part of the SVG coordinate space maps to the output rectangle. By expanding the viewBox beyond the content bounds, we add padding. By making the viewBox aspect ratio match the output aspect ratio, we prevent stretching. + +1. **Output aspect ratio**: `OUT_AR = OUT_W / OUT_H` (for 24×24 this is 1.0). +2. **Padding in SVG coordinates**: we want ~2px padding at output scale. The scale factor is `OUT_W / VB_CONTENT_W` approximately, so padding in SVG coords = `2 * (CW / (OUT_W - 4))` (or similar — the exact formula depends on which dimension is dominant). Simpler approach: aim for content to occupy ~83% of the viewBox (≈ 20/24), so: + - `PADDED_W = CW / 0.83` + - `PADDED_H = CH / 0.83` +3. **Match output aspect ratio**: the viewBox aspect ratio must equal `OUT_AR` to avoid stretching. + - If `PADDED_W / PADDED_H > OUT_AR`: width is dominant → `VB_W = PADDED_W`, `VB_H = VB_W / OUT_AR` + - If `PADDED_W / PADDED_H < OUT_AR`: height is dominant → `VB_H = PADDED_H`, `VB_W = VB_H * OUT_AR` + - If equal: `VB_W = PADDED_W`, `VB_H = PADDED_H` +4. **Center the content** in the new viewBox: + - `VB_X = CX_MIN - (VB_W - CW) / 2` + - `VB_Y = CY_MIN - (VB_H - CH) / 2` + - (Adjust if the user requested non-centered alignment) + +The new viewBox is: `viewBox="VB_X VB_Y VB_W VB_H"`. + +#### Sub-step 5: Recolor to white-on-transparent + +- Replace ALL `fill` color values (anything that isn't `none`) with `#FFFFFF`. +- Remove ALL `stroke` and `stroke-width` attributes entirely. +- Remove `opacity` attributes if present. + +#### Sub-step 6: Determine path composition + +Look at the icon's visual structure and decide how paths should combine: +- **Outlined shape** (e.g., circle outline with something inside): combine outer + inner cutout into one `<path>` with `fill-rule="evenodd"`. +- **Separate distinct parts** (e.g., magnifying glass + checkmark): keep as separate `<path>` elements. +- **Filled shape with cutout** (e.g., filled circle with checkmark punched out): combine into one path with `fill-rule="evenodd"`. + +#### Sub-step 7: Assemble final SVG + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<svg width="{OUT_W}px" height="{OUT_H}px" viewBox="{VB_X} {VB_Y} {VB_W} {VB_H}" xmlns="http://www.w3.org/2000/svg"> + <g stroke="none" fill="none" fill-rule="evenodd"> + <path d="..." fill="#FFFFFF"></path> + </g> +</svg> +``` + +- `width`/`height` = the output size from the request (default `24px`/`24px`). +- `viewBox` = the computed viewBox from Sub-step 4. The SVG renderer maps this coordinate region to the output size. +- Path `d` attributes are **unchanged** from vectosolve output (just background removed, colors replaced). +- No `<title>`, `id`, `xmlns:xlink`, `version`, `class`, `style`, XML comments, `<metadata>`, or `preserveAspectRatio`. +- No `<circle>`, `<rect>`, `<line>` — only `<path>`. + +Write the final SVG to `.ai/icon_{name}/{LETTER}.svg`. + +### Step 1c: Render + +If `RENDER_AVAILABLE`: +```bash +$CODEGEN --render-svg ".ai/icon_{name}/{LETTER}.svg" ".ai/icon_{name}/render_{LETTER}.png" 512 +``` + +Read the render to visually verify the result. + +## Phase 2: Review + +After rendering, assess the result: + +1. **Recognizable?** The icon should be clearly identifiable as the intended symbol. +2. **Scale reasonable?** Should fill the space appropriately with ~2-3px padding. +3. **Clean lines?** No broken paths, artifacts, or unwanted elements. +4. **Correct colors?** All white on transparent (no leftover colors from the mockup). + +If the result looks good → proceed to Phase 3 (Output). + +If there are fixable issues (stray element, missed color, etc.) → fix the SVG directly, re-render, and re-check. + +If the result is poor (vectosolve couldn't handle the input well) → report to the user and suggest: +- Trying a cleaner/larger crop of the icon +- Providing a different screenshot +- Following up: `/icon {icon_name} <description of what to change>` + +## Phase 3: Output + +1. Read the `Target:` line from `.ai/icon_{name}/context.md` to get the output path. + +2. Copy the final SVG to that target path (e.g., `Telegram/Resources/icons/menu/{icon_name}.svg`). + +3. Update `.ai/icon_{name}/context.md` — append to the end: + ``` + ## Latest Output + Letter: {LETTER} + Written to: {target_path} + ``` + +4. Report to the user: + - Final icon file path + - Number of vectosolve calls made (cost at $0.20/call) + - Suggest verifying visually + - Working directory `.ai/icon_{name}/` has all iterations + - Elapsed time since `$START_TIME` (format `Xm Ys`) + - Follow-up: `/icon {icon_name} <description of what to change>` + +## Text-only Follow-ups (no new image) + +When a follow-up has no attached image, the user wants to refine the existing SVG based on text feedback. In this case: + +1. Skip Phase 1 (no vectosolve call needed). +2. Read the latest SVG (`.ai/icon_{name}/{prev_letter}.svg`). +3. Read the latest render if available. +4. Apply the user's requested changes by editing the SVG directly. +5. Save as `.ai/icon_{name}/{LETTER}.svg`. +6. Render, review, and output as normal (Phases 1c → 3). + +If the changes are too complex for manual SVG editing, suggest the user provide a new screenshot instead. + +## Error Handling + +- If clipboard grab fails → tell user to re-copy and retry. +- If vectosolve returns an error → report it and suggest a different/cleaner screenshot. +- If vectosolve returns SVG that can't be parsed → save raw output for debugging, report to user. +- If the render helper fails → set `RENDER_AVAILABLE = false`, continue with SVG-only review. +- If post-processing produces a broken SVG → fall back to the raw vectosolve output and do lighter cleanup. diff --git a/.claude/commands/implement.md b/.claude/commands/implement.md new file mode 100644 index 00000000000000..f65644576341c0 --- /dev/null +++ b/.claude/commands/implement.md @@ -0,0 +1,290 @@ +--- +description: Autonomously implement a task (split into a task list if needed), then implement + test each task to approval via isolated per-task subagents +allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task, AskUserQuestion, TodoWrite +--- + +# Implement - Autonomous Implement-and-Test Orchestrator + +You are the **top orchestrator**. You take a request — an inline description OR a task-list file — +normalize it into a project with a testability-split task list, and drive each task to +test-approval through an isolated per-task `task-runner` subagent. Your context must stay lean: you +hold only the task list and one compact summary per task. All heavy work (planning, coding, +building, testing) happens inside subagents whose context is discarded. + +This is the tested superset of `/task`: it reuses `/task`'s phase prompts for implementation and +adds the impl⇄test loop defined in `.agents/shared/test-loop.md`. + +**Arguments:** `$ARGUMENTS` = ONE of: +- an inline task description (e.g. `add a dark-mode toggle to settings`) +- a path to a task-list file (e.g. `.ai/communities/tasks.txt` — a rough list of tasks to refine) +- an existing project name to resume (e.g. `communities`), optionally followed by extra work +- **just a project name with a prepared `.ai/<project>/tasks/about.md`** — the default task source + (see Artifacts). With no other input, `/implement <project>` plans and implements straight from + that file, so `/implement communities` alone fires the full pipeline off `.ai/communities/tasks/about.md`. +May also reference attached images. + +## Config + +Runs in the **current checkout** — wherever `/implement` is invoked. No worktrees are created; all +paths below are relative to that repository root. + +``` +BUILD = cmake --build ./out --config Debug --target Telegram +EXE = ./out/Debug/Telegram.exe +TEST_ACCOUNT = ./out/Debug/test_TelegramForcePortable # user-prepared golden; launch gate aborts if absent +MAX_ATTEMPTS = 4 +``` + +Tasks run **sequentially** in this one checkout (the build cache stays warm; app runs must +serialize against the account anyway). To parallelize, launch `/implement` in a different +checkout/slot (e.g. `C:\Telegram\tdesktop`, `D:\Telegram\tdesktop`, `D:\Telegram\twin`) — each run +is independent and single-tree. Don't run the **test phase** in two slots against the same account +at once (concurrent clients on one auth key can trigger a session reset); give parallel slots +separate test accounts. + +## Artifacts (per project) + +- `.ai/<project>/tasks/about.md` — the **default task source**: a human-prepared description (or + rough list) of the batch to implement, with any mockups dropped beside it in `.ai/<project>/tasks/`. + This is the file you prepare; the planner reads it as SOURCE when `/implement <project>` is invoked + with no other input. It is **distinct** from the project blueprint `.ai/<project>/about.md` (the + `tasks/` subdir is what disambiguates them). +- `.ai/<project>/implementing.md` — the canonical, final, testability-split task list (descriptions + + status). Your single source of truth; you are its only writer. +- `.ai/<project>/images/` — illustrations referenced by tasks (`images/01.png`, ...). +- `.ai/<project>/<letter>/` — per-task artifacts (context, plan, review, test, result, overlay). +- `.ai/<project>/about.md` — project blueprint (the `/task` convention). + +## Done (for `/goal` loop mode) + +The run is **done** when every task in `implementing.md` has `Status: approved` or +`Status: blocked: <reason>`. Under a `/goal` loop this is the stop condition. The run is +**resumable**: re-invoking with the project name reads `implementing.md` and continues from the +first unfinished task. + +## Phase A: Setup & input resolution + +1. Record start time (`Get-Date`). +2. **Test-account gate (hard precondition — before any work).** If + `out/Debug/test_TelegramForcePortable` does NOT exist, STOP the entire command immediately and + tell the user: the test account is not prepared — create `out/Debug/test_TelegramForcePortable` + (a portable-data folder authed to a throwaway test account) before `/implement` can run, because + autonomous testing is impossible without it. Do no implementation work. +3. **Resolve `$ARGUMENTS` into (project, SOURCE, mode) — without reading task files or images.** + The main thread never loads task prose or assets; resolving needs only paths and existence + checks. SOURCE ends up as EITHER inline text OR a confirmed file path (the planner reads it). + - **File input** — if the first token is a path: confirm it exists (`Test-Path`, do NOT read it) + and set SOURCE = that path. If the path is under `.ai/<name>/`, project = `<name>`; else derive + a short kebab name from the filename. Mode = **extend** if that project already has + `implementing.md`, else new. + - **Existing project** — else if `.ai/<FIRST_TOKEN>/` exists: project = `FIRST_TOKEN`. + - If there is a **remainder** → mode = **extend**: if the remainder is itself a path to an + existing file, SOURCE = that path (confirm with `Test-Path`, do NOT read it); otherwise + SOURCE = the remainder text. + - If the remainder is **empty**, resolve SOURCE in this priority order (existence checks only, + `Test-Path`, do NOT read): + 1. If `.ai/<project>/tasks/about.md` exists → SOURCE = that file (the **default task + source**); mode = **extend** if `implementing.md` already exists, else **new**. This is + the `/implement <project>` with a prepared task source path — it fires the full pipeline. + 2. Else if `implementing.md` exists → mode = **resume** (no SOURCE; Phase C finishes the + still-unfinished tasks). + 3. Else there is nothing to implement — tell the user to prepare `.ai/<project>/tasks/about.md` + (or pass a description / task-file path) and stop. + - **New inline** — else SOURCE = the `$ARGUMENTS` text; pick a unique short kebab-case project + name (consult `ls .ai/`). + After this step you always have a project name and either a SOURCE (inline text or a confirmed + path) or mode = **resume** — and you have read neither the file nor any image. +4. Create `.ai/<project>/` and `.ai/<project>/images/` if new. +5. **Images must be on disk.** The planner reads images as files, and subagents cannot see chat + attachments — so every image a task needs must exist as a file (referenced by the SOURCE file, or + under `.ai/<project>/images/`). The main thread **cannot** save a pasted/inline chat image to disk + (`Write` is text-only; there is no save-attachment tool, and on Windows clipboard-paste isn't even + supported). So if the user only pasted an image into the chat, either ask them to drop it into + `.ai/<project>/images/` as a file, or — as a lossy fallback — write a textual description of it for + the planner. Do not claim to have saved it. Images the SOURCE file *references by path* are the + planner's job, not handled here. +6. If mode = **resume**, skip Phase B and go to Phase C. + +## Phase B: Planning & testability split + +Spawn one planner subagent (Task, `general-purpose`): + +``` +You are a planning/splitting agent for a large C++ codebase (Telegram Desktop). + +SOURCE — EITHER an inline request OR a path to a task-list file. If it is a PATH, READ it yourself +(and any task files it points to); the main thread has NOT read it. If it is inline text, use it as +the request: +<the inline description, or the file path> +PROJECT: <project> MODE: <new | extend> + +IMAGES — the SOURCE and/or its task file may reference images by path (resolve them relative to the +SOURCE file's directory, or use absolute paths; when SOURCE is `.ai/<project>/tasks/about.md`, its +sibling files in `.ai/<project>/tasks/` — e.g. the mockup PNGs there — are those images). READ every +referenced image yourself, then COPY +each into `.ai/<project>/images/` with a descriptive kebab-case name, and reference it from the +specific task(s) it pertains to (see "Images per task" below). The main thread did NOT read or move +these — that is your job. If an image exists only as a textual description (because the user pasted +it into chat and it could not be saved to a file), it is provided here — treat that description as +the visual spec: <description(s) or none> + +Read AGENTS.md. Briefly scan the codebase to gauge scope. Produce the FINAL ordered task list that +satisfies BOTH constraints for every task: + +- **Implementable in one pass**: a single agent with a ~200k-token budget must be able to implement + the task fully on its own WITHOUT triggering context compaction — i.e. a bounded change it can + read and edit across a handful of files, not a sweep across dozens. If a unit is too big, split + it. +- **Independently testable**: each task must yield an observable behavior the test agent can drive + from an in-app debug overlay and verify via log/screenshot. Split on testable seams, so each task + ends at a point where something concrete can be exercised and checked. + +Use the minimal number of tasks subject to both constraints; preserve dependency order (a task +comes before any task that depends on it). If the SOURCE is already a list, respect its intended +breakdown and refine only as needed: split entries that are too big or not independently testable; +you may merge trivially tiny adjacent entries if the result is still one testable unit. + +Write `.ai/<project>/implementing.md` in EXACTLY this format: + +# Implementing: <project> + +## Goal +<one-line overall goal> + +## Tasks + +### a: <imperative title> +Status: todo +<2-4 line self-contained description: what to implement and the observable, testable result. Enough +that a fresh agent can act on it.> +Images: images/<file> — <caption> (this line only if the task uses an image) + +### b: <imperative title> +Status: todo +<...> + +**Images per task (required).** Every provided image is a design/resource the work must satisfy. +Attach each to the task(s) it pertains to via the `Images:` line, with a caption stating what that +task must match in it (the exact wording on a mockup, the glyph/shape of a resource, etc.). A task +that changes UI / visual / asset behavior MUST cite the specific mockups/resources it has to match; +do not leave such a task without its images, and do not leave a provided image referenced by no task +(if one genuinely applies to none, note why). These per-task references are the oracle the test +phase verifies against — be specific and per-task, not one shared dump on the first task. + +Use letters a, b, c... as task ids. Do not plan internals or implement. When done, reply with ONLY a +compact confirmation — `ready — <N> tasks` (extend: `ready — appended <letters>`); do NOT echo the +task list or image contents back, the main thread reads `implementing.md` itself. +``` + +For **extend** mode, instead instruct the planner to FIRST read the existing `implementing.md`, then +rewrite it as: (1) a TRIMMED completed-history — keep only the **three most recent** `Status: approved` +task blocks (the three nearest the bottom of the file) and drop all earlier approved ones; (2) every +still-unfinished task left untouched, in place and with its status — that is all `todo`, `in-progress`, +and `blocked` blocks (never drop these); then (3) APPEND new lettered tasks (continuing the letter +sequence from the highest letter still present after the trim) after them. The trim only removes +already-approved entries from the list — it never touches the per-task `.ai/<project>/<letter>/` +artifacts on disk, so a follow-up letter can still read an earlier letter's `context.md` even after its +block was trimmed out of `implementing.md`. It must append ONLY tasks from SOURCE not already +represented in `implementing.md` — so re-running `/implement <project>` against an unchanged default +`tasks/about.md` appends nothing (the planner replies `ready — appended (none)`, still applying the +completed-history trim) and Phase C just finishes whatever is still unfinished. (Any +`todo`/`in-progress` leftovers from an interrupted run are picked up by Phase C regardless, so +defaulting to extend never loses an in-flight batch — it is a superset of resume.) + +After the planner replies `ready`, read `implementing.md` back ONCE (your first and only load of the +task prose; you never read the images). Initialize a TodoWrite list mirroring the tasks so progress +is visible. + +## Phase C: Per-task loop + +For each task in `implementing.md` whose `Status` is not `approved`/`blocked`, in order: + +1. Set its `Status: in-progress` (and mark `in_progress` in TodoWrite). +2. Spawn ONE `task-runner` subagent (Task, `general-purpose`) with the prompt below. Wait for it. +3. Read ONLY its compact summary block (the `task-runner` writes all detail to `.ai/`). +4. Update the task's `Status:` line — `approved` if `STATUS: DONE`, else `blocked: <reason>`. +5. If `DISCOVERED` lists new tasks, append them to `implementing.md` as new lettered `### <letter>:` + blocks (`Status: todo`) **after** the current remaining tasks, and add them to TodoWrite. (You + are the only writer of `implementing.md`, so there are no write races.) +6. If `STATUS: BLOCKED`, stop the loop and report to the user — do not start the next task. (Under + `/goal`, surfacing the blocker is the correct stop; the loop should not spin on a blocked task.) + +### task-runner prompt + +``` +You are a task-runner for ONE task in an autonomous implement-and-test workflow on Telegram +Desktop (C++ / Qt). You own this task end to end and isolate its context from the orchestrator. +You MAY and SHOULD spawn your own subagents (the Task tool is available to you). + +PROJECT: <project> TASK: <letter> — <title> +TASK DESCRIPTION: +<the task's full description block from implementing.md> +IMAGES: <referenced .ai/<project>/images/* paths, or none — Read them if present> +TASK_DIR: .ai/<project>/<letter>/ +TASK_ID: <project>-<letter> + +Config (paths relative to this checkout): BUILD=<...> EXE=<...> MAX_ATTEMPTS=<...>. The test account +is the out/Debug/ portable-data folders (see test-loop.md "Test account"). + +Read first: AGENTS.md; REVIEW.md; `.claude/commands/task.md` (for the exact Phase 1-6 prompt +templates); `.agents/shared/test-loop.md` (for the testing phase). For a follow-up letter, also read +`.ai/<project>/about.md` and the previous letter's `context.md`. + +Run this pipeline for THIS task only, spawning a fresh subagent per phase (so each phase's output +stays in YOUR context, not the orchestrator's): + +1. CONTEXT — run task.md's Phase 1 (new) or Phase 1F (follow-up) prompt for this task; produces + `<TASK_DIR>/context.md` (and `about.md` for the project). +2. PLAN — task.md Phase 2 -> `<TASK_DIR>/plan.md`. +3. ASSESS — task.md Phase 3 (refine plan, size phases). +4. IMPLEMENT— task.md Phase 4, one subagent per plan phase. Implementation agents do NOT commit + yet; you commit after build passes. +5. BUILD — task.md Phase 5 (build with BUILD, fix errors). On file-lock errors, run the + path-scoped kill of THIS checkout's binary (see test-loop.md "Serialize app runs") and retry + once, else stop. +6. REVIEW — task.md Phase 6 but a SINGLE pass (not 3): one review agent, then one fix agent if + NEEDS_CHANGES, then rebuild. (Tests catch behavior; review catches dead code / duplication / + placement / style.) +7. COMMIT — `git add -A && git commit` with a concise plain-language subject (≤ ~50-60 chars, + matching recent `git log` style; usually the whole message — add a short plain body only if the + subject can't carry it). NO `Autotask:`/attempt trailer and NO `Co-Authored-By:`/attribution line + (this overrides the default; see test-loop.md "Commit message"). Commit submodules first if dirty, + then bump the pointer. Record the commit SHA as IMPL_SHA (you track the attempt number yourself). +8. TEST — run the loop in `.agents/shared/test-loop.md` to APPROVED, BLOCKED, or attempt cap. + Spawn a test-author subagent and feed it BOTH sides per test-loop.md "Design the tests from THIS + task": (1) the TASK SPEC — this task's full description block above PLUS its referenced IMAGES + (tell it to READ the mockups; they show the intended result), and (2) the implementation — + `git show <IMPL_SHA>` + touched files. It designs a falsifiable oracle per change and writes the + plan into `<TASK_DIR>/test.md` BEFORE running (for visual/asset changes the oracle compares the + tight crop against old vs intended-new art — judged VISUALLY, never by hash/byte; mobile mockups + are not pixel targets), covers every surface the task names, and never reuses another task's + navigate+screenshot. You drive RUN/ASSESS yourself, ADVERSARIALLY (no pass-by-inference; missing + evidence = TEST_FLAW; no-difference-from-before = IMPL_BUG), and keep the human-readable + `<TASK_DIR>/test.md` report. Spawn an impl-fix subagent on IMPL_BUG (it commits the next attempt → + new IMPL_SHA). After each run, save the overlay patch into TASK_DIR and `git reset --hard + <IMPL_SHA>` so the checkout returns to impl-only. Run the test-account SETUP steps before each + launch and honor every test-account hard rule (serialize app runs; avoid destructive calls). + +Skip TEST only if the task changed no runnable behavior (docs/config only) — say so explicitly. + +When done, write nothing new to chat except the compact summary block from test-loop.md +("TASK/STATUS/VERDICT/ATTEMPTS/TOUCHED/DISCOVERED/NOTES"). All reasoning lives in `.ai/`. +``` + +## Completion + +When the loop ends (all tasks approved/blocked, or a blocked task stopped it): +1. Summarize per task: approved vs blocked, attempts, files touched, key test evidence. +2. List any discovered tasks that were added. +3. Note the project name for `/implement <project> <follow-up>`. +4. Show total elapsed time (`Xh Ym Zs`, omit zero components). +5. Remind that test overlays are saved as `.ai/<project>/<letter>/test-overlay.patch` and the + checkout is left at each task's implementation commit (overlays reset away). + +## Error handling + +- A `task-runner` returning BLOCKED stops the loop; report its reason and the `test.md` path. +- If `implementing.md` or any artifact is malformed, re-spawn that step with tighter instructions. +- Never proceed past a file-lock build error — ask the user to close `Telegram.exe`. +- The launch gate (Phase A) guarantees the test account exists before any work begins; if it is + absent the command never starts. diff --git a/.claude/commands/reflect.md b/.claude/commands/reflect.md new file mode 100644 index 00000000000000..f05d292bea2aa6 --- /dev/null +++ b/.claude/commands/reflect.md @@ -0,0 +1,136 @@ +--- +description: Learn from corrections — examine staged vs unstaged diffs and optionally distill insights into AGENTS.md or REVIEW.md +allowed-tools: Read, Edit, Bash(git diff:*), Bash(git status:*), Bash(git log:*), Bash(ls:*), AskUserQuestion +--- + +# Reflect — Learn from Corrections + +You are a reflection agent. Your job is to examine the difference between what an AI agent produced (staged changes) and what the user corrected (unstaged changes), and determine whether any **general, reusable insight** can be extracted and added to the project's coding guidelines. + +**CRITICAL: Use extended thinking ultrathink for your analysis. This requires deep, careful reasoning.** + +## Arguments + +`$ARGUMENTS` = "$ARGUMENTS" + +If `$ARGUMENTS` is provided, it is a task name (project name from the `/task` workflow). This means the agent was working within `.ai/<task-name>/` and you should read the task context for deeper understanding of what the agent was trying to do. + +If `$ARGUMENTS` is empty, skip the task context step — just work from the diffs alone. + +## Context + +The workflow is: +1. An AI agent implemented something and its changes were staged (`git add`). +2. The user reviewed and corrected the agent's work. These corrections are unstaged. +3. You are now invoked to reflect on what went wrong and whether it reveals a pattern. + +## Step 1: Gather the Diffs and Task Context + +Run these commands in parallel: + +```bash +git diff --cached # What the agent wrote (staged) +git diff # What the user corrected (unstaged, on top of staged) +git status # Which files are involved +``` + +If either diff is empty, tell the user and stop. Both diffs must be non-empty for reflection to be meaningful. + +### Task context (only if `$ARGUMENTS` is non-empty) + +The task name is `$ARGUMENTS`. Read the task's project context: + +1. Read `.ai/$ARGUMENTS/about.md` — the project-level description of what this feature does. +2. Find the latest task iteration folder: list `.ai/$ARGUMENTS/` and pick the folder with the highest letter (`a`, `b`, `c`, ...). +3. Read `.ai/$ARGUMENTS/<latest-letter>/context.md` — the detailed implementation context the agent was working from. + +This helps you distinguish between: +- **Task-specific mistakes** — the agent misunderstood this particular feature's requirements or made a wrong choice within the specific problem. These are NOT documentation-worthy. +- **General convention mistakes** — the agent did something that violates a pattern the codebase follows broadly, regardless of which feature is being implemented. These ARE potentially documentation-worthy. + +Having the task context makes this distinction much sharper. Without it, you might mistake a task-specific correction for a general pattern or vice versa. + +## Step 2: Read the Current Guidelines + +Read both files: +- `AGENTS.md` — development guidelines: build system, coding style, API usage patterns, UI styling, localization, rpl, architectural conventions, "how to do things" +- `REVIEW.md` — mechanical style and formatting rules: brace placement, operator position, type checks, variable initialization, call formatting + +Read them carefully. You need to know exactly what's already documented to avoid duplicates and detect contradictions. + +## Step 3: Analyze the Corrections + +Now think deeply. For each correction the user made, ask yourself: + +1. **What did the agent do wrong?** Understand the specific mistake. +2. **Why was it wrong?** Identify the underlying principle. +3. **Is this already covered by AGENTS.md or REVIEW.md?** Check carefully: + - If the existing rule's scope, title, and examples **clearly cover** this exact scenario and the agent just ignored it — that's not a documentation problem. Skip it. + - If the existing rule **technically applies** but its scope is too narrow, its examples don't illustrate this usage pattern, or its wording would reasonably lead an agent to think it doesn't apply here — **the rule needs improvement**. Treat this as a potential insight (broaden the scope, add examples, adjust wording). A rule that agents repeatedly violate is an ineffective rule. +4. **Is this specific to this particular task, or is it general?** Most corrections are task-specific ("wrong variable here", "this should call that function instead"). These are NOT documentation-worthy. Only patterns that would apply across many different tasks are worth capturing. +5. **Would documenting this actually help a future agent?** Some things are too context-dependent or too obvious to be useful as a written rule. Be honest about this. + +## Step 4: Decision + +After analysis, you MUST reach one of these conclusions: + +### Conclusion A: No actionable insight + +The corrections are purely task-specific, or the existing documentation clearly and specifically covers the exact scenario and the agent simply ignored it. Say what the corrections were and why no doc changes are needed. Then **stop**. + +### Conclusion B: New insight found + +You can articulate a **concise, general rule** that: +- Applies broadly (not just to this one task) +- Is not already documented +- Would genuinely help a future agent avoid the same class of mistake +- Can be expressed in a few sentences with a clear code example + +If you have a new insight, proceed to Step 5. + +### Conclusion C: Existing rule needs improvement + +A rule already exists in AGENTS.md or REVIEW.md, but its **scope is too narrow**, its **examples don't cover** the pattern the agent encountered, or its **wording** would reasonably lead an agent to think the rule doesn't apply. The agent's mistake is evidence the rule isn't effective. + +This is NOT the same as Conclusion A. The test: would a careful agent, reading the existing rule, clearly know it applies to this specific situation? If no — the rule needs to be broadened, its examples expanded, or its title/scope adjusted. Proceed to Step 5. + +**Common signs of an ineffective rule:** +- The rule's title or scope restricts it to a context narrower than the actual principle (e.g., "in localization calls" when the pattern applies generally) +- The examples only show one usage pattern, and the agent encountered a different one +- The wording describes *what* to use but not *when* — so agents only apply it in situations that look like the examples + +## Step 5: Categorize and Check for Contradictions + +### Where does it belong? + +- **REVIEW.md** — if it's a mechanical/style rule: formatting, naming, syntax preferences, call structure, brace/operator placement, type usage patterns. Rules that can be checked by looking at code locally without understanding the broader feature. +- **AGENTS.md** — if it's an architectural/behavioral guideline: how to use APIs, where to place code, design patterns, build conventions, module organization, reactive patterns (rpl), localization usage, style system usage. Rules that require understanding the broader context. + +### Does it contradict existing content? + +Read the target file again carefully. Check if: +1. The new insight **contradicts** an existing rule — if so, do NOT just append or just remove. Instead, use AskUserQuestion to present both the existing rule and the new insight to the user, explain the contradiction, and ask how to reconcile them. +2. The new insight **overlaps** with an existing rule — if so, consider whether the existing rule should be extended/refined rather than adding a separate entry. +3. The new insight is **complementary** — it adds something new without conflicting. This is the simplest case. + +## Step 6: Propose the Change + +**Do NOT silently edit the files.** First, present your proposed change to the user: + +- Quote the exact text you want to add or modify +- Explain which file and where in the file +- Explain why this is general enough to document +- If modifying existing text, show the before and after + +Use AskUserQuestion to get the user's approval before making any edit. + +Only after the user approves, apply the edit using the Edit tool. + +## Rules + +- **Keep docs lean and high-signal.** Don't add vague or overly specific rules. But don't default to inaction either — if the user had to manually fix something that a better-worded rule would have prevented, improving that rule is high-signal work. +- **Never dump corrections verbatim.** The goal is distilled principles, not a changelog of mistakes. +- **One insight per reflection, maximum.** If you think you see multiple insights, pick the strongest one. You can always run `/reflect` again next time. +- **Keep the same style.** Match the formatting, tone, and level of detail of the target file. REVIEW.md uses specific before/after code examples. AGENTS.md uses explanatory sections with code snippets. +- **Don't add "don't do X" rules.** Frame rules positively: "do Y" is better than "don't do X." Show the right way, not just the wrong way. +- **No meta-commentary.** Don't add notes like "Added after reflection on..." — the rule should read as if it was always there. diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 00000000000000..baaa7c261db67d --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,119 @@ +--- +description: Prepare changelog, set version, and commit a new release +allowed-tools: Read, Bash, Edit, Grep, AskUserQuestion +--- + +# Release — Changelog, Set Version, Commit + +Full release flow: generate changelog entry, run `set_version`, and commit. + +**Arguments:** `$ARGUMENTS` = "$ARGUMENTS" + +Parse `$ARGUMENTS` for two optional parts (in any order): +- A **version number** like `6.7` or `6.7.0` — if provided, use it as the new version exactly. +- The word **"beta"** — if present, mark the release as beta. + +If no version number is given, auto-increment the patch component (see step 2). + +## Steps + +### 1. Check git status is clean + +Run `git status --porcelain`. If there are any uncommitted changes, **stop** and ask the user to commit or discard them before proceeding. Do not continue until status is clean. + +### 2. Read the current changelog + +Read `changelog.txt` from the repository root. Note the **latest version number** on the first line (e.g. `6.6.3 beta (12.03.26)`). Parse its major.minor.patch components. + +### 3. Determine the new version number + +**Important:** version numbers are shared across the beta and stable tracks — the sequence advances through both. The same `major.minor.patch` cannot be released as both beta and stable. A beta release "uses up" that number; the next stable must bump to a new patch. + +- **If a version was provided in arguments**, use it directly (append `.0` if only major.minor was given). If the latest changelog entry already used this exact number (regardless of beta/stable), warn the user — they likely want a bumped patch. +- **If no version was provided**, auto-increment from the latest changelog version: + - If it was a beta, and the new release is **not** beta, bump the patch component by 1 (do **not** reuse the beta's number for stable). + - If the new release is beta and the latest was also beta with the same major.minor, bump patch. + - Otherwise bump the patch component by 1. +- Present the chosen version to the user and ask for confirmation before proceeding. If the user suggests a different version, use that instead. + +### 4. Fetch tags and determine the last release tag + +Run `git fetch origin --tags` first to ensure all tags from the public repository are available locally. Then run `git tag --sort=-v:refname` and find the most recent `v*` tag. This is the baseline for the diff. + +### 5. Collect commits + +Run `git log <last-tag>..HEAD --oneline` to get all commits since the last release. + +### 6. Write the changelog entry + +Analyze every commit message. Group them mentally into features, improvements, and bug fixes. Then produce **brief, user-facing bullet points** following these rules: + +- **Style:** Match the existing changelog tone exactly — short, imperative sentences starting with a verb (Fix, Add, Allow, Show, Improve, Support…). Keep the trailing periods (the existing changelog uses them). +- **Brevity:** Each bullet should be one short sentence, around 80 characters when possible. No implementation details. No commit hashes. +- **Selection:** Only include changes that matter to end users. Skip CI, build infra, submodule bumps, code style, refactors, and intermediate WIP commits. Collapse many related commits (e.g. a dozen image-editor commits) into one or two bullets. +- **Ordering:** Features first, then improvements, then bug fixes. +- **Quantity:** Aim for 4-12 bullets total depending on the amount of changes. + +### 7. Format and insert into changelog.txt + +Use this exact format (date is today in DD.MM.YY): + +``` +<version> [beta ](DD.MM.YY) + +- Bullet one. +- Bullet two. +``` + +Prepend the new entry at the very top of `changelog.txt`, separated by a blank line from the previous first entry. Use the Edit tool. + +**Never delete or edit existing entries**, even if the new stable entry merges bullets from prior beta(s). Prior beta blocks remain as historical record for beta-track users. + +**For stable releases spanning prior beta(s):** the entry should cover everything since the last **stable** release (not just since the last beta), including noteworthy beta-shipped features, trimmed as needed to stay within 4–12 bullets. + +### 8. Wait for approval + +After writing the entry to `changelog.txt` (step 7), tell the user the changelog has been updated and ask them to review it in the IDE. They can edit it directly and tell you to continue, or tell you what to change in chat. Do **not** print the full entry in chat — the file itself is the review surface. + +**Do NOT proceed until the user explicitly approves.** + +### 9. Run set_version + +Once approved, run the `set_version` script from the repository root. On Windows: + +``` +.\Telegram\build\set_version.bat <version_arg> +``` + +Where `<version_arg>` is formatted as the `set_version` script expects: +- Stable: `6.7.0` or `6.7` +- Beta: `6.7.0.beta` + +Verify the script exits successfully (exit code 0). If it fails, show the error and stop. + +### 10. Commit + +Stage all changes and create a commit. The commit message format: + +**First line:** +- For stable: `Version <major>.<minor>.` if patch is 0, otherwise `Version <major>.<minor>.<patch>.` +- For beta: `Beta version <major>.<minor>.<patch>.` + +**Then an empty line, then the changelog bullets.** Copy bullet lines from the changelog as-is. Only wrap lines that exceed 72 characters; shorter lines must stay on a single line. When wrapping is needed, break at logically correct places (between words/phrases) and indent continuation lines with two spaces. + +Example commit message: +``` +Beta version 6.6.3. + +- Drawing tools in image editor + (brush, marker, eraser, arrow). +- Draw-to-reply button in media viewer. +- Trim recorded voice messages. +- Fix reorder freeze in chats list. +``` + +Use a HEREDOC to pass the message to `git commit -a`. + +### 11. Done + +Run `git log -1` to show the resulting commit and confirm success. diff --git a/.claude/commands/task.md b/.claude/commands/task.md new file mode 100644 index 00000000000000..31988405bb91ed --- /dev/null +++ b/.claude/commands/task.md @@ -0,0 +1,506 @@ +--- +description: Implement a feature or fix using multi-agent workflow with fresh context at each phase +allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task, AskUserQuestion, TodoWrite +--- + +# Task - Multi-Agent Implementation Workflow + +You orchestrate a multi-phase implementation workflow that uses fresh agent spawns to work within context window limits on a large codebase. + +**Arguments:** `$ARGUMENTS` = "$ARGUMENTS" + +If `$ARGUMENTS` is provided, it's the task description. If empty, ask the user what they want implemented. + +## Overview + +The workflow is organized around **projects**. Each project lives in `.ai/<project-name>/` and can contain multiple sequential **tasks** (labeled `a`, `b`, `c`, ... `z`). + +Project structure: +``` +.ai/<project-name>/ + about.md # Single source of truth for the entire project + a/ # First task + context.md # Gathered codebase context for this task + plan.md # Implementation plan + review1.md # Code review documents (up to 3) + review2.md + review3.md + b/ # Follow-up task + context.md + plan.md + review1.md + c/ # Another follow-up task + ... +``` + +- `about.md` is the project-level blueprint — a single comprehensive document describing what this project does and how it works, written as if everything is already fully implemented. It contains no temporal state ("current state", "pending changes", "not yet implemented"). It is **rewritten** (not appended to) each time a new task starts, incorporating the new task's changes as if they were always part of the design. +- Each task folder (`a/`, `b/`, ...) contains self-contained files for that task. The task's `context.md` carries all task-specific information: what specifically needs to change, the delta from the current codebase, gathered file references and code patterns. Planning, implementation, and review agents only read the current task's folder. + +## Phase 0: Setup + +**Record the current time now** (using `Get-Date` in PowerShell or equivalent) and store it as `$START_TIME`. You will use this at the end to display total elapsed time. + +⚠️ **CRITICAL: Follow-up detection MUST happen FIRST, before anything else.** + +### Step 0a: Follow-up detection (MANDATORY — do this BEFORE understanding the task) + +Extract the first word/token from `$ARGUMENTS` (everything before the first space or newline). Call it `FIRST_TOKEN`. + +Then run these TWO commands using the Bash tool, IN PARALLEL, right now: +1. `ls .ai/` — to see all existing project names +2. `ls .ai/<FIRST_TOKEN>/about.md` — to check if this specific project exists + +**Evaluate the results:** +- If command 2 **succeeds** (the file exists): this is a **follow-up task**. The project name is `FIRST_TOKEN`. The task description is everything in `$ARGUMENTS` AFTER `FIRST_TOKEN` (strip leading whitespace). +- If command 2 **fails** (file not found): this is a **new project**. The full `$ARGUMENTS` is the task description. + +**Do NOT proceed to step 0b until you have run these commands and determined follow-up vs new.** + +### Step 0b: Project setup + +**For new projects:** +- Using the list from command 1, pick a unique short name (1-2 lowercase words, hyphen-separated) that doesn't collide with existing projects. +- Create `.ai/<project-name>/` and `.ai/<project-name>/a/`. +- Set current task letter = `a`. + +**For follow-up tasks:** +- Scan `.ai/<project-name>/` for existing task folders (`a/`, `b/`, ...). Find the latest one (highest letter). +- The previous task letter = that highest letter. +- The new task letter = next letter in sequence. +- Create `.ai/<project-name>/<new-letter>/`. + +Then proceed to Phase 1 (Context Gathering) in both cases. Follow-up tasks do NOT skip context gathering — they go through a modified version of it. + +## Phase 1: Context Gathering + +### For New Projects (task letter = `a`) + +Spawn an agent (Task tool, subagent_type=`general-purpose`) with this prompt structure: + +``` +You are a context-gathering agent for a large C++ codebase (Telegram Desktop). + +TASK: <paste the user's task description here> + +YOUR JOB: Read AGENTS.md, inspect the codebase, find ALL files and code relevant to this task, and write two documents. + +Steps: +1. Read AGENTS.md for project conventions and build instructions. +2. Search the codebase for files, classes, functions, and patterns related to the task. +3. Read all potentially relevant files. Be thorough - read more rather than less. +4. For each relevant file, note: + - File path + - Relevant line ranges + - What the code does and how it relates to the task + - Key data structures, function signatures, patterns used +5. Look for similar existing features that could serve as a reference implementation. +6. Check api.tl if the task involves Telegram API. +7. Check .style files if the task involves UI. +8. Check lang.strings if the task involves user-visible text. + +Write TWO files: + +### File 1: .ai/<project-name>/about.md + +NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. No planning, implementation, or review agent will ever read it. Only the context-gathering agent of the next follow-up task reads about.md (together with the latest context.md) to produce a fresh context.md for that next task. + +Write it as if the project is already fully implemented and working. It should contain: +- **Project**: What this project does (feature description, goals, scope) +- **Architecture**: High-level architectural decisions, which modules are involved, how they interact +- **Key Design Decisions**: Important choices made about the approach +- **Relevant Codebase Areas**: Which parts of the codebase this project touches, key types and APIs involved + +Do NOT include temporal state like "Current State", "Pending Changes", "Not yet implemented", "TODO", or any other framing that distinguishes between "done" and "not done". Describe the project as a complete, coherent whole — as if everything is already working. This is a project overview, not a status tracker. Task-specific work belongs exclusively in context.md. + +### File 2: .ai/<project-name>/a/context.md + +This is the task-specific implementation context. This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. It should contain: +- **Task Description**: The full task restated clearly +- **Relevant Files**: Every file path with line ranges and descriptions of what's there +- **Key Code Patterns**: How similar things are done in the codebase (with code snippets) +- **Data Structures**: Relevant types, structs, classes +- **API Methods**: Any TL schema methods involved (copied from api.tl) +- **UI Styles**: Any relevant style definitions +- **Localization**: Any relevant string keys +- **Build Info**: Build command and any special notes +- **Reference Implementations**: Similar features that can serve as templates + +Be extremely thorough. Another agent with NO prior context will read this file and must be able to understand everything needed to implement the task. +``` + +After this agent completes, read both `about.md` and `a/context.md` to verify they were written properly. + +### For Follow-up Tasks (task letter = `b`, `c`, ...) + +Spawn an agent (Task tool, subagent_type=`general-purpose`) with this prompt structure: + +``` +You are a context-gathering agent for a follow-up task on an existing project in a large C++ codebase (Telegram Desktop). + +NEW TASK: <paste the follow-up task description here> + +YOUR JOB: Read the existing project state, gather any additional context needed, and produce fresh documents for the new task. + +Steps: +1. Read AGENTS.md for project conventions and build instructions. +2. Read .ai/<project-name>/about.md — this is the project-level blueprint describing everything done so far. +3. Read .ai/<project-name>/<previous-letter>/context.md — this is the previous task's gathered context. +4. Understand what has already been implemented by reading the actual source files referenced in about.md and the previous context. +5. Based on the NEW TASK description, search the codebase for any ADDITIONAL files, classes, functions, and patterns that are relevant to the new task but not already covered. +6. Read all newly relevant files thoroughly. + +Write TWO files: + +### File 1: .ai/<project-name>/about.md (REWRITE) + +NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. No planning, implementation, or review agent will ever read it. You are rewriting it now so that the next follow-up has an accurate project overview to start from. + +REWRITE this file (not append). The new about.md must be a single coherent document that describes the project as if everything — including this new task's changes — is already fully implemented and working. + +It should incorporate: +- Everything from the old about.md that is still accurate and relevant +- The new task's functionality described as part of the project (not as "changes to make") +- Any changed design decisions or architectural updates from the new task requirements + +It should NOT contain: +- Any temporal state: "Current State", "Pending Changes", "TODO", "Not yet implemented" +- History of how requirements changed between tasks +- References to "the old approach" vs "the new approach" +- Task-by-task changelog or timeline +- Any distinction between "what was done before" and "what this task adds" +- Information that contradicts the new task requirements (if the new task changes direction, the about.md should reflect the NEW direction as if it was always the plan) + +Think of about.md as "the complete description of what this project does and how it works." Someone reading it should understand the full project as a finished product, without knowing it went through multiple tasks. + +### File 2: .ai/<project-name>/<new-letter>/context.md + +This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. about.md will NOT be available to them. + +It should contain: +- **Task Description**: The new task restated clearly, with enough project background (from about.md and previous context.md) that an implementation agent can understand it without reading any other .ai/ files +- **Relevant Files**: Every file path with line ranges relevant to THIS task (including files modified by previous tasks and any newly relevant files) +- **Key Code Patterns**: How similar things are done in the codebase +- **Data Structures**: Relevant types, structs, classes +- **API Methods**: Any TL schema methods involved +- **UI Styles**: Any relevant style definitions +- **Localization**: Any relevant string keys +- **Build Info**: Build command and any special notes +- **Reference Implementations**: Similar features that can serve as templates + +Be extremely thorough. Another agent with NO prior context will read ONLY this file and must be able to understand everything needed to implement the new task. Do NOT assume the reader has seen about.md or any previous task files. The context.md is the single source of truth for all downstream agents — it must include all relevant project background, not just the delta. +``` + +After this agent completes, read both `about.md` and `<new-letter>/context.md` to verify they were written properly. + +## Phase 2: Planning + +Spawn an agent (Task tool, subagent_type=`general-purpose`) with this prompt structure: + +``` +You are a planning agent. You must create a detailed implementation plan. + +Read these files: +- .ai/<project-name>/<letter>/context.md - Contains all gathered context for this task +- Then read the specific source files referenced in context.md to understand the code deeply. + +Think carefully about the implementation approach. + +Create a detailed plan in: .ai/<project-name>/<letter>/plan.md + +The plan.md should contain: + +## Task +<one-line summary> + +## Approach +<high-level description of the implementation approach> + +## Files to Modify +<list of files that will be created or modified> + +## Files to Create +<list of new files, if any> + +## Implementation Steps + +Each step must be specific enough that an agent can execute it without ambiguity: +- Exact file paths +- Exact function names +- What code to add/modify/remove +- Where exactly in the file (after which function, in which class, etc.) + +Number every step. Group steps into phases if there are more than ~8 steps. + +### Phase 1: <name> +1. <specific step> +2. <specific step> +... + +### Phase 2: <name> (if needed) +... + +## Build Verification +- Build command to run +- Expected outcome + +## Status +- [ ] Phase 1: <name> +- [ ] Phase 2: <name> (if applicable) +- [ ] Build verification +- [ ] Code review +``` + +After this agent completes, read `plan.md` to verify it was written properly. + +## Phase 3: Plan Assessment + +Spawn an agent (Task tool, subagent_type=`general-purpose`) with this prompt structure: + +``` +You are a plan assessment agent. Review and refine an implementation plan. + +Read these files: +- .ai/<project-name>/<letter>/context.md +- .ai/<project-name>/<letter>/plan.md +- Then read the actual source files referenced to verify the plan makes sense. + +Carefully assess the plan: + +1. **Correctness**: Are the file paths and line references accurate? Does the plan reference real functions and types? +2. **Completeness**: Are there missing steps? Edge cases not handled? +3. **Code quality**: Will the plan minimize code duplication? Does it follow existing codebase patterns from AGENTS.md? +4. **Design**: Could the approach be improved? Are there better patterns already used in the codebase? +5. **Phase sizing**: Each phase should be implementable by a single agent in one session. If a phase has more than ~8-10 substantive code changes, split it further. + +Update plan.md with your refinements. Keep the same structure but: +- Fix any inaccuracies +- Add missing steps +- Improve the approach if you found better patterns +- Ensure phases are properly sized for single-agent execution +- Add a line at the top of the Status section: `Phases: <N>` indicating how many implementation phases there are +- Add `Assessed: yes` at the bottom of the file + +If the plan is small enough for a single agent (roughly <=8 steps), mark it as a single phase. +``` + +After this agent completes, read `plan.md` to verify it was assessed. + +## Phase 4: Implementation + +Now read `plan.md` yourself to understand the phases. + +For each phase in the plan that is not yet marked as done, spawn an implementation agent (Task tool, subagent_type=`general-purpose`): + +``` +You are an implementation agent working on phase <N> of an implementation plan. + +Read these files first: +- .ai/<project-name>/<letter>/context.md - Full codebase context +- .ai/<project-name>/<letter>/plan.md - Implementation plan + +Then read the source files you'll be modifying. + +YOUR TASK: Implement ONLY Phase <N> from the plan: +<paste the specific phase steps here> + +Rules: +- Follow the plan precisely +- Follow AGENTS.md coding conventions (no comments except complex algorithms, use auto, empty line before closing brace, etc.) +- Do NOT modify .ai/ files except to update the Status section in plan.md +- When done, update plan.md Status section: change `- [ ] Phase <N>: ...` to `- [x] Phase <N>: ...` +- Do NOT work on other phases + +When finished, report what you did and any issues encountered. +``` + +After each implementation agent returns: +1. Read `plan.md` to check the status was updated. +2. If more phases remain, spawn the next implementation agent. +3. If all phases are done, proceed to build verification. + +## Phase 5: Build Verification + +Only run this phase if the task involved modifying project source code (not just docs or config). + +Spawn a build verification agent (Task tool, subagent_type=`general-purpose`): + +``` +You are a build verification agent. + +Read these files: +- .ai/<project-name>/<letter>/context.md +- .ai/<project-name>/<letter>/plan.md + +The implementation is complete. Your job is to build the project and fix any build errors. + +Steps: +1. Run (from repository root): cmake --build ./out --config Debug --target Telegram +2. If the build succeeds, update plan.md: change `- [ ] Build verification` to `- [x] Build verification` +3. If the build fails: + a. Read the error messages carefully + b. Read the relevant source files + c. Fix the errors in accordance with the plan and AGENTS.md conventions + d. Rebuild and repeat until the build passes + e. Update plan.md status when done + +Rules: +- Only fix build errors, do not refactor or improve code +- Follow AGENTS.md conventions +- If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry + +When finished, report the build result. +``` + +After the build agent returns, read `plan.md` to confirm the final status. Then proceed to Phase 6. + +## Phase 6: Code Review Loop + +After build verification passes, run up to 3 review-fix iterations to improve code quality. Set iteration counter `R = 1`. + +### Review Loop + +``` +LOOP: + 1. Spawn review agent (Step 6a) with iteration R + 2. Read review<R>.md verdict: + - "APPROVED" → go to FINISH + - Has improvement suggestions → spawn fix agent (Step 6b) + 3. After fix agent completes and build passes: + R = R + 1 + If R > 3 → go to FINISH (stop iterating, accept current state) + Otherwise → go to step 1 + +FINISH: + - Update plan.md: change `- [ ] Code review` to `- [x] Code review` + - Proceed to Completion +``` + +### Step 6a: Code Review Agent + +Spawn an agent (Task tool, subagent_type=`general-purpose`): + +``` +You are a code review agent for Telegram Desktop (C++ / Qt). + +Read these files: +- .ai/<project-name>/<letter>/context.md - Codebase context +- .ai/<project-name>/<letter>/plan.md - Implementation plan +- REVIEW.md - Style and formatting rules to enforce +<if R > 1, also read:> +- .ai/<project-name>/<letter>/review<R-1>.md - Previous review (to see what was already addressed) + +Then run `git diff` to see all uncommitted changes made by the implementation. Implementation agents do not commit, so `git diff` shows exactly the current feature's changes. + +Then read the modified source files in full to understand changes in context. + +Perform a thorough code review. + +REVIEW CRITERIA (in order of importance): + +1. **Correctness and safety**: Obvious logic errors, missing null checks at API boundaries, potential crashes, use-after-free, dangling references, race conditions. This is the highest priority — bugs and safety issues must be caught first. Do NOT nitpick internal code that relies on framework guarantees. + +2. **Dead code**: Any code added or left behind that is never called or used, within the scope of the changes. Unused variables, unreachable branches, leftover scaffolding. + +3. **Redundant changes**: Changes in the diff that have no functional effect — moving declarations or code blocks to a different location without reason, reformatting untouched code, reordering includes or fields with no purpose. Every line in the diff should serve the feature. If a file appears in `git diff` but contains only no-op rearrangements, flag it for revert. + +4. **Code duplication**: Unnecessary repetition of logic that should be shared. Look for near-identical blocks that differ only in minor details and could be unified. + +5. **Wrong placement**: Code added to a module where it doesn't logically belong. If another existing module is a clearly better fit for the new code, flag it. Consider the existing module boundaries and responsibilities visible in context.md. + +6. **Function decomposition**: For longer functions (roughly 50+ lines), consider whether a logical sub-task could be cleanly extracted into a separate function. This is NOT a hard rule — a 100-line function that flows naturally and isn't easily divisible is perfectly fine. But sometimes even a 20-line function contains a clear isolated subtask that reads better as two 10-line functions. The key is to think about it each time: does extracting improve readability and reduce cognitive load, or does it just scatter logic across call sites for no real benefit? Only suggest extraction when there's a genuinely self-contained piece of logic with a clear name and purpose. + +7. **Module structure**: Only in exceptional cases — if a large amount of newly added code (hundreds of lines) is logically distinct from the rest of its host module, suggest extracting it into a new module. But do NOT suggest new modules lightly: every module adds significant build overhead due to PCH and heavy template usage. Only suggest this when the new code is both large enough AND logically separated enough to justify it. At the same time, don't let modules grow into multi-thousand-line monoliths either. + +8. **Style compliance**: Verify adherence to REVIEW.md rules (empty line before closing brace, operators at start of continuation lines, minimize type checks with direct cast instead of is+as, no if-with-initializer when simpler alternatives exist) and AGENTS.md conventions (no unnecessary comments, `auto` usage, no hardcoded sizes — must use .style definitions), etc. + +IMPORTANT GUIDELINES: +- Review ONLY the changes made, not pre-existing code in the repository. +- Be pragmatic. Don't suggest changes for the sake of it. Each suggestion should have a clear, concrete benefit. +- Don't suggest adding comments, docstrings, or type annotations — the codebase style avoids these. +- Don't suggest error handling for impossible scenarios or over-engineering. + +Write your review to: .ai/<project-name>/<letter>/review<R>.md + +The review document should contain: + +## Code Review - Iteration <R> + +## Summary +<1-2 sentence overall assessment> + +## Verdict: <APPROVED or NEEDS_CHANGES> + +<If APPROVED, stop here. Everything looks good.> + +<If NEEDS_CHANGES, continue with:> + +## Changes Required + +### <Issue 1 title> +- **Category**: <dead code | duplication | wrong placement | function decomposition | module structure | style | correctness> +- **File(s)**: <file paths> +- **Problem**: <clear description of what's wrong> +- **Fix**: <specific description of what to change> + +### <Issue 2 title> +... + +Keep the list focused. Only include issues that genuinely improve the code. If you find yourself listing more than ~5-6 issues, prioritize the most impactful ones. + +When finished, report your verdict clearly as: APPROVED or NEEDS_CHANGES. +``` + +After the review agent returns, read `review<R>.md`. If the verdict is APPROVED, proceed to Completion. If NEEDS_CHANGES, spawn the fix agent. + +### Step 6b: Review Fix Agent + +Spawn an agent (Task tool, subagent_type=`general-purpose`): + +``` +You are a review fix agent. You implement improvements identified during code review. + +Read these files: +- .ai/<project-name>/<letter>/context.md - Codebase context +- .ai/<project-name>/<letter>/plan.md - Original implementation plan +- .ai/<project-name>/<letter>/review<R>.md - Code review with required changes + +Then read the source files mentioned in the review. + +YOUR TASK: Implement ALL changes listed in review<R>.md. + +For each issue in the review: +1. Read the relevant source file(s). +2. Make the specified change. +3. Verify the change makes sense in context. + +After all changes are made: +1. Build (from repository root): cmake --build ./out --config Debug --target Telegram +2. If the build fails, fix build errors and rebuild until it passes. +3. If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry. + +Rules: +- Implement exactly the changes from the review, nothing more. +- Follow AGENTS.md coding conventions. +- Do NOT modify .ai/ files. + +When finished, report what changes were made. +``` + +After the fix agent returns, increment R and loop back to Step 6a (unless R > 3, in which case proceed to Completion). + +## Completion + +When all phases including build verification and code review are done: +1. Read the final `plan.md` and report the summary to the user. +2. Show which files were modified/created. +3. Note any issues encountered during implementation. +4. Summarize code review iterations: how many rounds, what was found and fixed, or if it was approved on first pass. +5. Calculate and display the total elapsed time since `$START_TIME` (format as `Xh Ym Zs`, omitting zero components — e.g. `12m 34s` or `1h 5m 12s`). +6. Remind the user of the project name so they can use `/task <project-name> <follow-up description>` for follow-up changes. + +## Error Handling + +- If any agent fails or gets stuck, report the issue to the user and ask how to proceed. +- If context.md or plan.md is not written properly by an agent, re-spawn that agent with more specific instructions. +- If build errors persist after the build agent's attempts, report the remaining errors to the user. +- If a review fix agent introduces new build errors that it cannot resolve, report to the user. diff --git a/.claude/grab_clipboard.ps1 b/.claude/grab_clipboard.ps1 new file mode 100644 index 00000000000000..bece778c603882 --- /dev/null +++ b/.claude/grab_clipboard.ps1 @@ -0,0 +1,11 @@ +param([string]$outPath) +Add-Type -AssemblyName System.Windows.Forms +$img = [System.Windows.Forms.Clipboard]::GetImage() +if ($img) { + $img.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png) + Write-Host "Saved to $outPath" + exit 0 +} else { + Write-Host "No image on clipboard" + exit 1 +} diff --git a/.claude/grab_clipboard.sh b/.claude/grab_clipboard.sh new file mode 100755 index 00000000000000..80c0c6d05c99a8 --- /dev/null +++ b/.claude/grab_clipboard.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Grab clipboard image on macOS and save as PNG. +outPath="$1" +if [ -z "$outPath" ]; then + echo "Usage: grab_clipboard.sh <output.png>" + exit 1 +fi + +osascript -e ' +set theFile to POSIX file "'"$outPath"'" +try + set theImage to the clipboard as «class PNGf» +on error + return "no image" +end try +set fh to open for access theFile with write permission +write theImage to fh +close access fh +return "ok" +' 2>/dev/null | grep -q "ok" + +if [ $? -eq 0 ]; then + echo "Saved to $outPath" + exit 0 +else + echo "No image on clipboard" + exit 1 +fi diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 00000000000000..db6b53b76545ab --- /dev/null +++ b/.cursorignore @@ -0,0 +1,2 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +Telegram/ThirdParty/ diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000000000..16a1bcc1192bed --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,30 @@ +{ + "name": "CentOS", + "image": "tdesktop:centos_env", + "customizations": { + "vscode": { + "settings": { + "C_Cpp.intelliSenseEngine": "disabled", + "cmake.generator": "Ninja Multi-Config", + "cmake.buildDirectory": "${workspaceFolder}/out", + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json" + }, + "extensions": [ + "ms-vscode.cpptools-extension-pack", + "llvm-vs-code-extensions.vscode-clangd", + "TheQtCompany.qt", + "ms-python.python", + "ms-azuretools.vscode-docker", + "eamodio.gitlens" + ] + } + }, + "capAdd": [ + "SYS_PTRACE" + ], + "securityOpt": [ + "seccomp=unconfined" + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/usr/src/tdesktop,type=bind,consistency=cached", + "workspaceFolder": "/usr/src/tdesktop" +} diff --git a/.github/scripts/generate_changelog.py b/.github/scripts/generate_changelog.py new file mode 100644 index 00000000000000..6cdd12652d31b2 --- /dev/null +++ b/.github/scripts/generate_changelog.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +"""Convert changelog.txt to a static HTML page for GitHub Pages.""" + +import re +import shutil +import sys +import html +from pathlib import Path + +MONTHS = [ + "", "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +] + +VERSION_RE = re.compile( + r"^(\d+\.\d+(?:\.\d+)?)\s*" # version number + r"(?:(alpha|beta|dev|stable)\s*)?" # optional tag + r"\((\d{2})\.(\d{2})\.(\d{2,4})\)$" # date (DD.MM.YY or DD.MM.YYYY) +) + + +def parse_date(day: str, month: str, year: str) -> tuple[str, str, str]: + """Return (sort_key, raw_display, full_display) from DD, MM, YY strings.""" + y = int(year) + if y < 100: + y += 2000 + m = int(month) + d = int(day) + sort_key = f"{y:04d}-{m:02d}-{d:02d}" + raw_display = f"{day}.{month}.{year}" + full_display = f"{d} {MONTHS[m]} {y}" + return sort_key, raw_display, full_display + + +def parse_changelog(text: str) -> list[dict]: + entries = [] + current = None + + for raw_line in text.splitlines(): + line = raw_line.rstrip() + m = VERSION_RE.match(line) + if m: + if current: + entries.append(current) + version, tag, day, month, year = m.groups() + sort_key, raw_date, full_date = parse_date(day, month, year) + current = { + "version": version, + "tag": tag or "", + "date": raw_date, + "full_date": full_date, + "sort_key": sort_key, + "lines": [], + } + elif current is not None: + # Skip blank lines at the start + if not line and not current["lines"]: + continue + # Skip stray artifact lines + if line.strip() in ("),", "),"): + continue + current["lines"].append(line) + + if current: + entries.append(current) + + # Trim trailing blank lines from each entry + for entry in entries: + while entry["lines"] and not entry["lines"][-1]: + entry["lines"].pop() + + return entries + + +def render_entry(entry: dict) -> str: + version = html.escape(entry["version"]) + tag = entry["tag"] + date = html.escape(entry["date"]) + anchor = f"v{version}" + + tag_html = "" + if tag and tag not in ("stable",): + tag_html = f' {html.escape(tag)}' + + parts = [ + f'<article class="entry" id="{anchor}">', + f' <h2><a class="anchor" href="#{anchor}"></a>' + f'{version}{tag_html}' + f' <time>{date}</time></h2>', + ] + + in_list = False + for line in entry["lines"]: + stripped = line.lstrip() + if stripped.startswith("- ") or stripped.startswith("\u2014 "): + # Bullet point (- or em dash) + if not in_list: + parts.append(" <ul>") + in_list = True + bullet_text = stripped[2:] + parts.append(f" <li>{html.escape(bullet_text)}</li>") + else: + if in_list: + parts.append(" </ul>") + in_list = False + if stripped: + parts.append(f" <p>{html.escape(stripped)}</p>") + + if in_list: + parts.append(" </ul>") + + parts.append("</article>") + return "\n".join(parts) + + +def build_html(entries: list[dict]) -> str: + count = len(entries) + first_date = entries[-1]["full_date"] if entries else "" + latest_version = entries[0]["version"] if entries else "" + + entries_html = "\n\n".join(render_entry(e) for e in entries) + + return f"""<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Version history + + + + + + +
+

Version history

+

{count} releases since {first_date} · latest: {latest_version}

+
+ +
+ + +
+{entries_html} +
+
+ + + + + + +""" + + +def main(): + repo = Path(__file__).resolve().parent.parent.parent + src = repo / "changelog.txt" + if len(sys.argv) > 1: + src = Path(sys.argv[1]) + + out = repo / "docs" / "changelog" / "index.html" + if len(sys.argv) > 2: + out = Path(sys.argv[2]) + + text = src.read_text(encoding="utf-8") + entries = parse_changelog(text) + html_content = build_html(entries) + + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(html_content, encoding="utf-8") + + # Copy favicon files from resources + icons_src = repo / "Telegram" / "Resources" / "art" + for name in ("icon16.png", "icon32.png"): + icon = icons_src / name + if icon.exists(): + shutil.copy2(icon, out.parent / name) + + print(f"Generated {out} ({len(entries)} entries, {out.stat().st_size:,} bytes)") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/cant-reproduce.yml b/.github/workflows/cant-reproduce.yml index 5c4edcba0fb0c5..8e20e07349b5b4 100644 --- a/.github/workflows/cant-reproduce.yml +++ b/.github/workflows/cant-reproduce.yml @@ -6,7 +6,7 @@ on: jobs: cant-reproduce: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: lee-dohm/no-response@v0.5.0 with: diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000000..418e29fdc1c3b6 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,45 @@ +name: Changelog + +on: + push: + branches: [dev] + paths: [changelog.txt] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Generate HTML + run: python .github/scripts/generate_changelog.py + + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: docs + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/copyright_year_updater.yml b/.github/workflows/copyright_year_updater.yml index 7cb129e36eae66..7ddb3d0403a778 100644 --- a/.github/workflows/copyright_year_updater.yml +++ b/.github/workflows/copyright_year_updater.yml @@ -9,7 +9,7 @@ on: jobs: Copyright-year: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: desktop-app/action_code_updater@master with: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ac40f3a330ec25..9582334f945ba4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,22 +5,19 @@ on: paths: - '.github/workflows/docker.yml' - 'Telegram/build/docker/centos_env/**' - pull_request: - paths: - - '.github/workflows/docker.yml' - - 'Telegram/build/docker/centos_env/**' jobs: docker: name: Ubuntu runs-on: ubuntu-latest + if: github.ref_name == github.event.repository.default_branch env: IMAGE_TAG: ghcr.io/${{ github.repository }}/centos_env:latest steps: - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: recursive @@ -32,6 +29,8 @@ jobs: - name: Free up some disk space. uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be + with: + tool-cache: true - name: Docker image build. run: | @@ -40,5 +39,4 @@ jobs: DEBUG= LTO= poetry run gen_dockerfile | DOCKER_BUILDKIT=1 docker build -t $IMAGE_TAG - - name: Push the Docker image. - if: ${{ github.ref_name == github.event.repository.default_branch }} run: docker push $IMAGE_TAG diff --git a/.github/workflows/issue_closer.yml b/.github/workflows/issue_closer.yml index d3784944f6d7a5..35540516559071 100644 --- a/.github/workflows/issue_closer.yml +++ b/.github/workflows/issue_closer.yml @@ -6,7 +6,7 @@ on: jobs: comment: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - name: Process an issue. uses: desktop-app/action_issue_closer@master diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8eb20a2e55c43f..f3c4cac54a90dc 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -12,6 +12,7 @@ on: - '!.github/workflows/linux.yml' - 'snap/**' - 'Telegram/build/**' + - '!Telegram/build/docker/centos_env/**' - 'Telegram/Resources/uwp/**' - 'Telegram/Resources/winrc/**' - 'Telegram/SourceFiles/platform/win/**' @@ -30,6 +31,7 @@ on: - '!.github/workflows/linux.yml' - 'snap/**' - 'Telegram/build/**' + - '!Telegram/build/docker/centos_env/**' - 'Telegram/Resources/uwp/**' - 'Telegram/Resources/winrc/**' - 'Telegram/SourceFiles/platform/win/**' @@ -42,7 +44,7 @@ jobs: linux: name: Rocky Linux 8 - runs-on: ubuntu-latest + runs-on: ${{ (github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly') && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }} strategy: matrix: @@ -52,54 +54,121 @@ jobs: env: UPLOAD_ARTIFACT: "true" + ONLY_CACHE: "false" + IMAGE_TAG: tdesktop:centos_env steps: - - name: Get repository name. - run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: recursive - path: ${{ env.REPO_NAME }} - name: First set up. run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env - docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env + echo "WEEK=$(date -u +%G%V)" >> $GITHUB_ENV + sudo apt update + curl -sSL https://install.python-poetry.org | python3 - + cd Telegram/build/docker/centos_env + poetry install + DOCKERFILE=$(DEBUG= LTO= poetry run gen_dockerfile) + echo "$DOCKERFILE" > Dockerfile + rm -rf __pycache__ - - name: Telegram Desktop build. + - name: Free up some disk space. + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be + with: + tool-cache: true + + - name: Set up Docker Buildx. + id: setup-buildx + uses: docker/setup-buildx-action@v4 + + - name: Libraries cache. + id: cache-libs + uses: actions/cache@v6 + with: + path: | + ${{ runner.temp }}/.buildx-cache + ${{ runner.temp }}/.mount-cache + key: ${{ runner.OS }}-libs-${{ hashFiles('Telegram/build/docker/centos_env/**') }} + restore-keys: ${{ runner.OS }}-libs- + + - name: Restore Docker cache mounts. + uses: reproducible-containers/buildkit-cache-dance@5422eac04292c961a382e0f584ea0f03ad9da723 + with: + builder: ${{ steps.setup-buildx.outputs.name }} + cache-dir: ${{ runner.temp }}/.mount-cache + dockerfile: Telegram/build/docker/centos_env/Dockerfile + skip-extraction: ${{ steps.cache-libs.outputs.cache-hit }} + + - name: Libraries. + uses: docker/build-push-action@v7 + with: + context: Telegram/build/docker/centos_env + load: ${{ env.ONLY_CACHE == 'false' }} + tags: ${{ env.IMAGE_TAG }} + cache-from: type=local,src=${{ runner.temp }}/.buildx-cache + cache-to: type=local,dest=${{ runner.temp }}/.buildx-cache-new,mode=max + + - name: Move cache. run: | - cd $REPO_NAME + rm -rf ${{ runner.temp }}/.buildx-cache + mv ${{ runner.temp }}/.buildx-cache{-new,} + - name: Restore Telegram Desktop cache. + id: cache-tdesktop + if: env.ONLY_CACHE == 'false' + uses: actions/cache/restore@v6 + with: + path: ${{ runner.temp }}/.tdesktop-cache + key: ${{ runner.OS }}-tdesktop-${{ matrix.defines || 'null' }}-${{ env.WEEK }} + restore-keys: ${{ runner.OS }}-tdesktop-${{ matrix.defines || 'null' }}- + + - name: Telegram Desktop build. + if: env.ONLY_CACHE == 'false' + run: | DEFINE="" if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV + echo "ARTIFACT_NAME=Telegram ${{ matrix.defines }}" >> $GITHUB_ENV else echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi + mkdir -p ${{ runner.temp }}/.tdesktop-cache + docker run --rm \ + -u $(id -u) \ -v $PWD:/usr/src/tdesktop \ + -v ${{ runner.temp }}/.tdesktop-cache:/var/cache/ccache \ -e CONFIG=Debug \ - tdesktop:centos_env \ - /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ - -D CMAKE_C_FLAGS_DEBUG="" \ - -D CMAKE_CXX_FLAGS_DEBUG="" \ - -D CMAKE_C_FLAGS="-Werror" \ - -D CMAKE_CXX_FLAGS="-Werror" \ - -D CMAKE_EXE_LINKER_FLAGS="-s" \ + -e CCACHE_SLOPPINESS=pch_defines,time_macros \ + $IMAGE_TAG \ + env -u CCACHE_DISABLE /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ + -D CMAKE_CONFIGURATION_TYPES=Debug \ + -D CMAKE_C_FLAGS_DEBUG="-O0 -fpch-preprocess -fuse-ld=lld" \ + -D CMAKE_CXX_FLAGS_DEBUG="-O0 -fpch-preprocess -fuse-ld=lld" \ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE + - name: Save Telegram Desktop cache. + if: > + github.ref_name == github.event.repository.default_branch + && steps.cache-tdesktop.outputs.cache-hit != 'true' + && env.ONLY_CACHE == 'false' + uses: actions/cache/save@v6 + with: + path: ${{ runner.temp }}/.tdesktop-cache + key: ${{ steps.cache-tdesktop.outputs.cache-primary-key }} + - name: Check. + if: env.ONLY_CACHE == 'false' run: | - filePath="$REPO_NAME/out/Debug/Telegram" + filePath="out/Debug/Telegram" if test -f "$filePath"; then echo "Build successfully done! :)" @@ -113,12 +182,12 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' run: | - cd $REPO_NAME/out/Debug - sudo mkdir artifact - sudo mv {Telegram,Updater} artifact/ - - uses: actions/upload-artifact@v4 + cd out/Debug + mkdir artifact + mv {Telegram,Updater} artifact/ + - uses: actions/upload-artifact@v7 if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. with: name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.REPO_NAME }}/out/Debug/artifact/ + path: out/Debug/artifact/ diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index cf3c4698962ba3..7904e79591f79a 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -6,9 +6,9 @@ on: jobs: lock: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v6 with: github-token: ${{ github.token }} issue-inactive-days: 45 diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index e5f5e56206c181..57cfccc2194b56 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -40,7 +40,7 @@ jobs: macos: name: MacOS - runs-on: macos-13 + runs-on: ${{ (github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly') && 'depot-macos-latest' || 'macos-latest' }} strategy: matrix: @@ -56,7 +56,7 @@ jobs: run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: recursive path: ${{ env.REPO_NAME }} @@ -67,26 +67,22 @@ jobs: brew update brew upgrade || true - brew install automake meson nasm ninja pkg-config + brew install automake libtool meson nasm ninja pkg-config # Disable spotlight. sudo mdutil -a -i off sudo xcode-select -s /Applications/Xcode.app/Contents/Developer - - name: ThirdParty cache. - id: cache-third-party - uses: actions/cache@v4 - with: - path: ThirdParty - key: ${{ runner.OS }}-third-party-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }} - restore-keys: ${{ runner.OS }}-third-party- + sudo sed -i '' '/CMAKE_${lang}_FLAGS_DEBUG_INIT/s/ -g//' /opt/homebrew/share/cmake/Modules/Compiler/GNU.cmake - name: Libraries cache. id: cache-libs - uses: actions/cache@v4 + uses: actions/cache@v6 with: - path: Libraries + path: | + Libraries + ThirdParty key: ${{ runner.OS }}-libs-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }} restore-keys: ${{ runner.OS }}-libs- @@ -95,9 +91,7 @@ jobs: ./$REPO_NAME/Telegram/build/prepare/mac.sh skip-release silent - name: Free up some disk space. - run: | - cd Libraries - find . -iname "*.dir" -exec rm -rf {} || true \; + run: find Libraries '(' '(' ! '(' -name '*.a' -o -name '*.h' -o -name '*.hpp' -o -name '*.inc' -o -name '*.cmake' -o -path '*/include/*' -o -path '*/objects-*' -o -path '*/cache_keys/*' -o -path '*/patches/*' -o -perm +111 ')' -type f ')' -o -empty ')' -delete - name: Telegram Desktop build. if: env.ONLY_CACHE == 'false' @@ -108,24 +102,21 @@ jobs: if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV + echo "ARTIFACT_NAME=Telegram ${{ matrix.defines }}" >> $GITHUB_ENV else echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi ./configure.sh \ - -D CMAKE_C_FLAGS="-Werror" \ - -D CMAKE_CXX_FLAGS="-Werror" \ + -D CMAKE_CONFIGURATION_TYPES=Debug \ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON \ -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE - cd ../out - - xcoderun='xcodebuild build -project Telegram.xcodeproj -scheme Telegram -destination "platform=macOS,arch=x86_64" -configuration Debug' - bash -c "$xcoderun" || bash -c "$xcoderun" || bash -c "$xcoderun" + cmake --build ../out --config Debug --parallel - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' @@ -134,7 +125,7 @@ jobs: mkdir artifact mv Telegram.app artifact/ mv Updater artifact/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. with: diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml index 7f2775eea1ab2e..1f9532a0ccd6cd 100644 --- a/.github/workflows/mac_packaged.yml +++ b/.github/workflows/mac_packaged.yml @@ -40,7 +40,7 @@ jobs: macos: name: MacOS - runs-on: macos-latest + runs-on: ${{ (github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly') && 'depot-macos-latest' || 'macos-latest' }} strategy: matrix: @@ -49,7 +49,11 @@ jobs: env: GIT: "https://github.com" + CMAKE_GENERATOR: "Ninja" + CMAKE_BUILD_TYPE: "Debug" + CMAKE_BUILD_PARALLEL_LEVEL: "" CMAKE_PREFIX_PATH: "/opt/homebrew/opt/ffmpeg@6:/opt/homebrew/opt/openal-soft" + TDE2E: "51743dfd01dff6179e2d8f7095729caa4e2222e9" UPLOAD_ARTIFACT: "true" ONLY_CACHE: "false" MANUAL_CACHING: "1" @@ -60,20 +64,22 @@ jobs: run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: recursive path: ${{ env.REPO_NAME }} - name: First set up. run: | + libs="ada-url ffmpeg@6 jpeg-xl libavif libheif minizip openal-soft openh264 openssl opus qtbase qtimageformats qtshadertools qtsvg xz" brew update brew upgrade || true - brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz + brew install autoconf automake boost cmake libtool ninja pkg-config python $libs sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + sudo sed -i '' '/CMAKE_${lang}_FLAGS_DEBUG_INIT/s/ -g//' /opt/homebrew/share/cmake/Modules/Compiler/GNU.cmake xcodebuild -version > CACHE_KEY.txt - brew list --versions >> CACHE_KEY.txt + brew list --versions $libs >> CACHE_KEY.txt echo $MANUAL_CACHING >> CACHE_KEY.txt echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt if [ "$AUTO_CACHING" = "1" ]; then @@ -82,27 +88,36 @@ jobs: fi echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV + echo "MACOSX_DEPLOYMENT_TARGET=$(grep 'set(QT_SUPPORTED_MIN_MACOS_VERSION' /opt/homebrew/Cellar/qtbase/*/lib/cmake/Qt6/Qt6ConfigExtras.cmake | sed -E 's/^.*"(.*)"\)$/\1/')" >> $GITHUB_ENV echo "LibrariesPath=`pwd`" >> $GITHUB_ENV - curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master + echo "RNNOISE=`curl -sSL --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/xiph/rnnoise/git/refs/heads/master | jq -r .object.sha`" >> $GITHUB_ENV + echo "WEBRTC=`curl -sSL --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master | jq -r .object.sha`" >> $GITHUB_ENV + - name: RNNoise cache. + id: cache-rnnoise + uses: actions/cache@v6 + with: + path: ${{ env.LibrariesPath }}/local/rnnoise + key: ${{ runner.OS }}-rnnoise-${{ env.RNNOISE }}-${{ env.CACHE_KEY }} - name: RNNoise. + if: steps.cache-rnnoise.outputs.cache-hit != 'true' run: | cd $LibrariesPath - git clone --depth=1 https://gitlab.xiph.org/xiph/rnnoise.git + git clone --depth=1 $GIT/xiph/rnnoise.git cd rnnoise ./autogen.sh - ./configure --disable-examples --disable-doc + ./configure --prefix=$LibrariesPath/local/rnnoise --disable-examples --disable-doc make -j$(sysctl -n hw.logicalcpu) - sudo make install + make install - name: WebRTC cache. id: cache-webrtc - uses: actions/cache@v4 + uses: actions/cache@v6 with: - path: ${{ env.LibrariesPath }}/tg_owt - key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }} + path: ${{ env.LibrariesPath }}/local/tg_owt + key: ${{ runner.OS }}-webrtc-${{ env.WEBRTC }}-${{ env.CACHE_KEY }} - name: WebRTC. if: steps.cache-webrtc.outputs.cache-hit != 'true' run: | @@ -111,56 +126,58 @@ jobs: git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git cd tg_owt - cmake -Bbuild -GNinja . \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_FLAGS_DEBUG="" \ - -DCMAKE_CXX_FLAGS_DEBUG="" + cmake -Bbuild . -DCMAKE_INSTALL_PREFIX=$LibrariesPath/local/tg_owt + cmake --build build + cmake --install build + + - name: TDE2E cache. + id: cache-tde2e + uses: actions/cache@v6 + with: + path: ${{ env.LibrariesPath }}/local/tde2e + key: ${{ runner.OS }}-tde2e-${{ env.TDE2E }}-${{ env.CACHE_KEY }} + - name: TDE2E. + if: steps.cache-tde2e.outputs.cache-hit != 'true' + run: | + cd $LibrariesPath - cmake --build build --parallel + git init tde2e + cd tde2e + git remote add origin $GIT/tdlib/td.git + git fetch --depth=1 origin $TDE2E + git reset --hard FETCH_HEAD + + cmake -Bbuild . -DCMAKE_INSTALL_PREFIX=$LibrariesPath/local/tde2e -DTD_E2E_ONLY=ON + cmake --build build + cmake --install build - name: Telegram Desktop build. if: env.ONLY_CACHE == 'false' - env: - tg_owt_DIR: ${{ env.LibrariesPath }}/tg_owt/build run: | cd $REPO_NAME + export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH$(find $LibrariesPath/local -mindepth 1 -maxdepth 1 -type d -exec printf ':%s' {} +)" DEFINE="" if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV + echo "ARTIFACT_NAME=Telegram ${{ matrix.defines }}" >> $GITHUB_ENV else echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi - cmake -Bbuild -GNinja . \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_FLAGS_DEBUG="" \ - -DCMAKE_CXX_FLAGS_DEBUG="" \ - -DCMAKE_EXE_LINKER_FLAGS="-s" \ - -DCMAKE_FIND_FRAMEWORK=LAST \ - -DTDESKTOP_API_TEST=ON \ - -DDESKTOP_APP_USE_PACKAGED_LAZY=ON \ - $DEFINE - - cmake --build build --parallel - - cd build - macdeployqt Telegram.app - codesign --remove-signature Telegram.app - - mkdir dmgsrc - mv Telegram.app dmgsrc - hdiutil create -volname Telegram -srcfolder dmgsrc -ov -format UDZO Telegram.dmg + cmake -Bbuild . -DTDESKTOP_API_TEST=ON $DEFINE + cmake --build build + macdeployqt build/Telegram.app + codesign --remove-signature build/Telegram.app - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' run: | cd $REPO_NAME/build mkdir artifact - mv Telegram.dmg artifact/ - - uses: actions/upload-artifact@v4 + mv Telegram.app artifact/ + - uses: actions/upload-artifact@v7 if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. with: diff --git a/.github/workflows/master_updater.yml b/.github/workflows/master_updater.yml index c59f62e29ad931..fedc24f8e5d781 100644 --- a/.github/workflows/master_updater.yml +++ b/.github/workflows/master_updater.yml @@ -5,33 +5,9 @@ on: types: released jobs: - updater: - runs-on: ubuntu-latest - env: - SKIP: "0" - to_branch: "master" + User-agent: + runs-on: ubuntu-slim steps: - - uses: actions/checkout@v4 + - uses: desktop-app/action_code_updater@master with: - fetch-depth: 0 - if: env.SKIP == '0' - - name: Push the code to the master branch. - if: env.SKIP == '0' - run: | - token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }} - if [ -z "${token}" ]; then - echo "Token is unset. Nothing to do." - exit 0 - fi - - url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY - latest_tag=$(git describe --tags --abbrev=0) - echo "Latest tag: $latest_tag" - - git remote set-url origin $url - git remote -v - git checkout master - git merge $latest_tag - - git push origin HEAD:refs/heads/$to_branch - echo "Done!" + type: "dev-to-master" diff --git a/.github/workflows/needs-user-action.yml b/.github/workflows/needs-user-action.yml index 46ad9f87d82d16..55ad7891ce3668 100644 --- a/.github/workflows/needs-user-action.yml +++ b/.github/workflows/needs-user-action.yml @@ -8,7 +8,7 @@ on: jobs: needs-user-action: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: lee-dohm/no-response@v0.5.0 with: diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index 2d77999835410a..bbe9bcf03cb4ed 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -40,14 +40,14 @@ jobs: snap: name: Ubuntu - runs-on: ubuntu-latest + runs-on: ${{ (github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly') && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }} env: UPLOAD_ARTIFACT: "true" steps: - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive @@ -57,14 +57,18 @@ jobs: sudo iptables -P FORWARD ACCEPT sudo snap install --classic snapcraft sudo usermod -aG lxd $USER - sudo snap run lxd init --auto - sudo snap run lxd waitready + sudo lxd init --auto + sudo lxd waitready - name: Free up some disk space. - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be + uses: samueldr/more-space-action@97048bd0df83fb05b5257887bdbaffc848887673 + with: + enable-remove-default-apt-patterns: false + enable-lvm-span: true + lvm-span-mountpoint: /var/snap/lxd/common/lxd/storage-pools/default/containers - name: Telegram Desktop snap build. - run: sg lxd -c 'snap run snapcraft --verbosity=debug' + run: sudo -u $USER snap run snapcraft --verbosity=debug - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' @@ -75,7 +79,7 @@ jobs: mkdir artifact mv $artifact_name artifact - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4148917e712d2a..6b05f45d5ff71e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -5,9 +5,9 @@ on: jobs: stale: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: stale-issue-message: | Hey there! diff --git a/.github/workflows/unused_styles_updater.yml b/.github/workflows/unused_styles_updater.yml new file mode 100644 index 00000000000000..2c021d1cd09f7a --- /dev/null +++ b/.github/workflows/unused_styles_updater.yml @@ -0,0 +1,16 @@ +name: Unused styles updater. + +on: + repository_dispatch: + types: ["Restart unused_styles_updater workflow."] + schedule: + - cron: "0 3 1 * *" + workflow_dispatch: + +jobs: + Unused-styles: + runs-on: ubuntu-latest + steps: + - uses: desktop-app/action_code_updater@master + with: + type: "unused-styles" diff --git a/.github/workflows/user_agent_updater.yml b/.github/workflows/user_agent_updater.yml index c1713b357f7ad9..1e3dae8182aefc 100644 --- a/.github/workflows/user_agent_updater.yml +++ b/.github/workflows/user_agent_updater.yml @@ -6,8 +6,6 @@ on: schedule: # At 00:00 on day-of-month 1. - cron: "0 0 1 * *" - pull_request_target: - types: [closed] jobs: User-agent: diff --git a/.github/workflows/waiting-for-answer.yml b/.github/workflows/waiting-for-answer.yml index 5e5ff87c968cd3..0cccca6b2d4343 100644 --- a/.github/workflows/waiting-for-answer.yml +++ b/.github/workflows/waiting-for-answer.yml @@ -8,7 +8,7 @@ on: jobs: waiting-for-answer: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: lee-dohm/no-response@v0.5.0 with: diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d08d5824d23d51..81048247d43532 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -5,6 +5,7 @@ on: paths-ignore: - 'docs/**' - '**.md' + - '!docs/building-win*.md' - 'changelog.txt' - 'LEGAL' - 'LICENSE' @@ -23,6 +24,7 @@ on: paths-ignore: - 'docs/**' - '**.md' + - '!docs/building-win*.md' - 'changelog.txt' - 'LEGAL' - 'LICENSE' @@ -42,12 +44,18 @@ jobs: windows: name: Windows - runs-on: windows-latest + runs-on: ${{ matrix.arch == 'arm64' && 'windows-11-arm' || ((github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly') && 'depot-windows-latest-16' || 'windows-latest') }} strategy: matrix: - arch: [Win32, x64] + arch: [x64_x86, x64, arm64] + qt: ["", qt6] generator: ["", "Ninja Multi-Config"] + exclude: + - arch: arm64 + qt: "" + - arch: x64_x86 + qt: qt6 env: UPLOAD_ARTIFACT: "true" @@ -64,31 +72,55 @@ jobs: mkdir %userprofile%\TBuild\Libraries mklink /d %GITHUB_WORKSPACE%\TBuild %userprofile%\TBuild echo TBUILD=%GITHUB_WORKSPACE%\TBuild>>%GITHUB_ENV% + echo LibrariesPath=%GITHUB_WORKSPACE%\TBuild\Libraries${{ matrix.arch == 'x64' && '\win64' || '' }}>>%GITHUB_ENV% - name: Get repository name. shell: bash run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - - uses: ilammy/msvc-dev-cmd@v1.13.0 - name: Native Tools Command Prompt. - with: - arch: ${{ matrix.arch }} - - name: Clone. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: recursive path: ${{ env.TBUILD }}\${{ env.REPO_NAME }} - - name: Set up environment paths. + - name: First set up. shell: bash run: | - echo "CACHE_KEY=$(sha256sum $TBUILD/$REPO_NAME/$PREPARE_PATH | awk '{ print $1 }')" >> $GITHUB_ENV + DOCPATH=$TBUILD/$REPO_NAME/docs/building-win.md + SDK="$(grep "SDK version" $DOCPATH | sed -r 's/.*\*\*(.*)\*\* SDK version.*/\1/')" + echo "SDK=$SDK" >> $GITHUB_ENV + + sed -i '/CMAKE_${lang}_FLAGS_DEBUG_INIT/s/${_Zi}//' "$PROGRAMFILES"/CMake/share/cmake*/Modules/Platform/Windows-MSVC.cmake + + echo "$(sha256sum $TBUILD/$REPO_NAME/$PREPARE_PATH | awk '{ print $1 }')" >> CACHE_KEY.txt + echo "$SDK" >> CACHE_KEY.txt + echo "CACHE_KEY=$(sha256sum CACHE_KEY.txt | awk '{ print $1 }')" >> $GITHUB_ENV echo "Configurate git for cherry-picks." git config --global user.email "you@example.com" git config --global user.name "Sample" + - name: Install VS 2026 Build Tools. + if: matrix.arch == 'arm64' + shell: pwsh + run: | + Invoke-WebRequest -Uri https://aka.ms/vs/18/stable/vs_buildtools.exe -OutFile vs_buildtools.exe + $p = Start-Process -FilePath .\vs_buildtools.exe -Wait -PassThru -ArgumentList ` + '--quiet','--wait','--norestart','--nocache', ` + '--add','Microsoft.VisualStudio.Workload.VCTools', ` + '--add','Microsoft.VisualStudio.Component.VC.Tools.ARM64', ` + '--add','Microsoft.VisualStudio.Component.VC.ATL.ARM64' + Remove-Item vs_buildtools.exe -Force + if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) { exit $p.ExitCode } + + - uses: Eden-CI/msvc-dev-cmd@master + name: Native Tools Command Prompt. + with: + arch: ${{ matrix.arch }} + sdk: ${{ env.SDK }} + toolset: ${{ case(matrix.arch == 'arm64', '', '14.44') }} + - name: NuGet sources. run: | nuget sources Disable -Name "Microsoft Visual Studio Offline Packages" @@ -96,27 +128,38 @@ jobs: - name: ThirdParty cache. id: cache-third-party - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ env.TBUILD }}\ThirdParty - key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }} - restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party- + key: ${{ runner.OS }}-${{ runner.arch }}-third-party-${{ env.CACHE_KEY }} + restore-keys: ${{ runner.OS }}-${{ runner.arch }}-third-party- - name: Libraries cache. id: cache-libs - uses: actions/cache@v4 + uses: actions/cache@v6 with: - path: ${{ env.TBUILD }}\Libraries + path: | + ${{ env.LibrariesPath }}\* + !${{ env.LibrariesPath }}\cache_keys + !${{ env.LibrariesPath }}\[qQ]t[_-]* + ${{ env.LibrariesPath }}\cache_keys\* + !${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]* key: ${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.CACHE_KEY }} restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-libs- + - name: Qt cache. + id: cache-qt + uses: actions/cache@v6 + with: + path: | + ${{ env.LibrariesPath }}\[qQ]t[_-]* + ${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]* + key: ${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-${{ env.CACHE_KEY }} + restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}- + - name: Libraries. - env: - GYP_MSVS_OVERRIDE_PATH: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\' - GYP_MSVS_VERSION: 2022 run: | - cd %TBUILD% - %REPO_NAME%\Telegram\build\prepare\win.bat skip-release silent + %TBUILD%\%REPO_NAME%\Telegram\build\prepare\win.bat skip-release silent ${{ matrix.qt }} - name: Read configuration matrix. shell: bash @@ -126,18 +169,23 @@ jobs: ARCH="" if [ -n "${{ matrix.arch }}" ]; then case "${{ matrix.arch }}" in - Win32) ARCH="x86";; + x64_x86) ARCH="x86";; + arm64) ARCH="arm";; *) ARCH="${{ matrix.arch }}";; esac echo "Architecture from matrix: $ARCH" - ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.arch }}" + ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.arch }}" + fi + + if [ -n "${{ matrix.qt }}" ]; then + ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.qt }}" fi GENERATOR="" if [ -n "${{ matrix.generator }}" ]; then GENERATOR="-G \"${{ matrix.generator }}\"" echo "Generator from matrix: $GENERATOR" - ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.generator }}" + ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.generator }}" fi echo "TDESKTOP_BUILD_GENERATOR=$GENERATOR" >> $GITHUB_ENV @@ -148,7 +196,7 @@ jobs: if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo "Define from matrix: $DEFINE" - ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.defines }}" + ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.defines }}" fi echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV @@ -162,11 +210,8 @@ jobs: echo "TDESKTOP_BUILD_API=$API" >> $GITHUB_ENV - name: Free up some disk space. - run: | - cd %TBUILD% - del /S Libraries\*.pdb - del /S Libraries\*.pch - del /S Libraries\*.obj + shell: bash + run: find $LibrariesPath '(' '(' ! '(' -name '*.lib' -o -name '*.a' -o -name '*.exe' -o -name '*.h' -o -name '*.hpp' -o -name '*.inc' -o -name '*.cmake' -o -path '*/include/*' -o -path '*/objects-*' -o -path '*/cache_keys/*' -o -path '*/patches/*' ')' -type f ')' -o -empty ')' -delete - name: Telegram Desktop build. if: env.ONLY_CACHE == 'false' @@ -176,12 +221,13 @@ jobs: call configure.bat ^ %TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_ARCH% ^ + ${{ matrix.qt }} ^ %TDESKTOP_BUILD_API% ^ - -D CMAKE_C_FLAGS="/WX" ^ - -D CMAKE_CXX_FLAGS="/WX" ^ + -D CMAKE_CONFIGURATION_TYPES=Debug ^ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON ^ + -D CMAKE_MSVC_DEBUG_INFORMATION_FORMAT= ^ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ - -D DESKTOP_APP_NO_PDB=ON ^ %TDESKTOP_BUILD_DEFINE% cmake --build ..\out --config Debug --parallel @@ -193,7 +239,7 @@ jobs: mkdir artifact move %OUT%\Telegram.exe artifact/ move %OUT%\Updater.exe artifact/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 name: Upload artifact. if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') with: diff --git a/.gitignore b/.gitignore index c2896799ea0b46..4da55962c4fbd8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ Release/ ipch/ .vs/ .vscode/ +.cache/ +compile_commands.json /Telegram/log.txt /Telegram/data @@ -51,3 +53,21 @@ stage *.*~ .idea/ cmake-build-debug/ +*.qsb + +# Local configuration files +settings.local.json +*.local.json +.env +.env.local +.env.*.local + +# Cursor IDE local settings (but keep .cursor/rules/) +.cursor/* +!.cursor/rules/ + +# AI work folder (session-specific, not for version control) +.ai + +# Generated changelog page (built by CI, deployed to GitHub Pages) +/docs/changelog/ diff --git a/.gitmodules b/.gitmodules index 91f154826c6a1c..4d64f0fc22c285 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ -[submodule "Telegram/ThirdParty/libtgvoip"] - path = Telegram/ThirdParty/libtgvoip - url = https://github.com/telegramdesktop/libtgvoip [submodule "Telegram/ThirdParty/GSL"] path = Telegram/ThirdParty/GSL - url = https://github.com/desktop-app/GSL.git + url = https://github.com/Microsoft/GSL.git [submodule "Telegram/ThirdParty/xxHash"] path = Telegram/ThirdParty/xxHash url = https://github.com/Cyan4973/xxHash.git @@ -76,12 +73,6 @@ [submodule "Telegram/lib_webview"] path = Telegram/lib_webview url = https://github.com/desktop-app/lib_webview.git -[submodule "Telegram/ThirdParty/jemalloc"] - path = Telegram/ThirdParty/jemalloc - url = https://github.com/jemalloc/jemalloc -[submodule "Telegram/ThirdParty/dispatch"] - path = Telegram/ThirdParty/dispatch - url = https://github.com/apple/swift-corelibs-libdispatch [submodule "Telegram/ThirdParty/kimageformats"] path = Telegram/ThirdParty/kimageformats url = https://github.com/KDE/kimageformats.git @@ -97,3 +88,15 @@ [submodule "Telegram/ThirdParty/xdg-desktop-portal"] path = Telegram/ThirdParty/xdg-desktop-portal url = https://github.com/flatpak/xdg-desktop-portal.git +[submodule "Telegram/lib_translate"] + path = Telegram/lib_translate + url = https://github.com/desktop-app/lib_translate +[submodule "Telegram/ThirdParty/cmark-gfm"] + path = Telegram/ThirdParty/cmark-gfm + url = https://github.com/github/cmark-gfm.git +[submodule "Telegram/ThirdParty/MicroTeX"] + path = Telegram/ThirdParty/MicroTeX + url = https://github.com/desktop-app/MicroTeX.git +[submodule "Telegram/ThirdParty/TooManyCooks"] + path = Telegram/ThirdParty/TooManyCooks + url = https://github.com/tzcnt/TooManyCooks.git diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000000..c87ae5fec805fd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,536 @@ +# Agent Guide for Telegram Desktop + +This guide defines repository-wide instructions for coding agents working with the Telegram Desktop codebase. + +## Working from Codex on Windows + WSL + +This checkout may be opened in Codex Desktop through the Windows UNC path `\\wsl.localhost\{distro}\home\{user}\Telegram\tdesktop`, while the real Linux path is `/home/{user}/Telegram/tdesktop`. Treat it as a WSL/Linux checkout first, not as a native Windows checkout. + +- Prefer running repository-aware commands through WSL: + +```powershell +wsl.exe -d {distro} --cd /home/{user}/Telegram/tdesktop -- +``` + +- PowerShell can read and write files through the UNC path, but native Windows tools may see different ownership, path, executable, or line-ending behavior than Linux tools. +- Git from PowerShell over `\\wsl.localhost\...` can fail with `detected dubious ownership`. Use WSL Git instead. Do not change global Git `safe.directory` settings unless the user explicitly asks for that. +- Keep path styles matched to the shell. Use `/home/{user}/Telegram/tdesktop/...` with WSL commands, and quoted `\\wsl.localhost\{distro}\home\{user}\Telegram\tdesktop\...` paths with native Windows commands. Avoid passing UNC paths to Linux tools or Linux paths to native Windows tools unless the tool explicitly supports them. +- If a command behaves strangely from the PowerShell UNC working directory, retry the same command through `wsl.exe -d {distro} --cd /home/{user}/Telegram/tdesktop -- ...` before concluding the repository or command is broken. +- Recursive searches and repo inspection are usually faster and more faithful through WSL, for example `wsl.exe -d {distro} --cd /home/{user}/Telegram/tdesktop -- rg ...`. +- Do not assume the WSL host has the build toolchain installed directly. In this setup, WSL may not have `cmake`, while Windows may have `cmake`, and the configured `out/` tree may still target the Linux Docker toolchain. Do not run native Windows `cmake --build out` against a Linux/Docker build tree. +- For WSL/Linux builds, use the Docker build entry point from the repository root: `Telegram/build/docker/centos_env/build_debug.sh`. The Docker daemon must be reachable from WSL; checking `docker info` is fine, but do not start a build unless the user asked for one. +- Existing build outputs may be Linux binaries, for example `out/Debug/Telegram` as an ELF executable, not `Telegram.exe`. Verify the build tree before assuming which platform produced it. +- Be careful with text file line endings. In a WSL/Linux checkout, files should remain LF-only unless the file already uses another convention. CRLF finishing applies only to native, non-WSL Windows runs/checkouts. Do not let PowerShell or Windows tools silently rewrite WSL files to CRLF. If a file becomes mixed, normalize it back to the convention appropriate for the current checkout, without adding a UTF-8 BOM. +- When using the local `task-think` skill from this WSL checkout, keep `.ai/...` artifacts and edited project text files LF-only. Treat the skill's Windows text-normalization phase as not applicable to WSL, except to record that line endings were checked and kept LF/no-BOM. Run CRLF normalization for `task-think` only in a native, non-WSL Windows checkout. + +## Build System Structure + +The build system expects this directory layout: + +```text +L:\Telegram\ # BuildPath +L:\Telegram\tdesktop\ # Repository (you work here) +L:\Telegram\Libraries\ # 32-bit dependencies (Linux/macOS) +L:\Telegram\win64\Libraries\ # 64-bit dependencies (Windows) +L:\Telegram\ThirdParty\ # Build tools (NuGet, Python, etc.) +``` + +Dependencies are located relative to the repository: `../Libraries`, `../win64/Libraries`, or `../ThirdParty`. + +## Build Configuration + +### Build Commands + +**From repository root, run:** + +```bash +cmake --build out --config Debug --target Telegram +``` + +That's it. The `out/` directory is already configured. The executable will be at `out/Debug/Telegram.exe`. + +**From WSL, run through the Linux Docker build environment:** + +```bash +Telegram/build/docker/centos_env/build_debug.sh +``` + +**Important:** When running cmake from a shell that doesn't support `cd`, use quoted absolute paths: +```bash +cmake --build "l:\Telegram\tx64\out" --config Debug --target Telegram +``` + +**Never build Release** - it's extremely heavy and not needed for testing changes. + +## Platform-Specific Requirements + +### Windows +- Requires Visual Studio 2022 +- Must run from appropriate Native Tools Command Prompt: + - "x64 Native Tools Command Prompt" for `win64` + - "x86 Native Tools Command Prompt" for `win` + - "ARM64 Native Tools Command Prompt" for `winarm` +- Dependencies: `../win64/Libraries` (64-bit) or `../Libraries` (32-bit) + +### macOS +- Requires Xcode +- Dependencies: `../Libraries/local/Qt-*` +- Set `QT` environment variable: `export QT=6.8` + +### Linux +- Build dependencies in `../Libraries` +- Set `QT` environment variable if needed + +## Key Files + +- **`Telegram/build/version`** - Version information +- **`out/`** - Build output directory + +## Troubleshooting + +### "Libraries not found" +Ensure the repository is in `L:\Telegram\tdesktop`. The build system requires `../win64/Libraries` to exist. + +### Build fails with "wrong command prompt" +On Windows, use the correct Visual Studio Native Tools Command Prompt matching your target (x64/x86/ARM64). + +### Build fails with PDB or EXE access errors + +**⚠️ CRITICAL: DO NOT RETRY THE BUILD. STOP AND WAIT FOR USER.** + +If the build fails with ANY of these errors: +- `fatal error C1041: cannot open program database` +- `cannot open output file 'Telegram.exe'` +- `LNK1104: cannot open file` +- Any "access denied" or "file in use" error + +**STOP IMMEDIATELY.** These errors mean files are locked by a running process (Telegram.exe or debugger). + +**What to do:** +1. Do NOT attempt another build - it will fail the same way +2. Do NOT try to delete files - they are locked +3. Do NOT try any workarounds or fixes +4. IMMEDIATELY inform the user: + +> "Build failed - files are locked. Please close Telegram.exe (and any debugger) so I can rebuild." + +**Then WAIT for user confirmation before attempting any build.** + +Retrying builds wastes time and context. The ONLY fix is for the user to close the running process. + +## Best Practices + +1. **Always use Debug builds** - Release builds are extremely heavy +2. **Don't build Release configuration** - it's too heavy for testing + +## Text File Format + +- On Windows, keep project text files with CRLF line endings. +- Do not save source, header, build/config, style, or localization files as UTF-8 with BOM. Use UTF-8 without BOM. +- When rewriting project text files for normalization, preserve file content otherwise and do not introduce a BOM. + +## Commits + +- Subject: one concise, plain-language line summarizing the change, ~50-60 characters, matching the style of recent `git log` subjects. This is usually the entire message. +- Add a short plain-language body only when the subject can't carry it (what was done, not the technical how) — a line or two at most. +- Never add a `Co-Authored-By:` line or any tool/assistant attribution trailer. +- Never add `Autotask:`/attempt or other workflow markers — commits read like normal history. + +## Local Storage Serialization + +Both app-level (`Core::Settings`) and session-level (`Main::SessionSettings`) use sequential binary serialization via `QDataStream`. Key rules: + +- New fields must ALWAYS be appended at the **end** of the stream, never inserted in the middle +- Reading new fields must be guarded with `!stream.atEnd()` and provide a meaningful default/fallback +- Inserting in the middle breaks reading of data saved by older versions (the new read code consumes bytes that belong to subsequent fields) +- For simple flags and values, prefer using the generic KV prefs facility (`writePref` / `readPref`) instead of adding to the binary stream -- this avoids serialization ordering issues entirely + +--- + +# Development Guidelines + +## Coding Style + +**Do NOT write comments in code:** + +This is important! Do not write single-line comments that describe what the next line does - they are bloat. Comments are allowed ONLY to describe complex algorithms in detail, when the explanation requires at least 4-5 lines. Self-documenting code with clear variable and function names is preferred. + +```cpp +// BAD - don't do this: +// Get the user's name +auto name = user->name(); +// Check if premium +if (user->isPremium()) { + +// GOOD - no comments needed, code is self-explanatory: +auto name = user->name(); +if (user->isPremium()) { + +// ACCEPTABLE - complex algorithm explanation (4+ lines): +// The algorithm works by first collecting all visible messages +// in the viewport, then calculating their intersection with +// the clip rectangle. Messages are grouped by date headers, +// and we need to account for sticky headers that may overlap +// with the first message in each group. +``` + +**Style and formatting rules** are in `REVIEW.md` — see that file for empty-line-before-closing-brace, operator placement in multi-line expressions, if-with-initializer, and other mechanical style rules. + +**Use `auto` for type deduction:** + +Prefer `auto` (or `const auto`, `const auto &`) instead of explicit types: + +```cpp +// Prefer this: +auto currentTitle = tr::lng_settings_title(tr::now); +auto nameProducer = GetNameProducer(); + +// Instead of this: +QString currentTitle = tr::lng_settings_title(tr::now); +rpl::producer nameProducer = GetNameProducer(); +``` + +**Use trailing return types only when the normal form is too long:** + +Prefer the normal return type form when the opening line fits comfortably, roughly around 77 characters or less: + +```cpp +// GOOD: +[[nodiscard]] TextWithEntities FlattenSummaryBlocks( + const std::vector &blocks); +``` + +Do not use one-line trailing return types, or put the trailing return type after `)` on the same line. If it fits on one line with trailing syntax, the normal form would be shorter and easier to read: + +```cpp +// BAD: +auto ComputeTitle() -> QString; + +// BAD: +[[nodiscard]] auto FlattenSummaryBlocks( + const std::vector &blocks) -> TextWithEntities; +``` + +Use `auto` with a trailing return type only when the normal opening line +`{attributes} {return-type} {class-name::}{function-name(}` would be too long, or would force the return type onto its own line. Put the arrow and return type on the next line so the return type remains easy to find: + +```cpp +// BAD: +not_null +HistoryView::Controls::SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args); +``` + +```cpp +// GOOD: +auto HistoryView::Controls::SetupCaptionAiButton( + SetupCaptionAiButtonArgs &&args) +-> not_null; +``` + +This applies to both declarations and definitions. + +**Use `_q` for QString literals:** + +Prefer the project literal `u"..."_q` instead of the verbose `QStringLiteral("...")` macro when creating `QString` values: + +```cpp +// Prefer this: +auto text = u"Settings"_q; + +// Instead of this: +auto text = QStringLiteral("Settings"); +``` + +## API Usage + +### API Schema Files + +API definitions use [TL Language](https://core.telegram.org/mtproto/TL): + +1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`** - MTProto protocol (encryption, auth, etc.) +2. **`Telegram/SourceFiles/mtproto/scheme/api.tl`** - Telegram API (messages, users, chats, etc.) + +### Making API Requests + +Standard pattern using `api()`, generated `MTP...` types, and callbacks: + +```cpp +api().request(MTPnamespace_MethodName( + MTP_flags(flags_value), + MTP_inputPeer(peer), + MTP_string(messageText), + MTP_long(randomId), + MTP_vector() +)).done([=](const MTPResponseType &result) { + // Handle successful response + + // Multiple constructors - use .match() or check type: + result.match([&](const MTPDuser &data) { + // use data.vfirst_name().v + }, [&](const MTPDuserEmpty &data) { + // handle empty user + }); + + // Single constructor - use .data() shortcut: + const auto &data = result.data(); + // use data.vmessages().v + +}).fail([=](const MTP::Error &error) { + // Handle API error + if (error.type() == u"FLOOD_WAIT_X"_q) { + // Handle flood wait + } +}).handleFloodErrors().send(); +``` + +**Key points:** +- Always refer to `api.tl` for method signatures and return types +- Use generated `MTP...` types for parameters (`MTP_int`, `MTP_string`, etc.) +- For multiple constructors, use `.match()` or check `.type()` against `mtpc_` constants then call `.c_constructorName()`: + ```cpp + // Using match: + result.match([&](const MTPDuser &data) { ... }, [&](const MTPDuserEmpty &data) { ... }); + // Or explicit type check: + if (result.type() == mtpc_user) { + const auto &data = result.c_user(); // asserts on type mismatch + } + ``` +- For single constructors, use `.data()` shortcut +- Include `.handleFloodErrors()` before `.send()` in rare cases where you want special case flood error handling +- Silently ignore HTTP 406 errors in UI: the server uses 406 to mean "show nothing to the user". Guard toasts with `MTP::IgnoreError(error)` or use `MTP::ShowErrorFallback(show, error)` (both in `mtproto/mtproto_response.h`) which shows `error.type()` as a toast unless the error should be ignored. + +## UI Styling + +### Style Files + +UI styles are defined in `.style` files using custom syntax: + +```style +using "ui/basic.style"; +using "ui/widgets/widgets.style"; + +MyButtonStyle { + textPadding: margins; + icon: icon; + height: pixels; +} + +defaultButton: MyButtonStyle { + textPadding: margins(10px, 15px, 10px, 15px); + icon: icon{{ "gui/icons/search", iconColor }}; + height: 30px; +} + +primaryButton: MyButtonStyle(defaultButton) { + icon: icon{{ "gui/icons/check", iconColor }}; +} +``` + +**Built-in types:** +- `int` - Integer numbers (e.g., `maxLines: 3;`) +- `bool` - Boolean values (e.g., `useShadow: true;`) +- `pixels` - Pixel values with `px` suffix (e.g., `10px`) +- `color` - Named colors from `ui/colors.palette` +- `icon` - Inline icon definition: `icon{{ "path/stem", color }}` +- `margins` - Four values: `margins(top, right, bottom, left)` +- `size` - Two values: `size(width, height)` +- `point` - Two values: `point(x, y)` +- `align` - Alignment: `align(center)`, `align(left)` +- `font` - Font: `font(14px semibold)` +- `double` - Floating point + +**Multi-part icons** (layers drawn bottom-up): +```style +myComplexIcon: icon{ + { "gui/icons/background", iconBgColor }, + { "gui/icons/foreground", iconFgColor } +}; +``` + +**Borders** are typically separate fields, not a single property: +```style +chatInput { + border: 1px; // width + borderFg: defaultInputFieldBorder; // color +} +``` + +**Never hardcode sizes in code:** + +The app supports different interface scale options. Style `px` values are automatically scaled at runtime, but raw integer constants in code are not. Never use hardcoded numbers for margins, paddings, spacing, sizes, coordinates, or any other dimensional values. Always define them in `.style` files and reference via `st::`. + +```cpp +// BAD - breaks at non-100% interface scale: +p.drawText(10, 20, text); +widget->setFixedHeight(48); +auto margin = 8; +auto iconSize = QSize(24, 24); + +// GOOD - define in .style file and reference: +p.drawText(st::myWidgetTextLeft, st::myWidgetTextTop, text); +widget->setFixedHeight(st::myWidgetHeight); +auto margin = st::myWidgetMargin; +auto iconSize = st::myWidgetIconSize; +``` + +**Duration constants**: Animation durations should NOT go in `.style` files, this is a legacy approach. Prefer `constexpr auto kName = crl::time(N)` in an anonymous namespace in the relevant `.cpp` file. + +### Usage in Code + +```cpp +#include "styles/style_widgets.h" + +// Access style members +int height = st::primaryButton.height; +const style::icon &icon = st::primaryButton.icon; +style::margins padding = st::primaryButton.textPadding; + +// Use in painting +void MyWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + p.fillRect(rect(), st::chatInput.backgroundColor); +} +``` + +## Localization + +### String Definitions + +Strings are defined in `Telegram/Resources/langs/lang.strings`: + +``` +"lng_settings_title" = "Settings"; +"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?"; +"lng_files_selected#one" = "{count} file selected"; +"lng_files_selected#other" = "{count} files selected"; +``` + +### Usage in Code + +**Immediate (current value):** + +```cpp +auto currentTitle = tr::lng_settings_title(tr::now); + +auto currentConfirmation = tr::lng_confirm_delete_item( + tr::now, + lt_item_name, currentItemName); + +auto filesText = tr::lng_files_selected(tr::now, lt_count, count); +``` + +**Reactive (rpl::producer):** + +```cpp +auto titleProducer = tr::lng_settings_title(); + +auto confirmationProducer = tr::lng_confirm_delete_item( + lt_item_name, + std::move(itemNameProducer)); + +auto filesTextProducer = tr::lng_files_selected( + lt_count, + countProducer | tr::to_count()); +``` + +**Key points:** +- Pass `tr::now` as first argument for immediate `QString` +- Omit `tr::now` for reactive `rpl::producer` +- Placeholders use `lt_tag_name, value` pattern +- For `{count}`: immediate uses `int`, reactive uses `rpl::producer` with `| tr::to_count()` +- Move producers with `std::move` when passing to placeholders +- Rich text projectors — these `tr::` helpers serve double duty: as the **last argument** (projector) they set the return type to `TextWithEntities`, and as **placeholder values** they wrap individual substitutions in formatting. Always prefer them over `Ui::Text::Bold()`, `Ui::Text::RichLangValue`, etc. — see REVIEW.md for the full mapping. + - `tr::marked` — basic projection, converts `QString` to `TextWithEntities` + - `tr::rich` — interprets `**bold**`/`__italic__` markup in the string + - `tr::bold`, `tr::italic`, `tr::underline` — wrap text in that formatting + - `tr::link` — wrap as a clickable link + - `tr::url(u"https://..."_q)` — returns a projection that converts text to a link pointing to the given URL; can be passed to `rpl::map` or directly to a `tr::lng_...` call + ```cpp + // As last argument (projector): + auto title = tr::lng_export_progress_title(tr::now, tr::bold); + auto text = tr::lng_proxy_incorrect_secret(tr::now, tr::rich); + // As placeholder value wrapper + projector: + auto desc = tr::lng_some_key( + tr::now, + lt_name, + tr::bold(userName), + lt_group, + tr::bold(groupName), + tr::rich); + // Nested tr::lng as placeholder: + auto linked = tr::lng_settings_birthday_contacts( + lt_link, + tr::lng_settings_birthday_contacts_link(tr::url(link)), + tr::marked); + ``` + +## RPL (Reactive Programming Library) + +### Core Concepts + +**Producers** represent streams of values over time: + +```cpp +auto intProducer = rpl::single(123); // Emits single value +auto lifetime = rpl::lifetime(); // Manages subscription lifetime +``` + +### Starting Pipelines + +```cpp +std::move(counter) | rpl::on_next([=](int value) { + qDebug() << "Received: " << value; +}, lifetime); + +// Without lifetime parameter - MUST store returned lifetime: +auto subscriptionLifetime = std::move(counter) | rpl::on_next([=](int value) { + // process value +}); +``` + +### Transforming Producers + +```cpp +auto strings = std::move(ints) | rpl::map([](int value) { + return QString::number(value * 2); +}); + +auto evenInts = std::move(ints) | rpl::filter([](int value) { + return (value % 2 == 0); +}); +``` + +### Combining Producers + +**`rpl::combine`** - combines latest values (lambdas receive unpacked arguments): + +```cpp +auto combined = rpl::combine(countProducer, textProducer); + +std::move(combined) | rpl::on_next([=](int count, const QString &text) { + qDebug() << "Count=" << count << ", Text=" << text; +}, lifetime); +``` + +**`rpl::merge`** - merges producers of same type: + +```cpp +auto merged = rpl::merge(sourceA, sourceB); + +std::move(merged) | rpl::on_next([=](QString &&value) { + qDebug() << "Merged value: " << value; +}, lifetime); +``` + +**Other pipeline starters** — besides `rpl::on_next`, there are: +- `rpl::on_error([=](Error &&e) { ... }, lifetime)` — handle errors +- `rpl::on_done([=] { ... }, lifetime)` — handle stream completion +- `rpl::on_next_error_done(nextCb, errorCb, doneCb, lifetime)` — handle all three + +The `Error` template parameter defaults to `rpl::no_error`: `rpl::producer`. + +**Key points:** +- Explicitly `std::move` producers when starting pipelines +- Pass `rpl::lifetime` to `on_...` methods or store returned lifetime +- Use `rpl::duplicate(producer)` to reuse a producer multiple times +- Combined producers automatically unpack tuples in lambdas (works with `rpl::map`, `rpl::filter`, and `rpl::on_next`) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000000..f8564d18cef4b9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code Pointer + +Read `AGENTS.md` and treat it as the canonical repository-wide instructions. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9393d81d723d23..343a5419d17b67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,14 +4,14 @@ # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -if (APPLE) - # target_precompile_headers with COMPILE_LANGUAGE restriction. - cmake_minimum_required(VERSION 3.23) -else() - cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.31) + +# Workaround some linking errors in GCC due to static library order. +if (POLICY CMP0156) + cmake_policy(SET CMP0156 OLD) endif() -if (POLICY CMP0149) - cmake_policy(SET CMP0149 NEW) +if (POLICY CMP0179) + cmake_policy(SET CMP0179 OLD) endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -20,19 +20,27 @@ include(cmake/validate_special_target.cmake) include(cmake/version.cmake) desktop_app_parse_version(Telegram/build/version) -set(project_langs C CXX) -if (APPLE) - list(APPEND project_langs OBJC OBJCXX) -elseif (LINUX) - list(APPEND project_langs ASM) +if (NOT DEFINED CMAKE_CONFIGURATION_TYPES) + set(configuration_types_init 1) endif() project(Telegram - LANGUAGES ${project_langs} + LANGUAGES C CXX VERSION ${desktop_app_version_cmake} DESCRIPTION "Official Telegram Desktop messenger" HOMEPAGE_URL "https://desktop.telegram.org" ) + +if (APPLE) + enable_language(OBJC OBJCXX) +endif() + +if (configuration_types_init + AND CMAKE_CONFIGURATION_TYPES + AND NOT MinSizeRel IN_LIST CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES};MinSizeRel" CACHE STRING "" FORCE) +endif() + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Telegram) get_filename_component(third_party_loc "Telegram/ThirdParty" REALPATH) @@ -47,9 +55,7 @@ include(cmake/variables.cmake) include(cmake/nice_target_sources.cmake) include(cmake/target_compile_options_if_exists.cmake) include(cmake/target_link_frameworks.cmake) -include(cmake/target_link_optional_libraries.cmake) include(cmake/target_link_options_if_exists.cmake) -include(cmake/target_link_static_libraries.cmake) include(cmake/init_target.cmake) include(cmake/generate_target.cmake) include(cmake/nuget.cmake) diff --git a/LEGAL b/LEGAL index 5d0f5e8a7155ed..09c9e383db122f 100644 --- a/LEGAL +++ b/LEGAL @@ -1,7 +1,7 @@ This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. -Copyright (c) 2014-2024 The Telegram Desktop Authors. +Copyright (c) 2014-2026 The Telegram Desktop Authors. Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/README.md b/README.md index f3995349b83274..d1bbce82ecda30 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ This is the complete source code and the build instructions for the official [Te [![Build Status](https://github.com/telegramdesktop/tdesktop/workflows/Windows./badge.svg)](https://github.com/telegramdesktop/tdesktop/actions) [![Build Status](https://github.com/telegramdesktop/tdesktop/workflows/MacOS./badge.svg)](https://github.com/telegramdesktop/tdesktop/actions) [![Build Status](https://github.com/telegramdesktop/tdesktop/workflows/Linux./badge.svg)](https://github.com/telegramdesktop/tdesktop/actions) +[![Built with Depot](https://img.shields.io/badge/Built%20with-Depot.dev-46A75A)](https://depot.dev) [![Preview of Telegram Desktop][preview_image]][preview_image_url] @@ -43,9 +44,9 @@ Version **1.8.15** was the last that supports older systems ## Third-party * Qt 6 ([LGPL](http://doc.qt.io/qt-6/lgpl.html)) and Qt 5.15 ([LGPL](http://doc.qt.io/qt-5/lgpl.html)) slightly patched -* OpenSSL 3.2.1 ([Apache License 2.0](https://www.openssl.org/source/apache-license-2.0.txt)) +* OpenSSL 3.2.1 ([Apache License 2.0](https://openssl-library.org/source/license/apache-license-2.0.txt)) * WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE)) -* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html)) +* zlib ([zlib License](http://www.zlib.net/zlib_license.html)) * LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html)) * liblzma ([public domain](http://tukaani.org/xz/)) * Google Breakpad ([License](https://chromium.googlesource.com/breakpad/breakpad/+/master/LICENSE)) @@ -58,7 +59,7 @@ Version **1.8.15** was the last that supports older systems * Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE)) * Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt)) * Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)) -* Vazir font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazir-font/blob/master/OFL.txt)) +* Vazirmatn font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazirmatn/blob/master/OFL.txt)) * Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md)) * xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE)) * QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license)) @@ -68,7 +69,7 @@ Version **1.8.15** was the last that supports older systems ## Build instructions -* Windows [(32-bit)][win32] [(64-bit)][win64] +* [Windows (32-bit and 64-bit)][win] * [macOS][mac] * [GNU/Linux using Docker][linux] @@ -78,9 +79,21 @@ Version **1.8.15** was the last that supports older systems [telegram_api]: https://core.telegram.org [telegram_proto]: https://core.telegram.org/mtproto [license]: LICENSE -[win32]: docs/building-win.md -[win64]: docs/building-win-x64.md +[win]: docs/building-win.md [mac]: docs/building-mac.md [linux]: docs/building-linux.md [preview_image]: https://github.com/telegramdesktop/tdesktop/blob/dev/docs/assets/preview.png "Preview of Telegram Desktop" [preview_image_url]: https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/docs/assets/preview.png + +## Thanks to + + + + + + Depot + + + +CI infrastructure sponsored by [Depot](https://depot.dev) — fast GitHub Actions runners. + diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 00000000000000..d1a9a646dd54c5 --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,555 @@ +# Code Review Style Guide + +This file contains style and formatting rules that the review subagent must check and fix. These are mechanical issues that should always be caught during code review. + +## Empty line before closing brace + +Always add an empty line before the closing brace of a **class** (which has one or more sections like `public:` / `private:`). Plain **structs** with just data members do NOT get a trailing empty line — they are compact: `struct Foo { data lines; };`. + +```cpp +// BAD: +class MyClass { +public: + void foo(); + +private: + int _value = 0; +}; + +// GOOD: +class MyClass { +public: + void foo(); + +private: + int _value = 0; + +}; +``` + +## No consecutive empty lines + +Use at most one empty line between declarations, definitions, include groups, or logical blocks. Two or more empty lines in a row add visual noise without adding structure. + +## Multi-line expressions — operators at the start of continuation lines + +When splitting an expression across multiple lines, place operators (like `&&`, `||`, `;`, `+`, etc.) at the **beginning** of continuation lines, not at the end of the previous line. This makes it immediately obvious from the left edge whether a line is a continuation or new code. + +```cpp +// BAD - continuation looks like scope code: +if (const auto &lottie = animation->lottie; + lottie && lottie->valid() && lottie->framesCount() > 1) { + lottie->animate([=] { + +// GOOD - semicolon at start signals continuation: +if (const auto &lottie = animation->lottie + ; lottie && lottie->valid() && lottie->framesCount() > 1) { + lottie->animate([=] { + +// BAD - trailing && makes next line look like independent code: +if (veryLongExpression() && + anotherLongExpression() && + anotherOne()) { + doSomething(); + +// GOOD - leading && clearly marks continuation: +if (veryLongExpression() + && anotherLongExpression() + && anotherOne()) { + doSomething(); +``` + +## Minimize type checks — prefer direct cast over is + as + +Don't check a type and then cast — just cast and check for null. `asUser()` already returns `nullptr` when the peer is not a user, so calling `isUser()` first is redundant. The same applies to `asChannel()`, `asChat()`, etc. + +```cpp +// BAD - redundant isUser() check, then asUser(): +if (peer && peer->isUser()) { + peer->asUser()->setNoForwardFlags( + +// GOOD - just cast and null-check: +if (const auto user = peer->asUser()) { + user->setNoForwardFlags( +``` + +When you need a specific subtype, look up the specific subtype directly instead of loading a generic type and then casting: + +```cpp +// BAD - loads generic peer, then casts: +if (const auto peer = session().data().peerLoaded(peerId) + ; peer && peer->isUser()) { + peer->asUser()->setNoForwardFlags( + +// GOOD - look up the specific subtype directly: +const auto userId = peerToUser(peerId); +if (const auto user = session().data().userLoaded(userId)) { + user->setNoForwardFlags( +``` + +Avoid C++17 `if` with initializer (`;` inside the condition) when the code can be written more clearly with simple nested `if` statements or by extracting the value beforehand: + +```cpp +// BAD - complex if-with-initializer: +if (const auto peer = session().data().peerLoaded(peerId) + ; peer && peer->isUser()) { + +// GOOD - simple nested ifs when direct lookup isn't available: +if (const auto peer = session().data().peerLoaded(peerId)) { + if (const auto user = peer->asUser()) { + +## Always initialize variables of basic types + +Never leave variables of basic types (`int`, `float`, `bool`, pointers, etc.) uninitialized. Custom types with constructors are fine — they initialize themselves. But for any basic type, always provide a default value (`= 0`, `= false`, `= nullptr`, etc.). This applies especially to class fields, where uninitialized members are a persistent source of bugs. + +The only exception is performance-critical hot paths where you can prove no read-from-uninitialized-memory occurs. For class fields there is no such exception — always initialize. + +```cpp +// BAD: +int _bulletLeft; +int _bulletTop; +bool _expanded; +SomeType *_pointer; + +// GOOD: +int _bulletLeft = 0; +int _bulletTop = 0; +bool _expanded = false; +SomeType *_pointer = nullptr; +``` + +## Use tr:: projections for TextWithEntities + +Inside `tr::lng_...()` calls, always use the `tr::` projection helpers instead of their `Ui::Text::` equivalents. The `tr::` helpers are shorter and work uniformly as both placeholder wrappers and final projectors. + +| Instead of | Use | +|---|---| +| `Ui::Text::Bold(x)` | `tr::bold(x)` | +| `Ui::Text::Italic(x)` | `tr::italic(x)` | +| `Ui::Text::RichLangValue` | `tr::rich` | +| `Ui::Text::WithEntities` | `tr::marked` | + +```cpp +// BAD - verbose Ui::Text:: functions: +tr::lng_some_key( + tr::now, + lt_name, + Ui::Text::Bold(name), + lt_group, + Ui::Text::Bold(group), + Ui::Text::RichLangValue) + +// GOOD - concise tr:: helpers: +tr::lng_some_key( + tr::now, + lt_name, + tr::bold(name), + lt_group, + tr::bold(group), + tr::rich) +``` + +Also use `tr::marked()` as the standard way to create `TextWithEntities` — not just as a projector: + +```cpp +// BAD - verbose constructor: +auto text = TextWithEntities(); +auto text = TextWithEntities{ u"hello"_q }; +auto text = TextWithEntities().append(u"hello"_q); + +// GOOD - concise: +auto text = tr::marked(); +auto text = tr::marked(u"hello"_q); +``` + +## Multi-line calls — one argument per line + +When a function call doesn't fit on one line, put each argument on its own line. Don't group "logical pairs" on the same line — it creates inconsistent line lengths and makes diffs noisier. + +```cpp +// BAD - pairs of arguments sharing lines: +tr::lng_some_key( + tr::now, + lt_name, tr::bold(name), + lt_group, tr::bold(group), + tr::rich) + +// GOOD - one argument per line: +tr::lng_some_key( + tr::now, + lt_name, + tr::bold(name), + lt_group, + tr::bold(group), + tr::rich) + +// Single-line is fine when everything fits: +auto text = tr::lng_settings_title(tr::now); +``` + +## std::optional access — avoid value() + +Do not call `std::optional::value()` because it throws an exception that is not available on older macOS targets. Use `has_value()`, `value_or()`, `operator bool()`, or `operator*` instead. + +## Sort includes alphabetically, nested folders first + +After the file's own header, sort `#include` directives alphabetically with two special rules: + +1. **Nested folders before files** in the same directory — like Finder / File Explorer (folders first, then files). E.g. `ui/controls/button.h` sorts before `ui/abstract_button.h`. +2. **Style includes (`styles/style_*.h`) always go last**, separated from the rest. + +```cpp +// BAD - arbitrary order, style mixed in: +#include "media/audio/media_audio.h" +#include "styles/style_media_player.h" +#include "data/data_document.h" +#include "apiwrap.h" + +// GOOD - alphabetical, folders first, styles last: +#include "apiwrap.h" +#include "data/data_document.h" +#include "media/audio/media_audio.h" + +#include "styles/style_media_player.h" +``` + +## Use C++17 nested namespace syntax + +Use `namespace A::B {` instead of nesting `namespace A { namespace B {`. The closing comment mirrors the opening: `} // namespace A::B`. + +```cpp +// BAD - old-style nesting: +namespace Media { +namespace Player { +... +} // namespace Player +} // namespace Media + +// GOOD - C++17 nested: +namespace Media::Player { +... +} // namespace Media::Player +``` + +## Merge consecutive branches with identical bodies + +When two or more consecutive `if` / `else if` branches execute the same code, combine their conditions into a single branch. + +```cpp +// BAD - duplicated body: +if (!document) { + finalize(); + return; +} +if (!document->isSong()) { + finalize(); + return; +} + +// GOOD - combined: +if (!document || !document->isSong()) { + finalize(); + return; +} +``` + +## Use base::take for read-and-reset + +When you need to read a variable's current value and reset it in one step, use `base::take(var)` instead of manually copying and clearing. `base::take` returns the old value and resets the variable to its default-constructed state. + +```cpp +// BAD - manual read + reset: +if (_playing) { + _listenedMs += crl::now() - _playStartedAt; + _playing = false; +} + +// GOOD: +if (base::take(_playing)) { + _listenedMs += crl::now() - _playStartedAt; +} + +// BAD - copy fields then clear them one by one: +const auto document = _document; +const auto contextId = _contextId; +_document = nullptr; +_listenedMs = 0; +if (!document) { + return; +} + +// GOOD - take everything upfront, then validate: +const auto document = base::take(_document); +const auto contextId = base::take(_contextId); +const auto duration = static_cast(base::take(_listenedMs) / 1000); +if (!document || duration <= 0) { + return; +} +``` + +## Don't wrap tr:: lang keys in rpl::single + +`tr::lng_*()` (without `tr::now`) already returns an `rpl::producer`. Wrapping a snapshot in `rpl::single()` defeats live language switching — the value is captured once and never updates. Just call the lang key without `tr::now`. + +```cpp +// BAD - frozen snapshot, won't update on language change: +rpl::single(tr::lng_ai_compose_title(tr::now)) + +// GOOD - live producer that updates automatically: +tr::lng_ai_compose_title() +``` + +## Extract method definitions from local classes + +When defining local classes (e.g. in anonymous namespaces), keep the class body compact — only declarations. Put all method definitions **after** all class definitions. This avoids unnecessary nesting inside the class body and keeps methods at the same indentation level as free functions. + +```cpp +// BAD - methods defined inline, adding a nesting level: +class MyWidget final : public Ui::RpWidget { +public: + MyWidget(QWidget *parent) + : RpWidget(parent) { + // ... 20 lines of setup + } + + void setActive(bool active) { + _active = active; + update(); + } + +protected: + void paintEvent(QPaintEvent *e) override { + // ... 30 lines of painting + } + +private: + bool _active = false; + +}; + +// GOOD - class is a compact declaration, methods defined after: +class MyWidget final : public Ui::RpWidget { +public: + MyWidget(QWidget *parent, QString label); + + void setActive(bool active); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + bool _active = false; + +}; + +MyWidget::MyWidget(QWidget *parent, QString label) +: RpWidget(parent) { + // ... 20 lines of setup +} + +void MyWidget::setActive(bool active) { + _active = active; + update(); +} + +void MyWidget::paintEvent(QPaintEvent *e) { + // ... 30 lines of painting +} +``` + +When there are multiple local classes, put **all class definitions first**, then **all method definitions** after. This keeps the declarations readable as an overview. + +## Do not repeat [[nodiscard]] on method definitions + +Put `[[nodiscard]]` on the method declaration inside the class. Do not repeat it on the out-of-class method definition. Free functions may keep `[[nodiscard]]` on their definition when that is the only declaration. + +```cpp +// BAD - duplicated attribute on the definition: +[[nodiscard]] int MyClass::value() const { + return _value; +} + +// GOOD - declaration carries the attribute, definition stays clean: +int MyClass::value() const { + return _value; +} +``` + +## Use RAII for resource cleanup + +When working with raw resources (Win32 HANDLEs, file descriptors, COM objects), use `gsl::finally` or a dedicated RAII wrapper for cleanup instead of calling release functions manually. Manual cleanup breaks when early returns are added later. + +```cpp +// BAD - manual cleanup, fragile with early returns: +const auto snapshot = CreateToolhelp32Snapshot(...); +if (snapshot != INVALID_HANDLE_VALUE) { + // ... logic that might grow early returns ... + CloseHandle(snapshot); +} + +// GOOD - RAII guard, cleanup runs on any exit path: +const auto snapshot = CreateToolhelp32Snapshot(...); +if (snapshot == INVALID_HANDLE_VALUE) { + return; +} +const auto guard = gsl::finally([&] { + CloseHandle(snapshot); +}); +// ... logic, early returns are safe ... +``` + +## Extract substantial logic from lambdas + +When a lambda grows beyond a few lines of self-contained logic, extract it into a named function (free function in anonymous namespace, or a private method). Lambdas should primarily be glue — captures, dispatch, short transforms. This applies when the lambda's captures are minimal and can easily become function parameters. When a lambda captures many variables from its surrounding context, it may be cleaner to keep it inline. + +```cpp +// BAD - substantial logic buried in a lambda: +crl::async([=] { + auto found = false; + auto pe = PROCESSENTRY32(); + pe.dwSize = sizeof(PROCESSENTRY32); + const auto snapshot = CreateToolhelp32Snapshot(...); + if (snapshot != INVALID_HANDLE_VALUE) { + for (...) { + if (/* match */) { + found = true; + break; + } + } + CloseHandle(snapshot); + } + crl::on_main(weak, [=] { handle(found); }); +}); + +// GOOD - logic extracted, lambda is just glue: +crl::async([=] { + const auto found = FindRunningReader(); + crl::on_main(weak, [=] { handle(found); }); +}); +``` + +## Data-driven matching over chained conditions + +When comparing a value against multiple known constants, store them in a collection and loop instead of chaining `||` conditions. Easier to extend, less repetition, and reads as data rather than logic. + +```cpp +// BAD - repetitive chain, hard to extend: +if (_wcsicmp(name, L"Narrator.exe") == 0 + || _wcsicmp(name, L"nvda.exe") == 0 + || _wcsicmp(name, L"jfw.exe") == 0 + || _wcsicmp(name, L"Zt.exe") == 0) { + +// GOOD - data-driven, easy to extend: +const auto list = std::array{ + L"Narrator.exe", + L"nvda.exe", + L"jfw.exe", + L"Zt.exe", +}; +for (const auto &entry : list) { + if (_wcsicmp(name, entry) == 0) { + return true; + } +} +``` + +## Use !isHidden() for logic checks, not isVisible() + +When you call `show()` / `hide()` / `setVisible()` on a widget and later branch on that state, always check `!isHidden()` (the widget's own flag) — never `isVisible()`. `isVisible()` returns `true` only when the widget **and every ancestor** are visible, so it silently returns `false` during parent show-animations, before the parent is laid out, etc. `isHidden()` reflects exactly the flag you set. + +```cpp +// BAD — breaks when parent is still animating / not yet shown: +child->setVisible(true); +// ... later, in resizeGetHeight or similar: +if (child->isVisible()) { // false if parent isn't visible yet! + child->moveToRight(x, y, w); +} + +// GOOD — checks the widget's own state: +if (!child->isHidden()) { + child->moveToRight(x, y, w); +} +``` + +The same applies to any logic that depends on a previous `show()`/`hide()` call: skip blocks, layout branches, opacity decisions, etc. + +## Consolidate make_state calls into a single State struct + +Every `make_state` is a separate heap allocation. When a function needs multiple pieces of lambda-captured mutable state, define a local `struct State` with all fields and call `make_state()` once, then capture the resulting pointer everywhere. + +```cpp +// BAD - two allocations: +const auto shown = lifetime.make_state(false); +const auto count = lifetime.make_state(0); + +// GOOD - one allocation: +struct State { + bool shown = false; + int count = 0; +}; +const auto state = lifetime.make_state(); +``` + +## Use trailing return type only when the normal form is too long + +Prefer the normal return type form when the opening line fits comfortably, roughly around 77 characters or less. A short return type is easier to read in the normal position: + +```cpp +// GOOD: +[[nodiscard]] TextWithEntities FlattenSummaryBlocks( + const std::vector &blocks); +``` + +Do not use one-line trailing return types, and do not put the trailing return type after `)` on the same line. If trailing syntax fits on one line, the normal form is shorter: + +```cpp +// BAD: +auto ComputeTitle() -> QString; + +// BAD: +[[nodiscard]] auto FlattenSummaryBlocks( + const std::vector &blocks) -> TextWithEntities; +``` + +Use `auto` with a trailing return type only when the normal opening line +`{attributes} {return-type} {class-name::}{function-name(}` would be too long, or would force the return type onto its own line. In that case, put the arrow and return type on the next line: + +```cpp +// BAD - return type orphaned on its own line: +not_null +HistoryView::Controls::SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args); + +// GOOD - long return type is visible and the function name stays on top: +auto HistoryView::Controls::SetupCaptionAiButton( + SetupCaptionAiButtonArgs &&args) +-> not_null; +``` + +## Mind data structure sizes and alignment + +When adding fields to a class or struct, consider the memory layout. A standalone `bool` between two pointer-sized fields wastes 7 bytes to alignment padding. Review new fields for packing opportunities: + +- If the struct already has bitfields, pack new boolean flags as `: 1` members rather than standalone `bool`. +- If alignment leaves a gap (e.g., an `int` followed by a pointer), consider whether a new small field can fill it. +- For classes instantiated in large quantities (per-message, per-element, per-row), every wasted byte is multiplied thousands of times. + +```cpp +// BAD - standalone bool adds 8 bytes (1 + 7 padding) before the next pointer: +mutable bool _myFlag = false; +mutable std::unique_ptr _foo; + +// GOOD - packed into existing bitfield group, no extra bytes: +mutable uint32 _myFlag : 1 = 0; +``` + +## Static member functions use PascalCase + +Non-static member functions use camelCase (`startBatch`, `finalize`). Static member functions use PascalCase (`ShouldTrack`, `Parse`, `Create`), matching the convention for free functions. + +```cpp +// BAD - camelCase for static method: +[[nodiscard]] static bool shouldTrack(not_null item); + +// GOOD - PascalCase for static method: +[[nodiscard]] static bool ShouldTrack(not_null item); +``` diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f378d925ad86dc..fc4a5690d310cf 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(lib_spellcheck) add_subdirectory(lib_storage) add_subdirectory(lib_lottie) add_subdirectory(lib_qr) +add_subdirectory(lib_translate) add_subdirectory(lib_webrtc) add_subdirectory(lib_webview) add_subdirectory(codegen) @@ -26,7 +27,6 @@ get_filename_component(res_loc Resources REALPATH) include(cmake/telegram_options.cmake) include(cmake/lib_ffmpeg.cmake) include(cmake/lib_stripe.cmake) -include(cmake/lib_tgvoip.cmake) include(cmake/lib_tgcalls.cmake) include(cmake/lib_prisma.cmake) include(cmake/td_export.cmake) @@ -34,8 +34,10 @@ include(cmake/td_iv.cmake) include(cmake/td_lang.cmake) include(cmake/td_mtproto.cmake) include(cmake/td_scheme.cmake) +include(cmake/td_tde2e.cmake) include(cmake/td_ui.cmake) -include(cmake/generate_appdata_changelog.cmake) +include(cmake/telegram_apple_swift_runtime.cmake) +include(cmake/generate_appstream_changelog.cmake) if (DESKTOP_APP_TEST_APPS) include(cmake/tests.cmake) @@ -47,30 +49,19 @@ if (WIN32) platform/win/windows_quiethours.idl platform/win/windows_toastactivator.idl ) - - nuget_add_winrt(Telegram) endif() set_target_properties(Telegram PROPERTIES AUTOMOC ON) target_link_libraries(Telegram PRIVATE - tdesktop::lib_tgcalls_legacy tdesktop::lib_tgcalls - tdesktop::lib_tgvoip - - # Order in this list defines the order of include paths in command line. - # We need to place desktop-app::external_minizip this early to have its - # include paths (usually ${PREFIX}/include/minizip) before any depend that - # would add ${PREFIX}/include. This path may have a different , - # for example installed by libzip (https://libzip.org). - desktop-app::external_minizip - tdesktop::td_export tdesktop::td_iv tdesktop::td_lang tdesktop::td_mtproto tdesktop::td_scheme + tdesktop::td_tde2e tdesktop::td_ui desktop-app::lib_webrtc desktop-app::lib_base @@ -81,11 +72,13 @@ PRIVATE desktop-app::lib_storage desktop-app::lib_lottie desktop-app::lib_qr + desktop-app::lib_translate desktop-app::lib_webview desktop-app::lib_ffmpeg desktop-app::lib_stripe desktop-app::external_rlottie desktop-app::external_zlib + desktop-app::external_minizip desktop-app::external_kcoreaddons desktop-app::external_qt_static_plugins desktop-app::external_qt @@ -97,6 +90,10 @@ PRIVATE desktop-app::external_xxhash ) +if (NOT DESKTOP_APP_DISABLE_SWIFT6) + telegram_add_apple_swift_runtime(Telegram) +endif() + target_precompile_headers(Telegram PRIVATE $<$:${src_loc}/stdafx.h>) nice_target_sources(Telegram ${src_loc} PRIVATE @@ -112,6 +109,8 @@ PRIVATE api/api_bot.h api/api_chat_filters.cpp api/api_chat_filters.h + api/api_chat_filters_remove_manager.cpp + api/api_chat_filters_remove_manager.h api/api_chat_invite.cpp api/api_chat_invite.h api/api_chat_links.cpp @@ -120,12 +119,18 @@ PRIVATE api/api_chat_participants.h api/api_cloud_password.cpp api/api_cloud_password.h + data/data_search_calendar.h + data/data_search_calendar.cpp api/api_common.cpp api/api_common.h api/api_confirm_phone.cpp api/api_confirm_phone.h + api/api_compose_with_ai.cpp + api/api_compose_with_ai.h api/api_credits.cpp api/api_credits.h + api/api_credits_history_entry.cpp + api/api_credits_history_entry.h api/api_earn.cpp api/api_earn.h api/api_editing.cpp @@ -147,12 +152,18 @@ PRIVATE api/api_peer_colors.h api/api_peer_photo.cpp api/api_peer_photo.h + api/api_peer_search.cpp + api/api_peer_search.h api/api_polls.cpp api/api_polls.h api/api_premium.cpp api/api_premium.h api/api_premium_option.cpp api/api_premium_option.h + api/api_reactions_notify_settings.cpp + api/api_reactions_notify_settings.h + api/api_read_metrics.cpp + api/api_read_metrics.h api/api_report.cpp api/api_report.h api/api_ringtones.cpp @@ -173,8 +184,14 @@ PRIVATE api/api_statistics_data_deserialize.h api/api_statistics_sender.cpp api/api_statistics_sender.h + api/api_stickers_creator.cpp + api/api_stickers_creator.h + api/api_suggest_post.cpp + api/api_suggest_post.h api/api_text_entities.cpp api/api_text_entities.h + api/api_todo_lists.cpp + api/api_todo_lists.h api/api_toggling_media.cpp api/api_toggling_media.h api/api_transcribes.cpp @@ -205,18 +222,24 @@ PRIVATE boxes/peers/add_bot_to_chat_box.h boxes/peers/add_participants_box.cpp boxes/peers/add_participants_box.h + boxes/peers/channel_ownership_transfer.cpp + boxes/peers/channel_ownership_transfer.h boxes/peers/choose_peer_box.cpp boxes/peers/choose_peer_box.h + boxes/peers/create_managed_bot_box.cpp + boxes/peers/create_managed_bot_box.h boxes/peers/edit_contact_box.cpp boxes/peers/edit_contact_box.h boxes/peers/edit_forum_topic_box.cpp boxes/peers/edit_forum_topic_box.h - boxes/peers/edit_linked_chat_box.cpp - boxes/peers/edit_linked_chat_box.h + boxes/peers/edit_discussion_link_box.cpp + boxes/peers/edit_discussion_link_box.h boxes/peers/edit_members_visible.cpp boxes/peers/edit_members_visible.h boxes/peers/edit_participant_box.cpp boxes/peers/edit_participant_box.h + boxes/peers/edit_tag_control.cpp + boxes/peers/edit_tag_control.h boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.h boxes/peers/edit_peer_color_box.cpp @@ -244,6 +267,10 @@ PRIVATE boxes/peers/prepare_short_info_box.h boxes/peers/replace_boost_box.cpp boxes/peers/replace_boost_box.h + boxes/peers/tag_info_box.cpp + boxes/peers/tag_info_box.h + boxes/peers/verify_peers_box.cpp + boxes/peers/verify_peers_box.h boxes/about_box.cpp boxes/about_box.h boxes/about_sponsored_box.cpp @@ -276,22 +303,30 @@ PRIVATE boxes/edit_caption_box.h boxes/edit_privacy_box.cpp boxes/edit_privacy_box.h + boxes/edit_todo_list_box.cpp + boxes/edit_todo_list_box.h boxes/gift_credits_box.cpp boxes/gift_credits_box.h boxes/gift_premium_box.cpp boxes/gift_premium_box.h boxes/language_box.cpp boxes/language_box.h - boxes/local_storage_box.cpp - boxes/local_storage_box.h boxes/max_invite_box.cpp boxes/max_invite_box.h boxes/moderate_messages_box.cpp boxes/moderate_messages_box.h boxes/peer_list_box.cpp boxes/peer_list_box.h + boxes/peer_list_scroll_cache.cpp + boxes/peer_list_scroll_cache.h boxes/peer_list_controllers.cpp boxes/peer_list_controllers.h + boxes/peer_list_section_headers.cpp + boxes/peer_list_section_headers.h + boxes/peer_list_section_index.cpp + boxes/peer_list_section_index.h + boxes/peer_list_widgets.cpp + boxes/peer_list_widgets.h boxes/peer_lists_box.cpp boxes/peer_lists_box.h boxes/passcode_box.cpp @@ -310,6 +345,8 @@ PRIVATE boxes/report_messages_box.h boxes/ringtones_box.cpp boxes/ringtones_box.h + boxes/select_future_owner_box.cpp + boxes/select_future_owner_box.h boxes/self_destruction_box.cpp boxes/self_destruction_box.h boxes/send_credits_box.cpp @@ -318,18 +355,42 @@ PRIVATE boxes/send_gif_with_caption_box.h boxes/send_files_box.cpp boxes/send_files_box.h - boxes/sessions_box.cpp - boxes/sessions_box.h + boxes/send_files_box_reply_header.cpp + boxes/send_files_box_reply_header.h boxes/share_box.cpp boxes/share_box.h + boxes/star_gift_auction_box.cpp + boxes/star_gift_auction_box.h boxes/star_gift_box.cpp boxes/star_gift_box.h + boxes/star_gift_cover_box.cpp + boxes/star_gift_cover_box.h + boxes/star_gift_craft_animation.cpp + boxes/star_gift_craft_animation.h + boxes/star_gift_craft_box.cpp + boxes/star_gift_craft_box.h + boxes/star_gift_preview_box.cpp + boxes/star_gift_preview_box.h + boxes/star_gift_resale_box.cpp + boxes/star_gift_resale_box.h + boxes/sticker_creator_box.cpp + boxes/sticker_creator_box.h boxes/sticker_set_box.cpp boxes/sticker_set_box.h boxes/stickers_box.cpp boxes/stickers_box.h + boxes/transfer_gift_box.cpp + boxes/transfer_gift_box.h + boxes/compose_ai_box.cpp + boxes/compose_ai_box.h + boxes/create_ai_tone_box.cpp + boxes/create_ai_tone_box.h + boxes/preview_ai_tone_box.cpp + boxes/preview_ai_tone_box.h boxes/translate_box.cpp boxes/translate_box.h + boxes/unconfirmed_auth_denied_box.cpp + boxes/unconfirmed_auth_denied_box.h boxes/url_auth_box.cpp boxes/url_auth_box.h boxes/username_box.cpp @@ -350,12 +411,22 @@ PRIVATE calls/group/calls_group_members_row.h calls/group/calls_group_menu.cpp calls/group/calls_group_menu.h + calls/group/calls_group_message_encryption.cpp + calls/group/calls_group_message_encryption.h + calls/group/calls_group_message_field.cpp + calls/group/calls_group_message_field.h + calls/group/calls_group_messages.cpp + calls/group/calls_group_messages.h + calls/group/calls_group_messages_ui.cpp + calls/group/calls_group_messages_ui.h calls/group/calls_group_panel.cpp calls/group/calls_group_panel.h calls/group/calls_group_rtmp.cpp calls/group/calls_group_rtmp.h calls/group/calls_group_settings.cpp calls/group/calls_group_settings.h + calls/group/calls_group_stars_box.cpp + calls/group/calls_group_stars_box.h calls/group/calls_group_toasts.cpp calls/group/calls_group_toasts.h calls/group/calls_group_viewport.cpp @@ -364,6 +435,8 @@ PRIVATE calls/group/calls_group_viewport_opengl.h calls/group/calls_group_viewport_raster.cpp calls/group/calls_group_viewport_raster.h + calls/group/calls_group_viewport_rhi.cpp + calls/group/calls_group_viewport_rhi.h calls/group/calls_group_viewport_tile.cpp calls/group/calls_group_viewport_tile.h calls/group/calls_volume_item.cpp @@ -378,6 +451,8 @@ PRIVATE calls/calls_instance.h calls/calls_panel.cpp calls/calls_panel.h + calls/calls_panel_background.cpp + calls/calls_panel_background.h calls/calls_signal_bars.cpp calls/calls_signal_bars.h calls/calls_top_bar.cpp @@ -388,6 +463,8 @@ PRIVATE calls/calls_video_bubble.h calls/calls_video_incoming.cpp calls/calls_video_incoming.h + calls/calls_window.cpp + calls/calls_window.h chat_helpers/compose/compose_features.h chat_helpers/compose/compose_show.cpp chat_helpers/compose/compose_show.h @@ -401,6 +478,8 @@ PRIVATE chat_helpers/emoji_keywords.h chat_helpers/emoji_list_widget.cpp chat_helpers/emoji_list_widget.h + chat_helpers/emoji_picker_overlay.cpp + chat_helpers/emoji_picker_overlay.h chat_helpers/emoji_sets_manager.cpp chat_helpers/emoji_sets_manager.h chat_helpers/emoji_suggestions_widget.cpp @@ -437,6 +516,11 @@ PRIVATE chat_helpers/ttl_media_layer_widget.h core/application.cpp core/application.h + core/proxy_rotation_manager.cpp + core/proxy_rotation_manager.h + core/cached_webview_availability.h + core/bank_card_click_handler.cpp + core/bank_card_click_handler.h core/base_integration.cpp core/base_integration.h core/changelogs.cpp @@ -453,7 +537,21 @@ PRIVATE core/crash_report_window.h core/crash_reports.cpp core/crash_reports.h + core/credits_amount.h core/deadlock_detector.h + core/deep_links/deep_links_chats.cpp + core/deep_links/deep_links_chats.h + core/deep_links/deep_links_contacts.cpp + core/deep_links/deep_links_contacts.h + core/deep_links/deep_links_new.cpp + core/deep_links/deep_links_new.h + core/deep_links/deep_links_router.cpp + core/deep_links/deep_links_router.h + core/deep_links/deep_links_settings.cpp + core/deep_links/deep_links_settings.h + core/deep_links/deep_links_types.h + core/external_control.cpp + core/external_control.h core/file_utilities.cpp core/file_utilities.h core/launcher.cpp @@ -487,10 +585,20 @@ PRIVATE data/components/credits.h data/components/factchecks.cpp data/components/factchecks.h + data/components/gift_auctions.cpp + data/components/gift_auctions.h data/components/location_pickers.cpp data/components/location_pickers.h + data/components/passkeys.cpp + data/components/passkeys.h + data/components/promo_suggestions.cpp + data/components/promo_suggestions.h + data/components/recent_inline_bots.cpp + data/components/recent_inline_bots.h data/components/recent_peers.cpp data/components/recent_peers.h + data/components/recent_shared_media_gifts.cpp + data/components/recent_shared_media_gifts.h data/components/scheduled_messages.cpp data/components/scheduled_messages.h data/components/sponsored_messages.cpp @@ -501,6 +609,8 @@ PRIVATE data/notify/data_notify_settings.h data/notify/data_peer_notify_settings.cpp data/notify/data_peer_notify_settings.h + data/notify/data_peer_notify_volume.cpp + data/notify/data_peer_notify_volume.h data/stickers/data_custom_emoji.cpp data/stickers/data_custom_emoji.h data/stickers/data_stickers_set.cpp @@ -510,6 +620,8 @@ PRIVATE data/data_abstract_sparse_ids.h data/data_abstract_structure.cpp data/data_abstract_structure.h + data/data_ai_compose_tones.cpp + data/data_ai_compose_tones.h data/data_audio_msg_id.cpp data/data_audio_msg_id.h data/data_auto_download.cpp @@ -590,6 +702,7 @@ PRIVATE data/data_peer_bot_command.h data/data_peer_bot_commands.cpp data/data_peer_bot_commands.h + data/data_peer_common.h data/data_peer_id.cpp data/data_peer_id.h data/data_peer_values.cpp @@ -600,6 +713,8 @@ PRIVATE data/data_photo_media.h data/data_poll.cpp data/data_poll.h + data/data_poll_messages.cpp + data/data_poll_messages.h data/data_premium_limits.cpp data/data_premium_limits.h data/data_pts_waiter.cpp @@ -611,6 +726,8 @@ PRIVATE data/data_report.h data/data_saved_messages.cpp data/data_saved_messages.h + data/data_saved_music.cpp + data/data_saved_music.h data/data_saved_sublist.cpp data/data_saved_sublist.h data/data_search_controller.cpp @@ -623,6 +740,8 @@ PRIVATE data/data_shared_media.h data/data_sparse_ids.cpp data/data_sparse_ids.h + data/data_star_gift.cpp + data/data_star_gift.h data/data_statistics.h data/data_stories.cpp data/data_stories.h @@ -634,8 +753,12 @@ PRIVATE data/data_streaming.h data/data_thread.cpp data/data_thread.h + data/data_todo_list.cpp + data/data_todo_list.h data/data_types.cpp data/data_types.h + data/data_unread_value.cpp + data/data_unread_value.h data/data_user.cpp data/data_user.h data/data_user_photos.cpp @@ -664,6 +787,8 @@ PRIVATE dialogs/dialogs_indexed_list.h dialogs/dialogs_inner_widget.cpp dialogs/dialogs_inner_widget.h + dialogs/dialogs_inner_widget_accessibility.cpp + dialogs/dialogs_inner_widget_accessibility.h dialogs/dialogs_key.cpp dialogs/dialogs_key.h dialogs/dialogs_list.cpp @@ -672,12 +797,30 @@ PRIVATE dialogs/dialogs_main_list.h dialogs/dialogs_pinned_list.cpp dialogs/dialogs_pinned_list.h + dialogs/dialogs_quick_action.cpp + dialogs/dialogs_quick_action.h dialogs/dialogs_row.cpp dialogs/dialogs_row.h dialogs/dialogs_search_from_controllers.cpp dialogs/dialogs_search_from_controllers.h dialogs/dialogs_search_tags.cpp dialogs/dialogs_search_tags.h + dialogs/dialogs_search_posts.cpp + dialogs/dialogs_search_posts.h + dialogs/dialogs_top_bar_suggestion.cpp + dialogs/dialogs_top_bar_suggestion.h + dialogs/dialogs_top_bar_suggestion_test.cpp + dialogs/suggestions/suggestion.cpp + dialogs/suggestions/suggestion.h + dialogs/suggestions/suggestion_birthday_contacts.cpp + dialogs/suggestions/suggestion_birthday_setup.cpp + dialogs/suggestions/suggestion_custom_promo.cpp + dialogs/suggestions/suggestion_gift_auctions.cpp + dialogs/suggestions/suggestion_low_credits_subs.cpp + dialogs/suggestions/suggestion_premium_grace.cpp + dialogs/suggestions/suggestion_premium_offer.cpp + dialogs/suggestions/suggestion_unreviewed_auth.cpp + dialogs/suggestions/suggestion_userpic_setup.cpp dialogs/dialogs_widget.cpp dialogs/dialogs_widget.h editor/color_picker.cpp @@ -718,6 +861,10 @@ PRIVATE history/admin_log/history_admin_log_section.cpp history/admin_log/history_admin_log_section.h history/view/controls/compose_controls_common.h + history/view/controls/history_view_compose_ai_button.cpp + history/view/controls/history_view_compose_ai_button.h + history/view/controls/history_view_compose_ai_tooltip.cpp + history/view/controls/history_view_compose_ai_tooltip.h history/view/controls/history_view_compose_controls.cpp history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_media_edit_manager.cpp @@ -728,12 +875,16 @@ PRIVATE history/view/controls/history_view_draft_options.h history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.h + history/view/controls/history_view_suggest_options.cpp + history/view/controls/history_view_suggest_options.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h history/view/controls/history_view_webpage_processor.cpp history/view/controls/history_view_webpage_processor.h + history/view/media/history_view_birthday_suggestion.cpp + history/view/media/history_view_birthday_suggestion.h history/view/media/history_view_call.cpp history/view/media/history_view_call.h history/view/media/history_view_contact.cpp @@ -770,12 +921,18 @@ PRIVATE history/view/media/history_view_media_spoiler.h history/view/media/history_view_media_unwrapped.cpp history/view/media/history_view_media_unwrapped.h + history/view/media/history_view_no_forwards_request.cpp + history/view/media/history_view_no_forwards_request.h history/view/media/history_view_photo.cpp history/view/media/history_view_photo.h history/view/media/history_view_poll.cpp history/view/media/history_view_poll.h + history/view/media/menu/history_view_poll_menu.cpp + history/view/media/menu/history_view_poll_menu.h history/view/media/history_view_premium_gift.cpp history/view/media/history_view_premium_gift.h + history/view/media/history_view_save_document_action.cpp + history/view/media/history_view_save_document_action.h history/view/media/history_view_service_box.cpp history/view/media/history_view_service_box.h history/view/media/history_view_similar_channels.cpp @@ -789,8 +946,14 @@ PRIVATE history/view/media/history_view_sticker_player_abstract.h history/view/media/history_view_story_mention.cpp history/view/media/history_view_story_mention.h + history/view/media/history_view_suggest_decision.cpp + history/view/media/history_view_suggest_decision.h history/view/media/history_view_theme_document.cpp history/view/media/history_view_theme_document.h + history/view/media/history_view_todo_list.cpp + history/view/media/history_view_todo_list.h + history/view/media/history_view_unique_gift.cpp + history/view/media/history_view_unique_gift.h history/view/media/history_view_userpic_suggestion.cpp history/view/media/history_view_userpic_suggestion.h history/view/media/history_view_web_page.cpp @@ -807,12 +970,16 @@ PRIVATE history/view/reactions/history_view_reactions_strip.h history/view/reactions/history_view_reactions_tabs.cpp history/view/reactions/history_view_reactions_tabs.h + history/view/history_view_top_peers_selector.cpp + history/view/history_view_top_peers_selector.h history/view/history_view_about_view.cpp history/view/history_view_about_view.h history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.h history/view/history_view_chat_preview.cpp history/view/history_view_chat_preview.h + history/view/history_view_chat_section.cpp + history/view/history_view_chat_section.h history/view/history_view_contact_status.cpp history/view/history_view_contact_status.h history/view/history_view_context_menu.cpp @@ -821,6 +988,14 @@ PRIVATE history/view/history_view_corner_buttons.h history/view/history_view_cursor_state.cpp history/view/history_view_cursor_state.h + history/view/history_view_drag.cpp + history/view/history_view_drag.h + history/view/history_view_draw_to_reply.cpp + history/view/history_view_draw_to_reply.h + history/view/history_view_add_poll_option.cpp + history/view/history_view_add_poll_option.h + history/view/history_view_element_overlay.cpp + history/view/history_view_element_overlay.h history/view/history_view_element.cpp history/view/history_view_element.h history/view/history_view_emoji_interactions.cpp @@ -831,7 +1006,11 @@ PRIVATE history/view/history_view_fake_items.h history/view/history_view_group_call_bar.cpp history/view/history_view_group_call_bar.h + history/view/history_view_group_members_widget.cpp + history/view/history_view_group_members_widget.h history/view/history_view_item_preview.h + history/view/history_view_keyboard_text_selection.cpp + history/view/history_view_keyboard_text_selection.h history/view/history_view_list_widget.cpp history/view/history_view_list_widget.h history/view/history_view_message.cpp @@ -847,16 +1026,22 @@ PRIVATE history/view/history_view_pinned_tracker.h history/view/history_view_quick_action.cpp history/view/history_view_quick_action.h - history/view/history_view_replies_section.cpp - history/view/history_view_replies_section.h + history/view/history_view_reaction_preview.cpp + history/view/history_view_reaction_preview.h + history/view/history_view_read_metrics_tracker.cpp + history/view/history_view_read_metrics_tracker.h history/view/history_view_reply.cpp history/view/history_view_reply.h + history/view/history_view_reply_button.cpp + history/view/history_view_reply_button.h history/view/history_view_requests_bar.cpp history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp history/view/history_view_schedule_box.h history/view/history_view_scheduled_section.cpp history/view/history_view_scheduled_section.h + history/view/history_view_self_forwards_tagger.cpp + history/view/history_view_self_forwards_tagger.h history/view/history_view_send_action.cpp history/view/history_view_send_action.h history/view/history_view_service_message.cpp @@ -865,8 +1050,10 @@ PRIVATE history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h - history/view/history_view_sublist_section.cpp - history/view/history_view_sublist_section.h + history/view/history_view_subsection_tabs.cpp + history/view/history_view_subsection_tabs.h + history/view/history_view_summary_header.cpp + history/view/history_view_summary_header.h history/view/history_view_text_helper.cpp history/view/history_view_text_helper.h history/view/history_view_transcribe_button.cpp @@ -899,20 +1086,34 @@ PRIVATE history/history_item_text.h history/history_inner_widget.cpp history/history_inner_widget.h + history/history_inner_widget_accessibility.cpp + history/history_inner_widget_accessibility.h history/history_location_manager.cpp history/history_location_manager.h + history/history_streamed_drafts.cpp + history/history_streamed_drafts.h history/history_translation.cpp history/history_translation.h history/history_unread_things.cpp history/history_unread_things.h history/history_view_highlight_manager.cpp history/history_view_highlight_manager.h + history/history_view_pull_to_next_channel.cpp + history/history_view_pull_to_next_channel.h + history/history_view_swipe_back_session.cpp + history/history_view_swipe_back_session.h history/history_widget.cpp history/history_widget.h info/bot/earn/info_bot_earn_list.cpp info/bot/earn/info_bot_earn_list.h info/bot/earn/info_bot_earn_widget.cpp info/bot/earn/info_bot_earn_widget.h + info/bot/starref/info_bot_starref_common.cpp + info/bot/starref/info_bot_starref_common.h + info/bot/starref/info_bot_starref_join_widget.cpp + info/bot/starref/info_bot_starref_join_widget.h + info/bot/starref/info_bot_starref_setup_widget.cpp + info/bot/starref/info_bot_starref_setup_widget.h info/channel_statistics/boosts/create_giveaway_box.cpp info/channel_statistics/boosts/create_giveaway_box.h info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp @@ -935,11 +1136,20 @@ PRIVATE info/downloads/info_downloads_provider.h info/downloads/info_downloads_widget.cpp info/downloads/info_downloads_widget.h + info/global_media/info_global_media_widget.cpp + info/global_media/info_global_media_widget.h + info/global_media/info_global_media_inner_widget.cpp + info/global_media/info_global_media_inner_widget.h + info/global_media/info_global_media_provider.cpp + info/global_media/info_global_media_provider.h + info/media/info_media_buttons.cpp info/media/info_media_buttons.h info/media/info_media_common.cpp info/media/info_media_common.h info/media/info_media_empty_widget.cpp info/media/info_media_empty_widget.h + info/media/info_media_grid_zoom.cpp + info/media/info_media_grid_zoom.h info/media/info_media_inner_widget.cpp info/media/info_media_inner_widget.h info/media/info_media_list_section.cpp @@ -952,18 +1162,26 @@ PRIVATE info/media/info_media_widget.h info/members/info_members_widget.cpp info/members/info_members_widget.h + info/peer_gifts/info_peer_gifts_collections.cpp + info/peer_gifts/info_peer_gifts_collections.h info/peer_gifts/info_peer_gifts_common.cpp info/peer_gifts/info_peer_gifts_common.h info/peer_gifts/info_peer_gifts_widget.cpp info/peer_gifts/info_peer_gifts_widget.h + info/polls/info_polls_list_widget.cpp + info/polls/info_polls_list_widget.h info/polls/info_polls_results_inner_widget.cpp info/polls/info_polls_results_inner_widget.h info/polls/info_polls_results_widget.cpp info/polls/info_polls_results_widget.h info/profile/info_profile_actions.cpp info/profile/info_profile_actions.h + info/profile/info_profile_badge_tooltip.cpp + info/profile/info_profile_badge_tooltip.h info/profile/info_profile_badge.cpp info/profile/info_profile_badge.h + info/profile/info_profile_birthday_effect.cpp + info/profile/info_profile_birthday_effect.h info/profile/info_profile_cover.cpp info/profile/info_profile_cover.h info/profile/info_profile_emoji_status_panel.cpp @@ -976,18 +1194,32 @@ PRIVATE info/profile/info_profile_members_controllers.h info/profile/info_profile_phone_menu.cpp info/profile/info_profile_phone_menu.h - info/profile/info_profile_text.cpp - info/profile/info_profile_text.h + info/profile/info_profile_section_stack.cpp + info/profile/info_profile_section_stack.h + info/profile/info_profile_status_label.cpp + info/profile/info_profile_status_label.h + info/profile/info_profile_top_bar.cpp + info/profile/info_profile_top_bar.h info/profile/info_profile_values.cpp info/profile/info_profile_values.h info/profile/info_profile_widget.cpp info/profile/info_profile_widget.h + info/reactions_list/info_reactions_list_widget.cpp + info/reactions_list/info_reactions_list_widget.h + info/requests_list/info_requests_list_widget.cpp + info/requests_list/info_requests_list_widget.h + info/saved/info_saved_music_common.cpp + info/saved/info_saved_music_common.h + info/saved/info_saved_music_provider.cpp + info/saved/info_saved_music_provider.h + info/saved/info_saved_music_widget.cpp + info/saved/info_saved_music_widget.h info/saved/info_saved_sublists_widget.cpp info/saved/info_saved_sublists_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h - info/similar_channels/info_similar_channels_widget.cpp - info/similar_channels/info_similar_channels_widget.h + info/similar_peers/info_similar_peers_widget.cpp + info/similar_peers/info_similar_peers_widget.h info/statistics/info_statistics_common.h info/statistics/info_statistics_inner_widget.cpp info/statistics/info_statistics_inner_widget.h @@ -995,8 +1227,12 @@ PRIVATE info/statistics/info_statistics_list_controllers.h info/statistics/info_statistics_recent_message.cpp info/statistics/info_statistics_recent_message.h + info/statistics/info_statistics_tag.h info/statistics/info_statistics_widget.cpp info/statistics/info_statistics_widget.h + info/stories/info_stories_albums.cpp + info/stories/info_stories_albums.h + info/stories/info_stories_common.h info/stories/info_stories_inner_widget.cpp info/stories/info_stories_inner_widget.h info/stories/info_stories_provider.cpp @@ -1031,6 +1267,10 @@ PRIVATE info/info_wrap_widget.h inline_bots/bot_attach_web_view.cpp inline_bots/bot_attach_web_view.h + inline_bots/inline_bot_confirm_prepared.cpp + inline_bots/inline_bot_confirm_prepared.h + inline_bots/inline_bot_downloads.cpp + inline_bots/inline_bot_downloads.h inline_bots/inline_bot_layout_internal.cpp inline_bots/inline_bot_layout_internal.h inline_bots/inline_bot_layout_item.cpp @@ -1039,12 +1279,16 @@ PRIVATE inline_bots/inline_bot_result.h inline_bots/inline_bot_send_data.cpp inline_bots/inline_bot_send_data.h + inline_bots/inline_bot_storage.cpp + inline_bots/inline_bot_storage.h inline_bots/inline_results_inner.cpp inline_bots/inline_results_inner.h inline_bots/inline_results_widget.cpp inline_bots/inline_results_widget.h intro/intro_code.cpp intro/intro_code.h + intro/intro_email.cpp + intro/intro_email.h intro/intro_password_check.cpp intro/intro_password_check.h intro/intro_phone.cpp @@ -1059,10 +1303,18 @@ PRIVATE intro/intro_step.h intro/intro_widget.cpp intro/intro_widget.h + iv/iv_cached_media.cpp + iv/iv_cached_media.h +# iv/editor/iv_editor_session.cpp +# iv/editor/iv_editor_session.h iv/iv_delegate_impl.cpp iv/iv_delegate_impl.h iv/iv_instance.cpp iv/iv_instance.h + iv/iv_rich_message_serializer.cpp + iv/iv_rich_message_serializer.h + iv/iv_rich_page.cpp + iv/iv_rich_page.h lang/lang_cloud_manager.cpp lang/lang_cloud_manager.h lang/lang_instance.cpp @@ -1071,6 +1323,12 @@ PRIVATE lang/lang_numbers_animation.h lang/lang_translator.cpp lang/lang_translator.h + lang/translate_mtproto_provider.cpp + lang/translate_mtproto_provider.h + lang/translate_provider.cpp + lang/translate_provider.h + lang/translate_url_provider.cpp + lang/translate_url_provider.h layout/layout_document_generic_preview.cpp layout/layout_document_generic_preview.h layout/layout_item_base.cpp @@ -1093,6 +1351,8 @@ PRIVATE main/session/session_show.h media/audio/media_audio.cpp media/audio/media_audio.h + media/audio/media_audio_edit.cpp + media/audio/media_audio_edit.h media/audio/media_audio_capture.cpp media/audio/media_audio_capture.h media/audio/media_audio_capture_common.h @@ -1102,6 +1362,8 @@ PRIVATE media/audio/media_audio_loader.h media/audio/media_audio_loaders.cpp media/audio/media_audio_loaders.h + media/audio/media_audio_local_cache.cpp + media/audio/media_audio_local_cache.h media/audio/media_audio_track.cpp media/audio/media_audio_track.h media/audio/media_child_ffmpeg_loader.cpp @@ -1110,6 +1372,8 @@ PRIVATE media/player/media_player_float.h media/player/media_player_instance.cpp media/player/media_player_instance.h + media/player/media_player_listen_tracker.cpp + media/player/media_player_listen_tracker.h media/player/media_player_panel.cpp media/player/media_player_panel.h media/player/media_player_volume_controller.cpp @@ -1162,37 +1426,56 @@ PRIVATE media/streaming/media_streaming_player.h media/streaming/media_streaming_reader.cpp media/streaming/media_streaming_reader.h + media/streaming/media_streaming_round_preview.cpp + media/streaming/media_streaming_round_preview.h media/streaming/media_streaming_utility.cpp media/streaming/media_streaming_utility.h media/streaming/media_streaming_video_track.cpp media/streaming/media_streaming_video_track.h + media/streaming/media_streaming_native_frame_mac.h media/view/media_view_group_thumbs.cpp media/view/media_view_group_thumbs.h + media/view/media_view_open_common.cpp + media/view/media_view_open_common.h media/view/media_view_overlay_opengl.cpp media/view/media_view_overlay_opengl.h media/view/media_view_overlay_raster.cpp media/view/media_view_overlay_raster.h + media/view/media_view_overlay_rhi.cpp + media/view/media_view_overlay_rhi.h + media/view/media_view_metal_texture.h media/view/media_view_overlay_renderer.h media/view/media_view_overlay_widget.cpp media/view/media_view_overlay_widget.h + media/view/media_view_recognition_selection.cpp + media/view/media_view_recognition_selection.h media/view/media_view_pip.cpp media/view/media_view_pip.h media/view/media_view_pip_opengl.cpp media/view/media_view_pip_opengl.h media/view/media_view_pip_raster.cpp media/view/media_view_pip_raster.h + media/view/media_view_pip_rhi.cpp + media/view/media_view_pip_rhi.h media/view/media_view_pip_renderer.h media/view/media_view_playback_controls.cpp media/view/media_view_playback_controls.h media/view/media_view_playback_progress.cpp media/view/media_view_playback_progress.h - media/view/media_view_open_common.h + media/view/media_view_playback_sponsored.cpp + media/view/media_view_playback_sponsored.h + media/view/media_view_video_stream.cpp + media/view/media_view_video_stream.h media/system_media_controls_manager.h media/system_media_controls_manager.cpp menu/menu_antispam_validator.cpp menu/menu_antispam_validator.h + menu/menu_dock.cpp + menu/menu_dock.h menu/menu_item_download_files.cpp menu/menu_item_download_files.h + menu/menu_item_rate_transcribe_session.cpp + menu/menu_item_rate_transcribe_session.h menu/menu_mute.cpp menu/menu_mute.h menu/menu_send.cpp @@ -1218,6 +1501,8 @@ PRIVATE mtproto/facade.h mtproto/mtp_instance.cpp mtproto/mtp_instance.h + mtproto/proxy_check.cpp + mtproto/proxy_check.h mtproto/sender.h mtproto/session.cpp mtproto/session.h @@ -1226,9 +1511,17 @@ PRIVATE mtproto/special_config_request.cpp mtproto/special_config_request.h mtproto/type_utils.h + overview/overview_checkbox.cpp + overview/overview_checkbox.h overview/overview_layout.cpp overview/overview_layout.h overview/overview_layout_delegate.h + poll/poll_link_box.cpp + poll/poll_link_box.h + poll/poll_link_thumbnail.cpp + poll/poll_link_thumbnail.h + poll/poll_media_upload.cpp + poll/poll_media_upload.h passport/passport_encryption.cpp passport/passport_encryption.h passport/passport_form_controller.cpp @@ -1270,10 +1563,15 @@ PRIVATE platform/linux/overlay_widget_linux.h platform/linux/specific_linux.cpp platform/linux/specific_linux.h + platform/linux/translate_provider_linux.cpp + platform/linux/translate_provider_linux.h platform/linux/tray_linux.cpp platform/linux/tray_linux.h + platform/linux/webauthn_linux.cpp platform/mac/file_utilities_mac.mm platform/mac/file_utilities_mac.h + platform/mac/global_menu_mac.h + platform/mac/global_menu_mac.mm platform/mac/launcher_mac.mm platform/mac/launcher_mac.h platform/mac/integration_mac.mm @@ -1281,6 +1579,8 @@ PRIVATE platform/mac/mac_iconv_helper.c platform/mac/main_window_mac.mm platform/mac/main_window_mac.h + platform/mac/native_event_mac.h + platform/mac/native_event_mac.mm platform/mac/notifications_manager_mac.mm platform/mac/notifications_manager_mac.h platform/mac/overlay_widget_mac.h @@ -1289,8 +1589,11 @@ PRIVATE platform/mac/specific_mac.h platform/mac/specific_mac_p.mm platform/mac/specific_mac_p.h - platform/mac/tray_mac.mm + platform/mac/translate_provider_mac.h + platform/mac/translate_provider_mac.mm platform/mac/tray_mac.h + platform/mac/tray_mac.mm + platform/mac/webauthn_mac.mm platform/mac/window_title_mac.mm platform/mac/touchbar/items/mac_formatter_item.h platform/mac/touchbar/items/mac_formatter_item.mm @@ -1323,14 +1626,18 @@ PRIVATE platform/win/overlay_widget_win.h platform/win/specific_win.cpp platform/win/specific_win.h + platform/win/translate_provider_win.h platform/win/tray_win.cpp platform/win/tray_win.h + platform/win/webauthn_win.cpp platform/win/windows_app_user_model_id.cpp platform/win/windows_app_user_model_id.h platform/win/windows_dlls.cpp platform/win/windows_dlls.h platform/win/windows_autostart_task.cpp platform/win/windows_autostart_task.h + platform/win/windows_taskbar_buttons.cpp + platform/win/windows_taskbar_buttons.h platform/win/windows_toast_activator.cpp platform/win/windows_toast_activator.h platform/platform_file_utilities.h @@ -1342,14 +1649,10 @@ PRIVATE platform/platform_overlay_widget.cpp platform/platform_overlay_widget.h platform/platform_specific.h + platform/platform_translate_provider.h platform/platform_tray.h + platform/platform_webauthn.h platform/platform_window_title.h - profile/profile_back_button.cpp - profile/profile_back_button.h - profile/profile_block_group_members.cpp - profile/profile_block_group_members.h - profile/profile_block_peer_list.cpp - profile/profile_block_peer_list.h profile/profile_block_widget.cpp profile/profile_block_widget.h profile/profile_cover_drop_area.cpp @@ -1374,8 +1677,6 @@ PRIVATE settings/business/settings_recipients_helper.h settings/business/settings_working_hours.cpp settings/business/settings_working_hours.h - settings/cloud_password/settings_cloud_password_common.cpp - settings/cloud_password/settings_cloud_password_common.h settings/cloud_password/settings_cloud_password_email.cpp settings/cloud_password/settings_cloud_password_email.h settings/cloud_password/settings_cloud_password_email_confirm.cpp @@ -1384,59 +1685,87 @@ PRIVATE settings/cloud_password/settings_cloud_password_hint.h settings/cloud_password/settings_cloud_password_input.cpp settings/cloud_password/settings_cloud_password_input.h + settings/cloud_password/settings_cloud_password_login_email.cpp + settings/cloud_password/settings_cloud_password_login_email.h + settings/cloud_password/settings_cloud_password_login_email_confirm.cpp + settings/cloud_password/settings_cloud_password_login_email_confirm.h settings/cloud_password/settings_cloud_password_manage.cpp settings/cloud_password/settings_cloud_password_manage.h settings/cloud_password/settings_cloud_password_start.cpp settings/cloud_password/settings_cloud_password_start.h - settings/settings_advanced.cpp - settings/settings_advanced.h - settings/settings_blocked_peers.cpp - settings/settings_blocked_peers.h - settings/settings_business.cpp - settings/settings_business.h - settings/settings_chat.cpp - settings/settings_chat.h - settings/settings_calls.cpp - settings/settings_calls.h + settings/cloud_password/settings_cloud_password_step.cpp + settings/cloud_password/settings_cloud_password_step.h + settings/cloud_password/settings_cloud_password_validate_icon.cpp + settings/cloud_password/settings_cloud_password_validate_icon.h + settings/sections/settings_active_sessions.cpp + settings/sections/settings_active_sessions.h + settings/sections/settings_advanced.cpp + settings/sections/settings_advanced.h + settings/sections/settings_chat.cpp + settings/sections/settings_chat.h + settings/sections/settings_blocked_peers.cpp + settings/sections/settings_blocked_peers.h + settings/sections/settings_business.cpp + settings/sections/settings_business.h + settings/sections/settings_calls.cpp + settings/sections/settings_calls.h settings/settings_codes.cpp settings/settings_codes.h settings/settings_common_session.cpp settings/settings_common_session.h - settings/settings_credits.cpp - settings/settings_credits.h + settings/detailed_settings_button.cpp + settings/detailed_settings_button.h + settings/sections/settings_credits.cpp + settings/sections/settings_credits.h settings/settings_credits_graphics.cpp settings/settings_credits_graphics.h settings/settings_experimental.cpp settings/settings_experimental.h - settings/settings_folders.cpp - settings/settings_folders.h - settings/settings_global_ttl.cpp - settings/settings_global_ttl.h - settings/settings_information.cpp - settings/settings_information.h + settings/sections/settings_folders.cpp + settings/sections/settings_folders.h + settings/sections/settings_global_ttl.cpp + settings/sections/settings_global_ttl.h + settings/sections/settings_information.cpp + settings/sections/settings_information.h settings/settings_intro.cpp settings/settings_intro.h - settings/settings_local_passcode.cpp - settings/settings_local_passcode.h - settings/settings_main.cpp - settings/settings_main.h - settings/settings_notifications.cpp - settings/settings_notifications.h - settings/settings_notifications_type.cpp - settings/settings_notifications_type.h + settings/sections/settings_local_passcode.cpp + settings/sections/settings_local_passcode.h + settings/sections/settings_local_storage.cpp + settings/sections/settings_local_storage.h + settings/sections/settings_main.cpp + settings/sections/settings_main.h + settings/settings_recent_searches.cpp + settings/settings_recent_searches.h + settings/settings_search.cpp + settings/settings_search.h + settings/settings_faq_suggestions.cpp + settings/settings_faq_suggestions.h + settings/settings_builder.cpp + settings/settings_builder.h + settings/sections/settings_notifications.cpp + settings/sections/settings_notifications.h + settings/sections/settings_privacy_security.cpp + settings/sections/settings_privacy_security.h + settings/sections/settings_notifications_reactions.cpp + settings/sections/settings_notifications_reactions.h + settings/sections/settings_notifications_type.cpp + settings/sections/settings_notifications_type.h + settings/sections/settings_passkeys.cpp + settings/sections/settings_passkeys.h settings/settings_power_saving.cpp settings/settings_power_saving.h - settings/settings_premium.cpp - settings/settings_premium.h + settings/sections/settings_premium.cpp + settings/sections/settings_premium.h settings/settings_privacy_controllers.cpp settings/settings_privacy_controllers.h - settings/settings_privacy_security.cpp - settings/settings_privacy_security.h settings/settings_scale_preview.cpp settings/settings_scale_preview.h settings/settings_type.h - settings/settings_websites.cpp - settings/settings_websites.h + settings/sections/settings_shortcuts.cpp + settings/sections/settings_shortcuts.h + settings/sections/settings_websites.cpp + settings/sections/settings_websites.h storage/details/storage_file_utilities.cpp storage/details/storage_file_utilities.h storage/details/storage_settings_scheme.cpp @@ -1489,8 +1818,12 @@ PRIVATE support/support_preload.h support/support_templates.cpp support/support_templates.h + tde2e/tde2e_integration.cpp + tde2e/tde2e_integration.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h + ui/boxes/emoji_stake_box.cpp + ui/boxes/emoji_stake_box.h ui/boxes/peer_qr_box.cpp ui/boxes/peer_qr_box.h ui/chat/attach/attach_item_single_file_preview.cpp @@ -1501,14 +1834,26 @@ PRIVATE ui/chat/choose_send_as.h ui/chat/choose_theme_controller.cpp ui/chat/choose_theme_controller.h + ui/chat/sponsored_message_bar.cpp + ui/chat/sponsored_message_bar.h + ui/controls/compose_ai_button_factory.cpp + ui/controls/compose_ai_button_factory.h + ui/controls/custom_emoji_toast_icon.cpp + ui/controls/custom_emoji_toast_icon.h ui/controls/emoji_button_factory.cpp ui/controls/emoji_button_factory.h ui/controls/location_picker.cpp ui/controls/location_picker.h ui/controls/silent_toggle.cpp ui/controls/silent_toggle.h + ui/controls/table_rows.cpp + ui/controls/table_rows.h ui/controls/userpic_button.cpp ui/controls/userpic_button.h + ui/controls/warning_tooltip.cpp + ui/controls/warning_tooltip.h + ui/effects/animated_string.cpp + ui/effects/animated_string.h ui/effects/credits_graphics.cpp ui/effects/credits_graphics.h ui/effects/emoji_fly_animation.cpp @@ -1518,8 +1863,18 @@ PRIVATE ui/effects/message_sending_animation_controller.h ui/effects/reaction_fly_animation.cpp ui/effects/reaction_fly_animation.h + ui/effects/thanos_effect.cpp + ui/effects/thanos_effect.h + ui/effects/thanos_effect_controller.cpp + ui/effects/thanos_effect_controller.h + ui/effects/thanos_effect_renderer.cpp + ui/effects/thanos_effect_renderer.h + ui/effects/thanos_effect_session.cpp + ui/effects/thanos_effect_session.h ui/effects/send_action_animations.cpp ui/effects/send_action_animations.h + ui/effects/voice_once_particles.cpp + ui/effects/voice_once_particles.h ui/image/image.cpp ui/image/image.h ui/image/image_location.cpp @@ -1530,8 +1885,10 @@ PRIVATE ui/text/format_song_document_name.h ui/widgets/expandable_peer_list.cpp ui/widgets/expandable_peer_list.h - ui/widgets/label_with_custom_emoji.cpp - ui/widgets/label_with_custom_emoji.h + ui/widgets/chat_filters_tabs_strip.cpp + ui/widgets/chat_filters_tabs_strip.h + ui/widgets/peer_bubble.cpp + ui/widgets/peer_bubble.h ui/countryinput.cpp ui/countryinput.h ui/dynamic_thumbnails.cpp @@ -1543,10 +1900,12 @@ PRIVATE ui/item_text_options.cpp ui/item_text_options.h ui/resize_area.h - ui/search_field_controller.cpp - ui/search_field_controller.h + ui/top_background_gradient.cpp + ui/top_background_gradient.h ui/unread_badge.cpp ui/unread_badge.h + ui/peer/video_userpic_player.cpp + ui/peer/video_userpic_player.h window/main_window.cpp window/main_window.h window/notifications_manager.cpp @@ -1562,6 +1921,8 @@ PRIVATE window/window_adaptive.h window/window_chat_preview.cpp window/window_chat_preview.h + window/window_chat_switch_process.cpp + window/window_chat_switch_process.h window/window_connecting_widget.cpp window/window_connecting_widget.h window/window_controller.cpp @@ -1583,9 +1944,12 @@ PRIVATE window/window_section_common.h window/window_separate_id.cpp window/window_separate_id.h + window/session/window_session_media.cpp window/window_session_controller.cpp window/window_session_controller.h window/window_session_controller_link_info.h + window/window_setup_email.cpp + window/window_setup_email.h window/window_top_bar_wrap.h window/themes/window_theme.cpp window/themes/window_theme.h @@ -1618,10 +1982,25 @@ PRIVATE settings.cpp settings.h stdafx.h + tray_accounts_menu.h tray.cpp tray.h ) +if (APPLE) + nice_target_sources(Telegram ${src_loc} + PRIVATE + tray_accounts_menu.cpp + media/view/media_view_metal_texture.mm + media/streaming/media_streaming_native_frame_mac.mm + ) +else() + nice_target_sources(Telegram ${src_loc} + PRIVATE + tray_accounts_menu_dummy.cpp + ) +endif() + if (NOT build_winstore) remove_target_sources(Telegram ${src_loc} platform/win/windows_start_task.cpp @@ -1648,7 +2027,6 @@ PRIVATE qrc/emoji_preview.qrc qrc/telegram/animations.qrc qrc/telegram/export.qrc - qrc/telegram/iv.qrc qrc/telegram/picker.qrc qrc/telegram/telegram.qrc qrc/telegram/sounds.qrc @@ -1659,6 +2037,9 @@ PRIVATE numbers.txt ) +include(cmake/qrhi_shaders.cmake) +include(cmake/generate_models.cmake) + if (APPLE AND NOT build_macstore) nice_target_sources(Telegram ${res_loc} PRIVATE @@ -1679,6 +2060,12 @@ if (WIN32) # COMMENT # $ # ) + + if (QT_VERSION LESS 6) + target_link_libraries(Telegram PRIVATE desktop-app::win_directx_helper) + endif() + + target_link_options(Telegram PRIVATE /PDBPAGESIZE:8192) elseif (APPLE) if (NOT DESKTOP_APP_USE_PACKAGED) target_link_libraries(Telegram PRIVATE desktop-app::external_iconv) @@ -1743,22 +2130,31 @@ elseif (APPLE) COMMAND mkdir -p $/../Resources COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $/../Resources COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $/../Resources + COMMAND cp ${CMAKE_BINARY_DIR}/external_microtex_bundled.rcc $/../Resources ) if (NOT build_macstore AND NOT DESKTOP_APP_DISABLE_CRASH_REPORTS) - if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") - set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}") + if (DESKTOP_APP_USE_PACKAGED) + find_program(CRASHPAD_HANDLER crashpad_handler REQUIRED) + elseif (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") + set(CRASHPAD_HANDLER "${libs_loc}/crashpad/out/$,Debug,Release>.${DESKTOP_APP_MAC_ARCH}/crashpad_handler") else() - set(crashpad_dir_part "") + set(CRASHPAD_HANDLER "${libs_loc}/crashpad/out/$,Debug,Release>/crashpad_handler") endif() add_custom_command(TARGET Telegram PRE_LINK COMMAND mkdir -p $/../Helpers - COMMAND cp ${libs_loc}/crashpad/out/$,Debug,Release>${crashpad_dir_part}/crashpad_handler $/../Helpers/ + COMMAND cp ${CRASHPAD_HANDLER} $/../Helpers/ ) endif() else() + nice_target_sources(Telegram ${res_loc} + PRIVATE + qrc/telegram/bot_webview_shell.qrc + ) + include(${cmake_helpers_loc}/external/glib/generate_dbus.cmake) generate_dbus(Telegram org.freedesktop.portal. XdpBackground ${third_party_loc}/xdg-desktop-portal/data/org.freedesktop.portal.Background.xml) + generate_dbus(Telegram org.freedesktop.portal. FlatpakPortal ${src_loc}/platform/linux/org.freedesktop.portal.Flatpak.xml) generate_dbus(Telegram org.freedesktop. XdgNotifications ${src_loc}/platform/linux/org.freedesktop.Notifications.xml) if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION) @@ -1773,8 +2169,9 @@ if (build_macstore) set(bundle_identifier "org.telegram.desktop") set(bundle_entitlements "Telegram Lite.entitlements") set(output_name "Telegram Lite") - set_target_properties(Telegram PROPERTIES - XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${libs_loc}/breakpad/src/client/mac/build/Release + target_link_options(Telegram + PRIVATE + -F${libs_loc}/breakpad/src/client/mac/build/Release ) target_link_frameworks(Telegram PRIVATE Breakpad) add_custom_command(TARGET Telegram @@ -1792,11 +2189,7 @@ else() set(bundle_identifier "com.tdesktop.Telegram") endif() set(bundle_entitlements "Telegram.entitlements") - if (LINUX AND DESKTOP_APP_USE_PACKAGED) - set(output_name "telegram-desktop") - else() - set(output_name "Telegram") - endif() + set(output_name "Telegram") endif() if (CMAKE_GENERATOR STREQUAL Xcode) @@ -1813,7 +2206,7 @@ set_target_properties(Telegram PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${bundle_identifier} XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${desktop_app_version_string} XCODE_ATTRIBUTE_PRODUCT_NAME ${output_name} - XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME Icon XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES XCODE_ATTRIBUTE_COMBINE_HIDPI_IMAGES YES XCODE_ATTRIBUTE_COPY_PHASE_STRIP NO @@ -1838,8 +2231,9 @@ PRIVATE G_LOG_DOMAIN="Telegram" ) +get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if (APPLE - OR "${CMAKE_GENERATOR}" STREQUAL "Ninja Multi-Config" + OR is_multi_config OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL "" OR NOT "${output_name}" STREQUAL "Telegram") set(output_folder ${CMAKE_BINARY_DIR}) @@ -1849,7 +2243,7 @@ endif() set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder}) -if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") +if (MSVC) target_link_libraries(Telegram PRIVATE delayimp @@ -1862,6 +2256,7 @@ if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") /DELAYLOAD:user32.dll /DELAYLOAD:gdi32.dll /DELAYLOAD:advapi32.dll + /DELAYLOAD:avrt.dll /DELAYLOAD:shell32.dll /DELAYLOAD:ole32.dll /DELAYLOAD:oleaut32.dll @@ -1874,6 +2269,7 @@ if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") /DELAYLOAD:crypt32.dll /DELAYLOAD:bcrypt.dll /DELAYLOAD:netapi32.dll + /DELAYLOAD:mpr.dll /DELAYLOAD:imm32.dll /DELAYLOAD:userenv.dll /DELAYLOAD:wtsapi32.dll @@ -1899,7 +2295,7 @@ if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") /DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib /DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll - /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll + # /DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll /DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll @@ -1946,7 +2342,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins base/platform/win/base_windows_safe_library.h ) target_include_directories(Updater PRIVATE ${lib_base_loc}) - if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + if (MSVC) target_link_libraries(Updater PRIVATE delayimp @@ -1964,7 +2360,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins endif() elseif (APPLE) add_custom_command(TARGET Updater - PRE_LINK + POST_BUILD COMMAND mkdir -p $/../Frameworks COMMAND cp $ $/../Frameworks/ ) @@ -1990,6 +2386,10 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins desktop-app::external_openssl ) + if (DESKTOP_APP_USE_PACKAGED) + target_compile_definitions(Packer PRIVATE PACKER_USE_PACKAGED) + endif() + set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder}) endif() elseif (build_winstore) @@ -2012,16 +2412,25 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED) include(GNUInstallDirs) configure_file("../lib/xdg/org.telegram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" @ONLY) configure_file("../lib/xdg/org.telegram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" @ONLY) - generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml") + generate_appstream_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml") install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") - install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png") - install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png") - install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg") + install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon16@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon32@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon48@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon64@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon128@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon256@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/art/icon512@2x.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512@2/apps" RENAME "org.telegram.desktop.png") + install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-symbolic.svg") + install(FILES "Resources/icons/tray_monochrome_attention.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-attention-symbolic.svg") + install(FILES "Resources/icons/tray_monochrome_mute.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-mute-symbolic.svg") install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/Telegram/Resources/animations/ban.tgs b/Telegram/Resources/animations/ban.tgs new file mode 100644 index 00000000000000..1ac1aaa8a493a9 Binary files /dev/null and b/Telegram/Resources/animations/ban.tgs differ diff --git a/Telegram/Resources/animations/cake.tgs b/Telegram/Resources/animations/cake.tgs new file mode 100644 index 00000000000000..2d49c2066dcb62 Binary files /dev/null and b/Telegram/Resources/animations/cake.tgs differ diff --git a/Telegram/Resources/animations/camera_outline.tgs b/Telegram/Resources/animations/camera_outline.tgs new file mode 100644 index 00000000000000..f63db249de6cf4 Binary files /dev/null and b/Telegram/Resources/animations/camera_outline.tgs differ diff --git a/Telegram/Resources/animations/chat/sparkles_emoji.tgs b/Telegram/Resources/animations/chat/sparkles_emoji.tgs new file mode 100644 index 00000000000000..9a97d10b764367 Binary files /dev/null and b/Telegram/Resources/animations/chat/sparkles_emoji.tgs differ diff --git a/Telegram/Resources/animations/chat/video_to_voice.tgs b/Telegram/Resources/animations/chat/video_to_voice.tgs new file mode 100644 index 00000000000000..854a6c734ce31b Binary files /dev/null and b/Telegram/Resources/animations/chat/video_to_voice.tgs differ diff --git a/Telegram/Resources/animations/chat/voice_to_video.tgs b/Telegram/Resources/animations/chat/voice_to_video.tgs new file mode 100644 index 00000000000000..de864f07d9cf6f Binary files /dev/null and b/Telegram/Resources/animations/chat/voice_to_video.tgs differ diff --git a/Telegram/Resources/animations/chat/white_flag_emoji.tgs b/Telegram/Resources/animations/chat/white_flag_emoji.tgs new file mode 100644 index 00000000000000..4c7837d24e6ee8 Binary files /dev/null and b/Telegram/Resources/animations/chat/white_flag_emoji.tgs differ diff --git a/Telegram/Resources/animations/cleaning_cache.tgs b/Telegram/Resources/animations/cleaning_cache.tgs new file mode 100644 index 00000000000000..a488fd72d2c72b Binary files /dev/null and b/Telegram/Resources/animations/cleaning_cache.tgs differ diff --git a/Telegram/Resources/animations/cloud_password/validate.tgs b/Telegram/Resources/animations/cloud_password/validate.tgs new file mode 100644 index 00000000000000..fd5161d885486b Binary files /dev/null and b/Telegram/Resources/animations/cloud_password/validate.tgs differ diff --git a/Telegram/Resources/animations/cocoon.tgs b/Telegram/Resources/animations/cocoon.tgs new file mode 100644 index 00000000000000..9eba8213addbee Binary files /dev/null and b/Telegram/Resources/animations/cocoon.tgs differ diff --git a/Telegram/Resources/animations/craft_failed.tgs b/Telegram/Resources/animations/craft_failed.tgs new file mode 100644 index 00000000000000..a64ab0a7ffaf9d Binary files /dev/null and b/Telegram/Resources/animations/craft_failed.tgs differ diff --git a/Telegram/Resources/animations/craft_progress.tgs b/Telegram/Resources/animations/craft_progress.tgs new file mode 100644 index 00000000000000..7bcdbda3b276bc Binary files /dev/null and b/Telegram/Resources/animations/craft_progress.tgs differ diff --git a/Telegram/Resources/animations/diamond.tgs b/Telegram/Resources/animations/diamond.tgs new file mode 100644 index 00000000000000..63d1896d9a7f14 Binary files /dev/null and b/Telegram/Resources/animations/diamond.tgs differ diff --git a/Telegram/Resources/animations/dice/dice_6.tgs b/Telegram/Resources/animations/dice/dice_6.tgs new file mode 100644 index 00000000000000..f151a226e7e1b3 Binary files /dev/null and b/Telegram/Resources/animations/dice/dice_6.tgs differ diff --git a/Telegram/Resources/animations/edit_peers/direct_messages.tgs b/Telegram/Resources/animations/edit_peers/direct_messages.tgs new file mode 100644 index 00000000000000..3a0806e1ae3274 Binary files /dev/null and b/Telegram/Resources/animations/edit_peers/direct_messages.tgs differ diff --git a/Telegram/Resources/animations/edit_peers/topics.tgs b/Telegram/Resources/animations/edit_peers/topics.tgs new file mode 100644 index 00000000000000..a5552a4acb86a3 Binary files /dev/null and b/Telegram/Resources/animations/edit_peers/topics.tgs differ diff --git a/Telegram/Resources/animations/edit_peers/topics_list.tgs b/Telegram/Resources/animations/edit_peers/topics_list.tgs new file mode 100644 index 00000000000000..d85bf7ff85b8c8 Binary files /dev/null and b/Telegram/Resources/animations/edit_peers/topics_list.tgs differ diff --git a/Telegram/Resources/animations/edit_peers/topics_tabs.tgs b/Telegram/Resources/animations/edit_peers/topics_tabs.tgs new file mode 100644 index 00000000000000..3a240b4c62e16e Binary files /dev/null and b/Telegram/Resources/animations/edit_peers/topics_tabs.tgs differ diff --git a/Telegram/Resources/animations/hello_status.tgs b/Telegram/Resources/animations/hello_status.tgs new file mode 100644 index 00000000000000..b48182c2c11a4c Binary files /dev/null and b/Telegram/Resources/animations/hello_status.tgs differ diff --git a/Telegram/Resources/animations/media_forbidden.tgs b/Telegram/Resources/animations/media_forbidden.tgs new file mode 100644 index 00000000000000..34e9808eea86d0 Binary files /dev/null and b/Telegram/Resources/animations/media_forbidden.tgs differ diff --git a/Telegram/Resources/animations/my_gifts_empty.tgs b/Telegram/Resources/animations/my_gifts_empty.tgs new file mode 100644 index 00000000000000..85393b18fe76b3 Binary files /dev/null and b/Telegram/Resources/animations/my_gifts_empty.tgs differ diff --git a/Telegram/Resources/animations/no_chats.tgs b/Telegram/Resources/animations/no_chats.tgs new file mode 100644 index 00000000000000..a30673b48a7821 Binary files /dev/null and b/Telegram/Resources/animations/no_chats.tgs differ diff --git a/Telegram/Resources/animations/passkeys.tgs b/Telegram/Resources/animations/passkeys.tgs new file mode 100644 index 00000000000000..62e11b25406ed3 Binary files /dev/null and b/Telegram/Resources/animations/passkeys.tgs differ diff --git a/Telegram/Resources/animations/photo_editor/arrow.tgs b/Telegram/Resources/animations/photo_editor/arrow.tgs new file mode 100644 index 00000000000000..9a9b65a13fef68 Binary files /dev/null and b/Telegram/Resources/animations/photo_editor/arrow.tgs differ diff --git a/Telegram/Resources/animations/photo_editor/blur.tgs b/Telegram/Resources/animations/photo_editor/blur.tgs new file mode 100644 index 00000000000000..3e4ca0aad8bd86 Binary files /dev/null and b/Telegram/Resources/animations/photo_editor/blur.tgs differ diff --git a/Telegram/Resources/animations/photo_editor/eraser.tgs b/Telegram/Resources/animations/photo_editor/eraser.tgs new file mode 100644 index 00000000000000..f8119d19daa34b Binary files /dev/null and b/Telegram/Resources/animations/photo_editor/eraser.tgs differ diff --git a/Telegram/Resources/animations/photo_editor/marker.tgs b/Telegram/Resources/animations/photo_editor/marker.tgs new file mode 100644 index 00000000000000..47bdec5e1d7a1d Binary files /dev/null and b/Telegram/Resources/animations/photo_editor/marker.tgs differ diff --git a/Telegram/Resources/animations/photo_editor/pen.tgs b/Telegram/Resources/animations/photo_editor/pen.tgs new file mode 100644 index 00000000000000..4c739f679a3ef8 Binary files /dev/null and b/Telegram/Resources/animations/photo_editor/pen.tgs differ diff --git a/Telegram/Resources/animations/photo_suggest_icon.tgs b/Telegram/Resources/animations/photo_suggest_icon.tgs new file mode 100644 index 00000000000000..d05f5648e180a8 Binary files /dev/null and b/Telegram/Resources/animations/photo_suggest_icon.tgs differ diff --git a/Telegram/Resources/animations/profile/profile_muting.tgs b/Telegram/Resources/animations/profile/profile_muting.tgs new file mode 100644 index 00000000000000..bdae1b5f90aff1 Binary files /dev/null and b/Telegram/Resources/animations/profile/profile_muting.tgs differ diff --git a/Telegram/Resources/animations/profile/profile_unmuting.tgs b/Telegram/Resources/animations/profile/profile_unmuting.tgs new file mode 100644 index 00000000000000..a1dd834d6c7f79 Binary files /dev/null and b/Telegram/Resources/animations/profile/profile_unmuting.tgs differ diff --git a/Telegram/Resources/animations/rtmp.tgs b/Telegram/Resources/animations/rtmp.tgs new file mode 100644 index 00000000000000..53917fd1a0b775 Binary files /dev/null and b/Telegram/Resources/animations/rtmp.tgs differ diff --git a/Telegram/Resources/animations/settings/chat_automation.tgs b/Telegram/Resources/animations/settings/chat_automation.tgs new file mode 100644 index 00000000000000..703ceb2e61ed84 Binary files /dev/null and b/Telegram/Resources/animations/settings/chat_automation.tgs differ diff --git a/Telegram/Resources/animations/show_or_premium_lastseen.tgs b/Telegram/Resources/animations/show_or_premium_lastseen.tgs new file mode 100644 index 00000000000000..1048f5806edd28 Binary files /dev/null and b/Telegram/Resources/animations/show_or_premium_lastseen.tgs differ diff --git a/Telegram/Resources/animations/show_or_premium_readtime.tgs b/Telegram/Resources/animations/show_or_premium_readtime.tgs new file mode 100644 index 00000000000000..ac3ea3f758ea58 Binary files /dev/null and b/Telegram/Resources/animations/show_or_premium_readtime.tgs differ diff --git a/Telegram/Resources/animations/starref_link.tgs b/Telegram/Resources/animations/starref_link.tgs new file mode 100644 index 00000000000000..ea630045260142 Binary files /dev/null and b/Telegram/Resources/animations/starref_link.tgs differ diff --git a/Telegram/Resources/animations/stop.tgs b/Telegram/Resources/animations/stop.tgs new file mode 100644 index 00000000000000..70ace96393b53c Binary files /dev/null and b/Telegram/Resources/animations/stop.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/archive.tgs b/Telegram/Resources/animations/swipe_action/archive.tgs new file mode 100644 index 00000000000000..73e049e69c344c Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/archive.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/delete.tgs b/Telegram/Resources/animations/swipe_action/delete.tgs new file mode 100644 index 00000000000000..daa3a01626fc0d Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/delete.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/disabled.tgs b/Telegram/Resources/animations/swipe_action/disabled.tgs new file mode 100644 index 00000000000000..a4fb31cb257a14 Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/disabled.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/mute.tgs b/Telegram/Resources/animations/swipe_action/mute.tgs new file mode 100644 index 00000000000000..97d7bbf68c6e7f Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/mute.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/pin.tgs b/Telegram/Resources/animations/swipe_action/pin.tgs new file mode 100644 index 00000000000000..5fbeedbc4fb7a0 Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/pin.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/read.tgs b/Telegram/Resources/animations/swipe_action/read.tgs new file mode 100644 index 00000000000000..59b258677c082b Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/read.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/unarchive.tgs b/Telegram/Resources/animations/swipe_action/unarchive.tgs new file mode 100644 index 00000000000000..f8a4f2108fd675 Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/unarchive.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/unmute.tgs b/Telegram/Resources/animations/swipe_action/unmute.tgs new file mode 100644 index 00000000000000..563749f53fe9db Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/unmute.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/unpin.tgs b/Telegram/Resources/animations/swipe_action/unpin.tgs new file mode 100644 index 00000000000000..8e96e36ec92ef4 Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/unpin.tgs differ diff --git a/Telegram/Resources/animations/swipe_action/unread.tgs b/Telegram/Resources/animations/swipe_action/unread.tgs new file mode 100644 index 00000000000000..97ce96a05f58bc Binary files /dev/null and b/Telegram/Resources/animations/swipe_action/unread.tgs differ diff --git a/Telegram/Resources/animations/toast/chats_archived.tgs b/Telegram/Resources/animations/toast/chats_archived.tgs new file mode 100644 index 00000000000000..7fc3e57802c11c Binary files /dev/null and b/Telegram/Resources/animations/toast/chats_archived.tgs differ diff --git a/Telegram/Resources/animations/toast/chats_filter_in.tgs b/Telegram/Resources/animations/toast/chats_filter_in.tgs new file mode 100644 index 00000000000000..aba04090d0b62d Binary files /dev/null and b/Telegram/Resources/animations/toast/chats_filter_in.tgs differ diff --git a/Telegram/Resources/animations/toast/contact_check.tgs b/Telegram/Resources/animations/toast/contact_check.tgs new file mode 100644 index 00000000000000..c8f62a39a37758 Binary files /dev/null and b/Telegram/Resources/animations/toast/contact_check.tgs differ diff --git a/Telegram/Resources/animations/toast/copy.tgs b/Telegram/Resources/animations/toast/copy.tgs new file mode 100644 index 00000000000000..152af85f798597 Binary files /dev/null and b/Telegram/Resources/animations/toast/copy.tgs differ diff --git a/Telegram/Resources/animations/toast/delete.tgs b/Telegram/Resources/animations/toast/delete.tgs new file mode 100644 index 00000000000000..7925f503f0d8bc Binary files /dev/null and b/Telegram/Resources/animations/toast/delete.tgs differ diff --git a/Telegram/Resources/animations/toast/mute.tgs b/Telegram/Resources/animations/toast/mute.tgs new file mode 100644 index 00000000000000..1dd6ccf0fa3a63 Binary files /dev/null and b/Telegram/Resources/animations/toast/mute.tgs differ diff --git a/Telegram/Resources/animations/toast/pin.tgs b/Telegram/Resources/animations/toast/pin.tgs new file mode 100644 index 00000000000000..9e12da9a10afdc Binary files /dev/null and b/Telegram/Resources/animations/toast/pin.tgs differ diff --git a/Telegram/Resources/animations/toast/save_to_gallery.tgs b/Telegram/Resources/animations/toast/save_to_gallery.tgs new file mode 100644 index 00000000000000..bd9de4b9196d5a Binary files /dev/null and b/Telegram/Resources/animations/toast/save_to_gallery.tgs differ diff --git a/Telegram/Resources/animations/toast/save_to_music.tgs b/Telegram/Resources/animations/toast/save_to_music.tgs new file mode 100644 index 00000000000000..6c7a66aaa86b7b Binary files /dev/null and b/Telegram/Resources/animations/toast/save_to_music.tgs differ diff --git a/Telegram/Resources/animations/toast/saved_messages.tgs b/Telegram/Resources/animations/toast/saved_messages.tgs new file mode 100644 index 00000000000000..6d2cac78305883 Binary files /dev/null and b/Telegram/Resources/animations/toast/saved_messages.tgs differ diff --git a/Telegram/Resources/animations/toast/star_premium_2.tgs b/Telegram/Resources/animations/toast/star_premium_2.tgs new file mode 100644 index 00000000000000..141a1a68cc97b1 Binary files /dev/null and b/Telegram/Resources/animations/toast/star_premium_2.tgs differ diff --git a/Telegram/Resources/animations/toast/tagged.tgs b/Telegram/Resources/animations/toast/tagged.tgs new file mode 100644 index 00000000000000..7d2d20cfca90ce Binary files /dev/null and b/Telegram/Resources/animations/toast/tagged.tgs differ diff --git a/Telegram/Resources/animations/toast/unmute.tgs b/Telegram/Resources/animations/toast/unmute.tgs new file mode 100644 index 00000000000000..7685f107cc7f53 Binary files /dev/null and b/Telegram/Resources/animations/toast/unmute.tgs differ diff --git a/Telegram/Resources/animations/toast/unpin.tgs b/Telegram/Resources/animations/toast/unpin.tgs new file mode 100644 index 00000000000000..b35563a2b18609 Binary files /dev/null and b/Telegram/Resources/animations/toast/unpin.tgs differ diff --git a/Telegram/Resources/animations/toast/voip_invite.tgs b/Telegram/Resources/animations/toast/voip_invite.tgs new file mode 100644 index 00000000000000..0a552e9ff7537e Binary files /dev/null and b/Telegram/Resources/animations/toast/voip_invite.tgs differ diff --git a/Telegram/Resources/animations/transcribe_loading.tgs b/Telegram/Resources/animations/transcribe_loading.tgs new file mode 100644 index 00000000000000..f63b6e9a0282ce Binary files /dev/null and b/Telegram/Resources/animations/transcribe_loading.tgs differ diff --git a/Telegram/Resources/art/affiliate_logo.png b/Telegram/Resources/art/affiliate_logo.png new file mode 100644 index 00000000000000..d5f9b5087d5564 Binary files /dev/null and b/Telegram/Resources/art/affiliate_logo.png differ diff --git a/Telegram/Resources/art/cocoon.webp b/Telegram/Resources/art/cocoon.webp new file mode 100644 index 00000000000000..060239730be9f1 Binary files /dev/null and b/Telegram/Resources/art/cocoon.webp differ diff --git a/Telegram/Resources/art/dice/dice1.svg b/Telegram/Resources/art/dice/dice1.svg new file mode 100644 index 00000000000000..4ab6abbe6156b8 --- /dev/null +++ b/Telegram/Resources/art/dice/dice1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice2.svg b/Telegram/Resources/art/dice/dice2.svg new file mode 100644 index 00000000000000..1e9e0c85340392 --- /dev/null +++ b/Telegram/Resources/art/dice/dice2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice3.svg b/Telegram/Resources/art/dice/dice3.svg new file mode 100644 index 00000000000000..84ff3fcc134889 --- /dev/null +++ b/Telegram/Resources/art/dice/dice3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice4.svg b/Telegram/Resources/art/dice/dice4.svg new file mode 100644 index 00000000000000..dc4af3c88f651a --- /dev/null +++ b/Telegram/Resources/art/dice/dice4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice5.svg b/Telegram/Resources/art/dice/dice5.svg new file mode 100644 index 00000000000000..cdd1efd1db262f --- /dev/null +++ b/Telegram/Resources/art/dice/dice5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/dice/dice6.svg b/Telegram/Resources/art/dice/dice6.svg new file mode 100644 index 00000000000000..f024e88b055fe1 --- /dev/null +++ b/Telegram/Resources/art/dice/dice6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/premium/coin_border.png b/Telegram/Resources/art/premium/coin_border.png new file mode 100644 index 00000000000000..03a5254f568947 Binary files /dev/null and b/Telegram/Resources/art/premium/coin_border.png differ diff --git a/Telegram/Resources/art/premium/coin_inner.obj b/Telegram/Resources/art/premium/coin_inner.obj new file mode 100644 index 00000000000000..03b7f0fabc0efb --- /dev/null +++ b/Telegram/Resources/art/premium/coin_inner.obj @@ -0,0 +1,281 @@ +# 3D mesh decoded from .binobj (source of truth; baked back to .binobj at build time). +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v -0.332026988 24.7804127 0.521257997 +v 2.48780489 24.6208229 0.521257997 +v 5.27214098 24.1440811 0.521257997 +v 7.98604107 23.3561516 0.521257997 +v 10.5953112 22.2669773 0.521257997 +v 13.0671577 20.8902378 0.521257997 +v 15.3705359 19.2432289 0.521257997 +v 17.4764156 17.3467064 0.521256983 +v 19.3583431 15.2244949 0.521256983 +v 20.9926777 12.9032536 0.521256983 +v 22.35882 10.4122334 0.521256983 +v 23.4396133 7.78272915 0.521256983 +v 24.2214775 5.04778004 0.521256983 +v 24.6945534 2.24185109 0.521256983 +v 24.8529148 -0.599849999 0.521256983 +v 24.6945534 -3.44155097 0.52125603 +v 24.2214775 -6.24748087 0.52125603 +v 23.4396133 -8.98242855 0.52125603 +v 22.35882 -11.6119356 0.52125603 +v 20.9926777 -14.102953 0.52125603 +v 19.3583431 -16.4241962 0.52125603 +v 17.4764175 -18.5464039 0.52125603 +v 15.3705368 -20.4429264 0.52125603 +v 13.0671587 -22.0899372 0.52125603 +v 10.5953093 -23.4666748 0.52125603 +v 7.98604107 -24.5558491 0.52125603 +v 5.27214098 -25.3437786 0.521255016 +v 2.48780489 -25.8205223 0.521255016 +v -0.332026988 -25.9801102 0.521255016 +v -3.15185905 -25.8205223 0.52125603 +v -5.9361949 -25.3437786 0.52125603 +v -8.65009499 -24.5558491 0.52125603 +v -11.259366 -23.4666748 0.52125603 +v -13.7312136 -22.0899372 0.52125603 +v -16.0345917 -20.4429264 0.52125603 +v -18.1404686 -18.5464058 0.52125603 +v -20.022398 -16.4241943 0.52125603 +v -21.6567326 -14.1029539 0.52125603 +v -23.0228748 -11.6119337 0.52125603 +v -24.1036701 -8.98242855 0.52125603 +v -24.8855343 -6.24748087 0.52125603 +v -25.3586082 -3.44155097 0.521256983 +v -25.5169697 -0.599849999 0.521256983 +v -25.3586082 2.24185109 0.521256983 +v -24.8855343 5.04778004 0.521256983 +v -24.1036701 7.7827282 0.521256983 +v -23.0228767 10.4122343 0.521256983 +v -21.6567326 12.9032526 0.521256983 +v -20.0223999 15.2244949 0.521256983 +v -18.1404724 17.3467045 0.521256983 +v -16.0345917 19.2432289 0.521256983 +v -13.7312145 20.8902378 0.521257997 +v -11.2593651 22.2669773 0.521257997 +v -8.65009499 23.3561516 0.521257997 +v -5.93619394 24.1440811 0.521257997 +v -3.15185905 24.6208229 0.521257997 +v 2.48780489 24.6208229 -2.76847601 +v -0.332026988 24.7804127 -2.76847601 +v 5.27214098 24.1440811 -2.76847601 +v 7.98604107 23.3561516 -2.76847601 +v 10.5953112 22.2669773 -2.76847601 +v 13.0671577 20.8902378 -2.76847601 +v 15.3705359 19.2432289 -2.76847601 +v 17.4764156 17.3467064 -2.76847601 +v 19.3583431 15.2244949 -2.76847601 +v 20.9926777 12.9032536 -2.76847601 +v 22.35882 10.4122334 -2.76847601 +v 23.4396133 7.78272915 -2.76847696 +v 24.2214775 5.04778004 -2.76847601 +v 24.6945534 2.24185109 -2.76847696 +v 24.8529148 -0.599849999 -2.76847696 +v 24.6945534 -3.44155097 -2.76847696 +v 24.2214775 -6.24748087 -2.76847696 +v 23.4396133 -8.98242855 -2.76847696 +v 22.35882 -11.6119356 -2.76847696 +v 20.9926777 -14.102953 -2.76847792 +v 19.3583431 -16.4241962 -2.76847792 +v 17.4764175 -18.5464039 -2.76847792 +v 15.3705368 -20.4429264 -2.76847792 +v 13.0671587 -22.0899372 -2.76847792 +v 10.5953093 -23.4666748 -2.76847792 +v 7.98604107 -24.5558491 -2.76847792 +v 5.27214098 -25.3437786 -2.76847792 +v 2.48780489 -25.8205223 -2.76847792 +v -0.332026988 -25.9801102 -2.76847792 +v -3.15185905 -25.8205223 -2.76847792 +v -5.9361949 -25.3437786 -2.76847792 +v -8.65009499 -24.5558491 -2.76847792 +v -11.259366 -23.4666748 -2.76847792 +v -13.7312136 -22.0899372 -2.76847792 +v -16.0345917 -20.4429264 -2.76847792 +v -18.1404686 -18.5464058 -2.76847792 +v -20.022398 -16.4241943 -2.76847696 +v -21.6567326 -14.1029539 -2.76847792 +v -23.0228748 -11.6119337 -2.76847792 +v -24.1036701 -8.98242855 -2.76847696 +v -24.8855343 -6.24748087 -2.76847696 +v -25.3586082 -3.44155097 -2.76847696 +v -25.5169697 -0.599849999 -2.76847696 +v -25.3586082 2.24185109 -2.76847696 +v -24.8855343 5.04778004 -2.76847696 +v -24.1036701 7.7827282 -2.76847601 +v -23.0228767 10.4122343 -2.76847696 +v -21.6567326 12.9032526 -2.76847601 +v -20.0223999 15.2244949 -2.76847601 +v -18.1404724 17.3467045 -2.76847601 +v -16.0345917 19.2432289 -2.76847601 +v -13.7312145 20.8902378 -2.76847601 +v -11.2593651 22.2669773 -2.76847601 +v -8.65009499 23.3561516 -2.76847601 +v -5.9361949 24.1440811 -2.76847601 +v -3.15185905 24.6208229 -2.76847601 +vt 0.686730981 0.89130801 +vt 0.641683996 0.910112977 +vt 0.309423 0.89130801 +vt 0.354470998 0.910112977 +vt 0.401324987 0.923716009 +vt 0.594829977 0.923716009 +vt 0.546760023 0.93194598 +vt 0.498077005 0.934701025 +vt 0.449393988 0.93194598 +vt 0.266748011 0.867540002 +vt 0.226981997 0.83910501 +vt 0.729405999 0.867540002 +vt 0.190624997 0.806362987 +vt 0.805528998 0.806362987 +vt 0.158133999 0.769724011 +vt 0.838020027 0.769724011 +vt 0.866235971 0.729649007 +vt 0.129919007 0.729649007 +vt 0.106333002 0.686643004 +vt 0.889820993 0.686643004 +vt 0.0876739994 0.641246021 +vt 0.0741750002 0.594029009 +vt 0.908481002 0.641246021 +vt 0.0660080016 0.54558599 +vt 0.92197901 0.594029009 +vt 0.0632740036 0.496526003 +vt 0.930145979 0.54558599 +vt 0.0660080016 0.447465986 +vt 0.930145979 0.447465986 +vt 0.0741750002 0.399022996 +vt 0.92197901 0.399022996 +vt 0.908481002 0.351806015 +vt 0.0876739994 0.351806015 +vt 0.106333002 0.306409001 +vt 0.889820993 0.306409001 +vt 0.129919007 0.263402998 +vt 0.158133999 0.223327994 +vt 0.866235971 0.263402998 +vt 0.190624997 0.186689004 +vt 0.838020027 0.223327994 +vt 0.226981997 0.153946996 +vt 0.805528998 0.186689004 +vt 0.266748011 0.125512004 +vt 0.729405999 0.125512004 +vt 0.309423 0.101744004 +vt 0.686730981 0.101744004 +vt 0.354470998 0.0829399973 +vt 0.641683996 0.0829399973 +vt 0.401324987 0.0693370029 +vt 0.594829977 0.0693370029 +vt 0.449393988 0.0611060001 +vt 0.498077005 0.0583509989 +vt 0.546760023 0.0611060001 +vt 0.769173026 0.153946996 +vt 0.932880998 0.496526003 +vt 0.769173026 0.83910501 +vt 0.401324004 0.0693370029 +vn -0 -0 1 +vn -0 -0 -1 +f 5/1/1 4/2/1 53/3/1 +f 110/4/2 111/5/2 60/2/2 +f 3/6/1 2/7/1 55/5/1 +f 2/7/1 1/8/1 56/9/1 +f 55/5/1 2/7/1 56/9/1 +f 55/5/1 54/4/1 3/6/1 +f 54/4/1 53/3/1 4/2/1 +f 3/6/1 54/4/1 4/2/1 +f 53/3/1 52/10/1 5/1/1 +f 52/10/1 51/11/1 6/12/1 +f 5/1/1 52/10/1 6/12/1 +f 51/11/1 50/13/1 8/14/1 +f 50/13/1 49/15/1 9/16/1 +f 10/17/1 49/15/1 48/18/1 +f 48/18/1 47/19/1 11/20/1 +f 47/19/1 46/21/1 11/20/1 +f 46/21/1 45/22/1 12/23/1 +f 11/20/1 46/21/1 12/23/1 +f 45/22/1 44/24/1 13/25/1 +f 44/24/1 43/26/1 14/27/1 +f 43/26/1 42/28/1 16/29/1 +f 42/28/1 41/30/1 17/31/1 +f 18/32/1 41/30/1 40/33/1 +f 40/33/1 39/34/1 19/35/1 +f 39/34/1 38/36/1 19/35/1 +f 38/36/1 37/37/1 20/38/1 +f 19/35/1 38/36/1 20/38/1 +f 37/37/1 36/39/1 21/40/1 +f 36/39/1 35/41/1 22/42/1 +f 35/41/1 34/43/1 24/44/1 +f 34/43/1 33/45/1 25/46/1 +f 33/45/1 32/47/1 26/48/1 +f 32/47/1 31/49/1 27/50/1 +f 31/49/1 30/51/1 27/50/1 +f 30/51/1 29/52/1 28/53/1 +f 27/50/1 30/51/1 28/53/1 +f 27/50/1 26/48/1 32/47/1 +f 26/48/1 25/46/1 33/45/1 +f 25/46/1 24/44/1 34/43/1 +f 24/44/1 23/54/1 35/41/1 +f 23/54/1 22/42/1 35/41/1 +f 22/42/1 21/40/1 36/39/1 +f 21/40/1 20/38/1 37/37/1 +f 19/35/1 18/32/1 40/33/1 +f 18/32/1 17/31/1 41/30/1 +f 42/28/1 17/31/1 16/29/1 +f 16/29/1 15/55/1 43/26/1 +f 15/55/1 14/27/1 43/26/1 +f 14/27/1 13/25/1 44/24/1 +f 13/25/1 12/23/1 45/22/1 +f 11/20/1 10/17/1 48/18/1 +f 10/17/1 9/16/1 49/15/1 +f 50/13/1 9/16/1 8/14/1 +f 8/14/1 7/56/1 51/11/1 +f 7/56/1 6/12/1 51/11/1 +f 112/9/2 58/8/2 57/7/2 +f 57/7/2 59/6/2 112/9/2 +f 59/6/2 60/2/2 111/5/2 +f 112/9/2 59/6/2 111/5/2 +f 60/2/2 61/1/2 109/3/2 +f 61/1/2 62/12/2 108/10/2 +f 62/12/2 63/56/2 108/10/2 +f 63/56/2 64/14/2 107/11/2 +f 108/10/2 63/56/2 107/11/2 +f 64/14/2 65/16/2 105/15/2 +f 65/16/2 66/17/2 104/18/2 +f 66/17/2 67/20/2 104/18/2 +f 67/20/2 68/23/2 103/19/2 +f 104/18/2 67/20/2 103/19/2 +f 68/23/2 69/25/2 102/21/2 +f 69/25/2 70/27/2 101/22/2 +f 70/27/2 71/55/2 100/24/2 +f 71/55/2 72/29/2 99/26/2 +f 72/29/2 73/31/2 97/30/2 +f 73/31/2 74/32/2 96/33/2 +f 74/32/2 75/35/2 96/33/2 +f 75/35/2 76/38/2 95/34/2 +f 96/33/2 75/35/2 95/34/2 +f 76/38/2 77/40/2 93/37/2 +f 77/40/2 78/42/2 92/39/2 +f 78/42/2 79/54/2 92/39/2 +f 79/54/2 80/44/2 91/41/2 +f 92/39/2 79/54/2 91/41/2 +f 80/44/2 81/46/2 89/45/2 +f 81/46/2 82/48/2 88/47/2 +f 82/48/2 83/50/2 88/47/2 +f 83/50/2 84/53/2 87/57/2 +f 88/47/2 83/50/2 87/57/2 +f 84/53/2 85/52/2 86/51/2 +f 86/51/2 87/57/2 84/53/2 +f 88/47/2 89/45/2 81/46/2 +f 89/45/2 90/43/2 80/44/2 +f 90/43/2 91/41/2 80/44/2 +f 92/39/2 93/37/2 77/40/2 +f 93/37/2 94/36/2 76/38/2 +f 94/36/2 95/34/2 76/38/2 +f 96/33/2 97/30/2 73/31/2 +f 97/30/2 98/28/2 72/29/2 +f 98/28/2 99/26/2 72/29/2 +f 99/26/2 100/24/2 71/55/2 +f 100/24/2 101/22/2 70/27/2 +f 101/22/2 102/21/2 69/25/2 +f 102/21/2 103/19/2 68/23/2 +f 104/18/2 105/15/2 65/16/2 +f 105/15/2 106/13/2 64/14/2 +f 106/13/2 107/11/2 64/14/2 +f 108/10/2 109/3/2 61/1/2 +f 109/3/2 110/4/2 60/2/2 diff --git a/Telegram/Resources/art/premium/coin_logo.obj b/Telegram/Resources/art/premium/coin_logo.obj new file mode 100644 index 00000000000000..66b2ba732c28eb --- /dev/null +++ b/Telegram/Resources/art/premium/coin_logo.obj @@ -0,0 +1,2227 @@ +# 3D mesh decoded from .binobj (source of truth; baked back to .binobj at build time). +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v 17.7109451 -0.564248979 -3.419173 +v 17.5940895 -0.251874 -3.419173 +v 16.8931866 0.381357998 -3.419173 +v 15.5693579 1.02586401 -3.419173 +v 10.8696461 3.06970406 -3.41917109 +v -1.21869302 8.25569725 -3.41916895 +v -8.14043808 11.0665941 -3.41916609 +v -12.1315041 12.1704216 -3.4191649 +v -12.3533545 12.1413603 -3.4191649 +v -13.2234211 11.6918497 -3.41916394 +v -13.3316479 11.5302191 -3.41916394 +v -13.4054632 11.3657866 -3.41916394 +v -13.5437584 10.1791248 -3.41916394 +v -13.5305204 9.92968464 -3.41916394 +v -10.2690344 -10.7624893 -3.41916203 +v -8.76296043 -13.5862312 -3.41916299 +v -8.30170155 -13.7132282 -3.41916299 +v -7.34092283 -13.6489267 -3.41916299 +v -6.44059992 -13.3229551 -3.41916299 +v -5.5646348 -12.8125639 -3.41916394 +v -3.74139309 -11.5475273 -3.4191649 +v -1.09378195 -9.77816391 -3.41916609 +v 0.088835001 -8.97351646 -3.41916609 +v 3.86536694 -6.19874001 -3.419168 +v 2.0629251 -2.64928699 -3.419168 +v -0.871262014 0.0925000012 -3.41916704 +v -3.137604 2.23444605 -3.41916609 +v -5.03022814 4.10011387 -3.41916609 +v -5.8782959 5.16451216 -3.41916704 +v -5.25391579 5.68733501 -3.41916704 +v 2.75399804 0.500262976 -3.419168 +v 7.67969704 -2.7854569 -3.4191699 +v 8.33005238 -3.11310506 -3.4191699 +v 8.95442486 -3.34368491 -3.4191699 +v 9.55281353 -3.47720098 -3.4191699 +v 11.721097 -3.26896405 -3.41917109 +v 12.6859941 -3.02756906 -3.41917109 +v 15.6278648 -2.12884498 -3.41917205 +v 16.5098476 -1.822873 -3.419173 +v 17.6083946 -1.086725 -3.419173 +v 17.7109451 -0.564249992 -2.79506302 +v 17.5940895 -0.251874 -2.79506302 +v 17.3215027 0.0633300021 -2.79506302 +v 17.3215027 0.0633300021 -3.419173 +v 16.8931866 0.381357998 -2.79506302 +v 16.3091373 0.702203989 -2.79506302 +v 16.3091373 0.702203989 -3.419173 +v 15.5693588 1.02586401 -2.79506302 +v 10.869647 3.06970406 -2.79506111 +v 6.84098577 4.81460619 -2.79505992 +v 6.84098577 4.81460619 -3.4191699 +v 3.48337412 6.260571 -2.79505897 +v 3.48337412 6.260571 -3.41916895 +v 0.796814024 7.40760183 -2.79505801 +v 0.796813011 7.40760088 -3.419168 +v -1.21869302 8.25569725 -2.79505897 +v -5.30144787 9.93825912 -2.79505706 +v -5.30144787 9.93825912 -3.41916704 +v -8.14043713 11.0665941 -2.7950561 +v -10.0118971 11.7457552 -2.7950561 +v -10.011898 11.7457552 -3.41916609 +v -11.1920586 12.0807896 -2.79505491 +v -11.1920605 12.0807896 -3.4191649 +v -11.95716 12.1767502 -2.79505491 +v -11.9571609 12.1767502 -3.4191649 +v -12.1315031 12.1704216 -2.79505491 +v -12.3533545 12.1413603 -2.79505491 +v -12.5995827 12.0820017 -2.79505491 +v -12.5995836 12.0820017 -3.4191649 +v -12.8470583 11.9847803 -2.79505491 +v -12.8470592 11.9847803 -3.4191649 +v -13.0726519 11.8421307 -2.79505491 +v -13.0726528 11.8421307 -3.4191649 +v -13.2234201 11.6918497 -2.79505396 +v -13.3316469 11.5302191 -2.79505396 +v -13.4054623 11.3657866 -2.79505396 +v -13.4529972 11.207099 -2.79505396 +v -13.4529982 11.207099 -3.41916394 +v -13.4823818 11.0627031 -2.79505396 +v -13.4823828 11.0627031 -3.41916394 +v -13.5057487 10.8971291 -2.79505396 +v -13.5057497 10.8971291 -3.41916394 +v -13.5268087 10.6819429 -2.79505396 +v -13.5268097 10.6819429 -3.41916394 +v -13.5409994 10.4362431 -2.79505396 +v -13.5410004 10.4362431 -3.41916394 +v -13.5437574 10.1791248 -2.79505396 +v -13.5305185 9.92968464 -2.79505396 +v -13.1165123 6.5194478 -2.79505301 +v -13.1165133 6.51944685 -3.41916299 +v -12.4699783 2.148 -2.79505301 +v -12.4699793 2.148 -3.41916299 +v -11.704299 -2.58754802 -2.79505301 +v -11.7042999 -2.58754802 -3.41916299 +v -10.9328585 -7.09008408 -2.79505301 +v -10.9328585 -7.09008503 -3.41916299 +v -10.2690334 -10.7624893 -2.79505205 +v -9.98333168 -11.8907843 -2.79505205 +v -9.98333263 -11.8907843 -3.41916203 +v -9.62376118 -12.7125072 -2.79505205 +v -9.62376213 -12.7125072 -3.41916203 +v -9.21030903 -13.2651577 -2.79505301 +v -9.21030998 -13.2651577 -3.41916299 +v -8.76295853 -13.5862312 -2.79505301 +v -8.30170059 -13.7132282 -2.79505301 +v -7.34092283 -13.6489267 -2.79505301 +v -6.44059992 -13.3229551 -2.79505396 +v -5.5646348 -12.8125639 -2.79505396 +v -4.67693186 -12.1950054 -2.79505396 +v -4.67693186 -12.1950054 -3.41916394 +v -3.74139309 -11.5475273 -2.79505491 +v -2.33644104 -10.6167526 -2.79505491 +v -2.33644104 -10.6167526 -3.4191649 +v -1.09378195 -9.77816391 -2.7950561 +v 0.088835001 -8.97351646 -2.7950561 +v 1.31366301 -8.14455986 -2.79505706 +v 1.31366301 -8.14455986 -3.41916609 +v 2.68295598 -7.23304987 -2.79505801 +v 2.68295598 -7.23304987 -3.419168 +v 3.86536694 -6.19874001 -2.79505801 +v 4.15901709 -5.29502392 -2.79505801 +v 4.15901709 -5.29502392 -3.419168 +v 3.79945707 -4.45008612 -2.79505801 +v 3.79945707 -4.45008707 -3.419168 +v 3.02224112 -3.59211206 -2.79505801 +v 3.02224112 -3.59211206 -3.419168 +v 2.0629251 -2.64928699 -2.79505801 +v 1.08888495 -1.73298895 -2.79505801 +v 1.08888495 -1.73298895 -3.419168 +v -0.871262014 0.0925000012 -2.79505706 +v -3.137604 2.23444605 -2.79505706 +v -5.03022814 4.10011482 -2.7950561 +v -5.86921978 5.0967741 -2.79505491 +v -5.86921978 5.0967741 -3.41916704 +v -5.8782959 5.16451216 -2.79505491 +v -5.87825298 5.26563597 -2.79505491 +v -5.87825298 5.26563501 -3.41916704 +v -5.86027908 5.38377285 -2.79505706 +v -5.86027908 5.3837719 -3.41916704 +v -5.81556082 5.50255489 -2.79505706 +v -5.81556082 5.50255394 -3.41916704 +v -5.73528481 5.60560894 -2.79505706 +v -5.73528481 5.60560799 -3.41916704 +v -5.62587309 5.67296314 -2.79505706 +v -5.62587309 5.67296314 -3.41916704 +v -5.50439787 5.70227385 -2.79505706 +v -5.50439787 5.70227289 -3.41916704 +v -5.37802505 5.70368481 -2.79505706 +v -5.37802505 5.70368385 -3.41916704 +v -5.25391579 5.68733501 -2.79505706 +v -5.13923788 5.66336489 -2.79505706 +v -5.13923788 5.66336393 -3.41916704 +v -4.53155804 5.31472492 -2.79505706 +v -4.53155804 5.31472397 -3.41916704 +v -3.01345992 4.33799601 -2.79505706 +v -3.01345992 4.33799601 -3.41916609 +v -0.58494103 2.73317504 -2.79505706 +v -0.58494103 2.73317504 -3.41916704 +v 2.75399804 0.500262976 -2.79505801 +v 7.00336218 -2.36073995 -2.79505897 +v 7.00336218 -2.36073995 -3.4191699 +v 7.67969704 -2.7854569 -2.79505992 +v 8.33005333 -3.11310506 -2.79505992 +v 8.95442581 -3.34368491 -2.79505992 +v 9.55281353 -3.47720098 -2.79505992 +v 10.1252203 -3.51365209 -2.79505992 +v 10.1252193 -3.51365209 -3.4191699 +v 10.8441992 -3.44087601 -2.79506111 +v 10.8441982 -3.44087601 -3.41917109 +v 11.7210979 -3.26896405 -2.79506111 +v 12.6859951 -3.02756906 -2.79506207 +v 13.6689644 -2.746346 -2.79506207 +v 13.6689634 -2.746346 -3.41917205 +v 14.6000843 -2.4549489 -2.79506207 +v 14.6000834 -2.4549489 -3.41917205 +v 15.6278658 -2.12884498 -2.79506207 +v 16.5098476 -1.822873 -2.79506302 +v 17.1890259 -1.49088299 -2.79506302 +v 17.1890259 -1.49088299 -3.419173 +v 17.6083946 -1.086725 -2.79506302 +v 0.105411001 16.7273979 1.11999905 +v 0.345261991 16.7147732 1.11999905 +v 0.581322014 16.6745682 1.11999905 +v 0.80707401 16.6032829 1.11999905 +v 1.01599801 16.4974174 1.11999905 +v 1.20157599 16.3534756 1.11999905 +v 1.32826197 16.2077866 1.11999905 +v 1.43035698 16.0346966 1.11999905 +v 1.50581396 15.8335915 1.11999905 +v 1.55258799 15.6038523 1.11999905 +v 1.56863403 15.3448668 1.11999905 +v 1.56863403 15.1605854 1.11999905 +v 1.56863403 14.7211456 1.11999905 +v 1.56863403 14.1966543 1.11999905 +v 1.56863403 13.7572136 1.11999905 +v 1.56863403 13.5729322 1.11999905 +v 3.15907192 13.3233585 1.11999905 +v 4.65419817 12.9316282 1.11999905 +v 6.02081823 12.3993158 1.11999905 +v 7.22574186 11.727994 1.11999905 +v 8.23577785 10.91924 1.11999905 +v 8.80188179 10.3189545 1.11999905 +v 9.25199318 9.70048046 1.11999905 +v 9.58047676 9.07110405 1.11999905 +v 9.78169918 8.43811131 1.11999905 +v 9.85002518 7.80878496 1.11999905 +v 9.82569885 7.49253893 1.11999905 +v 9.75514603 7.19792891 1.11999905 +v 9.64200401 6.92755795 1.12 +v 9.48991108 6.68403101 1.12 +v 9.30250454 6.46994686 1.12 +v 9.07253456 6.27994108 1.12 +v 8.81319046 6.12785816 1.12 +v 8.52901745 6.01616812 1.12 +v 8.22456837 5.94734097 1.12 +v 7.90438986 5.92385101 1.12 +v 7.33121204 5.96799612 1.12 +v 6.79715014 6.10252285 1.12 +v 6.28231716 6.33056688 1.12 +v 5.76682281 6.65526295 1.12 +v 5.23078012 7.0797472 1.12 +v 4.18395901 7.97779608 1.11999905 +v 3.16072512 8.64487267 1.11999905 +v 2.12022305 9.09902 1.11999905 +v 1.02160001 9.35827827 1.11999905 +v -0.175996006 9.44068909 1.11999905 +v -0.900669992 9.39911556 1.11999905 +v -1.56719804 9.27902508 1.11999905 +v -2.17204404 9.08736229 1.11999905 +v -2.71166801 8.83106995 1.11999905 +v -3.18253303 8.51709366 1.11999905 +v -3.61706591 8.11411953 1.11999905 +v -3.96249294 7.65745401 1.11999905 +v -4.21453094 7.15459919 1.11999905 +v -4.36889505 6.6130538 1.12 +v -4.42130184 6.04031801 1.12 +v -4.38049316 5.53547812 1.12 +v -4.25691414 5.067801 1.12 +v -4.04883623 4.63680696 1.12 +v -3.7545321 4.24201298 1.12 +v -3.37227297 3.882936 1.12 +v -2.94513392 3.58780408 1.12 +v -2.43714499 3.32225204 1.12 +v -1.846573 3.08675098 1.12 +v -1.17168498 2.88177204 1.12 +v -0.410748988 2.70778799 1.12 +v -0.0152270002 2.62619209 1.12 +v 0.927940011 2.43161702 1.12 +v 2.0536561 2.19938207 1.12 +v 2.99682403 2.004807 1.12 +v 3.39234495 1.92321098 1.12 +v 4.78504276 1.59496701 1.12 +v 6.03043413 1.17929602 1.12 +v 7.12831688 0.674637973 1.12 +v 8.0784893 0.0794320032 1.12 +v 8.88075066 -0.607885003 1.12 +v 9.52265453 -1.37318397 1.11999905 +v 10.019618 -2.22838807 1.11999905 +v 10.3729563 -3.17356706 1.11999905 +v 10.5839901 -4.20879507 1.11999905 +v 10.6540413 -5.33414078 1.11999905 +v 10.5555544 -6.651227 1.12 +v 10.2652121 -7.87847614 1.12 +v 9.79068756 -9.00913429 1.12 +v 9.13966084 -10.0364475 1.12 +v 8.3198061 -10.9536591 1.12 +v 7.27590084 -11.7985697 1.12 +v 6.06304693 -12.5051193 1.12 +v 4.69099712 -13.0665941 1.12 +v 3.16950703 -13.4762774 1.12 +v 1.50833201 -13.7274532 1.12 +v 1.50833201 -13.8932819 1.12 +v 1.50833201 -14.2887201 1.12 +v 1.50833201 -14.7606926 1.12 +v 1.50833201 -15.1561298 1.12 +v 1.50833201 -15.3219585 1.12 +v 1.49228597 -15.5809469 1.12 +v 1.44551206 -15.8106861 1.12 +v 1.37005496 -16.0117931 1.12 +v 1.26795995 -16.1848812 1.12 +v 1.14127398 -16.3305683 1.12 +v 0.955695987 -16.4745121 1.12 +v 0.746771991 -16.5803776 1.12 +v 0.521021008 -16.6516628 1.12 +v 0.284960002 -16.6918678 1.12 +v 0.0451090001 -16.7044907 1.12 +v -0.196757004 -16.6917133 1.12 +v -0.435656011 -16.6510601 1.12 +v -0.664767027 -16.5790424 1.12 +v -0.87727201 -16.4721718 1.12 +v -1.06634903 -16.3269634 1.12 +v -1.19433606 -16.1816311 1.12 +v -1.29772401 -16.0090923 1.12 +v -1.37430406 -15.8087206 1.12 +v -1.42186999 -15.5798845 1.12 +v -1.43821597 -15.3219585 1.12 +v -1.43821597 -15.1592388 1.12 +v -1.43821597 -14.7712126 1.12 +v -1.43821597 -14.3080854 1.12 +v -1.43821597 -13.9200583 1.12 +v -1.43821597 -13.7573366 1.12 +v -3.25013494 -13.5464516 1.12 +v -4.94489288 -13.1700163 1.12 +v -6.48735285 -12.6305323 1.12 +v -7.8423748 -11.9305029 1.12 +v -8.97482014 -11.0724287 1.12 +v -9.54416466 -10.4921713 1.12 +v -10.0029211 -9.88578606 1.12 +v -10.3419342 -9.25695324 1.12 +v -10.5520515 -8.60934734 1.12 +v -10.6241264 -7.94664907 1.12 +v -10.5984468 -7.57830191 1.12 +v -10.5235186 -7.24243593 1.12 +v -10.4025087 -6.9403491 1.12 +v -10.2385826 -6.67333794 1.12 +v -10.0349073 -6.44269991 1.12 +v -9.77230453 -6.23402596 1.12 +v -9.47389889 -6.0711832 1.12 +v -9.14502144 -5.95448303 1.12 +v -8.79100513 -5.88423491 1.12 +v -8.41718483 -5.86075115 1.12 +v -7.94380999 -5.89670706 1.12 +v -7.48150587 -6.01501799 1.12 +v -7.00411797 -6.23135185 1.12 +v -6.48548985 -6.56137419 1.12 +v -5.8994689 -7.020751 1.12 +v -4.7603302 -7.93607521 1.12 +v -3.58193588 -8.65157413 1.12 +v -2.37061095 -9.16519165 1.12 +v -1.13268697 -9.47486973 1.12 +v 0.125511006 -9.57855225 1.12 +v 0.977401972 -9.53865528 1.12 +v 1.74607205 -9.42228222 1.12 +v 2.43071294 -9.23440647 1.12 +v 3.03051305 -8.9800024 1.12 +v 3.54466105 -8.66404533 1.12 +v 4.01046419 -8.25353336 1.12 +v 4.37620306 -7.77952099 1.12 +v 4.63989687 -7.2461009 1.12 +v 4.79956579 -6.65736723 1.12 +v 4.85323 -6.01741123 1.12 +v 4.81260395 -5.51269817 1.11999905 +v 4.68770695 -5.04885292 1.11999905 +v 4.47400713 -4.62345886 1.11999905 +v 4.16697311 -4.23409986 1.11999905 +v 3.76207304 -3.87836099 1.11999905 +v 3.30957389 -3.58684206 1.11999905 +v 2.76379991 -3.31911993 1.11999905 +v 2.12057805 -3.0738759 1.11999905 +v 1.37573099 -2.8497901 1.11999905 +v 0.525086999 -2.64554191 1.11999905 +v 0.131674007 -2.5597651 1.11999905 +v -0.806464016 -2.35521889 1.11999905 +v -1.92617798 -2.11108398 1.11999905 +v -2.86431694 -1.90653896 1.11999905 +v -3.25773001 -1.82076204 1.11999905 +v -4.59268618 -1.48558605 1.11999905 +v -5.7895298 -1.05978703 1.12 +v -6.84740305 -0.543057024 1.12 +v -7.76544523 0.0649129972 1.12 +v -8.54279613 0.764429986 1.12 +v -9.17054462 1.54515696 1.12 +v -9.65782356 2.41389799 1.12 +v -10.0051918 3.36962509 1.12 +v -10.2132044 4.4113059 1.12 +v -10.2824183 5.53791189 1.12 +v -10.1845474 6.82223701 1.12 +v -9.89668941 8.02005672 1.11999905 +v -9.4274807 9.12346935 1.11999905 +v -8.78555202 10.1245785 1.11999905 +v -7.97953701 11.0154829 1.11999905 +v -6.95675898 11.8308792 1.11999905 +v -5.77239084 12.5051966 1.11999905 +v -4.43723917 13.0303583 1.11999905 +v -2.962111 13.3982849 1.11999905 +v -1.35781395 13.6008978 1.11999905 +v -1.35781395 13.7822704 1.11999905 +v -1.35781395 14.214776 1.11999905 +v -1.35781395 14.7309904 1.11999905 +v -1.35781395 15.1634951 1.11999905 +v -1.35781395 15.3448668 1.11999905 +v -1.34199095 15.6066008 1.11999905 +v -1.29579496 15.8380814 1.11999905 +v -1.22113299 16.0400581 1.11999905 +v -1.11991405 16.2132854 1.11999905 +v -0.994045973 16.3585148 1.11999905 +v -0.808125019 16.5020428 1.11999905 +v -0.59859401 16.6066704 1.11999905 +v -0.372078001 16.676424 1.11999905 +v -0.135201007 16.7153244 1.11999905 +v 0.105411001 16.7273979 0.479999989 +v 0.345261991 16.7147732 0.479999989 +v 0.581322014 16.6745682 0.479999989 +v 0.80707401 16.6032829 0.479999989 +v 1.01599801 16.4974174 0.479999989 +v 1.20157599 16.3534756 0.479999989 +v 1.32826197 16.2077866 0.479999989 +v 1.43035698 16.0346966 0.479999989 +v 1.50581396 15.8335915 0.479999989 +v 1.55258799 15.6038523 0.479999989 +v 1.56863403 15.3448668 0.479999989 +v 1.56863403 15.1605854 0.479999989 +v 1.56863403 14.7211456 0.479999989 +v 1.56863403 14.1966543 0.479999989 +v 1.56863403 13.7572136 0.479999989 +v 1.56863403 13.5729322 0.479999989 +v 3.15907192 13.3233585 0.479999989 +v 4.65419817 12.9316282 0.479999989 +v 6.02081823 12.3993158 0.479999989 +v 7.22574186 11.727994 0.479999989 +v 8.23577785 10.91924 0.479999989 +v 8.80188179 10.3189545 0.479999989 +v 9.25199318 9.70048046 0.479999989 +v 9.58047676 9.07110405 0.479999006 +v 9.78169918 8.43811131 0.479999006 +v 9.85002518 7.80878496 0.479999006 +v 9.82569885 7.49253893 0.479999006 +v 9.75514603 7.19792891 0.479999006 +v 9.64200401 6.92755795 0.479999006 +v 9.48991108 6.68403101 0.479999006 +v 9.30250454 6.46994686 0.479999989 +v 9.07253456 6.27994108 0.479999989 +v 8.81319046 6.12785816 0.479999989 +v 8.52901745 6.01616812 0.479999989 +v 8.22456837 5.94734097 0.479999989 +v 7.90438986 5.92385101 0.479999989 +v 7.33121204 5.96799612 0.479999989 +v 6.79715014 6.10252285 0.479999989 +v 6.28231716 6.33056688 0.479999989 +v 5.76682281 6.65526295 0.479999006 +v 5.23078012 7.0797472 0.479999006 +v 4.18395901 7.97779608 0.479999006 +v 3.16072512 8.64487267 0.479999006 +v 2.12022305 9.09902 0.479999006 +v 1.02160001 9.35827827 0.479999989 +v -0.175996006 9.44068909 0.479999989 +v -0.900669992 9.39911556 0.479999989 +v -1.56719804 9.27902508 0.479999989 +v -2.17204404 9.08736229 0.479999006 +v -2.71166801 8.83106995 0.479999006 +v -3.18253303 8.51709366 0.479999006 +v -3.61706591 8.11411953 0.479999006 +v -3.96249294 7.65745401 0.479999006 +v -4.21453094 7.15459919 0.479999006 +v -4.36889505 6.6130538 0.479999006 +v -4.42130184 6.04031801 0.479999989 +v -4.38049316 5.53547812 0.479999989 +v -4.25691414 5.067801 0.479999989 +v -4.04883623 4.63680696 0.479999989 +v -3.7545321 4.24201298 0.479999989 +v -3.37227297 3.882936 0.479999989 +v -2.94513392 3.58780408 0.479999989 +v -2.43714499 3.32225204 0.479999989 +v -1.846573 3.08675098 0.479999989 +v -1.17168498 2.88177204 0.479999989 +v -0.410748988 2.70778799 0.479999989 +v -0.0152270002 2.62619209 0.479999989 +v 0.927940011 2.43161702 0.479999989 +v 2.0536561 2.19938207 0.479999989 +v 2.99682403 2.004807 0.479999989 +v 3.39234495 1.92321098 0.479999989 +v 4.78504276 1.59496701 0.479999989 +v 6.03043413 1.17929602 0.479999989 +v 7.12831688 0.674637973 0.479999989 +v 8.0784893 0.0794320032 0.479999989 +v 8.88075066 -0.607885003 0.479999989 +v 9.52265453 -1.37318397 0.479999989 +v 10.019618 -2.22838807 0.479999989 +v 10.3729563 -3.17356706 0.479999989 +v 10.5839901 -4.20879507 0.479999989 +v 10.6540413 -5.33414078 0.479999989 +v 10.5555544 -6.651227 0.479999989 +v 10.2652121 -7.87847614 0.480001003 +v 9.79068756 -9.00913429 0.480001003 +v 9.13966084 -10.0364475 0.480001003 +v 8.3198061 -10.9536591 0.480001003 +v 7.27590084 -11.7985697 0.480001003 +v 6.06304693 -12.5051193 0.480001003 +v 4.69099712 -13.0665941 0.480001003 +v 3.16950703 -13.4762774 0.480001003 +v 1.50833201 -13.7274532 0.480001003 +v 1.50833201 -13.8932819 0.480001003 +v 1.50833201 -14.2887201 0.479999989 +v 1.50833201 -14.7606926 0.479999989 +v 1.50833201 -15.1561298 0.479999989 +v 1.50833201 -15.3219585 0.479999989 +v 1.49228597 -15.5809469 0.479999989 +v 1.44551206 -15.8106861 0.479999989 +v 1.37005496 -16.0117931 0.479999989 +v 1.26795995 -16.1848812 0.479999989 +v 1.14127398 -16.3305683 0.479999989 +v 0.955695987 -16.4745121 0.479999989 +v 0.746771991 -16.5803776 0.479999989 +v 0.521021008 -16.6516628 0.479999989 +v 0.284960002 -16.6918678 0.479999989 +v 0.0451090001 -16.7044907 0.479999989 +v -0.196757004 -16.6917133 0.479999989 +v -0.435656011 -16.6510601 0.479999989 +v -0.664767027 -16.5790424 0.479999989 +v -0.87727201 -16.4721718 0.479999989 +v -1.06634903 -16.3269634 0.479999989 +v -1.19433606 -16.1816311 0.479999989 +v -1.29772401 -16.0090923 0.479999989 +v -1.37430406 -15.8087206 0.479999989 +v -1.42186999 -15.5798845 0.479999989 +v -1.43821597 -15.3219585 0.479999989 +v -1.43821597 -15.1592388 0.479999989 +v -1.43821597 -14.7712126 0.479999989 +v -1.43821597 -14.3080854 0.479999989 +v -1.43821597 -13.9200583 0.480001003 +v -1.43821597 -13.7573366 0.480001003 +v -3.25013494 -13.5464516 0.480001003 +v -4.94489288 -13.1700163 0.480001003 +v -6.48735285 -12.6305323 0.480001003 +v -7.8423748 -11.9305029 0.480001003 +v -8.97482014 -11.0724287 0.480001003 +v -9.54416466 -10.4921713 0.480001003 +v -10.0029211 -9.88578606 0.480001003 +v -10.3419342 -9.25695324 0.480001003 +v -10.5520515 -8.60934734 0.480001003 +v -10.6241264 -7.94664907 0.480001003 +v -10.5984468 -7.57830191 0.480001003 +v -10.5235186 -7.24243593 0.480001003 +v -10.4025087 -6.9403491 0.480001003 +v -10.2385826 -6.67333794 0.479999989 +v -10.0349073 -6.44269991 0.479999989 +v -9.77230453 -6.23402596 0.479999989 +v -9.47389889 -6.0711832 0.479999989 +v -9.14502144 -5.95448303 0.479999989 +v -8.79100513 -5.88423491 0.479999989 +v -8.41718483 -5.86075115 0.479999989 +v -7.94380999 -5.89670706 0.479999989 +v -7.48150587 -6.01501799 0.479999989 +v -7.00411797 -6.23135185 0.479999989 +v -6.48548985 -6.56137419 0.479999989 +v -5.8994689 -7.020751 0.480001003 +v -4.7603302 -7.93607521 0.480001003 +v -3.58193588 -8.65157413 0.480001003 +v -2.37061095 -9.16519165 0.480001003 +v -1.13268697 -9.47486973 0.480001003 +v 0.125511006 -9.57855225 0.480001003 +v 0.977401972 -9.53865528 0.480001003 +v 1.74607205 -9.42228222 0.480001003 +v 2.43071294 -9.23440647 0.480001003 +v 3.03051305 -8.9800024 0.480001003 +v 3.54466105 -8.66404533 0.480001003 +v 4.01046419 -8.25353336 0.480001003 +v 4.37620306 -7.77952099 0.480001003 +v 4.63989687 -7.2461009 0.480001003 +v 4.79956579 -6.65736723 0.479999989 +v 4.85323 -6.01741123 0.479999989 +v 4.81260395 -5.51269817 0.479999989 +v 4.68770695 -5.04885292 0.479999989 +v 4.47400713 -4.62345886 0.479999989 +v 4.16697311 -4.23409986 0.479999989 +v 3.76207304 -3.87836099 0.479999989 +v 3.30957389 -3.58684206 0.479999989 +v 2.76379991 -3.31911993 0.479999989 +v 2.12057805 -3.0738759 0.479999989 +v 1.37573099 -2.8497901 0.479999989 +v 0.525086999 -2.64554191 0.479999989 +v 0.131674007 -2.5597651 0.479999989 +v -0.806464016 -2.35521889 0.479999989 +v -1.92617798 -2.11108398 0.479999989 +v -2.86431694 -1.90653896 0.479999989 +v -3.25773001 -1.82076204 0.479999989 +v -4.59268618 -1.48558605 0.479999989 +v -5.7895298 -1.05978703 0.479999989 +v -6.84740305 -0.543056011 0.479999989 +v -7.76544523 0.0649129972 0.479999989 +v -8.54279613 0.764429986 0.479999989 +v -9.17054462 1.54515696 0.479999989 +v -9.65782356 2.41389799 0.479999989 +v -10.0051918 3.36962509 0.479999989 +v -10.2132044 4.4113059 0.479999989 +v -10.2824183 5.53791189 0.479999989 +v -10.1845474 6.82223701 0.479999006 +v -9.89668941 8.02005672 0.479999006 +v -9.4274807 9.12346935 0.479999006 +v -8.78555202 10.1245785 0.479999989 +v -7.97953701 11.0154829 0.479999989 +v -6.95675898 11.8308792 0.479999989 +v -5.77239084 12.5051966 0.479999989 +v -4.43723917 13.0303583 0.479999989 +v -2.962111 13.3982849 0.479999989 +v -1.35781395 13.6008978 0.479999989 +v -1.35781395 13.7822704 0.479999989 +v -1.35781395 14.214776 0.479999989 +v -1.35781395 14.7309904 0.479999989 +v -1.35781395 15.1634951 0.479999989 +v -1.35781395 15.3448668 0.479999989 +v -1.34199095 15.6066008 0.479999989 +v -1.29579496 15.8380814 0.479999989 +v -1.22113299 16.0400581 0.479999989 +v -1.11991405 16.2132854 0.479999989 +v -0.994045973 16.3585148 0.479999989 +v -0.808125019 16.5020428 0.479999989 +v -0.59859401 16.6066704 0.479999989 +v -0.372078001 16.676424 0.479999989 +v -0.135201007 16.7153244 0.479999989 +vt 0.906646013 0.888722003 +vt 0.918564022 0.889859021 +vt 0.916351974 0.88993901 +vt 0.921378016 0.889490008 +vt 0.924502015 0.888737023 +vt 0.927641988 0.887503982 +vt 0.891673982 0.884472013 +vt 0.930503011 0.885694027 +vt 0.932416022 0.88378799 +vt 0.867932975 0.875855982 +vt 0.933789015 0.881736994 +vt 0.934724987 0.87965101 +vt 0.935328007 0.877637982 +vt 0.935701013 0.875805974 +vt 0.831918001 0.861541986 +vt 0.935998023 0.873705983 +vt 0.936264992 0.870975971 +vt 0.936444998 0.867859006 +vt 0.936479986 0.864597023 +vt 0.93631202 0.861433029 +vt 0.780124009 0.840197027 +vt 0.931060016 0.818170011 +vt 0.754554987 0.829437971 +vt 0.720472991 0.814885974 +vt 0.834492028 0.807803988 +vt 0.836032987 0.807431996 +vt 0.922858 0.762714028 +vt 0.677878022 0.796543002 +vt 0.83288902 0.807821989 +vt 0.831314981 0.807614028 +vt 0.829859972 0.807309985 +vt 0.837421 0.806577981 +vt 0.822151005 0.802887022 +vt 0.838440001 0.805270016 +vt 0.83900702 0.803762972 +vt 0.839235008 0.802264988 +vt 0.802892029 0.790497005 +vt 0.839235008 0.800981998 +vt 0.839119971 0.800122023 +vt 0.828477025 0.787478983 +vt 0.62677002 0.774407029 +vt 0.772083998 0.770138025 +vt 0.804467022 0.763810992 +vt 0.567149997 0.748479009 +vt 0.729726017 0.741810977 +vt 0.775716007 0.73663801 +vt 0.913143992 0.702638984 +vt 0.557765007 0.744373024 +vt 0.550355017 0.740302026 +vt 0.675818026 0.705515981 +vt 0.544921994 0.736267984 +vt 0.750849009 0.713479996 +vt 0.541463971 0.732268989 +vt 0.539981008 0.728305995 +vt 0.541281998 0.721678019 +vt 0.546602011 0.716551006 +vt 0.555218995 0.712338984 +vt 0.738493025 0.701856017 +vt 0.566407025 0.708458006 +vt 0.579446018 0.704321027 +vt 0.667237997 0.700128019 +vt 0.591257989 0.700623989 +vt 0.903357983 0.645519018 +vt 0.726323009 0.689894974 +vt 0.603727996 0.697057009 +vt 0.658987999 0.695972025 +vt 0.615969002 0.693993986 +vt 0.651067019 0.693045974 +vt 0.627093017 0.691812992 +vt 0.643476009 0.691353023 +vt 0.636214018 0.690890014 +vt 0.716463029 0.679010987 +vt 0.711902022 0.668291986 +vt 0.715627015 0.656826973 +vt 0.730627 0.643706024 +vt 0.894936979 0.598931015 +vt 0.747997999 0.632142007 +vt 0.763535976 0.621626019 +vt 0.778539002 0.611418009 +vt 0.794303 0.60078001 +vt 0.812126994 0.588971972 +vt 0.891312003 0.584617019 +vt 0.823994994 0.580757976 +vt 0.886750996 0.574193001 +vt 0.83525598 0.572924018 +vt 0.881506026 0.567182004 +vt 0.846369028 0.566448987 +vt 0.875829995 0.563108981 +vt 0.857789993 0.562313974 +vt 0.869979024 0.561497986 +vt 0.541463971 0.732268989 +vt 0.567149997 0.748479009 +vt 0.754554987 0.829437971 +vt 0.906646013 0.888722003 +vt 0.936444998 0.867859006 +vt 0.922858 0.762714028 +vt 0.886750996 0.574193001 +vt 0.875829995 0.563108981 +vt 0.836032987 0.807431996 +vt 0.834492028 0.807803988 +vt 0.615969002 0.693993986 +vt 0.243977994 0.465353012 +vt 0.250073999 0.465346009 +vt 0.247031003 0.465505987 +vt 0.240972996 0.464859009 +vt 0.253068 0.464836001 +vt 0.238100007 0.463973999 +vt 0.255932003 0.463930994 +vt 0.235441998 0.462646991 +vt 0.258583009 0.462588012 +vt 0.233082995 0.46082601 +vt 0.260937005 0.460761994 +vt 0.231485993 0.458983988 +vt 0.262544006 0.458914012 +vt 0.230202004 0.456786007 +vt 0.263839006 0.456717998 +vt 0.229255006 0.45422399 +vt 0.264795989 0.454167008 +vt 0.228669003 0.451287001 +vt 0.265390009 0.451252013 +vt 0.228468001 0.447966993 +vt 0.265592992 0.447966993 +vt 0.228468001 0.445665985 +vt 0.265592992 0.445629001 +vt 0.228468001 0.44017899 +vt 0.265592992 0.440053999 +vt 0.228468001 0.433631003 +vt 0.265592992 0.433400989 +vt 0.228468001 0.428144008 +vt 0.265592992 0.427825987 +vt 0.228468001 0.425843 +vt 0.265592992 0.425487995 +vt 0.208115995 0.423272997 +vt 0.285769999 0.422322005 +vt 0.189402997 0.418605 +vt 0.304737002 0.417351991 +vt 0.172464997 0.411942989 +vt 0.322073996 0.410600007 +vt 0.157440007 0.403387994 +vt 0.337359995 0.402083009 +vt 0.144464999 0.393043995 +vt 0.350172997 0.391822994 +vt 0.134240001 0.381742001 +vt 0.357354999 0.384207994 +vt 0.363065004 0.376361996 +vt 0.126095995 0.369042009 +vt 0.243460998 0.373066008 +vt 0.367231995 0.368378013 +vt 0.234267995 0.372539014 +vt 0.258653998 0.37202099 +vt 0.225812003 0.371015012 +vt 0.272590995 0.368732005 +vt 0.218138993 0.368584007 +vt 0.120144002 0.355044007 +vt 0.28579101 0.362971008 +vt 0.211292997 0.365332991 +vt 0.369785011 0.360347986 +vt 0.205320001 0.36135 +vt 0.298772007 0.354508013 +vt 0.199807003 0.356236994 +vt 0.370651007 0.352364004 +vt 0.195425004 0.350443989 +vt 0.116492003 0.339848995 +vt 0.312052011 0.343115002 +vt 0.370343 0.348351985 +vt 0.192228004 0.344065011 +vt 0.369448006 0.344615012 +vt 0.368012011 0.341185004 +vt 0.190270007 0.337195009 +vt 0.318852007 0.33772999 +vt 0.366082996 0.338095009 +vt 0.115249999 0.323554993 +vt 0.363705993 0.335379004 +vt 0.325390995 0.333611012 +vt 0.189604998 0.329928994 +vt 0.360787988 0.33296901 +vt 0.331923008 0.330718011 +vt 0.35749799 0.331039995 +vt 0.353893012 0.329623014 +vt 0.338698 0.329012007 +vt 0.190123007 0.323525012 +vt 0.350030988 0.328750014 +vt 0.345968992 0.328451991 +vt 0.116129003 0.309262991 +vt 0.191689998 0.317591995 +vt 0.194330007 0.312124014 +vt 0.198063999 0.307116002 +vt 0.118767001 0.296048999 +vt 0.202913001 0.302560002 +vt 0.208332002 0.298815995 +vt 0.214775994 0.295448005 +vt 0.123173997 0.283924013 +vt 0.222268 0.292459995 +vt 0.230829999 0.28986001 +vt 0.240483001 0.287651986 +vt 0.245499998 0.286617011 +vt 0.257465988 0.284148991 +vt 0.27174601 0.281203002 +vt 0.129355997 0.272902995 +vt 0.283710986 0.278733999 +vt 0.288729012 0.277698994 +vt 0.306396991 0.273535013 +vt 0.322196007 0.268261999 +vt 0.137318999 0.262998998 +vt 0.336124003 0.261860013 +vt 0.147181004 0.254124999 +vt 0.348177999 0.254308999 +vt 0.358354986 0.245590001 +vt 0.158827007 0.246411994 +vt 0.172246993 0.239857003 +vt 0.366497993 0.235881001 +vt 0.187430993 0.234455004 +vt 0.372803003 0.225032002 +vt 0.204365999 0.230203003 +vt 0.209356993 0.229114994 +vt 0.221258 0.226520002 +vt 0.235462993 0.223423004 +vt 0.377285004 0.213040993 +vt 0.247363999 0.220827997 +vt 0.252355009 0.219740003 +vt 0.263146013 0.217149004 +vt 0.272594988 0.214305997 +vt 0.280755013 0.211195007 +vt 0.379963011 0.199908003 +vt 0.287678987 0.207799003 +vt 0.293419003 0.204099998 +vt 0.298556 0.199587002 +vt 0.380851001 0.185632005 +vt 0.302451015 0.194647998 +vt 0.305162013 0.189251006 +vt 0.306746989 0.183366999 +vt 0.379601985 0.168924004 +vt 0.307262003 0.176964 +vt 0.134170994 0.178654 +vt 0.144917995 0.178495005 +vt 0.138913006 0.178951994 +vt 0.129679993 0.177763 +vt 0.150783002 0.176994994 +vt 0.125506997 0.176282004 +vt 0.156838998 0.174250007 +vt 0.306580991 0.168845996 +vt 0.121721998 0.174216002 +vt 0.163417995 0.170063004 +vt 0.118390001 0.171569005 +vt 0.115806997 0.168642998 +vt 0.170853004 0.164235994 +vt 0.375919014 0.153355002 +vt 0.304556012 0.161376998 +vt 0.113727003 0.165255994 +vt 0.112191997 0.161423996 +vt 0.185304001 0.152623996 +vt 0.111240998 0.157162994 +vt 0.301209986 0.154609993 +vt 0.110916004 0.152490005 +vt 0.296570987 0.148597002 +vt 0.369899005 0.139010996 +vt 0.200252995 0.143546999 +vt 0.111830004 0.144082993 +vt 0.290661007 0.143389001 +vt 0.114495002 0.135867 +vt 0.215619996 0.137031004 +vt 0.284139007 0.139381006 +vt 0.276529998 0.136152998 +vt 0.361640006 0.125979006 +vt 0.231324002 0.133102998 +vt 0.267843992 0.133770004 +vt 0.118795998 0.127890006 +vt 0.258092999 0.132293999 +vt 0.247286007 0.131787002 +vt 0.124615997 0.120196998 +vt 0.351238996 0.114343002 +vt 0.131839007 0.112836003 +vt 0.337996006 0.103624001 +vt 0.146204993 0.101949997 +vt 0.322609991 0.0946609974 +vt 0.163395002 0.0930700004 +vt 0.305204004 0.0875379965 +vt 0.182962999 0.0862260014 +vt 0.285901994 0.0823410004 +vt 0.204462007 0.0814500004 +vt 0.264827996 0.0791539997 +vt 0.227448002 0.0787750036 +vt 0.264827996 0.0770509988 +vt 0.227448002 0.076710999 +vt 0.264827996 0.0720340014 +vt 0.227448002 0.0717879981 +vt 0.264827996 0.0660469979 +vt 0.227448002 0.0659129992 +vt 0.264827996 0.0610300004 +vt 0.227448002 0.0609910004 +vt 0.264827996 0.0589260012 +vt 0.227448002 0.0589260012 +vt 0.227656007 0.0556540005 +vt 0.264625013 0.0556409992 +vt 0.228258997 0.052751001 +vt 0.264030993 0.0527260005 +vt 0.229231 0.0502090007 +vt 0.263074011 0.050175 +vt 0.230542004 0.0480200015 +vt 0.26177901 0.047979001 +vt 0.232166007 0.0461769998 +vt 0.260172009 0.0461309999 +vt 0.234565005 0.0443350002 +vt 0.257818013 0.0443050005 +vt 0.237259999 0.0429789983 +vt 0.255167007 0.0429619998 +vt 0.240167007 0.0420649983 +vt 0.252303004 0.0420579985 +vt 0.243198007 0.0415499993 +vt 0.249309003 0.0415479988 +vt 0.246266007 0.0413869992 +vt 0.202913001 0.302560002 +vt 0.208332002 0.298815995 +vt 0.240483001 0.287651986 +vt 0.214775994 0.295448005 +vt 0.209356993 0.229114994 +vt 0.204365999 0.230203003 +vt 0.147181004 0.254124999 +vn -0 -0 -1 +vn 0.638899982 0.379099995 -0.669399977 +vn 0.996599972 0.0820000023 -0 +vn 0.756799996 0.0623000003 -0.650699973 +vn 0.496699989 0.535099983 -0.683300018 +vn 0.860000014 0.510299981 -0 +vn 0.389800012 0.607500017 -0.692099988 +vn 0.680299997 0.732900023 -0 +vn 0.316599995 0.643299997 -0.697099984 +vn 0.540099978 0.841600001 -0 +vn 0.282700002 0.648400009 -0.706900001 +vn 0.441599995 0.897199988 -0 +vn 0.281599998 0.648800015 -0.706900001 +vn 0.399800003 0.916599989 -0 +vn 0.282799989 0.648400009 -0.706900001 +vn 0.280499995 0.649399996 -0.706900001 +vn 0.398000002 0.917400002 -0 +vn 0.278800011 0.650300026 -0.706700027 +vn 0.396400005 0.918099999 -0 +vn 0.276199996 0.651600003 -0.706499994 +vn 0.394199997 0.91900003 -0 +vn 0.278800011 0.650200009 -0.706700027 +vn 0.272199988 0.653500021 -0.70630002 +vn 0.390300006 0.920700014 -0 +vn 0.265799999 0.656799972 -0.70569998 +vn 0.38440001 0.923200011 -0 +vn 0.355300009 0.934800029 -0 +vn 0.375200003 0.926900029 -0 +vn 0.252400011 0.664099991 -0.703700006 +vn 0.307300001 0.951600015 -0 +vn 0.219699994 0.680400014 -0.699100018 +vn 0.199300006 0.979900002 -0 +vn 0.144199997 0.708899975 -0.690400004 +vn 0.0441999994 0.999000013 -0 +vn 0.0320000015 0.723699987 -0.689400017 +vn -0.0832000002 0.996500015 -0 +vn -0.059700001 0.714900017 -0.696699977 +vn -0.182400003 0.983200014 -0 +vn -0.131099999 0.70660001 -0.695299983 +vn -0.300700009 0.953700006 -0 +vn -0.217099994 0.688600004 -0.691900015 +vn -0.452100009 0.89200002 -0 +vn -0.328799993 0.648699999 -0.686399996 +vn -0.624000013 0.781400025 -0 +vn -0.562099993 0.462599993 -0.685599983 +vn -0.455599993 0.570599973 -0.683200002 +vn -0.87470001 0.484699994 -0 +vn -0.772199988 0.635399997 -0 +vn -0.675899982 0.251700014 -0.692700028 +vn -0.634199977 0.351399988 -0.68870002 +vn -0.6954 0.174500003 -0.697099984 +vn -0.937099993 0.349000007 -0 +vn -0.703400016 0.121100001 -0.700399995 +vn -0.969900012 0.243399993 -0 +vn -0.6954 0.174600005 -0.697099984 +vn -0.992900014 0.118600003 -0 +vn -0.985499978 0.169699997 -0 +vn -0.709399998 0.0551999994 -0.702600002 +vn -0.706799984 0.0843999982 -0.702300012 +vn -0.99940002 0.0342000015 -0 +vn -0.996999979 0.0775000006 -0 +vn -0.713999987 -0.0152000003 -0.699999988 +vn -0.711899996 0.0243999995 -0.701900005 +vn -0.711899996 -0.061999999 -0.699599981 +vn -0.999800026 -0.0210999995 -0 +vn -0.713999987 -0.0151000004 -0.699999988 +vn -0.703700006 -0.0947000012 -0.704200029 +vn -0.996200025 -0.0868000016 -0 +vn -0.700299978 -0.108400002 -0.705600023 +vn -0.991100013 -0.133399993 -0 +vn -0.698499978 -0.116300002 -0.706099987 +vn -0.988200009 -0.152999997 -0 +vn -0.697399974 -0.1228 -0.706099987 +vn -0.986400008 -0.164199993 -0 +vn -0.698499978 -0.1514 -0.699400008 +vn -0.984899998 -0.1734 -0 +vn -0.685500026 -0.234999999 -0.689100027 +vn -0.977299988 -0.211799994 -0 +vn -0.631799996 -0.368000001 -0.682200015 +vn -0.94599998 -0.324299991 -0 +vn -0.700100005 -0.714100003 -0 +vn -0.864099979 -0.503300011 -0 +vn -0.320100009 -0.67019999 -0.66960001 +vn -0.516900003 -0.5273 -0.674399972 +vn -0.0746999979 -0.737299979 -0.671400011 +vn -0.430999994 -0.902400017 -0 +vn 0.151299998 -0.720300019 -0.676900029 +vn -0.100699998 -0.994899988 -0 +vn 0.307599992 -0.657899976 -0.687399983 +vn 0.205599993 -0.978600025 -0 +vn 0.307700008 -0.657899976 -0.687399983 +vn 0.537699997 -0.843100011 -0 +vn 0.423700005 -0.905799985 -0 +vn 0.384900004 -0.603600025 -0.698199987 +vn 0.570100009 -0.82160002 -0 +vn 0.402999997 -0.58069998 -0.707400024 +vn 0.560699999 -0.828000009 -0 +vn 0.393599987 -0.58859998 -0.706099987 +vn 0.395200014 -0.583599985 -0.709399998 +vn 0.396899998 -0.585699975 -0.706700027 +vn 0.555800021 -0.83130002 -0 +vn 0.396899998 -0.584900022 -0.707400024 +vn 0.56099999 -0.827799976 -0 +vn 0.393599987 -0.586399972 -0.708000004 +vn 0.561399996 -0.827499986 -0 +vn 0.607599974 -0.79430002 -0 +vn 0.557299972 -0.830299973 -0 +vn 0.438199997 -0.573000014 -0.692600012 +vn 0.834699988 -0.550599992 -0 +vn 0.770900011 0.0340000018 -0.635999978 +vn 0.834800005 -0.550599992 -0 +vn 0.633000016 -0.417600006 -0.651899993 +vn 0.624100029 0.399300009 -0.671599984 +vn 0.999000013 0.0441000015 -0 +vn 0.514699996 0.494199991 -0.700600028 +vn 0.842299998 0.538999975 -0 +vn 0.49180001 0.511500001 -0.704699993 +vn 0.721300006 0.692600012 -0 +vn 0.483599991 0.516700029 -0.706499994 +vn 0.693099976 0.720799983 -0 +vn 0.49180001 0.511399984 -0.704699993 +vn 0.483200014 0.515100002 -0.707899988 +vn 0.683300018 0.730099976 -0 +vn 0.489399999 0.507099986 -0.709500015 +vn 0.684199989 0.729300022 -0 +vn 0.511300027 0.4727 -0.717700005 +vn 0.694500029 0.719500005 -0 +vn 0.577099979 0.255299985 -0.77579999 +vn 0.734300017 0.678799987 -0 +vn 0.689899981 0.0458999984 -0.72240001 +vn 0.914499998 0.404500008 -0 +vn 0.687399983 -0.052099999 -0.724399984 +vn 0.997799993 0.0662999973 -0 +vn 0.687399983 -0.0520000011 -0.724399984 +vn 0.967499971 -0.252799988 -0 +vn 0.997099996 -0.0755999982 -0 +vn 0.584299982 -0.327499986 -0.742600024 +vn 0.659799993 -0.172399998 -0.731400013 +vn 0.441799998 -0.493200004 -0.74940002 +vn 0.872300029 -0.488999993 -0 +vn 0.584299982 -0.327499986 -0.742500007 +vn 0.256599993 -0.616699994 -0.744199991 +vn 0.667200029 -0.744899988 -0 +vn 0.441700011 -0.493299991 -0.74940002 +vn 0.0841000006 -0.674600005 -0.733299971 +vn 0.384200007 -0.923300028 -0 +vn -0.0599000007 -0.998199999 -0 +vn 0.123599999 -0.992299974 -0 +vn -0.117200002 -0.688499987 -0.715699971 +vn -0.0412999988 -0.689100027 -0.723500013 +vn -0.237499997 -0.62440002 -0.744099975 +vn -0.167699993 -0.985800028 -0 +vn -0.364300013 -0.59920001 -0.712899983 +vn -0.355500013 -0.934700012 -0 +vn -0.385500014 -0.591099977 -0.708500028 +vn -0.519500017 -0.854499996 -0 +vn -0.391099989 -0.58829999 -0.707700014 +vn -0.546199977 -0.837599993 -0 +vn -0.385500014 -0.591199994 -0.708500028 +vn -0.393700004 -0.586899996 -0.707499981 +vn -0.553600013 -0.832799971 -0 +vn -0.391099989 -0.588400006 -0.707700014 +vn -0.39379999 -0.586899996 -0.707499981 +vn -0.54519999 -0.83829999 -0 +vn -0.557200015 -0.83039999 -0 +vn -0.352499992 -0.624800026 -0.696699977 +vn -0.387499988 -0.595700026 -0.703499973 +vn -0.398799986 -0.916999996 -0 +vn -0.491400003 -0.870899975 -0 +vn -0.203999996 -0.692200005 -0.692300022 +vn -0.286900014 -0.659699976 -0.694599986 +vn -0.1021 -0.716600001 -0.689999998 +vn -0.282700002 -0.959200025 -0 +vn 0.0135000004 -0.724600017 -0.689100027 +vn -0.141100004 -0.99000001 -0 +vn 0.0136000002 -0.724600017 -0.689100027 +vn 0.146699995 -0.989199996 -0 +vn 0.0186000001 -0.999800026 -0 +vn 0.155100003 -0.69569999 -0.701399982 +vn 0.1052 -0.709500015 -0.696799994 +vn 0.183899999 -0.686600029 -0.703400016 +vn 0.217600003 -0.976000011 -0 +vn 0.155000001 -0.69569999 -0.701399982 +vn 0.203600004 -0.680000007 -0.704400003 +vn 0.258899987 -0.965900004 -0 +vn 0.184 -0.686600029 -0.703400016 +vn 0.212699994 -0.674799979 -0.706700027 +vn 0.286900014 -0.958000004 -0 +vn 0.223800004 -0.673900008 -0.704100013 +vn 0.300599992 -0.953700006 -0 +vn 0.315100014 -0.949100018 -0 +vn 0.423799992 -0.605400026 -0.673699975 +vn 0.27669999 -0.664900005 -0.693799973 +vn 0.878099978 -0.478399992 -0 +vn 0.573499978 -0.819199979 -0 +vn 0.756799996 0.0621999986 -0.650600016 +vn 0.670799971 -0.365500003 -0.645299971 +vn -0 -0 1 +vn 0.0526000001 0.998600006 -0 +vn 0.612900019 0.790199995 -0 +vn 0.979900002 0.199499995 -0 +vn 1 -0 -0 +vn 0.253399998 0.967299998 -0 +vn 0.727500021 0.686100006 -0 +vn 0.994199991 0.107900001 -0 +vn -1 -0 -0 +vn 0.848200023 -0.529699981 -0 +vn 0.365799993 -0.930700004 -0 +vn -0.996800005 -0.0802000016 -0 +vn -0.0687000006 -0.997600019 -0 +vn 0.42899999 -0.903299987 -0 +vn 0.893999994 -0.448100001 -0 +vn 0.966799974 0.255499989 -0 +vn 0.568499982 0.822700024 -0 +vn 0.222900003 0.974799991 -0 +vn 0.202000007 0.979399979 -0 +vn 0.417699993 0.908599973 -0 +vn 0.864600003 0.502399981 -0 +vn -0.938000023 0.346700013 -0 +vn 0.997200012 -0.0746000037 -0 +vn 0.745599985 -0.666400015 -0 +vn -0.980599999 -0.195800006 -0 +vn 0.25999999 -0.965600014 -0 +vn 0.979900002 -0.199499995 -0 +vn 0.612900019 -0.790199995 -0 +vn 0.0526000001 -0.998600006 -0 +vn -0.115599997 -0.993300021 -0 +vn -0.603900015 -0.796999991 -0 +vn -0.951200008 -0.308600008 -0 +vn -0.446700007 0.894699991 -0 +vn -0.928300023 0.371899992 -0 +vn -0.479000002 0.877799988 -0 +vn -0.920300007 0.391299993 -0 +vn 0.0756999999 0.997099996 -0 +vn 0.616900027 0.787 -0 +vn -0.791700006 0.610899985 -0 +vn -0.660000026 -0.75120002 -0 +vn -0.288100004 -0.957599998 -0 +vn -0.213 -0.976999998 -0 +vn -0.0500999987 0.998700023 -0 +vn -0.335200012 -0.942200005 -0 +vn 0.167899996 0.985800028 -0 +vn 0.754599988 0.656199992 -0 +vn 0.998099983 0.0617999993 -0 +vn 0.362899989 0.931800008 -0 +vn -0.620800018 -0.783999979 -0 +vn 0.808499992 0.588400006 -0 +vn 0.997099996 -0.076700002 -0 +vn 0.752399981 -0.658699989 -0 +vn 0.220500007 -0.975399971 -0 +vn -0.405000001 -0.914300025 -0 +vn -0.54610002 -0.837700009 -0 +vn -0.365999997 0.930599988 -0 +vn 0.0573000014 -0.998399973 -0 +vn -0.779299974 -0.626600027 -0 +vn 0.554799974 -0.832000017 -0 +vn -0.229699999 -0.97329998 -0 +vn 0.9005 0.434799999 -0 +vn 0.46329999 0.886200011 -0 +vn -0.458999991 -0.888400018 -0 +vn 0.530900002 0.847500026 -0 +vn 0.936699986 0.350199997 -0 +vn 0.973100007 -0.230199993 -0 +vn 0.629100025 -0.7773 -0 +vn 0.149499997 -0.988799989 -0 +vn 0.93629998 -0.351300001 -0 +vn -0.998099983 -0.0612999983 -0 +vn 0.451999992 -0.89200002 -0 +vn -0.0527999997 -0.998600006 -0 +vn -0.609099984 -0.793099999 -0 +vn -0.979099989 -0.203500003 -0 +vn 0.518999994 0.854799986 -0 +vn -0.994099975 -0.108099997 -0 +vn -0.449299991 -0.893400013 -0 +vn -0.852199972 0.523199975 -0 +vn -0.334399998 0.942399979 -0 +vn 0.247899994 0.968800008 -0 +vn 0.626399994 0.779500008 -0 +vn 0.0820999965 0.996599972 -0 +vn -0.841799974 0.539799988 -0 +vn -0.390500009 0.920599997 -0 +vn -0.896399975 0.443199992 -0 +vn -0.965600014 -0.25999999 -0 +vn -0.541599989 -0.840600014 -0 +vn -0.233500004 -0.97240001 -0 +vn -0.438899994 -0.898500025 -0 +vn -0.934099972 -0.356999993 -0 +vn -0.741599977 0.670899987 -0 +vn -0.241999999 0.970300019 -0 +vn -0.980700016 0.195700005 -0 +vn -0.611100018 0.791599989 -0 +vn -0.244299993 -0.969699979 -0 +vn -0.167799994 -0.985800028 -0 +vn 0.301099986 0.953599989 -0 +vn 0.861299992 0.508000016 -0 +vn -0.96509999 0.261799991 -0 +vn 0.486699998 0.873600006 -0 +vn 0.886500001 0.462700009 -0 +vn -0.0627000034 0.998000026 -0 +vn 0.972500026 -0.232899994 -0 +vn 0.0732000023 -0.997300029 -0 +vn -0.400000006 -0.916499972 -0 +vn 0.177300006 -0.984200001 -0 +vn 0.680000007 -0.733200014 -0 +vn -0.998199999 0.0603 -0 +vn 0.995800018 -0.0910999998 -0 +vn 0.801699996 0.5977 -0 +vn -0.872200012 -0.489199996 -0 +vn 0.370400012 0.928900003 -0 +vn 0.229399994 0.97329998 -0 +vn 0.650600016 0.75940001 -0 +vn 0.979799986 0.199699998 -0 +vn 0.922100008 -0.386999995 -0 +vn -0.65109998 -0.759000003 -0 +vn 0.503400028 -0.864099979 -0 +vn 0.861299992 -0.508000016 -0 +vn 0.301099986 -0.953599989 -0 +vn -0.755699992 0.654900014 -0 +vn -0.997099996 0.0759999976 -0 +vn -0.998000026 -0.0631999969 -0 +vn -0.440400004 -0.897800028 -0 +vn -0.3301 -0.943899989 -0 +vn -0.797500014 -0.603299975 -0 +vn -0.997600019 0.0694999993 -0 +vn -0.749599993 0.661899984 -0 +vn -0.194600001 0.98089999 -0 +vn 0.412800014 0.91079998 -0 +vn -0.0467999987 0.998899996 -0 +vn -0.523599982 0.851999998 -0 +vn -0.162100002 0.986800015 -0 +vn -0.893599987 -0.448900014 -0 +vn -0.661199987 0.750199974 -0 +vn -0.264600009 0.964299977 -0 +vn 0.451999992 0.89200002 -0 +vn 0.93629998 0.351300001 -0 +vn 0.155000001 0.987900019 -0 +vn 0.625 0.780600011 -0 +vn -0.552100003 -0.833700001 -0 +vn 0.953000009 0.302899987 -0 +vn 0.922500014 -0.386000007 -0 +vn 0.505900025 -0.862600029 -0 +vn -0.0768000036 -0.996999979 -0 +vn -0.216800004 -0.976199985 -0 +vn 0.302100003 -0.953299999 -0 +vn 0.797500014 -0.603299975 -0 +vn 0.996699989 0.0806000009 -0 +vn 0.684700012 0.728900015 -0 +vn 0.290600002 0.956799984 -0 +vn 0.316599995 0.948599994 -0 +vn -0.939800024 -0.341600001 -0 +vn 0.766200006 0.6426 -0 +vn -0.7852 -0.619199991 -0 +vn 0.844699979 -0.535300016 -0 +vn 0.378699988 -0.925499976 -0 +vn 0.998099983 -0.0617999993 -0 +vn -0.713800013 -0.700399995 -0 +vn 0.754599988 -0.656199992 -0 +vn 0.167899996 -0.985800028 -0 +vn -0.299899995 -0.953999996 -0 +vn -0.857800007 -0.513999999 -0 +vn -0.972299993 0.233700007 -0 +vn -0.880200028 -0.4745 -0 +vn -0.976000011 0.217700005 -0 +vn -0.622099996 0.782899976 -0 +vn 0.636900008 -0.770900011 -0 +vn 0.242699996 0.970099986 -0 +vn 0.536899984 0.843699992 -0 +vn 0.390399992 0.920700014 -0 +vn -0.149700001 0.988699973 -0 +vn -0.750500023 -0.660899997 -0 +vn -0.996500015 0.0835999995 -0 +vn -0.356299996 -0.934400022 -0 +vn -0.243499994 -0.969900012 -0 +vn -0.623399973 0.781899989 -0 +vn -0.668900013 -0.743300021 -0 +vn 0.961700022 -0.274100006 -0 +vn -0.494800001 0.869000018 -0 +vn -0.863399982 0.504499972 -0 +vn -0.29429999 0.95569998 -0 +vn -0.125300005 0.9921 -0 +vn -0.532999992 -0.846099973 -0 +vn 0.399899989 0.916599989 -0 +vn 0.398200005 0.917299986 -0 +vn 0.280499995 0.649299979 -0.706900001 +vn 0.396600008 0.917999983 -0 +vn 0.393999994 0.919099987 -0 +vn 0.39410001 0.919099987 -0 +vn 0.252400011 0.664200008 -0.703700006 +vn 0.0320000015 0.72359997 -0.689400017 +vn -0.131099999 0.706700027 -0.695299983 +vn -0.217099994 0.688499987 -0.691900015 +vn -0.56220001 0.462500006 -0.685599983 +vn -0.562099993 0.462500006 -0.685599983 +vn -0.711899996 -0.0621000007 -0.699599981 +vn -0.0746000037 -0.737299979 -0.671400011 +vn 0.396800011 -0.584900022 -0.707400024 +vn 0.561500013 -0.827400029 -0 +vn 0.438300014 -0.572899997 -0.692600012 +vn 0.683399975 0.730000019 -0 +vn 0.577099979 0.255199999 -0.77579999 +vn 0.689899981 0.0460000001 -0.72240001 +vn 0.584299982 -0.327399999 -0.742600024 +vn 0.441700011 -0.493299991 -0.749300003 +vn 0.256700009 -0.616699994 -0.744199991 +vn -0.237399995 -0.62440002 -0.744099975 +vn -0.39379999 -0.586799979 -0.707499981 +vn -0.203899994 -0.692200005 -0.692300022 +vn 0.756900012 0.0623000003 -0.650600016 +vn 0.253500015 0.967299998 -0 +vn 0.953000009 0.303000003 -0 +vn 0.998099983 0.0621000007 -0 +f 63/1/1 8/2/1 65/3/1 +f 63/1/1 9/4/1 8/2/1 +f 63/1/1 69/5/1 9/4/1 +f 63/1/1 71/6/1 69/5/1 +f 61/7/1 71/6/1 63/1/1 +f 61/7/1 73/8/1 71/6/1 +f 61/7/1 10/9/1 73/8/1 +f 7/10/1 10/9/1 61/7/1 +f 7/10/1 11/11/1 10/9/1 +f 7/10/1 12/12/1 11/11/1 +f 7/10/1 78/13/1 12/12/1 +f 7/10/1 80/14/1 78/13/1 +f 58/15/1 80/14/1 7/10/1 +f 58/15/1 82/16/1 80/14/1 +f 58/15/1 84/17/1 82/16/1 +f 58/15/1 86/18/1 84/17/1 +f 58/15/1 13/19/1 86/18/1 +f 58/15/1 14/20/1 13/19/1 +f 6/21/1 14/20/1 58/15/1 +f 6/21/1 90/22/1 14/20/1 +f 55/23/1 90/22/1 6/21/1 +f 53/24/1 90/22/1 55/23/1 +f 53/24/1 147/25/1 90/22/1 +f 147/25/1 145/26/1 90/22/1 +f 145/26/1 92/27/1 90/22/1 +f 51/28/1 149/29/1 53/24/1 +f 149/29/1 147/25/1 53/24/1 +f 51/28/1 30/30/1 149/29/1 +f 51/28/1 152/31/1 30/30/1 +f 143/32/1 92/27/1 145/26/1 +f 51/28/1 154/33/1 152/31/1 +f 141/34/1 92/27/1 143/32/1 +f 139/35/1 92/27/1 141/34/1 +f 137/36/1 92/27/1 139/35/1 +f 51/28/1 156/37/1 154/33/1 +f 29/38/1 92/27/1 137/36/1 +f 134/39/1 92/27/1 29/38/1 +f 28/40/1 92/27/1 134/39/1 +f 5/41/1 156/37/1 51/28/1 +f 5/41/1 158/42/1 156/37/1 +f 27/43/1 92/27/1 28/40/1 +f 4/44/1 158/42/1 5/41/1 +f 4/44/1 31/45/1 158/42/1 +f 26/46/1 92/27/1 27/43/1 +f 26/46/1 94/47/1 92/27/1 +f 47/48/1 31/45/1 4/44/1 +f 3/49/1 31/45/1 47/48/1 +f 3/49/1 161/50/1 31/45/1 +f 44/51/1 161/50/1 3/49/1 +f 129/52/1 94/47/1 26/46/1 +f 2/53/1 161/50/1 44/51/1 +f 1/54/1 161/50/1 2/53/1 +f 40/55/1 161/50/1 1/54/1 +f 179/56/1 161/50/1 40/55/1 +f 39/57/1 161/50/1 179/56/1 +f 25/58/1 94/47/1 129/52/1 +f 38/59/1 161/50/1 39/57/1 +f 175/60/1 161/50/1 38/59/1 +f 175/60/1 32/61/1 161/50/1 +f 173/62/1 32/61/1 175/60/1 +f 25/58/1 96/63/1 94/47/1 +f 126/64/1 96/63/1 25/58/1 +f 37/65/1 32/61/1 173/62/1 +f 37/65/1 33/66/1 32/61/1 +f 36/67/1 33/66/1 37/65/1 +f 36/67/1 34/68/1 33/66/1 +f 169/69/1 34/68/1 36/67/1 +f 169/69/1 35/70/1 34/68/1 +f 167/71/1 35/70/1 169/69/1 +f 124/72/1 96/63/1 126/64/1 +f 122/73/1 96/63/1 124/72/1 +f 24/74/1 96/63/1 122/73/1 +f 119/75/1 96/63/1 24/74/1 +f 119/75/1 15/76/1 96/63/1 +f 117/77/1 15/76/1 119/75/1 +f 23/78/1 15/76/1 117/77/1 +f 22/79/1 15/76/1 23/78/1 +f 113/80/1 15/76/1 22/79/1 +f 21/81/1 15/76/1 113/80/1 +f 21/81/1 99/82/1 15/76/1 +f 110/83/1 99/82/1 21/81/1 +f 110/83/1 101/84/1 99/82/1 +f 20/85/1 101/84/1 110/83/1 +f 20/85/1 103/86/1 101/84/1 +f 19/87/1 103/86/1 20/85/1 +f 19/87/1 16/88/1 103/86/1 +f 18/89/1 16/88/1 19/87/1 +f 18/89/1 17/90/1 16/88/1 +f 2/53/2 41/54/3 1/54/4 +f 44/51/5 42/91/6 2/53/2 +f 3/49/7 43/51/8 44/51/5 +f 47/48/9 45/49/10 3/49/7 +f 4/44/11 46/48/12 47/48/9 +f 5/41/13 48/92/14 4/44/15 +f 51/28/16 49/41/17 5/41/13 +f 53/24/18 50/28/19 51/28/16 +f 55/23/20 52/24/21 53/24/22 +f 6/21/23 54/93/24 55/23/20 +f 58/15/25 56/21/26 6/21/23 +f 58/15/25 59/10/27 57/15/28 +f 7/10/29 60/7/30 59/10/27 +f 61/7/31 62/94/32 60/7/30 +f 63/1/33 64/3/34 62/94/32 +f 65/3/35 66/2/36 64/3/34 +f 8/2/37 67/4/38 66/2/36 +f 9/4/39 68/5/40 67/4/38 +f 69/5/41 70/6/42 68/5/40 +f 71/6/43 72/8/44 70/6/42 +f 10/9/45 72/8/44 73/8/46 +f 10/9/45 75/11/47 74/9/48 +f 12/12/49 75/11/47 11/11/50 +f 78/13/51 76/12/52 12/12/49 +f 80/14/53 77/13/54 78/13/55 +f 80/14/53 81/16/56 79/14/57 +f 84/17/58 81/16/56 82/16/59 +f 84/17/58 85/95/60 83/17/61 +f 13/19/62 85/95/60 86/18/63 +f 14/20/64 87/19/65 13/19/66 +f 90/22/67 88/20/68 14/20/64 +f 92/27/69 89/22/70 90/22/67 +f 94/47/71 91/96/72 92/27/69 +f 96/63/73 93/47/74 94/47/71 +f 15/76/75 95/63/76 96/63/73 +f 99/82/77 97/76/78 15/76/75 +f 101/84/79 98/82/80 99/82/77 +f 101/84/79 102/86/81 100/97/82 +f 16/88/83 102/86/81 103/86/84 +f 17/90/85 104/98/86 16/88/83 +f 18/89/87 105/90/88 17/90/85 +f 19/87/89 106/89/90 18/89/87 +f 19/87/91 108/85/92 107/87/93 +f 20/85/94 109/83/95 108/85/92 +f 110/83/96 111/81/97 109/83/95 +f 113/80/98 111/81/97 21/81/99 +f 22/79/100 112/80/101 113/80/98 +f 23/78/102 114/79/103 22/79/100 +f 117/77/104 115/78/105 23/78/102 +f 117/77/104 118/75/106 116/77/107 +f 119/75/108 120/74/109 118/75/106 +f 122/73/110 120/74/111 24/74/112 +f 124/72/113 121/73/114 122/73/110 +f 126/64/115 123/72/116 124/72/113 +f 25/58/117 125/64/118 126/64/115 +f 129/52/119 127/58/120 25/58/121 +f 26/46/122 128/52/123 129/52/119 +f 27/43/124 130/46/125 26/46/122 +f 28/40/126 131/43/127 27/43/124 +f 134/39/128 132/40/129 28/40/126 +f 29/38/130 133/39/131 134/39/128 +f 137/36/132 135/38/133 29/38/130 +f 137/36/134 138/35/135 136/36/136 +f 141/34/137 138/35/135 139/35/138 +f 143/32/139 140/34/140 141/34/141 +f 145/26/142 142/32/143 143/32/144 +f 147/25/145 144/99/146 145/26/142 +f 147/25/145 148/29/147 146/100/148 +f 30/30/149 148/29/147 149/29/150 +f 152/31/151 150/30/152 30/30/149 +f 154/33/153 151/31/154 152/31/151 +f 156/37/155 153/33/156 154/33/153 +f 158/42/157 155/37/158 156/37/159 +f 31/45/160 157/42/161 158/42/162 +f 31/45/163 160/50/164 159/45/165 +f 32/61/166 160/50/164 161/50/167 +f 32/61/166 163/66/168 162/61/169 +f 34/68/170 163/66/168 33/66/171 +f 35/70/172 164/68/173 34/68/170 +f 167/71/174 165/70/175 35/70/172 +f 167/71/176 168/69/177 166/71/178 +f 36/67/179 168/69/177 169/69/180 +f 37/65/181 170/101/182 36/67/183 +f 173/62/184 171/65/185 37/65/186 +f 175/60/187 172/62/188 173/62/184 +f 38/59/189 174/60/190 175/60/187 +f 38/59/189 177/57/146 176/59/191 +f 179/56/192 177/57/146 39/57/193 +f 179/56/192 180/55/194 178/56/195 +f 1/54/196 180/55/194 40/55/197 +f 390/102/198 182/103/198 181/104/198 +f 389/105/198 182/103/198 390/102/198 +f 389/105/198 183/106/198 182/103/198 +f 388/107/198 183/106/198 389/105/198 +f 388/107/198 184/108/198 183/106/198 +f 387/109/198 184/108/198 388/107/198 +f 387/109/198 185/110/198 184/108/198 +f 386/111/198 185/110/198 387/109/198 +f 386/111/198 186/112/198 185/110/198 +f 385/113/198 186/112/198 386/111/198 +f 385/113/198 187/114/198 186/112/198 +f 384/115/198 187/114/198 385/113/198 +f 384/115/198 188/116/198 187/114/198 +f 383/117/198 188/116/198 384/115/198 +f 383/117/198 189/118/198 188/116/198 +f 382/119/198 189/118/198 383/117/198 +f 382/119/198 190/120/198 189/118/198 +f 381/121/198 190/120/198 382/119/198 +f 381/121/198 191/122/198 190/120/198 +f 380/123/198 191/122/198 381/121/198 +f 380/123/198 192/124/198 191/122/198 +f 379/125/198 192/124/198 380/123/198 +f 379/125/198 193/126/198 192/124/198 +f 378/127/198 193/126/198 379/125/198 +f 378/127/198 194/128/198 193/126/198 +f 377/129/198 194/128/198 378/127/198 +f 377/129/198 195/130/198 194/128/198 +f 376/131/198 195/130/198 377/129/198 +f 376/131/198 196/132/198 195/130/198 +f 375/133/198 196/132/198 376/131/198 +f 375/133/198 197/134/198 196/132/198 +f 374/135/198 197/134/198 375/133/198 +f 374/135/198 198/136/198 197/134/198 +f 373/137/198 198/136/198 374/135/198 +f 373/137/198 199/138/198 198/136/198 +f 372/139/198 199/138/198 373/137/198 +f 372/139/198 200/140/198 199/138/198 +f 371/141/198 200/140/198 372/139/198 +f 371/141/198 201/142/198 200/140/198 +f 370/143/198 201/142/198 371/141/198 +f 370/143/198 202/144/198 201/142/198 +f 370/143/198 203/145/198 202/144/198 +f 369/146/198 226/147/198 370/143/198 +f 226/147/198 203/145/198 370/143/198 +f 226/147/198 204/148/198 203/145/198 +f 369/146/198 227/149/198 226/147/198 +f 225/150/198 204/148/198 226/147/198 +f 369/146/198 228/151/198 227/149/198 +f 224/152/198 204/148/198 225/150/198 +f 369/146/198 229/153/198 228/151/198 +f 368/154/198 229/153/198 369/146/198 +f 223/155/198 204/148/198 224/152/198 +f 368/154/198 230/156/198 229/153/198 +f 223/155/198 205/157/198 204/148/198 +f 368/154/198 231/158/198 230/156/198 +f 222/159/198 205/157/198 223/155/198 +f 368/154/198 232/160/198 231/158/198 +f 222/159/198 206/161/198 205/157/198 +f 368/154/198 233/162/198 232/160/198 +f 367/163/198 233/162/198 368/154/198 +f 221/164/198 206/161/198 222/159/198 +f 221/164/198 207/165/198 206/161/198 +f 367/163/198 234/166/198 233/162/198 +f 221/164/198 208/167/198 207/165/198 +f 221/164/198 209/168/198 208/167/198 +f 367/163/198 235/169/198 234/166/198 +f 220/170/198 209/168/198 221/164/198 +f 220/170/198 210/171/198 209/168/198 +f 366/172/198 235/169/198 367/163/198 +f 220/170/198 211/173/198 210/171/198 +f 219/174/198 211/173/198 220/170/198 +f 366/172/198 236/175/198 235/169/198 +f 219/174/198 212/176/198 211/173/198 +f 218/177/198 212/176/198 219/174/198 +f 218/177/198 213/178/198 212/176/198 +f 218/177/198 214/179/198 213/178/198 +f 217/180/198 214/179/198 218/177/198 +f 366/172/198 237/181/198 236/175/198 +f 217/180/198 215/182/198 214/179/198 +f 216/183/198 215/182/198 217/180/198 +f 365/184/198 237/181/198 366/172/198 +f 365/184/198 238/185/198 237/181/198 +f 365/184/198 239/186/198 238/185/198 +f 365/184/198 240/187/198 239/186/198 +f 364/188/198 240/187/198 365/184/198 +f 364/188/198 241/189/198 240/187/198 +f 364/188/198 242/190/198 241/189/198 +f 364/188/198 243/191/198 242/190/198 +f 363/192/198 243/191/198 364/188/198 +f 363/192/198 244/193/198 243/191/198 +f 363/192/198 245/194/198 244/193/198 +f 363/192/198 246/195/198 245/194/198 +f 363/192/198 247/196/198 246/195/198 +f 363/192/198 248/197/198 247/196/198 +f 363/192/198 249/198/198 248/197/198 +f 362/199/198 249/198/198 363/192/198 +f 362/199/198 250/200/198 249/198/198 +f 362/199/198 251/201/198 250/200/198 +f 362/199/198 252/202/198 251/201/198 +f 362/199/198 253/203/198 252/202/198 +f 361/204/198 253/203/198 362/199/198 +f 361/204/198 254/205/198 253/203/198 +f 360/206/198 254/205/198 361/204/198 +f 360/206/198 255/207/198 254/205/198 +f 360/206/198 256/208/198 255/207/198 +f 359/209/198 256/208/198 360/206/198 +f 358/210/198 256/208/198 359/209/198 +f 358/210/198 257/211/198 256/208/198 +f 357/212/198 257/211/198 358/210/198 +f 357/212/198 258/213/198 257/211/198 +f 356/214/198 258/213/198 357/212/198 +f 355/215/198 258/213/198 356/214/198 +f 354/216/198 258/213/198 355/215/198 +f 353/217/198 258/213/198 354/216/198 +f 353/217/198 259/218/198 258/213/198 +f 352/219/198 259/218/198 353/217/198 +f 351/220/198 259/218/198 352/219/198 +f 350/221/198 259/218/198 351/220/198 +f 349/222/198 259/218/198 350/221/198 +f 348/223/198 259/218/198 349/222/198 +f 348/223/198 260/224/198 259/218/198 +f 347/225/198 260/224/198 348/223/198 +f 346/226/198 260/224/198 347/225/198 +f 345/227/198 260/224/198 346/226/198 +f 345/227/198 261/228/198 260/224/198 +f 344/229/198 261/228/198 345/227/198 +f 343/230/198 261/228/198 344/229/198 +f 342/231/198 261/228/198 343/230/198 +f 342/231/198 262/232/198 261/228/198 +f 341/233/198 262/232/198 342/231/198 +f 320/234/198 322/235/198 321/236/198 +f 319/237/198 322/235/198 320/234/198 +f 319/237/198 323/238/198 322/235/198 +f 318/239/198 323/238/198 319/237/198 +f 318/239/198 324/240/198 323/238/198 +f 340/241/198 262/232/198 341/233/198 +f 317/242/198 324/240/198 318/239/198 +f 317/242/198 325/243/198 324/240/198 +f 316/244/198 325/243/198 317/242/198 +f 315/245/198 325/243/198 316/244/198 +f 315/245/198 326/246/198 325/243/198 +f 340/241/198 263/247/198 262/232/198 +f 339/248/198 263/247/198 340/241/198 +f 314/249/198 326/246/198 315/245/198 +f 313/250/198 326/246/198 314/249/198 +f 313/250/198 327/251/198 326/246/198 +f 312/252/198 327/251/198 313/250/198 +f 338/253/198 263/247/198 339/248/198 +f 311/254/198 327/251/198 312/252/198 +f 337/255/198 263/247/198 338/253/198 +f 337/255/198 264/256/198 263/247/198 +f 311/254/198 328/257/198 327/251/198 +f 310/258/198 328/257/198 311/254/198 +f 336/259/198 264/256/198 337/255/198 +f 309/260/198 328/257/198 310/258/198 +f 309/260/198 329/261/198 328/257/198 +f 335/262/198 264/256/198 336/259/198 +f 334/263/198 264/256/198 335/262/198 +f 334/263/198 265/264/198 264/256/198 +f 309/260/198 330/265/198 329/261/198 +f 333/266/198 265/264/198 334/263/198 +f 308/267/198 330/265/198 309/260/198 +f 332/268/198 265/264/198 333/266/198 +f 308/267/198 331/269/198 330/265/198 +f 331/269/198 265/264/198 332/268/198 +f 308/267/198 265/264/198 331/269/198 +f 307/270/198 265/264/198 308/267/198 +f 307/270/198 266/271/198 265/264/198 +f 306/272/198 266/271/198 307/270/198 +f 306/272/198 267/273/198 266/271/198 +f 305/274/198 267/273/198 306/272/198 +f 305/274/198 268/275/198 267/273/198 +f 304/276/198 268/275/198 305/274/198 +f 304/276/198 269/277/198 268/275/198 +f 303/278/198 269/277/198 304/276/198 +f 303/278/198 270/279/198 269/277/198 +f 302/280/198 270/279/198 303/278/198 +f 302/280/198 271/281/198 270/279/198 +f 301/282/198 271/281/198 302/280/198 +f 301/282/198 272/283/198 271/281/198 +f 300/284/198 272/283/198 301/282/198 +f 300/284/198 273/285/198 272/283/198 +f 299/286/198 273/285/198 300/284/198 +f 299/286/198 274/287/198 273/285/198 +f 298/288/198 274/287/198 299/286/198 +f 298/288/198 275/289/198 274/287/198 +f 297/290/198 275/289/198 298/288/198 +f 297/290/198 276/291/198 275/289/198 +f 296/292/198 276/291/198 297/290/198 +f 295/293/198 276/291/198 296/292/198 +f 295/293/198 277/294/198 276/291/198 +f 294/295/198 277/294/198 295/293/198 +f 294/295/198 278/296/198 277/294/198 +f 293/297/198 278/296/198 294/295/198 +f 293/297/198 279/298/198 278/296/198 +f 292/299/198 279/298/198 293/297/198 +f 292/299/198 280/300/198 279/298/198 +f 291/301/198 280/300/198 292/299/198 +f 291/301/198 281/302/198 280/300/198 +f 290/303/198 281/302/198 291/301/198 +f 290/303/198 282/304/198 281/302/198 +f 289/305/198 282/304/198 290/303/198 +f 289/305/198 283/306/198 282/304/198 +f 288/307/198 283/306/198 289/305/198 +f 288/307/198 284/308/198 283/306/198 +f 287/309/198 284/308/198 288/307/198 +f 287/309/198 285/310/198 284/308/198 +f 286/311/198 285/310/198 287/309/198 +f 182/103/199 391/104/199 181/104/199 +f 186/112/200 395/110/200 185/110/200 +f 190/120/201 399/118/201 189/118/201 +f 194/128/202 403/126/202 193/126/202 +f 198/136/203 407/134/203 197/134/203 +f 202/144/204 411/142/204 201/142/204 +f 206/161/205 415/157/205 205/157/205 +f 380/123/206 589/125/206 379/125/206 +f 210/171/207 419/168/207 209/168/207 +f 214/179/208 423/178/208 213/178/208 +f 342/231/209 551/233/209 341/233/209 +f 226/147/210 435/150/210 225/150/210 +f 230/156/211 439/153/211 229/153/211 +f 234/166/212 443/162/212 233/162/212 +f 238/185/213 447/181/213 237/181/213 +f 242/190/214 451/312/214 241/189/214 +f 246/195/215 455/194/215 245/194/215 +f 250/200/216 459/198/216 249/198/216 +f 254/205/217 463/203/217 253/203/217 +f 258/213/218 467/211/218 257/211/218 +f 384/115/219 593/117/219 383/117/219 +f 262/232/220 471/228/220 261/228/220 +f 266/271/221 475/264/221 265/264/221 +f 365/184/222 574/188/222 364/188/222 +f 270/279/223 479/277/223 269/277/223 +f 274/287/202 483/285/202 273/285/202 +f 278/296/224 487/294/224 277/294/224 +f 282/304/225 491/302/225 281/302/225 +f 286/311/226 495/310/226 285/310/226 +f 297/290/206 508/288/206 507/290/206 +f 302/280/227 511/282/227 301/282/227 +f 306/272/228 515/274/228 305/274/228 +f 310/258/229 519/260/229 309/260/229 +f 388/107/230 597/109/230 387/109/230 +f 314/249/231 523/250/231 313/250/231 +f 318/239/232 527/242/232 317/242/232 +f 369/146/233 578/154/233 368/154/233 +f 322/235/234 531/236/234 321/236/234 +f 326/246/235 535/243/235 325/243/235 +f 338/253/236 547/255/236 337/255/236 +f 345/227/237 556/226/237 555/227/237 +f 350/221/238 559/222/238 349/222/238 +f 354/216/239 563/217/239 353/217/239 +f 181/104/240 600/102/240 390/102/240 +f 297/290/206 506/292/206 296/292/206 +f 357/212/241 568/210/241 567/212/241 +f 183/106/242 392/103/242 182/103/242 +f 187/114/243 396/112/243 186/112/243 +f 191/122/244 400/120/244 190/120/244 +f 195/130/202 404/128/202 194/128/202 +f 199/138/245 408/136/245 198/136/245 +f 220/170/246 431/164/246 430/170/246 +f 203/145/247 412/144/247 202/144/247 +f 207/165/248 416/161/248 206/161/248 +f 211/173/249 420/171/249 210/171/249 +f 215/182/250 424/179/250 214/179/250 +f 219/174/251 428/177/251 218/177/251 +f 223/155/252 432/159/252 222/159/252 +f 374/135/253 583/137/253 373/137/253 +f 227/149/254 436/147/254 226/147/254 +f 362/199/255 571/204/255 361/204/255 +f 231/158/256 440/156/256 230/156/256 +f 225/150/257 434/152/257 224/152/257 +f 239/186/258 448/185/258 238/185/258 +f 243/191/259 452/313/259 242/190/259 +f 247/196/216 456/314/216 246/195/216 +f 251/201/216 460/200/216 250/200/216 +f 305/274/260 514/276/260 304/276/260 +f 255/207/261 464/205/261 254/205/261 +f 259/218/262 468/213/262 258/213/262 +f 263/247/263 472/232/263 262/232/263 +f 267/273/264 476/271/264 266/271/264 +f 271/281/265 480/279/265 270/279/265 +f 275/289/202 484/287/202 274/287/202 +f 279/298/266 488/296/266 278/296/266 +f 365/184/267 576/172/267 575/184/267 +f 283/306/268 492/304/268 282/304/268 +f 287/309/269 496/311/269 286/311/269 +f 291/301/270 500/303/270 290/303/270 +f 295/293/271 504/295/271 294/295/271 +f 328/257/272 537/251/272 327/251/272 +f 311/254/273 520/258/273 310/258/273 +f 290/303/274 499/305/274 289/305/274 +f 314/249/275 525/245/275 524/249/275 +f 319/237/276 528/239/276 318/239/276 +f 323/238/277 532/235/277 322/235/277 +f 327/251/278 536/246/278 326/246/278 +f 331/269/279 540/265/279 330/265/279 +f 370/143/280 579/146/280 369/146/280 +f 335/262/281 544/263/281 334/263/281 +f 339/248/282 548/253/282 338/253/282 +f 342/231/283 553/230/283 552/231/283 +f 347/225/284 556/226/284 346/226/284 +f 351/220/285 560/221/285 350/221/285 +f 358/210/286 569/209/286 568/210/286 +f 294/295/287 503/297/287 293/297/287 +f 371/141/288 580/143/288 370/143/288 +f 375/133/289 584/135/289 374/135/289 +f 379/125/206 588/127/206 378/127/206 +f 383/117/290 592/119/290 382/119/290 +f 387/109/291 596/111/291 386/111/291 +f 355/215/239 564/216/239 354/216/239 +f 218/177/292 427/180/292 217/180/292 +f 288/307/293 497/309/293 287/309/293 +f 378/127/206 587/129/206 377/129/206 +f 184/108/294 393/106/294 183/106/294 +f 188/116/295 397/114/295 187/114/295 +f 192/124/202 401/122/202 191/122/202 +f 196/132/202 405/130/202 195/130/202 +f 339/248/296 550/241/296 549/248/296 +f 200/140/297 409/138/297 199/138/297 +f 204/148/298 413/145/298 203/145/298 +f 321/236/299 530/234/299 320/234/299 +f 208/167/300 417/165/300 207/165/300 +f 216/183/301 425/182/301 215/182/301 +f 224/152/302 433/155/302 223/155/302 +f 228/151/303 437/149/303 227/149/303 +f 232/160/304 441/158/304 231/158/304 +f 382/119/305 591/121/305 381/121/305 +f 236/175/306 445/169/306 235/169/306 +f 240/187/307 449/186/307 239/186/307 +f 362/199/308 573/192/308 572/199/308 +f 244/193/309 453/315/309 243/191/309 +f 248/197/216 457/196/216 247/196/216 +f 252/202/310 461/201/310 251/201/310 +f 256/208/311 465/207/311 255/207/311 +f 260/224/312 469/218/312 259/218/312 +f 264/256/313 473/247/313 263/247/313 +f 222/159/314 431/164/314 221/164/314 +f 268/275/315 477/273/315 267/273/315 +f 272/283/202 481/281/202 271/281/202 +f 276/291/202 485/289/202 275/289/202 +f 280/300/316 489/298/316 279/298/316 +f 284/308/317 493/306/317 283/306/317 +f 386/111/318 595/113/318 385/113/318 +f 367/163/319 576/172/319 366/172/319 +f 296/292/320 505/293/320 295/293/320 +f 300/284/206 509/286/206 299/286/206 +f 348/223/321 557/225/321 347/225/321 +f 304/276/322 513/278/322 303/278/322 +f 308/267/323 517/270/323 307/270/323 +f 312/252/324 521/254/324 311/254/324 +f 315/245/325 526/244/325 525/245/325 +f 320/234/326 529/237/326 319/237/326 +f 324/240/327 533/238/327 323/238/327 +f 332/268/328 541/269/328 331/269/328 +f 336/259/329 545/262/329 335/262/329 +f 390/102/330 599/105/330 389/105/330 +f 344/229/331 553/230/331 343/230/331 +f 352/219/239 561/220/239 351/220/239 +f 356/214/239 565/316/239 355/215/239 +f 337/255/332 546/259/332 336/259/332 +f 334/263/333 543/266/333 333/266/333 +f 299/286/206 508/288/206 298/288/206 +f 185/110/334 394/108/334 184/108/334 +f 189/118/335 398/116/335 188/116/335 +f 193/126/202 402/124/202 192/124/202 +f 197/134/336 406/132/336 196/132/336 +f 201/142/337 410/140/337 200/140/337 +f 360/206/338 569/209/338 359/209/338 +f 205/157/339 414/148/339 204/148/339 +f 209/168/340 418/167/340 208/167/340 +f 213/178/341 422/176/341 212/176/341 +f 217/180/342 426/183/342 216/183/342 +f 303/278/343 512/280/343 302/280/343 +f 229/153/344 438/151/344 228/151/344 +f 233/162/345 442/160/345 232/160/345 +f 237/181/346 446/175/346 236/175/346 +f 241/189/347 450/187/347 240/187/347 +f 245/194/348 454/193/348 244/193/348 +f 249/198/216 458/197/216 248/197/216 +f 253/203/349 462/202/349 252/202/349 +f 364/188/350 573/192/350 363/192/350 +f 257/211/351 466/208/351 256/208/351 +f 345/227/352 554/229/352 344/229/352 +f 265/264/353 474/256/353 264/256/353 +f 269/277/354 478/275/354 268/275/354 +f 273/285/202 482/283/202 272/283/202 +f 277/294/355 486/291/355 276/291/355 +f 307/270/356 516/272/356 306/272/356 +f 281/302/357 490/300/357 280/300/357 +f 285/310/358 494/308/358 284/308/358 +f 289/305/359 498/307/359 288/307/359 +f 292/299/360 503/297/360 502/299/360 +f 368/154/361 577/163/361 367/163/361 +f 308/267/362 519/260/362 518/267/362 +f 313/250/363 522/252/363 312/252/363 +f 317/242/364 526/244/364 316/244/364 +f 301/282/206 510/284/206 300/284/206 +f 212/176/365 421/173/365 211/173/365 +f 330/265/366 539/261/366 329/261/366 +f 325/243/367 534/240/367 324/240/367 +f 329/261/368 538/257/368 328/257/368 +f 333/266/369 542/268/369 332/268/369 +f 292/299/370 501/301/370 291/301/370 +f 341/233/371 550/241/371 340/241/371 +f 349/222/372 558/223/372 348/223/372 +f 353/217/239 562/219/239 352/219/239 +f 357/212/373 566/317/373 356/214/373 +f 372/139/374 581/141/374 371/141/374 +f 361/204/375 570/318/375 360/206/375 +f 235/169/376 444/166/376 234/166/376 +f 372/139/377 583/137/377 582/139/377 +f 377/129/206 586/131/206 376/131/206 +f 381/121/206 590/123/206 380/123/206 +f 385/113/378 594/115/378 384/115/378 +f 389/105/379 598/107/379 388/107/379 +f 376/131/380 585/133/380 375/133/380 +f 220/170/381 429/174/381 219/174/381 +f 2/53/2 42/91/6 41/54/3 +f 44/51/5 43/51/8 42/91/6 +f 3/49/7 45/49/10 43/51/8 +f 47/48/9 46/48/12 45/49/10 +f 4/44/15 48/92/382 46/48/12 +f 5/41/13 49/41/383 48/92/14 +f 51/28/384 50/28/385 49/41/17 +f 53/24/18 52/24/386 50/28/19 +f 55/23/20 54/93/24 52/24/387 +f 6/21/23 56/21/26 54/93/24 +f 58/15/25 57/15/28 56/21/26 +f 58/15/25 7/10/29 59/10/27 +f 7/10/388 61/7/31 60/7/30 +f 61/7/31 63/1/33 62/94/32 +f 63/1/33 65/3/35 64/3/34 +f 65/3/389 8/2/37 66/2/36 +f 8/2/37 9/4/39 67/4/38 +f 9/4/390 69/5/391 68/5/40 +f 69/5/41 71/6/43 70/6/42 +f 71/6/43 73/8/46 72/8/44 +f 10/9/392 74/9/48 72/8/44 +f 10/9/393 11/11/50 75/11/47 +f 12/12/49 76/12/52 75/11/47 +f 78/13/55 77/13/54 76/12/52 +f 80/14/53 79/14/57 77/13/54 +f 80/14/53 82/16/59 81/16/56 +f 84/17/58 83/17/61 81/16/56 +f 84/17/58 86/18/63 85/95/60 +f 13/19/66 87/19/65 85/95/60 +f 14/20/394 88/20/68 87/19/65 +f 90/22/67 89/22/70 88/20/68 +f 92/27/69 91/96/72 89/22/70 +f 94/47/71 93/47/74 91/96/72 +f 96/63/73 95/63/76 93/47/74 +f 15/76/75 97/76/78 95/63/76 +f 99/82/77 98/82/80 97/76/78 +f 101/84/79 100/97/82 98/82/80 +f 101/84/79 103/86/84 102/86/81 +f 16/88/83 104/98/86 102/86/81 +f 17/90/395 105/90/88 104/98/86 +f 18/89/87 106/89/90 105/90/88 +f 19/87/91 107/87/93 106/89/90 +f 19/87/91 20/85/94 108/85/92 +f 20/85/94 110/83/96 109/83/95 +f 110/83/96 21/81/99 111/81/97 +f 113/80/98 112/80/101 111/81/97 +f 22/79/100 114/79/103 112/80/101 +f 23/78/396 115/78/397 114/79/103 +f 117/77/104 116/77/107 115/78/105 +f 117/77/104 119/75/398 118/75/106 +f 119/75/398 24/74/112 120/74/109 +f 122/73/110 121/73/114 120/74/111 +f 124/72/113 123/72/116 121/73/114 +f 126/64/115 125/64/118 123/72/116 +f 25/58/121 127/58/120 125/64/118 +f 129/52/119 128/52/399 127/58/120 +f 26/46/122 130/46/125 128/52/123 +f 27/43/124 131/43/127 130/46/125 +f 28/40/126 132/40/129 131/43/127 +f 134/39/400 133/39/131 132/40/129 +f 29/38/401 135/38/133 133/39/131 +f 137/36/134 136/36/136 135/38/133 +f 137/36/132 139/35/138 138/35/135 +f 141/34/402 140/34/140 138/35/135 +f 143/32/403 142/32/143 140/34/140 +f 145/26/404 144/99/146 142/32/143 +f 147/25/145 146/100/148 144/99/146 +f 147/25/145 149/29/150 148/29/147 +f 30/30/149 150/30/152 148/29/147 +f 152/31/405 151/31/154 150/30/152 +f 154/33/153 153/33/156 151/31/154 +f 156/37/159 155/37/158 153/33/156 +f 158/42/157 157/42/161 155/37/158 +f 31/45/163 159/45/165 157/42/161 +f 31/45/406 161/50/167 160/50/164 +f 32/61/166 162/61/169 160/50/164 +f 32/61/166 33/66/171 163/66/168 +f 34/68/407 164/68/173 163/66/168 +f 35/70/172 165/70/175 164/68/173 +f 167/71/174 166/71/178 165/70/175 +f 167/71/174 169/69/180 168/69/177 +f 36/67/179 170/101/182 168/69/177 +f 37/65/186 171/65/185 170/101/182 +f 173/62/184 172/62/188 171/65/185 +f 175/60/187 174/60/190 172/62/188 +f 38/59/189 176/59/191 174/60/190 +f 38/59/189 39/57/193 177/57/146 +f 179/56/192 178/56/195 177/57/146 +f 179/56/192 40/55/197 180/55/194 +f 1/54/408 41/54/3 180/55/194 +f 182/103/199 392/103/199 391/104/199 +f 186/112/200 396/112/200 395/110/200 +f 190/120/201 400/120/201 399/118/201 +f 194/128/202 404/128/202 403/126/202 +f 198/136/409 408/136/409 407/134/409 +f 202/144/204 412/144/204 411/142/204 +f 206/161/205 416/161/205 415/157/205 +f 380/123/206 590/123/206 589/125/206 +f 210/171/207 420/171/207 419/168/207 +f 214/179/208 424/179/208 423/178/208 +f 342/231/209 552/231/209 551/233/209 +f 226/147/210 436/147/210 435/150/210 +f 230/156/211 440/156/211 439/153/211 +f 234/166/212 444/166/212 443/162/212 +f 238/185/213 448/185/213 447/181/213 +f 242/190/214 452/313/214 451/312/214 +f 246/195/215 456/314/215 455/194/215 +f 250/200/216 460/200/216 459/198/216 +f 254/205/217 464/205/217 463/203/217 +f 258/213/218 468/213/218 467/211/218 +f 384/115/219 594/115/219 593/117/219 +f 262/232/220 472/232/220 471/228/220 +f 266/271/221 476/271/221 475/264/221 +f 365/184/222 575/184/222 574/188/222 +f 270/279/223 480/279/223 479/277/223 +f 274/287/202 484/287/202 483/285/202 +f 278/296/224 488/296/224 487/294/224 +f 282/304/225 492/304/225 491/302/225 +f 286/311/226 496/311/226 495/310/226 +f 297/290/206 298/288/206 508/288/206 +f 302/280/227 512/280/227 511/282/227 +f 306/272/228 516/272/228 515/274/228 +f 310/258/229 520/258/229 519/260/229 +f 388/107/230 598/107/230 597/109/230 +f 314/249/231 524/249/231 523/250/231 +f 318/239/232 528/239/232 527/242/232 +f 369/146/233 579/146/233 578/154/233 +f 322/235/234 532/235/234 531/236/234 +f 326/246/235 536/246/235 535/243/235 +f 338/253/236 548/253/236 547/255/236 +f 345/227/237 346/226/237 556/226/237 +f 350/221/238 560/221/238 559/222/238 +f 354/216/239 564/216/239 563/217/239 +f 181/104/240 391/104/240 600/102/240 +f 297/290/206 507/290/206 506/292/206 +f 357/212/241 358/210/241 568/210/241 +f 183/106/242 393/106/242 392/103/242 +f 187/114/243 397/114/243 396/112/243 +f 191/122/244 401/122/244 400/120/244 +f 195/130/202 405/130/202 404/128/202 +f 199/138/245 409/138/245 408/136/245 +f 220/170/246 221/164/246 431/164/246 +f 203/145/247 413/145/247 412/144/247 +f 207/165/248 417/165/248 416/161/248 +f 211/173/249 421/173/249 420/171/249 +f 215/182/250 425/182/250 424/179/250 +f 219/174/251 429/174/251 428/177/251 +f 223/155/252 433/155/252 432/159/252 +f 374/135/253 584/135/253 583/137/253 +f 227/149/254 437/149/254 436/147/254 +f 362/199/255 572/199/255 571/204/255 +f 231/158/256 441/158/256 440/156/256 +f 225/150/257 435/150/257 434/152/257 +f 239/186/258 449/186/258 448/185/258 +f 243/191/259 453/315/259 452/313/259 +f 247/196/216 457/196/216 456/314/216 +f 251/201/216 461/201/216 460/200/216 +f 305/274/260 515/274/260 514/276/260 +f 255/207/261 465/207/261 464/205/261 +f 259/218/262 469/218/262 468/213/262 +f 263/247/263 473/247/263 472/232/263 +f 267/273/264 477/273/264 476/271/264 +f 271/281/265 481/281/265 480/279/265 +f 275/289/202 485/289/202 484/287/202 +f 279/298/266 489/298/266 488/296/266 +f 365/184/267 366/172/267 576/172/267 +f 283/306/268 493/306/268 492/304/268 +f 287/309/269 497/309/269 496/311/269 +f 291/301/270 501/301/270 500/303/270 +f 295/293/271 505/293/271 504/295/271 +f 328/257/272 538/257/272 537/251/272 +f 311/254/273 521/254/273 520/258/273 +f 290/303/274 500/303/274 499/305/274 +f 314/249/275 315/245/275 525/245/275 +f 319/237/276 529/237/276 528/239/276 +f 323/238/277 533/238/277 532/235/277 +f 327/251/278 537/251/278 536/246/278 +f 331/269/279 541/269/279 540/265/279 +f 370/143/280 580/143/280 579/146/280 +f 335/262/281 545/262/281 544/263/281 +f 339/248/282 549/248/282 548/253/282 +f 342/231/283 343/230/283 553/230/283 +f 347/225/284 557/225/284 556/226/284 +f 351/220/285 561/220/285 560/221/285 +f 358/210/286 359/209/286 569/209/286 +f 294/295/287 504/295/287 503/297/287 +f 371/141/288 581/141/288 580/143/288 +f 375/133/289 585/133/289 584/135/289 +f 379/125/206 589/125/206 588/127/206 +f 383/117/290 593/117/290 592/119/290 +f 387/109/291 597/109/291 596/111/291 +f 218/177/292 428/177/292 427/180/292 +f 288/307/293 498/307/293 497/309/293 +f 378/127/206 588/127/206 587/129/206 +f 184/108/294 394/108/294 393/106/294 +f 188/116/295 398/116/295 397/114/295 +f 192/124/202 402/124/202 401/122/202 +f 196/132/202 406/132/202 405/130/202 +f 339/248/296 340/241/296 550/241/296 +f 200/140/297 410/140/297 409/138/297 +f 204/148/298 414/148/298 413/145/298 +f 321/236/299 531/236/299 530/234/299 +f 208/167/300 418/167/300 417/165/300 +f 216/183/301 426/183/301 425/182/301 +f 224/152/302 434/152/302 433/155/302 +f 228/151/303 438/151/303 437/149/303 +f 232/160/304 442/160/304 441/158/304 +f 382/119/305 592/119/305 591/121/305 +f 236/175/306 446/175/306 445/169/306 +f 240/187/307 450/187/307 449/186/307 +f 362/199/308 363/192/308 573/192/308 +f 244/193/309 454/193/309 453/315/309 +f 248/197/216 458/197/216 457/196/216 +f 252/202/310 462/202/310 461/201/310 +f 256/208/311 466/208/311 465/207/311 +f 264/256/313 474/256/313 473/247/313 +f 222/159/314 432/159/314 431/164/314 +f 268/275/315 478/275/315 477/273/315 +f 272/283/202 482/283/202 481/281/202 +f 276/291/202 486/291/202 485/289/202 +f 280/300/316 490/300/316 489/298/316 +f 284/308/317 494/308/317 493/306/317 +f 386/111/318 596/111/318 595/113/318 +f 367/163/319 577/163/319 576/172/319 +f 296/292/320 506/292/320 505/293/320 +f 300/284/206 510/284/206 509/286/206 +f 348/223/321 558/223/321 557/225/321 +f 304/276/322 514/276/322 513/278/322 +f 308/267/323 518/267/323 517/270/323 +f 312/252/324 522/252/324 521/254/324 +f 315/245/325 316/244/325 526/244/325 +f 320/234/326 530/234/326 529/237/326 +f 324/240/327 534/240/327 533/238/327 +f 332/268/328 542/268/328 541/269/328 +f 336/259/329 546/259/329 545/262/329 +f 390/102/330 600/102/330 599/105/330 +f 344/229/331 554/229/331 553/230/331 +f 352/219/239 562/219/239 561/220/239 +f 356/214/239 566/317/239 565/316/239 +f 337/255/332 547/255/332 546/259/332 +f 334/263/333 544/263/333 543/266/333 +f 299/286/206 509/286/206 508/288/206 +f 185/110/334 395/110/334 394/108/334 +f 189/118/335 399/118/335 398/116/335 +f 193/126/202 403/126/202 402/124/202 +f 197/134/336 407/134/336 406/132/336 +f 201/142/337 411/142/337 410/140/337 +f 360/206/338 570/318/338 569/209/338 +f 205/157/410 415/157/410 414/148/410 +f 209/168/340 419/168/340 418/167/340 +f 213/178/341 423/178/341 422/176/341 +f 217/180/342 427/180/342 426/183/342 +f 303/278/343 513/278/343 512/280/343 +f 229/153/344 439/153/344 438/151/344 +f 233/162/345 443/162/345 442/160/345 +f 237/181/346 447/181/346 446/175/346 +f 241/189/347 451/312/347 450/187/347 +f 245/194/348 455/194/348 454/193/348 +f 249/198/216 459/198/216 458/197/216 +f 253/203/349 463/203/349 462/202/349 +f 364/188/350 574/188/350 573/192/350 +f 257/211/351 467/211/351 466/208/351 +f 345/227/352 555/227/352 554/229/352 +f 265/264/353 475/264/353 474/256/353 +f 269/277/354 479/277/354 478/275/354 +f 273/285/202 483/285/202 482/283/202 +f 277/294/355 487/294/355 486/291/355 +f 307/270/356 517/270/356 516/272/356 +f 281/302/357 491/302/357 490/300/357 +f 285/310/358 495/310/358 494/308/358 +f 289/305/359 499/305/359 498/307/359 +f 292/299/360 293/297/360 503/297/360 +f 368/154/361 578/154/361 577/163/361 +f 308/267/362 309/260/362 519/260/362 +f 313/250/363 523/250/363 522/252/363 +f 317/242/364 527/242/364 526/244/364 +f 301/282/206 511/282/206 510/284/206 +f 212/176/365 422/176/365 421/173/365 +f 330/265/366 540/265/366 539/261/366 +f 325/243/367 535/243/367 534/240/367 +f 329/261/368 539/261/368 538/257/368 +f 333/266/369 543/266/369 542/268/369 +f 292/299/370 502/299/370 501/301/370 +f 341/233/371 551/233/371 550/241/371 +f 349/222/372 559/222/372 558/223/372 +f 353/217/239 563/217/239 562/219/239 +f 357/212/373 567/212/373 566/317/373 +f 372/139/374 582/139/374 581/141/374 +f 361/204/375 571/204/375 570/318/375 +f 235/169/376 445/169/376 444/166/376 +f 372/139/377 373/137/377 583/137/377 +f 377/129/206 587/129/206 586/131/206 +f 381/121/206 591/121/206 590/123/206 +f 385/113/378 595/113/378 594/115/378 +f 389/105/379 599/105/379 598/107/379 +f 376/131/380 586/131/380 585/133/380 +f 220/170/381 430/170/381 429/174/381 +f 564/216/239 355/215/239 565/316/239 +f 469/218/312 260/224/312 470/224/312 +f 260/224/411 261/228/411 470/224/411 +f 471/228/411 470/224/411 261/228/411 diff --git a/Telegram/Resources/art/premium/coin_outer.obj b/Telegram/Resources/art/premium/coin_outer.obj new file mode 100644 index 00000000000000..de81610f687952 --- /dev/null +++ b/Telegram/Resources/art/premium/coin_outer.obj @@ -0,0 +1,2363 @@ +# 3D mesh decoded from .binobj (source of truth; baked back to .binobj at build time). +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v -0.332026988 24.7804146 -3.92194796 +v -0.332026988 27.6801491 -3.7071259 +v -0.332026988 24.7804146 1.64672995 +v -0.332026988 27.6801491 1.43190801 +v 2.50967407 24.6208248 -3.92194796 +v 2.83432794 27.5023289 -3.70712495 +v 2.50967407 24.6208248 1.64672995 +v 2.83432794 27.5023289 1.43190801 +v 5.31560421 24.144083 -3.92194796 +v 5.96086502 26.9711113 -3.70712495 +v 5.31560421 24.144083 1.64672995 +v 5.96086502 26.9711113 1.43190801 +v 8.05055237 23.3561535 -3.92194796 +v 9.00826359 26.0931683 -3.7071259 +v 8.05055141 23.3561535 1.64672995 +v 9.00826454 26.0931683 1.43190801 +v 10.6800575 22.2669792 -3.92194796 +v 11.9382019 24.879549 -3.7071259 +v 10.6800575 22.2669792 1.64672995 +v 11.9382029 24.879549 1.43190801 +v 13.1710749 20.8902416 -3.92194796 +v 14.7138395 23.3455086 -3.7071259 +v 13.1710758 20.8902416 1.64672995 +v 14.7138395 23.3455086 1.43190801 +v 15.4923172 19.2432308 -3.92194796 +v 17.3002644 21.5103416 -3.7071259 +v 15.4923182 19.2432308 1.64672995 +v 17.3002644 21.5103416 1.43190801 +v 17.6145287 17.3467083 -3.92194891 +v 19.6649513 19.3971272 -3.7071259 +v 17.6145287 17.3467083 1.64672899 +v 19.6649513 19.3971272 1.43190706 +v 19.5110512 15.2244959 -3.92194796 +v 21.778162 17.0324421 -3.7071259 +v 19.5110512 15.2244959 1.64672899 +v 21.778162 17.0324421 1.43190706 +v 21.158062 12.9032555 -3.92194891 +v 23.6133289 14.4460163 -3.7071259 +v 21.158062 12.9032555 1.64672899 +v 23.6133289 14.4460163 1.43190706 +v 22.5347996 10.4122343 -3.92194796 +v 25.1473694 11.6703806 -3.7071259 +v 22.5347996 10.4122343 1.64672899 +v 25.1473694 11.6703815 1.43190706 +v 23.6239738 7.78272915 -3.92194891 +v 26.3609905 8.74044228 -3.70712709 +v 23.6239738 7.78272915 1.64672899 +v 26.3609905 8.74044228 1.43190706 +v 24.4119034 5.04778099 -3.92194891 +v 27.2389317 5.69304085 -3.7071259 +v 24.4119034 5.04778099 1.64672899 +v 27.2389317 5.69304085 1.43190706 +v 24.8886471 2.24185109 -3.92194891 +v 27.7701511 2.56650496 -3.70712709 +v 24.8886471 2.24185109 1.64672899 +v 27.7701511 2.56650496 1.43190706 +v 25.0482349 -0.599849999 -3.92194891 +v 27.9479694 -0.599849999 -3.70712709 +v 25.0482349 -0.599849999 1.64672899 +v 27.9479694 -0.599849999 1.43190598 +v 24.8886471 -3.44155097 -3.92194891 +v 27.7701511 -3.76620603 -3.70712709 +v 24.8886471 -3.44155097 1.64672899 +v 27.7701511 -3.76620603 1.43190598 +v 24.4119034 -6.24748087 -3.9219501 +v 27.2389297 -6.89274216 -3.70712709 +v 24.4119034 -6.24748087 1.64672899 +v 27.2389297 -6.89274216 1.43190598 +v 23.6239758 -8.98242855 -3.9219501 +v 26.3609905 -9.94014168 -3.70712805 +v 23.6239758 -8.98242855 1.64672899 +v 26.3609905 -9.94014168 1.43190598 +v 22.5348015 -11.6119366 -3.92194891 +v 25.1473694 -12.8700819 -3.70712709 +v 22.5348015 -11.6119366 1.64672804 +v 25.1473694 -12.8700809 1.43190598 +v 21.158062 -14.1029539 -3.9219501 +v 23.6133308 -15.6457186 -3.70712709 +v 21.158062 -14.1029539 1.64672804 +v 23.6133308 -15.6457186 1.43190598 +v 19.5110512 -16.4241962 -3.9219501 +v 21.778162 -18.2321434 -3.70712805 +v 19.5110512 -16.4241962 1.64672804 +v 21.778162 -18.2321434 1.43190598 +v 17.6145306 -18.5464058 -3.9219501 +v 19.6649475 -20.5968285 -3.70712805 +v 17.6145306 -18.5464058 1.64672804 +v 19.6649475 -20.5968285 1.43190598 +v 15.4923182 -20.4429283 -3.9219501 +v 17.3002644 -22.7100391 -3.70712805 +v 15.4923191 -20.4429283 1.64672804 +v 17.3002644 -22.7100391 1.43190598 +v 13.1710758 -22.0899391 -3.9219501 +v 14.7138376 -24.5452061 -3.70712805 +v 13.1710768 -22.0899391 1.64672804 +v 14.7138386 -24.5452061 1.43190598 +v 10.6800556 -23.4666767 -3.9219501 +v 11.9382029 -26.0792465 -3.70712805 +v 10.6800566 -23.4666767 1.64672804 +v 11.9382038 -26.0792465 1.43190503 +v 8.05055141 -24.555851 -3.9219501 +v 9.00826454 -27.2928677 -3.70712805 +v 8.05055141 -24.555851 1.64672804 +v 9.00826454 -27.2928677 1.43190503 +v 5.31560421 -25.3437805 -3.9219501 +v 5.96086502 -28.1708088 -3.70712805 +v 5.31560421 -25.3437805 1.64672804 +v 5.96086502 -28.1708088 1.43190503 +v 2.50967407 -25.8205242 -3.9219501 +v 2.83432794 -28.7020283 -3.70712805 +v 2.50967407 -25.8205242 1.64672804 +v 2.83432794 -28.7020283 1.43190503 +v -0.332026988 -25.9801121 -3.92195106 +v -0.332026988 -28.8798466 -3.70712805 +v -0.332026988 -25.9801121 1.64672804 +v -0.332026988 -28.8798466 1.43190503 +v -3.17372799 -25.8205242 -3.92195106 +v -3.49838305 -28.7020283 -3.70712805 +v -3.17372799 -25.8205242 1.64672804 +v -3.49838305 -28.7020283 1.43190503 +v -5.97965813 -25.3437805 -3.9219501 +v -6.62491798 -28.1708069 -3.70712805 +v -5.97965813 -25.3437805 1.64672804 +v -6.62491894 -28.1708069 1.43190503 +v -8.71460724 -24.5558529 -3.9219501 +v -9.67232037 -27.2928677 -3.70712805 +v -8.71460724 -24.5558529 1.64672804 +v -9.67231941 -27.2928677 1.43190503 +v -11.3441153 -23.4666786 -3.9219501 +v -12.6022596 -26.0792465 -3.70712805 +v -11.3441143 -23.4666786 1.64672804 +v -12.6022587 -26.0792465 1.43190503 +v -13.8351326 -22.0899391 -3.9219501 +v -15.3778973 -24.545208 -3.70712805 +v -13.8351316 -22.0899391 1.64672804 +v -15.3778963 -24.545208 1.43190503 +v -16.156374 -20.4429283 -3.9219501 +v -17.9643192 -22.7100391 -3.70712805 +v -16.156374 -20.4429283 1.64672804 +v -17.9643192 -22.7100391 1.43190598 +v -18.2785854 -18.5464077 -3.9219501 +v -20.3290081 -20.5968246 -3.70712805 +v -18.2785854 -18.5464077 1.64672804 +v -20.3290081 -20.5968246 1.43190598 +v -20.175108 -16.4241962 -3.9219501 +v -22.4422188 -18.2321415 -3.70712805 +v -20.175108 -16.4241962 1.64672804 +v -22.4422188 -18.2321415 1.43190598 +v -21.8221169 -14.1029549 -3.9219501 +v -24.2773857 -15.6457176 -3.70712709 +v -21.8221169 -14.1029549 1.64672804 +v -24.2773857 -15.6457176 1.43190598 +v -23.1988564 -11.6119356 -3.92194891 +v -25.8114262 -12.8700819 -3.70712709 +v -23.1988564 -11.6119356 1.64672804 +v -25.8114262 -12.8700809 1.43190598 +v -24.2880306 -8.98242855 -3.9219501 +v -27.0250454 -9.94014263 -3.70712805 +v -24.2880306 -8.98242855 1.64672804 +v -27.0250454 -9.94014263 1.43190598 +v -25.0759602 -6.24748087 -3.92194891 +v -27.9029903 -6.89274216 -3.70712709 +v -25.0759602 -6.24748087 1.64672899 +v -27.9029903 -6.89274216 1.43190598 +v -25.552702 -3.44155192 -3.92194891 +v -28.434206 -3.76620603 -3.70712709 +v -25.552702 -3.44155192 1.64672899 +v -28.434206 -3.76620603 1.43190598 +v -25.7122917 -0.599849999 -3.92194891 +v -28.6120262 -0.599849999 -3.70712709 +v -25.7122917 -0.599849999 1.64672899 +v -28.6120262 -0.599849999 1.43190598 +v -25.552702 2.24185109 -3.92194891 +v -28.434206 2.56650496 -3.70712709 +v -25.552702 2.24185109 1.64672899 +v -28.434206 2.56650496 1.43190706 +v -25.0759602 5.04778099 -3.92194891 +v -27.9029884 5.69304085 -3.70712709 +v -25.0759602 5.04778099 1.64672899 +v -27.9029884 5.69304085 1.43190706 +v -24.2880306 7.78272915 -3.92194891 +v -27.0250454 8.74044132 -3.70712709 +v -24.2880306 7.78272915 1.64672899 +v -27.0250454 8.74044132 1.43190706 +v -23.1988564 10.4122362 -3.92194796 +v -25.8114262 11.6703806 -3.7071259 +v -23.1988564 10.4122362 1.64672899 +v -25.8114262 11.6703815 1.43190706 +v -21.8221188 12.9032536 -3.92194891 +v -24.2773857 14.4460182 -3.7071259 +v -21.8221188 12.9032536 1.64672899 +v -24.2773857 14.4460182 1.43190706 +v -20.175108 15.2244959 -3.92194796 +v -22.4422188 17.0324421 -3.7071259 +v -20.175108 15.2244959 1.64672899 +v -22.4422188 17.0324421 1.43190706 +v -18.2785854 17.3467064 -3.92194796 +v -20.3290043 19.397131 -3.7071259 +v -18.2785854 17.3467064 1.64672995 +v -20.3290043 19.397131 1.43190706 +v -16.156374 19.2432308 -3.92194796 +v -17.9643192 21.5103416 -3.7071259 +v -16.156374 19.2432308 1.64672995 +v -17.9643192 21.5103416 1.43190801 +v -13.8351336 20.8902397 -3.92194796 +v -15.3778954 23.3455086 -3.7071259 +v -13.8351326 20.8902397 1.64672995 +v -15.3778944 23.3455086 1.43190706 +v -11.3441133 22.2669792 -3.92194796 +v -12.6022606 24.879549 -3.7071259 +v -11.3441124 22.2669792 1.64672995 +v -12.6022596 24.879549 1.43190801 +v -8.71460724 23.3561535 -3.92194796 +v -9.67232132 26.0931683 -3.7071259 +v -8.71460724 23.3561535 1.64672995 +v -9.67232037 26.0931683 1.43190801 +v -5.97965813 24.144083 -3.92194796 +v -6.62491798 26.9711132 -3.7071259 +v -5.97965813 24.144083 1.64672995 +v -6.62491894 26.9711132 1.43190801 +v -3.17372799 24.6208248 -3.92194796 +v -3.49838305 27.5023289 -3.7071259 +v -3.17372799 24.6208248 1.64672995 +v -3.49838305 27.5023289 1.43190801 +v 2.80981803 27.2846775 -3.92194796 +v -0.332026988 27.4611206 -3.92194796 +v -0.332026988 27.4611206 1.64672995 +v 2.80981803 27.2846775 1.64672995 +v 5.912117 26.7575779 -3.92194796 +v 5.912117 26.7575779 1.64672995 +v 8.93593216 25.8864288 -3.92194796 +v 8.93593311 25.8864288 1.64672995 +v 11.8431702 24.6822128 -3.92194796 +v 11.8431711 24.6822128 1.64672995 +v 14.5972986 23.1600609 -3.92194796 +v 14.5972996 23.1600609 1.64672995 +v 17.1637115 21.3390923 -3.92194796 +v 17.1637115 21.3390923 1.64672995 +v 19.5100765 19.2422523 -3.92194796 +v 19.5100765 19.2422523 1.64672995 +v 21.6069145 16.8958874 -3.92194891 +v 21.6069145 16.8958874 1.64672899 +v 23.4278812 14.3294754 -3.92194796 +v 23.4278812 14.3294754 1.64672899 +v 24.9500332 11.5753489 -3.92194891 +v 24.9500332 11.5753498 1.64672899 +v 26.1542492 8.66810989 -3.92194891 +v 26.1542492 8.66810989 1.64672899 +v 27.0253983 5.64429379 -3.92194891 +v 27.0253983 5.64429379 1.64672899 +v 27.5524979 2.54199505 -3.92194891 +v 27.5524979 2.54199505 1.64672899 +v 27.7289429 -0.599849999 -3.92194891 +v 27.7289429 -0.599849999 1.64672899 +v 27.5524979 -3.74169493 -3.92194891 +v 27.5524979 -3.74169493 1.64672899 +v 27.0253963 -6.84399509 -3.92194891 +v 27.0253963 -6.84399509 1.64672804 +v 26.1542511 -9.86781025 -3.92194891 +v 26.1542511 -9.86781025 1.64672804 +v 24.9500332 -12.7750511 -3.9219501 +v 24.9500332 -12.7750502 1.64672804 +v 23.4278812 -15.5291777 -3.9219501 +v 23.4278812 -15.5291777 1.64672804 +v 21.6069145 -18.0955887 -3.9219501 +v 21.6069145 -18.0955887 1.64672804 +v 19.5100746 -20.4419537 -3.9219501 +v 19.5100746 -20.4419537 1.64672804 +v 17.1637096 -22.5387917 -3.9219501 +v 17.1637096 -22.5387917 1.64672804 +v 14.5972967 -24.3597584 -3.92195106 +v 14.5972977 -24.3597584 1.64672804 +v 11.8431711 -25.8819103 -3.9219501 +v 11.8431721 -25.8819103 1.64672804 +v 8.93593311 -27.0861263 -3.9219501 +v 8.93593407 -27.0861263 1.64672804 +v 5.91211796 -27.9572754 -3.9219501 +v 5.912117 -27.9572754 1.64672804 +v 2.80981803 -28.484375 -3.92195106 +v 2.80981803 -28.484375 1.64672804 +v -0.332026988 -28.66082 -3.92195106 +v -0.332026988 -28.66082 1.64672804 +v -3.47387195 -28.484375 -3.92195106 +v -3.47387195 -28.484375 1.64672697 +v -6.57617092 -27.9572735 -3.92195106 +v -6.57617092 -27.9572735 1.64672697 +v -9.59998894 -27.0861282 -3.9219501 +v -9.59998798 -27.0861282 1.64672804 +v -12.5072279 -25.8819103 -3.9219501 +v -12.5072269 -25.8819103 1.64672804 +v -15.2613564 -24.3597584 -3.9219501 +v -15.2613554 -24.3597584 1.64672804 +v -17.8277664 -22.5387917 -3.9219501 +v -17.8277664 -22.5387917 1.64672804 +v -20.1741333 -20.4419518 -3.9219501 +v -20.1741333 -20.4419518 1.64672804 +v -22.2709694 -18.0955887 -3.9219501 +v -22.2709694 -18.0955887 1.64672804 +v -24.091938 -15.5291767 -3.9219501 +v -24.091938 -15.5291767 1.64672804 +v -25.6140881 -12.7750511 -3.9219501 +v -25.6140881 -12.7750492 1.64672804 +v -26.818306 -9.8678112 -3.92194891 +v -26.818306 -9.8678112 1.64672804 +v -27.6894569 -6.84399509 -3.92194891 +v -27.6894569 -6.84399509 1.64672899 +v -28.2165546 -3.74169493 -3.9219501 +v -28.2165546 -3.74169493 1.64672899 +v -28.3929977 -0.599849999 -3.92194891 +v -28.3929977 -0.599849999 1.64672899 +v -28.2165546 2.54199505 -3.92194891 +v -28.2165546 2.54199505 1.64672899 +v -27.689455 5.64429379 -3.92194796 +v -27.689455 5.64429379 1.64672899 +v -26.818306 8.66810894 -3.92194796 +v -26.818306 8.66810894 1.64672899 +v -25.61409 11.5753489 -3.92194891 +v -25.61409 11.5753498 1.64672899 +v -24.091938 14.3294773 -3.92194796 +v -24.091938 14.3294773 1.64672995 +v -22.2709694 16.8958893 -3.92194796 +v -22.2709694 16.8958893 1.64672995 +v -20.1741295 19.2422562 -3.92194796 +v -20.1741295 19.2422562 1.64672995 +v -17.8277645 21.3390923 -3.92194796 +v -17.8277645 21.3390923 1.64672995 +v -15.2613544 23.1600609 -3.92194796 +v -15.2613525 23.1600609 1.64672995 +v -12.5072289 24.6822109 -3.92194796 +v -12.5072279 24.6822109 1.64672995 +v -9.59998894 25.8864288 -3.92194796 +v -9.59998798 25.8864288 1.64672995 +v -6.57617188 26.7575798 -3.92194796 +v -6.57617188 26.7575798 1.64672995 +v -3.47387195 27.2846775 -3.92194796 +v -3.47387195 27.2846775 1.64672995 +v -0.332026988 24.7804146 0.633257985 +v 2.50967407 24.6208248 0.633257985 +v 5.31560421 24.144083 0.633257985 +v 8.05055141 23.3561535 0.633257985 +v 10.6800575 22.2669792 0.633257985 +v 13.1710758 20.8902416 0.633257985 +v 15.4923182 19.2432308 0.633257985 +v 17.6145287 17.3467083 0.633257985 +v 19.5110512 15.2244959 0.633256972 +v 21.158062 12.9032555 0.633256972 +v 22.5347996 10.4122343 0.633256972 +v 23.6239738 7.78272915 0.633256972 +v 24.4119034 5.04778099 0.633256972 +v 24.8886471 2.24185109 0.633256972 +v 25.0482349 -0.599849999 0.633256972 +v 24.8886471 -3.44155097 0.633256972 +v 24.4119034 -6.24748087 0.633256972 +v 23.6239758 -8.98242855 0.633256018 +v 22.5348015 -11.6119366 0.633256018 +v 21.158062 -14.1029539 0.633256018 +v 19.5110512 -16.4241962 0.633256018 +v 17.6145306 -18.5464058 0.633256018 +v 15.4923191 -20.4429283 0.633256018 +v 13.1710768 -22.0899391 0.633256018 +v 10.6800566 -23.4666767 0.633256018 +v 8.05055141 -24.555851 0.633256018 +v 5.31560421 -25.3437805 0.633256018 +v 2.50967407 -25.8205242 0.633256018 +v -0.332026988 -25.9801121 0.633256018 +v -3.17372894 -25.8205242 0.633256018 +v -5.97965908 -25.3437805 0.633256018 +v -8.71460724 -24.5558529 0.633256018 +v -11.3441143 -23.4666786 0.633256018 +v -13.8351316 -22.0899391 0.633256018 +v -16.156374 -20.4429283 0.633256018 +v -18.2785854 -18.5464077 0.633256018 +v -20.175108 -16.4241962 0.633256018 +v -21.8221169 -14.1029549 0.633256018 +v -23.1988564 -11.6119356 0.633256018 +v -24.2880306 -8.98242855 0.633256018 +v -25.0759602 -6.24748087 0.633256972 +v -25.552702 -3.44155192 0.633256972 +v -25.7122917 -0.599849999 0.633256972 +v -25.552702 2.24185109 0.633256972 +v -25.0759602 5.04778099 0.633256972 +v -24.2880306 7.78272915 0.633256972 +v -23.1988564 10.4122362 0.633256972 +v -21.8221188 12.9032536 0.633256972 +v -20.175108 15.2244959 0.633257985 +v -18.2785854 17.3467064 0.633257985 +v -16.156374 19.2432308 0.633257985 +v -13.8351326 20.8902397 0.633257985 +v -11.3441124 22.2669792 0.633257985 +v -8.71460724 23.3561535 0.633257985 +v -5.97965813 24.144083 0.633257985 +v -3.17372894 24.6208248 0.633257985 +v 2.50967407 24.6208248 -2.880476 +v -0.332026988 24.7804146 -2.880476 +v 5.31560421 24.144083 -2.880476 +v 8.05055237 23.3561535 -2.880476 +v 10.6800575 22.2669792 -2.880476 +v 13.1710758 20.8902416 -2.880476 +v 15.4923182 19.2432308 -2.880476 +v 17.6145287 17.3467083 -2.880476 +v 19.5110512 15.2244959 -2.880476 +v 21.158062 12.9032555 -2.880476 +v 22.5347996 10.4122343 -2.880476 +v 23.6239738 7.78272915 -2.88047695 +v 24.4119034 5.04778099 -2.88047695 +v 24.8886471 2.24185109 -2.88047695 +v 25.0482349 -0.599849999 -2.88047695 +v 24.8886471 -3.44155097 -2.88047695 +v 24.4119034 -6.24748087 -2.88047695 +v 23.6239758 -8.98242855 -2.88047695 +v 22.5348015 -11.6119366 -2.88047695 +v 21.158062 -14.1029539 -2.88047791 +v 19.5110512 -16.4241962 -2.88047695 +v 17.6145306 -18.5464058 -2.88047791 +v 15.4923191 -20.4429283 -2.88047791 +v 13.1710768 -22.0899391 -2.88047791 +v 10.6800566 -23.4666767 -2.88047791 +v 8.05055237 -24.555851 -2.88047791 +v 5.31560421 -25.3437805 -2.88047791 +v 2.50967407 -25.8205242 -2.88047791 +v -0.332026988 -25.9801121 -2.88047791 +v -3.17372894 -25.8205242 -2.88047791 +v -5.97965908 -25.3437805 -2.88047791 +v -8.71460629 -24.5558529 -2.88047791 +v -11.3441143 -23.4666786 -2.88047791 +v -13.8351316 -22.0899391 -2.88047791 +v -16.156374 -20.4429283 -2.88047791 +v -18.2785854 -18.5464077 -2.88047791 +v -20.175108 -16.4241962 -2.88047695 +v -21.8221169 -14.1029549 -2.88047791 +v -23.1988564 -11.6119356 -2.88047695 +v -24.2880306 -8.98242855 -2.88047695 +v -25.0759602 -6.24748087 -2.88047695 +v -25.552702 -3.44155192 -2.88047695 +v -25.7122917 -0.599849999 -2.88047695 +v -25.552702 2.24185109 -2.88047695 +v -25.0759602 5.04778099 -2.88047695 +v -24.2880306 7.78272915 -2.880476 +v -23.1988564 10.4122362 -2.880476 +v -21.8221188 12.9032536 -2.880476 +v -20.175108 15.2244959 -2.880476 +v -18.2785854 17.3467064 -2.880476 +v -16.156374 19.2432308 -2.880476 +v -13.8351326 20.8902397 -2.880476 +v -11.3441124 22.2669792 -2.880476 +v -8.71460629 23.3561535 -2.880476 +v -5.97965813 24.144083 -2.880476 +v -3.17372894 24.6208248 -2.880476 +v -0.332026988 24.7804127 0.521257997 +v 2.48780489 24.6208229 0.521257997 +v 5.27214098 24.1440811 0.521257997 +v 7.98604107 23.3561516 0.521257997 +v 10.5953112 22.2669773 0.521257997 +v 13.0671577 20.8902378 0.521257997 +v 15.3705359 19.2432289 0.521257997 +v 17.4764156 17.3467064 0.521256983 +v 19.3583431 15.2244949 0.521256983 +v 20.9926777 12.9032536 0.521256983 +v 22.35882 10.4122334 0.521256983 +v 23.4396133 7.78272915 0.521256983 +v 24.2214775 5.04778004 0.521256983 +v 24.6945534 2.24185109 0.521256983 +v 24.8529148 -0.599849999 0.521256983 +v 24.6945534 -3.44155097 0.52125603 +v 24.2214775 -6.24748087 0.52125603 +v 23.4396133 -8.98242855 0.52125603 +v 22.35882 -11.6119356 0.52125603 +v 20.9926777 -14.102953 0.52125603 +v 19.3583431 -16.4241962 0.52125603 +v 17.4764175 -18.5464039 0.52125603 +v 15.3705368 -20.4429264 0.52125603 +v 13.0671587 -22.0899372 0.52125603 +v 10.5953093 -23.4666748 0.52125603 +v 7.98604107 -24.5558491 0.52125603 +v 5.27214098 -25.3437786 0.521255016 +v 2.48780489 -25.8205223 0.521255016 +v -0.332026988 -25.9801102 0.521255016 +v -3.15185905 -25.8205223 0.52125603 +v -5.9361949 -25.3437786 0.52125603 +v -8.65009499 -24.5558491 0.52125603 +v -11.259366 -23.4666748 0.52125603 +v -13.7312136 -22.0899372 0.52125603 +v -16.0345917 -20.4429264 0.52125603 +v -18.1404686 -18.5464058 0.52125603 +v -20.022398 -16.4241943 0.52125603 +v -21.6567326 -14.1029539 0.52125603 +v -23.0228748 -11.6119337 0.52125603 +v -24.1036701 -8.98242855 0.52125603 +v -24.8855343 -6.24748087 0.52125603 +v -25.3586082 -3.44155097 0.521256983 +v -25.5169697 -0.599849999 0.521256983 +v -25.3586082 2.24185109 0.521256983 +v -24.8855343 5.04778004 0.521256983 +v -24.1036701 7.7827282 0.521256983 +v -23.0228767 10.4122343 0.521256983 +v -21.6567326 12.9032526 0.521256983 +v -20.0223999 15.2244949 0.521256983 +v -18.1404724 17.3467045 0.521256983 +v -16.0345917 19.2432289 0.521256983 +v -13.7312145 20.8902378 0.521257997 +v -11.2593651 22.2669773 0.521257997 +v -8.65009499 23.3561516 0.521257997 +v -5.93619394 24.1440811 0.521257997 +v -3.15185905 24.6208229 0.521257997 +v 2.48780489 24.6208229 -2.76847601 +v -0.332026988 24.7804127 -2.76847601 +v 5.27214098 24.1440811 -2.76847601 +v 7.98604107 23.3561516 -2.76847601 +v 10.5953112 22.2669773 -2.76847601 +v 13.0671577 20.8902378 -2.76847601 +v 15.3705359 19.2432289 -2.76847601 +v 17.4764156 17.3467064 -2.76847601 +v 19.3583431 15.2244949 -2.76847601 +v 20.9926777 12.9032536 -2.76847601 +v 22.35882 10.4122334 -2.76847601 +v 23.4396133 7.78272915 -2.76847696 +v 24.2214775 5.04778004 -2.76847601 +v 24.6945534 2.24185109 -2.76847696 +v 24.8529148 -0.599849999 -2.76847696 +v 24.6945534 -3.44155097 -2.76847696 +v 24.2214775 -6.24748087 -2.76847696 +v 23.4396133 -8.98242855 -2.76847696 +v 22.35882 -11.6119356 -2.76847696 +v 20.9926777 -14.102953 -2.76847792 +v 19.3583431 -16.4241962 -2.76847792 +v 17.4764175 -18.5464039 -2.76847792 +v 15.3705368 -20.4429264 -2.76847792 +v 13.0671587 -22.0899372 -2.76847792 +v 10.5953093 -23.4666748 -2.76847792 +v 7.98604107 -24.5558491 -2.76847792 +v 5.27214098 -25.3437786 -2.76847792 +v 2.48780489 -25.8205223 -2.76847792 +v -0.332026988 -25.9801102 -2.76847792 +v -3.15185905 -25.8205223 -2.76847792 +v -5.9361949 -25.3437786 -2.76847792 +v -8.65009499 -24.5558491 -2.76847792 +v -11.259366 -23.4666748 -2.76847792 +v -13.7312136 -22.0899372 -2.76847792 +v -16.0345917 -20.4429264 -2.76847792 +v -18.1404686 -18.5464058 -2.76847792 +v -20.022398 -16.4241943 -2.76847696 +v -21.6567326 -14.1029539 -2.76847792 +v -23.0228748 -11.6119337 -2.76847792 +v -24.1036701 -8.98242855 -2.76847696 +v -24.8855343 -6.24748087 -2.76847696 +v -25.3586082 -3.44155097 -2.76847696 +v -25.5169697 -0.599849999 -2.76847696 +v -25.3586082 2.24185109 -2.76847696 +v -24.8855343 5.04778004 -2.76847696 +v -24.1036701 7.7827282 -2.76847601 +v -23.0228767 10.4122343 -2.76847696 +v -21.6567326 12.9032526 -2.76847601 +v -20.0223999 15.2244949 -2.76847601 +v -18.1404724 17.3467045 -2.76847601 +v -16.0345917 19.2432289 -2.76847601 +v -13.7312145 20.8902378 -2.76847601 +v -11.2593651 22.2669773 -2.76847601 +v -8.65009499 23.3561516 -2.76847601 +v -5.9361949 24.1440811 -2.76847601 +v -3.15185905 24.6208229 -2.76847601 +vt 0.443412006 0.981693983 +vt 0.498077005 0.98476398 +vt 0.606719971 0.97252202 +vt 0.659331977 0.957364976 +vt 0.709914982 0.93641299 +vt 0.757834971 0.909928977 +vt 0.802488029 0.878246009 +vt 0.843312979 0.841762006 +vt 0.879796982 0.800936997 +vt 0.91148001 0.756283998 +vt 0.937964022 0.70836401 +vt 0.958917022 0.657781005 +vt 0.974074006 0.605168998 +vt 0.983245015 0.551190972 +vt 0.986315012 0.496526003 +vt 0.983245015 0.441861004 +vt 0.974074006 0.387883008 +vt 0.958917022 0.335271001 +vt 0.937964022 0.284687996 +vt 0.91148001 0.236768007 +vt 0.879796982 0.192114994 +vt 0.843312979 0.151289999 +vt 0.802488029 0.114807002 +vt 0.757834971 0.0831229985 +vt 0.709914982 0.0566390008 +vt 0.659331977 0.0356869996 +vt 0.606719971 0.0205290001 +vt 0.552743018 0.0113580003 +vt 0.498077005 0.00828799978 +vt 0.443412006 0.0113580003 +vt 0.38943401 0.0205290001 +vt 0.336822003 0.0356869996 +vt 0.286238998 0.0566390008 +vt 0.238318995 0.0831229985 +vt 0.193665996 0.114807002 +vt 0.152841002 0.151289999 +vt 0.116357997 0.192114994 +vt 0.0846740007 0.236768007 +vt 0.0581899993 0.284687996 +vt 0.0372380018 0.335271001 +vt 0.0220810007 0.387883008 +vt 0.0129089998 0.441861004 +vt 0.00984000042 0.496526003 +vt 0.0129089998 0.551190972 +vt 0.0220810007 0.605168998 +vt 0.0372380018 0.657781005 +vt 0.0581899993 0.70836401 +vt 0.0846740007 0.756283998 +vt 0.116357997 0.800936997 +vt 0.152841002 0.841762006 +vt 0.193665996 0.878246009 +vt 0.238318995 0.909928977 +vt 0.286238998 0.93641299 +vt 0.286238998 0.93641299 +vt 0.336822003 0.957364976 +vt 0.38943401 0.972522974 +vt 0.552743018 0.981693983 +vt 0.911664009 0.351806015 +vt 0.892859995 0.306409001 +vt 0.498077005 0.980982006 +vt 0.55231899 0.977936029 +vt 0.605879009 0.96883601 +vt 0.658083022 0.953796029 +vt 0.70827502 0.933005989 +vt 0.755823016 0.906727016 +vt 0.757834971 0.909928977 +vt 0.800131023 0.875289023 +vt 0.840640008 0.839088023 +vt 0.876839995 0.798579991 +vt 0.908277988 0.754271984 +vt 0.934557021 0.706723988 +vt 0.955347002 0.65653199 +vt 0.970386982 0.604327977 +vt 0.979487002 0.550768018 +vt 0.982532978 0.496526003 +vt 0.979487002 0.442283988 +vt 0.970386982 0.388725013 +vt 0.955347002 0.336519986 +vt 0.934557021 0.286327988 +vt 0.908277988 0.238780007 +vt 0.876839995 0.194472 +vt 0.840640008 0.153963998 +vt 0.800131023 0.117762998 +vt 0.755823016 0.0863249972 +vt 0.70827502 0.0600459985 +vt 0.658083022 0.039255999 +vt 0.605879009 0.024216 +vt 0.55231899 0.0151159996 +vt 0.498077005 0.0120700002 +vt 0.44383499 0.0151159996 +vt 0.390276015 0.024216 +vt 0.338070989 0.039255999 +vt 0.338070989 0.039255999 +vt 0.28787899 0.0600459985 +vt 0.240330994 0.0863249972 +vt 0.196023002 0.117762998 +vt 0.155515 0.153963998 +vt 0.119314 0.194472 +vt 0.0878759995 0.238780007 +vt 0.0615970008 0.286327988 +vt 0.0408070013 0.336519986 +vt 0.0257670004 0.388725013 +vt 0.0166669991 0.442283988 +vt 0.0136209996 0.496526003 +vt 0.0166669991 0.550768018 +vt 0.0257670004 0.604327977 +vt 0.0408070013 0.65653199 +vt 0.0615970008 0.706723988 +vt 0.0878759995 0.754271984 +vt 0.119314 0.798579991 +vt 0.155515 0.839088023 +vt 0.196024001 0.875289023 +vt 0.240330994 0.906727016 +vt 0.240330994 0.906727016 +vt 0.28787899 0.933005989 +vt 0.338070989 0.953796029 +vt 0.338070989 0.953796029 +vt 0.390276015 0.96883601 +vt 0.44383499 0.977936029 +vt 0.353356987 0.0829399973 +vt 0.400573999 0.0693370029 +vt 0.400573999 0.0693370029 +vt 0.498077005 0.934701979 +vt 0.547137022 0.93194598 +vt 0.595579982 0.923716009 +vt 0.642798007 0.910112977 +vt 0.688193977 0.89130801 +vt 0.642796993 0.910112977 +vt 0.73119998 0.867540002 +vt 0.771274984 0.83910501 +vt 0.807914019 0.806362987 +vt 0.840655982 0.769724011 +vt 0.869090974 0.729649007 +vt 0.892859995 0.686643004 +vt 0.911664009 0.641246021 +vt 0.925266981 0.594029009 +vt 0.933497012 0.54558599 +vt 0.936251998 0.496526003 +vt 0.933497012 0.447465986 +vt 0.925266981 0.399022996 +vt 0.869090974 0.263402998 +vt 0.840655982 0.223327994 +vt 0.807914019 0.186689004 +vt 0.771274984 0.153946996 +vt 0.771274984 0.153946996 +vt 0.73119998 0.125512004 +vt 0.688193977 0.101744004 +vt 0.642796993 0.0829399973 +vt 0.595579982 0.0693370029 +vt 0.547137022 0.0611060001 +vt 0.498077005 0.0583509989 +vt 0.449016988 0.0611060001 +vt 0.307960004 0.101744004 +vt 0.264954001 0.125512004 +vt 0.224878997 0.153946996 +vt 0.188240007 0.186689004 +vt 0.155497998 0.223327994 +vt 0.127063006 0.263402998 +vt 0.103294998 0.306409001 +vt 0.0844909996 0.351806015 +vt 0.0708879977 0.399022996 +vt 0.0626569986 0.447465986 +vt 0.0599020012 0.496526003 +vt 0.0626569986 0.54558599 +vt 0.0708879977 0.594029009 +vt 0.0844909996 0.641246021 +vt 0.103294998 0.686643004 +vt 0.127063006 0.729649007 +vt 0.155497998 0.769724011 +vt 0.188240007 0.806362987 +vt 0.224878997 0.83910501 +vt 0.264954001 0.867540002 +vt 0.307960004 0.89130801 +vt 0.353356987 0.910112977 +vt 0.400573999 0.923716009 +vt 0.449016988 0.93194598 +vt 0.354470998 0.910112977 +vt 0.401324987 0.923716009 +vt 0.866235971 0.263402998 +vt 0.642798007 0.0829399973 +vt 0.546760023 0.0611060001 +vt 0.594829977 0.923716009 +vt 0.449393988 0.93194598 +vt 0.449393988 0.0611060001 +vt 0.641683996 0.910112977 +vt 0.498077005 0.934701025 +vt 0.401324987 0.0693370029 +vt 0.686730981 0.89130801 +vt 0.354470998 0.0829399973 +vt 0.729405999 0.867540002 +vt 0.309423 0.101744004 +vt 0.769173026 0.83910501 +vt 0.266748011 0.125512004 +vt 0.805528998 0.806362987 +vt 0.226981997 0.153946996 +vt 0.838020027 0.769724011 +vt 0.190624997 0.186689004 +vt 0.866235971 0.729649007 +vt 0.158133999 0.223327994 +vt 0.889820993 0.686643004 +vt 0.129919007 0.263402998 +vt 0.908481002 0.641246021 +vt 0.106333002 0.306409001 +vt 0.92197901 0.594029009 +vt 0.0876739994 0.351806015 +vt 0.930145979 0.54558599 +vt 0.0741750002 0.399022996 +vt 0.932880998 0.496526003 +vt 0.0660080016 0.447465986 +vt 0.930145979 0.447465986 +vt 0.0632740036 0.496526003 +vt 0.92197901 0.399022996 +vt 0.0660080016 0.54558599 +vt 0.908481002 0.351806015 +vt 0.0741750002 0.594029009 +vt 0.889820993 0.306409001 +vt 0.0876739994 0.641246021 +vt 0.106333002 0.686643004 +vt 0.838020027 0.223327994 +vt 0.129919007 0.729649007 +vt 0.805528998 0.186689004 +vt 0.158133999 0.769724011 +vt 0.769173026 0.153946996 +vt 0.190624997 0.806362987 +vt 0.729405999 0.125512004 +vt 0.226981997 0.83910501 +vt 0.686730981 0.101744004 +vt 0.266748011 0.867540002 +vt 0.641683996 0.0829399973 +vt 0.309423 0.89130801 +vt 0.594829977 0.0693370029 +vt 0.546760023 0.93194598 +vt 0.401324004 0.0693370029 +vn -0.103500001 0.918200016 0.382299989 +vn -0 0.924000025 -0.382299989 +vn -0.103500001 0.918200016 -0.382299989 +vn 0.205599993 0.900900006 0.382299989 +vn 0.305200011 0.872200012 -0.382299989 +vn 0.205599993 0.900900006 -0.382299989 +vn 0.400900006 0.832499981 0.382299989 +vn 0.400900006 0.832499981 -0.382299989 +vn 0.491600007 0.782400012 -0.382299989 +vn 0.576099992 0.72240001 0.382299989 +vn 0.576099992 0.72240001 -0.382299989 +vn 0.653400004 0.653400004 -0.382299989 +vn 0.653400004 0.653400004 0.382299989 +vn 0.72240001 0.576099992 -0.382299989 +vn 0.72240001 0.576099992 0.382299989 +vn 0.782400012 0.491600007 -0.382299989 +vn 0.832499981 0.400900006 0.382299989 +vn 0.832499981 0.400900006 -0.382299989 +vn 0.872200012 0.305200011 -0.382299989 +vn 0.872200012 0.305200011 0.382299989 +vn 0.900900006 0.205599993 -0.382299989 +vn 0.900900006 0.205599993 0.382299989 +vn 0.918200016 0.103500001 -0.382299989 +vn 0.918200016 0.103500001 0.382299989 +vn 0.924000025 -0 -0.382299989 +vn 0.924000025 -0 0.382299989 +vn 0.918200016 -0.103500001 -0.382299989 +vn 0.918200016 -0.103500001 0.382299989 +vn 0.900900006 -0.205599993 -0.382299989 +vn 0.872200012 -0.305200011 0.382299989 +vn 0.872200012 -0.305200011 -0.382299989 +vn 0.832499981 -0.400900006 0.382299989 +vn 0.832499981 -0.400900006 -0.382299989 +vn 0.782400012 -0.491600007 -0.382299989 +vn 0.782400012 -0.491600007 0.382299989 +vn 0.72240001 -0.576099992 -0.382299989 +vn 0.72240001 -0.576099992 0.382299989 +vn 0.653400004 -0.653400004 -0.382299989 +vn 0.653400004 -0.653400004 0.382299989 +vn 0.576099992 -0.72240001 -0.382299989 +vn 0.491600007 -0.782400012 0.382299989 +vn 0.491600007 -0.782400012 -0.382299989 +vn 0.400900006 -0.832499981 -0.382299989 +vn 0.305200011 -0.872200012 0.382299989 +vn 0.305200011 -0.872200012 -0.382299989 +vn 0.205599993 -0.900900006 0.382299989 +vn 0.205599993 -0.900900006 -0.382299989 +vn 0.103500001 -0.918200016 -0.382299989 +vn 0.103500001 -0.918200016 0.382299989 +vn -0 -0.924000025 -0.382299989 +vn -0 -0.924000025 0.382299989 +vn -0.103500001 -0.918200016 -0.382299989 +vn -0.103500001 -0.918200016 0.382299989 +vn -0.205599993 -0.900900006 -0.382299989 +vn -0.305200011 -0.872200012 0.382299989 +vn -0.305200011 -0.872200012 -0.382299989 +vn -0.400900006 -0.832499981 0.382299989 +vn -0.400900006 -0.832499981 -0.382299989 +vn -0.491600007 -0.782400012 -0.382299989 +vn -0.576099992 -0.72240001 0.382299989 +vn -0.576099992 -0.72240001 -0.382299989 +vn -0.653400004 -0.653400004 -0.382299989 +vn -0.653400004 -0.653400004 0.382299989 +vn -0.72240001 -0.576099992 -0.382299989 +vn -0.72240001 -0.576099992 0.382299989 +vn -0.782400012 -0.491600007 -0.382299989 +vn -0.782400012 -0.491600007 0.382299989 +vn -0.832499981 -0.400900006 -0.382299989 +vn -0.832499981 -0.400900006 0.382299989 +vn -0.872200012 -0.305200011 -0.382299989 +vn -0.872200012 -0.305200011 0.382299989 +vn -0.900900006 -0.205599993 -0.382299989 +vn -0.900900006 -0.205599993 0.382299989 +vn -0.918200016 -0.103500001 -0.382299989 +vn -0.918200016 -0.103500001 0.382299989 +vn -0.924000025 -0 -0.382299989 +vn -0.924000025 -0 0.382299989 +vn -0.918200016 0.103500001 -0.382299989 +vn -0.918200016 0.103500001 0.382299989 +vn -0.900900006 0.205599993 -0.382299989 +vn -0.900900006 0.205599993 0.382299989 +vn -0.872200012 0.305200011 -0.382299989 +vn -0.872200012 0.305200011 0.382299989 +vn -0.832499981 0.400900006 -0.382299989 +vn -0.832499981 0.400900006 0.382299989 +vn -0.782400012 0.491600007 -0.382299989 +vn -0.782400012 0.491600007 0.382299989 +vn -0.72240001 0.576099992 -0.382299989 +vn -0.72240001 0.576099992 0.382299989 +vn -0.653400004 0.653400004 -0.382299989 +vn -0.653400004 0.653400004 0.382299989 +vn -0.576099992 0.72240001 -0.382299989 +vn -0.576099992 0.72240001 0.382299989 +vn -0.491600007 0.782400012 -0.382299989 +vn -0.491600007 0.782400012 0.382299989 +vn -0.400900006 0.832499981 -0.382299989 +vn -0.400900006 0.832499981 0.382299989 +vn -0.305200011 0.872200012 -0.382299989 +vn -0.305200011 0.872200012 0.382299989 +vn -0.205599993 0.900900006 -0.382299989 +vn -0.205599993 0.900900006 0.382299989 +vn 0.103500001 0.918200016 0.382299989 +vn 0.103500001 0.918200016 -0.382299989 +vn -0.655099988 0.229200006 0.719900012 +vn -0.80430001 0.382200003 0.455000013 +vn -0.62529999 0.301099986 0.719900012 +vn -0 0.924000025 0.382299989 +vn -0 0.389699996 -0.921000004 +vn -0 0.389699996 0.921000004 +vn 0.0436000004 0.387199998 0.921000004 +vn 0.0436000004 0.387199998 -0.921000004 +vn 0.0866999999 0.379900008 0.921000004 +vn 0.0866999999 0.379900008 -0.921000004 +vn 0.128700003 0.367799997 0.921000004 +vn 0.305200011 0.872200012 0.382299989 +vn 0.128700003 0.367799997 -0.921000004 +vn 0.169100001 0.351099998 0.921000004 +vn 0.207300007 0.329899997 -0.921000004 +vn 0.169100001 0.351099998 -0.921000004 +vn 0.207300007 0.329899997 0.921000004 +vn 0.491600007 0.782400012 0.382299989 +vn 0.242899999 0.3046 -0.921000004 +vn 0.242899999 0.3046 0.921000004 +vn 0.2755 0.2755 0.921000004 +vn 0.3046 0.242899999 -0.921000004 +vn 0.2755 0.2755 -0.921000004 +vn 0.3046 0.242899999 0.921000004 +vn 0.782400012 0.491600007 0.382299989 +vn 0.329899997 0.207300007 0.921000004 +vn 0.329899997 0.207300007 -0.921000004 +vn 0.351099998 0.169100001 0.921000004 +vn 0.367799997 0.128700003 -0.921000004 +vn 0.351099998 0.169100001 -0.921000004 +vn 0.367799997 0.128700003 0.921000004 +vn 0.379900008 0.0866999999 -0.921000004 +vn 0.379900008 0.0866999999 0.921000004 +vn 0.387199998 0.0436000004 -0.921000004 +vn 0.387199998 0.0436000004 0.921000004 +vn 0.389699996 -0 -0.921000004 +vn 0.389699996 -0 0.921000004 +vn 0.387199998 -0.0436000004 0.921000004 +vn 0.387199998 -0.0436000004 -0.921000004 +vn 0.900900006 -0.205599993 0.382299989 +vn 0.379900008 -0.0866999999 0.921000004 +vn 0.379900008 -0.0866999999 -0.921000004 +vn 0.367799997 -0.128700003 0.921000004 +vn 0.367799997 -0.128700003 -0.921000004 +vn 0.351099998 -0.169100001 0.921000004 +vn 0.329899997 -0.207300007 -0.921000004 +vn 0.351099998 -0.169100001 -0.921000004 +vn 0.329899997 -0.207300007 0.921000004 +vn 0.3046 -0.242899999 -0.921000004 +vn 0.3046 -0.242899999 0.921000004 +vn 0.2755 -0.2755 0.921000004 +vn 0.242899999 -0.3046 -0.921000004 +vn 0.2755 -0.2755 -0.921000004 +vn 0.242899999 -0.3046 0.921000004 +vn 0.576099992 -0.72240001 0.382299989 +vn 0.207300007 -0.329899997 0.921000004 +vn 0.207300007 -0.329899997 -0.921000004 +vn 0.400900006 -0.832499981 0.382299989 +vn 0.169100001 -0.351099998 0.921000004 +vn 0.128700003 -0.367799997 -0.921000004 +vn 0.169100001 -0.351099998 -0.921000004 +vn 0.128700003 -0.367799997 0.921000004 +vn 0.0866999999 -0.379900008 0.921000004 +vn 0.0436000004 -0.387199998 -0.921000004 +vn 0.0866999999 -0.379900008 -0.921000004 +vn 0.0436000004 -0.387199998 0.921000004 +vn -0 -0.389699996 -0.921000004 +vn -0 -0.389699996 0.921000004 +vn -0.0436000004 -0.387199998 0.921000004 +vn -0.0436000004 -0.387199998 -0.921000004 +vn -0.205599993 -0.900900006 0.382299989 +vn -0.0866999999 -0.379900008 0.921000004 +vn -0.0866999999 -0.379900008 -0.921000004 +vn -0.128700003 -0.367799997 0.921000004 +vn -0.128700003 -0.367799997 -0.921000004 +vn -0.169100001 -0.351099998 0.921000004 +vn -0.207300007 -0.329899997 -0.921000004 +vn -0.169100001 -0.351099998 -0.921000004 +vn -0.207300007 -0.329899997 0.921000004 +vn -0.491600007 -0.782400012 0.382299989 +vn -0.243000001 -0.3046 -0.921000004 +vn -0.243000001 -0.3046 0.921000004 +vn -0.2755 -0.2755 0.921000004 +vn -0.3046 -0.242899999 -0.921000004 +vn -0.2755 -0.2755 -0.921000004 +vn -0.3046 -0.242899999 0.921000004 +vn -0.329899997 -0.207300007 0.921000004 +vn -0.329899997 -0.207300007 -0.921000004 +vn -0.351099998 -0.169100001 0.921000004 +vn -0.367799997 -0.128700003 -0.921000004 +vn -0.351099998 -0.169100001 -0.921000004 +vn -0.367799997 -0.128700003 0.921000004 +vn -0.379900008 -0.0866999999 0.921000004 +vn -0.387199998 -0.0436000004 -0.921000004 +vn -0.379900008 -0.0866999999 -0.921000004 +vn -0.387199998 -0.0436000004 0.921000004 +vn -0.389699996 -0 -0.921000004 +vn -0.389699996 -0 0.921000004 +vn -0.387199998 0.0436000004 0.921000004 +vn -0.387199998 0.0436000004 -0.921000004 +vn -0.379900008 0.0866999999 0.921000004 +vn -0.379900008 0.0866999999 -0.921000004 +vn -0.367799997 0.128700003 0.921000004 +vn -0.367799997 0.128700003 -0.921000004 +vn -0.351099998 0.169100001 0.921000004 +vn -0.329899997 0.207300007 -0.921000004 +vn -0.351099998 0.169100001 -0.921000004 +vn -0.329899997 0.207300007 0.921000004 +vn -0.3046 0.242899999 -0.921000004 +vn -0.3046 0.242899999 0.921000004 +vn -0.2755 0.2755 0.921000004 +vn -0.242899999 0.3046 -0.921000004 +vn -0.2755 0.2755 -0.921000004 +vn -0.242899999 0.3046 0.921000004 +vn -0.207300007 0.329899997 0.921000004 +vn -0.207300007 0.329899997 -0.921000004 +vn -0.169100001 0.351099998 0.921000004 +vn -0.128700003 0.367799997 -0.921000004 +vn -0.169100001 0.351099998 -0.921000004 +vn -0.128700003 0.367799997 0.921000004 +vn -0.0866999999 0.379900008 0.921000004 +vn -0.0436000004 0.387199998 -0.921000004 +vn -0.0866999999 0.379900008 -0.921000004 +vn -0.0436000004 0.387199998 0.921000004 +vn 0.229200006 0.655099988 -0.719900012 +vn 0.229100004 0.972299993 -0.0460000001 +vn 0.154400006 0.676599979 -0.719900012 +vn -0 -0 -1 +vn -0 -0 1 +vn 0.229100004 -0.972299993 0.0460000001 +vn 0.309500009 -0.934199989 0.177599996 +vn 0.209999993 -0.974300027 0.0811000019 +vn -0.369199991 -0.587700009 0.719900012 +vn -0.437599987 -0.88410002 0.164199993 +vn -0.526499987 -0.817600012 0.233199999 +vn 0.676599979 -0.154400006 0.719900012 +vn 0.832799971 -0.287900001 0.472799987 +vn 0.655099988 -0.229200006 0.719900012 +vn 0.229200006 0.655099988 0.719900012 +vn 0.437599987 0.88410002 0.164199993 +vn 0.301099986 0.62529999 0.719900012 +vn -0.7676 0.475300014 0.430000007 +vn -0.587700009 0.369199991 0.719900012 +vn -0.603100002 -0.740100026 0.297500014 +vn -0.432700008 -0.542599976 0.719900012 +vn 0.80430001 -0.382200003 0.455000013 +vn 0.62529999 -0.301099986 0.719900012 +vn 0.526499987 0.817600012 0.233199999 +vn 0.369199991 0.587700009 0.719900012 +vn -0.722500026 0.566600025 0.396299988 +vn -0.542599976 0.432700008 0.719900012 +vn -0.490799993 -0.490799993 0.719900012 +vn -0.667999983 -0.655399978 0.352299988 +vn 0.7676 -0.475300014 0.430000007 +vn 0.587700009 -0.369199991 0.719900012 +vn 0.603100002 0.740100026 0.297500014 +vn 0.432700008 0.542599976 0.719900012 +vn -0.667999983 0.655399978 0.352299988 +vn -0.490799993 0.490799993 0.719900012 +vn -0.722500026 -0.566600025 0.396299988 +vn -0.542599976 -0.432700008 0.719900012 +vn 0.542599976 -0.432700008 0.719900012 +vn 0.722500026 -0.566600025 0.396299988 +vn 0.667999983 0.655399978 0.352400005 +vn 0.490799993 0.490799993 0.719900012 +vn -0.603100002 0.740100026 0.297500014 +vn -0.432700008 0.542599976 0.719900012 +vn -0.7676 -0.475300014 0.430000007 +vn -0.587700009 -0.369199991 0.719900012 +vn 0.667999983 -0.655399978 0.352400005 +vn 0.490799993 -0.490799993 0.719900012 +vn 0.542599976 0.432700008 0.719900012 +vn 0.722500026 0.566600025 0.396299988 +vn -0.526499987 0.817600012 0.233199999 +vn -0.369199991 0.587700009 0.719900012 +vn -0.80430001 -0.382200003 0.455000013 +vn -0.62529999 -0.301099986 0.719900012 +vn 0.603100002 -0.740100026 0.297500014 +vn 0.432700008 -0.542599976 0.719900012 +vn 0.7676 0.475300014 0.430000007 +vn 0.587700009 0.369199991 0.719900012 +vn -0.301099986 0.62529999 0.719900012 +vn -0.437599987 0.88410002 0.164199993 +vn -0.832799971 -0.287900001 0.472799987 +vn -0.655099988 -0.229200006 0.719900012 +vn 0.526499987 -0.817600012 0.233099997 +vn 0.369199991 -0.587700009 0.719900012 +vn 0.80430001 0.382200003 0.455000013 +vn 0.62529999 0.301099986 0.719900012 +vn -0.229200006 0.655099988 0.719900012 +vn -0.337500006 0.936100006 0.0988999978 +vn -0.853200018 -0.192599997 0.484699994 +vn -0.676599979 -0.154400006 0.719900012 +vn 0.437599987 -0.88410002 0.164199993 +vn 0.301099986 -0.62529999 0.719900012 +vn 0.832799971 0.287900001 0.472799987 +vn 0.655099988 0.229200006 0.719900012 +vn -0.229100004 0.972299993 0.0460000001 +vn -0.154400006 0.676599979 0.719900012 +vn -0.689700007 -0.0776999965 0.719900012 +vn -0.865499973 -0.0965000018 0.49149999 +vn 0.229200006 -0.655099988 0.719900012 +vn 0.337500006 -0.936100006 0.0988999978 +vn 0.853200018 0.192599997 0.484699994 +vn 0.676599979 0.154400006 0.719900012 +vn -0.0776999965 0.689700007 0.719900012 +vn -0.115699999 0.993200004 0.0119000003 +vn -0.869700015 -0 0.493699998 +vn -0.694000006 -0 0.719900012 +vn -0 -0.694000006 0.719900012 +vn -0.115699999 -0.993200004 0.0119000003 +vn -0.0776999965 -0.689700007 0.719900012 +vn 0.154400006 -0.676599979 0.719900012 +vn 0.865499973 0.0965000018 0.49149999 +vn 0.689700007 0.0776999965 0.719900012 +vn -0 1 9.99999975e-05 +vn -0 0.694000006 0.719900012 +vn -0.865499973 0.0965000018 0.49149999 +vn -0.689700007 0.0776999965 0.719900012 +vn -0.229100004 -0.972299993 0.0460000001 +vn -0.154400006 -0.676599979 0.719900012 +vn 0.115699999 -0.993200004 0.0119000003 +vn 0.0776999965 -0.689700007 0.719900012 +vn 0.694000006 -0 0.719900012 +vn 0.869599998 -0 0.493699998 +vn 0.0776999965 0.689700007 0.719900012 +vn 0.115699999 0.993200004 0.0119000003 +vn -0.853200018 0.192599997 0.484699994 +vn -0.676599979 0.154400006 0.719900012 +vn -0.337500006 -0.936100006 0.0988999978 +vn -0.229200006 -0.655099988 0.719900012 +vn -0 -1 9.99999975e-05 +vn 0.865499973 -0.0965000018 0.49149999 +vn 0.689700007 -0.0776999965 0.719900012 +vn 0.154400006 0.676599979 0.719900012 +vn 0.229100004 0.972299993 0.0460000001 +vn -0.832799971 0.287900001 0.472799987 +vn -0.301099986 -0.62529999 0.719900012 +vn 0.853200018 -0.192599997 0.484699994 +vn 0.337500006 0.936100006 0.0988999978 +vn -0.722500026 0.566600025 -0.396299988 +vn -0.5273 0.342999995 -0.777400017 +vn -0.7676 0.475300014 -0.430000007 +vn -0.62529999 0.301099986 -0.719900012 +vn -0.832799971 0.287900001 -0.472799987 +vn -0.655099988 0.229200006 -0.719900012 +vn -0.369199991 -0.587700009 -0.719900012 +vn -0.437599987 -0.88410002 -0.164199993 +vn -0.301099986 -0.62529999 -0.719900012 +vn 0.655099988 -0.229200006 -0.719900012 +vn 0.853200018 -0.192599997 -0.484699994 +vn 0.676599979 -0.154400006 -0.719900012 +vn 0.437599987 0.88410002 -0.164199993 +vn 0.337500006 0.936100006 -0.0988999978 +vn -0.587700009 0.369199991 -0.719900012 +vn -0.80430001 0.382200003 -0.455000013 +vn -0.432700008 -0.542599976 -0.719900012 +vn -0.526499987 -0.817600012 -0.233199999 +vn 0.62529999 -0.301099986 -0.719900012 +vn 0.832799971 -0.287900001 -0.472799987 +vn 0.301099986 0.62529999 -0.719900012 +vn 0.526499987 0.817600012 -0.233199999 +vn -0.542599976 0.432700008 -0.719900012 +vn -0.490799993 -0.490799993 -0.719900012 +vn -0.603100002 -0.740100026 -0.297500014 +vn 0.587700009 -0.369199991 -0.719900012 +vn 0.80430001 -0.382200003 -0.455000013 +vn 0.369199991 0.587700009 -0.719900012 +vn 0.603100002 0.740100026 -0.297500014 +vn -0.667999983 0.655399978 -0.352299988 +vn -0.542599976 -0.432700008 -0.719900012 +vn -0.667999983 -0.655399978 -0.352299988 +vn 0.542599976 -0.432700008 -0.719900012 +vn 0.7676 -0.475300014 -0.430000007 +vn 0.490799993 0.490799993 -0.719900012 +vn 0.432700008 0.542599976 -0.719900012 +vn -0.490799993 0.490799993 -0.719900012 +vn -0.603100002 0.740100026 -0.297500014 +vn -0.7676 -0.475300014 -0.430000007 +vn -0.722500026 -0.566600025 -0.396299988 +vn 0.490799993 -0.490799993 -0.719900012 +vn 0.722500026 -0.566600025 -0.396299988 +vn 0.542599976 0.432700008 -0.719900012 +vn 0.667999983 0.655399978 -0.352400005 +vn -0.432700008 0.542599976 -0.719900012 +vn -0.526499987 0.817600012 -0.233199999 +vn -0.62529999 -0.301099986 -0.719900012 +vn -0.587700009 -0.369199991 -0.719900012 +vn 0.432700008 -0.542599976 -0.719900012 +vn 0.667999983 -0.655399978 -0.352299988 +vn 0.587700009 0.369199991 -0.719900012 +vn 0.722500026 0.566600025 -0.396299988 +vn -0.369199991 0.587700009 -0.719900012 +vn -0.437599987 0.88410002 -0.164199993 +vn -0.655099988 -0.229200006 -0.719900012 +vn -0.80430001 -0.382200003 -0.455000013 +vn 0.369199991 -0.587599993 -0.719900012 +vn 0.603100002 -0.740100026 -0.297500014 +vn 0.62529999 0.301099986 -0.719900012 +vn 0.7676 0.475300014 -0.430000007 +vn -0.301099986 0.62529999 -0.719900012 +vn -0.337500006 0.936100006 -0.0988999978 +vn -0.853200018 -0.192599997 -0.484699994 +vn -0.832799971 -0.287900001 -0.472799987 +vn 0.301099986 -0.62529999 -0.719900012 +vn 0.526499987 -0.817600012 -0.233099997 +vn 0.655099988 0.229200006 -0.719900012 +vn 0.80430001 0.382200003 -0.455000013 +vn -0.229200006 0.655099988 -0.719900012 +vn -0.229100004 0.972299993 -0.0460000001 +vn -0.689700007 -0.0776999965 -0.719900012 +vn -0.676599979 -0.154400006 -0.719900012 +vn 0.229200006 -0.655099988 -0.719900012 +vn 0.437599987 -0.88410002 -0.164199993 +vn 0.676599979 0.154400006 -0.719900012 +vn 0.832799971 0.287900001 -0.472799987 +vn -0.0776999965 0.689700007 -0.719900012 +vn -0.154400006 0.676599979 -0.719900012 +vn -0.694000006 -0 -0.719900012 +vn -0.865499973 -0.0965000018 -0.49149999 +vn -0 -0.694000006 -0.719900012 +vn -0.115699999 -0.993200004 -0.0119000003 +vn -0 -1 -9.99999975e-05 +vn 0.154400006 -0.676599979 -0.719900012 +vn 0.337500006 -0.936100006 -0.0988999978 +vn 0.689700007 0.0776999965 -0.719900012 +vn 0.853200018 0.192599997 -0.484699994 +vn -0 0.694000006 -0.719900012 +vn -0.115699999 0.993200004 -0.0119000003 +vn -0.689700007 0.0776999965 -0.719900012 +vn -0.869700015 -0 -0.493699998 +vn -0.0776999965 -0.689700007 -0.719900012 +vn -0.229100004 -0.972299993 -0.0460000001 +vn 0.115699999 -0.993200004 -0.0119000003 +vn 0.229100004 -0.972299993 -0.0460000001 +vn 0.694000006 -0 -0.719900012 +vn 0.865499973 0.0965000018 -0.49149999 +vn 0.0776999965 0.689700007 -0.719900012 +vn -0 1 -9.99999975e-05 +vn -0.853200018 0.192599997 -0.484699994 +vn -0.865499973 0.0965000018 -0.49149999 +vn -0.229200006 -0.655099988 -0.719900012 +vn -0.154400006 -0.676599979 -0.719900012 +vn 0.0776999965 -0.689700007 -0.719900012 +vn 0.689700007 -0.0776999965 -0.719900012 +vn 0.869599998 -0 -0.493699998 +vn 0.115699999 0.993200004 -0.0119000003 +vn -0.676599979 0.154400006 -0.719900012 +vn -0.337500006 -0.936100006 -0.0988999978 +vn 0.865499973 -0.0965000018 -0.49149999 +vn -0.105700001 0.994199991 0.0205000006 +vn -0 1 -0 +vn -0.209999993 -0.974300027 0.0811000019 +vn 0.105700001 -0.994199991 0.0205000006 +vn 0.105700001 0.994199991 0.0205000006 +vn -0.309500009 -0.934199989 0.177599996 +vn -0 -1 -0 +vn 0.209999993 0.974300027 0.0811000019 +vn -0.396899998 -0.867600024 0.299499989 +vn 0.309500009 0.934199989 0.177599996 +vn -0.464100003 -0.774600029 0.4296 +vn 0.396899998 0.867600024 0.299499989 +vn -0.506799996 -0.663999975 0.549899995 +vn 0.464100003 0.774699986 0.4296 +vn -0.527100027 -0.548799992 0.648800015 +vn 0.506799996 0.663999975 0.549799979 +vn -0.531599998 -0.440100014 0.723699987 +vn 0.527100027 0.548799992 0.648800015 +vn -0.5273 -0.342999995 0.777400017 +vn 0.531700015 0.440100014 0.723699987 +vn -0.519200027 -0.258300006 0.814700007 +vn 0.5273 0.342999995 0.777400017 +vn -0.510699987 -0.184300005 0.8398 +vn 0.519200027 0.258300006 0.814700007 +vn -0.503700018 -0.118500002 0.855700016 +vn 0.510699987 0.184300005 0.8398 +vn -0.4991 -0.0579999983 0.864600003 +vn 0.503700018 0.118500002 0.855700016 +vn -0.497599989 -0 0.867399991 +vn 0.4991 0.0579999983 0.864600003 +vn -0.4991 0.0579999983 0.864600003 +vn 0.497599989 -0 0.867399991 +vn -0.503700018 0.118500002 0.855700016 +vn 0.4991 -0.0579999983 0.864600003 +vn -0.510699987 0.184300005 0.8398 +vn 0.503700018 -0.118500002 0.855700016 +vn -0.519200027 0.258300006 0.814700007 +vn 0.510699987 -0.184300005 0.8398 +vn -0.5273 0.342999995 0.777400017 +vn 0.519200027 -0.258300006 0.814700007 +vn -0.531700015 0.440100014 0.723699987 +vn 0.5273 -0.342999995 0.777400017 +vn -0.527100027 0.548799992 0.648800015 +vn 0.531700015 -0.440100014 0.723699987 +vn -0.506799996 0.663999975 0.549799979 +vn 0.527100027 -0.548799992 0.648800015 +vn -0.464100003 0.774699986 0.4296 +vn 0.506799996 -0.663999975 0.549799979 +vn -0.396899998 0.867600024 0.299400002 +vn 0.464100003 -0.774699986 0.4296 +vn -0.309500009 0.934199989 0.177599996 +vn 0.396899998 -0.867600024 0.299499989 +vn -0.209999993 0.974300027 0.0811000019 +vn -0.105700001 -0.994199991 0.0205000006 +vn 0.519200027 -0.258300006 -0.814700007 +vn -0.531700015 0.440100014 -0.723699987 +vn 0.5273 -0.342999995 -0.777400017 +vn -0.527100027 0.548799992 -0.648800015 +vn 0.531599998 -0.440100014 -0.723699987 +vn -0.506799996 0.663999975 -0.549799979 +vn 0.527100027 -0.548799992 -0.648800015 +vn -0.464100003 0.774699986 -0.4296 +vn 0.506799996 -0.663999975 -0.549799979 +vn -0.396899998 0.867600024 -0.299499989 +vn 0.464100003 -0.774699986 -0.4296 +vn -0.309500009 0.934199989 -0.177599996 +vn 0.396899998 -0.867600024 -0.299499989 +vn -0.209999993 0.974300027 -0.0811000019 +vn -0.105700001 -0.994199991 -0.0205000006 +vn 0.309500009 -0.934199989 -0.177599996 +vn -0.105700001 0.994199991 -0.0205000006 +vn -0.209999993 -0.974300027 -0.0811000019 +vn 0.209999993 -0.974300027 -0.0811000019 +vn 0.105700001 0.994199991 -0.0205000006 +vn -0.309500009 -0.934199989 -0.177599996 +vn 0.105700001 -0.994199991 -0.0205000006 +vn 0.209999993 0.974300027 -0.0811000019 +vn -0.396899998 -0.867600024 -0.299499989 +vn 0.309500009 0.934199989 -0.177599996 +vn -0.464100003 -0.774600029 -0.4296 +vn 0.396899998 0.867600024 -0.299499989 +vn -0.506799996 -0.663999975 -0.549899995 +vn 0.464100003 0.774699986 -0.4296 +vn -0.527100027 -0.548799992 -0.648800015 +vn 0.506799996 0.663999975 -0.549799979 +vn -0.531599998 -0.440100014 -0.723699987 +vn 0.527100027 0.548799992 -0.648800015 +vn -0.5273 -0.342999995 -0.777400017 +vn 0.531599998 0.440100014 -0.723699987 +vn -0.519200027 -0.258300006 -0.814700007 +vn 0.5273 0.342999995 -0.777400017 +vn -0.510699987 -0.184300005 -0.8398 +vn 0.519200027 0.258300006 -0.814700007 +vn -0.503700018 -0.118500002 -0.855700016 +vn 0.510699987 0.184300005 -0.8398 +vn -0.4991 -0.0579999983 -0.864600003 +vn 0.503700018 0.118500002 -0.855700016 +vn -0.497599989 -0 -0.867399991 +vn 0.4991 0.0579999983 -0.864600003 +vn 0.497599989 -0 -0.867399991 +vn -0.4991 0.0579999983 -0.864600003 +vn -0.503700018 0.118500002 -0.855700016 +vn 0.4991 -0.0579999983 -0.864600003 +vn -0.510699987 0.184300005 -0.8398 +vn 0.503700018 -0.118500002 -0.855700016 +vn -0.519200027 0.258300006 -0.814700007 +vn 0.510699987 -0.184300005 -0.8398 +f 224/1/1 2/2/2 222/1/3 +f 12/3/4 14/4/5 10/3/6 +f 14/4/5 20/5/7 18/5/8 +f 20/5/7 22/6/9 18/5/8 +f 22/6/9 28/7/10 26/7/11 +f 28/7/10 30/8/12 26/7/11 +f 32/8/13 34/9/14 30/8/12 +f 36/9/15 38/10/16 34/9/14 +f 38/10/16 44/11/17 42/11/18 +f 44/11/17 46/12/19 42/11/18 +f 48/12/20 50/13/21 46/12/19 +f 52/13/22 54/14/23 50/13/21 +f 56/14/24 58/15/25 54/14/23 +f 60/15/26 62/16/27 58/15/25 +f 64/16/28 66/17/29 62/16/27 +f 66/17/29 72/18/30 70/18/31 +f 70/18/31 76/19/32 74/19/33 +f 76/19/32 78/20/34 74/19/33 +f 80/20/35 82/21/36 78/20/34 +f 84/21/37 86/22/38 82/21/36 +f 88/22/39 90/23/40 86/22/38 +f 90/23/40 96/24/41 94/24/42 +f 96/24/41 98/25/43 94/24/42 +f 98/25/43 104/26/44 102/26/45 +f 102/26/45 108/27/46 106/27/47 +f 108/27/46 110/28/48 106/27/47 +f 112/28/49 114/29/50 110/28/48 +f 116/29/51 118/30/52 114/29/50 +f 120/30/53 122/31/54 118/30/52 +f 122/31/54 128/32/55 126/32/56 +f 126/32/56 132/33/57 130/33/58 +f 132/33/57 134/34/59 130/33/58 +f 134/34/59 140/35/60 138/35/61 +f 140/35/60 142/36/62 138/35/61 +f 144/36/63 146/37/64 142/36/62 +f 148/37/65 150/38/66 146/37/64 +f 152/38/67 154/39/68 150/38/66 +f 156/39/69 158/40/70 154/39/68 +f 160/40/71 162/41/72 158/40/70 +f 164/41/73 166/42/74 162/41/72 +f 168/42/75 170/43/76 166/42/74 +f 172/43/77 174/44/78 170/43/76 +f 176/44/79 178/45/80 174/44/78 +f 180/45/81 182/46/82 178/45/80 +f 184/46/83 186/47/84 182/46/82 +f 188/47/85 190/48/86 186/47/84 +f 192/48/87 194/49/88 190/48/86 +f 196/49/89 198/50/90 194/49/88 +f 200/50/91 202/51/92 198/50/90 +f 204/51/93 206/52/94 202/51/92 +f 208/52/95 210/53/96 206/52/94 +f 212/54/97 214/55/98 210/53/96 +f 216/55/99 218/56/100 214/55/98 +f 220/56/101 222/1/3 218/56/100 +f 8/57/102 10/3/6 6/57/103 +f 71/58/104 355/59/105 75/59/106 +f 4/2/107 6/57/103 2/2/2 +f 6/57/103 226/60/108 2/2/2 +f 8/57/102 227/60/109 228/61/110 +f 10/3/6 225/61/111 6/57/103 +f 12/3/4 228/61/110 230/62/112 +f 14/4/5 229/62/113 10/3/6 +f 12/3/4 232/63/114 16/4/115 +f 18/5/8 231/63/116 14/4/5 +f 20/5/7 232/63/114 234/64/117 +f 18/5/8 235/65/118 233/64/119 +f 20/5/7 236/65/120 24/66/121 +f 22/6/9 237/67/122 235/65/118 +f 24/66/121 238/67/123 28/7/10 +f 30/8/12 237/67/122 26/7/11 +f 32/8/13 238/67/123 240/68/124 +f 30/8/12 241/69/125 239/68/126 +f 32/8/13 242/69/127 36/9/15 +f 38/10/16 241/69/125 34/9/14 +f 40/10/128 242/69/127 244/70/129 +f 42/11/18 243/70/130 38/10/16 +f 44/11/17 244/70/129 246/71/131 +f 42/11/18 247/72/132 245/71/133 +f 44/11/17 248/72/134 48/12/20 +f 46/12/19 249/73/135 247/72/132 +f 52/13/22 248/72/134 250/73/136 +f 50/13/21 251/74/137 249/73/135 +f 52/13/22 252/74/138 56/14/24 +f 54/14/23 253/75/139 251/74/137 +f 56/14/24 254/75/140 60/15/26 +f 62/16/27 253/75/139 58/15/25 +f 64/16/28 254/75/140 256/76/141 +f 66/17/29 255/76/142 62/16/27 +f 68/17/143 256/76/141 258/77/144 +f 70/18/31 257/77/145 66/17/29 +f 72/18/30 258/77/144 260/78/146 +f 74/19/33 259/78/147 70/18/31 +f 76/19/32 260/78/146 262/79/148 +f 74/19/33 263/80/149 261/79/150 +f 76/19/32 264/80/151 80/20/35 +f 78/20/34 265/81/152 263/80/149 +f 80/20/35 266/81/153 84/21/37 +f 86/22/38 265/81/152 82/21/36 +f 88/22/39 266/81/153 268/82/154 +f 86/22/38 269/83/155 267/82/156 +f 88/22/39 270/83/157 92/23/158 +f 94/24/42 269/83/155 90/23/40 +f 96/24/41 270/83/157 272/84/159 +f 98/25/43 271/84/160 94/24/42 +f 100/25/161 272/84/159 274/85/162 +f 98/25/43 275/86/163 273/85/164 +f 100/25/161 276/86/165 104/26/44 +f 106/27/47 275/86/163 102/26/45 +f 104/26/44 278/87/166 108/27/46 +f 106/27/47 279/88/167 277/87/168 +f 108/27/46 280/88/169 112/28/49 +f 110/28/48 281/89/170 279/88/167 +f 112/28/49 282/89/171 116/29/51 +f 118/30/52 281/89/170 114/29/50 +f 120/30/53 282/89/171 284/90/172 +f 122/31/54 283/90/173 118/30/52 +f 124/31/174 284/90/172 286/91/175 +f 126/32/56 285/91/176 122/31/54 +f 128/32/55 286/91/175 288/92/177 +f 130/33/58 287/93/178 126/32/56 +f 132/33/57 288/92/177 290/94/179 +f 130/33/58 291/95/180 289/94/181 +f 132/33/57 292/95/182 136/34/183 +f 134/34/59 293/96/184 291/95/180 +f 136/34/183 294/96/185 140/35/60 +f 142/36/62 293/96/184 138/35/61 +f 144/36/63 294/96/185 296/97/186 +f 142/36/62 297/98/187 295/97/188 +f 144/36/63 298/98/189 148/37/65 +f 150/38/66 297/98/187 146/37/64 +f 152/38/67 298/98/189 300/99/190 +f 154/39/68 299/99/191 150/38/66 +f 156/39/69 300/99/190 302/100/192 +f 154/39/68 303/101/193 301/100/194 +f 156/39/69 304/101/195 160/40/71 +f 162/41/72 303/101/193 158/40/70 +f 160/40/71 306/102/196 164/41/73 +f 162/41/72 307/103/197 305/102/198 +f 164/41/73 308/103/199 168/42/75 +f 166/42/74 309/104/200 307/103/197 +f 168/42/75 310/104/201 172/43/77 +f 174/44/78 309/104/200 170/43/76 +f 176/44/79 310/104/201 312/105/202 +f 178/45/80 311/105/203 174/44/78 +f 180/45/81 312/105/202 314/106/204 +f 182/46/82 313/106/205 178/45/80 +f 180/45/81 316/107/206 184/46/83 +f 186/47/84 315/107/207 182/46/82 +f 188/47/85 316/107/206 318/108/208 +f 186/47/84 319/109/209 317/108/210 +f 188/47/85 320/109/211 192/48/87 +f 190/48/86 321/110/212 319/109/209 +f 192/48/87 322/110/213 196/49/89 +f 198/50/90 321/110/212 194/49/88 +f 200/50/91 322/110/213 324/111/214 +f 198/50/90 325/112/215 323/111/216 +f 200/50/91 326/112/217 204/51/93 +f 206/52/94 325/112/215 202/51/92 +f 208/52/95 326/112/217 328/113/218 +f 210/53/96 327/114/219 206/52/94 +f 212/54/97 328/113/218 330/115/220 +f 210/53/96 331/116/221 329/115/222 +f 212/54/97 332/117/223 216/55/99 +f 218/56/100 331/116/221 214/55/98 +f 216/55/99 334/118/224 220/56/101 +f 218/56/100 335/119/225 333/118/226 +f 220/56/101 336/119/227 224/1/1 +f 222/1/3 226/60/108 335/119/225 +f 224/1/1 227/60/109 4/2/107 +f 125/120/228 423/121/229 121/122/230 +f 1/123/231 225/61/231 5/124/231 +f 3/123/232 228/61/232 227/60/232 +f 9/125/231 225/61/231 229/62/231 +f 11/125/232 228/61/232 7/124/232 +f 9/125/231 231/63/231 13/126/231 +f 11/125/232 232/63/232 230/62/232 +f 13/126/231 233/64/231 17/127/231 +f 19/127/232 232/63/232 15/128/232 +f 17/127/231 235/65/231 21/129/231 +f 19/127/232 236/65/232 234/64/232 +f 21/129/231 237/67/231 25/130/231 +f 23/129/232 238/67/232 236/65/232 +f 29/131/231 237/67/231 239/68/231 +f 31/131/232 238/67/232 27/130/232 +f 33/132/231 239/68/231 241/69/231 +f 35/132/232 240/68/232 31/131/232 +f 37/133/231 241/69/231 243/70/231 +f 39/133/232 242/69/232 35/132/232 +f 41/134/231 243/70/231 245/71/231 +f 43/134/232 244/70/232 39/133/232 +f 41/134/231 247/72/231 45/135/231 +f 43/134/232 248/72/232 246/71/232 +f 49/136/231 247/72/231 249/73/231 +f 51/136/232 248/72/232 47/135/232 +f 49/136/231 251/74/231 53/137/231 +f 51/136/232 252/74/232 250/73/232 +f 57/138/231 251/74/231 253/75/231 +f 59/138/232 252/74/232 55/137/232 +f 57/138/231 255/76/231 61/139/231 +f 59/138/232 256/76/232 254/75/232 +f 65/140/231 255/76/231 257/77/231 +f 67/140/232 256/76/232 63/139/232 +f 65/140/231 259/78/231 69/58/231 +f 67/140/232 260/78/232 258/77/232 +f 73/59/231 259/78/231 261/79/231 +f 75/59/232 260/78/232 71/58/232 +f 73/59/231 263/80/231 77/141/231 +f 75/59/232 264/80/232 262/79/232 +f 77/141/231 265/81/231 81/142/231 +f 79/141/232 266/81/232 264/80/232 +f 85/143/231 265/81/231 267/82/231 +f 87/143/232 266/81/232 83/142/232 +f 89/144/231 267/82/231 269/83/231 +f 91/145/232 268/82/232 87/143/232 +f 93/146/231 269/83/231 271/84/231 +f 95/146/232 270/83/232 91/145/232 +f 97/147/231 271/84/231 273/85/231 +f 99/147/232 272/84/232 95/146/232 +f 97/147/231 275/86/231 101/148/231 +f 99/147/232 276/86/232 274/85/232 +f 105/149/231 275/86/231 277/87/231 +f 107/149/232 276/86/232 103/148/232 +f 105/149/231 279/88/231 109/150/231 +f 107/149/232 280/88/232 278/87/232 +f 113/151/231 279/88/231 281/89/231 +f 115/151/232 280/88/232 111/150/232 +f 113/151/231 283/90/231 117/152/231 +f 115/151/232 284/90/232 282/89/232 +f 121/122/231 283/90/231 285/91/231 +f 123/122/232 284/90/232 119/152/232 +f 125/120/231 285/91/231 287/93/231 +f 127/120/232 286/91/232 123/122/232 +f 129/153/231 287/93/231 289/94/231 +f 131/153/232 288/92/232 127/120/232 +f 129/153/231 291/95/231 133/154/231 +f 131/153/232 292/95/232 290/94/232 +f 133/154/231 293/96/231 137/155/231 +f 135/154/232 294/96/232 292/95/232 +f 141/156/231 293/96/231 295/97/231 +f 143/156/232 294/96/232 139/155/232 +f 145/157/231 295/97/231 297/98/231 +f 147/157/232 296/97/232 143/156/232 +f 149/158/231 297/98/231 299/99/231 +f 151/158/232 298/98/232 147/157/232 +f 153/159/231 299/99/231 301/100/231 +f 155/159/232 300/99/232 151/158/232 +f 153/159/231 303/101/231 157/160/231 +f 155/159/232 304/101/232 302/100/232 +f 161/161/231 303/101/231 305/102/231 +f 163/161/232 304/101/232 159/160/232 +f 161/161/231 307/103/231 165/162/231 +f 163/161/232 308/103/232 306/102/232 +f 169/163/231 307/103/231 309/104/231 +f 171/163/232 308/103/232 167/162/232 +f 169/163/231 311/105/231 173/164/231 +f 171/163/232 312/105/232 310/104/232 +f 177/165/231 311/105/231 313/106/231 +f 179/165/232 312/105/232 175/164/232 +f 177/165/231 315/107/231 181/166/231 +f 179/165/232 316/107/232 314/106/232 +f 185/167/231 315/107/231 317/108/231 +f 187/167/232 316/107/232 183/166/232 +f 185/167/231 319/109/231 189/168/231 +f 191/168/232 318/108/232 187/167/232 +f 189/168/231 321/110/231 193/169/231 +f 191/168/232 322/110/232 320/109/232 +f 197/170/231 321/110/231 323/111/231 +f 199/170/232 322/110/232 195/169/232 +f 201/171/231 323/111/231 325/112/231 +f 203/171/232 324/111/232 199/170/232 +f 205/172/231 325/112/231 327/114/231 +f 207/172/232 326/112/232 203/171/232 +f 209/173/231 327/114/231 329/115/231 +f 211/173/232 328/113/232 207/172/232 +f 209/173/231 331/116/231 213/174/231 +f 211/173/232 332/117/232 330/115/232 +f 217/175/231 331/116/231 333/118/231 +f 219/175/232 332/117/232 215/174/232 +f 217/175/231 335/119/231 221/176/231 +f 219/175/232 336/119/232 334/118/232 +f 1/123/231 335/119/231 226/60/231 +f 3/123/232 336/119/232 223/176/232 +f 391/175/233 502/177/234 503/178/235 +f 23/129/236 341/127/237 342/129/238 +f 179/165/239 382/166/240 183/166/241 +f 127/120/242 369/153/243 131/153/244 +f 75/59/106 356/141/245 79/141/246 +f 23/129/236 343/130/247 27/130/248 +f 183/166/241 383/167/249 187/167/250 +f 131/153/244 370/154/251 135/154/252 +f 79/141/246 357/142/253 83/142/254 +f 31/131/255 343/130/247 344/131/256 +f 187/167/250 384/168/257 191/168/258 +f 135/154/252 371/155/259 139/155/260 +f 83/142/254 358/143/261 87/143/262 +f 31/131/255 345/132/263 35/132/264 +f 195/169/265 384/168/257 385/169/266 +f 139/155/260 372/156/267 143/156/268 +f 87/143/262 359/145/269 91/145/270 +f 35/132/264 346/133/271 39/133/272 +f 195/169/265 386/170/273 199/170/274 +f 147/157/275 372/156/267 373/157/276 +f 91/145/270 360/146/277 95/146/278 +f 39/133/272 347/134/279 43/134/280 +f 199/170/274 387/171/281 203/171/282 +f 147/157/275 374/158/283 151/158/284 +f 99/147/285 360/146/277 361/147/286 +f 43/134/280 348/135/287 47/135/288 +f 203/171/282 388/172/289 207/172/290 +f 151/158/284 375/159/291 155/159/292 +f 103/148/293 361/147/286 362/148/294 +f 47/135/288 349/136/295 51/136/296 +f 207/172/290 389/173/297 211/173/298 +f 155/159/292 376/160/299 159/160/300 +f 103/148/293 363/149/301 107/149/302 +f 55/137/303 349/136/295 350/137/304 +f 215/174/305 389/173/297 390/174/306 +f 159/160/300 377/161/307 163/161/308 +f 111/150/309 363/149/301 364/150/310 +f 55/137/303 351/138/311 59/138/312 +f 3/123/313 338/124/314 7/124/315 +f 215/174/305 391/175/233 219/175/316 +f 163/161/308 378/162/317 167/162/318 +f 111/150/309 365/151/319 115/151/320 +f 59/138/312 352/139/321 63/139/322 +f 7/124/315 339/125/323 11/125/324 +f 219/175/316 392/176/325 223/176/326 +f 171/163/327 378/162/317 379/163/328 +f 119/152/329 365/151/319 366/152/330 +f 63/139/322 353/140/331 67/140/332 +f 11/125/324 340/128/333 15/128/334 +f 223/176/326 337/123/335 3/123/313 +f 171/163/327 380/164/336 175/164/337 +f 123/122/338 366/152/330 367/121/339 +f 67/140/332 354/58/340 71/58/104 +f 15/128/334 341/127/237 19/127/341 +f 175/164/337 381/165/342 179/165/239 +f 127/120/242 367/121/339 368/120/343 +f 413/142/344 524/179/345 412/141/346 +f 73/59/347 410/58/348 69/58/349 +f 21/129/350 397/127/351 17/127/352 +f 181/166/353 437/165/354 177/165/355 +f 125/120/228 425/153/356 424/120/357 +f 77/141/358 411/59/359 73/59/347 +f 25/130/360 398/129/361 21/129/350 +f 185/167/362 438/166/363 181/166/353 +f 129/153/364 426/154/365 425/153/356 +f 81/142/366 412/141/346 77/141/358 +f 29/131/367 399/130/368 25/130/360 +f 189/168/369 439/167/370 185/167/362 +f 133/154/371 427/155/372 426/154/365 +f 81/142/366 414/143/373 413/142/344 +f 33/132/374 400/131/375 29/131/367 +f 193/169/376 440/168/377 189/168/369 +f 141/156/378 427/155/372 137/155/379 +f 85/143/380 415/145/381 414/143/373 +f 33/132/374 402/133/382 401/132/383 +f 197/170/384 441/169/385 193/169/376 +f 145/157/386 428/156/387 141/156/378 +f 89/144/388 416/146/389 415/145/381 +f 41/134/390 402/133/382 37/133/391 +f 201/171/392 442/170/393 197/170/384 +f 149/158/394 429/157/395 145/157/386 +f 93/146/396 417/147/397 416/146/389 +f 45/135/398 403/134/399 41/134/390 +f 205/172/400 443/171/401 201/171/392 +f 153/159/402 430/158/403 149/158/394 +f 97/147/404 418/180/405 417/147/397 +f 45/135/398 405/136/406 404/135/407 +f 209/173/408 444/172/409 205/172/400 +f 157/160/410 431/159/411 153/159/402 +f 101/148/412 419/149/413 418/180/405 +f 53/137/414 405/136/406 49/136/415 +f 213/174/416 445/173/417 209/173/408 +f 161/161/418 432/160/419 157/160/410 +f 109/150/420 419/149/413 105/149/421 +f 57/138/422 406/137/423 53/137/414 +f 1/123/424 393/124/425 394/123/426 +f 217/175/427 446/174/428 213/174/416 +f 165/162/429 433/161/430 161/161/418 +f 113/151/431 420/150/432 109/150/420 +f 61/139/433 407/138/434 57/138/422 +f 5/124/435 395/125/436 393/124/425 +f 217/175/427 448/176/437 447/175/438 +f 169/163/439 434/162/440 165/162/429 +f 117/152/441 421/151/442 113/151/431 +f 61/139/433 409/140/443 408/139/444 +f 13/126/445 395/125/436 9/125/446 +f 221/176/447 394/123/426 448/176/437 +f 173/164/448 435/163/449 169/163/439 +f 121/122/230 422/152/450 117/152/441 +f 69/58/349 409/140/443 65/140/451 +f 13/126/445 397/127/351 396/126/452 +f 177/165/355 436/164/453 173/164/448 +f 365/151/319 476/181/454 477/151/455 +f 338/124/314 451/182/456 339/125/323 +f 392/176/325 503/178/235 504/183/457 +f 365/151/319 478/184/458 366/152/330 +f 339/125/323 452/185/459 340/128/333 +f 337/123/335 504/183/457 449/186/460 +f 366/152/330 479/187/461 367/121/339 +f 340/128/333 453/188/462 341/127/237 +f 367/121/339 480/189/463 368/120/343 +f 341/127/237 454/190/464 342/129/238 +f 368/120/343 481/191/465 369/153/243 +f 342/129/238 455/192/466 343/130/247 +f 369/153/243 482/193/467 370/154/251 +f 343/130/247 456/194/468 344/131/256 +f 370/154/251 483/195/469 371/155/259 +f 344/131/256 457/196/470 345/132/263 +f 371/155/259 484/197/471 372/156/267 +f 345/132/263 458/198/472 346/133/271 +f 372/156/267 485/199/473 373/157/276 +f 346/133/271 459/200/474 347/134/279 +f 373/157/276 486/201/475 374/158/283 +f 347/134/279 460/202/476 348/135/287 +f 374/158/283 487/203/477 375/159/291 +f 348/135/287 461/204/478 349/136/295 +f 375/159/291 488/205/479 376/160/299 +f 349/136/295 462/206/480 350/137/304 +f 376/160/299 489/207/481 377/161/307 +f 350/137/304 463/208/482 351/138/311 +f 377/161/307 490/209/483 378/162/317 +f 352/139/321 463/208/482 464/210/484 +f 378/162/317 491/211/485 379/163/328 +f 353/140/331 464/210/484 465/212/486 +f 380/164/336 491/211/485 492/213/487 +f 354/58/340 465/212/486 466/214/488 +f 381/165/342 492/213/487 493/215/489 +f 355/59/105 466/214/488 467/216/490 +f 382/166/240 493/215/489 494/217/491 +f 356/141/245 467/216/490 468/179/492 +f 383/167/249 494/217/491 495/218/493 +f 357/142/253 468/179/492 469/219/494 +f 384/168/257 495/218/493 496/220/495 +f 358/143/261 469/219/494 470/221/496 +f 385/169/266 496/220/495 497/222/497 +f 359/145/269 470/221/496 471/223/498 +f 386/170/273 497/222/497 498/224/499 +f 360/146/277 471/223/498 472/225/500 +f 387/171/281 498/224/499 499/226/501 +f 361/147/286 472/225/500 473/227/502 +f 388/172/289 499/226/501 500/228/503 +f 362/148/294 473/227/502 474/229/504 +f 389/173/297 500/228/503 501/230/505 +f 363/149/301 474/229/504 475/231/506 +f 390/174/306 501/230/505 502/177/234 +f 364/150/310 475/231/506 476/181/454 +f 337/123/335 450/232/507 338/124/314 +f 440/168/377 551/218/508 439/167/370 +f 414/143/373 525/219/509 413/142/344 +f 441/169/385 552/220/510 440/168/377 +f 415/145/381 526/221/511 414/143/373 +f 442/170/393 553/222/512 441/169/385 +f 416/146/389 527/223/513 415/145/381 +f 443/171/401 554/224/514 442/170/393 +f 417/147/397 528/225/515 416/146/389 +f 444/172/409 555/226/516 443/171/401 +f 418/180/405 529/227/517 417/147/397 +f 445/173/417 556/228/518 444/172/409 +f 419/149/413 530/229/519 418/180/405 +f 446/174/428 557/230/520 445/173/417 +f 420/150/432 531/231/521 419/149/413 +f 394/123/426 505/232/522 506/186/460 +f 447/175/438 558/177/523 446/174/428 +f 421/151/442 532/181/524 420/150/432 +f 393/124/425 507/182/525 505/232/522 +f 448/176/437 559/178/526 447/175/438 +f 421/151/442 534/184/527 533/151/455 +f 395/125/436 508/185/528 507/182/525 +f 394/123/426 560/183/529 448/176/437 +f 422/152/450 535/233/530 534/184/527 +f 396/126/452 509/188/531 508/185/528 +f 423/121/229 536/189/532 535/233/530 +f 397/127/351 510/190/533 509/188/531 +f 424/120/357 537/191/534 536/189/532 +f 398/129/361 511/192/535 510/190/533 +f 425/153/356 538/193/536 537/191/534 +f 399/130/368 512/194/537 511/192/535 +f 426/154/365 539/195/538 538/193/536 +f 400/131/375 513/196/539 512/194/537 +f 427/155/372 540/197/540 539/195/538 +f 401/132/383 514/198/541 513/196/539 +f 428/156/387 541/199/542 540/197/540 +f 402/133/382 515/200/543 514/198/541 +f 429/157/395 542/201/544 541/199/542 +f 403/134/399 516/202/545 515/200/543 +f 430/158/403 543/203/546 542/201/544 +f 404/135/407 517/204/547 516/202/545 +f 431/159/411 544/205/548 543/203/546 +f 405/136/406 518/206/549 517/204/547 +f 432/160/419 545/207/550 544/205/548 +f 406/137/423 519/208/551 518/206/549 +f 433/161/430 546/209/552 545/207/550 +f 408/139/444 519/208/551 407/138/434 +f 434/162/440 547/211/553 546/209/552 +f 409/140/443 520/210/554 408/139/444 +f 436/164/453 547/211/553 435/163/449 +f 410/58/348 521/212/555 409/140/443 +f 437/165/354 548/213/556 436/164/453 +f 411/59/359 522/214/557 410/58/348 +f 438/166/363 549/215/558 437/165/354 +f 412/141/346 523/216/559 411/59/359 +f 439/167/370 550/217/560 438/166/363 +f 224/1/1 4/2/107 2/2/2 +f 12/3/4 16/4/115 14/4/5 +f 14/4/5 16/4/115 20/5/7 +f 20/5/7 24/66/121 22/6/9 +f 22/6/9 24/66/121 28/7/10 +f 28/7/10 32/8/13 30/8/12 +f 32/8/13 36/9/15 34/9/14 +f 36/9/15 40/10/128 38/10/16 +f 38/10/16 40/10/128 44/11/17 +f 44/11/17 48/12/20 46/12/19 +f 48/12/20 52/13/22 50/13/21 +f 52/13/22 56/14/24 54/14/23 +f 56/14/24 60/15/26 58/15/25 +f 60/15/26 64/16/28 62/16/27 +f 64/16/28 68/17/143 66/17/29 +f 66/17/29 68/17/143 72/18/30 +f 70/18/31 72/18/30 76/19/32 +f 76/19/32 80/20/35 78/20/34 +f 80/20/35 84/21/37 82/21/36 +f 84/21/37 88/22/39 86/22/38 +f 88/22/39 92/23/158 90/23/40 +f 90/23/40 92/23/158 96/24/41 +f 96/24/41 100/25/161 98/25/43 +f 98/25/43 100/25/161 104/26/44 +f 102/26/45 104/26/44 108/27/46 +f 108/27/46 112/28/49 110/28/48 +f 112/28/49 116/29/51 114/29/50 +f 116/29/51 120/30/53 118/30/52 +f 120/30/53 124/31/174 122/31/54 +f 122/31/54 124/31/174 128/32/55 +f 126/32/56 128/32/55 132/33/57 +f 132/33/57 136/34/183 134/34/59 +f 134/34/59 136/34/183 140/35/60 +f 140/35/60 144/36/63 142/36/62 +f 144/36/63 148/37/65 146/37/64 +f 148/37/65 152/38/67 150/38/66 +f 152/38/67 156/39/69 154/39/68 +f 156/39/69 160/40/71 158/40/70 +f 160/40/71 164/41/73 162/41/72 +f 164/41/73 168/42/75 166/42/74 +f 168/42/75 172/43/77 170/43/76 +f 172/43/77 176/44/79 174/44/78 +f 176/44/79 180/45/81 178/45/80 +f 180/45/81 184/46/83 182/46/82 +f 184/46/83 188/47/85 186/47/84 +f 188/47/85 192/48/87 190/48/86 +f 192/48/87 196/49/89 194/49/88 +f 196/49/89 200/50/91 198/50/90 +f 200/50/91 204/51/93 202/51/92 +f 204/51/93 208/52/95 206/52/94 +f 208/52/95 212/54/97 210/53/96 +f 212/54/97 216/55/99 214/55/98 +f 216/55/99 220/56/101 218/56/100 +f 220/56/101 224/1/1 222/1/3 +f 8/57/102 12/3/4 10/3/6 +f 71/58/104 354/58/340 355/59/105 +f 4/2/107 8/57/102 6/57/103 +f 6/57/103 225/61/111 226/60/108 +f 8/57/102 4/2/107 227/60/109 +f 10/3/6 229/62/113 225/61/111 +f 12/3/4 8/57/102 228/61/110 +f 14/4/5 231/63/116 229/62/113 +f 12/3/4 230/62/112 232/63/114 +f 18/5/8 233/64/119 231/63/116 +f 20/5/7 16/4/115 232/63/114 +f 18/5/8 22/6/9 235/65/118 +f 20/5/7 234/64/117 236/65/120 +f 22/6/9 26/7/11 237/67/122 +f 24/66/121 236/65/120 238/67/123 +f 30/8/12 239/68/126 237/67/122 +f 32/8/13 28/7/10 238/67/123 +f 30/8/12 34/9/14 241/69/125 +f 32/8/13 240/68/124 242/69/127 +f 38/10/16 243/70/130 241/69/125 +f 40/10/128 36/9/15 242/69/127 +f 42/11/18 245/71/133 243/70/130 +f 44/11/17 40/10/128 244/70/129 +f 42/11/18 46/12/19 247/72/132 +f 44/11/17 246/71/131 248/72/134 +f 46/12/19 50/13/21 249/73/135 +f 52/13/22 48/12/20 248/72/134 +f 50/13/21 54/14/23 251/74/137 +f 52/13/22 250/73/136 252/74/138 +f 54/14/23 58/15/25 253/75/139 +f 56/14/24 252/74/138 254/75/140 +f 62/16/27 255/76/142 253/75/139 +f 64/16/28 60/15/26 254/75/140 +f 66/17/29 257/77/145 255/76/142 +f 68/17/143 64/16/28 256/76/141 +f 70/18/31 259/78/147 257/77/145 +f 72/18/30 68/17/143 258/77/144 +f 74/19/33 261/79/150 259/78/147 +f 76/19/32 72/18/30 260/78/146 +f 74/19/33 78/20/34 263/80/149 +f 76/19/32 262/79/148 264/80/151 +f 78/20/34 82/21/36 265/81/152 +f 80/20/35 264/80/151 266/81/153 +f 86/22/38 267/82/156 265/81/152 +f 88/22/39 84/21/37 266/81/153 +f 86/22/38 90/23/40 269/83/155 +f 88/22/39 268/82/154 270/83/157 +f 94/24/42 271/84/160 269/83/155 +f 96/24/41 92/23/158 270/83/157 +f 98/25/43 273/85/164 271/84/160 +f 100/25/161 96/24/41 272/84/159 +f 98/25/43 102/26/45 275/86/163 +f 100/25/161 274/85/162 276/86/165 +f 106/27/47 277/87/168 275/86/163 +f 104/26/44 276/86/165 278/87/166 +f 106/27/47 110/28/48 279/88/167 +f 108/27/46 278/87/166 280/88/169 +f 110/28/48 114/29/50 281/89/170 +f 112/28/49 280/88/169 282/89/171 +f 118/30/52 283/90/173 281/89/170 +f 120/30/53 116/29/51 282/89/171 +f 122/31/54 285/91/176 283/90/173 +f 124/31/174 120/30/53 284/90/172 +f 126/32/56 287/93/178 285/91/176 +f 128/32/55 124/31/174 286/91/175 +f 130/33/58 289/94/181 287/93/178 +f 132/33/57 128/32/55 288/92/177 +f 130/33/58 134/34/59 291/95/180 +f 132/33/57 290/94/179 292/95/182 +f 134/34/59 138/35/61 293/96/184 +f 136/34/183 292/95/182 294/96/185 +f 142/36/62 295/97/188 293/96/184 +f 144/36/63 140/35/60 294/96/185 +f 142/36/62 146/37/64 297/98/187 +f 144/36/63 296/97/186 298/98/189 +f 150/38/66 299/99/191 297/98/187 +f 152/38/67 148/37/65 298/98/189 +f 154/39/68 301/100/194 299/99/191 +f 156/39/69 152/38/67 300/99/190 +f 154/39/68 158/40/70 303/101/193 +f 156/39/69 302/100/192 304/101/195 +f 162/41/72 305/102/198 303/101/193 +f 160/40/71 304/101/195 306/102/196 +f 162/41/72 166/42/74 307/103/197 +f 164/41/73 306/102/196 308/103/199 +f 166/42/74 170/43/76 309/104/200 +f 168/42/75 308/103/199 310/104/201 +f 174/44/78 311/105/203 309/104/200 +f 176/44/79 172/43/77 310/104/201 +f 178/45/80 313/106/205 311/105/203 +f 180/45/81 176/44/79 312/105/202 +f 182/46/82 315/107/207 313/106/205 +f 180/45/81 314/106/204 316/107/206 +f 186/47/84 317/108/210 315/107/207 +f 188/47/85 184/46/83 316/107/206 +f 186/47/84 190/48/86 319/109/209 +f 188/47/85 318/108/208 320/109/211 +f 190/48/86 194/49/88 321/110/212 +f 192/48/87 320/109/211 322/110/213 +f 198/50/90 323/111/216 321/110/212 +f 200/50/91 196/49/89 322/110/213 +f 198/50/90 202/51/92 325/112/215 +f 200/50/91 324/111/214 326/112/217 +f 206/52/94 327/114/219 325/112/215 +f 208/52/95 204/51/93 326/112/217 +f 210/53/96 329/115/222 327/114/219 +f 212/54/97 208/52/95 328/113/218 +f 210/53/96 214/55/98 331/116/221 +f 212/54/97 330/115/220 332/117/223 +f 218/56/100 333/118/226 331/116/221 +f 216/55/99 332/117/223 334/118/224 +f 218/56/100 222/1/3 335/119/225 +f 220/56/101 334/118/224 336/119/227 +f 222/1/3 2/2/2 226/60/108 +f 224/1/1 336/119/227 227/60/109 +f 125/120/228 424/120/357 423/121/229 +f 1/123/231 226/60/231 225/61/231 +f 3/123/232 7/124/232 228/61/232 +f 9/125/231 5/124/231 225/61/231 +f 11/125/232 230/62/232 228/61/232 +f 9/125/231 229/62/231 231/63/231 +f 11/125/232 15/128/232 232/63/232 +f 13/126/231 231/63/231 233/64/231 +f 19/127/232 234/64/232 232/63/232 +f 17/127/231 233/64/231 235/65/231 +f 19/127/232 23/129/232 236/65/232 +f 21/129/231 235/65/231 237/67/231 +f 23/129/232 27/130/232 238/67/232 +f 29/131/231 25/130/231 237/67/231 +f 31/131/232 240/68/232 238/67/232 +f 33/132/231 29/131/231 239/68/231 +f 35/132/232 242/69/232 240/68/232 +f 37/133/231 33/132/231 241/69/231 +f 39/133/232 244/70/232 242/69/232 +f 41/134/231 37/133/231 243/70/231 +f 43/134/232 246/71/232 244/70/232 +f 41/134/231 245/71/231 247/72/231 +f 43/134/232 47/135/232 248/72/232 +f 49/136/231 45/135/231 247/72/231 +f 51/136/232 250/73/232 248/72/232 +f 49/136/231 249/73/231 251/74/231 +f 51/136/232 55/137/232 252/74/232 +f 57/138/231 53/137/231 251/74/231 +f 59/138/232 254/75/232 252/74/232 +f 57/138/231 253/75/231 255/76/231 +f 59/138/232 63/139/232 256/76/232 +f 65/140/231 61/139/231 255/76/231 +f 67/140/232 258/77/232 256/76/232 +f 65/140/231 257/77/231 259/78/231 +f 67/140/232 71/58/232 260/78/232 +f 73/59/231 69/58/231 259/78/231 +f 75/59/232 262/79/232 260/78/232 +f 73/59/231 261/79/231 263/80/231 +f 75/59/232 79/141/232 264/80/232 +f 77/141/231 263/80/231 265/81/231 +f 79/141/232 83/142/232 266/81/232 +f 85/143/231 81/142/231 265/81/231 +f 87/143/232 268/82/232 266/81/232 +f 89/144/231 85/143/231 267/82/231 +f 91/145/232 270/83/232 268/82/232 +f 93/146/231 89/144/231 269/83/231 +f 95/146/232 272/84/232 270/83/232 +f 97/147/231 93/146/231 271/84/231 +f 99/147/232 274/85/232 272/84/232 +f 97/147/231 273/85/231 275/86/231 +f 99/147/232 103/148/232 276/86/232 +f 105/149/231 101/148/231 275/86/231 +f 107/149/232 278/87/232 276/86/232 +f 105/149/231 277/87/231 279/88/231 +f 107/149/232 111/150/232 280/88/232 +f 113/151/231 109/150/231 279/88/231 +f 115/151/232 282/89/232 280/88/232 +f 113/151/231 281/89/231 283/90/231 +f 115/151/232 119/152/232 284/90/232 +f 121/122/231 117/152/231 283/90/231 +f 123/122/232 286/91/232 284/90/232 +f 125/120/231 121/122/231 285/91/231 +f 127/120/232 288/92/232 286/91/232 +f 129/153/231 125/120/231 287/93/231 +f 131/153/232 290/94/232 288/92/232 +f 129/153/231 289/94/231 291/95/231 +f 131/153/232 135/154/232 292/95/232 +f 133/154/231 291/95/231 293/96/231 +f 135/154/232 139/155/232 294/96/232 +f 141/156/231 137/155/231 293/96/231 +f 143/156/232 296/97/232 294/96/232 +f 145/157/231 141/156/231 295/97/231 +f 147/157/232 298/98/232 296/97/232 +f 149/158/231 145/157/231 297/98/231 +f 151/158/232 300/99/232 298/98/232 +f 153/159/231 149/158/231 299/99/231 +f 155/159/232 302/100/232 300/99/232 +f 153/159/231 301/100/231 303/101/231 +f 155/159/232 159/160/232 304/101/232 +f 161/161/231 157/160/231 303/101/231 +f 163/161/232 306/102/232 304/101/232 +f 161/161/231 305/102/231 307/103/231 +f 163/161/232 167/162/232 308/103/232 +f 169/163/231 165/162/231 307/103/231 +f 171/163/232 310/104/232 308/103/232 +f 169/163/231 309/104/231 311/105/231 +f 171/163/232 175/164/232 312/105/232 +f 177/165/231 173/164/231 311/105/231 +f 179/165/232 314/106/232 312/105/232 +f 177/165/231 313/106/231 315/107/231 +f 179/165/232 183/166/232 316/107/232 +f 185/167/231 181/166/231 315/107/231 +f 187/167/232 318/108/232 316/107/232 +f 185/167/231 317/108/231 319/109/231 +f 191/168/232 320/109/232 318/108/232 +f 189/168/231 319/109/231 321/110/231 +f 191/168/232 195/169/232 322/110/232 +f 197/170/231 193/169/231 321/110/231 +f 199/170/232 324/111/232 322/110/232 +f 201/171/231 197/170/231 323/111/231 +f 203/171/232 326/112/232 324/111/232 +f 205/172/231 201/171/231 325/112/231 +f 207/172/232 328/113/232 326/112/232 +f 209/173/231 205/172/231 327/114/231 +f 211/173/232 330/115/232 328/113/232 +f 209/173/231 329/115/231 331/116/231 +f 211/173/232 215/174/232 332/117/232 +f 217/175/231 213/174/231 331/116/231 +f 219/175/232 334/118/232 332/117/232 +f 217/175/231 333/118/231 335/119/231 +f 219/175/232 223/176/232 336/119/232 +f 1/123/231 221/176/231 335/119/231 +f 3/123/232 227/60/232 336/119/232 +f 391/175/233 390/174/306 502/177/234 +f 23/129/236 19/127/341 341/127/237 +f 179/165/239 381/165/342 382/166/240 +f 127/120/242 368/120/343 369/153/243 +f 75/59/106 355/59/105 356/141/245 +f 23/129/236 342/129/238 343/130/247 +f 183/166/241 382/166/240 383/167/249 +f 131/153/244 369/153/243 370/154/251 +f 79/141/246 356/141/245 357/142/253 +f 31/131/255 27/130/248 343/130/247 +f 187/167/250 383/167/249 384/168/257 +f 135/154/252 370/154/251 371/155/259 +f 83/142/254 357/142/253 358/143/261 +f 31/131/255 344/131/256 345/132/263 +f 195/169/265 191/168/258 384/168/257 +f 139/155/260 371/155/259 372/156/267 +f 87/143/262 358/143/261 359/145/269 +f 35/132/264 345/132/263 346/133/271 +f 195/169/265 385/169/266 386/170/273 +f 147/157/275 143/156/268 372/156/267 +f 91/145/270 359/145/269 360/146/277 +f 39/133/272 346/133/271 347/134/279 +f 199/170/274 386/170/273 387/171/281 +f 147/157/275 373/157/276 374/158/283 +f 99/147/285 95/146/278 360/146/277 +f 43/134/280 347/134/279 348/135/287 +f 203/171/282 387/171/281 388/172/289 +f 151/158/284 374/158/283 375/159/291 +f 103/148/293 99/147/285 361/147/286 +f 47/135/288 348/135/287 349/136/295 +f 207/172/290 388/172/289 389/173/297 +f 155/159/292 375/159/291 376/160/299 +f 103/148/293 362/148/294 363/149/301 +f 55/137/303 51/136/296 349/136/295 +f 215/174/305 211/173/298 389/173/297 +f 159/160/300 376/160/299 377/161/307 +f 111/150/309 107/149/302 363/149/301 +f 55/137/303 350/137/304 351/138/311 +f 3/123/313 337/123/335 338/124/314 +f 215/174/305 390/174/306 391/175/233 +f 163/161/308 377/161/307 378/162/317 +f 111/150/309 364/150/310 365/151/319 +f 59/138/312 351/138/311 352/139/321 +f 7/124/315 338/124/314 339/125/323 +f 219/175/316 391/175/233 392/176/325 +f 171/163/327 167/162/318 378/162/317 +f 119/152/329 115/151/320 365/151/319 +f 63/139/322 352/139/321 353/140/331 +f 11/125/324 339/125/323 340/128/333 +f 223/176/326 392/176/325 337/123/335 +f 171/163/327 379/163/328 380/164/336 +f 123/122/338 119/152/329 366/152/330 +f 67/140/332 353/140/331 354/58/340 +f 15/128/334 340/128/333 341/127/237 +f 175/164/337 380/164/336 381/165/342 +f 127/120/242 123/122/338 367/121/339 +f 413/142/344 525/219/509 524/179/345 +f 73/59/347 411/59/359 410/58/348 +f 21/129/350 398/129/361 397/127/351 +f 181/166/353 438/166/363 437/165/354 +f 125/120/228 129/153/364 425/153/356 +f 77/141/358 412/141/346 411/59/359 +f 25/130/360 399/130/368 398/129/361 +f 185/167/362 439/167/370 438/166/363 +f 129/153/364 133/154/371 426/154/365 +f 81/142/366 413/142/344 412/141/346 +f 29/131/367 400/131/375 399/130/368 +f 189/168/369 440/168/377 439/167/370 +f 133/154/371 137/155/379 427/155/372 +f 81/142/366 85/143/380 414/143/373 +f 33/132/374 401/132/383 400/131/375 +f 193/169/376 441/169/385 440/168/377 +f 141/156/378 428/156/387 427/155/372 +f 85/143/380 89/144/388 415/145/381 +f 33/132/374 37/133/391 402/133/382 +f 197/170/384 442/170/393 441/169/385 +f 145/157/386 429/157/395 428/156/387 +f 89/144/388 93/146/396 416/146/389 +f 41/134/390 403/134/399 402/133/382 +f 201/171/392 443/171/401 442/170/393 +f 149/158/394 430/158/403 429/157/395 +f 93/146/396 97/147/404 417/147/397 +f 45/135/398 404/135/407 403/134/399 +f 205/172/400 444/172/409 443/171/401 +f 153/159/402 431/159/411 430/158/403 +f 97/147/404 101/148/412 418/180/405 +f 45/135/398 49/136/415 405/136/406 +f 209/173/408 445/173/417 444/172/409 +f 157/160/410 432/160/419 431/159/411 +f 101/148/412 105/149/421 419/149/413 +f 53/137/414 406/137/423 405/136/406 +f 213/174/416 446/174/428 445/173/417 +f 161/161/418 433/161/430 432/160/419 +f 109/150/420 420/150/432 419/149/413 +f 57/138/422 407/138/434 406/137/423 +f 1/123/424 5/124/435 393/124/425 +f 217/175/427 447/175/438 446/174/428 +f 165/162/429 434/162/440 433/161/430 +f 113/151/431 421/151/442 420/150/432 +f 61/139/433 408/139/444 407/138/434 +f 5/124/435 9/125/446 395/125/436 +f 217/175/427 221/176/447 448/176/437 +f 169/163/439 435/163/449 434/162/440 +f 117/152/441 422/152/450 421/151/442 +f 61/139/433 65/140/451 409/140/443 +f 13/126/445 396/126/452 395/125/436 +f 221/176/447 1/123/424 394/123/426 +f 173/164/448 436/164/453 435/163/449 +f 121/122/230 423/121/229 422/152/450 +f 69/58/349 410/58/348 409/140/443 +f 13/126/445 17/127/352 397/127/351 +f 177/165/355 437/165/354 436/164/453 +f 365/151/319 364/150/310 476/181/454 +f 338/124/314 450/232/507 451/182/456 +f 392/176/325 391/175/233 503/178/235 +f 365/151/319 477/151/455 478/184/458 +f 339/125/323 451/182/456 452/185/459 +f 337/123/335 392/176/325 504/183/457 +f 366/152/330 478/184/458 479/187/461 +f 340/128/333 452/185/459 453/188/462 +f 367/121/339 479/187/461 480/189/463 +f 341/127/237 453/188/462 454/190/464 +f 368/120/343 480/189/463 481/191/465 +f 342/129/238 454/190/464 455/192/466 +f 369/153/243 481/191/465 482/193/467 +f 343/130/247 455/192/466 456/194/468 +f 370/154/251 482/193/467 483/195/469 +f 344/131/256 456/194/468 457/196/470 +f 371/155/259 483/195/469 484/197/471 +f 345/132/263 457/196/470 458/198/472 +f 372/156/267 484/197/471 485/199/473 +f 346/133/271 458/198/472 459/200/474 +f 373/157/276 485/199/473 486/201/475 +f 347/134/279 459/200/474 460/202/476 +f 374/158/283 486/201/475 487/203/477 +f 348/135/287 460/202/476 461/204/478 +f 375/159/291 487/203/477 488/205/479 +f 349/136/295 461/204/478 462/206/480 +f 376/160/299 488/205/479 489/207/481 +f 350/137/304 462/206/480 463/208/482 +f 377/161/307 489/207/481 490/209/483 +f 352/139/321 351/138/311 463/208/482 +f 378/162/317 490/209/483 491/211/485 +f 353/140/331 352/139/321 464/210/484 +f 380/164/336 379/163/328 491/211/485 +f 354/58/340 353/140/331 465/212/486 +f 381/165/342 380/164/336 492/213/487 +f 355/59/105 354/58/340 466/214/488 +f 382/166/240 381/165/342 493/215/489 +f 356/141/245 355/59/105 467/216/490 +f 383/167/249 382/166/240 494/217/491 +f 357/142/253 356/141/245 468/179/492 +f 384/168/257 383/167/249 495/218/493 +f 358/143/261 357/142/253 469/219/494 +f 385/169/266 384/168/257 496/220/495 +f 359/145/269 358/143/261 470/221/496 +f 386/170/273 385/169/266 497/222/497 +f 360/146/277 359/145/269 471/223/498 +f 387/171/281 386/170/273 498/224/499 +f 361/147/286 360/146/277 472/225/500 +f 388/172/289 387/171/281 499/226/501 +f 362/148/294 361/147/286 473/227/502 +f 389/173/297 388/172/289 500/228/503 +f 363/149/301 362/148/294 474/229/504 +f 390/174/306 389/173/297 501/230/505 +f 364/150/310 363/149/301 475/231/506 +f 337/123/335 449/186/460 450/232/507 +f 440/168/377 552/220/510 551/218/508 +f 414/143/373 526/221/511 525/219/509 +f 441/169/385 553/222/512 552/220/510 +f 415/145/381 527/223/513 526/221/511 +f 442/170/393 554/224/514 553/222/512 +f 416/146/389 528/225/515 527/223/513 +f 443/171/401 555/226/516 554/224/514 +f 417/147/397 529/227/517 528/225/515 +f 444/172/409 556/228/518 555/226/516 +f 418/180/405 530/229/519 529/227/517 +f 445/173/417 557/230/520 556/228/518 +f 419/149/413 531/231/521 530/229/519 +f 446/174/428 558/177/523 557/230/520 +f 420/150/432 532/181/524 531/231/521 +f 394/123/426 393/124/425 505/232/522 +f 447/175/438 559/178/526 558/177/523 +f 421/151/442 533/151/455 532/181/524 +f 393/124/425 395/125/436 507/182/525 +f 448/176/437 560/183/529 559/178/526 +f 421/151/442 422/152/450 534/184/527 +f 395/125/436 396/126/452 508/185/528 +f 394/123/426 506/186/460 560/183/529 +f 422/152/450 423/121/229 535/233/530 +f 396/126/452 397/127/351 509/188/531 +f 423/121/229 424/120/357 536/189/532 +f 397/127/351 398/129/361 510/190/533 +f 424/120/357 425/153/356 537/191/534 +f 398/129/361 399/130/368 511/192/535 +f 425/153/356 426/154/365 538/193/536 +f 399/130/368 400/131/375 512/194/537 +f 426/154/365 427/155/372 539/195/538 +f 400/131/375 401/132/383 513/196/539 +f 427/155/372 428/156/387 540/197/540 +f 401/132/383 402/133/382 514/198/541 +f 428/156/387 429/157/395 541/199/542 +f 402/133/382 403/134/399 515/200/543 +f 429/157/395 430/158/403 542/201/544 +f 403/134/399 404/135/407 516/202/545 +f 430/158/403 431/159/411 543/203/546 +f 404/135/407 405/136/406 517/204/547 +f 431/159/411 432/160/419 544/205/548 +f 405/136/406 406/137/423 518/206/549 +f 432/160/419 433/161/430 545/207/550 +f 406/137/423 407/138/434 519/208/551 +f 433/161/430 434/162/440 546/209/552 +f 408/139/444 520/210/554 519/208/551 +f 434/162/440 435/163/449 547/211/553 +f 409/140/443 521/212/555 520/210/554 +f 436/164/453 548/213/556 547/211/553 +f 410/58/348 522/214/557 521/212/555 +f 437/165/354 549/215/558 548/213/556 +f 411/59/359 523/216/559 522/214/557 +f 438/166/363 550/217/560 549/215/558 +f 412/141/346 524/179/345 523/216/559 +f 439/167/370 551/218/508 550/217/560 diff --git a/Telegram/Resources/art/premium/coin_stars.obj b/Telegram/Resources/art/premium/coin_stars.obj new file mode 100644 index 00000000000000..df345a485650cc --- /dev/null +++ b/Telegram/Resources/art/premium/coin_stars.obj @@ -0,0 +1,2137 @@ +# 3D mesh decoded from .binobj (source of truth; baked back to .binobj at build time). +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v 17.0529709 -7.40135288 0.987299025 +v 16.6459484 -7.8074832 0.987299025 +v 15.810586 -7.992764 0.987299025 +v 15.7302771 -8.02249527 0.987299025 +v 15.6758432 -8.08117867 0.987299025 +v 15.6558008 -8.15955257 0.987299025 +v 15.6762972 -8.23908234 0.987299025 +v 15.7315817 -8.29777431 0.987299025 +v 15.8123465 -8.32634068 0.987299025 +v 16.6506844 -8.49965286 0.987299025 +v 17.0602894 -8.91117954 0.987299025 +v 17.2539082 -9.77368546 0.987299025 +v 17.2842293 -9.85181046 0.987299025 +v 17.3420792 -9.90405846 0.987299025 +v 17.4188404 -9.92310333 0.987299025 +v 17.4956017 -9.90405846 0.987299025 +v 17.5534496 -9.85181046 0.987299025 +v 17.5837631 -9.77368546 0.987299025 +v 17.782814 -8.91389275 0.987299025 +v 18.191864 -8.50390339 0.987299025 +v 19.0288582 -8.32634068 0.987299025 +v 19.1083832 -8.29777527 0.987299025 +v 19.1632099 -8.23908329 0.987299025 +v 19.1836452 -8.15955353 0.987299025 +v 19.162756 -8.08078766 0.987299025 +v 19.1070786 -8.02171326 0.987299025 +v 19.0271034 -7.99276495 0.987299025 +v 18.187521 -7.81945276 0.987299025 +v 17.7774563 -7.40792608 0.987299025 +v 17.583765 -6.54542208 0.987299025 +v 17.5538425 -6.46729708 0.987299025 +v 17.4963875 -6.41504812 0.987299025 +v 17.4188423 -6.3960042 0.987299025 +v 17.3432541 -6.41504812 0.987299025 +v 17.2854042 -6.46729708 0.987299025 +v 17.253912 -6.54542208 0.987299025 +v 4.9120512 20.9299164 0.987299025 +v 4.94354296 21.0080414 0.987299025 +v 5.00139284 21.0602913 0.987299025 +v 5.07698202 21.0793343 0.987299025 +v 5.15452719 21.0602913 0.987299025 +v 5.21198177 21.0080414 0.987299025 +v 5.24190378 20.9299164 0.987299025 +v 5.43559599 20.0674133 0.987299025 +v 5.84566021 19.6558838 0.987299025 +v 6.68524313 19.4825726 0.987299025 +v 6.76521778 19.4536247 0.987299025 +v 6.8208952 19.3945503 0.987299025 +v 6.841784 19.3157845 0.987299025 +v 6.82134914 19.2362556 0.987299025 +v 6.76652193 19.1775627 0.987299025 +v 6.68699694 19.1489983 0.987299025 +v 5.85000277 18.9714355 0.987299025 +v 5.44095278 18.5614471 0.987299025 +v 5.24190187 17.7016525 0.987299025 +v 5.21158886 17.6235275 0.987299025 +v 5.15374088 17.5712795 0.987299025 +v 5.07698011 17.5522346 0.987299025 +v 5.00021791 17.5712795 0.987299025 +v 4.94236898 17.6235275 0.987299025 +v 4.91204691 17.7016525 0.987299025 +v 4.71842909 18.5641575 0.987299025 +v 4.30882406 18.9756851 0.987299025 +v 3.47048593 19.1489983 0.987299025 +v 3.38972092 19.1775627 0.987299025 +v 3.33443689 19.2362556 0.987299025 +v 3.31394005 19.3157845 0.987299025 +v 3.33398294 19.3941593 0.987299025 +v 3.38841701 19.4528427 0.987299025 +v 3.46872497 19.4825745 0.987299025 +v 4.30408812 19.6678543 0.987299025 +v 4.71111012 20.0739861 0.987299025 +v 17.0529709 -7.40135288 0.987299025 +v 17.0529709 -7.40135288 0.503844023 +v 16.6459484 -7.8074832 0.987299025 +v 16.6459484 -7.8074832 0.503844023 +v 15.810586 -7.992764 0.987299025 +v 15.810586 -7.992764 0.503844023 +v 15.7302771 -8.02249527 0.987299025 +v 15.7302771 -8.02249527 0.503844023 +v 15.6758432 -8.08117867 0.987299025 +v 15.6758432 -8.08117867 0.503844023 +v 15.6558008 -8.15955257 0.987299025 +v 15.6558008 -8.15955257 0.503844023 +v 15.6762972 -8.23908234 0.987299025 +v 15.6762972 -8.23908234 0.503844023 +v 15.7315817 -8.29777431 0.987299025 +v 15.7315817 -8.29777431 0.503844023 +v 15.8123465 -8.32634068 0.987299025 +v 15.8123465 -8.32634068 0.503844023 +v 16.6506844 -8.49965286 0.987299025 +v 16.6506844 -8.49965286 0.503844023 +v 17.0602894 -8.91117954 0.987299025 +v 17.0602894 -8.91117954 0.503844023 +v 17.2539082 -9.77368546 0.987299025 +v 17.2539082 -9.77368546 0.503844023 +v 17.2842293 -9.85181046 0.987299025 +v 17.2842293 -9.85181046 0.503844023 +v 17.3420792 -9.90405846 0.987299025 +v 17.3420792 -9.90405846 0.503844023 +v 17.4188404 -9.92310333 0.987299025 +v 17.4188404 -9.92310333 0.503844023 +v 17.4956017 -9.90405846 0.987299025 +v 17.4956017 -9.90405846 0.503844023 +v 17.5534496 -9.85181046 0.987299025 +v 17.5534496 -9.85181046 0.503844023 +v 17.5837631 -9.77368546 0.987299025 +v 17.5837631 -9.77368546 0.503844023 +v 17.782814 -8.91389275 0.987299025 +v 17.782814 -8.91389275 0.503844023 +v 18.191864 -8.50390339 0.987299025 +v 18.191864 -8.50390339 0.503844023 +v 19.0288582 -8.32634068 0.987299025 +v 19.0288582 -8.32634068 0.503844023 +v 19.1083832 -8.29777527 0.987299025 +v 19.1083832 -8.29777527 0.503844023 +v 19.1632099 -8.23908329 0.987299025 +v 19.1632099 -8.23908329 0.503844023 +v 19.1836452 -8.15955353 0.987299025 +v 19.1836452 -8.15955353 0.503844023 +v 19.162756 -8.08078766 0.987299025 +v 19.162756 -8.08078766 0.503844023 +v 19.1070786 -8.02171326 0.987299025 +v 19.1070786 -8.02171326 0.503844023 +v 19.0271034 -7.99276495 0.987299025 +v 19.0271034 -7.99276495 0.503844023 +v 18.187521 -7.81945276 0.503844023 +v 17.7774563 -7.40792608 0.987299025 +v 17.7774563 -7.40792608 0.503844023 +v 17.583765 -6.54542208 0.987299025 +v 17.583765 -6.54542208 0.503844023 +v 17.5538425 -6.46729708 0.987299025 +v 17.5538425 -6.46729708 0.503844023 +v 17.4963875 -6.41504812 0.987299025 +v 17.4963875 -6.41504812 0.503844023 +v 17.4188423 -6.3960042 0.987299025 +v 17.4188423 -6.3960042 0.503844023 +v 17.3432541 -6.41504812 0.987299025 +v 17.3432541 -6.41504812 0.503844023 +v 17.2854042 -6.46729708 0.987299025 +v 17.2854042 -6.46729708 0.503844023 +v 17.253912 -6.54542208 0.987299025 +v 17.253912 -6.54542208 0.503844023 +v -9.73268127 19.0501385 0.987299025 +v 4.9120512 20.9299164 0.503844023 +v 4.9120512 20.9299164 0.987299025 +v 4.94354296 21.0080414 0.503844023 +v 4.94354296 21.0080414 0.987299025 +v 5.00139284 21.0602913 0.503844023 +v 5.00139284 21.0602913 0.987299025 +v 5.07698202 21.0793343 0.503844023 +v 5.07698202 21.0793343 0.987299025 +v 5.15452719 21.0602913 0.503844023 +v 5.15452719 21.0602913 0.987299025 +v 5.21198177 21.0080414 0.503844023 +v 5.21198177 21.0080414 0.987299025 +v 5.24190378 20.9299164 0.503844023 +v 5.24190378 20.9299164 0.987299025 +v 5.43559599 20.0674133 0.503844023 +v 5.43559599 20.0674133 0.987299025 +v 5.84566021 19.6558838 0.503844023 +v 6.68524313 19.4825726 0.503844023 +v 6.68524313 19.4825726 0.987299025 +v 6.76521778 19.4536247 0.503844023 +v 6.76521778 19.4536247 0.987299025 +v 6.8208952 19.3945503 0.503844023 +v 6.8208952 19.3945503 0.987299025 +v 6.841784 19.3157845 0.503844023 +v 6.841784 19.3157845 0.987299025 +v 6.82134914 19.2362556 0.503844023 +v 6.82134914 19.2362556 0.987299025 +v 6.76652193 19.1775627 0.503844023 +v 6.76652193 19.1775627 0.987299025 +v 6.68699694 19.1489983 0.503844023 +v 6.68699694 19.1489983 0.987299025 +v 5.85000277 18.9714355 0.503844023 +v 5.85000277 18.9714355 0.987299025 +v 5.44095278 18.5614471 0.503844023 +v 5.44095278 18.5614471 0.987299025 +v 5.24190187 17.7016525 0.503844023 +v 5.24190187 17.7016525 0.987299025 +v 5.21158886 17.6235275 0.503844023 +v 5.21158886 17.6235275 0.987299025 +v 5.15374088 17.5712795 0.503844023 +v 5.15374088 17.5712795 0.987299025 +v 5.07698011 17.5522346 0.503844023 +v 5.07698011 17.5522346 0.987299025 +v 5.00021791 17.5712795 0.503844023 +v 5.00021791 17.5712795 0.987299025 +v 4.94236898 17.6235275 0.503844023 +v 4.94236898 17.6235275 0.987299025 +v 4.91204691 17.7016525 0.503844023 +v 4.91204691 17.7016525 0.987299025 +v 4.71842909 18.5641575 0.503844023 +v 4.71842909 18.5641575 0.987299025 +v 4.30882406 18.9756851 0.503844023 +v 4.30882406 18.9756851 0.987299025 +v 3.47048593 19.1489983 0.503844023 +v 3.47048593 19.1489983 0.987299025 +v 3.38972092 19.1775627 0.503844023 +v 3.38972092 19.1775627 0.987299025 +v 3.33443689 19.2362556 0.503844023 +v 3.33443689 19.2362556 0.987299025 +v 3.31394005 19.3157845 0.503844023 +v 3.31394005 19.3157845 0.987299025 +v 3.33398294 19.3941593 0.503844023 +v 3.33398294 19.3941593 0.987299025 +v 3.38841701 19.4528427 0.503844023 +v 3.38841701 19.4528427 0.987299025 +v 3.46872497 19.4825745 0.503844023 +v 3.46872497 19.4825745 0.987299025 +v 4.30408812 19.6678543 0.503844023 +v 4.30408812 19.6678543 0.987299025 +v 4.71111012 20.0739861 0.503844023 +v 4.71111012 20.0739861 0.987299025 +v 17.4523716 11.6629057 0.999832988 +v 17.0453491 11.2567759 0.999834001 +v 16.2099857 11.0714941 0.999834001 +v 16.1296768 11.0417633 0.999834001 +v 16.075243 10.9830799 0.999834001 +v 16.0552006 10.904706 0.999834001 +v 16.0756969 10.8251762 0.999834001 +v 16.1309814 10.7664843 0.999834001 +v 16.2117462 10.7379179 0.999834001 +v 17.0500851 10.5646067 0.999834001 +v 17.4596901 10.1530781 0.999834001 +v 17.6533089 9.29057407 0.999834001 +v 17.68363 9.21244812 0.999834001 +v 17.7414799 9.16020012 0.999834001 +v 17.8182411 9.14115524 0.999834001 +v 17.8950024 9.16020012 0.999834001 +v 17.9528503 9.21244812 0.999834001 +v 17.9831638 9.29057407 0.999834001 +v 18.1822147 10.1503668 0.999834001 +v 18.5912647 10.5603552 0.999834001 +v 19.4282589 10.7379179 0.999834001 +v 19.5077839 10.7664833 0.999834001 +v 19.5626106 10.8251753 0.999834001 +v 19.583046 10.904705 0.999834001 +v 19.5621567 10.9834709 0.999834001 +v 19.5064793 11.0425453 0.999834001 +v 19.4265041 11.0714931 0.999834001 +v 18.5869217 11.2448053 0.999834001 +v 18.176857 11.656333 0.999832988 +v 17.9831657 12.518837 0.999832988 +v 17.9532433 12.596962 0.999832988 +v 17.8957882 12.6492109 0.999832988 +v 17.818243 12.6682549 0.999832988 +v 17.7426548 12.6492109 0.999832988 +v 17.6848049 12.596962 0.999832988 +v 17.6533127 12.518837 0.999832988 +v 17.4523716 11.6629057 0.999832988 +v 17.4523716 11.6629057 0.516377985 +v 17.0453491 11.2567759 0.999834001 +v 17.0453491 11.2567759 0.516378999 +v 16.2099857 11.0714941 0.999834001 +v 16.2099857 11.0714941 0.516378999 +v 16.1296768 11.0417633 0.999834001 +v 16.1296768 11.0417633 0.516378999 +v 16.075243 10.9830799 0.999834001 +v 16.075243 10.9830799 0.516378999 +v 16.0552006 10.904706 0.999834001 +v 16.0552006 10.904706 0.516378999 +v 16.0756969 10.8251762 0.999834001 +v 16.0756969 10.8251762 0.516378999 +v 16.1309814 10.7664843 0.999834001 +v 16.1309814 10.7664843 0.516378999 +v 16.2117462 10.7379179 0.999834001 +v 16.2117462 10.7379179 0.516378999 +v 17.0500851 10.5646067 0.999834001 +v 17.0500851 10.5646067 0.516378999 +v 17.4596901 10.1530781 0.999834001 +v 17.4596901 10.1530781 0.516378999 +v 17.6533089 9.29057407 0.999834001 +v 17.6533089 9.29057407 0.516378999 +v 17.68363 9.21244812 0.999834001 +v 17.68363 9.21244812 0.516378999 +v 17.7414799 9.16020012 0.999834001 +v 17.7414799 9.16020012 0.516378999 +v 17.8182411 9.14115524 0.999834001 +v 17.8182411 9.14115524 0.516378999 +v 17.8950024 9.16020012 0.999834001 +v 17.8950024 9.16020012 0.516378999 +v 17.9528503 9.21244812 0.999834001 +v 17.9528503 9.21244812 0.516378999 +v 17.9831638 9.29057407 0.999834001 +v 17.9831638 9.29057407 0.516378999 +v 18.1822147 10.1503668 0.999834001 +v 18.1822147 10.1503668 0.516378999 +v 18.5912647 10.5603552 0.999834001 +v 18.5912647 10.5603552 0.516378999 +v 19.4282589 10.7379179 0.999834001 +v 19.4282589 10.7379179 0.516378999 +v 19.5077839 10.7664833 0.999834001 +v 19.5077839 10.7664833 0.516378999 +v 19.5626106 10.8251753 0.999834001 +v 19.5626106 10.8251753 0.516378999 +v 19.583046 10.904705 0.999834001 +v 19.583046 10.904705 0.516378999 +v 19.5621567 10.9834709 0.999834001 +v 19.5621567 10.9834709 0.516378999 +v 19.5064793 11.0425453 0.999834001 +v 19.5064793 11.0425453 0.516378999 +v 19.4265041 11.0714931 0.999834001 +v 19.4265041 11.0714931 0.516378999 +v 18.5869217 11.2448053 0.516378999 +v 18.176857 11.656333 0.999832988 +v 18.176857 11.656333 0.516377985 +v 17.9831657 12.518837 0.999832988 +v 17.9831657 12.518837 0.516377985 +v 17.9532433 12.596962 0.999832988 +v 17.9532433 12.596962 0.516377985 +v 17.8957882 12.6492109 0.999832988 +v 17.8957882 12.6492109 0.516377985 +v 17.818243 12.6682549 0.999832988 +v 17.818243 12.6682549 0.516377985 +v 17.7426548 12.6492109 0.999832988 +v 17.7426548 12.6492109 0.516377985 +v 17.6848049 12.596962 0.999832988 +v 17.6848049 12.596962 0.516377985 +v 17.6533127 12.518837 0.999832988 +v 17.6533127 12.518837 0.516377985 +v -9.70118904 19.1282635 0.987299025 +v -9.64333916 19.1805134 0.987299025 +v -9.56775093 19.1995564 0.987299025 +v -9.49020576 19.1805134 0.987299025 +v -9.4327507 19.1282635 0.987299025 +v -9.40282822 19.0501385 0.987299025 +v -9.20913696 18.1876354 0.987299025 +v -8.79907227 17.776104 0.987299025 +v -7.95948982 17.6027927 0.987299025 +v -7.87951517 17.5738449 0.987299025 +v -7.8238368 17.5147705 0.987299025 +v -7.802948 17.4360046 0.987299025 +v -7.82338285 17.3564758 0.987299025 +v -7.87821007 17.2977829 0.987299025 +v -7.95773506 17.2692184 0.987299025 +v -8.79472923 17.0916557 0.987299025 +v -9.20377922 16.6816673 0.987299025 +v -9.40283012 15.8218727 0.987299025 +v -9.43314362 15.7437477 0.987299025 +v -9.49099159 15.6914997 0.987299025 +v -9.56775284 15.6724548 0.987299025 +v -9.64451408 15.6914997 0.987299025 +v -9.70236397 15.7437477 0.987299025 +v -9.73268509 15.8218727 0.987299025 +v -9.92630386 16.6843777 0.987299025 +v -10.3359089 17.0959053 0.987299025 +v -11.1742468 17.2692184 0.987299025 +v -11.2550116 17.2977829 0.987299025 +v -11.3102961 17.3564758 0.987299025 +v -11.3307924 17.4360046 0.987299025 +v -11.31075 17.5143795 0.987299025 +v -11.2563162 17.5730629 0.987299025 +v -11.1760073 17.6027946 0.987299025 +v -10.3406448 17.7880745 0.987299025 +v -9.93362236 18.1942081 0.987299025 +v -9.73268127 19.0501385 0.503844023 +v -9.73268127 19.0501385 0.987299025 +v -9.70118904 19.1282635 0.503844023 +v -9.70118904 19.1282635 0.987299025 +v -9.64333916 19.1805134 0.503844023 +v -9.64333916 19.1805134 0.987299025 +v -9.56775093 19.1995564 0.503844023 +v -9.56775093 19.1995564 0.987299025 +v -9.49020576 19.1805134 0.503844023 +v -9.49020576 19.1805134 0.987299025 +v -9.4327507 19.1282635 0.503844023 +v -9.4327507 19.1282635 0.987299025 +v -9.40282822 19.0501385 0.503844023 +v -9.40282822 19.0501385 0.987299025 +v -9.20913696 18.1876354 0.503844023 +v -9.20913696 18.1876354 0.987299025 +v -8.79907227 17.776104 0.503844023 +v -7.95948982 17.6027927 0.503844023 +v -7.95948982 17.6027927 0.987299025 +v -7.87951517 17.5738449 0.503844023 +v -7.87951517 17.5738449 0.987299025 +v -7.8238368 17.5147705 0.503844023 +v -7.8238368 17.5147705 0.987299025 +v -7.802948 17.4360046 0.503844023 +v -7.802948 17.4360046 0.987299025 +v -7.82338285 17.3564758 0.503844023 +v -7.82338285 17.3564758 0.987299025 +v -7.87821007 17.2977829 0.503844023 +v -7.87821007 17.2977829 0.987299025 +v -7.95773506 17.2692184 0.503844023 +v -7.95773506 17.2692184 0.987299025 +v -8.79472923 17.0916557 0.503844023 +v -8.79472923 17.0916557 0.987299025 +v -9.20377922 16.6816673 0.503844023 +v -9.20377922 16.6816673 0.987299025 +v -9.40283012 15.8218727 0.503844023 +v -9.40283012 15.8218727 0.987299025 +v -9.43314362 15.7437477 0.503844023 +v -9.43314362 15.7437477 0.987299025 +v -9.49099159 15.6914997 0.503844023 +v -9.49099159 15.6914997 0.987299025 +v -9.56775284 15.6724548 0.503844023 +v -9.56775284 15.6724548 0.987299025 +v -9.64451408 15.6914997 0.503844023 +v -9.64451408 15.6914997 0.987299025 +v -9.70236397 15.7437477 0.503844023 +v -9.70236397 15.7437477 0.987299025 +v -9.73268509 15.8218727 0.503844023 +v -9.73268509 15.8218727 0.987299025 +v -9.92630386 16.6843777 0.503844023 +v -9.92630386 16.6843777 0.987299025 +v -10.3359089 17.0959053 0.503844023 +v -10.3359089 17.0959053 0.987299025 +v -11.1742468 17.2692184 0.503844023 +v -11.1742468 17.2692184 0.987299025 +v -11.2550116 17.2977829 0.503844023 +v -11.2550116 17.2977829 0.987299025 +v -11.3102961 17.3564758 0.503844023 +v -11.3102961 17.3564758 0.987299025 +v -11.3307924 17.4360046 0.503844023 +v -11.3307924 17.4360046 0.987299025 +v -11.31075 17.5143795 0.503844023 +v -11.31075 17.5143795 0.987299025 +v -11.2563162 17.5730629 0.503844023 +v -11.2563162 17.5730629 0.987299025 +v -11.1760073 17.6027946 0.503844023 +v -11.1760073 17.6027946 0.987299025 +v -10.3406448 17.7880745 0.503844023 +v -10.3406448 17.7880745 0.987299025 +v -9.93362236 18.1942081 0.503844023 +v -9.93362236 18.1942081 0.987299025 +v -19.4914989 6.60830307 0.987299025 +v -19.4600067 6.68642807 0.987299025 +v -19.4021568 6.73867798 0.987299025 +v -19.3265686 6.75772095 0.987299025 +v -19.2490234 6.73867798 0.987299025 +v -19.1915684 6.68642807 0.987299025 +v -19.1616459 6.60830307 0.987299025 +v -18.9679546 5.74580002 0.987299025 +v -18.5578899 5.33427 0.987299025 +v -17.7183075 5.16095781 0.987299025 +v -17.6383324 5.13200998 0.987299025 +v -17.582655 5.07293606 0.987299025 +v -17.5617657 4.99417019 0.987299025 +v -17.582201 4.9146409 0.987299025 +v -17.6370277 4.85594797 0.987299025 +v -17.7165527 4.827384 0.987299025 +v -18.5535469 4.6498208 0.987299025 +v -18.9625969 4.23983288 0.987299025 +v -19.1616478 3.38003802 0.987299025 +v -19.1919613 3.30191302 0.987299025 +v -19.2498093 3.24966502 0.987299025 +v -19.3265705 3.23061991 0.987299025 +v -19.4033318 3.24966502 0.987299025 +v -19.4611816 3.30191302 0.987299025 +v -19.4915028 3.38003802 0.987299025 +v -19.6851215 4.24254322 0.987299025 +v -20.0947266 4.65407085 0.987299025 +v -20.9330654 4.827384 0.987299025 +v -21.0138302 4.85594797 0.987299025 +v -21.0691147 4.9146409 0.987299025 +v -21.0896111 4.99417019 0.987299025 +v -21.0695686 5.07254505 0.987299025 +v -21.0151348 5.13122797 0.987299025 +v -20.9348259 5.1609602 0.987299025 +v -20.0994625 5.34624004 0.987299025 +v -19.69244 5.75237322 0.987299025 +v -19.4914989 6.60830307 0.503844023 +v -19.4914989 6.60830307 0.987299025 +v -19.4600067 6.68642807 0.503844023 +v -19.4600067 6.68642807 0.987299025 +v -19.4021568 6.73867798 0.503844023 +v -19.4021568 6.73867798 0.987299025 +v -19.3265686 6.75772095 0.503844023 +v -19.3265686 6.75772095 0.987299025 +v -19.2490234 6.73867798 0.503844023 +v -19.2490234 6.73867798 0.987299025 +v -19.1915684 6.68642807 0.503844023 +v -19.1915684 6.68642807 0.987299025 +v -19.1616459 6.60830307 0.503844023 +v -19.1616459 6.60830307 0.987299025 +v -18.9679546 5.74580002 0.503844023 +v -18.9679546 5.74580002 0.987299025 +v -18.5578899 5.33427 0.503844023 +v -17.7183075 5.16095781 0.503844023 +v -17.7183075 5.16095781 0.987299025 +v -17.6383324 5.13200998 0.503844023 +v -17.6383324 5.13200998 0.987299025 +v -17.582655 5.07293606 0.503844023 +v -17.582655 5.07293606 0.987299025 +v -17.5617657 4.99417019 0.503844023 +v -17.5617657 4.99417019 0.987299025 +v -17.582201 4.9146409 0.503844023 +v -17.582201 4.9146409 0.987299025 +v -17.6370277 4.85594797 0.503844023 +v -17.6370277 4.85594797 0.987299025 +v -17.7165527 4.827384 0.503844023 +v -17.7165527 4.827384 0.987299025 +v -18.5535469 4.6498208 0.503844023 +v -18.5535469 4.6498208 0.987299025 +v -18.9625969 4.23983288 0.503844023 +v -18.9625969 4.23983288 0.987299025 +v -19.1616478 3.38003802 0.503844023 +v -19.1616478 3.38003802 0.987299025 +v -19.1919613 3.30191302 0.503844023 +v -19.1919613 3.30191302 0.987299025 +v -19.2498093 3.24966502 0.503844023 +v -19.2498093 3.24966502 0.987299025 +v -19.3265705 3.23061991 0.503844023 +v -19.3265705 3.23061991 0.987299025 +v -19.4033318 3.24966502 0.503844023 +v -19.4033318 3.24966502 0.987299025 +v -19.4611816 3.30191302 0.503844023 +v -19.4611816 3.30191302 0.987299025 +v -19.4915028 3.38003802 0.503844023 +v -19.4915028 3.38003802 0.987299025 +v -19.6851215 4.24254322 0.503844023 +v -19.6851215 4.24254322 0.987299025 +v -20.0947266 4.65407085 0.503844023 +v -20.0947266 4.65407085 0.987299025 +v -20.9330654 4.827384 0.503844023 +v -20.9330654 4.827384 0.987299025 +v -21.0138302 4.85594797 0.503844023 +v -21.0138302 4.85594797 0.987299025 +v -21.0691147 4.9146409 0.503844023 +v -21.0691147 4.9146409 0.987299025 +v -21.0896111 4.99417019 0.503844023 +v -21.0896111 4.99417019 0.987299025 +v -21.0695686 5.07254505 0.503844023 +v -21.0695686 5.07254505 0.987299025 +v -21.0151348 5.13122797 0.503844023 +v -21.0151348 5.13122797 0.987299025 +v -20.9348259 5.1609602 0.503844023 +v -20.9348259 5.1609602 0.987299025 +v -20.0994625 5.34624004 0.503844023 +v -20.0994625 5.34624004 0.987299025 +v -19.69244 5.75237322 0.503844023 +v -19.69244 5.75237322 0.987299025 +v -17.2138233 -7.84859419 0.987299025 +v -17.1823311 -7.77046919 0.987299025 +v -17.1244812 -7.7182188 0.987299025 +v -17.048893 -7.69917583 0.987299025 +v -16.9713478 -7.7182188 0.987299025 +v -16.9138927 -7.77046919 0.987299025 +v -16.8839703 -7.84859419 0.987299025 +v -16.690279 -8.71109676 0.987299025 +v -16.2802143 -9.12262726 0.987299025 +v -15.4406319 -9.29593754 0.987299025 +v -15.3606567 -9.32488632 0.987299025 +v -15.3049793 -9.38396072 0.987299025 +v -15.28409 -9.46272659 0.987299025 +v -15.3045254 -9.5422554 0.987299025 +v -15.3593521 -9.60094833 0.987299025 +v -15.4388771 -9.62951279 0.987299025 +v -16.2758713 -9.80707645 0.987299025 +v -16.6849213 -10.2170639 0.987299025 +v -16.8839722 -11.0768595 0.987299025 +v -16.9142857 -11.1549845 0.987299025 +v -16.9721336 -11.2072325 0.987299025 +v -17.0488949 -11.2262764 0.987299025 +v -17.1256561 -11.2072325 0.987299025 +v -17.183506 -11.1549845 0.987299025 +v -17.2138271 -11.0768595 0.987299025 +v -17.4074459 -10.2143536 0.987299025 +v -17.8170509 -9.80282593 0.987299025 +v -18.6553898 -9.62951279 0.987299025 +v -18.7361546 -9.60094833 0.987299025 +v -18.7914391 -9.5422554 0.987299025 +v -18.8119354 -9.46272659 0.987299025 +v -18.791893 -9.38435173 0.987299025 +v -18.7374592 -9.32566833 0.987299025 +v -18.6571503 -9.29593658 0.987299025 +v -17.8217869 -9.11065674 0.987299025 +v -17.4147644 -8.70452404 0.987299025 +v -17.2138233 -7.84859419 0.503844023 +v -17.2138233 -7.84859419 0.987299025 +v -17.1823311 -7.77046919 0.503844023 +v -17.1823311 -7.77046919 0.987299025 +v -17.1244812 -7.7182188 0.503844023 +v -17.1244812 -7.7182188 0.987299025 +v -17.048893 -7.69917583 0.503844023 +v -17.048893 -7.69917583 0.987299025 +v -16.9713478 -7.7182188 0.503844023 +v -16.9713478 -7.7182188 0.987299025 +v -16.9138927 -7.77046919 0.503844023 +v -16.9138927 -7.77046919 0.987299025 +v -16.8839703 -7.84859419 0.503844023 +v -16.8839703 -7.84859419 0.987299025 +v -16.690279 -8.71109676 0.503844023 +v -16.690279 -8.71109676 0.987299025 +v -16.2802143 -9.12262726 0.503844023 +v -15.4406319 -9.29593754 0.503844023 +v -15.4406319 -9.29593754 0.987299025 +v -15.3606567 -9.32488632 0.503844023 +v -15.3606567 -9.32488632 0.987299025 +v -15.3049793 -9.38396072 0.503844023 +v -15.3049793 -9.38396072 0.987299025 +v -15.28409 -9.46272659 0.503844023 +v -15.28409 -9.46272659 0.987299025 +v -15.3045254 -9.5422554 0.503844023 +v -15.3045254 -9.5422554 0.987299025 +v -15.3593521 -9.60094833 0.503844023 +v -15.3593521 -9.60094833 0.987299025 +v -15.4388771 -9.62951279 0.503844023 +v -15.4388771 -9.62951279 0.987299025 +v -16.2758713 -9.80707645 0.503844023 +v -16.2758713 -9.80707645 0.987299025 +v -16.6849213 -10.2170639 0.503844023 +v -16.6849213 -10.2170639 0.987299025 +v -16.8839722 -11.0768595 0.503844023 +v -16.8839722 -11.0768595 0.987299025 +v -16.9142857 -11.1549845 0.503844023 +v -16.9142857 -11.1549845 0.987299025 +v -16.9721336 -11.2072325 0.503844023 +v -16.9721336 -11.2072325 0.987299025 +v -17.0488949 -11.2262764 0.503844023 +v -17.0488949 -11.2262764 0.987299025 +v -17.1256561 -11.2072325 0.503844023 +v -17.1256561 -11.2072325 0.987299025 +v -17.183506 -11.1549845 0.503844023 +v -17.183506 -11.1549845 0.987299025 +v -17.2138271 -11.0768595 0.503844023 +v -17.2138271 -11.0768595 0.987299025 +v -17.4074459 -10.2143536 0.503844023 +v -17.4074459 -10.2143536 0.987299025 +v -17.8170509 -9.80282593 0.503844023 +v -17.8170509 -9.80282593 0.987299025 +v -18.6553898 -9.62951279 0.503844023 +v -18.6553898 -9.62951279 0.987299025 +v -18.7361546 -9.60094833 0.503844023 +v -18.7361546 -9.60094833 0.987299025 +v -18.7914391 -9.5422554 0.503844023 +v -18.7914391 -9.5422554 0.987299025 +v -18.8119354 -9.46272659 0.503844023 +v -18.8119354 -9.46272659 0.987299025 +v -18.791893 -9.38435173 0.503844023 +v -18.791893 -9.38435173 0.987299025 +v -18.7374592 -9.32566833 0.503844023 +v -18.7374592 -9.32566833 0.987299025 +v -18.6571503 -9.29593658 0.503844023 +v -18.6571503 -9.29593658 0.987299025 +v -17.8217869 -9.11065674 0.503844023 +v -17.8217869 -9.11065674 0.987299025 +v -17.4147644 -8.70452404 0.503844023 +v -17.4147644 -8.70452404 0.987299025 +v -6.44442987 -16.9490128 0.987299025 +v -6.41293812 -16.8708878 0.987299025 +v -6.35508823 -16.8186378 0.987299025 +v -6.27950001 -16.7995949 0.987299025 +v -6.20195484 -16.8186378 0.987299025 +v -6.14449978 -16.8708878 0.987299025 +v -6.11457682 -16.9490128 0.987299025 +v -5.92088604 -17.8115158 0.987299025 +v -5.51082087 -18.2230453 0.987299025 +v -4.6712389 -18.3963585 0.987299025 +v -4.59126377 -18.4253044 0.987299025 +v -4.53558588 -18.4843788 0.987299025 +v -4.51469707 -18.5631447 0.987299025 +v -4.53513193 -18.6426735 0.987299025 +v -4.58995914 -18.7013664 0.987299025 +v -4.66948414 -18.7299309 0.987299025 +v -5.50647783 -18.9074936 0.987299025 +v -5.91552782 -19.317482 0.987299025 +v -6.1145792 -20.1772766 0.987299025 +v -6.14489317 -20.2554016 0.987299025 +v -6.20274115 -20.3076515 0.987299025 +v -6.27950191 -20.3266945 0.987299025 +v -6.35626316 -20.3076515 0.987299025 +v -6.41411304 -20.2554016 0.987299025 +v -6.44443417 -20.1772766 0.987299025 +v -6.63805294 -19.3147736 0.987299025 +v -7.04765797 -18.903244 0.987299025 +v -7.88599682 -18.7299309 0.987299025 +v -7.96676207 -18.7013664 0.987299025 +v -8.02204609 -18.6426735 0.987299025 +v -8.04254246 -18.5631447 0.987299025 +v -8.02250004 -18.4847717 0.987299025 +v -7.96806622 -18.4260864 0.987299025 +v -7.88775682 -18.3963547 0.987299025 +v -7.05239391 -18.2110748 0.987299025 +v -6.64537096 -17.8049431 0.987299025 +v -6.44442987 -16.9490128 0.503844023 +v -6.44442987 -16.9490128 0.987299025 +v -6.41293812 -16.8708878 0.503844023 +v -6.41293812 -16.8708878 0.987299025 +v -6.35508823 -16.8186378 0.503844023 +v -6.35508823 -16.8186378 0.987299025 +v -6.27950001 -16.7995949 0.503844023 +v -6.27950001 -16.7995949 0.987299025 +v -6.20195484 -16.8186378 0.503844023 +v -6.20195484 -16.8186378 0.987299025 +v -6.14449978 -16.8708878 0.503844023 +v -6.14449978 -16.8708878 0.987299025 +v -6.11457682 -16.9490128 0.503844023 +v -6.11457682 -16.9490128 0.987299025 +v -5.92088604 -17.8115158 0.503844023 +v -5.92088604 -17.8115158 0.987299025 +v -5.51082087 -18.2230453 0.503844023 +v -4.6712389 -18.3963585 0.503844023 +v -4.6712389 -18.3963585 0.987299025 +v -4.59126377 -18.4253044 0.503844023 +v -4.59126377 -18.4253044 0.987299025 +v -4.53558588 -18.4843788 0.503844023 +v -4.53558588 -18.4843788 0.987299025 +v -4.51469707 -18.5631447 0.503844023 +v -4.51469707 -18.5631447 0.987299025 +v -4.53513193 -18.6426735 0.503844023 +v -4.53513193 -18.6426735 0.987299025 +v -4.58995914 -18.7013664 0.503844023 +v -4.58995914 -18.7013664 0.987299025 +v -4.66948414 -18.7299309 0.503844023 +v -4.66948414 -18.7299309 0.987299025 +v -5.50647783 -18.9074936 0.503844023 +v -5.50647783 -18.9074936 0.987299025 +v -5.91552782 -19.317482 0.503844023 +v -5.91552782 -19.317482 0.987299025 +v -6.1145792 -20.1772766 0.503844023 +v -6.1145792 -20.1772766 0.987299025 +v -6.14489317 -20.2554016 0.503844023 +v -6.14489317 -20.2554016 0.987299025 +v -6.20274115 -20.3076515 0.503844023 +v -6.20274115 -20.3076515 0.987299025 +v -6.27950191 -20.3266945 0.503844023 +v -6.27950191 -20.3266945 0.987299025 +v -6.35626316 -20.3076515 0.503844023 +v -6.35626316 -20.3076515 0.987299025 +v -6.41411304 -20.2554016 0.503844023 +v -6.41411304 -20.2554016 0.987299025 +v -6.44443417 -20.1772766 0.503844023 +v -6.44443417 -20.1772766 0.987299025 +v -6.63805294 -19.3147736 0.503844023 +v -6.63805294 -19.3147736 0.987299025 +v -7.04765797 -18.903244 0.503844023 +v -7.04765797 -18.903244 0.987299025 +v -7.88599682 -18.7299309 0.503844023 +v -7.88599682 -18.7299309 0.987299025 +v -7.96676207 -18.7013664 0.503844023 +v -7.96676207 -18.7013664 0.987299025 +v -8.02204609 -18.6426735 0.503844023 +v -8.02204609 -18.6426735 0.987299025 +v -8.04254246 -18.5631447 0.503844023 +v -8.04254246 -18.5631447 0.987299025 +v -8.02250004 -18.4847717 0.503844023 +v -8.02250004 -18.4847717 0.987299025 +v -7.96806622 -18.4260864 0.503844023 +v -7.96806622 -18.4260864 0.987299025 +v -7.88775682 -18.3963547 0.503844023 +v -7.88775682 -18.3963547 0.987299025 +v -7.05239391 -18.2110748 0.503844023 +v -7.05239391 -18.2110748 0.987299025 +v -6.64537096 -17.8049431 0.503844023 +v -6.64537096 -17.8049431 0.987299025 +v 10.0222101 -16.6688595 0.987299025 +v 10.0537024 -16.5907345 0.987299025 +v 10.1115522 -16.5384846 0.987299025 +v 10.1871405 -16.5194416 0.987299025 +v 10.2646856 -16.5384846 0.987299025 +v 10.3221407 -16.5907345 0.987299025 +v 10.3520632 -16.6688595 0.987299025 +v 10.5457544 -17.5313625 0.987299025 +v 10.9558191 -17.9428921 0.987299025 +v 11.7954016 -18.1162052 0.987299025 +v 11.8753767 -18.1451511 0.987299025 +v 11.9310541 -18.2042255 0.987299025 +v 11.9519434 -18.2829914 0.987299025 +v 11.9315081 -18.3625202 0.987299025 +v 11.8766813 -18.4212132 0.987299025 +v 11.7971563 -18.4497776 0.987299025 +v 10.9601622 -18.6273403 0.987299025 +v 10.5511122 -19.0373287 0.987299025 +v 10.3520613 -19.8971233 0.987299025 +v 10.3217478 -19.9752483 0.987299025 +v 10.2638998 -20.0274982 0.987299025 +v 10.1871386 -20.0465412 0.987299025 +v 10.1103773 -20.0274982 0.987299025 +v 10.0525274 -19.9752483 0.987299025 +v 10.0222063 -19.8971233 0.987299025 +v 9.82858753 -19.0346203 0.987299025 +v 9.41898346 -18.6230907 0.987299025 +v 8.58064365 -18.4497776 0.987299025 +v 8.49987888 -18.4212132 0.987299025 +v 8.44459438 -18.3625202 0.987299025 +v 8.42409801 -18.2829914 0.987299025 +v 8.44414043 -18.2046185 0.987299025 +v 8.49857426 -18.1459332 0.987299025 +v 8.57888317 -18.1162014 0.987299025 +v 9.41424656 -17.9309216 0.987299025 +v 9.82126904 -17.5247898 0.987299025 +v 10.0222101 -16.6688595 0.503844023 +v 10.0222101 -16.6688595 0.987299025 +v 10.0537024 -16.5907345 0.503844023 +v 10.0537024 -16.5907345 0.987299025 +v 10.1115522 -16.5384846 0.503844023 +v 10.1115522 -16.5384846 0.987299025 +v 10.1871405 -16.5194416 0.503844023 +v 10.1871405 -16.5194416 0.987299025 +v 10.2646856 -16.5384846 0.503844023 +v 10.2646856 -16.5384846 0.987299025 +v 10.3221407 -16.5907345 0.503844023 +v 10.3221407 -16.5907345 0.987299025 +v 10.3520632 -16.6688595 0.503844023 +v 10.3520632 -16.6688595 0.987299025 +v 10.5457544 -17.5313625 0.503844023 +v 10.5457544 -17.5313625 0.987299025 +v 10.9558191 -17.9428921 0.503844023 +v 11.7954016 -18.1162052 0.503844023 +v 11.7954016 -18.1162052 0.987299025 +v 11.8753767 -18.1451511 0.503844023 +v 11.8753767 -18.1451511 0.987299025 +v 11.9310541 -18.2042255 0.503844023 +v 11.9310541 -18.2042255 0.987299025 +v 11.9519434 -18.2829914 0.503844023 +v 11.9519434 -18.2829914 0.987299025 +v 11.9315081 -18.3625202 0.503844023 +v 11.9315081 -18.3625202 0.987299025 +v 11.8766813 -18.4212132 0.503844023 +v 11.8766813 -18.4212132 0.987299025 +v 11.7971563 -18.4497776 0.503844023 +v 11.7971563 -18.4497776 0.987299025 +v 10.9601622 -18.6273403 0.503844023 +v 10.9601622 -18.6273403 0.987299025 +v 10.5511122 -19.0373287 0.503844023 +v 10.5511122 -19.0373287 0.987299025 +v 10.3520613 -19.8971233 0.503844023 +v 10.3520613 -19.8971233 0.987299025 +v 10.3217478 -19.9752483 0.503844023 +v 10.3217478 -19.9752483 0.987299025 +v 10.2638998 -20.0274982 0.503844023 +v 10.2638998 -20.0274982 0.987299025 +v 10.1871386 -20.0465412 0.503844023 +v 10.1871386 -20.0465412 0.987299025 +v 10.1103773 -20.0274982 0.503844023 +v 10.1103773 -20.0274982 0.987299025 +v 10.0525274 -19.9752483 0.503844023 +v 10.0525274 -19.9752483 0.987299025 +v 10.0222063 -19.8971233 0.503844023 +v 10.0222063 -19.8971233 0.987299025 +v 9.82858753 -19.0346203 0.503844023 +v 9.82858753 -19.0346203 0.987299025 +v 9.41898346 -18.6230907 0.503844023 +v 9.41898346 -18.6230907 0.987299025 +v 8.58064365 -18.4497776 0.503844023 +v 8.58064365 -18.4497776 0.987299025 +v 8.49987888 -18.4212132 0.503844023 +v 8.49987888 -18.4212132 0.987299025 +v 8.44459438 -18.3625202 0.503844023 +v 8.44459438 -18.3625202 0.987299025 +v 8.42409801 -18.2829914 0.503844023 +v 8.42409801 -18.2829914 0.987299025 +v 8.44414043 -18.2046185 0.503844023 +v 8.44414043 -18.2046185 0.987299025 +v 8.49857426 -18.1459332 0.503844023 +v 8.49857426 -18.1459332 0.987299025 +v 8.57888317 -18.1162014 0.503844023 +v 8.57888317 -18.1162014 0.987299025 +v 9.41424656 -17.9309216 0.503844023 +v 9.41424656 -17.9309216 0.987299025 +v 9.82126904 -17.5247898 0.503844023 +v 9.82126904 -17.5247898 0.987299025 +vt 0.764985025 0.456185997 +vt 0.767931998 0.468735993 +vt 0.781620026 0.450055987 +vt 0.775608003 0.456090003 +vt 0.759018004 0.450230986 +vt 0.793929994 0.447515011 +vt 0.746770024 0.447515011 +vt 0.795103014 0.44709 +vt 0.745591998 0.447079003 +vt 0.795919001 0.446224004 +vt 0.744794011 0.446218014 +vt 0.744499981 0.445068985 +vt 0.795925975 0.443902999 +vt 0.744800985 0.443902999 +vt 0.795122027 0.443042994 +vt 0.745611012 0.443042994 +vt 0.746794999 0.442624003 +vt 0.793955982 0.442624003 +vt 0.759087026 0.440082997 +vt 0.775686026 0.434008986 +vt 0.781683981 0.440019995 +vt 0.765093029 0.43404901 +vt 0.772768021 0.421402991 +vt 0.767931998 0.421402991 +vt 0.772323012 0.420257986 +vt 0.768375993 0.420257986 +vt 0.770349979 0.419212013 +vt 0.771475017 0.419490993 +vt 0.769223988 0.419490993 +vt 0.796225011 0.445068985 +vt 0.772768021 0.468735993 +vt 0.76839298 0.469880998 +vt 0.769241989 0.470647007 +vt 0.771486998 0.470647007 +vt 0.772328973 0.469880998 +vt 0.770349979 0.470925987 +vt 0 0 +vn -0 -0 1 +vn -0.87349999 0.486900002 -0 +vn -0.480500013 0.876999974 -0 +vn -0.480599999 0.876999974 -0 +vn -0.282499999 0.959299982 -0 +vn -0.555400014 0.83160001 -0 +vn -0.555299997 0.831700027 -0 +vn -0.878000021 0.478599995 -0 +vn -1 -0.000899999985 -0 +vn -1 -0.000899999985 0.000199999995 +vn -0.875699997 -0.482800007 -0 +vn -0.875699997 -0.482800007 0.000199999995 +vn -0.546000004 -0.837800026 -0 +vn -0.546000004 -0.837800026 9.99999975e-05 +vn -0.268599987 -0.96329999 -0 +vn -0.475699991 -0.879599988 -0 +vn -0.876600027 -0.481099993 -0 +vn -0.876699984 -0.481099993 -0 +vn -0.956700027 -0.291200012 -0 +vn -0.956700027 -0.291099995 -0 +vn -0.823499978 -0.567300022 -0 +vn -0.469599992 -0.8829 -0 +vn -0 -1 -0 +vn 0.469599992 -0.8829 -0 +vn 0.823499978 -0.567399979 -0 +vn 0.823499978 -0.567300022 -0 +vn 0.95569998 -0.29429999 -0 +vn 0.95569998 -0.294400007 -0 +vn 0.87470001 -0.484600008 -0 +vn 0.874800026 -0.484600008 -0 +vn 0.477499992 -0.878600001 -0 +vn 0.273400009 -0.961899996 -0 +vn 0.549799979 -0.835300028 -0 +vn 0.876900017 -0.480699986 -0 +vn 1 0.00389999989 -0 +vn 0.874000013 0.486000001 -0 +vn 0.549000025 0.835799992 -0 +vn 0.272000015 0.962300003 -0 +vn 0.475400001 0.879800022 -0 +vn 0.87650001 0.481400013 -0 +vn 0.957300007 0.289099991 -0 +vn 0.825699985 0.564100027 -0 +vn 0.470099986 0.882600009 -0 +vn -0.00300000003 1 -0 +vn -0.471199989 0.882000029 -0 +vn -0.819800019 0.572600007 -0 +vn -0.953299999 0.302100003 -0 +vn -0.875699997 -0.482800007 9.99999975e-05 +vn -1 -0.00079999998 0.000199999995 +vn -0.88410002 -0.447899997 0.133100003 +vn -0.962800026 -0.263700008 -0.0582999997 +vn -0.972000003 -0.056400001 -0.228200004 +vn -0.522700012 -0.832400024 0.184200004 +vn -0.828400016 -0.529600024 0.182500005 +vn -0.769500017 -0.584399998 -0.257400006 +vn -0.247899994 -0.967499971 -0.0491999984 +vn -0.431499988 -0.888700008 0.154699996 +vn -0.178599998 -0.981899977 0.0636000037 +vn -0.505800009 -0.851999998 -0.135000005 +vn -0.27610001 -0.960600019 -0.0318999998 +vn -0.245700002 -0.944000006 0.220300004 +vn -0.845300019 -0.515100002 -0.141900003 +vn -0.564499974 -0.818099976 -0.109499998 +vn -0.644800007 -0.727999985 0.232999995 +vn -0.988900006 -0.0439999998 -0.142100006 +vn -0.8829 -0.454899997 -0.116099998 +vn -0.916100025 -0.326700002 0.232600003 +vn -0.888899982 0.43599999 -0.140400007 +vn -0.992900014 0.0267999992 -0.115699999 +vn -0.960600019 0.1558 0.230299994 +vn -0.840399981 0.524299979 -0.1373 +vn -0.858799994 0.499300003 -0.114399999 +vn -0.768599987 0.601700008 0.217299998 +vn -0.397599995 0.916700006 -0.0406999998 +vn -0.529799998 0.841099977 -0.108999997 +vn -0.35620001 0.931999981 0.0672999993 +vn -0.207900003 0.937399983 0.279300004 +vn -0.257699996 0.9648 -0.0529999994 +vn -0.0520000011 0.972000003 -0.229100004 +vn -0.679199994 0.680700004 0.274399996 +vn -0.529799998 0.827799976 0.184499994 +vn -0.593999982 0.765799999 -0.2465 +vn -0.976400018 0.203500003 -0.0719999969 +vn -0.8829 0.446500003 0.145099998 +vn -0.976800025 0.201800004 0.0714000016 +vn -0.834999979 0.535099983 -0.128700003 +vn -0.949999988 0.310299993 -0.0357000008 +vn -0.933499992 0.290600002 0.209999993 +vn -0.79519999 0.589399993 -0.142299995 +vn -0.800899982 0.589699984 -0.1039 +vn -0.71390003 0.662899971 0.225799993 +vn -0.426200002 0.893400013 -0.142199993 +vn -0.444700003 0.888700008 -0.111699998 +vn -0.319799989 0.92019999 0.225799993 +vn 0.0384999998 0.988600016 -0.145500004 +vn 0.0238000005 0.993399978 -0.112099998 +vn 0.146500006 0.962100029 0.230100006 +vn 0.516099989 0.845600009 -0.136299998 +vn 0.490700006 0.863900006 -0.113700002 +vn 0.594200015 0.774600029 0.216700003 +vn 0.910600007 0.411000013 -0.0430999994 +vn 0.835300028 0.538999975 -0.1083 +vn 0.92750001 0.366899997 0.0715000033 +vn 0.937300026 0.210500002 0.277700007 +vn 0.96329999 0.262499988 -0.0566000007 +vn 0.971899986 0.056400001 -0.228400007 +vn 0.522400022 0.832599998 0.184 +vn 0.828199983 0.529900014 0.182500005 +vn 0.708400011 0.705799997 -0 +vn 0.202199996 0.979399979 -0 +vn 0.193599999 0.938000023 0.287600011 +vn 0.2016 0.976800025 -0.071800001 +vn 0.43900001 0.887300014 0.141299993 +vn 0.509599984 0.850099981 -0.1329 +vn 0.279900014 0.959399998 -0.0337000005 +vn 0.254200011 0.942499995 0.216800004 +vn 0.843900025 0.51789999 -0.140200004 +vn 0.567300022 0.816399992 -0.107900001 +vn 0.646000028 0.727900028 0.229900002 +vn 0.883099973 0.444700003 -0.149800003 +vn 0.881200016 0.458600014 -0.114500001 +vn 0.912400007 0.334100008 0.236399993 +vn 0.887899995 -0.43779999 -0.141299993 +vn 0.992699981 -0.0243999995 -0.117899999 +vn 0.960200012 -0.156499997 0.231399998 +vn 0.829699993 -0.529799998 0.176100001 +vn 0.5273 -0.828800023 0.1875 +vn 0.595700026 -0.764299989 -0.247099996 +vn 0.960699975 -0.272799999 -0.0516000018 +vn 0.88410002 -0.444299996 0.144999996 +vn 0.977400005 -0.200499997 0.0666999966 +vn 0.83859998 -0.528500021 -0.132200003 +vn 0.952700019 -0.302100003 -0.0333999991 +vn 0.936699986 -0.27579999 0.215900004 +vn 0.501600027 -0.853999972 -0.1382 +vn 0.804099977 -0.584800005 -0.106799997 +vn 0.713999987 -0.662199974 0.227500007 +vn 0.422300011 -0.895200014 -0.142199993 +vn 0.442699999 -0.889599979 -0.1127 +vn 0.316399992 -0.921500027 0.225199997 +vn 0.441500008 -0.886300027 0.140100002 +vn 0.248600006 -0.967199981 -0.0529000014 +vn 0.0401999988 -0.971400023 -0.233999997 +vn 0.28490001 -0.957799971 -0.0388999991 +vn 0.523999989 -0.844600022 -0.110200003 +vn 0.34709999 -0.935400009 0.0670000017 +vn 0.839699984 -0.524999976 -0.1391 +vn 0.857599974 -0.501399994 -0.1149 +vn 0.766700029 -0.603100002 0.220100001 +vn -0.951799989 -0.3037 -0.0428000018 +vn -0.833199978 -0.542299986 -0.108099997 +vn -0.925599992 -0.371300012 0.0737000033 +vn -0.793200016 -0.594699979 -0.131200001 +vn -0.490099996 -0.864400029 -0.112499997 +vn -0.591899991 -0.776499987 0.216100007 +vn -0.427700013 -0.893299997 -0.138400003 +vn -0.0265999995 -0.993399978 -0.111599997 +vn -0.150000006 -0.962199986 0.227500007 +vn -0.530499995 0.829100013 -0.176599994 +vn -0.67809999 0.679799974 -0.279300004 +vn -0.550999999 0.806699991 0.213799998 +vn 0.0295000002 0.990100026 -0.137400001 +vn -0.216000006 0.973999977 0.0680999979 +vn -0.248699993 0.966799974 -0.0593000017 +vn -0.357499987 0.931500018 0.0671000034 +vn -0.337700009 0.912199974 0.232099995 +vn -0.551999986 0.832799971 -0.0405000001 +vn -0.738900006 0.633700013 0.229000002 +vn -0.710600019 0.659099996 0.246199995 +vn -0.875800014 0.481000006 -0.0401999988 +vn -0.949100018 0.200800002 0.242799997 +vn -0.938399971 0.239999995 0.248600006 +vn -0.999199986 0.00240000011 -0.0410999991 +vn -0.927600026 -0.281899989 0.245299995 +vn -0.937900007 -0.241699994 0.248899996 +vn -0.876600027 -0.479400009 -0.0417000018 +vn -0.676999986 -0.693799973 0.245700002 +vn -0.707499981 -0.666400015 0.235400006 +vn -0.548200011 -0.835399985 -0.0386999995 +vn -0.28549999 -0.929899991 0.231800005 +vn -0.332700014 -0.940599978 0.0680000037 +vn -0.269600004 -0.962899983 -0.0119000003 +vn -0.143999994 -0.989000022 0.0333999991 +vn -0.193900004 -0.937900007 -0.287800014 +vn -0.332500011 -0.909200013 0.25060001 +vn -0.834800005 -0.51880002 -0.184200004 +vn -0.680899978 -0.6778 -0.277399987 +vn -0.806200027 -0.551999986 0.213200003 +vn -0.990700006 0.0270000007 -0.133100003 +vn -0.97299999 -0.218400002 0.0746000037 +vn -0.965200007 -0.253300011 -0.0653999969 +vn -0.925000012 -0.372700006 0.0736000016 +vn -0.907100022 -0.352100015 0.230900005 +vn -0.824699998 -0.564100027 -0.0395000018 +vn -0.624800026 -0.746699989 0.228 +vn -0.650099993 -0.719900012 0.243200004 +vn -0.471899986 -0.880800009 -0.0386999995 +vn -0.195500001 -0.950900018 0.239999995 +vn -0.233700007 -0.941999972 0.240799993 +vn -0.00310000009 -0.999199986 -0.0388999991 +vn 0.271400005 -0.932699978 0.237599999 +vn 0.233600006 -0.941399992 0.243200004 +vn 0.466500014 -0.883700013 -0.0392999984 +vn 0.678900003 -0.693899989 0.240199998 +vn 0.652199984 -0.722100019 0.230900005 +vn 0.82130003 -0.569299996 -0.0368000008 +vn 0.921299994 -0.315200001 0.227599993 +vn 0.929899991 -0.360799998 0.0713 +vn 0.955299973 -0.295399994 -0.0121999998 +vn 0.985899985 -0.164100006 0.0342000015 +vn 0.936699986 -0.216900006 -0.274800003 +vn 0.906899989 -0.345499992 0.240999997 +vn 0.531700015 -0.828400016 -0.176100001 +vn 0.678600013 -0.677100003 -0.284700006 +vn 0.549700022 -0.806200027 0.218600005 +vn -0.0436000004 -0.989199996 -0.140100002 +vn 0.207000002 -0.976000011 0.0678000003 +vn 0.239700004 -0.969099998 -0.0590999983 +vn 0.348199993 -0.935000002 0.0669 +vn 0.328599989 -0.914799988 0.235100001 +vn 0.546500027 -0.836499989 -0.0405000001 +vn 0.736599982 -0.635299981 0.231900007 +vn 0.708100021 -0.66140002 0.247299999 +vn 0.874599993 -0.483099997 -0.0405000001 +vn 0.948800027 -0.201000005 0.243799999 +vn 0.937099993 -0.240799993 0.252600014 +vn 0.999100029 0.00039999999 -0.0423000008 +vn 0.924499989 0.288599998 0.2491 +vn 0.936999977 0.248500004 0.245700002 +vn 0.874899983 0.48269999 -0.0408000015 +vn 0.67750001 0.694400012 0.242400005 +vn 0.707899988 0.667200029 0.231700003 +vn 0.551199973 0.833500028 -0.0384000018 +vn 0.293300003 0.92839998 0.228300005 +vn 0.33950001 0.937900007 0.0716999993 +vn 0.273000002 0.961899996 -0.0124000004 +vn 0.834500015 0.519400001 -0.184 +vn 0.680499971 0.67809999 -0.277700007 +vn 0.805899978 0.552299976 0.213400006 +vn 0.990700006 -0.0272000004 -0.133200005 +vn 0.973100007 0.218500003 0.072300002 +vn 0.965499997 0.252400011 -0.063500002 +vn 0.926999986 0.368200004 0.0714000016 +vn 0.908500016 0.347900003 0.2315 +vn 0.826900005 0.560899973 -0.0395000018 +vn 0.627399981 0.744400024 0.228400007 +vn 0.652100027 0.717100024 0.246000007 +vn 0.472499996 0.880500019 -0.0390000008 +vn 0.192200005 0.950900018 0.242799997 +vn 0.231399998 0.942399979 0.241400003 +vn 9.99999975e-05 0.999199986 -0.0394000001 +vn -0.274300009 0.931599975 0.238299996 +vn -0.237100005 0.940999985 0.241500005 +vn -0.468199998 0.882799983 -0.0384000018 +vn -0.67900002 0.694400012 0.238399997 +vn -0.653199971 0.723200023 0.224600002 +vn -0.817700028 0.574599981 -0.0357999988 +vn -0.918200016 0.328599989 0.221200004 +vn -0.924799979 0.372799993 0.0762000009 +vn -0.952899992 0.30309999 -0.0131000001 +vn -0.986000001 0.162799999 0.0366999991 +vn -0.936200023 0.219799995 -0.274399996 +vn -0.906000018 0.348500013 0.240400001 +vn -0.937399983 -0.2104 -0.277399987 +vn -0.975799978 -0.206 -0.0734999999 +vn -0.987399995 0.0121999998 -0.157800004 +vn -0.678799987 -0.675599992 -0.287900001 +vn -0.649999976 -0.708800018 0.273900002 +vn -0.860400021 -0.493200004 -0.128700003 +vn -0.182600006 -0.981000006 0.0650999993 +vn -0.0614 -0.965300024 0.254000008 +vn -0.139699996 -0.989899993 0.0239000004 +vn -0.48089999 -0.860800028 0.166600004 +vn -0.312900007 -0.947600007 -0.0647 +vn -0.116400003 -0.989700019 0.0830999985 +vn -0.827400029 -0.534099996 0.173600003 +vn -0.656899989 -0.720000029 -0.2236 +vn -0.545300007 -0.833700001 0.0878000036 +vn -0.982200027 -0.0697999969 0.1743 +vn -0.918500006 -0.316799998 -0.236499995 +vn -0.879899979 -0.467000008 0.0877000019 +vn -0.895399988 0.410899997 0.171900004 +vn -0.957700014 0.1646 -0.236100003 +vn -0.996100008 0.0170000009 0.0869999975 +vn -0.853900015 0.5097 0.104900002 +vn -0.763100028 0.602500021 -0.233799994 +vn -0.860899985 0.502200007 0.0815000013 +vn -0.403400004 0.914499998 0.0305000003 +vn -0.404900014 0.887300014 -0.220799997 +vn -0.409399986 0.912299991 0.0104999999 +vn -0.205200002 0.976400018 -0.0671999976 +vn 0.0157999992 0.986999989 -0.159700006 +vn -0.530600011 0.828999996 -0.176599994 +vn -0.707799971 0.650200009 0.276199996 +vn -0.504700005 0.854900002 -0.120399997 +vn -0.987900019 0.154699996 0.0126999998 +vn -0.966799974 0.0891999975 0.239399999 +vn -0.986999989 0.158399999 0.0267999992 +vn -0.844299972 0.512000024 0.158299997 +vn -0.933600008 0.350800008 -0.0724999979 +vn -0.982299984 0.169499993 0.0794999972 +vn -0.810199976 0.575900018 0.109399997 +vn -0.70569998 0.675700009 -0.213100001 +vn -0.818000019 0.568799973 0.0855000019 +vn -0.445800006 0.888400018 0.109399997 +vn -0.310900003 0.922399998 -0.229100004 +vn -0.456099987 0.885800004 0.0852999985 +vn 0.0181000009 0.993600011 0.111199997 +vn 0.157900006 0.960500002 -0.229200006 +vn 0.00769999996 0.996200025 0.0870999992 +vn 0.501800001 0.85860002 0.104900002 +vn 0.594600022 0.769400001 -0.233400002 +vn 0.494199991 0.865499973 0.0815000013 +vn 0.908299983 0.417100012 0.0324999988 +vn 0.882799983 0.415100008 -0.219999999 +vn 0.905799985 0.423500001 0.0109000001 +vn 0.975899994 0.206499994 -0.0713 +vn 0.987399995 -0.0122999996 -0.157900006 +vn 0.67839998 0.675999999 -0.287600011 +vn 0.649399996 0.709299982 0.274100006 +vn -0.0516999997 0.988600016 -0.141299993 +vn 0.131400004 0.991299987 -0 +vn -0.0881000012 0.996100008 -0 +vn 0.485300004 0.858900011 0.163800001 +vn 0.318599999 0.9454 -0.0681999996 +vn 0.127200007 0.988499999 0.0816999972 +vn 0.825999975 0.536899984 0.171700001 +vn 0.658200026 0.719900012 -0.220100001 +vn 0.547699988 0.832199991 0.0866999999 +vn 0.877399981 0.465999991 0.113899998 +vn 0.917299986 0.322699994 -0.233400002 +vn 0.874599993 0.476599991 0.0891000032 +vn 0.894500017 -0.412099987 0.173299998 +vn 0.956700027 -0.164499998 -0.240099996 +vn 0.995999992 -0.0171000008 0.0873000026 +vn 0.680700004 -0.679099977 -0.274800003 +vn 0.709900022 -0.645699978 0.281300008 +vn 0.506099999 -0.853999972 -0.120300002 +vn 0.976599991 -0.203899994 0.067900002 +vn 0.967199981 -0.0852999985 0.239399999 +vn 0.986800015 -0.159999996 0.0251000002 +vn 0.847800016 -0.504800022 0.162599996 +vn 0.937900007 -0.340200007 -0.0678000003 +vn 0.985199988 -0.150600001 0.0816999972 +vn 0.519800007 -0.837599993 0.168300003 +vn 0.706200004 -0.673300028 -0.219099998 +vn 0.819000006 -0.567399979 0.0860000029 +vn 0.442099988 -0.890299976 0.108999997 +vn 0.307799995 -0.922999978 -0.230800003 +vn 0.452300012 -0.887799978 0.0851000026 +vn 0.198899999 -0.937699974 -0.284799993 +vn 0.1963 -0.978299975 -0.0669 +vn -0.0299999993 -0.986299992 -0.162200004 +vn 0.291999996 -0.955200016 0.0478999987 +vn 0.397100002 -0.890100002 -0.223499998 +vn 0.400400013 -0.916299999 0.0104 +vn 0.853500009 -0.510200024 0.106200002 +vn 0.760999978 -0.604799986 -0.234799996 +vn 0.860499978 -0.502699971 0.0825999975 +vn -0.948800027 -0.311300009 0.0526000001 +vn -0.881200016 -0.418799996 -0.219500005 +vn -0.902999997 -0.429500014 0.0111999996 +vn -0.775900006 -0.610300004 0.159799993 +vn -0.593200028 -0.771300018 -0.230800003 +vn -0.49180001 -0.866900027 0.0812000036 +vn -0.403400004 -0.899299979 0.168899998 +vn -0.1602 -0.960200012 -0.228599995 +vn -0.0127999997 -0.996200025 0.0860999972 +vn -0.858900011 0.499300003 -0.114399999 +vn -0.834999979 0.535000026 -0.128700003 +vn 0.843800008 0.51789999 -0.140200004 +vn 0.887899995 -0.437700003 -0.141299993 +vn 0.804099977 -0.584900022 -0.106799997 +vn 0.839600027 -0.524999976 -0.1391 +vn 0.857500017 -0.501399994 -0.1149 +vn -0.833199978 -0.542200029 -0.108099997 +vn -0.564599991 -0.818099976 -0.109499998 +vn -0.644800007 -0.727900028 0.232999995 +vn -0.875800014 -0.482800007 -0 +vn -0.878000021 0.478700012 -0 +vn 0.960699975 -0.272899985 -0.0516000018 +vn 0.874000013 0.485900015 -0 +vn -0.545899987 -0.837800026 9.99999975e-05 +vn -1 -0.00079999998 -0 +vn -0.555299997 0.83160001 -0 +vn -0.845300019 -0.514999986 -0.141900003 +vn -0.644900024 -0.727900028 0.232999995 +vn -0.8829 -0.455000013 -0.116099998 +vn -0.916000009 -0.326700002 0.232600003 +vn -0.529900014 0.841000021 -0.108999997 +vn 0.992699981 -0.0242999997 -0.117899999 +vn -0.469700009 -0.882799983 -0 +vn 0.316300005 -0.921500027 0.225199997 +vn -0.427599996 -0.893299997 -0.138400003 +f 29/1/1 30/2/1 36/2/1 +f 244/1/1 245/2/1 251/2/1 +f 74/1/2 76/3/3 75/4/3 +f 76/5/4 78/6/5 77/5/5 +f 78/7/5 80/8/6 79/7/6 +f 80/9/7 82/10/8 81/9/8 +f 82/11/8 84/3/9 83/11/9 +f 84/12/10 86/13/11 85/12/11 +f 86/14/12 88/15/13 87/14/13 +f 88/16/14 90/16/15 89/15/15 +f 90/17/15 92/18/16 91/17/16 +f 92/19/16 94/20/17 93/21/17 +f 94/22/18 96/23/19 95/22/19 +f 96/24/20 98/25/21 97/24/21 +f 98/26/21 100/27/22 99/28/22 +f 100/29/22 102/28/23 101/26/23 +f 102/27/23 104/26/24 103/25/24 +f 104/28/24 106/24/25 105/23/26 +f 106/25/26 108/22/27 107/20/27 +f 108/23/28 110/21/29 109/19/29 +f 110/20/30 112/19/31 111/18/31 +f 112/21/31 114/17/32 113/16/32 +f 114/18/32 116/14/33 115/13/33 +f 116/15/33 118/12/34 117/30/34 +f 118/13/34 120/11/35 119/10/35 +f 120/30/35 122/9/36 121/8/36 +f 122/10/36 124/7/37 123/6/37 +f 124/8/37 126/5/38 125/3/38 +f 127/3/39 129/1/40 128/31/40 +f 129/4/40 131/2/41 130/32/41 +f 131/31/41 133/33/42 132/34/42 +f 133/35/42 135/36/43 134/12/43 +f 135/34/43 137/12/44 136/3/44 +f 137/36/44 139/34/45 138/36/45 +f 139/33/45 141/35/46 140/33/46 +f 141/32/46 143/32/47 142/35/47 +f 143/2/47 74/31/2 73/2/2 +f 145/2/47 214/31/2 215/2/2 +f 147/32/46 145/32/47 146/35/47 +f 149/33/45 147/35/46 148/33/46 +f 151/36/44 149/34/45 150/36/45 +f 153/34/43 151/12/44 152/3/44 +f 155/35/42 153/36/43 154/12/43 +f 157/31/41 155/33/42 156/34/42 +f 159/4/40 157/2/41 158/32/41 +f 161/3/39 159/1/40 160/31/40 +f 164/8/37 162/5/38 163/3/38 +f 166/10/36 164/7/37 165/6/37 +f 168/30/35 166/9/36 167/8/36 +f 170/13/34 168/11/35 169/10/35 +f 172/15/33 170/12/34 171/30/34 +f 174/18/32 172/14/33 173/13/33 +f 176/21/31 174/17/32 175/16/32 +f 178/20/30 176/19/31 177/18/31 +f 180/23/28 178/21/29 179/19/29 +f 182/25/26 180/22/28 181/20/28 +f 184/28/24 182/24/26 183/23/26 +f 186/27/23 184/26/24 185/25/24 +f 188/29/22 186/28/23 187/26/23 +f 190/26/21 188/27/22 189/28/22 +f 192/24/19 190/25/21 191/24/21 +f 194/22/18 192/23/19 193/22/19 +f 196/19/16 194/20/17 195/21/17 +f 198/17/15 196/18/16 197/17/16 +f 200/16/14 198/16/15 199/15/15 +f 202/14/48 200/15/13 201/14/13 +f 204/12/49 202/13/11 203/12/11 +f 206/11/8 204/3/9 205/11/9 +f 208/9/7 206/10/8 207/9/8 +f 210/7/5 208/8/6 209/7/6 +f 212/5/4 210/6/5 211/5/5 +f 214/1/2 212/3/3 213/4/3 +f 194/22/50 193/22/51 195/21/52 +f 94/22/50 95/22/51 93/21/52 +f 92/19/53 93/21/54 91/17/55 +f 90/17/56 91/17/57 89/15/58 +f 88/16/59 89/15/60 87/14/61 +f 86/14/62 87/14/63 85/12/64 +f 84/12/65 85/12/66 83/11/67 +f 82/11/68 83/11/69 81/9/70 +f 80/8/71 81/9/72 79/7/73 +f 78/6/74 79/7/75 77/5/76 +f 76/3/77 77/5/78 75/4/79 +f 74/31/80 75/4/81 73/2/82 +f 143/32/83 73/2/84 142/35/85 +f 141/32/86 142/35/87 140/33/88 +f 139/34/89 140/33/90 138/36/91 +f 137/12/92 138/36/93 136/3/94 +f 135/36/95 136/3/96 134/12/97 +f 133/33/98 134/12/99 132/34/100 +f 131/2/101 132/34/102 130/32/103 +f 129/1/104 130/32/105 128/31/106 +f 127/3/107 128/31/108 28/37/109 +f 28/37/110 125/37/110 127/3/111 +f 126/5/112 127/3/113 125/37/110 +f 124/8/114 125/3/115 123/6/116 +f 122/10/117 123/6/118 121/8/119 +f 120/11/120 121/8/121 119/10/122 +f 118/13/123 119/10/124 117/30/125 +f 110/20/126 111/18/127 109/19/128 +f 108/23/129 109/19/130 107/20/131 +f 106/25/132 107/20/133 105/23/134 +f 104/28/135 105/23/136 103/25/137 +f 102/28/138 103/25/139 101/26/140 +f 112/21/141 113/16/142 111/18/143 +f 114/18/144 115/13/145 113/16/146 +f 116/14/147 117/30/148 115/13/149 +f 96/24/150 97/24/151 95/22/152 +f 98/26/153 99/28/154 97/24/155 +f 100/29/156 101/26/157 99/28/158 +f 357/1/1 348/19/1 329/1/1 +f 253/1/159 255/3/160 254/4/161 +f 255/5/162 257/6/163 256/5/164 +f 257/7/165 259/8/166 258/7/167 +f 259/9/168 261/10/169 260/9/170 +f 261/11/171 263/3/172 262/11/173 +f 263/12/174 265/13/175 264/12/176 +f 265/14/177 267/15/178 266/14/179 +f 267/16/180 269/16/181 268/15/182 +f 269/17/183 271/18/184 270/17/185 +f 271/19/186 273/20/187 272/21/188 +f 273/22/189 275/23/190 274/22/191 +f 275/24/192 277/25/193 276/24/194 +f 277/26/195 279/27/196 278/28/197 +f 279/29/198 281/28/199 280/26/200 +f 281/27/201 283/26/202 282/25/203 +f 283/28/204 285/24/205 284/23/206 +f 285/25/207 287/22/208 286/20/209 +f 287/23/210 289/21/211 288/19/212 +f 289/20/213 291/19/214 290/18/215 +f 291/21/216 293/17/217 292/16/218 +f 293/18/219 295/14/220 294/13/221 +f 295/15/222 297/12/223 296/30/224 +f 297/13/225 299/11/226 298/10/227 +f 299/30/228 301/9/229 300/8/230 +f 301/10/231 303/7/232 302/6/233 +f 303/8/234 305/5/235 304/3/236 +f 306/3/237 308/1/238 307/31/239 +f 308/4/240 310/2/241 309/32/242 +f 310/31/243 312/33/244 311/34/245 +f 312/35/246 314/36/247 313/12/248 +f 314/34/249 316/12/250 315/3/251 +f 316/36/252 318/34/253 317/36/254 +f 318/33/255 320/35/256 319/33/257 +f 320/32/258 322/32/259 321/35/260 +f 322/2/261 253/31/262 252/2/263 +f 273/22/264 274/22/265 272/21/266 +f 271/19/267 272/21/268 270/17/269 +f 269/17/270 270/17/271 268/15/272 +f 267/16/273 268/15/274 266/14/275 +f 265/14/276 266/14/277 264/12/278 +f 263/12/279 264/12/280 262/11/281 +f 261/11/282 262/11/283 260/9/284 +f 259/8/285 260/9/286 258/7/287 +f 257/6/288 258/7/289 256/5/290 +f 255/3/162 256/5/291 254/4/292 +f 253/31/293 254/4/294 252/2/295 +f 322/32/296 252/2/297 321/35/298 +f 320/32/299 321/35/300 319/33/301 +f 318/34/302 319/33/303 317/36/304 +f 316/12/305 317/36/306 315/3/307 +f 314/36/308 315/3/309 313/12/310 +f 312/33/311 313/12/312 311/34/313 +f 310/2/314 311/34/315 309/32/316 +f 308/1/240 309/32/317 307/31/318 +f 306/3/319 307/31/320 243/37/109 +f 243/37/110 304/37/110 306/3/321 +f 305/5/322 306/3/323 304/37/110 +f 303/8/324 304/3/325 302/6/326 +f 301/10/327 302/6/328 300/8/329 +f 299/11/330 300/8/331 298/10/332 +f 297/13/333 298/10/334 296/30/335 +f 289/20/336 290/18/337 288/19/338 +f 287/23/339 288/19/340 286/20/341 +f 285/25/342 286/20/343 284/23/344 +f 283/28/345 284/23/346 282/25/347 +f 281/28/348 282/25/349 280/26/350 +f 291/21/351 292/16/352 290/18/353 +f 293/18/354 294/13/355 292/16/356 +f 295/14/357 296/30/358 294/13/359 +f 275/24/360 276/24/361 274/22/362 +f 277/26/363 278/28/364 276/24/365 +f 279/29/366 280/26/367 278/28/368 +f 196/19/53 195/21/54 197/17/55 +f 198/17/56 197/17/57 199/15/58 +f 200/16/59 199/15/60 201/14/61 +f 202/14/62 201/14/63 203/12/64 +f 204/12/65 203/12/66 205/11/67 +f 206/11/68 205/11/69 207/9/70 +f 208/8/71 207/9/369 209/7/73 +f 210/6/74 209/7/75 211/5/76 +f 212/3/77 211/5/78 213/4/79 +f 214/31/80 213/4/81 215/2/82 +f 145/32/83 215/2/84 146/35/85 +f 147/32/370 146/35/87 148/33/88 +f 149/34/89 148/33/90 150/36/91 +f 151/12/92 150/36/93 152/3/94 +f 153/36/95 152/3/96 154/12/97 +f 155/33/98 154/12/99 156/34/100 +f 157/2/101 156/34/102 158/32/103 +f 159/1/104 158/32/105 160/31/106 +f 161/3/107 160/31/108 45/37/109 +f 45/37/110 163/37/110 161/3/111 +f 162/5/112 161/3/113 163/37/110 +f 164/8/114 163/3/115 165/6/116 +f 166/10/371 165/6/118 167/8/119 +f 168/11/120 167/8/121 169/10/122 +f 170/13/372 169/10/124 171/30/125 +f 178/20/126 177/18/127 179/19/128 +f 180/23/129 179/19/130 181/20/131 +f 182/25/132 181/20/133 183/23/134 +f 184/28/135 183/23/373 185/25/137 +f 186/28/138 185/25/139 187/26/140 +f 176/21/141 175/16/142 177/18/143 +f 174/18/144 173/13/145 175/16/146 +f 172/14/374 171/30/375 173/13/149 +f 192/24/150 191/24/376 193/22/152 +f 190/26/153 189/28/154 191/24/155 +f 188/29/156 187/26/157 189/28/158 +f 464/1/1 455/19/1 436/1/1 +f 358/2/47 427/31/2 428/2/2 +f 360/32/46 358/32/47 359/35/47 +f 362/33/45 360/35/46 361/33/46 +f 364/36/44 362/34/45 363/36/45 +f 366/34/43 364/12/44 365/3/44 +f 368/35/42 366/36/43 367/12/43 +f 370/31/41 368/33/42 369/34/42 +f 372/4/40 370/2/41 371/32/41 +f 374/3/39 372/1/40 373/31/40 +f 377/8/37 375/5/38 376/3/38 +f 379/10/36 377/7/37 378/6/37 +f 381/30/35 379/9/36 380/8/36 +f 383/13/34 381/11/35 382/10/35 +f 385/15/33 383/12/34 384/30/34 +f 387/18/32 385/14/33 386/13/33 +f 389/21/31 387/17/32 388/16/32 +f 391/20/30 389/19/31 390/18/31 +f 393/23/28 391/21/29 392/19/29 +f 395/25/26 393/22/28 394/20/28 +f 397/28/24 395/24/26 396/23/26 +f 399/27/23 397/26/24 398/25/24 +f 401/29/22 399/28/23 400/26/23 +f 403/26/21 401/27/22 402/28/22 +f 405/24/19 403/25/21 404/24/21 +f 407/22/18 405/23/19 406/22/19 +f 409/19/16 407/20/17 408/21/17 +f 411/17/15 409/18/16 410/17/16 +f 413/16/14 411/16/15 412/15/15 +f 415/14/48 413/15/13 414/14/13 +f 417/12/10 415/13/11 416/12/11 +f 419/11/8 417/3/9 418/11/9 +f 421/9/7 419/10/8 420/9/8 +f 423/7/5 421/8/6 422/7/6 +f 425/5/4 423/6/5 424/5/5 +f 427/1/2 425/3/3 426/4/3 +f 407/22/50 406/22/51 408/21/52 +f 409/19/53 408/21/54 410/17/55 +f 411/17/56 410/17/57 412/15/58 +f 413/16/59 412/15/60 414/14/61 +f 415/14/62 414/14/377 416/12/378 +f 417/12/65 416/12/66 418/11/67 +f 419/11/68 418/11/69 420/9/70 +f 421/8/71 420/9/72 422/7/73 +f 423/6/74 422/7/75 424/5/76 +f 425/3/77 424/5/78 426/4/79 +f 427/31/80 426/4/81 428/2/82 +f 358/32/83 428/2/84 359/35/85 +f 360/32/370 359/35/87 361/33/88 +f 362/34/89 361/33/90 363/36/91 +f 364/12/92 363/36/93 365/3/94 +f 366/36/95 365/3/96 367/12/97 +f 368/33/98 367/12/99 369/34/100 +f 370/2/101 369/34/102 371/32/103 +f 372/1/104 371/32/105 373/31/106 +f 374/3/107 373/31/108 330/37/109 +f 330/37/110 376/37/110 374/3/111 +f 375/5/112 374/3/113 376/37/110 +f 377/8/114 376/3/115 378/6/116 +f 379/10/371 378/6/118 380/8/119 +f 381/11/120 380/8/121 382/10/122 +f 383/13/372 382/10/124 384/30/125 +f 391/20/126 390/18/127 392/19/128 +f 393/23/129 392/19/130 394/20/131 +f 395/25/132 394/20/133 396/23/134 +f 397/28/135 396/23/373 398/25/137 +f 399/28/138 398/25/139 400/26/140 +f 389/21/141 388/16/142 390/18/143 +f 387/18/144 386/13/145 388/16/146 +f 385/14/374 384/30/375 386/13/149 +f 405/24/150 404/24/376 406/22/152 +f 403/26/153 402/28/154 404/24/155 +f 401/29/156 400/26/157 402/28/158 +f 571/1/1 562/19/1 543/1/1 +f 465/2/47 534/31/2 535/2/2 +f 467/32/46 465/32/47 466/35/47 +f 469/33/45 467/35/46 468/33/46 +f 471/36/44 469/34/45 470/36/45 +f 473/34/43 471/12/44 472/3/44 +f 475/35/42 473/36/43 474/12/43 +f 477/31/41 475/33/42 476/34/42 +f 479/4/40 477/2/41 478/32/41 +f 481/3/39 479/1/40 480/31/40 +f 484/8/37 482/5/38 483/3/38 +f 486/10/36 484/7/37 485/6/37 +f 488/30/35 486/9/36 487/8/36 +f 490/13/34 488/11/35 489/10/35 +f 492/15/33 490/12/34 491/30/34 +f 494/18/32 492/14/33 493/13/33 +f 496/21/31 494/17/32 495/16/32 +f 498/20/30 496/19/31 497/18/31 +f 500/23/28 498/21/29 499/19/29 +f 502/25/26 500/22/27 501/20/27 +f 504/28/24 502/24/26 503/23/26 +f 506/27/23 504/26/24 505/25/24 +f 508/29/22 506/28/23 507/26/23 +f 510/26/21 508/27/22 509/28/22 +f 512/24/20 510/25/21 511/24/21 +f 514/22/18 512/23/19 513/22/19 +f 516/19/16 514/20/17 515/21/17 +f 518/17/15 516/18/16 517/17/16 +f 520/16/14 518/16/15 519/15/15 +f 522/14/48 520/15/13 521/14/13 +f 524/12/49 522/13/379 523/12/379 +f 526/11/8 524/3/9 525/11/9 +f 528/9/7 526/10/380 527/9/380 +f 530/7/5 528/8/6 529/7/6 +f 532/5/4 530/6/5 531/5/5 +f 534/1/2 532/3/3 533/4/3 +f 514/22/50 513/22/51 515/21/52 +f 516/19/53 515/21/54 517/17/55 +f 518/17/56 517/17/57 519/15/58 +f 520/16/59 519/15/60 521/14/61 +f 522/14/62 521/14/63 523/12/64 +f 524/12/65 523/12/66 525/11/67 +f 526/11/68 525/11/69 527/9/70 +f 528/8/71 527/9/369 529/7/73 +f 530/6/74 529/7/75 531/5/76 +f 532/3/77 531/5/78 533/4/79 +f 534/31/80 533/4/81 535/2/82 +f 465/32/83 535/2/84 466/35/85 +f 467/32/86 466/35/87 468/33/88 +f 469/34/89 468/33/90 470/36/91 +f 471/12/92 470/36/93 472/3/94 +f 473/36/95 472/3/96 474/12/97 +f 475/33/98 474/12/99 476/34/100 +f 477/2/101 476/34/102 478/32/103 +f 479/1/104 478/32/105 480/31/106 +f 481/3/107 480/31/108 437/37/109 +f 437/37/110 483/37/110 481/3/111 +f 482/5/112 481/3/113 483/37/110 +f 484/8/114 483/3/115 485/6/116 +f 486/10/117 485/6/118 487/8/119 +f 488/11/120 487/8/121 489/10/122 +f 490/13/123 489/10/124 491/30/125 +f 498/20/126 497/18/127 499/19/128 +f 500/23/381 499/19/130 501/20/131 +f 502/25/132 501/20/133 503/23/134 +f 504/28/135 503/23/136 505/25/137 +f 506/28/138 505/25/139 507/26/140 +f 496/21/141 495/16/142 497/18/143 +f 494/18/144 493/13/145 495/16/146 +f 492/14/374 491/30/375 493/13/149 +f 512/24/150 511/24/151 513/22/152 +f 510/26/153 509/28/154 511/24/155 +f 508/29/156 507/26/157 509/28/158 +f 678/1/1 669/19/1 650/1/1 +f 572/2/47 641/31/2 642/2/2 +f 574/32/46 572/32/47 573/35/47 +f 576/33/45 574/35/46 575/33/46 +f 578/36/44 576/34/45 577/36/45 +f 580/34/43 578/12/44 579/3/44 +f 582/35/42 580/36/43 581/12/43 +f 584/31/41 582/33/42 583/34/42 +f 586/4/40 584/2/41 585/32/41 +f 588/3/39 586/1/40 587/31/40 +f 591/8/37 589/5/38 590/3/38 +f 593/10/36 591/7/37 592/6/37 +f 595/30/35 593/9/382 594/8/36 +f 597/13/34 595/11/35 596/10/35 +f 599/15/33 597/12/34 598/30/34 +f 601/18/32 599/14/33 600/13/33 +f 603/21/31 601/17/32 602/16/32 +f 605/20/30 603/19/31 604/18/31 +f 607/23/28 605/21/29 606/19/29 +f 609/25/26 607/22/27 608/20/27 +f 611/28/24 609/24/25 610/23/26 +f 613/27/23 611/26/24 612/25/24 +f 615/29/22 613/28/23 614/26/23 +f 617/26/21 615/27/22 616/28/22 +f 619/24/20 617/25/21 618/24/21 +f 621/22/18 619/23/19 620/22/19 +f 623/19/16 621/20/17 622/21/17 +f 625/17/15 623/18/16 624/17/16 +f 627/16/383 625/16/15 626/15/15 +f 629/14/48 627/15/13 628/14/13 +f 631/12/49 629/13/379 630/12/379 +f 633/11/8 631/3/384 632/11/384 +f 635/9/385 633/10/8 634/9/8 +f 637/7/5 635/8/385 636/7/385 +f 639/5/4 637/6/5 638/5/5 +f 641/1/2 639/3/3 640/4/3 +f 621/22/50 620/22/51 622/21/52 +f 623/19/53 622/21/54 624/17/55 +f 625/17/56 624/17/57 626/15/58 +f 627/16/59 626/15/60 628/14/61 +f 629/14/386 628/14/377 630/12/387 +f 631/12/65 630/12/388 632/11/389 +f 633/11/68 632/11/69 634/9/70 +f 635/8/71 634/9/369 636/7/73 +f 637/6/74 636/7/390 638/5/76 +f 639/3/77 638/5/78 640/4/79 +f 641/31/80 640/4/81 642/2/82 +f 572/32/83 642/2/84 573/35/85 +f 574/32/86 573/35/87 575/33/88 +f 576/34/89 575/33/90 577/36/91 +f 578/12/92 577/36/93 579/3/94 +f 580/36/95 579/3/96 581/12/97 +f 582/33/98 581/12/99 583/34/100 +f 584/2/101 583/34/102 585/32/103 +f 586/1/104 585/32/105 587/31/106 +f 588/3/107 587/31/108 544/37/109 +f 544/37/110 590/37/110 588/3/111 +f 589/5/112 588/3/113 590/37/110 +f 591/8/114 590/3/115 592/6/116 +f 593/10/371 592/6/118 594/8/119 +f 595/11/120 594/8/121 596/10/122 +f 597/13/372 596/10/391 598/30/125 +f 605/20/126 604/18/127 606/19/128 +f 607/23/129 606/19/130 608/20/131 +f 609/25/132 608/20/133 610/23/134 +f 611/28/135 610/23/136 612/25/137 +f 613/28/138 612/25/139 614/26/140 +f 603/21/141 602/16/142 604/18/143 +f 601/18/144 600/13/145 602/16/146 +f 599/14/374 598/30/375 600/13/149 +f 619/24/150 618/24/151 620/22/152 +f 617/26/153 616/28/154 618/24/155 +f 615/29/156 614/26/157 616/28/158 +f 785/1/1 776/19/1 757/1/1 +f 679/2/47 748/31/2 749/2/2 +f 681/32/46 679/32/47 680/35/47 +f 683/33/45 681/35/46 682/33/46 +f 685/36/44 683/34/45 684/36/45 +f 687/34/43 685/12/44 686/3/44 +f 689/35/42 687/36/43 688/12/43 +f 691/31/41 689/33/42 690/34/42 +f 693/4/40 691/2/41 692/32/41 +f 695/3/39 693/1/40 694/31/40 +f 698/8/37 696/5/38 697/3/38 +f 700/10/36 698/7/37 699/6/37 +f 702/30/35 700/9/36 701/8/36 +f 704/13/34 702/11/35 703/10/35 +f 706/15/33 704/12/34 705/30/34 +f 708/18/32 706/14/33 707/13/33 +f 710/21/31 708/17/32 709/16/32 +f 712/20/30 710/19/31 711/18/31 +f 714/23/28 712/21/29 713/19/29 +f 716/25/26 714/22/28 715/20/28 +f 718/28/24 716/24/26 717/23/26 +f 720/27/23 718/26/24 719/25/24 +f 722/29/22 720/28/23 721/26/23 +f 724/26/21 722/27/392 723/28/392 +f 726/24/19 724/25/21 725/24/21 +f 728/22/18 726/23/19 727/22/19 +f 730/19/16 728/20/17 729/21/17 +f 732/17/15 730/18/16 731/17/16 +f 734/16/14 732/16/15 733/15/15 +f 736/14/48 734/15/13 735/14/13 +f 738/12/49 736/13/11 737/12/11 +f 740/11/8 738/3/9 739/11/9 +f 742/9/385 740/10/8 741/9/8 +f 744/7/5 742/8/6 743/7/6 +f 746/5/4 744/6/5 745/5/5 +f 748/1/2 746/3/3 747/4/3 +f 728/22/50 727/22/51 729/21/52 +f 730/19/53 729/21/54 731/17/55 +f 732/17/56 731/17/57 733/15/58 +f 734/16/59 733/15/60 735/14/61 +f 736/14/62 735/14/63 737/12/64 +f 738/12/65 737/12/66 739/11/67 +f 740/11/68 739/11/69 741/9/70 +f 742/8/71 741/9/369 743/7/73 +f 744/6/74 743/7/75 745/5/76 +f 746/3/77 745/5/78 747/4/79 +f 748/31/80 747/4/81 749/2/82 +f 679/32/83 749/2/84 680/35/85 +f 681/32/370 680/35/87 682/33/88 +f 683/34/89 682/33/90 684/36/91 +f 685/12/92 684/36/93 686/3/94 +f 687/36/95 686/3/96 688/12/97 +f 689/33/98 688/12/99 690/34/100 +f 691/2/101 690/34/102 692/32/103 +f 693/1/104 692/32/105 694/31/106 +f 695/3/107 694/31/108 651/37/109 +f 651/37/110 697/37/110 695/3/111 +f 696/5/112 695/3/113 697/37/110 +f 698/8/114 697/3/115 699/6/116 +f 700/10/371 699/6/118 701/8/119 +f 702/11/120 701/8/121 703/10/122 +f 704/13/372 703/10/124 705/30/125 +f 712/20/126 711/18/127 713/19/128 +f 714/23/129 713/19/130 715/20/131 +f 716/25/132 715/20/133 717/23/134 +f 718/28/135 717/23/136 719/25/137 +f 720/28/138 719/25/139 721/26/393 +f 710/21/141 709/16/142 711/18/143 +f 708/18/144 707/13/145 709/16/146 +f 706/14/374 705/30/375 707/13/149 +f 726/24/150 725/24/376 727/22/152 +f 724/26/153 723/28/154 725/24/155 +f 722/29/394 721/26/157 723/28/158 +f 786/2/47 855/31/2 856/2/2 +f 788/32/46 786/32/47 787/35/47 +f 790/33/45 788/35/46 789/33/46 +f 792/36/44 790/34/45 791/36/45 +f 794/34/43 792/12/44 793/3/44 +f 796/35/42 794/36/43 795/12/43 +f 798/31/41 796/33/42 797/34/42 +f 800/4/40 798/2/41 799/32/41 +f 802/3/39 800/1/40 801/31/40 +f 805/8/37 803/5/38 804/3/38 +f 807/10/36 805/7/37 806/6/37 +f 809/30/35 807/9/382 808/8/36 +f 811/13/34 809/11/35 810/10/35 +f 813/15/33 811/12/34 812/30/34 +f 815/18/32 813/14/33 814/13/33 +f 817/21/31 815/17/32 816/16/32 +f 819/20/30 817/19/31 818/18/31 +f 821/23/28 819/21/29 820/19/29 +f 823/25/26 821/22/28 822/20/28 +f 825/28/24 823/24/26 824/23/26 +f 827/27/23 825/26/24 826/25/24 +f 829/29/22 827/28/23 828/26/23 +f 831/26/21 829/27/392 830/28/392 +f 833/24/19 831/25/21 832/24/21 +f 835/22/18 833/23/19 834/22/19 +f 837/19/16 835/20/17 836/21/17 +f 839/17/15 837/18/16 838/17/16 +f 841/16/14 839/16/15 840/15/15 +f 843/14/48 841/15/13 842/14/13 +f 845/12/10 843/13/11 844/12/11 +f 847/11/8 845/3/9 846/11/9 +f 849/9/385 847/10/8 848/9/8 +f 851/7/5 849/8/6 850/7/6 +f 853/5/4 851/6/5 852/5/5 +f 855/1/2 853/3/3 854/4/3 +f 835/22/50 834/22/51 836/21/52 +f 837/19/53 836/21/54 838/17/55 +f 839/17/56 838/17/57 840/15/58 +f 841/16/59 840/15/60 842/14/61 +f 843/14/62 842/14/63 844/12/64 +f 845/12/65 844/12/66 846/11/67 +f 847/11/68 846/11/69 848/9/70 +f 849/8/71 848/9/369 850/7/73 +f 851/6/74 850/7/75 852/5/76 +f 853/3/77 852/5/78 854/4/79 +f 855/31/80 854/4/81 856/2/82 +f 786/32/83 856/2/84 787/35/85 +f 788/32/370 787/35/87 789/33/88 +f 790/34/89 789/33/90 791/36/91 +f 792/12/92 791/36/93 793/3/94 +f 794/36/95 793/3/96 795/12/97 +f 796/33/98 795/12/99 797/34/100 +f 798/2/101 797/34/102 799/32/103 +f 800/1/104 799/32/105 801/31/106 +f 802/3/107 801/31/108 758/37/109 +f 758/37/110 804/37/110 802/3/111 +f 803/5/112 802/3/113 804/37/110 +f 805/8/114 804/3/115 806/6/116 +f 807/10/371 806/6/118 808/8/119 +f 809/11/120 808/8/121 810/10/122 +f 811/13/372 810/10/391 812/30/125 +f 819/20/126 818/18/127 820/19/128 +f 821/23/129 820/19/130 822/20/131 +f 823/25/132 822/20/133 824/23/134 +f 825/28/135 824/23/136 826/25/137 +f 827/28/138 826/25/139 828/26/393 +f 817/21/141 816/16/142 818/18/143 +f 815/18/144 814/13/145 816/16/146 +f 813/14/374 812/30/375 814/13/149 +f 833/24/150 832/24/376 834/22/152 +f 831/26/153 830/28/154 832/24/155 +f 829/29/394 828/26/157 830/28/158 +f 72/1/1 63/19/1 44/1/1 +f 13/26/1 14/29/1 15/28/1 +f 15/28/1 16/26/1 17/24/1 +f 17/24/1 18/22/1 13/26/1 +f 18/22/1 19/21/1 11/22/1 +f 13/26/1 18/22/1 12/24/1 +f 20/19/1 21/17/1 27/5/1 +f 21/17/1 22/14/1 26/7/1 +f 25/9/1 23/12/1 24/11/1 +f 15/28/1 17/24/1 13/26/1 +f 23/12/1 25/9/1 22/14/1 +f 12/24/1 18/22/1 11/22/1 +f 22/14/1 25/9/1 26/7/1 +f 21/17/1 26/7/1 27/5/1 +f 11/22/1 19/21/1 29/1/1 +f 19/21/1 20/19/1 28/4/1 +f 29/1/1 19/21/1 28/4/1 +f 8/16/1 9/17/1 4/9/1 +f 9/17/1 10/19/1 3/7/1 +f 3/7/1 10/19/1 2/5/1 +f 10/19/1 11/22/1 29/1/1 +f 20/19/1 27/5/1 28/4/1 +f 2/5/1 10/19/1 1/1/1 +f 6/12/1 7/14/1 8/16/1 +f 4/9/1 5/11/1 6/12/1 +f 3/7/1 4/9/1 9/17/1 +f 35/32/1 36/2/1 30/2/1 +f 36/2/1 1/1/1 29/1/1 +f 35/32/1 33/36/1 34/33/1 +f 4/9/1 6/12/1 8/16/1 +f 33/36/1 35/32/1 31/33/1 +f 33/36/1 31/33/1 32/12/1 +f 1/1/1 10/19/1 29/1/1 +f 31/33/1 35/32/1 30/2/1 +f 228/26/1 229/29/1 230/28/1 +f 230/28/1 231/26/1 232/24/1 +f 232/24/1 233/22/1 228/26/1 +f 233/22/1 234/21/1 226/22/1 +f 228/26/1 233/22/1 227/24/1 +f 235/19/1 236/17/1 242/5/1 +f 236/17/1 237/14/1 241/7/1 +f 240/9/1 238/12/1 239/11/1 +f 230/28/1 232/24/1 228/26/1 +f 238/12/1 240/9/1 237/14/1 +f 227/24/1 233/22/1 226/22/1 +f 237/14/1 240/9/1 241/7/1 +f 236/17/1 241/7/1 242/5/1 +f 226/22/1 234/21/1 244/1/1 +f 234/21/1 235/19/1 243/4/1 +f 244/1/1 234/21/1 243/4/1 +f 223/16/1 224/17/1 219/9/1 +f 224/17/1 225/19/1 218/7/1 +f 218/7/1 225/19/1 217/5/1 +f 225/19/1 226/22/1 244/1/1 +f 235/19/1 242/5/1 243/4/1 +f 217/5/1 225/19/1 216/1/1 +f 221/12/1 222/14/1 223/16/1 +f 219/9/1 220/11/1 221/12/1 +f 218/7/1 219/9/1 224/17/1 +f 250/32/1 251/2/1 245/2/1 +f 251/2/1 216/1/1 244/1/1 +f 250/32/1 248/36/1 249/33/1 +f 219/9/1 221/12/1 223/16/1 +f 248/36/1 250/32/1 246/33/1 +f 248/36/1 246/33/1 247/12/1 +f 216/1/1 225/19/1 244/1/1 +f 246/33/1 250/32/1 245/2/1 +f 347/22/1 346/24/1 340/22/1 +f 346/24/1 345/26/1 340/22/1 +f 347/22/1 340/22/1 339/21/1 +f 345/26/1 344/29/1 343/28/1 +f 343/28/1 342/26/1 341/24/1 +f 341/24/1 340/22/1 345/26/1 +f 338/19/1 337/17/1 331/5/1 +f 337/17/1 336/14/1 332/7/1 +f 333/9/1 335/12/1 334/11/1 +f 343/28/1 341/24/1 345/26/1 +f 335/12/1 333/9/1 336/14/1 +f 336/14/1 333/9/1 332/7/1 +f 347/22/1 339/21/1 329/1/1 +f 339/21/1 338/19/1 330/4/1 +f 329/1/1 339/21/1 330/4/1 +f 350/16/1 349/17/1 354/9/1 +f 349/17/1 348/19/1 355/7/1 +f 355/7/1 348/19/1 356/5/1 +f 348/19/1 347/22/1 329/1/1 +f 337/17/1 332/7/1 331/5/1 +f 331/5/1 330/4/1 338/19/1 +f 329/1/1 328/2/1 144/2/1 +f 328/2/1 327/33/1 323/32/1 +f 329/1/1 144/2/1 357/1/1 +f 327/33/1 326/12/1 325/36/1 +f 356/5/1 348/19/1 357/1/1 +f 352/12/1 351/14/1 350/16/1 +f 354/9/1 353/11/1 352/12/1 +f 355/7/1 354/9/1 349/17/1 +f 323/32/1 144/2/1 328/2/1 +f 323/32/1 325/36/1 324/33/1 +f 354/9/1 352/12/1 350/16/1 +f 325/36/1 323/32/1 327/33/1 +f 454/22/1 453/24/1 447/22/1 +f 453/24/1 452/26/1 447/22/1 +f 454/22/1 447/22/1 446/21/1 +f 452/26/1 451/29/1 450/28/1 +f 450/28/1 449/26/1 448/24/1 +f 448/24/1 447/22/1 452/26/1 +f 445/19/1 444/17/1 438/5/1 +f 444/17/1 443/14/1 439/7/1 +f 440/9/1 442/12/1 441/11/1 +f 450/28/1 448/24/1 452/26/1 +f 442/12/1 440/9/1 443/14/1 +f 443/14/1 440/9/1 439/7/1 +f 454/22/1 446/21/1 436/1/1 +f 446/21/1 445/19/1 437/4/1 +f 436/1/1 446/21/1 437/4/1 +f 457/16/1 456/17/1 461/9/1 +f 456/17/1 455/19/1 462/7/1 +f 462/7/1 455/19/1 463/5/1 +f 455/19/1 454/22/1 436/1/1 +f 444/17/1 439/7/1 438/5/1 +f 438/5/1 437/4/1 445/19/1 +f 436/1/1 435/2/1 429/2/1 +f 435/2/1 434/33/1 430/32/1 +f 436/1/1 429/2/1 464/1/1 +f 434/33/1 433/12/1 432/36/1 +f 463/5/1 455/19/1 464/1/1 +f 459/12/1 458/14/1 457/16/1 +f 461/9/1 460/11/1 459/12/1 +f 462/7/1 461/9/1 456/17/1 +f 430/32/1 429/2/1 435/2/1 +f 430/32/1 432/36/1 431/33/1 +f 461/9/1 459/12/1 457/16/1 +f 432/36/1 430/32/1 434/33/1 +f 561/22/1 560/24/1 554/22/1 +f 560/24/1 559/26/1 554/22/1 +f 561/22/1 554/22/1 553/21/1 +f 559/26/1 558/29/1 557/28/1 +f 557/28/1 556/26/1 555/24/1 +f 555/24/1 554/22/1 559/26/1 +f 552/19/1 551/17/1 545/5/1 +f 551/17/1 550/14/1 546/7/1 +f 547/9/1 549/12/1 548/11/1 +f 557/28/1 555/24/1 559/26/1 +f 549/12/1 547/9/1 550/14/1 +f 550/14/1 547/9/1 546/7/1 +f 561/22/1 553/21/1 543/1/1 +f 553/21/1 552/19/1 544/4/1 +f 543/1/1 553/21/1 544/4/1 +f 564/16/1 563/17/1 568/9/1 +f 563/17/1 562/19/1 569/7/1 +f 569/7/1 562/19/1 570/5/1 +f 562/19/1 561/22/1 543/1/1 +f 551/17/1 546/7/1 545/5/1 +f 545/5/1 544/4/1 552/19/1 +f 543/1/1 542/2/1 536/2/1 +f 542/2/1 541/33/1 537/32/1 +f 543/1/1 536/2/1 571/1/1 +f 541/33/1 540/12/1 539/36/1 +f 570/5/1 562/19/1 571/1/1 +f 566/12/1 565/14/1 564/16/1 +f 568/9/1 567/11/1 566/12/1 +f 569/7/1 568/9/1 563/17/1 +f 537/32/1 536/2/1 542/2/1 +f 537/32/1 539/36/1 538/33/1 +f 568/9/1 566/12/1 564/16/1 +f 539/36/1 537/32/1 541/33/1 +f 668/22/1 667/24/1 661/22/1 +f 667/24/1 666/26/1 661/22/1 +f 668/22/1 661/22/1 660/21/1 +f 666/26/1 665/29/1 664/28/1 +f 664/28/1 663/26/1 662/24/1 +f 662/24/1 661/22/1 666/26/1 +f 659/19/1 658/17/1 652/5/1 +f 658/17/1 657/14/1 653/7/1 +f 654/9/1 656/12/1 655/11/1 +f 664/28/1 662/24/1 666/26/1 +f 656/12/1 654/9/1 657/14/1 +f 657/14/1 654/9/1 653/7/1 +f 668/22/1 660/21/1 650/1/1 +f 660/21/1 659/19/1 651/4/1 +f 650/1/1 660/21/1 651/4/1 +f 671/16/1 670/17/1 675/9/1 +f 670/17/1 669/19/1 676/7/1 +f 676/7/1 669/19/1 677/5/1 +f 669/19/1 668/22/1 650/1/1 +f 658/17/1 653/7/1 652/5/1 +f 652/5/1 651/4/1 659/19/1 +f 650/1/1 649/2/1 643/2/1 +f 649/2/1 648/33/1 644/32/1 +f 650/1/1 643/2/1 678/1/1 +f 648/33/1 647/12/1 646/36/1 +f 677/5/1 669/19/1 678/1/1 +f 673/12/1 672/14/1 671/16/1 +f 675/9/1 674/11/1 673/12/1 +f 676/7/1 675/9/1 670/17/1 +f 644/32/1 643/2/1 649/2/1 +f 644/32/1 646/36/1 645/33/1 +f 675/9/1 673/12/1 671/16/1 +f 646/36/1 644/32/1 648/33/1 +f 775/22/1 774/24/1 768/22/1 +f 774/24/1 773/26/1 768/22/1 +f 775/22/1 768/22/1 767/21/1 +f 773/26/1 772/29/1 771/28/1 +f 771/28/1 770/26/1 769/24/1 +f 769/24/1 768/22/1 773/26/1 +f 766/19/1 765/17/1 759/5/1 +f 765/17/1 764/14/1 760/7/1 +f 761/9/1 763/12/1 762/11/1 +f 771/28/1 769/24/1 773/26/1 +f 763/12/1 761/9/1 764/14/1 +f 764/14/1 761/9/1 760/7/1 +f 775/22/1 767/21/1 757/1/1 +f 767/21/1 766/19/1 758/4/1 +f 757/1/1 767/21/1 758/4/1 +f 778/16/1 777/17/1 782/9/1 +f 777/17/1 776/19/1 783/7/1 +f 783/7/1 776/19/1 784/5/1 +f 776/19/1 775/22/1 757/1/1 +f 765/17/1 760/7/1 759/5/1 +f 759/5/1 758/4/1 766/19/1 +f 757/1/1 756/2/1 750/2/1 +f 756/2/1 755/33/1 751/32/1 +f 757/1/1 750/2/1 785/1/1 +f 755/33/1 754/12/1 753/36/1 +f 784/5/1 776/19/1 785/1/1 +f 780/12/1 779/14/1 778/16/1 +f 782/9/1 781/11/1 780/12/1 +f 783/7/1 782/9/1 777/17/1 +f 751/32/1 750/2/1 756/2/1 +f 751/32/1 753/36/1 752/33/1 +f 782/9/1 780/12/1 778/16/1 +f 753/36/1 751/32/1 755/33/1 +f 62/22/1 61/24/1 55/22/1 +f 61/24/1 60/26/1 55/22/1 +f 62/22/1 55/22/1 54/21/1 +f 60/26/1 59/29/1 58/28/1 +f 58/28/1 57/26/1 56/24/1 +f 56/24/1 55/22/1 60/26/1 +f 53/19/1 52/17/1 46/5/1 +f 52/17/1 51/14/1 47/7/1 +f 48/9/1 50/12/1 49/11/1 +f 58/28/1 56/24/1 60/26/1 +f 50/12/1 48/9/1 51/14/1 +f 51/14/1 48/9/1 47/7/1 +f 62/22/1 54/21/1 44/1/1 +f 54/21/1 53/19/1 45/4/1 +f 44/1/1 54/21/1 45/4/1 +f 65/16/1 64/17/1 69/9/1 +f 64/17/1 63/19/1 70/7/1 +f 70/7/1 63/19/1 71/5/1 +f 63/19/1 62/22/1 44/1/1 +f 52/17/1 47/7/1 46/5/1 +f 46/5/1 45/4/1 53/19/1 +f 44/1/1 43/2/1 37/2/1 +f 43/2/1 42/33/1 38/32/1 +f 44/1/1 37/2/1 72/1/1 +f 42/33/1 41/12/1 40/36/1 +f 71/5/1 63/19/1 72/1/1 +f 67/12/1 66/14/1 65/16/1 +f 69/9/1 68/11/1 67/12/1 +f 70/7/1 69/9/1 64/17/1 +f 38/32/1 37/2/1 43/2/1 +f 38/32/1 40/36/1 39/33/1 +f 69/9/1 67/12/1 65/16/1 +f 40/36/1 38/32/1 42/33/1 diff --git a/Telegram/Resources/art/premium/diamond.obj b/Telegram/Resources/art/premium/diamond.obj new file mode 100644 index 00000000000000..b8bf01fdbfc6ab --- /dev/null +++ b/Telegram/Resources/art/premium/diamond.obj @@ -0,0 +1,65 @@ +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v 0 -0.750370979 0 +v 0.38727501 0.423541009 -0.803673029 +v 0.869799018 0.423541009 -0.198298007 +v 0.697345972 0.423541009 0.556400001 +v -0.000222000002 0.423541009 0.892117023 +v -0.697624028 0.423541009 0.556052029 +v -0.869700015 0.423541009 -0.198732004 +v -0.386875004 0.423541009 -0.803866029 +v 0.502963006 0.76503402 -0.40110001 +v 0.627183974 0.76503402 0.143151 +v 0.279123008 0.76503402 0.579604983 +v -0.279123008 0.76503402 0.579604983 +v -0.627183974 0.76503402 0.143151 +v -0.502963006 0.76503402 -0.40110001 +v 0 0.76503402 -0.643314004 +vt 0 0 +vn 0.645200014 -0.564999998 -0.514299989 +vn 0.804400027 -0.564999998 0.183799997 +vn 0.357800007 -0.564999998 0.743499994 +vn -0.358200014 -0.564999998 0.743300021 +vn -0.804499984 -0.564999998 0.183400005 +vn -0.644999981 -0.564999998 -0.514599979 +vn 0.000199999995 -0.564999998 -0.825100005 +vn 0.576799989 0.675100029 0.460000008 +vn 0.392500013 0.425300002 0.815500021 +vn -0 0.675100029 0.737699986 +vn -0.39289999 0.425300002 0.815299988 +vn -0.576799989 0.675100029 0.460000008 +vn -0.882399976 0.425300002 0.201199993 +vn -0.719200015 0.675100029 -0.164199993 +vn -0.707499981 0.425300002 -0.564499974 +vn -0.320100009 0.675100029 -0.664699972 +vn 0.000199999995 0.425300002 -0.905099988 +vn 0.320100009 0.675100029 -0.664699972 +vn 0.707799971 0.425300002 -0.564100027 +vn 0.719200015 0.675100029 -0.164199993 +vn 0.882300019 0.425300002 0.2016 +vn -0 1 -0 +f 3/1/1 1/1/1 2/1/1 +f 4/1/2 1/1/2 3/1/2 +f 5/1/3 1/1/3 4/1/3 +f 6/1/4 1/1/4 5/1/4 +f 7/1/5 1/1/5 6/1/5 +f 8/1/6 1/1/6 7/1/6 +f 2/1/7 1/1/7 8/1/7 +f 11/1/8 4/1/8 10/1/8 +f 5/1/9 4/1/9 11/1/9 +f 12/1/10 5/1/10 11/1/10 +f 6/1/11 5/1/11 12/1/11 +f 13/1/12 6/1/12 12/1/12 +f 7/1/13 6/1/13 13/1/13 +f 14/1/14 7/1/14 13/1/14 +f 8/1/15 7/1/15 14/1/15 +f 15/1/16 8/1/16 14/1/16 +f 2/1/17 8/1/17 15/1/17 +f 9/1/18 2/1/18 15/1/18 +f 3/1/19 2/1/19 9/1/19 +f 10/1/20 3/1/20 9/1/20 +f 3/1/21 10/1/21 4/1/21 +f 15/1/22 13/1/22 11/1/22 +f 15/1/22 14/1/22 13/1/22 +f 13/1/22 12/1/22 11/1/22 +f 11/1/22 10/1/22 9/1/22 +f 9/1/22 15/1/22 11/1/22 diff --git a/Telegram/Resources/art/premium/diamond_outer.obj b/Telegram/Resources/art/premium/diamond_outer.obj new file mode 100644 index 00000000000000..ddb5fea680fd55 --- /dev/null +++ b/Telegram/Resources/art/premium/diamond_outer.obj @@ -0,0 +1,69 @@ +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v 0 -1.06897998 0 +v 0.488651991 0.418583006 -1.01405394 +v 1.097489 0.418583006 -0.250209004 +v 0.879894972 0.418583006 0.702049017 +v -0.000279 0.418583006 1.12565005 +v -0.88024199 0.418583006 0.701613009 +v -1.09736502 0.418583006 -0.250752002 +v -0.488150001 0.418583006 -1.01429605 +v 0.590651989 0.937004983 -0.471022993 +v 0.734121025 0.933223009 0.167564005 +v 0.328667015 0.940138996 0.682497025 +v -0.326719999 0.933223009 0.678429008 +v -0.737766027 0.938952029 0.168384999 +v -0.588716984 0.933223009 -0.469493002 +v 4.99999987e-06 0.938952029 -0.756738007 +vt 0 0 +vn 0.646099985 -0.563300014 -0.514999986 +vn 0.805499971 -0.563300014 0.184100002 +vn 0.3583 -0.563300014 0.744499981 +vn -0.358700007 -0.563300014 0.744300008 +vn -0.805599988 -0.563300014 0.183699995 +vn -0.645900011 -0.563300014 -0.515299976 +vn 0.000199999995 -0.563300014 -0.826200008 +vn 0.600199997 0.65170002 0.463800013 +vn 0.389099985 0.441500008 0.808499992 +vn -0.0115999999 0.65170002 0.758400023 +vn -0.387100011 0.452499986 0.803300023 +vn -0.586499989 0.652400017 0.479999989 +vn -0.873899996 0.443399996 0.199200004 +vn -0.736699998 0.652400017 -0.178000003 +vn -0.697099984 0.452499986 -0.556200027 +vn -0.337399989 0.652400017 -0.678600013 +vn 0.000199999995 0.443399996 -0.896300018 +vn 0.332599998 0.650099993 -0.683099985 +vn 0.699699998 0.446500003 -0.557699978 +vn 0.739199996 0.653599977 -0.162200004 +vn 0.869300008 0.452499986 0.198599994 +vn -0.00079999998 1 -0.000600000028 +vn -0.0159000009 0.999800026 -0.0126999998 +vn -0.0107000005 0.99970001 0.0197999999 +vn 0.0190999992 0.999800026 0.00159999996 +vn 0.00419999985 1 -0.00179999997 +f 3/1/1 1/1/1 2/1/1 +f 4/1/2 1/1/2 3/1/2 +f 5/1/3 1/1/3 4/1/3 +f 6/1/4 1/1/4 5/1/4 +f 7/1/5 1/1/5 6/1/5 +f 8/1/6 1/1/6 7/1/6 +f 2/1/7 1/1/7 8/1/7 +f 11/1/8 4/1/8 10/1/8 +f 5/1/9 4/1/9 11/1/9 +f 12/1/10 5/1/10 11/1/10 +f 6/1/11 5/1/11 12/1/11 +f 13/1/12 6/1/12 12/1/12 +f 7/1/13 6/1/13 13/1/13 +f 14/1/14 7/1/14 13/1/14 +f 8/1/15 7/1/15 14/1/15 +f 15/1/16 8/1/16 14/1/16 +f 2/1/17 8/1/17 15/1/17 +f 9/1/18 2/1/18 15/1/18 +f 3/1/19 2/1/19 9/1/19 +f 10/1/20 3/1/20 9/1/20 +f 3/1/21 10/1/21 4/1/21 +f 15/1/22 13/1/22 11/1/22 +f 15/1/23 14/1/23 13/1/23 +f 13/1/24 12/1/24 11/1/24 +f 11/1/25 10/1/25 9/1/25 +f 9/1/26 15/1/26 11/1/26 diff --git a/Telegram/Resources/art/premium/diamond_outer_2.obj b/Telegram/Resources/art/premium/diamond_outer_2.obj new file mode 100644 index 00000000000000..4b6a514df52961 --- /dev/null +++ b/Telegram/Resources/art/premium/diamond_outer_2.obj @@ -0,0 +1,65 @@ +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v -0 -1.13013005 -0 +v 0.517108977 0.416763991 -1.07310998 +v 1.16140401 0.416763991 -0.264780998 +v 0.931137979 0.416763991 0.742933989 +v -0.000293999998 0.416763991 1.19120395 +v -0.931505024 0.416763991 0.74247402 +v -1.161273 0.416763991 -0.265354991 +v -0.516578972 0.416763991 -1.07336605 +v 0.618188977 0.991851985 -0.492980987 +v 0.770861983 0.991851985 0.175951004 +v 0.343059987 0.991851985 0.712387979 +v -0.34307301 0.991851985 0.712382019 +v -0.770865023 0.991851985 0.175937995 +v -0.618179977 0.991851985 -0.492991 +v 7.0000001e-06 0.991851985 -0.790687978 +vt 0 0 +vn 0.642499983 -0.569999993 -0.512099981 +vn 0.800999999 -0.569999993 0.182999998 +vn 0.356299996 -0.569999993 0.7403 +vn -0.356700003 -0.569999993 0.740199983 +vn -0.801100016 -0.569999993 0.182600006 +vn -0.642199993 -0.569999993 -0.512399971 +vn 0.000199999995 -0.569999993 -0.82160002 +vn 0.600799978 0.639900029 0.479200006 +vn 0.389200002 0.441000015 0.808700025 +vn -0 0.639900029 0.76849997 +vn -0.389600009 0.441000015 0.808499992 +vn -0.600799978 0.639900029 0.479099989 +vn -0.875100017 0.441000015 0.199499995 +vn -0.749199986 0.639900029 -0.171000004 +vn -0.701600015 0.441000015 -0.559800029 +vn -0.333400011 0.639900029 -0.692399979 +vn 0.000199999995 0.441000015 -0.897499979 +vn 0.333400011 0.639900029 -0.692399979 +vn 0.701900005 0.441000015 -0.559400022 +vn 0.749199986 0.639900029 -0.171000004 +vn 0.875 0.441000015 0.199900001 +vn -0 1 -0 +f 2/1/1 3/1/1 1/1/1 +f 3/1/2 4/1/2 1/1/2 +f 4/1/3 5/1/3 1/1/3 +f 5/1/4 6/1/4 1/1/4 +f 6/1/5 7/1/5 1/1/5 +f 7/1/6 8/1/6 1/1/6 +f 8/1/7 2/1/7 1/1/7 +f 10/1/8 11/1/8 4/1/8 +f 11/1/9 5/1/9 4/1/9 +f 11/1/10 12/1/10 5/1/10 +f 12/1/11 6/1/11 5/1/11 +f 12/1/12 13/1/12 6/1/12 +f 13/1/13 7/1/13 6/1/13 +f 13/1/14 14/1/14 7/1/14 +f 14/1/15 8/1/15 7/1/15 +f 14/1/16 15/1/16 8/1/16 +f 15/1/17 2/1/17 8/1/17 +f 15/1/18 9/1/18 2/1/18 +f 9/1/19 3/1/19 2/1/19 +f 9/1/20 10/1/20 3/1/20 +f 4/1/21 3/1/21 10/1/21 +f 10/1/22 14/1/22 12/1/22 +f 14/1/22 9/1/22 15/1/22 +f 12/1/22 14/1/22 13/1/22 +f 10/1/22 12/1/22 11/1/22 +f 14/1/22 10/1/22 9/1/22 diff --git a/Telegram/Resources/art/premium/flecks.png b/Telegram/Resources/art/premium/flecks.png new file mode 100644 index 00000000000000..b6695a1690d98b Binary files /dev/null and b/Telegram/Resources/art/premium/flecks.png differ diff --git a/Telegram/Resources/art/premium/star.obj b/Telegram/Resources/art/premium/star.obj new file mode 100644 index 00000000000000..a8cc84093cbe64 --- /dev/null +++ b/Telegram/Resources/art/premium/star.obj @@ -0,0 +1,18251 @@ +# 3D mesh decoded from .binobj (source of truth; baked back to .binobj at build time). +# UVs are stored raw here; the V coordinate is flipped (1-v) at load time. +v -0.103200004 27.3429031 -4.3691349 +v -0.921007991 26.3249779 -4.0468359 +v -1.15358305 25.2562637 -3.71698189 +v 1.35340703 -0.495142013 6.66928196 +v 2.39414907 -0.386483014 6.65102386 +v 0.908179998 25.9860134 -3.933182 +v 0.319952995 -0.126155004 0.0581469983 +v 2.24310994 -0.360754013 0.0373330005 +v 1.27218997 -0.462208986 0.0514319986 +v 1.52056897 23.8180561 -3.28783703 +v -0.0233730003 24.1532192 -3.37743497 +v 0.44908601 22.0349197 -2.7895081 +v 1.82794702 21.981329 -2.79083395 +v 2.74957609 23.3583279 -3.18677402 +v -1.35894597 23.7580318 -3.27873397 +v -2.277457 22.5031452 -2.954427 +v -1.12660396 21.833147 -2.7500701 +v 3.6192441 22.423378 -2.96252704 +v 4.60329008 21.5523701 -2.77907705 +v 3.34760809 21.1011372 -2.60858297 +v 4.62258911 20.4319897 -2.49855399 +v 3.03088188 24.3124313 -3.469908 +v 2.22142696 20.3826752 -2.39483094 +v 0.606887996 19.4607296 -2.15617204 +v -0.473601013 20.4358139 -2.39049006 +v -2.21846604 20.6159992 -2.46739197 +v -3.7702539 21.0314808 -2.63148499 +v -3.93886399 19.3480301 -2.23449707 +v -4.77740383 20.4333534 -2.53670001 +v -4.06157684 22.4211273 -3.00376797 +v 3.75344801 19.2794037 -2.18492699 +v 3.59288096 17.7471294 -1.84488297 +v 5.08681917 17.836401 -1.93500805 +v 1.80748403 18.4209061 -1.936373 +v 0.697205007 17.4671135 -1.72268605 +v -0.602970004 18.3546829 -1.91194403 +v -1.89323294 17.4733009 -1.75009894 +v -1.992226 19.3772278 -2.16730809 +v -3.21656799 18.1033173 -1.92638302 +v -4.5995698 17.6358089 -1.89436901 +v 4.001369 16.1750908 -1.54841805 +v 2.79484797 15.0578089 -1.29774499 +v 2.55327511 16.807003 -1.61812794 +v 5.26112509 14.7151604 -1.34512603 +v 5.29022884 15.966732 -1.57381201 +v 1.31580698 15.2467661 -1.299402 +v -0.343216985 15.921998 -1.41818404 +v -1.82915902 14.9054928 -1.25853503 +v -2.93170905 16.2269573 -1.53500605 +v -3.88161707 15.235177 -1.39364302 +v -5.38371897 17.02108 -1.81944203 +v -7.46859217 14.5915432 -1.52851796 +v -5.25357294 15.5123215 -1.52008295 +v 6.66723394 13.2908916 -1.20574999 +v 4.98189783 12.6522007 -0.999445975 +v 3.71888804 13.727396 -1.10562599 +v 3.12408209 12.009758 -0.824706018 +v 1.62712705 13.4317389 -1.00058806 +v 1.94071496 11.9230566 -0.782513976 +v 0.107428998 12.4078054 -0.833989978 +v -0.498650998 14.0592155 -1.092538 +v -1.98063397 13.1559858 -0.974731982 +v -4.31356812 14.0368643 -1.20818305 +v -3.67241192 12.8179045 -0.986639023 +v -6.35899305 13.6603518 -1.28202796 +v -5.48161316 12.5572758 -1.04827499 +v 5.67541218 11.1304312 -0.828876019 +v 7.07975483 11.3040562 -0.952576995 +v 8.28427124 9.593503 -0.846688986 +v 4.07553005 11.528307 -0.797245979 +v 3.2458539 10.3038387 -0.606781006 +v 0.763613999 10.3628311 -0.563097 +v -0.949016988 11.3249922 -0.693311989 +v -2.484375 11.0020227 -0.68542701 +v -0.879643977 9.68631172 -0.492121011 +v -4.0710392 11.2828169 -0.786907971 +v -4.36267519 9.91420555 -0.633979023 +v -6.28450918 10.8585939 -0.87371099 +v -6.20007801 9.43526554 -0.699660003 +v 19.1762905 8.21221256 -2.42608809 +v 17.2106743 8.93722439 -2.08353305 +v 17.8393383 7.18965721 -2.045398 +v 17.5056934 9.81051922 -2.24247408 +v 14.9987526 8.91669846 -1.66740894 +v 16.7387066 7.96679306 -1.89363003 +v 14.9046783 7.55504322 -1.51925004 +v 15.8654985 10.1021051 -1.95711398 +v 14.9067698 9.83474255 -1.75547802 +v 13.2855387 8.00247765 -1.29907799 +v 13.2114506 9.24208546 -1.41322899 +v 10.6433277 9.60744286 -1.10127401 +v 11.9312611 8.28133488 -1.12866795 +v 10.4957619 7.18926811 -0.846327007 +v 12.1121759 10.6919651 -1.42256904 +v 9.05832672 8.10255814 -0.769194007 +v 7.58985901 8.15517235 -0.637099981 +v 6.08414602 8.99944878 -0.604354024 +v 6.06626177 6.80268717 -0.402478009 +v 2.62062001 9.05187988 -0.446431011 +v 4.76029491 9.3608408 -0.565549016 +v 4.52878523 7.223423 -0.346491992 +v 2.95722795 7.40674782 -0.299845994 +v 1.19183898 8.18444347 -0.33236599 +v -0.883956015 8.43158531 -0.360514998 +v -2.05778503 7.09779406 -0.263130993 +v -0.588746011 6.88110685 -0.217985004 +v -3.02394795 9.06397724 -0.478347003 +v -3.08403492 7.80807686 -0.359577 +v -4.69250202 8.15307903 -0.467774004 +v -5.97427177 7.73551607 -0.512925029 +v -7.61051893 9.27994633 -0.799959004 +v -7.47149992 7.81745195 -0.643091977 +v -9.18087673 7.73124599 -0.804193974 +v -9.08691788 9.66987801 -0.990090013 +v -11.1588287 8.80176353 -1.14762795 +v -12.7797947 7.79362917 -1.28202403 +v -11.5048513 10.0253878 -1.33176506 +v -14.2737961 9.3977356 -1.68808496 +v -14.3276119 8.14106083 -1.56738496 +v -12.9711905 10.0605717 -1.550946 +v -15.7932014 9.01541519 -1.92005396 +v -15.6833401 7.57456493 -1.75667405 +v -17.6089993 9.27209854 -2.31149602 +v -17.1405354 7.34633112 -2.0243361 +v -15.3687878 10.2190533 -1.97768998 +v -16.6459656 9.92490768 -2.18950891 +v -18.6934814 8.2018404 -2.43712306 +v -20.2231522 8.51864433 -2.8284409 +v -21.6097908 7.97957802 -3.126405 +v -19.8975487 6.91571617 -2.60182595 +v -19.5571194 9.49830627 -2.7754159 +v 25.4143906 5.3156662 -3.8357439 +v 26.2277164 7.10243082 -4.22346401 +v 26.8470345 5.15573883 -4.274652 +v 27.6434803 6.75901604 -4.65223122 +v 28.4580956 4.90066814 -4.79706621 +v 23.3479881 6.12165499 -3.29312992 +v 24.5395184 6.83877516 -3.68734694 +v 23.7168846 4.32680988 -3.28169608 +v 21.8380337 5.79849911 -2.86493611 +v 19.6677761 6.07402897 -2.35688996 +v 20.3361359 4.61247301 -2.42117596 +v 20.740778 7.60299587 -2.73566508 +v 22.0305634 7.56259108 -3.0586741 +v 18.4200401 5.18884706 -2.01877904 +v 16.5529194 6.11881495 -1.70363402 +v 14.8239202 5.88341713 -1.37420905 +v 16.9528065 4.3567791 -1.67256296 +v 13.4763432 6.16437483 -1.17450094 +v 13.3922567 4.7187891 -1.06891799 +v 11.5919857 5.63556719 -0.868534982 +v 12.2890911 6.69355202 -1.04037797 +v 8.96886921 6.21057892 -0.602680981 +v 7.2803278 5.89982224 -0.424638003 +v 7.9059701 4.56241083 -0.397798985 +v 10.0228462 5.03924799 -0.638373971 +v 5.39853096 5.27442312 -0.25397101 +v 2.94959593 5.6337471 -0.165178999 +v 4.02324104 4.88337088 -0.159137994 +v 1.25966001 6.24080276 -0.170030996 +v 0.967519999 4.87861776 -0.0803769976 +v -0.579460979 5.28710699 -0.107152 +v -2.15438294 4.97580194 -0.117358997 +v -3.39158392 6.04632711 -0.228102997 +v -4.77090693 5.89604187 -0.286538005 +v -6.79539013 6.67519999 -0.486568987 +v -6.23920918 5.29924822 -0.348549008 +v -9.66063499 5.66563988 -0.696901023 +v -8.47331047 6.53768682 -0.631338 +v -8.17352104 5.30375099 -0.514325023 +v -11.6005621 5.58136797 -0.937345982 +v -10.7297745 6.99179077 -0.924588025 +v -13.3072166 6.14973688 -1.23126698 +v -14.5951643 4.90956688 -1.36424899 +v -14.6979733 6.27665901 -1.47200596 +v -12.5047722 4.36283207 -0.995095015 +v -16.8713799 5.93999815 -1.85755205 +v -18.909626 6.61166382 -2.3457799 +v -17.8049088 5.0277319 -1.99291897 +v -18.9879856 4.52020693 -2.22634006 +v -21.1963215 6.37303305 -2.87996697 +v -20.2629242 5.32396317 -2.57394195 +v -22.0498791 5.01730108 -3.01108408 +v -24.2135201 7.09057999 -3.769274 +v -22.9778156 7.41639805 -3.44165492 +v -24.0039062 5.2480278 -3.57057595 +v -27.7297764 5.01402807 -4.73546124 +v -27.9401054 6.25595284 -4.89501381 +v -26.0507202 6.93858814 -4.32019281 +v -25.8620167 5.95949793 -4.18356705 +v -27.550972 7.52548695 -4.86907482 +v -28.7873631 6.66102123 -5.2234602 +v 23.6560001 3.1396451 -3.21068192 +v 22.1079178 2.27198005 -2.7658689 +v 25.7698536 3.01008391 -3.82838011 +v 23.2633305 1.10647202 -3.05126595 +v 24.3142872 1.87100005 -3.36128497 +v 21.5654659 3.66457891 -2.6760819 +v 21.3151817 1.57086504 -2.5447681 +v 19.4709377 3.18303609 -2.15148711 +v 18.0024452 2.69501495 -1.81452799 +v 15.4936447 4.33419609 -1.39728498 +v 16.396698 2.41539502 -1.485605 +v 13.8888884 3.48490405 -1.08691204 +v 13.2754335 2.18011808 -0.947542012 +v 10.5219059 4.09535408 -0.646643996 +v 11.4814472 3.357337 -0.731684983 +v 9.09394169 3.61698389 -0.466325015 +v 9.54481506 1.99833906 -0.460108012 +v 6.184587 3.80081797 -0.225188002 +v 7.43821716 2.31510901 -0.266801 +v 4.39028597 2.80345201 -0.081629999 +v 2.62010789 3.32562494 -0.0338330008 +v 1.17141998 3.29892111 -0.00690799998 +v 1.19206595 2.08799696 0.0298629999 +v -1.090469 3.73045802 -0.0300089996 +v -1.926983 2.56307602 -0.00644099992 +v -0.308690995 1.88306701 0.0380120017 +v -1.86860502 1.01496005 0.0295170005 +v -0.261139989 0.0881669968 0.0585040003 +v -1.20280099 -0.531994998 0.0456169993 +v -3.49317789 3.64412403 -0.0982669964 +v -5.02210999 3.72200108 -0.181435004 +v -3.50149202 1.59302199 -0.0344379991 +v -4.76650286 1.79001498 -0.103890002 +v -5.30865383 -0.305738002 -0.119378999 +v -3.27022004 -0.262454003 -0.0116729997 +v -6.31711102 3.72494888 -0.270437002 +v -5.91411114 1.61661398 -0.173546001 +v -6.98330402 -0.0697600022 -0.243351996 +v -9.38346958 4.04633093 -0.574903011 +v -7.66356087 3.52681589 -0.373573005 +v -9.08390236 2.52737308 -0.482127994 +v -8.07574654 1.66842496 -0.35699001 +v -8.22090054 0.273304999 -0.357295007 +v -9.92778873 0.559773982 -0.542725027 +v -10.4023142 3.47512794 -0.668170989 +v -11.9725771 2.58156705 -0.847478986 +v -10.9733772 1.61875296 -0.686062992 +v -13.4073725 3.0833261 -1.079983 +v -14.6994381 2.16719699 -1.26877105 +v -16.5223694 4.37357712 -1.692788 +v -15.6228142 3.11367989 -1.46302199 +v -19.3237343 3.17529607 -2.2413919 +v -17.8003197 2.8005271 -1.88756096 +v -22.1705475 2.94527102 -2.942204 +v -20.4190578 3.50489712 -2.51535106 +v -20.52808 1.67226803 -2.484442 +v -25.9737339 4.02642679 -4.10149002 +v -24.6692543 2.74815989 -3.64665508 +v -24.2526131 3.84803796 -3.5672729 +v -23.1269035 3.49973297 -3.226861 +v -25.1500511 1.26954603 -3.75615096 +v -23.5926476 1.14551795 -3.293643 +v -22.416666 0.497114986 -2.95901895 +v 20.3215542 0.311578989 -2.29029012 +v 21.2750225 -0.564773977 -2.52464199 +v 20.5514011 -1.96860802 -2.36681795 +v 22.0743179 -0.862363994 -2.72911501 +v 19.2253761 1.38618898 -2.04932308 +v 18.2390079 -0.0835150033 -1.82015705 +v 15.3602161 1.64417601 -1.27852201 +v 17.0520706 0.887130976 -1.58290505 +v 16.0576 -0.190346003 -1.38883901 +v 14.2137804 0.500478983 -1.06937802 +v 12.6124153 0.845857024 -0.827893019 +v 12.6232643 -0.750437021 -0.826328993 +v 11.3617878 0.796414018 -0.654711008 +v 9.94986153 0.646207988 -0.483934999 +v 7.55075312 1.12820005 -0.25228399 +v 8.59401608 0.224720001 -0.340604991 +v 3.99498892 0.61848402 -0.0216739997 +v 3.0823431 1.582847 -9.60000034e-05 +v 2.57922506 0.330246001 0.0286689997 +v 5.70795918 1.75289905 -0.128622994 +v 5.96218109 -0.186343998 -0.126115993 +v -11.878005 -0.235916004 -0.792636991 +v -12.8229198 1.188887 -0.940997005 +v -12.0578556 -1.20025802 -0.826870978 +v -10.4335203 -1.47012305 -0.614951015 +v -11.3937407 -2.30114007 -0.759109974 +v -9.46112347 -0.454571992 -0.488146991 +v -9.06811523 -1.26758099 -0.453530997 +v -8.06685066 -0.744117975 -0.345391989 +v -7.23132801 -1.194363 -0.273972005 +v -14.2815132 0.52353698 -1.16867101 +v -13.7912645 -1.45230305 -1.09783995 +v -15.0140953 -0.93344301 -1.30014896 +v -16.2891006 0.85532701 -1.53698301 +v -20.7962933 0.460480988 -2.53694201 +v -19.0213203 -0.0830340013 -2.11081195 +v -18.1447392 1.32556498 -1.92563605 +v -16.1716423 -1.43168795 -1.522802 +v -17.4901428 -1.09929895 -1.78180897 +v -17.1104546 -2.96643806 -1.75017297 +v -15.0019617 -2.79238796 -1.339643 +v -21.026556 -0.851534009 -2.59700394 +v -19.3057289 -1.46274805 -2.18810201 +v -18.5152168 -2.857481 -2.04534698 +v 19.0905743 -1.04003 -2.01337194 +v 18.1409454 -1.96960998 -1.82313395 +v 18.8988457 -3.03661895 -2.01833296 +v 17.7950649 -4.88061905 -1.86784399 +v 18.9613056 -4.30681992 -2.08760691 +v 16.5446529 -1.60656095 -1.49609601 +v 14.9347134 -2.52389789 -1.22440803 +v 16.9191742 -3.46561909 -1.62112105 +v 14.5311384 -1.04911804 -1.12540698 +v 12.9145145 -2.30740309 -0.897356987 +v 11.4971733 -1.63132906 -0.683526993 +v 10.1507912 -2.96339893 -0.553018987 +v 9.52041054 -1.11213803 -0.44005999 +v 7.53227806 -2.62286401 -0.281776011 +v 9.17799854 -3.20089197 -0.456515998 +v 7.11939096 -1.09715402 -0.215669006 +v 5.13839483 -1.72393596 -0.0929140002 +v 15.6835155 -4.85057497 -1.45544195 +v 13.7792053 -4.21426296 -1.09942698 +v 11.9057245 -3.43528008 -0.790982008 +v 12.2174044 -4.8264122 -0.901234984 +v 10.5060244 -5.4221139 -0.71419102 +v 8.80543709 -5.00833797 -0.503000975 +v 5.4364109 -3.28254104 -0.154492006 +v 7.29063702 -4.32694101 -0.331315011 +v 6.168437 -5.44653511 -0.308795989 +v 13.7548494 -6.21399784 -1.21724606 +v 12.8568745 -7.65973711 -1.19848096 +v 15.2558689 -7.73570013 -1.59149504 +v 15.6119537 -5.97117901 -1.51545501 +v 11.5215149 -6.68387699 -0.929253995 +v 8.65897083 -6.49530697 -0.588458002 +v 9.47611713 -7.61034584 -0.762938023 +v 6.613276 -7.09380102 -0.460979998 +v 5.26470184 -6.79326677 -0.34888199 +v 5.19218206 -8.30712223 -0.476339012 +v 4.16771984 -5.745152 -0.215999007 +v 3.33352709 -7.41615915 -0.308838993 +v 2.21434402 -6.04320288 -0.169577003 +v 1.179497 -5.78369617 -0.135966003 +v 0.831822991 -7.50329924 -0.263597012 +v 0.227923006 -4.75573206 -0.0690409988 +v -0.382685989 -6.78902817 -0.205317006 +v -2.49471092 -7.21869421 -0.282615989 +v -2.81189489 -6.52072001 -0.240222007 +v -4.29723597 -7.27742815 -0.365395993 +v -4.80641794 -8.4359417 -0.496362001 +v -1.46493399 -5.68212605 -0.143261001 +v 13.1180115 -9.50669956 -1.42211103 +v 14.7760286 -8.6173296 -1.59463298 +v 14.4625549 -9.85966778 -1.677302 +v 11.2358007 -8.59878063 -1.06310904 +v 10.9477568 -10.4784126 -1.23498499 +v 9.71778965 -9.53539753 -0.982052982 +v 7.90276003 -8.63293839 -0.704589009 +v 8.22870255 -10.2505512 -0.913016021 +v 6.79298306 -8.90179825 -0.642383993 +v 5.3426342 -9.83947372 -0.646700978 +v 3.78415608 -8.89529133 -0.466475993 +v 2.3529911 -9.10283947 -0.439485013 +v 3.72947311 -11.0496683 -0.710942984 +v 1.27381206 -9.57064247 -0.47251001 +v 0.142765 -10.8763618 -0.620083988 +v -0.651701987 -9.45103931 -0.456135005 +v -1.02323997 -8.04366016 -0.319507986 +v -2.79266095 -8.991189 -0.458166987 +v -2.55993104 -10.8328342 -0.661082983 +v -4.34807205 -10.4458561 -0.689980984 +v -5.88332987 -9.6232872 -0.692873001 +v -7.484828 -9.57244778 -0.816106021 +v -8.72431755 -9.30804253 -0.908766985 +v -9.132164 -10.5111923 -1.09033799 +v -6.78611279 -8.25774479 -0.619984984 +v 14.2080889 -11.0240154 -1.77668202 +v 13.6791382 -12.3169355 -1.86837101 +v 12.2325706 -10.9567833 -1.467273 +v 15.7889662 -11.9361687 -2.17519903 +v 11.2743425 -12.3537388 -1.52782595 +v 8.12127972 -12.2961245 -1.17094398 +v 9.3774786 -11.414135 -1.17511594 +v 6.16063499 -10.8844967 -0.823018014 +v 6.61492491 -12.1416683 -1.02451205 +v 4.71689081 -12.7418537 -0.992026985 +v 1.58925903 -11.4008837 -0.698890984 +v -0.0282029994 -12.8803921 -0.896893978 +v 2.14763999 -12.6837034 -0.888661981 +v -1.28842795 -11.7029829 -0.742304981 +v -3.2696929 -12.6827869 -0.940001011 +v -4.56699991 -12.5195017 -0.980868995 +v -5.82995701 -11.477664 -0.915288985 +v -6.95950079 -10.7322731 -0.907122016 +v -6.8839221 -12.5366278 -1.14418495 +v -8.48483944 -11.6203537 -1.165573 +v -10.2188606 -12.0131245 -1.41388404 +v 14.1495399 -13.4361181 -2.11444592 +v 14.2654743 -14.8009682 -2.36131001 +v 12.4209614 -13.9161148 -1.92639399 +v 12.6854706 -15.4711599 -2.23499608 +v 16.0683537 -13.3547621 -2.43867898 +v 10.9072762 -13.8236208 -1.70759106 +v 7.81717777 -14.5004549 -1.48981595 +v 9.25040054 -13.4314203 -1.453632 +v 9.53606701 -15.1670218 -1.77446902 +v 6.47152996 -13.8291168 -1.27009106 +v 4.48588991 -14.2855511 -1.22335398 +v 6.04056501 -15.3985252 -1.50902104 +v 3.1284709 -13.324975 -1.01341701 +v 2.43380809 -15.6314917 -1.38133705 +v 4.46839905 -15.8333197 -1.49492598 +v 0.273099005 -14.4017954 -1.13891494 +v 1.54110599 -14.5590754 -1.175174 +v -1.47141504 -13.631835 -1.03013206 +v -3.04352689 -14.0964565 -1.15144801 +v -5.11445713 -15.0214977 -1.41477501 +v -3.0326519 -15.5482044 -1.40330803 +v -5.5099659 -13.7838907 -1.23269296 +v -7.06221914 -14.5538101 -1.47998095 +v -8.78705788 -13.3347883 -1.44479203 +v -8.69752026 -14.9034023 -1.69600797 +v -10.6589603 -14.2267332 -1.80927503 +v -12.5306511 -14.1244488 -2.056072 +v -14.1781635 -14.5053167 -2.38503003 +v -14.6464787 -15.5542402 -2.65398693 +v -13.7482996 -12.6380434 -2.01424289 +v -12.3521795 -12.5083418 -1.77405202 +v 16.0604229 -16.8708019 -3.07112789 +v 14.6395483 -16.1598587 -2.67446089 +v 16.9028034 -17.6134052 -3.38749003 +v 14.6431408 -17.6808052 -2.9814899 +v 16.0443554 -15.3315086 -2.77205205 +v 12.8288946 -16.8999748 -2.52873492 +v 10.9868879 -16.089653 -2.116009 +v 9.57514381 -17.0131741 -2.13050604 +v 11.5168943 -18.1742439 -2.60813594 +v 7.74642515 -16.0791111 -1.76725304 +v 8.06365013 -17.6512318 -2.10678101 +v 5.5719471 -17.004858 -1.78217101 +v 5.56963921 -18.2187614 -2.033674 +v 0.631785989 -16.077776 -1.43989396 +v 1.90420306 -17.627079 -1.75854897 +v -0.0286689997 -17.6903133 -1.75847602 +v 3.72245002 -16.6914368 -1.627599 +v 3.734725 -18.1843166 -1.93319702 +v 4.3674612 -19.7308903 -2.30876207 +v 2.96279812 -19.291111 -2.14994097 +v 3.07927895 -20.701889 -2.48856711 +v -1.02346504 -15.9096861 -1.41557002 +v -2.53091311 -16.7860413 -1.62002695 +v -1.51742399 -18.0206852 -1.84599304 +v -4.32347202 -16.6732712 -1.67729199 +v -3.07970905 -18.1298237 -1.91672802 +v -4.71279907 -18.1234818 -1.99502003 +v -4.45513582 -19.4490376 -2.27443194 +v -2.89986992 -20.0301304 -2.33812308 +v -1.39594698 -19.0287819 -2.06301188 +v -5.87304688 -16.3618145 -1.71368301 +v -6.62253618 -17.6965008 -2.03812099 +v -7.98806 -16.1757908 -1.85610402 +v -9.17375565 -17.5915165 -2.263134 +v -10.4517422 -16.1695042 -2.1308949 +v -11.6243258 -17.585989 -2.57217407 +v -12.1042833 -15.7680807 -2.28181195 +v -12.9912767 -16.0091133 -2.46087408 +v -14.6535149 -16.9960899 -2.93505907 +v -15.9583302 -16.5150452 -3.08253503 +v -15.6069365 -18.2095776 -3.36613488 +v 16.2621136 -18.6075268 -3.47795796 +v 16.6745262 -20.4782295 -3.99699211 +v 14.8774519 -19.8416424 -3.50678301 +v 17.5993748 -18.8581657 -3.80418611 +v 17.850338 -20.8660641 -4.33886719 +v 13.4275093 -18.8743057 -3.04164505 +v 13.1623735 -20.1884518 -3.30738211 +v 11.6950455 -19.5935345 -2.95293498 +v 9.93765736 -20.4550438 -2.9351449 +v 9.73602676 -18.8649616 -2.54165101 +v 5.80282211 -19.496418 -2.33504605 +v 7.39765978 -18.5442734 -2.24153304 +v 8.08023167 -19.7743797 -2.57905602 +v 5.42797279 -20.9180241 -2.65249395 +v 4.27051878 -21.4665127 -2.7285471 +v 7.04268694 -20.8697968 -2.75446105 +v -5.93416023 -19.6743565 -2.42195892 +v -7.90481615 -19.1019268 -2.45755196 +v -8.21887302 -20.8767223 -2.91094303 +v -10.3832998 -19.0015488 -2.71259093 +v -11.152276 -20.5581703 -3.18227506 +v -13.5507517 -18.4678707 -3.05543399 +v -12.3468752 -19.7095242 -3.14847708 +v -13.7104111 -20.5152397 -3.56263995 +v -16.7999268 -19.0956688 -3.80140495 +v -15.2460957 -19.6939411 -3.63519692 +v 15.5391617 -21.1278267 -3.94470596 +v 15.765173 -22.3612633 -4.30993414 +v 13.3334846 -21.6337471 -3.6962781 +v 13.9225082 -23.1135597 -4.19176197 +v 8.55993462 -21.9700451 -3.17293191 +v 10.1352072 -22.430582 -3.46691298 +v 11.5374537 -20.8967514 -3.24591994 +v 12.0021276 -22.4090767 -3.70304608 +v 9.09270191 -23.4783669 -3.63975191 +v 7.62714577 -22.9169559 -3.34037304 +v 10.7943058 -23.8013535 -3.92784405 +v 10.251894 -25.0662937 -4.23714018 +v 12.4150047 -24.4856415 -4.35124493 +v -9.81721878 -22.1631222 -3.42026997 +v -9.7818737 -20.7111073 -3.04444504 +v -8.24162102 -22.850605 -3.4308269 +v -6.43650389 -20.7334423 -2.71496296 +v -9.05435658 -24.1277161 -3.87957501 +v -7.30040693 -23.1729889 -3.43012309 +v -8.53522491 -24.3704605 -3.89513898 +v -11.8511553 -22.120573 -3.68104911 +v -10.338089 -23.5482578 -3.86586595 +v -11.8393335 -24.4337502 -4.32958889 +v -10.1477852 -25.2198048 -4.33750916 +v -13.3572903 -21.8395214 -3.84007907 +v -15.1118946 -21.2683086 -3.99934196 +v -17.0373154 -22.341711 -4.66399002 +v -16.7784786 -20.7633591 -4.19853306 +v -15.4640741 -23.1780949 -4.58099604 +v 14.1758385 -24.5853386 -4.66160011 +v 15.595583 -23.7772217 -4.67591619 +v 15.7133503 -25.3328609 -5.16469097 +v 17.1490784 -23.6643124 -4.948174 +v 14.3139048 -25.9189262 -5.09661198 +v 12.6720552 -26.1794319 -4.91265583 +v 15.8824844 -26.8484344 -5.68291998 +v 17.0790863 -26.9030724 -5.94176579 +v -13.3074675 -24.0089626 -4.43239307 +v -14.3843365 -23.1338081 -4.36844397 +v -15.0474091 -24.6123276 -4.92084312 +v -16.5027828 -25.0028305 -5.324646 +v -14.0328264 -25.9082203 -5.13549185 +v -12.1430159 -26.1432114 -4.90173197 +v -15.575489 -26.0824089 -5.478302 +v -16.2121468 -26.9157352 -5.87530994 +v -6.34078789 -22.5878983 -3.18717003 +v -4.894907 -21.893671 -2.89973402 +v -6.69215107 -23.388195 -3.43773198 +v -3.45876193 -21.593998 -2.7494359 +v -3.87467408 -20.4591408 -2.48303008 +v -1.00711501 -20.2852917 -2.34737992 +v -12.5227528 -26.8569412 -5.19280005 +v -14.0195255 -27.612812 -5.69475985 +v -10.4385462 -25.6468754 -4.50406408 +v -13.0060406 -27.3486919 -5.43563795 +v -8.35952854 -24.4331951 -3.89388609 +v -10.6950436 -25.9752197 -4.64451599 +v -11.2187653 -28.5037479 0.699948013 +v -13.6669645 -29.940876 -0.131308004 +v -8.56880665 -26.8968449 1.72714305 +v -13.7127771 -29.9622478 0.072976999 +v -11.3209925 -28.5562439 0.921891987 +v -6.19265795 -25.439743 2.39766097 +v -3.46832395 -23.8039894 2.97260809 +v -5.7401228 -25.1753883 2.24547696 +v -3.44415808 -23.7958813 2.76684189 +v -17.3482399 -31.4109631 -1.35211599 +v -18.0155087 -31.3232098 -1.41836596 +v -16.52425 -31.282341 -1.110798 +v -17.3952446 -31.4396687 -1.100811 +v -18.0486717 -31.3424416 -1.17210495 +v -16.5864334 -31.2986698 -0.802726984 +v -13.7061977 -29.9630127 0.0475619994 +v -15.3500299 -30.8177299 -0.749695003 +v -19.8008919 -30.2679691 -1.48175097 +v -20.2530174 -29.7181625 -1.41095805 +v -20.2684097 -29.7640247 -1.20224297 +v -19.2645912 -30.7312546 -1.49226701 +v -19.819458 -30.3130112 -1.20050001 +v -19.2830601 -30.76367 -1.22092497 +v -18.6540871 -31.0795383 -1.51050496 +v -20.8961849 -27.3638973 -0.769062996 +v -20.8498077 -26.0773945 -0.349227995 +v -20.8556442 -26.1299896 -0.166802004 +v -20.8119373 -28.3529778 -1.03889298 +v -20.8967953 -27.4362221 -0.492567986 +v -20.8014297 -28.4242992 -0.734569013 +v -20.6000729 -29.16366 -1.01739597 +v -20.5934944 -29.1060333 -1.26609004 +v -19.9277706 -20.5554237 1.33661401 +v -19.458231 -18.7177505 1.920977 +v -20.3375378 -22.5028648 0.761460006 +v -19.9230747 -20.6303711 1.61065996 +v -20.3393593 -22.5765209 1.00027096 +v -20.6557713 -24.4737015 0.423247993 +v -20.8505249 -26.1460381 -0.109108999 +v -20.6535645 -24.3908539 0.181658 +v -17.8050117 -14.641571 2.96417499 +v -17.1037655 -13.6195383 3.24868488 +v -17.1104736 -13.6771851 3.45521688 +v -18.4098091 -15.7896233 2.62790799 +v -18.4173908 -15.8370218 2.805547 +v -17.8036976 -14.7121735 3.21005607 +v -18.9536266 -17.2021294 2.52739 +v -19.4504681 -18.7740383 2.10823989 +v -18.9521656 -17.1250038 2.28372407 +v -13.7016554 -10.7250357 4.24101782 +v -11.5956202 -9.50048256 4.68086386 +v -15.2102461 -11.7603025 3.89643192 +v -11.5919704 -9.57438564 4.92324686 +v -13.6993856 -10.7987356 4.48682308 +v -15.2073574 -11.8515825 4.15578699 +v -16.2948456 -12.7672958 3.79602098 +v -16.2903843 -12.689394 3.55245495 +v -2.48929405 -4.88485622 5.7598052 +v 0.140224993 -3.59916806 5.8825202 +v -5.51969624 -6.37996483 5.55592394 +v 0.170918003 -3.64229894 6.07832384 +v -2.41662788 -4.92403221 6.00936222 +v -5.49605417 -6.479352 5.85132408 +v -8.71817112 -8.0595808 5.39198685 +v -11.592042 -9.57417393 4.9228878 +v -8.71321487 -7.98125792 5.14838886 +v 2.89953494 -2.08475709 5.86903477 +v 2.96444106 -1.95768499 5.85500908 +v 3.01082611 -1.97552299 6.06374884 +v 2.56330395 -2.32499909 5.86888695 +v 2.95973396 -2.1183691 6.11723185 +v 2.61484408 -2.36465597 6.111516 +v 0.170955002 -3.64234591 6.07847118 +v 1.71536303 -2.79432201 5.87498522 +v 3.00277996 -1.58166099 5.87516689 +v 2.948946 -1.41632903 5.87212896 +v 3.01630807 -1.46169496 6.0731492 +v 3.01833606 -1.70542002 5.89970398 +v 3.06790709 -1.57490802 6.12292624 +v 3.0805459 -1.70544505 6.11475801 +v 3.00275302 -1.83196104 5.87061119 +v 2.75049901 -1.21155596 5.88153887 +v 2.71744609 -1.15081298 6.08570099 +v 2.87010098 -1.301579 5.91042614 +v 2.83771491 -1.18622398 6.1626358 +v 2.94978499 -1.31128001 6.10698795 +v 1.22229397 -1.29059994 5.91424894 +v -0.550769985 -1.56671906 5.93036318 +v 2.18791795 -1.16075301 5.92934895 +v -0.549992979 -1.52208698 6.13356876 +v 1.24702203 -1.22415304 6.19195223 +v 2.19951606 -1.10247898 6.16672802 +v 2.60763097 -1.09181595 6.14372778 +v 2.58155894 -1.14475095 5.89480114 +v -10.5277166 -3.19489408 5.24257088 +v -13.8858309 -3.63618708 4.92380714 +v -13.8623886 -3.65999794 4.77353716 +v -6.92281818 -2.62565207 5.63943577 +v -10.5691614 -3.14501691 5.49055004 +v -6.93305206 -2.54111695 5.94173193 +v -3.45124698 -1.99818504 6.08210707 +v -0.549995005 -1.52216494 6.13333988 +v -3.50009894 -2.06233811 5.82957315 +v -19.9199562 -3.84324694 3.62083197 +v -21.2557468 -3.55446911 3.30927396 +v -21.2941208 -3.5213511 3.51791191 +v -18.3734341 -3.96519494 3.97525907 +v -19.9664459 -3.7944591 3.86830902 +v -18.4086437 -3.91823912 4.1917038 +v -13.9012899 -3.60886097 5.02374601 +v -16.4133892 -3.911901 4.33118582 +v -25.0983543 -1.80080402 2.36888599 +v -26.3982239 -0.921649992 2.00525808 +v -23.836174 -2.52671194 2.72996998 +v -26.4384232 -0.872572005 2.25780201 +v -25.1462078 -1.75419295 2.58739209 +v -23.8669529 -2.48294592 2.94750404 +v -22.5952263 -3.06832504 3.26804399 +v -22.5577126 -3.11426902 3.01903796 +v -30.2038708 2.28074908 0.741362989 +v -31.1913681 3.30851007 0.369423985 +v -29.0424728 1.19850504 1.18824697 +v -31.2282639 3.35146809 0.609804988 +v -30.2481823 2.33082604 0.989175022 +v -29.0739536 1.26343799 1.49265695 +v -27.761795 0.130927995 1.81762695 +v -26.4343319 -0.881922007 2.22193694 +v -27.7431049 0.105645001 1.59144998 +v -32.6474876 5.68322277 -0.306665003 +v -32.7465439 6.38413095 -0.408721 +v -32.7821236 6.40124083 -0.197965994 +v -32.3822021 4.96963596 -0.132955 +v -32.6820259 5.71287489 -0.0542359985 +v -32.4086876 5.00048304 0.0878980011 +v -31.9013119 4.189538 0.0597760007 +v -32.2624207 8.4956913 -0.392062008 +v -31.8742886 9.10272026 -0.266867995 +v -32.5469475 7.82309914 -0.438659996 +v -32.2974243 8.49669266 -0.167300001 +v -31.9101181 9.08207512 -0.0208810009 +v -32.5858688 7.82582188 -0.161844 +v -32.7136688 7.11098909 -0.437858999 +v -29.3088188 10.393239 0.457621008 +v -27.3517704 10.7369862 1.07241297 +v -30.5875893 10.0238781 0.0867919996 +v -27.3929977 10.7200756 1.25016201 +v -29.3685608 10.3633051 0.735282004 +v -30.6344433 9.99980736 0.304048985 +v -31.4190655 9.58193874 0.0753299966 +v -31.3684006 9.60114956 -0.174733996 +v -15.1075125 11.9644537 3.87309289 +v -21.1902161 11.3991604 2.84643698 +v -21.1859589 11.4060535 2.66457391 +v -15.1330996 11.9350119 4.13537693 +v -18.027071 11.6722565 3.60271502 +v -24.5387764 11.0530043 2.11501503 +v -27.4044495 10.7107153 1.30665505 +v -24.4646931 11.0826674 1.87210298 +v -11.6529608 12.5114374 4.29874992 +v -11.3651133 12.693284 4.3502841 +v -12.22118 12.3443079 4.2731452 +v -11.6574726 12.493228 4.55558777 +v -11.3535223 12.6726427 4.59915304 +v -12.2256746 12.3124733 4.54674816 +v -15.1311932 11.9399872 4.10929489 +v -13.2960749 12.168787 4.11502314 +v -10.2395363 14.3774729 4.19632101 +v -9.45063591 16.0580864 4.04143 +v -10.7573586 13.400279 4.31267214 +v -9.43552113 16.0656242 4.22648382 +v -10.2211905 14.3720913 4.48143578 +v -10.7274113 13.3858891 4.62396002 +v -11.0861044 12.9062004 4.5708251 +v -11.0956221 12.9140387 4.31540012 +v -5.55041504 24.7700977 2.35852289 +v -4.29132986 27.4328995 1.66960895 +v -6.93013287 21.7233505 3.0787921 +v -4.26557302 27.4640274 1.871876 +v -5.5112319 24.8207455 2.606143 +v -6.88288689 21.7402153 3.38753295 +v -9.43556499 16.0656338 4.22618818 +v -1.98852098 30.916399 0.578216016 +v -1.39087796 31.2264347 0.465779006 +v -1.38049805 31.2602253 0.677568018 +v -2.6088469 30.3075657 0.802031994 +v -1.965855 30.9517498 0.832005024 +v -2.58329105 30.3362465 1.02306604 +v -4.26566792 27.4639797 1.87147701 +v -3.34688306 29.2015114 1.12132394 +v 0.638122022 31.4181423 0.425862998 +v 1.29696798 31.2386379 0.528487027 +v -0.0454620011 31.4869385 0.431019992 +v 0.630199015 31.4480934 0.681055009 +v 1.274979 31.2596817 0.774671018 +v -0.048246 31.5074596 0.704719007 +v -0.731317997 31.4186134 0.422271013 +v 3.24813008 29.1996403 1.14558196 +v 4.18741798 27.4329243 1.70030403 +v 2.51357388 30.3049507 0.821608007 +v 4.16095686 27.4636745 1.89871705 +v 3.21154594 29.2395229 1.42579305 +v 2.48432112 30.336256 1.04084504 +v 1.86840999 30.951704 0.845197976 +v 1.89332902 30.9161396 0.593428016 +v 9.33081818 16.0566883 4.08767986 +v 9.3203516 16.0669174 4.25787687 +v 6.81886101 21.7169533 3.12938404 +v 8.09222794 18.7851582 3.9430759 +v 6.77567291 21.7557354 3.36835909 +v 5.40083408 24.8201561 2.64542794 +v 4.15560913 27.4669056 1.92867696 +v 5.44548702 24.761404 2.40047598 +v 10.9718304 12.9146271 4.3932972 +v 11.2359304 12.6925688 4.37847614 +v 11.2369432 12.6871729 4.59311295 +v 10.6312695 13.4052467 4.38951683 +v 10.9612532 12.9062557 4.64969683 +v 10.616312 13.3967648 4.61322784 +v 10.1149597 14.3821115 4.26946306 +v 13.1748552 12.169035 4.21045303 +v 15.0011845 11.9640026 3.97422194 +v 12.0937891 12.3455534 4.36174011 +v 15.0215006 11.9441023 4.18477488 +v 13.1845675 12.141696 4.49473715 +v 12.0996361 12.3037243 4.67148113 +v 11.5325928 12.4933395 4.63842201 +v 11.5290327 12.5117846 4.38297987 +v 24.4278927 11.0786724 2.029037 +v 27.3225327 10.7355728 1.24498498 +v 21.0948009 11.4060211 2.86322498 +v 27.3743954 10.709362 1.48113203 +v 24.4912376 11.0499783 2.27549911 +v 21.1683159 11.3548365 3.15595603 +v 17.9404793 11.6696844 3.72655392 +v 15.0214844 11.9441624 4.18446016 +v 31.351265 9.61386871 0.0217579994 +v 31.8437939 9.11770535 -0.118020996 +v 31.8857136 9.11188602 0.0923369974 +v 30.5732079 10.0300322 0.277990997 +v 31.4002075 9.59487247 0.273499995 +v 30.6158619 10.0070448 0.49722299 +v 27.3652611 10.719346 1.43124497 +v 29.2911701 10.3949347 0.642533004 +v 32.7091904 7.13774204 -0.236488998 +v 32.7480164 6.41226721 -0.209134996 +v 32.7822685 6.42935324 0.00224200008 +v 32.5447769 7.8484292 -0.207341999 +v 32.7430649 7.14879799 0.0460699983 +v 32.5705261 7.85157824 0.0143139996 +v 32.2479973 8.51764774 -0.189363003 +v 31.4789314 3.80339599 0.425132006 +v 30.1105499 2.31399393 0.977714002 +v 32.2833519 4.88913202 0.107632004 +v 30.1393204 2.35165596 1.21066296 +v 31.5129032 3.84539604 0.705730975 +v 32.3097954 4.92267704 0.327775002 +v 32.6871338 5.73936892 0.144712999 +v 32.6544914 5.70950794 -0.107543997 +v 20.8795319 -6.15373611 3.42707109 +v 25.5053692 -2.02493811 2.4280479 +v 20.8859253 -6.13705683 3.62298012 +v 23.0629025 -4.19608307 3.23850894 +v 25.5272541 -1.96721399 2.73466396 +v 28.0271435 0.30714801 1.93001294 +v 30.137331 2.34565306 1.17994201 +v 27.9840374 0.256572992 1.68326402 +v 18.3704891 -8.66266155 3.7299149 +v 18.2415714 -8.9825201 3.7643981 +v 18.7382736 -8.19052315 3.72685504 +v 18.3667889 -8.66070843 3.98685098 +v 18.2231407 -8.98777866 4.01297903 +v 18.7203579 -8.17510891 4.03967285 +v 19.511467 -7.39805079 3.86104393 +v 20.8852959 -6.13335419 3.65381408 +v 19.5149288 -7.40407324 3.60461807 +v 18.2945042 -11.0015697 3.48989606 +v 18.6153965 -12.8169022 3.1991179 +v 18.1542702 -9.91158009 3.67356491 +v 18.6062069 -12.8508768 3.438519 +v 18.2799816 -11.0247335 3.77472591 +v 18.1332474 -9.93489933 3.94539809 +v 18.1397762 -9.333004 3.96521401 +v 18.1488972 -9.31838894 3.71000099 +v 20.2963371 -22.1385307 0.990288019 +v 20.6955051 -25.0162048 0.139116004 +v 19.7520809 -18.8769989 1.89857304 +v 20.6930485 -25.0769367 0.371365994 +v 20.2947769 -22.2100735 1.23618305 +v 19.7253914 -18.9266148 2.20683694 +v 18.6063251 -12.8508272 3.43825293 +v 19.1589298 -15.6667099 2.62100101 +v 20.618187 -29.1923294 -1.17403305 +v 20.2844296 -29.811924 -1.26712203 +v 20.8162899 -28.3251858 -0.933358014 +v 20.6239471 -29.2490463 -0.923793018 +v 20.2872391 -29.8516769 -1.02103496 +v 20.8188152 -28.3912392 -0.661692977 +v 20.6929646 -25.076664 0.370173991 +v 20.8527393 -27.0018921 -0.513795018 +v 18.679369 -31.1503201 -1.42573202 +v 18.0308266 -31.3811512 -1.38785005 +v 18.0640144 -31.4138966 -1.178141 +v 19.2870407 -30.8047409 -1.40401804 +v 18.7102947 -31.1856346 -1.14453304 +v 19.3073578 -30.8350983 -1.13183498 +v 19.8255424 -30.34268 -1.38955104 +v 15.3641911 -30.882864 -0.680565 +v 13.6694403 -30.0004978 -0.0689110011 +v 16.5445404 -31.3509026 -1.03538704 +v 13.7124624 -30.0221195 0.129637003 +v 15.4221668 -30.9100437 -0.402532995 +v 16.5907135 -31.3717003 -0.817591012 +v 17.4199848 -31.5100555 -1.02352405 +v 17.3738194 -31.4812927 -1.27377498 +v 5.95968008 -25.3744011 2.21729803 +v 3.35061407 -23.8071213 2.78710699 +v 8.521945 -26.9328728 1.77241504 +v 3.37235093 -23.8138924 2.97793198 +v 11.2996254 -28.6050682 0.977840006 +v 13.7126732 -30.0221272 0.130425006 +v 11.2204828 -28.5651741 0.737121999 +v 0.278678 -22.2317848 3.22588801 +v -0.057395 -22.2058315 3.217417 +v -0.0582650006 -22.2147846 3.43223691 +v 0.827049017 -22.4296799 3.2027719 +v 0.285778999 -22.233923 3.48317599 +v 0.843685985 -22.4154472 3.51606202 +v 3.37366796 -23.8152714 2.9956491 +v 1.78723502 -22.913168 3.04178095 +v -1.889395 -22.9064102 3.03076911 +v -0.934660971 -22.4262695 3.19792891 +v -3.47267103 -23.8029652 3.00408506 +v -1.90722799 -22.9100494 3.28667808 +v -0.954298019 -22.4242878 3.44301009 +v -0.402691007 -22.2339306 3.48061991 +v -0.392852992 -22.2316322 3.22429609 +v -13.7660732 -29.936945 0.261628002 +v -11.394248 -28.5400448 1.07829797 +v -8.6230402 -26.8603172 1.92859197 +v -5.82020378 -25.1399078 2.6925981 +v -3.49352598 -23.7816391 3.1114161 +v -18.0659046 -31.2918167 -0.937085986 +v -17.4232121 -31.4114037 -0.933233023 +v -15.4077663 -30.8437748 -0.471240997 +v -13.749608 -29.948164 0.207339004 +v -20.2341633 -29.7775574 -0.946653008 +v -19.7738266 -30.2867203 -0.942816019 +v -19.2665386 -30.7375565 -1.036026 +v -18.6822243 -31.0694695 -0.999459028 +v -18.685442 -31.1151314 -1.22998905 +v -20.8010635 -26.1950512 0.0742949992 +v -20.808897 -27.4826946 -0.249651998 +v -20.5642586 -29.180603 -0.851637006 +v -19.4225578 -18.817564 2.2269299 +v -19.8300304 -20.717762 1.84225202 +v -20.2744846 -22.6483154 1.21098495 +v -20.6115208 -24.5305271 0.578974009 +v -20.7958927 -26.1953354 0.0761979967 +v -17.0633755 -13.7850866 3.68657398 +v -18.3796062 -15.9227896 3.0282371 +v -17.7508717 -14.7921209 3.39067602 +v -18.8784313 -17.3040237 2.75860691 +v -19.3965626 -18.8422756 2.28230405 +v -11.5453796 -9.69548035 5.12284184 +v -13.6543398 -10.916193 4.68492603 +v -15.1644869 -11.9487839 4.31157589 +v -16.2625961 -12.8497934 3.94691396 +v 0.229914993 -3.71603489 6.25292683 +v -2.33355308 -4.98273182 6.16069317 +v -8.69691753 -8.15003109 5.53931713 +v -11.5536804 -9.67645168 5.096138 +v 3.08267808 -2.00054312 6.21612215 +v 3.0379281 -2.16099811 6.26298618 +v 3.19652104 -2.03836107 6.36234617 +v 2.71455693 -2.43809891 6.30881214 +v 1.78206098 -2.84315205 6.14953089 +v 0.229702994 -3.71574593 6.2526741 +v 3.05908108 -1.37561297 6.22364283 +v 3.17282391 -1.70488298 6.27792501 +v 3.15506911 -1.56181896 6.27241516 +v 3.19373608 -1.39468002 6.37415314 +v 3.07951808 -1.84430802 6.14493895 +v 2.76949 -1.06895101 6.28480101 +v 2.98482895 -1.20790601 6.2913661 +v 2.93670797 -1.07027805 6.38041878 +v -0.546274006 -1.43627501 6.31164694 +v 1.28831899 -1.07400799 6.41297102 +v 2.23575592 -0.983700991 6.37033987 +v 2.64015603 -1.01474297 6.29348612 +v -13.9205332 -3.55240297 5.14649105 +v -10.6061792 -3.06670094 5.64194489 +v -3.41555905 -1.90859699 6.23771811 +v -0.547430992 -1.46293795 6.26468086 +v -21.3127518 -3.45867205 3.67526698 +v -18.4328594 -3.83724999 4.35961199 +v -19.990736 -3.684093 4.07377481 +v -21.3195362 -3.35336304 3.83214307 +v -16.4540081 -3.853899 4.6074729 +v -13.9331675 -3.4945941 5.22689581 +v -25.1453075 -1.68197298 2.77735996 +v -23.875658 -2.40677691 3.11891389 +v -26.4347897 -0.791188002 2.43678904 +v -22.6053581 -2.96190405 3.47668695 +v -31.2106819 3.41591501 0.796030998 +v -30.2434502 2.39650798 1.14895797 +v -27.7731438 0.212203994 2.00349808 +v -26.438736 -0.81032002 2.405231 +v -32.7632217 6.42068577 0.0221650004 +v -32.3890266 5.04037905 0.270824999 +v -32.658577 5.74291182 0.115202002 +v -31.9383888 4.23243904 0.338982999 +v -31.2300911 3.35879803 0.640073001 +v -32.2889786 8.46669865 0.0333920009 +v -31.8924198 9.03063202 0.181862995 +v -32.5487595 7.80674314 0.0477470011 +v -32.6985741 7.12067795 0.0744939968 +v -32.7494049 7.12131596 -0.155128002 +v -27.4189224 10.6781693 1.40769303 +v -30.6446381 9.94524193 0.484212995 +v -29.3792839 10.2527199 0.977975011 +v -31.420866 9.53399754 0.241357997 +v -21.2413025 11.3407221 3.04880095 +v -17.9004898 11.6080055 3.81880999 +v -15.1299849 11.8190374 4.37440014 +v -24.5753689 10.9878807 2.27067304 +v -27.4257507 10.6358747 1.49417603 +v -11.3047075 12.5948324 4.82185507 +v -11.6379328 12.4394398 4.7194581 +v -12.1989651 12.2151232 4.76446676 +v -13.3028297 12.1422033 4.39965296 +v -15.1335936 11.8648376 4.29794788 +v -9.38282967 16.0461884 4.4175868 +v -10.1326771 14.3079338 4.72502422 +v -11.0483589 12.8657608 4.73495913 +v -4.20812607 27.4627857 2.06052303 +v -5.44467878 24.8343067 2.76310301 +v -8.21181011 18.786129 3.8839221 +v -9.40031147 16.0536976 4.36581802 +v -1.35227001 31.242403 0.896525979 +v -2.53832603 30.3235455 1.20457196 +v -1.92857695 30.933794 0.999167979 +v -3.31349301 29.2394161 1.40242803 +v -4.22714615 27.4656887 2.00814509 +v 1.23382103 31.2127037 1.00714505 +v 0.611069024 31.4057846 0.910448015 +v -0.0489349999 31.4643097 0.888432026 +v -0.72547698 31.4484882 0.705093026 +v 4.1212101 27.4657555 2.037081 +v 2.43796301 30.3235531 1.22238898 +v 3.11622691 29.2074261 1.67114794 +v 4.04767418 27.4439907 2.19702196 +v 1.83010495 30.9340057 1.01168096 +v 9.2770052 16.0537262 4.4329052 +v 8.03081512 18.6492901 4.20071697 +v 9.21610641 16.0240784 4.57099009 +v 6.70191097 21.7385025 3.56256199 +v 5.33299208 24.8341331 2.80190301 +v 4.07595301 27.4550743 2.14575791 +v 11.2056189 12.6366196 4.80683517 +v 10.5725307 13.3602448 4.79177284 +v 10.9221802 12.865715 4.81426287 +v 10.0969677 14.3718338 4.55487919 +v 9.30321407 16.0646477 4.35486221 +v 15.026329 11.8821869 4.37241793 +v 13.1609402 12.0292435 4.73630095 +v 11.5119305 12.4397116 4.80228806 +v 27.3948231 10.6349735 1.66799998 +v 24.5278282 10.9844522 2.43154407 +v 17.8281498 11.617116 3.91202402 +v 15.0263157 11.8821211 4.37237787 +v 31.8853474 9.07118988 0.310382009 +v 30.6252441 9.95239162 0.676949978 +v 31.4013844 9.54674053 0.439016014 +v 29.3469353 10.3655462 0.920772016 +v 27.3883286 10.6715918 1.59008706 +v 32.7619247 6.44792509 0.221802995 +v 32.551487 7.83873987 0.200588003 +v 32.670845 7.13672495 0.333609015 +v 32.2867928 8.51299763 0.0925429985 +v 32.2878647 4.96064281 0.51049298 +v 30.1078491 2.4232161 1.43091404 +v 31.468092 3.91507101 0.927137017 +v 32.6623497 5.76805305 0.312323987 +v 20.8677902 -6.1084199 3.76294398 +v 22.9604988 -4.20029783 3.44694901 +v 20.8162441 -6.05144882 3.92762995 +v 28.0184402 0.369991004 2.09078097 +v 30.1232643 2.39929295 1.37005198 +v 18.1408653 -8.96681023 4.23849201 +v 18.3215237 -8.63853931 4.15325308 +v 19.4533558 -7.35020018 4.08405304 +v 20.8459301 -6.08271885 3.84637904 +v 18.5424118 -12.8776026 3.6277051 +v 18.1892319 -11.0420008 3.99213004 +v 18.0191936 -9.94070339 4.19991922 +v 18.0873032 -9.33224964 4.131217 +v 20.6242428 -25.1258678 0.58740598 +v 20.2487488 -22.2611179 1.39161396 +v 19.1399536 -15.6747179 2.90998602 +v 20.2534332 -29.8531742 -0.853161991 +v 20.5875111 -29.2655182 -0.758316994 +v 20.7578278 -28.4172726 -0.461975992 +v 20.851078 -27.0690079 -0.237093002 +v 20.6247406 -25.1255054 0.585584998 +v 18.0876236 -31.3858814 -0.921168983 +v 18.7035217 -31.1312408 -0.885340989 +v 19.2900715 -30.8092232 -0.947863996 +v 19.8037319 -30.366003 -0.879923999 +v 19.8435268 -30.3866615 -1.10902095 +v 13.7490854 -30.0087662 0.267652005 +v 16.6273041 -31.3441486 -0.635172009 +v 15.4899855 -30.8412971 -0.153192997 +v 13.7944889 -29.9653854 0.409359008 +v 17.4471207 -31.4816856 -0.85482198 +v 3.390625 -23.8020554 3.10118294 +v 5.7420702 -25.1765079 2.69751501 +v 3.43808103 -23.7312241 3.29679108 +v 8.57567215 -26.8966808 1.97426498 +v 11.3883018 -28.5861931 1.15869904 +v 13.7651453 -29.9977818 0.322127998 +v -0.0590439998 -22.1680851 3.64874101 +v 0.296290994 -22.1878586 3.65066791 +v 1.83888996 -22.8363132 3.54844904 +v 1.80088198 -22.9118366 3.32733798 +v 3.40907788 -23.7787437 3.18746305 +v -3.51365399 -23.7543831 3.19665694 +v -1.943398 -22.8320293 3.53835893 +v -0.981139004 -22.3539734 3.66932297 +v -0.416842014 -22.1652412 3.70491195 +v -11.4401464 -28.4646034 1.24106801 +v -13.8414907 -29.839756 0.465884 +v -8.68238354 -26.7849274 2.09453511 +v -5.93264914 -25.071516 2.8603909 +v -3.53523707 -23.719677 3.27563 +v -17.4437408 -31.3441048 -0.763773024 +v -16.6503906 -31.1885891 -0.518935978 +v -18.0592918 -31.1464291 -0.704050004 +v -15.523901 -30.6743164 -0.072333999 +v -15.4771271 -30.7748852 -0.222610995 +v -13.8028107 -29.8965359 0.368292004 +v -19.2279587 -30.6765289 -0.862167001 +v -20.1045876 -29.7197781 -0.674094975 +v -19.7107372 -30.2282524 -0.784107983 +v -18.6560688 -30.9948463 -0.835672975 +v -20.6788826 -28.4470139 -0.455864012 +v -20.6282539 -26.2531853 0.318695009 +v -20.7021561 -27.4951382 -0.104859002 +v -20.4970036 -29.1749058 -0.684179008 +v -19.2963181 -18.9217091 2.44029689 +v -19.7178326 -20.7732887 1.97435296 +v -20.1716156 -22.7045345 1.35889494 +v -20.5279064 -24.5699444 0.733765006 +v -20.6924324 -26.2370663 0.247204006 +v -17.6757641 -14.8919687 3.52395105 +v -18.2558002 -16.0502415 3.23339009 +v -16.9169693 -13.9656544 3.89755201 +v -18.759634 -17.4041805 2.90491009 +v -19.2959652 -18.9219532 2.44072104 +v -15.0928488 -12.0679359 4.44295502 +v -11.4440956 -9.88419533 5.29366016 +v -13.5837688 -11.0342693 4.80832481 +v -16.1968708 -12.9480877 4.08434391 +v -2.25952792 -5.10976219 6.31377888 +v 0.340276003 -3.84768295 6.42044687 +v -5.40439701 -6.64166117 6.06305599 +v -8.63124561 -8.25657177 5.67370176 +v -11.4861803 -9.81018734 5.23825312 +v 3.152246 -2.21887994 6.39694786 +v 3.39027095 -2.10263991 6.49897909 +v 2.86585498 -2.54818797 6.46405983 +v 2.02330303 -3.01712799 6.45823193 +v 0.339866012 -3.84718299 6.42016983 +v 1.88527596 -2.93665099 6.34126377 +v 3.27448797 -1.53368294 6.40571308 +v 3.34818792 -1.70177603 6.44392776 +v 3.23548603 -1.86935306 6.35883379 +v 2.9132359 -0.946193993 6.46105623 +v 3.11477995 -1.094064 6.4604249 +v 3.12385511 -1.268062 6.37227583 +v -0.53797698 -1.27240801 6.48702097 +v 2.231004 -0.813417017 6.51735115 +v 2.68930888 -0.902707994 6.43154001 +v -10.6148596 -2.94850302 5.78193378 +v -13.952302 -3.37274003 5.348629 +v -6.95425701 -2.36633801 6.16195202 +v -3.43185592 -1.76989901 6.39729977 +v -0.542361021 -1.35723901 6.41022301 +v -18.4505768 -3.67273402 4.53798199 +v -19.9957581 -3.50858307 4.24402809 +v -21.3041153 -3.16581392 3.99095106 +v -16.4964809 -3.569453 4.94991016 +v -16.48699 -3.71113491 4.83016396 +v -25.1128674 -1.53719103 2.969275 +v -26.4006195 -0.672402024 2.59974599 +v -23.8527012 -2.25423288 3.30671811 +v -22.5811253 -2.81918812 3.6343801 +v -30.1965923 2.47399807 1.30802 +v -31.1486378 3.50760508 0.972980976 +v -29.0264187 1.39740205 1.73435497 +v -27.7265472 0.347521007 2.19984508 +v -26.4114876 -0.705238998 2.55950999 +v -32.595459 5.77469778 0.283832014 +v -32.6931572 6.43759394 0.198035002 +v -32.2979355 5.11129189 0.483956993 +v -32.58395 6.45358896 0.356023997 +v -31.8059082 4.38762808 0.735936999 +v -31.8891659 4.31518793 0.587173998 +v -31.1948452 3.44382405 0.857420981 +v -32.2137032 8.40198612 0.253870994 +v -32.4747314 7.77668715 0.219646007 +v -31.8028564 8.91669559 0.41484201 +v -32.614994 7.10778618 0.236880004 +v -27.4280701 10.5736341 1.59303296 +v -30.6164989 9.8214941 0.691036999 +v -29.354393 10.1355019 1.12184799 +v -27.4119148 10.4209242 1.75337601 +v -31.3927708 9.4601841 0.406349987 +v -17.9345245 11.4764795 3.98328996 +v -21.2490883 11.2103548 3.25373292 +v -15.1023769 11.6470976 4.5524931 +v -24.5698204 10.8896465 2.42736006 +v -27.4249477 10.5169878 1.66306305 +v -11.5947094 12.3552618 4.87538719 +v -11.1997299 12.4388208 5.02397299 +v -12.150527 12.0791998 4.92363501 +v -13.2810879 12.0301056 4.64116192 +v -15.1199608 11.7457743 4.46362495 +v -9.26737309 15.9823246 4.61770821 +v -10.6116505 13.2846861 4.89381409 +v -10.017272 14.1717033 4.90850592 +v -10.9890327 12.7930603 4.89165688 +v -5.363451 24.7919102 2.92239308 +v -4.10819721 27.4214191 2.23919296 +v -6.7511611 21.700758 3.6308229 +v -8.05447388 18.5694599 4.28492498 +v -8.15227127 18.6500626 4.14223003 +v -9.32829475 16.0175972 4.52806282 +v -1.877177 30.8791046 1.16511405 +v -1.31256294 31.1762009 1.06979001 +v -2.44403791 30.2517395 1.41337502 +v -1.26306796 31.073122 1.22357297 +v -3.11034799 29.1354046 1.812819 +v -3.21985698 29.2073822 1.648453 +v -4.15435886 27.4436989 2.16882801 +v -0.0494370013 31.3800621 1.05860698 +v 0.591023028 31.3233166 1.07143402 +v 1.16270006 31.0731583 1.23232198 +v -0.682291985 31.2882195 1.11627305 +v -0.702709973 31.3836021 0.96143502 +v 2.34203696 30.2517014 1.43076396 +v 3.00545692 29.1354237 1.83484304 +v 3.90764809 27.3696251 2.38015103 +v 1.77733696 30.8792458 1.17798698 +v 9.11785889 15.967454 4.71101809 +v 7.93177795 18.5690899 4.34256601 +v 6.59876204 21.6882648 3.71684289 +v 5.25049114 24.7917805 2.96072292 +v 3.94736695 27.3933201 2.33804202 +v 10.8617392 12.7929258 4.97039604 +v 11.1487083 12.5517197 4.96833992 +v 10.4686565 13.2690411 4.99111509 +v 11.0715151 12.4388542 5.10417509 +v 10.0067768 14.3079357 4.79746103 +v 9.11738682 15.9671717 4.71158409 +v 9.2158556 16.023941 4.57130814 +v 15.0066214 11.7221069 4.59612083 +v 12.0559454 12.1707926 4.91498804 +v 13.0774984 11.8697557 4.91767216 +v 11.4674559 12.3551865 4.95856524 +v 24.5210247 10.8867216 2.58748007 +v 27.39328 10.5146103 1.83879197 +v 21.1790237 11.2071695 3.39620209 +v 17.8443909 11.5158529 4.061728 +v 15.0065994 11.7219906 4.59616613 +v 31.3727055 9.47246838 0.603899002 +v 31.8260422 8.98353958 0.527869999 +v 30.5964527 9.8280201 0.883759975 +v 29.3435612 10.240221 1.19543195 +v 27.3974552 10.5882444 1.74688399 +v 32.6905632 6.46374989 0.397581995 +v 32.4610329 7.80065393 0.421288013 +v 32.5807114 6.4782052 0.55448401 +v 32.1623917 8.40567207 0.504674971 +v 32.2408867 8.46186543 0.348825991 +v 32.194519 5.02972507 0.723617971 +v 30.0286617 2.51254296 1.60173702 +v 31.3863926 3.97940493 1.08241296 +v 32.5979843 5.79970694 0.481020004 +v 22.9090328 -4.08763218 3.62013102 +v 25.452549 -1.856305 2.98312712 +v 20.6955585 -5.9395628 4.11351299 +v 27.958395 0.436529011 2.25246596 +v 30.0461159 2.495579 1.57556605 +v 18.2390594 -8.60499191 4.31269217 +v 18.5972137 -8.09691906 4.31453705 +v 17.9671268 -8.91421032 4.44636583 +v 19.331419 -7.29378986 4.26128101 +v 20.7676582 -6.00473499 4.0181222 +v 18.4063396 -12.9052982 3.82730103 +v 18.0718594 -11.016448 4.14231491 +v 17.9986477 -9.3175211 4.28977108 +v 20.1608639 -22.2865582 1.55025399 +v 19.5975323 -18.9820805 2.44925404 +v 20.5068798 -25.1568661 0.755106986 +v 19.0057564 -15.6428776 3.19446802 +v 18.5171165 -12.8851519 3.6822021 +v 20.518816 -29.2600632 -0.590746999 +v 20.1791859 -29.8254738 -0.670138001 +v 20.6655159 -28.4197845 -0.297623008 +v 20.6341419 -27.126358 0.171228006 +v 20.7609692 -27.1117401 0.0067230002 +v 20.507328 -25.156765 0.754616022 +v 19.2506733 -30.7480373 -0.773675025 +v 18.0846329 -31.2382736 -0.645078003 +v 18.6755219 -31.0480194 -0.72635901 +v 19.7473259 -30.3117142 -0.716732979 +v 16.6747475 -31.2397804 -0.420902014 +v 13.8525286 -29.8780441 0.557516992 +v 15.5358849 -30.7408218 -0.00252799992 +v 17.4664803 -31.414711 -0.685908973 +v 5.85760403 -25.0948353 2.89386797 +v 8.6327219 -26.8206139 2.14084101 +v 3.50882506 -23.5867558 3.48529005 +v 11.4263983 -28.4960079 1.32203603 +v 13.8304119 -29.9149361 0.504809022 +v 0.305557996 -22.1028957 3.81183791 +v -0.0597180016 -22.0629406 3.83457708 +v 0.884288013 -22.2799625 3.79368901 +v 1.85336995 -22.7224293 3.70175505 +v 3.46996403 -23.670023 3.39219999 +v -3.56764007 -23.6587429 3.37037706 +v -1.98099804 -22.6533661 3.75517988 +v -1.021716 -22.208271 3.86294794 +v -0.437721997 -22.024807 3.90361595 +v -11.5388317 -28.2954121 1.43417501 +v -13.9220266 -29.6825714 0.62924099 +v -8.78425312 -26.6138 2.28274202 +v -6.02680779 -24.9121418 3.01643991 +v -3.60733795 -23.5750866 3.46361089 +v -17.4618988 -31.1734219 -0.550755978 +v -16.6972809 -31.0045662 -0.32365799 +v -18.0364037 -30.9835663 -0.562204003 +v -15.582407 -30.5577698 0.0432979986 +v -13.8843346 -29.7624969 0.559401989 +v -19.6225262 -30.1562824 -0.651199996 +v -19.1578903 -30.566103 -0.697619975 +v -19.9526901 -29.6247025 -0.513105989 +v -18.6231995 -30.863699 -0.676589012 +v -20.5746536 -27.5120106 0.00628500013 +v -20.4981251 -28.4258823 -0.267452002 +v -20.4283867 -26.2781906 0.452562988 +v -20.3395176 -29.1274319 -0.475546002 +v -19.5893593 -20.8497295 2.06975198 +v -19.125452 -19.0196514 2.57902193 +v -20.0362854 -22.7608395 1.47931397 +v -20.3409691 -24.6286297 0.914039016 +v -20.5205688 -26.2696266 0.401403993 +v -17.5329475 -15.0346699 3.65667105 +v -18.1130581 -16.1619244 3.34744906 +v -16.752039 -14.1254854 3.99166393 +v -18.6286011 -17.4752178 3.0026679 +v -19.0936165 -19.0362072 2.59891701 +v -13.4970541 -11.1967974 4.90928602 +v -14.9814968 -12.2218437 4.54440594 +v -11.3152657 -10.0862589 5.3765769 +v -16.0507927 -13.1251707 4.23187923 +v -2.12100697 -5.31165314 6.43776083 +v 0.480969995 -4.01132488 6.52912092 +v -5.2885251 -6.82403088 6.18671799 +v -8.5085783 -8.46104717 5.81135416 +v -11.3624201 -10.0156336 5.3580761 +v 3.36604905 -2.32648897 6.53000498 +v 3.58873796 -2.16749001 6.56293011 +v 3.03926897 -2.68060994 6.55434179 +v 0.480518997 -4.01080322 6.52899122 +v 3.41115594 -1.40506101 6.50954103 +v 3.55794096 -1.59154403 6.54751587 +v 3.47273707 -1.07407999 6.58032703 +v 3.43455195 -1.89798796 6.49808407 +v 3.05323195 -0.778922021 6.57280779 +v 3.25620008 -0.966808975 6.55841112 +v 3.29039407 -1.16176605 6.51470613 +v 2.33504605 -0.628978014 6.60599995 +v -0.526449025 -1.06129396 6.60566378 +v 2.7797699 -0.687094986 6.57358217 +v -10.6359749 -2.71776295 5.93066597 +v -13.9716654 -3.164397 5.46901417 +v -6.97038078 -2.13327694 6.30715322 +v -3.44249105 -1.53145599 6.5324049 +v -0.531984985 -1.16121995 6.56005096 +v -18.4517403 -3.48664498 4.64825678 +v -19.990736 -3.32043409 4.34144783 +v -21.2698765 -2.96819806 4.08007812 +v -16.5159798 -3.41200995 5.03388977 +v -13.9621382 -3.28773594 5.41004801 +v -25.0426197 -1.35340798 3.10992599 +v -26.3210049 -0.495198995 2.74577403 +v -23.8042183 -2.082479 3.42852211 +v -22.5583725 -2.6385529 3.74544501 +v -30.0752945 2.6300149 1.49684203 +v -31.0093575 3.6516211 1.15088499 +v -28.9312496 1.55409205 1.89930606 +v -27.6368694 0.518498003 2.34621191 +v -26.3594208 -0.574301004 2.69054604 +v -32.4313202 5.83054399 0.494773 +v -32.1481285 5.19429922 0.653217018 +v -32.4173508 6.46907711 0.494778007 +v -31.7083721 4.47836208 0.852070987 +v -31.1025066 3.56054091 1.05043697 +v -32.0829697 8.30928707 0.432859004 +v -32.3442612 7.72830915 0.381130993 +v -31.6647415 8.78127003 0.570740998 +v -32.4757156 7.103508 0.392926991 +v -29.329586 10.000638 1.22951996 +v -30.5471172 9.65606594 0.852083027 +v -27.3701 10.2340221 1.86740303 +v -31.2982559 9.30434704 0.610180974 +v -17.9116173 11.2819214 4.129179 +v -21.22995 10.9993191 3.42990589 +v -15.0594568 11.454731 4.66085005 +v -24.5479488 10.6823835 2.608567 +v -27.3943691 10.3319616 1.816203 +v -11.4962416 12.1758118 5.05732203 +v -11.0916157 12.2823267 5.13196898 +v -12.129776 11.8931513 5.03969383 +v -13.1992016 11.8699741 4.82379818 +v -15.0790453 11.5365648 4.62549782 +v -9.10764027 15.8816204 4.77060509 +v -10.405592 13.1660948 5.0769701 +v -10.859211 12.6361895 5.07446384 +v -5.18851519 24.6991978 3.1078639 +v -3.96316195 27.335886 2.402637 +v -6.569911 21.6101246 3.81118703 +v -7.92346096 18.5122337 4.39089108 +v -9.18535519 15.9316998 4.70771313 +v -1.77371502 30.7342644 1.36966205 +v -2.32290697 30.1282864 1.57655299 +v -1.19773102 30.915411 1.35681295 +v -2.98214197 29.0224152 1.94350004 +v -4.01651812 27.3696575 2.35156202 +v 0.550112009 31.1924038 1.23093498 +v -0.0501240008 31.2364101 1.21716297 +v 1.09625304 30.9151211 1.36524403 +v -0.647836983 31.1679878 1.24504995 +v 2.21988201 30.1283569 1.59282994 +v 2.87635303 29.0224133 1.96459198 +v 3.75538206 27.2667198 2.49728894 +v 1.67238402 30.7347126 1.38170505 +v 7.80003691 18.5122566 4.4474659 +v 8.98276424 15.8825274 4.83480406 +v 6.45062208 21.6100388 3.85808706 +v 5.07427216 24.6991253 3.14484191 +v 3.80748105 27.3036232 2.46324992 +v 10.7307358 12.6363754 5.15212202 +v 10.277132 13.1656771 5.15154314 +v 10.9625769 12.282362 5.21140909 +v 9.89020824 14.1711197 4.98028183 +v 8.98212624 15.8821163 4.83525181 +v 11.9896975 11.9975863 5.07736397 +v 14.9593287 11.4985762 4.74760294 +v 11.2301044 11.9140539 5.25833988 +v 11.3676939 12.1755276 5.13982201 +v 24.4982681 10.6792545 2.76866889 +v 27.3667393 10.3521128 1.97544801 +v 21.1587601 10.9962902 3.57216501 +v 17.821312 11.3074474 4.23904514 +v 14.9594088 11.4989452 4.74734783 +v 31.2780743 9.3156023 0.806908011 +v 31.6916313 8.83825588 0.732428014 +v 30.5268822 9.66198921 1.04414904 +v 29.3193321 10.0254717 1.39822102 +v 27.3803349 10.4204836 1.92722702 +v 32.4897385 7.13254118 0.574117005 +v 32.3300018 7.7510128 0.581936002 +v 32.4127693 6.49217081 0.692699015 +v 32.0646858 8.32944775 0.633387983 +v 31.2720451 4.08106279 1.21928096 +v 29.8914146 2.63502598 1.76125395 +v 32.0418739 5.11037493 0.892769992 +v 32.4321251 5.8548522 0.691079974 +v 22.7778816 -3.95324397 3.77617908 +v 25.3142319 -1.70891798 3.17066097 +v 20.5509815 -5.81681108 4.23032522 +v 27.8204308 0.578706026 2.44459605 +v 29.9131851 2.61644602 1.73934698 +v 18.0569038 -8.52828217 4.50152206 +v 18.4811802 -7.9516468 4.4700799 +v 17.7892113 -8.85738182 4.56017494 +v 20.6293106 -5.88231897 4.17663383 +v 17.8399277 -9.98769855 4.37266302 +v 18.1860104 -12.9269552 3.98416591 +v 17.7034225 -9.92731667 4.45983124 +v 17.8041496 -9.27783966 4.47745085 +v 19.9676495 -22.3251514 1.73532498 +v 20.3741779 -25.1730042 0.872419 +v 19.3996086 -19.0253448 2.62953997 +v 18.7968006 -15.7331467 3.39019299 +v 18.3338013 -12.9140644 3.8901329 +v 20.3602219 -29.2129993 -0.383466005 +v 20.0215168 -29.7370987 -0.460343987 +v 20.5021439 -28.398428 -0.133024007 +v 20.4686108 -27.116766 0.303474993 +v 20.3295612 -25.176939 0.906346023 +v 18.6454506 -30.9345798 -0.591663003 +v 19.1700878 -30.6221371 -0.590689003 +v 18.0579815 -31.0548553 -0.480935007 +v 19.6441021 -30.2297878 -0.560347021 +v 15.5939837 -30.6243534 0.113536 +v 13.9316092 -29.7174301 0.716033995 +v 16.7156601 -31.0744267 -0.249314994 +v 17.4833794 -31.2439308 -0.472469002 +v 5.94987106 -24.9349194 3.05082011 +v 8.73383808 -26.6495419 2.32968807 +v 3.58136296 -23.4121685 3.60643911 +v 11.5147562 -28.3448029 1.49119604 +v 13.9400311 -29.6951466 0.727284014 +v 13.8828678 -29.8206177 0.623144984 +v 0.324037999 -21.9095459 4.00567007 +v -0.0603340007 -21.890789 4.00610685 +v 1.00276899 -22.1337318 3.94878888 +v 3.544137 -23.5046024 3.55204201 +v -3.6427691 -23.4937286 3.52947998 +v -1.11805904 -22.0555153 3.98857999 +v -0.463905007 -21.8599586 4.03242922 +v -11.6560173 -28.0307808 1.575073 +v -14.0187922 -29.4342251 0.763945997 +v -8.91007137 -26.3589211 2.41392207 +v -6.14345694 -24.675108 3.13528609 +v -3.68084192 -23.4001427 3.58426595 +v -3.74297309 -23.2396946 3.65373206 +v -17.4670963 -30.9044819 -0.381850004 +v -16.7362366 -30.7646332 -0.185222998 +v -18.0112362 -30.8302574 -0.470562011 +v -15.6472244 -30.3096046 0.182281002 +v -13.9588614 -29.5934677 0.685936987 +v -14.0186033 -29.4334774 0.762574017 +v -19.4637814 -29.9903812 -0.49288699 +v -19.0285664 -30.3628979 -0.522442997 +v -19.8264389 -29.542614 -0.423963994 +v -18.5379753 -30.6461868 -0.516478002 +v -20.3303375 -27.4892864 0.135585994 +v -20.2702007 -28.3757782 -0.136065006 +v -20.2660389 -26.2867527 0.520195007 +v -20.0990505 -29.0325623 -0.313255996 +v -19.338932 -20.9360008 2.16732407 +v -18.8754139 -19.1321888 2.67349601 +v -19.7465401 -22.8441238 1.60724902 +v -20.0638943 -24.6829472 1.03608203 +v -20.2666969 -26.2872753 0.521714985 +v -17.2874737 -15.2147751 3.74483204 +v -17.8713684 -16.3206005 3.43868494 +v -16.6188354 -14.2424765 4.0281682 +v -18.3926678 -17.6096783 3.08706093 +v -18.8747559 -19.131918 2.67172503 +v -13.3073769 -11.4461184 4.97625589 +v -14.7938032 -12.4532509 4.61435795 +v -11.2127237 -10.2358952 5.40266085 +v -15.8420229 -13.3535938 4.30566883 +v -1.96531296 -5.54931784 6.48702288 +v 0.675674975 -4.23400784 6.58191299 +v -5.1104002 -7.10434389 6.25517178 +v -8.33426285 -8.7254858 5.87203598 +v -11.2127409 -10.235877 5.40287209 +v 3.64359689 -2.46985102 6.58408499 +v 3.76004791 -2.22374201 6.58440304 +v 3.24429798 -2.81139803 6.58658218 +v 2.33158588 -3.3614049 6.594944 +v 0.675683022 -4.23400497 6.58181477 +v 3.80591989 -1.42148495 6.59978914 +v 3.65282106 -1.83152902 6.56508207 +v 3.7325201 -1.17295003 6.60430622 +v 3.83764505 -1.68744802 6.59257793 +v 3.82329798 -1.95893598 6.58955717 +v 3.301121 -0.609000981 6.62499619 +v 3.1053431 -0.486303002 6.63143682 +v 3.47515392 -0.765668988 6.61764193 +v 3.62035108 -0.954128027 6.61249208 +v 1.34916103 -0.49595499 6.66999817 +v -0.509720981 -0.768639028 6.67147684 +v 2.394135 -0.386482 6.65080118 +v 2.8912251 -0.39793399 6.63965416 +v -10.6507111 -2.40460396 6.0061841 +v -13.9835424 -2.8743 5.53940821 +v -6.98818684 -1.82841694 6.37729883 +v -3.46178603 -1.25021195 6.59490919 +v -0.520250022 -0.951602995 6.63790894 +v -0.509688973 -0.768319011 6.6695652 +v -19.9475613 -3.07406592 4.41152191 +v -18.4374599 -3.19163203 4.73173904 +v -21.2323818 -2.79599404 4.12409306 +v -16.5130672 -3.13050199 5.10451508 +v -13.9772882 -3.05401897 5.50321388 +v -13.9832764 -2.8739779 5.53771782 +v -24.9321365 -1.10448802 3.205688 +v -26.1857796 -0.254689991 2.85128593 +v -23.7039013 -1.81162405 3.53194594 +v -22.4773865 -2.3752501 3.83386302 +v -29.8817081 2.84522891 1.62857497 +v -28.7438011 1.79628694 2.03894496 +v -30.8227196 3.80934405 1.26352 +v -27.4840813 0.737950027 2.45415306 +v -26.2712479 -0.403012007 2.79324603 +v -26.1850471 -0.25444901 2.84945202 +v -32.1701851 5.90565681 0.659834027 +v -31.9262466 5.29650211 0.788963974 +v -32.2605324 6.48241377 0.583419025 +v -31.4909344 4.61206579 0.987245977 +v -30.9500999 3.70314503 1.19170201 +v -30.8226223 3.80936098 1.26331604 +v -31.8746834 8.18679047 0.584475994 +v -32.107811 7.64536905 0.549757004 +v -31.5475845 8.67259026 0.655484974 +v -32.238018 7.06972408 0.547821999 +v -29.2309761 9.75565815 1.353297 +v -30.4305096 9.43471813 0.976908028 +v -27.3227329 10.066102 1.93315995 +v -31.1345367 9.08137894 0.764854014 +v -17.8686466 11.0133247 4.23155785 +v -21.181488 10.710227 3.54360104 +v -15.0168114 11.2850056 4.71926212 +v -24.4867668 10.3899717 2.73133993 +v -27.3233376 10.0665035 1.93457305 +v -11.3596821 11.9144163 5.17811012 +v -10.993433 12.1417046 5.19227219 +v -11.9957142 11.7112684 5.11991787 +v -13.1375799 11.5100641 4.98741007 +v -15.0168695 11.2850819 4.71953106 +v -9.70522213 13.994668 5.07869911 +v -8.8808012 15.7283583 4.88330889 +v -10.2655191 12.9630766 5.16615915 +v -10.6623554 12.4186602 5.19680977 +v -4.94576788 24.5434418 3.23580408 +v -3.72822189 27.1644478 2.54108906 +v -6.32357216 21.4678173 3.93032002 +v -7.69200993 18.3812752 4.49863195 +v -9.02527332 15.8266954 4.81915379 +v -8.88036442 15.7279272 4.88154984 +v -1.62917304 30.5035858 1.52452803 +v -2.16442108 29.9434147 1.70315599 +v -1.13819206 30.7675457 1.44033599 +v -2.83317304 28.8953304 2.02936506 +v -3.86405301 27.2660122 2.47040391 +v -3.72797799 27.1638126 2.53971791 +v 0.503440976 30.9270458 1.39706504 +v -0.0507109985 30.9808788 1.38008106 +v 1.03623402 30.7676487 1.44814396 +v -0.604997993 30.92696 1.39311504 +v 2.06041694 29.9435005 1.71833205 +v 2.72673512 28.8954315 2.04938889 +v 3.61780405 27.1639156 2.56617594 +v 1.52653003 30.5036964 1.535887 +v 7.56767988 18.3812828 4.55359602 +v 8.75373363 15.7284555 4.94707823 +v 6.20332813 21.4678535 3.97543812 +v 4.83057308 24.5434055 3.271029 +v 3.61805201 27.1644974 2.56753111 +v 10.5328531 12.4186621 5.27317286 +v 10.8639374 12.1417046 5.27100515 +v 10.1363592 12.9630766 5.23963881 +v 9.57668304 13.9946623 5.14814377 +v 8.75368595 15.7283964 4.94683981 +v 13.0154123 11.5096502 5.08081579 +v 14.9053621 11.2839651 4.82504892 +v 11.8681526 11.7111664 5.20570707 +v 27.2923985 10.0650377 2.11150908 +v 24.4366875 10.3865585 2.89103508 +v 21.1099186 10.7069893 3.68551397 +v 17.7752285 11.0110931 4.35448599 +v 14.9053736 11.2839842 4.82512712 +v 31.1145744 9.09140778 0.960295022 +v 30.4106483 9.43993473 1.16791201 +v 31.5272236 8.68713856 0.85352999 +v 29.2079735 9.75649548 1.53743601 +v 27.3380527 10.2318611 2.04194999 +v 27.2907639 10.0638123 2.10726905 +v 32.2280388 7.09116793 0.746380985 +v 32.0932198 7.66572189 0.749155998 +v 32.2556458 6.50367117 0.780358016 +v 31.856348 8.20479774 0.783773005 +v 31.0483665 4.21044922 1.35476506 +v 31.8180199 5.20978403 1.027408 +v 29.6985817 2.77994895 1.87015104 +v 32.1697235 5.92537308 0.854578018 +v 22.5878143 -3.766572 3.89223289 +v 25.1067467 -1.51090002 3.2991991 +v 20.416914 -5.7074151 4.29755211 +v 27.6000233 0.77067399 2.582407 +v 29.6992931 2.77986598 1.87156498 +v 17.79492 -8.40380478 4.63208199 +v 17.6283436 -8.80465412 4.62565184 +v 18.1966267 -7.86112499 4.60169697 +v 19.0126553 -7.01644802 4.50801992 +v 20.4175491 -5.70770884 4.2993288 +v 17.6477203 -11.058136 4.37876892 +v 17.5104046 -9.89744568 4.53770399 +v 17.9688683 -12.9382601 4.06765699 +v 17.5188389 -9.22434044 4.60673285 +v 20.0983677 -25.1795616 1.01222801 +v 19.6824455 -22.3484364 1.86447096 +v 19.1181488 -19.0621433 2.74948096 +v 18.5082684 -15.7806187 3.51156712 +v 18.1427231 -12.9295998 4.00367022 +v 17.9687824 -12.9382372 4.06741905 +v 20.1205063 -29.1156025 -0.222258002 +v 20.2739658 -28.3461609 -0.00257400004 +v 19.8477173 -29.6193752 -0.332704991 +v 20.2779503 -27.1040516 0.391164988 +v 20.096632 -25.1782875 1.00815797 +v 18.5592251 -30.7168484 -0.431576014 +v 19.049139 -30.4330006 -0.436630994 +v 18.0324116 -30.9011154 -0.388965994 +v 19.4849491 -30.0633812 -0.402790993 +v 15.6576967 -30.3763905 0.253037989 +v 16.7539425 -30.834568 -0.110448003 +v 14.0155497 -29.4938488 0.828104973 +v 17.4876614 -30.9753399 -0.303559989 +v 6.06561613 -24.697855 3.17051506 +v 8.85836697 -26.3950939 2.4614799 +v 3.64324498 -23.2511978 3.67664695 +v 11.631134 -28.0804081 1.63281298 +v 14.0155621 -29.4939117 0.828227997 +v 0.360338002 -21.6235771 4.14222717 +v -0.0608480014 -21.5946236 4.15058422 +v 0.984166026 -21.8311043 4.08986807 +v 2.01280808 -22.3353405 3.95496988 +v 3.64319301 -23.2519169 3.67838001 +v -2.12563396 -22.330761 3.94135189 +v -3.74296498 -23.240448 3.65549994 +v -1.10354698 -21.8299847 4.08264112 +v -0.481602013 -21.6241245 4.14077091 +v -11.5263891 -27.9523144 1.61562598 +v -13.4378176 -28.9231434 1.02702606 +v -14.0186033 -29.4334774 0.762574017 +v -11.2066593 -27.624197 1.76449597 +v -8.91473007 -26.3609543 2.41085792 +v -8.61125183 -26.0681744 2.52889109 +v -10.8600302 -27.1728935 1.94541502 +v -9.14104557 -26.2544804 2.41982102 +v -9.66199017 -26.0167828 2.43438697 +v -7.18280077 -25.1955948 2.90917611 +v -6.13963223 -24.6721077 3.13456297 +v -3.73844695 -23.2385063 3.66012001 +v -7.79095411 -24.9745636 2.91910791 +v -6.75477505 -24.3448277 3.18015003 +v -5.224473 -23.602314 3.48542404 +v -6.59289789 -22.1304092 3.76421905 +v -3.08288002 -21.5673294 4.09601688 +v -1.45793104 -20.5123997 4.3858552 +v -0.481687009 -21.6236858 4.1399498 +v -0.0608399995 -21.5938969 4.14878416 +v -1.10424399 -21.831131 4.08618879 +v -2.12574196 -22.3301601 3.93979692 +v -15.0409412 -29.7438927 0.486615986 +v -12.9832649 -28.1922741 1.33533001 +v -12.654665 -26.3523235 1.95217001 +v -17.4673271 -30.9045849 -0.380861998 +v -18.0112362 -30.8302574 -0.470562011 +v -16.7362328 -30.7643833 -0.185267001 +v -17.0876503 -29.0176125 0.348663002 +v -15.6466703 -30.3086605 0.181024 +v -19.4635639 -29.9900398 -0.493151009 +v -19.8264389 -29.542614 -0.423963994 +v -18.4825459 -29.4580555 -0.0909850001 +v -19.0274658 -30.3612366 -0.525663972 +v -18.5375538 -30.6456127 -0.51749599 +v -20.0991306 -29.0329494 -0.311807007 +v -20.2697029 -28.3753033 -0.137239993 +v -18.6180592 -27.6157227 0.484495014 +v -20.3303642 -27.4891815 0.137106001 +v -20.2660389 -26.2867527 0.520195007 +v -19.3384609 -20.9356995 2.16655207 +v -18.4545231 -19.6619415 2.64685893 +v -18.8743763 -19.1293526 2.6747601 +v -18.0076427 -22.3991795 2.09540296 +v -18.017746 -20.6185112 2.52322102 +v -19.7453098 -22.8425903 1.60581696 +v -18.6359386 -24.5360661 1.39769804 +v -20.063343 -24.6828995 1.03451097 +v -18.9327564 -26.0254498 0.903548002 +v -17.220396 -25.7786179 1.33449495 +v -17.2877197 -15.2153721 3.74660301 +v -16.6188354 -14.2424765 4.0281682 +v -17.8704357 -16.320528 3.43651891 +v -15.9768171 -16.3864441 3.7869761 +v -17.2419224 -17.6334972 3.31339693 +v -18.3922234 -17.609745 3.08556199 +v -13.307682 -11.4465666 4.97508907 +v -14.7924566 -12.4528913 4.61202621 +v -11.2127237 -10.2358952 5.40266085 +v -15.8987427 -14.1780319 4.1713028 +v -15.841671 -13.3536301 4.30420399 +v -9.61338997 -9.8722887 5.62864399 +v -6.47182798 -8.20431042 6.07574177 +v -8.33665943 -8.72691536 5.87011194 +v -4.97419786 -7.03703785 6.26592588 +v -7.69874191 -9.03225708 5.89820099 +v -1.87106299 -5.50266123 6.488626 +v 0.238408998 -5.11724186 6.53860283 +v 0.675880015 -4.23415899 6.57984209 +v -1.57959604 -6.11706209 6.45890522 +v -3.02359796 -7.02542591 6.35454082 +v -4.61812592 -7.84434414 6.21999311 +v -6.7626071 -8.88815308 5.98928499 +v -4.90120792 -8.94768906 6.10736895 +v -9.38278961 -10.0411205 5.63506317 +v -7.76911879 -10.0776854 5.78544903 +v -10.5768375 -11.6409473 5.31555319 +v -8.78110981 -11.0278282 5.58530807 +v -11.5820036 -11.031848 5.26673889 +v -12.3795261 -12.6423187 4.95221519 +v -11.5845146 -13.7095718 4.906672 +v -13.5172901 -13.4400721 4.67526388 +v -14.722127 -13.6569738 4.45250416 +v 3.64370394 -2.47000098 6.58256006 +v 4.27632999 -2.54581594 6.55638409 +v 3.76004791 -2.22374201 6.58440304 +v 3.24433398 -2.81140494 6.58716202 +v 5.84363794 -3.50881505 6.44673681 +v 3.20202088 -4.00009298 6.54615784 +v 2.33163595 -3.36160898 6.59318781 +v 4.55044603 -4.18765116 6.48667717 +v 5.592834 -5.52634478 6.36479616 +v 2.81572199 -5.49636078 6.48374319 +v 1.94928396 -4.56942511 6.5513072 +v 1.27347302 -6.21968889 6.4665699 +v 3.823493 -1.958884 6.58788395 +v 4.73405504 -1.50446296 6.55953121 +v 3.83764505 -1.68744802 6.59257793 +v 3.80611491 -1.42145896 6.59814596 +v 3.7325201 -1.17295003 6.60430622 +v 5.31305695 -0.188094005 6.54105711 +v 6.28700876 -1.44826198 6.47306681 +v 3.30172706 -0.609378994 6.62455606 +v 3.47519708 -0.765685976 6.618577 +v 3.1053431 -0.486303002 6.63143682 +v 3.62048197 -0.953936994 6.61084223 +v -0.509688973 -0.768319011 6.6695652 +v 2.891186 -0.397675008 6.6384201 +v 3.289747 1.71705997 6.61286116 +v 2.75327802 0.362351 6.64384317 +v -0.668861985 -0.431528986 6.67158794 +v -3.33880711 -1.22982097 6.59635782 +v -6.0228548 -1.31604099 6.45598316 +v -6.98562098 -1.827788 6.37615108 +v -1.43790698 -0.194426 6.66242313 +v -3.284271 -0.542421997 6.60800886 +v -10.6547241 -2.40494704 6.00413084 +v -8.90794277 -1.57505095 6.21161699 +v -13.9832764 -2.8739779 5.53771782 +v -7.33637285 -1.19201899 6.35831404 +v -6.94449997 -0.505060971 6.39702702 +v -5.90473604 -0.492202014 6.47105789 +v -4.47926283 -0.325809002 6.55541515 +v 0.637826025 0.0195550006 6.67723989 +v 1.34553099 0.639385998 6.66897583 +v -0.713504016 1.507411 6.66177988 +v 1.25285196 2.25181103 6.64511681 +v -15.8129387 -2.87888503 5.23818207 +v -14.2335148 -2.06831598 5.52263594 +v -13.7458153 -0.765824974 5.61613989 +v -12.1943798 -0.505792975 5.8417449 +v -12.7363338 -1.83399498 5.75000286 +v -11.1142912 -0.903376997 5.9768858 +v -9.83877277 0.243062004 6.13074017 +v -9.53156662 -1.00171995 6.1554451 +v -11.1207342 -1.69527102 5.96490002 +v -7.99162817 -0.369598001 6.31057596 +v -17.6965923 -3.00637507 4.88550901 +v -16.5122433 -3.13015294 5.10293484 +v -18.4359741 -3.19104409 4.7296381 +v -19.9480247 -3.07371593 4.41123676 +v -21.2323608 -2.79597712 4.12385178 +v -20.7881508 -1.55312395 4.26088095 +v -24.9318142 -1.10409796 3.20406199 +v -24.3715649 -0.626734972 3.36557889 +v -25.6067982 -0.311262995 3.01876712 +v -26.1902256 -0.251359999 2.85086012 +v -22.7009583 -1.00051498 3.80399203 +v -23.7027607 -1.81105196 3.52876401 +v -22.4766521 -2.37523699 3.83375311 +v -27.4820805 0.737021983 2.45316505 +v -28.7416382 1.79593003 2.03637791 +v -29.881813 2.84555793 1.62875998 +v -30.8232327 3.80968404 1.26418996 +v -29.2032814 2.61911607 1.86434698 +v -28.4813023 3.93652892 2.052531 +v -29.8596191 4.14739895 1.58441997 +v -30.6952381 5.40237188 1.22645497 +v -29.2168179 5.44571114 1.73017597 +v -32.1696548 5.90569592 0.659057021 +v -32.2610283 6.48244381 0.584774971 +v -31.9260006 5.29656792 0.788842976 +v -31.4901123 4.61203623 0.985855997 +v -31.8738728 8.1865778 0.583014011 +v -32.1063805 7.64503193 0.547564983 +v -31.5475845 8.67259026 0.655484974 +v -30.8018131 6.72281408 1.09732902 +v -32.2373276 7.06951189 0.546675026 +v -31.134079 9.08105278 0.763879001 +v -30.6073608 8.12825775 1.04771399 +v -30.4299564 9.43440533 0.975706995 +v -29.2308655 9.75558853 1.35442495 +v -27.3222237 10.0665207 1.93554497 +v -29.0982876 7.332798 1.63387597 +v -17.8648796 11.0130138 4.22981501 +v -16.5900841 10.9590073 4.48420095 +v -15.0168114 11.2850056 4.71926212 +v -19.4632397 10.6625566 3.93877411 +v -21.1786289 10.7101068 3.54289389 +v -20.6904602 10.6210566 3.670362 +v -17.9683285 10.6280994 4.25654221 +v -21.1053047 10.1623602 3.62441111 +v -19.0152702 9.88919163 4.12614584 +v -23.2529106 10.0683393 3.10066009 +v -24.5094357 10.3873892 2.7251451 +v -23.1182404 10.4161921 3.09437799 +v -21.8273811 9.07685089 3.56817698 +v -24.8308792 8.57915497 2.82790089 +v -23.3128033 8.49367523 3.24833012 +v -26.1121655 7.53147793 2.55568099 +v -25.7646484 9.542799 2.46129489 +v -27.4277611 7.41996288 2.164361 +v -28.2950821 8.91280365 1.750337 +v -11.3594255 11.9139643 5.17655277 +v -10.9469976 11.2091618 5.31809711 +v -10.993433 12.1417046 5.19227219 +v -11.9957628 11.7113523 5.12051201 +v -12.4351807 10.7433186 5.18087196 +v -13.1373682 11.5095901 4.98577881 +v -14.0152416 10.766921 4.94582891 +v -15.0049839 11.0552979 4.75095606 +v -9.70557976 13.9936428 5.0796032 +v -10.2666082 12.9626999 5.17047596 +v -8.88036442 15.7279272 4.88154984 +v -10.661973 12.4183388 5.19544888 +v -4.93713903 24.5603352 3.22848105 +v -4.19881201 25.3958797 3.04030704 +v -3.72797799 27.1638126 2.53971791 +v -5.13299417 23.6735916 3.45639896 +v -6.32417583 21.465416 3.92944098 +v -5.99667501 21.9034863 3.85005093 +v -4.47439194 24.1134262 3.38097906 +v -5.56791401 22.3811836 3.7586 +v -4.88024282 22.668045 3.73114991 +v -6.88229179 19.8391895 4.25850582 +v -7.6988821 18.3650208 4.50037289 +v -8.11867905 15.6844273 4.96609497 +v -7.53420496 17.473938 4.6908412 +v -6.82270098 18.9779243 4.44621992 +v -5.98476315 20.5387554 4.16878796 +v -4.90801907 21.4272346 4.03181601 +v -7.37504578 13.669467 5.35258913 +v -8.66789341 14.196291 5.15437078 +v -8.57868099 12.0888224 5.46502495 +v -6.833179 11.6607761 5.67237186 +v -7.28920317 10.2030344 5.80998087 +v -9.84268761 10.3727875 5.54686785 +v -1.62898397 30.5032387 1.52402699 +v -1.13819206 30.7675457 1.44033599 +v -2.1645689 29.9429798 1.70339894 +v -2.83274007 28.8967667 2.03055596 +v -2.88295698 27.9021816 2.34711599 +v -3.3495369 26.3577251 2.79966688 +v -2.57070708 24.2111511 3.43290496 +v 0.503434002 30.9273148 1.39861596 +v 1.03623402 30.7676487 1.44814396 +v -0.0507079996 30.9790974 1.37677801 +v -0.604993999 30.9267025 1.39319301 +v -0.209106997 29.4226284 1.91283906 +v 2.72673512 28.8954315 2.04938889 +v 2.060462 29.9438801 1.72057199 +v 2.8125689 27.7709942 2.40782905 +v 3.61780405 27.1639156 2.56617594 +v 1.52631903 30.5030518 1.53451598 +v 7.57043791 18.3737679 4.553092 +v 7.55459118 18.1525536 4.60007715 +v 8.15557957 16.3392334 4.89402199 +v 8.75327206 15.7279272 4.94506407 +v 6.20395422 21.465416 3.97456908 +v 6.66687679 20.245779 4.22631693 +v 7.2482419 18.6074142 4.53087711 +v 6.66165018 19.726368 4.34130192 +v 5.93837309 21.7290955 3.92866492 +v 4.89620018 24.3992462 3.30511308 +v 5.61743879 22.6813908 3.71430206 +v 6.90590715 18.14468 4.65153885 +v 6.2274828 20.4052296 4.22119284 +v 5.42436123 22.5949402 3.74901295 +v 5.52754879 21.8478489 3.9252851 +v 4.77058506 23.4057198 3.57616711 +v 4.25823307 24.9062386 3.19912195 +v 3.8607111 26.0512257 2.88919497 +v 2.98874307 25.9176369 2.96013188 +v 1.50267506 26.3329506 2.87304306 +v 0.865935981 27.971056 2.38019395 +v -0.150524005 25.9964962 2.97776198 +v 10.5324574 12.418313 5.27160501 +v 10.8639374 12.1417046 5.27100515 +v 9.89848328 11.4942274 5.46243 +v 10.1363592 12.9630766 5.23963881 +v 9.13954353 13.0924883 5.32437277 +v 8.37173271 11.835906 5.56574202 +v 8.51283169 9.83373928 5.79107094 +v 7.73838902 13.2977858 5.4196949 +v 9.57625294 13.9943724 5.14654684 +v 8.81808281 14.5968647 5.1270628 +v 7.23518991 14.4281063 5.28956604 +v 11.2299967 11.9140511 5.25857401 +v 11.8681011 11.7117538 5.20935011 +v 13.0148821 11.509326 5.07985878 +v 14.0405331 11.1054487 4.98251915 +v 14.9053354 11.283886 4.82477808 +v 12.61059 10.1411648 5.29823589 +v 11.1624718 9.89616108 5.5077548 +v 24.4231205 10.387538 2.89297509 +v 22.6603146 10.4301395 3.34497595 +v 27.2892303 10.0642147 2.10906792 +v 20.0540047 10.6523428 3.93031812 +v 22.1463566 10.1304798 3.50639892 +v 21.1072636 10.7068491 3.68474388 +v 19.0714989 10.4785786 4.15665913 +v 16.9300556 10.6350527 4.55623007 +v 17.7951794 10.8621531 4.36859512 +v 15.8292875 10.9739285 4.71143007 +v 17.767107 11.0113525 4.35422993 +v 14.7106142 9.71937847 5.03961897 +v 31.1146412 9.09124279 0.96082598 +v 31.5272236 8.68713856 0.85352999 +v 29.2898407 8.37691212 1.65391099 +v 30.4101048 9.43961811 1.16670299 +v 28.1782265 7.74816704 2.06782699 +v 27.4155159 8.96371937 2.19018006 +v 29.2069588 9.75612259 1.53589201 +v 25.6255035 8.3315649 2.77874088 +v 24.3316269 8.34294701 3.13403606 +v 22.4848709 8.68846321 3.57669806 +v 20.4207993 8.98897934 4.0317688 +v 21.9592209 7.33199978 3.82428694 +v 31.8555489 8.20458698 0.782314003 +v 32.0914307 7.66521788 0.745864987 +v 32.2272415 7.09094 0.744913995 +v 32.1710167 5.92576218 0.858789027 +v 32.2556458 6.50367117 0.780358016 +v 30.4221764 7.24548101 1.37536895 +v 31.0473099 4.21025419 1.35319996 +v 30.5939503 5.33752108 1.45145702 +v 29.8603745 4.28762007 1.75726104 +v 29.6985817 2.77994895 1.87015104 +v 31.8173695 5.20977592 1.02620995 +v 22.6309032 -3.72758794 3.88271904 +v 20.9286957 -4.70806599 4.23968601 +v 20.416914 -5.7074151 4.29755211 +v 21.429018 -3.68413591 4.17326021 +v 25.0257778 -1.58372998 3.31963801 +v 23.7310104 -0.836139977 3.67841411 +v 25.1216278 -0.180960998 3.30539489 +v 26.4312572 0.960458994 2.93081808 +v 25.009819 1.29863 3.32962608 +v 28.1910477 2.32596302 2.37146592 +v 27.5519352 0.725044012 2.60050797 +v 26.9244099 1.81181598 2.77199197 +v 27.4351444 2.86395788 2.58901811 +v 28.9481449 3.28165007 2.10057497 +v 28.8565693 5.650877 2.01183009 +v 27.6140881 4.65146589 2.46035409 +v 19.1466236 -5.17742395 4.60387182 +v 20.1968861 -4.92329979 4.39373684 +v 20.1820145 -2.8732059 4.48541307 +v 18.9157505 -6.41426086 4.57201099 +v 17.7946949 -8.40374184 4.63153601 +v 16.8568306 -8.76399899 4.77570105 +v 17.6283436 -8.80465412 4.62565184 +v 18.1968327 -7.8614049 4.60336304 +v 19.0121956 -7.01609278 4.50643682 +v 16.5960598 -6.8831768 4.98422289 +v 17.1747704 -5.53548813 4.96834517 +v 17.6471767 -11.0580816 4.37718391 +v 16.9707851 -11.1175203 4.49945211 +v 17.9687824 -12.9382372 4.06741905 +v 17.510088 -9.89734364 4.53719378 +v 17.518404 -9.22426319 4.60580683 +v 19.698679 -22.4537926 1.83336401 +v 19.4794979 -21.5329876 2.10923409 +v 20.096302 -25.1713104 1.01240003 +v 19.0586433 -19.3843784 2.69334507 +v 19.1169529 -19.0579052 2.74921298 +v 19.0226231 -20.8242054 2.37896609 +v 18.7230606 -18.564888 2.93569589 +v 18.2546997 -18.8851929 2.96527195 +v 18.5679646 -17.1956959 3.24227309 +v 18.1115589 -15.9037685 3.56912899 +v 17.7867317 -14.2595558 3.90580797 +v 18.5045891 -15.7639685 3.51343107 +v 17.0253372 -12.7897043 4.2699728 +v 16.6761417 -14.4992352 4.07683182 +v 17.3149471 -16.4366512 3.62823391 +v 17.3399162 -18.0879326 3.30710912 +v 15.4033813 -15.865386 4.07088614 +v 15.7966652 -11.1801367 4.69952297 +v 20.1199474 -29.1151295 -0.223570004 +v 18.9778786 -28.5030251 0.236279994 +v 19.4849262 -30.0632915 -0.402271986 +v 19.8477173 -29.6193752 -0.332704991 +v 20.2734699 -28.3456802 -0.00374899991 +v 17.7553253 -29.449276 0.175628006 +v 19.0770206 -27.3655357 0.579253972 +v 20.2779503 -27.1040516 0.391164988 +v 18.9942474 -26.1498871 0.969332993 +v 18.2141933 -24.2294827 1.68207002 +v 19.1135426 -23.7736378 1.616189 +v 18.4855995 -22.3553677 2.11705399 +v 19.3295727 -22.6083031 1.87420201 +v 18.5587502 -30.7161331 -0.432969004 +v 19.0489807 -30.4328251 -0.436632991 +v 18.0329037 -30.9020329 -0.386409014 +v 17.4874439 -30.9746189 -0.304881006 +v 16.753952 -30.8343639 -0.110363998 +v 15.657443 -30.3759251 0.253006011 +v 14.6456108 -29.2665329 0.805189013 +v 14.0123243 -29.4922276 0.830052972 +v 15.8744345 -29.3546143 0.564038992 +v 6.06172085 -24.694809 3.16978502 +v 6.45329523 -24.7536068 3.12988997 +v 4.75650597 -23.4629974 3.5760839 +v 3.64324498 -23.2511978 3.67664695 +v 8.85756111 -26.3940544 2.46044111 +v 8.25261307 -25.9245758 2.65674496 +v 6.24655294 -24.2960873 3.26632595 +v 7.77233315 -25.4505272 2.83086991 +v 10.0595579 -27.0325184 2.14528394 +v 11.6332865 -28.0810852 1.63074195 +v 10.4594803 -27.0429764 2.09835792 +v 10.0981903 -26.2984638 2.36209989 +v 11.1828089 -27.3074512 1.93022501 +v 12.1884375 -28.1426563 1.539307 +v 13.8336601 -28.0568752 1.32981503 +v 3.48850298 -20.5566444 4.33508921 +v 1.82444894 -21.1640167 4.23925686 +v 3.417943 -22.211874 3.94935894 +v 2.12970996 -19.2923908 4.64955091 +v 5.6167388 -21.8411922 3.93451309 +v 0.360056013 -21.6235218 4.14227819 +v 0.33644101 -20.2956562 4.45111895 +v 0.984166026 -21.8311043 4.08986807 +v 2.01293206 -22.3347397 3.95340705 +v -1.69700897 -19.3745098 4.62860012 +v -0.392435998 -18.4430428 4.84057379 +v -1.11109698 28.3260498 2.25876999 +v -1.36184704 27.1718712 2.61454201 +v -1.58270502 25.5651493 3.08411407 +v 0.303766012 24.4139481 3.42237091 +v -0.79191798 23.5828457 3.63912511 +v 2.2107079 24.0172634 3.50900006 +v -1.33619106 22.4412327 3.919276 +v -0.0895889997 21.4519787 4.1741538 +v -2.09990692 20.4257889 4.38195515 +v -3.06462598 22.1188564 3.95383406 +v 1.20599699 22.7639408 3.84970307 +v 2.58341193 21.47822 4.13914919 +v 3.90599298 22.2515888 3.90958405 +v 3.36474299 24.3505955 3.38625312 +v -4.18275404 20.2486744 4.34208918 +v -3.33998299 19.0824566 4.63099003 +v -5.44088221 19.1137276 4.51709509 +v -5.21963692 17.8509636 4.78774405 +v -1.00063097 20.1301842 4.46814203 +v 0.317436993 18.8022594 4.76029205 +v 1.35289097 20.0378418 4.49169397 +v 2.68576002 18.9019661 4.70868778 +v 4.11618423 20.4358521 4.32859516 +v 6.31203985 18.9054546 4.53793907 +v 4.90778494 18.7419796 4.65525293 +v -2.88160992 17.8440571 4.89779806 +v -3.98030901 16.7258358 5.06557512 +v -6.00634909 16.4245396 5.00350714 +v -1.02262199 17.8565235 4.94252396 +v 0.323677987 16.1347504 5.26919222 +v -1.96447396 15.933526 5.27867222 +v 1.36454904 17.3771667 5.03662395 +v 3.27009296 15.5853014 5.3165679 +v 3.8928349 17.4666061 4.95372486 +v 5.57514 17.1051579 4.9389348 +v 5.24894285 15.5889006 5.22979593 +v 6.88106203 16.3002453 5.00224018 +v -2.56669807 14.2554817 5.53735399 +v -4.61715889 13.5409718 5.55856895 +v -4.72915983 15.0903034 5.31217003 +v -6.28023481 14.7738972 5.26513004 +v -6.37838507 13.0734396 5.51522398 +v -1.08542597 14.0804682 5.59723902 +v 0.794746995 14.0515404 5.61079788 +v -0.34097299 12.7793007 5.79429913 +v 1.70855701 15.4087868 5.38237715 +v 2.93550706 13.538847 5.64947796 +v 5.19352913 13.5729609 5.55215406 +v -4.97409391 12.3496456 5.70593882 +v -4.7622838 10.6513548 5.9305048 +v -2.72726607 12.2152452 5.82593584 +v -2.75536299 9.95858765 6.09340382 +v 0.50810498 11.0460215 6.02045202 +v -1.42316103 11.0314236 6.00693607 +v 1.68067503 12.1118727 5.87530899 +v 1.75982296 10.5105171 6.06980705 +v 3.84298992 11.7647514 5.86392498 +v 4.9332571 10.8219471 5.92919683 +v 6.62898493 12.1242018 5.66587305 +v 6.53579521 10.3864298 5.88352108 +v -5.69843006 9.0981617 6.03921986 +v -5.06681585 7.71033478 6.20386887 +v -7.37194395 7.704072 6.04497719 +v -8.15904045 8.93118191 5.86568403 +v -9.75361919 7.99231911 5.79249096 +v -4.02820015 9.03616619 6.13979578 +v -0.600305974 9.40974045 6.19676208 +v 0.130866006 7.24576998 6.395823 +v -1.95103395 8.05535507 6.30196714 +v 3.388381 9.14985466 6.17585993 +v 1.47987294 8.60124969 6.2721839 +v 5.40453911 9.08469486 6.08961678 +v 4.79222822 7.23849201 6.286798 +v 6.58463287 8.63443565 6.06117487 +v 8.4477272 8.07863712 5.96542501 +v 9.78104591 9.6699028 5.6858511 +v 10.8434505 8.18107319 5.71454 +v -16.0158348 9.92099285 4.70740223 +v -17.4516354 8.90135288 4.54678011 +v -16.2608261 7.61169386 4.88725901 +v -18.3655949 7.92769909 4.4535079 +v -19.7906094 7.60978508 4.17886877 +v -20.1776028 8.73677254 3.99040794 +v -21.9103661 7.3452549 3.70652294 +v -12.8668947 8.92817307 5.31573582 +v -14.5306568 8.78244877 5.07776403 +v -18.2071686 6.31411791 4.61359978 +v -16.2434959 5.31648207 5.05182505 +v -18.8753128 5.34777403 4.53860998 +v -20.8262024 4.59022284 4.147645 +v -21.4570885 5.9754529 3.91923308 +v -13.4812593 7.01541996 5.39068222 +v -14.8215351 6.67299795 5.20672178 +v -14.048686 5.05521011 5.43586302 +v -12.8289356 4.58876181 5.63981819 +v -11.4420938 8.94165421 5.50717306 +v -10.4237919 7.04257202 5.79412603 +v -11.8897419 6.5977149 5.64650679 +v -9.7405262 5.66823387 5.96544695 +v -11.1940603 4.60124493 5.85726881 +v -8.29365921 6.41065502 6.06151199 +v -7.47505188 5.12327909 6.21502495 +v -6.4209218 6.35293913 6.21935177 +v -4.9207058 5.98804283 6.34033108 +v -3.37858009 6.69884014 6.36405516 +v -1.74291694 6.36329508 6.4376092 +v -2.78698301 5.24906301 6.47948503 +v 0.267123014 5.43110991 6.51898909 +v -1.25566804 4.9281249 6.5331521 +v 2.43215299 7.04927301 6.3845048 +v 2.32324195 5.69472504 6.48001623 +v 5.8525672 5.46383476 6.34953785 +v 4.07330179 5.69905615 6.42383099 +v 7.10809088 7.05959606 6.15688896 +v 7.25683784 5.00053883 6.28238583 +v 8.91990566 4.30789089 6.176723 +v 10.5239363 6.65526485 5.87276888 +v 9.35853863 6.16159296 6.02958393 +v 11.1689892 5.0363121 5.90074396 +v 14.2440929 8.67818642 5.21406412 +v 13.1506748 7.74232292 5.46017218 +v 12.2918224 6.43170691 5.67593002 +v 13.855485 5.38019896 5.52565193 +v 16.8048439 9.46565914 4.70887709 +v 15.9646158 8.23321533 4.97851419 +v 15.0982094 6.65898991 5.25013781 +v 16.8474407 5.77272701 5.01205206 +v 15.5335035 5.04437208 5.280756 +v 19.3713646 8.72436523 4.28199816 +v 18.9679203 7.71471405 4.45714521 +v 18.0954361 9.21924877 4.49270678 +v 17.4541569 7.47955608 4.77431917 +v 18.8164406 5.82493496 4.63132191 +v -26.1869392 5.29250288 2.69359493 +v -27.6886578 5.52617979 2.2227931 +v -26.9712391 3.61872101 2.54165792 +v -25.969389 2.37065005 2.88474202 +v -27.7206192 1.96092105 2.36275792 +v -23.9851723 6.66952276 3.22625303 +v -24.2613964 5.44686985 3.23298597 +v -24.8993931 3.94050193 3.13453889 +v -23.4079647 2.23935795 3.60014296 +v -22.9208679 3.70723605 3.67695904 +v -21.4887943 2.20392489 4.08178997 +v -19.5608177 3.2917881 4.48987198 +v -18.2668171 2.48599195 4.78478384 +v -17.1811104 3.75196505 4.95532084 +v -15.2335939 3.62292004 5.30998611 +v -16.3614063 2.189291 5.15977383 +v -13.0894527 2.69064212 5.678267 +v -14.5273123 1.68370605 5.48310423 +v -11.4889307 2.95217395 5.88627815 +v -8.11567879 3.6371491 6.22803402 +v -9.46561146 3.97364807 6.08302689 +v -9.43028927 2.56844711 6.13881111 +v -6.42567205 3.98071599 6.35193396 +v -5.07250977 2.96595001 6.47701788 +v -6.83942699 2.17149401 6.38214684 +v -4.11078882 4.0001421 6.49034119 +v -2.12094808 3.15747309 6.59412718 +v -3.0072391 1.66307497 6.607481 +v -0.670166016 3.25803494 6.6156168 +v 1.26326394 3.77156091 6.59700918 +v 2.78313589 3.5907321 6.57660103 +v 4.81059885 4.33822203 6.46673584 +v 6.11327505 2.252738 6.46837378 +v 4.75916004 2.97621393 6.52076483 +v 7.55467892 2.37846208 6.363657 +v 10.7782717 3.55287695 6.01621008 +v 11.568511 2.70605493 5.95325422 +v 9.21238041 1.93258202 6.22866917 +v 13.0334387 3.91810989 5.71806479 +v 14.2119617 1.97789001 5.61036301 +v 15.3106394 3.37814808 5.39566708 +v 16.8823872 3.69026804 5.1114068 +v 18.2113209 4.03365993 4.84687614 +v 19.9958916 4.03801584 4.4792161 +v 18.6083832 2.34747505 4.82864189 +v 20.5938988 6.44930887 4.20870018 +v 21.3037949 4.52827501 4.16295624 +v 22.1953297 3.36345601 4.00433302 +v 20.2192326 2.34200406 4.4892149 +v 23.7737389 6.96420097 3.39899898 +v 24.7339516 6.00573683 3.21401 +v 22.6141605 5.45270395 3.79889202 +v 24.4785004 4.22421217 3.38404107 +v 23.7607307 2.54753304 3.6367929 +v 26.5528355 7.41466284 2.59212995 +v 27.4279385 6.41838789 2.40566492 +v 25.8744431 5.46647787 2.9294641 +v 26.2762184 3.84931898 2.89778304 +v 25.4868088 2.80583906 3.16160488 +v -24.1149349 0.919443011 3.432096 +v -25.4158077 1.13294494 3.06854296 +v -22.405735 0.397879004 3.88233089 +v -19.7678089 1.31902897 4.4935112 +v -17.9129715 0.901835024 4.88610792 +v -18.8401527 -1.25696397 4.69515705 +v -20.493639 -0.178496003 4.34080505 +v -15.6772909 0.313497007 5.30687189 +v -17.5770149 -0.643646002 4.95461798 +v -13.43997 0.523904979 5.66466522 +v -11.3812494 1.31550705 5.9401269 +v -7.74722004 0.846773982 6.33104181 +v -5.688241 0.876362026 6.48277283 +v -4.4614439 1.32119906 6.54902792 +v 4.34309578 0.928480029 6.5857048 +v 8.50663185 -0.0541869998 6.31396198 +v 7.11710405 -0.0718130022 6.42725992 +v 10.3007383 0.736814976 6.1355381 +v 11.3097677 -0.30893299 6.02143717 +v 12.5423717 0.893571019 5.86341 +v 13.2855139 -0.660310984 5.76352119 +v 16.6074619 1.70446897 5.22185612 +v 14.9764223 0.185984001 5.51298523 +v 19.0803757 0.253448009 4.7631259 +v 17.3686523 0.186704993 5.0998168 +v 21.8696003 1.67872202 4.12820721 +v 20.7918167 0.693365991 4.39195585 +v 23.5170956 1.07959604 3.72807002 +v 22.6077251 -0.26730299 3.96533704 +v -16.8207169 -1.56117499 5.08818007 +v -15.4405928 -1.13273597 5.33994198 +v 8.02670383 -2.17772603 6.33142519 +v 8.83562279 -3.11309695 6.23125601 +v 10.3576155 -1.52344406 6.11997604 +v 10.5762539 -3.374825 6.04728079 +v 14.2268267 -1.74315596 5.6113739 +v 12.207406 -2.48390388 5.8798871 +v 13.6742306 -3.20057201 5.65696001 +v 15.5663967 -1.797364 5.40003395 +v 17.1161499 -1.79158998 5.1274991 +v 17.1538467 -3.35590291 5.07726097 +v 18.5107613 -2.00849009 4.85727882 +v 20.5292187 -1.03083098 4.44642019 +v 22.1095581 -2.03577304 4.0648818 +v 7.66349506 -4.52182198 6.27746105 +v 9.84697723 -4.46602583 6.07848597 +v 10.5297613 -6.50392389 5.88723516 +v 9.33073521 -6.04563093 6.041996 +v 12.0440025 -4.85886908 5.80608702 +v 13.8753595 -5.47515821 5.51986599 +v 15.6216507 -4.4164381 5.30358601 +v 14.8663893 -6.49907303 5.30390215 +v 18.5125408 -3.961555 4.79306507 +v 7.527637 -6.82867002 6.14775276 +v 6.72937202 -8.66096592 6.05345106 +v 5.11105108 -7.52743101 6.25044394 +v 3.71859908 -6.545681 6.38575792 +v 2.97597599 -7.62150002 6.32937002 +v 11.1707678 -8.39331818 5.66034079 +v 8.98824596 -8.30442142 5.901124 +v 12.4460363 -6.7522192 5.63558102 +v 13.4658365 -8.3845396 5.36273909 +v 15.4850512 -8.78785419 5.01114082 +v 0.909029007 -8.07157898 6.32920599 +v -1.08446395 -8.66213608 6.26898718 +v -0.400963992 -7.30833817 6.39194107 +v -2.67220592 -7.78133488 6.30867386 +v 1.73213601 -9.56258106 6.17796993 +v 2.66224408 -11.3039227 5.96505499 +v 0.125213996 -11.5474768 5.96501684 +v 0.0528259985 -9.91176605 6.1516881 +v -1.82064903 -10.649622 6.04957724 +v -2.98496199 -9.69048691 6.1196909 +v 4.03734779 -9.21810627 6.14892101 +v 6.48022509 -9.78586197 5.95763206 +v 5.18188 -11.1785803 5.88003492 +v 4.09097385 -10.5694151 6.00066423 +v 8.13172245 -10.1662254 5.79140997 +v 7.08977699 -11.5261021 5.71559811 +v 9.81557846 -9.88172054 5.66326523 +v 11.546772 -10.0931273 5.44498014 +v 10.8057356 -11.6039734 5.35617685 +v 9.16953087 -11.1950455 5.57805395 +v 12.8446922 -10.2731075 5.25740719 +v 14.1417398 -10.170332 5.0824461 +v -4.63337183 -10.9053316 5.91269588 +v -6.42421103 -10.1931515 5.88203907 +v -3.59830904 -11.9599113 5.83037281 +v -2.76668906 -13.01682 5.72097206 +v -1.96101904 -13.9466429 5.60569382 +v -5.06299686 -13.5390511 5.54095888 +v -5.3933239 -12.0660391 5.72364616 +v -7.18603277 -11.8947458 5.62014914 +v -7.27425623 -13.2866135 5.42418814 +v -0.904640973 -12.5737572 5.82243586 +v 1.00538003 -12.6272688 5.8198328 +v -0.126093999 -13.8462648 5.64891195 +v 2.34594107 -13.2661076 5.71048307 +v 5.78913498 -12.536788 5.67255688 +v 4.08300591 -12.9827728 5.69525099 +v 8.84675217 -12.7020884 5.41497898 +v 8.85949039 -14.6396971 5.1278491 +v 6.76813316 -13.6677284 5.44759512 +v 10.4130268 -13.6294537 5.12257624 +v 12.4634924 -12.2347736 5.06880283 +v 14.3800106 -11.8897381 4.83864403 +v 13.8476486 -13.4982405 4.69693899 +v 11.8107147 -13.8796644 4.91880894 +v 15.3751707 -13.8244486 4.40907192 +v -9.10082531 -12.5450153 5.35829592 +v -9.79480743 -13.6754913 5.12476301 +v -8.25615883 -14.6316662 5.13314486 +v -7.69669724 -16.3832283 4.88809586 +v -9.41956425 -16.1096973 4.77160501 +v -10.5447512 -15.9314518 4.67659903 +v -12.5551243 -15.4384785 4.50017595 +v -11.9792347 -16.861208 4.32645988 +v -13.3936367 -17.0414982 4.09185505 +v -5.62248898 -15.9654293 5.12023497 +v -6.63750505 -14.9646521 5.21527576 +v -4.34482622 -16.898983 5.024086 +v -3.7099731 -14.8783236 5.40310192 +v -1.77937603 -15.5090036 5.36235094 +v -2.90338898 -16.3740005 5.18025589 +v -0.238893002 -16.0353527 5.2944231 +v 1.79105997 -14.5452938 5.528512 +v 2.86219311 -16.2831173 5.21436024 +v 1.06108296 -15.6165056 5.36376476 +v 4.02037716 -14.6057596 5.45693588 +v 6.07728577 -15.5486107 5.19433022 +v 4.603508 -16.3451538 5.13948393 +v 7.70086098 -15.5568037 5.07669783 +v 8.85067844 -17.296854 4.66589499 +v 10.7452488 -15.9048214 4.72043705 +v 12.6217213 -14.920785 4.64959717 +v 14.0773401 -15.5273304 4.33993483 +v 12.3993359 -17.2190323 4.27677488 +v -14.5558977 -15.5431309 4.17870378 +v -14.3499861 -18.3831158 3.68067193 +v -15.0963554 -17.7336712 3.68636394 +v -16.6508522 -18.8542709 3.18188691 +v -10.8437414 -17.9801579 4.25840521 +v -10.4891605 -19.6783752 3.94891191 +v -12.3509846 -19.0867863 3.8366251 +v -7.52789116 -18.5593681 4.48940516 +v -9.4058094 -18.4222565 4.33482599 +v -4.9377408 -18.5773163 4.66915512 +v -6.74024391 -18.3584938 4.59143782 +v -3.26717591 -17.9619045 4.86877298 +v -1.63415599 -17.6718159 4.97537708 +v -3.25384808 -19.5273838 4.5496912 +v 1.33154404 -17.6422024 4.99469614 +v 3.53457499 -18.6904774 4.73719788 +v 4.39489412 -17.9471626 4.84774113 +v 6.35840511 -17.3125782 4.86002207 +v 5.71507978 -18.9407787 4.57875776 +v 7.77387381 -17.9376698 4.63479519 +v 7.35734177 -19.8780842 4.26753998 +v 8.70295715 -19.655592 4.19981384 +v 10.9941101 -18.5494366 4.192204 +v 9.85031033 -19.556818 4.10729313 +v 13.6792822 -17.5937996 4.02396107 +v 13.1351452 -19.1640949 3.78461599 +v 14.9800091 -17.6162853 3.81697011 +v 16.4005947 -18.0531082 3.489465 +v 15.3663015 -19.7009506 3.32415891 +v -14.406394 -20.2322693 3.27739096 +v -12.7845211 -22.4064026 3.01102996 +v -14.9887733 -21.6553497 2.84830904 +v -16.6132927 -20.6984482 2.78265405 +v -11.926301 -20.8230553 3.51225591 +v -9.762743 -21.2926865 3.66905594 +v -8.86715126 -19.9679241 4.06368685 +v -7.80282211 -20.9035721 3.95385909 +v -6.20209122 -20.2517586 4.23016787 +v -4.58400297 -21.0399971 4.15273523 +v 5.77842617 -20.3686886 4.26785612 +v 8.03221703 -21.4886436 3.84526801 +v 10.3209677 -21.4861794 3.61972189 +v 11.9969645 -20.4688358 3.65391994 +v 13.3705177 -22.2738247 3.03576303 +v 11.921133 -21.9068871 3.32437301 +v 14.5235291 -20.7662315 3.22241211 +v 16.1665573 -21.5684319 2.75552297 +v 17.5696297 -19.9516087 2.86928797 +v 17.3382549 -21.9430199 2.449054 +v -16.1902828 -22.9600182 2.30784893 +v -15.0465307 -24.2684383 2.16445494 +v -16.8933754 -24.3971138 1.79236901 +v -13.7536459 -23.9298363 2.47139311 +v -11.9034986 -24.3018112 2.64101791 +v -10.3868017 -22.6053143 3.27816105 +v -10.1760216 -24.1378384 2.90323997 +v -8.41024208 -22.8732414 3.421103 +v -8.79028416 -24.6348457 2.91886401 +v 6.72141695 -23.0852184 3.5550611 +v 9.03773403 -22.9504547 3.39298201 +v 8.56199646 -24.4142132 3.05125093 +v 7.48505402 -24.5941238 3.09588289 +v 10.5965958 -23.4587383 3.09585905 +v 9.80035305 -24.7837486 2.82739902 +v 11.6038513 -23.8546028 2.87138605 +v 13.8492699 -24.224905 2.45897889 +v 15.1791048 -23.1391411 2.53427792 +v 16.0668316 -24.2149315 2.09394908 +v -15.990572 -26.1652393 1.45729303 +v -14.8815994 -27.7094593 1.18241096 +v -14.1809874 -26.1220875 1.78279698 +v -11.2330894 -25.8459339 2.29457092 +v 8.84428787 -25.8073597 2.635288 +v 11.8214178 -25.5191345 2.38376689 +v 13.467514 -26.2947788 1.92803895 +v 15.0011663 -26.061821 1.75314701 +v 15.807703 -27.3760872 1.21865797 +v 16.874279 -25.5175343 1.58063304 +v 17.2551479 -27.769516 0.826771021 +v -16.7347889 -27.8708725 0.792644024 +v -3.24615002 -22.363245 -2.42643404 +v -5.4551959 -23.6913128 -2.911304 +v -8.15140915 -25.4007397 -3.50854897 +v -3.23746395 -22.4728527 -2.21234488 +v -12.8721533 -28.1517849 -5.15348005 +v -8.32168388 -25.5645599 -3.43850398 +v -12.8902845 -28.2724457 -4.95654297 +v -5.43967199 -23.8032627 -2.68174005 +v -12.8722324 -28.1517391 -5.15334702 +v -15.5829411 -29.5347271 -5.89098787 +v -15.5513353 -29.4120369 -6.07542801 +v -16.325573 -29.5369244 -6.28372908 +v -16.9350872 -29.4442902 -6.38384104 +v -16.3727818 -29.6739349 -6.07337379 +v -16.9943886 -29.5827579 -6.17234898 +v -14.484354 -29.1134644 -5.50197792 +v -18.0918732 -28.8908062 -6.45439911 +v -18.1787186 -29.0284653 -6.22763205 +v -19.0205479 -27.9665184 -6.3304038 +v -18.6898251 -28.5956402 -6.19474792 +v -19.1193295 -28.0786285 -6.11718702 +v -17.606348 -29.3621216 -6.22181702 +v -19.3361588 -27.3988228 -6.20506477 +v -19.5648975 -26.711441 -5.97106791 +v -19.5693035 -24.5627136 -5.35577917 +v -19.7257938 -25.8535957 -5.52017403 +v -19.6773491 -24.6312199 -5.14105415 +v -19.6468563 -26.7860088 -5.78671408 +v -19.438961 -27.4977531 -5.99236012 +v -19.5849743 -24.5713596 -5.32927084 +v -19.3894997 -22.9973278 -4.85141993 +v -19.191349 -21.2655258 -4.1023078 +v -18.2831783 -17.6413383 -3.22668099 +v -18.8119488 -19.4255352 -3.534127 +v -18.3622093 -17.6682549 -3.0393889 +v -19.4970512 -23.0474281 -4.62482214 +v -18.2537479 -17.6362724 -3.27917409 +v -17.7900124 -16.1628342 -2.87322998 +v -17.372364 -14.9209423 -2.32450008 +v -16.0776062 -12.8657141 -1.90222895 +v -16.8063202 -13.8346167 -1.99535203 +v -16.1481495 -12.8715582 -1.69987595 +v -17.8901024 -16.1816807 -2.63838196 +v -15.2920341 -11.9958296 -1.68220198 +v -14.3060751 -11.1094284 -1.30076897 +v -10.8903513 -8.98707104 -0.574612975 +v -12.931941 -10.1349993 -0.788936973 +v -10.939105 -8.97519398 -0.377770007 +v -14.3578215 -11.1087799 -1.12412095 +v -15.3754997 -11.9896746 -1.44033003 +v -10.8741608 -8.99521637 -0.624836981 +v -8.27744102 -7.62637806 -0.196787 +v -5.36978912 -6.11112213 0.383691996 +v -2.19693899 -4.59931278 0.401309997 +v 0.176776007 -3.42550302 0.518145025 +v -2.16136003 -4.52964401 0.656090021 +v 0.147245005 -3.39959908 0.717423975 +v -8.22574139 -7.5456562 0.0670500025 +v 0.176666006 -3.4253931 0.518710017 +v 2.47307611 -2.22322512 0.507701993 +v 2.43777204 -2.19689798 0.754314005 +v 2.79663897 -1.99143898 0.495104998 +v 2.8477509 -1.85950506 0.551612973 +v 2.75432491 -1.97037697 0.746455014 +v 2.81602502 -1.85111701 0.763314009 +v 1.64133704 -2.63776207 0.756950021 +v 2.864465 -1.607463 0.725758016 +v 2.91047311 -1.60378098 0.507059991 +v 2.89832211 -1.48088503 0.503946006 +v 2.82506204 -1.38962495 0.718971014 +v 2.85122108 -1.49408495 0.754508972 +v 2.85149693 -1.73064399 0.751293004 +v 2.81167293 -1.274279 0.537280023 +v 2.72218108 -1.22773099 0.735992014 +v 2.55715609 -1.11508405 0.730390012 +v 2.64626908 -1.16826296 0.765744984 +v 2.78067899 -1.30696201 0.760311007 +v 2.465868 -1.02702296 0.521094024 +v 2.07241392 -1.09804702 0.753663003 +v 2.08193707 -1.03677797 0.516236007 +v -0.501060009 -1.43229198 0.570309997 +v 1.16157901 -1.22162199 0.793624997 +v -0.50525701 -1.48076999 0.772040009 +v 2.45271707 -1.08283806 0.771453023 +v -0.501049995 -1.43210101 0.569741011 +v -3.16892099 -1.86899495 0.469112009 +v -3.84848309 -2.04378295 0.683230996 +v -6.49335814 -2.41100693 0.245739996 +v -10.0193281 -2.98037004 -0.0754090026 +v -9.90468788 -3.01546097 0.160285994 +v -13.0797606 -3.45808697 -0.291765988 +v -17.3249283 -3.74507999 -1.08966196 +v -17.2800732 -3.68283701 -1.29974794 +v -18.7416401 -3.56599498 -1.61390996 +v -20.0125465 -3.31339097 -1.84317803 +v -18.8012142 -3.63207793 -1.37438798 +v -20.0712051 -3.36021304 -1.64240098 +v -15.4874392 -3.69668698 -0.702615976 +v -21.2106285 -2.88412094 -2.17744899 +v -22.4390163 -2.35938096 -2.39535999 +v -23.62327 -1.64974201 -2.76595211 +v -24.8179607 -0.821083009 -3.13885498 +v -23.6920147 -1.70429301 -2.55603504 +v -24.9233665 -0.872386992 -2.90079308 +v -22.4985466 -2.39340091 -2.22473192 +v -21.292923 -2.94423008 -1.94265902 +v -24.8453884 -0.837908983 -3.08495903 +v -26.0780258 0.119469002 -3.48845196 +v -27.3927193 1.11965096 -3.71791101 +v -27.2866325 1.16780996 -3.93410993 +v -28.4019051 2.18982196 -4.31938887 +v -29.3371506 3.14309192 -4.65188599 +v -28.5145798 2.14994907 -4.09345102 +v -26.1897659 0.0953309983 -3.29060102 +v -29.4328079 3.12443089 -4.48860502 +v -30.5558701 4.68741894 -4.9688921 +v -30.6866894 5.36577511 -5.29922295 +v -30.778244 6.01595497 -5.38625813 +v -30.826025 5.36653423 -5.08502483 +v -30.9227295 6.02946091 -5.17078495 +v -30.1211567 3.95510006 -4.73735714 +v -30.7211304 7.38369179 -5.23100424 +v -30.597229 7.35126209 -5.4146452 +v -29.9923592 8.55145264 -5.25394583 +v -30.4631386 8.02318954 -5.16607523 +v -30.0936298 8.5970726 -5.07060719 +v -30.889576 6.71496296 -5.20832014 +v -29.5000477 9.00011826 -5.17874479 +v -28.8662472 9.46117687 -4.76342106 +v -28.7668972 9.39398766 -4.95254612 +v -25.8011208 10.1280632 -3.84671307 +v -27.6752911 9.81656456 -4.3621192 +v -29.6184731 9.0681982 -4.9618721 +v -25.7422733 10.0770741 -3.99142408 +v -20.0384483 10.7644815 -2.29765701 +v -19.9265919 10.6805296 -2.50704408 +v -16.8212528 10.9730368 -1.84341002 +v -14.2219105 11.2549047 -1.30040896 +v -16.8506088 11.0617428 -1.60453606 +v -14.2534704 11.3004084 -1.16075695 +v -23.1102047 10.466877 -3.03042793 +v -14.1980801 11.2146778 -1.39143896 +v -11.5181856 11.6543303 -0.809201002 +v -11.4712086 11.5767946 -1.01325405 +v -10.9352713 11.7352133 -0.969744027 +v -10.670188 11.931488 -0.908303022 +v -10.992403 11.8179836 -0.735110998 +v -10.719286 11.9928236 -0.708607018 +v -12.5410566 11.4952879 -0.905826986 +v -10.3987617 12.1232433 -0.955035985 +v -10.1385117 12.6545372 -0.772949994 +v -8.84440327 15.0917492 -1.246629 +v -9.6565733 13.5843906 -0.830480993 +v -8.91214561 15.1626158 -1.02680302 +v -10.4664698 12.1990681 -0.719434023 +v -8.84415722 15.0914764 -1.24739301 +v -7.75266695 17.5365143 -1.606305 +v -6.52403116 20.5169277 -1.93897605 +v -3.9887321 25.8052654 -3.4589541 +v -5.2489171 23.3538132 -2.55736089 +v -4.03660297 25.9003201 -3.26055002 +v -7.82868814 17.6006298 -1.37100196 +v -3.99433899 25.8162766 -3.43730211 +v -2.40690589 28.4884338 -4.29985523 +v -2.45195794 28.6231403 -4.05962515 +v -1.83075404 29.0757523 -4.46804619 +v -1.28261304 29.4006596 -4.5146389 +v -1.86431003 29.203619 -4.24993086 +v -1.29977095 29.5067863 -4.32921696 +v -3.1481111 27.583353 -3.73414302 +v -0.0315850005 29.7259388 -4.43713379 +v -0.0290890001 29.6022701 -4.6240449 +v 1.23532295 29.4852676 -4.36788702 +v 0.616321981 29.6790485 -4.39214182 +v -0.677353024 29.6790352 -4.39626408 +v 1.77111495 29.075449 -4.45562983 +v 2.38579702 28.6124477 -4.06989813 +v 3.92724109 25.8162746 -3.40877199 +v 3.08208394 27.5852909 -3.71222806 +v 3.96827602 25.900423 -3.23148108 +v 1.80267 29.2045708 -4.23633623 +v 3.92175508 25.8052254 -3.43052006 +v 6.44502306 20.51931 -1.89276004 +v 7.67235088 17.5363579 -1.55075896 +v 8.76062298 15.0909224 -1.18531299 +v 7.75101805 17.5904694 -1.31356394 +v 8.82761574 15.1625977 -0.962969005 +v 5.1483922 23.4129009 -2.5341289 +v 8.76083279 15.0911608 -1.18463504 +v 10.054821 12.6500111 -0.699959993 +v 10.3134756 12.1230555 -0.88083303 +v 10.5846596 11.9315672 -0.831628025 +v 10.3799753 12.1981239 -0.645421028 +v 10.6325102 11.9927101 -0.631703019 +v 9.57247734 13.5809116 -0.760564983 +v 10.8501015 11.73528 -0.891187012 +v 11.4383526 11.6532145 -0.726760983 +v 11.3877964 11.5767183 -0.931023002 +v 12.4646645 11.4942379 -0.817300022 +v 14.1817455 11.2974138 -1.06730497 +v 10.906683 11.8179102 -0.655183971 +v 14.1366491 11.2261868 -1.26361704 +v 16.7695847 10.9708138 -1.72772002 +v 19.9318352 10.7685671 -2.14682794 +v 25.7467823 10.0757294 -3.82535195 +v 23.0570755 10.4678259 -2.87055492 +v 25.8163681 10.1357555 -3.64816904 +v 16.8078556 11.0587893 -1.49016201 +v 25.7333603 10.0593777 -3.85514998 +v 28.8762264 9.46936798 -4.58073902 +v 28.781683 9.40092945 -4.77099514 +v 29.5145454 9.01235867 -4.99264479 +v 30.0064926 8.56891918 -5.06456423 +v 29.6303978 9.08092022 -4.77555799 +v 30.1062908 8.61479092 -4.88067293 +v 27.6837883 9.81924438 -4.18628788 +v 30.7393436 7.41209078 -5.040411 +v 30.8491268 6.04573393 -5.15039587 +v 30.9140701 6.74273586 -5.01876116 +v 30.9604702 6.05826187 -4.96751595 +v 30.4781094 8.04629612 -4.97410917 +v 30.7233868 5.39030981 -5.11444998 +v 30.4958496 4.61738491 -4.74302387 +v 30.3733578 4.62367392 -4.93022823 +v 28.3297501 2.20779896 -4.11299276 +v 29.7539864 3.59345698 -4.39286518 +v 28.4480438 2.18354988 -3.90601492 +v 30.8624554 5.3930192 -4.89882803 +v 28.3467255 2.20358896 -4.08671808 +v 26.447155 0.382946014 -3.46690106 +v 26.3454971 0.136983007 -3.16107607 +v 23.9959126 -1.85948598 -2.76914191 +v 21.6339054 -3.986974 -2.194031 +v 19.6605988 -5.77634001 -1.76705694 +v 21.7071838 -4.06069517 -1.95880997 +v 19.7304859 -5.81203794 -1.59944105 +v 19.6367226 -5.7633729 -1.81602204 +v 17.6060848 -7.68548012 -1.54928696 +v 17.7123032 -7.74065781 -1.29715002 +v 17.2706184 -8.13768101 -1.503613 +v 17.2306499 -8.48575974 -1.31239998 +v 17.368187 -8.18537998 -1.27217996 +v 18.4467297 -6.99940777 -1.38831699 +v 17.0574856 -8.7691288 -1.52357495 +v 17.1548462 -9.36419296 -1.37766695 +v 17.1937065 -10.3595781 -1.72814298 +v 17.494545 -12.0743246 -2.01702809 +v 17.2978516 -10.3991804 -1.49819005 +v 17.5941849 -12.105298 -1.79983604 +v 17.1594048 -8.80496883 -1.29149699 +v 17.5078831 -12.0779457 -1.99205399 +v 17.9735489 -14.649291 -2.54952407 +v 18.6312771 -17.6439934 -3.00725198 +v 19.0930996 -20.9407082 -4.10884809 +v 19.4563313 -23.5594711 -4.90106297 +v 19.1847725 -20.8957176 -3.85562992 +v 19.5576744 -23.6242962 -4.69594812 +v 18.0838528 -14.6414785 -2.28423309 +v 19.4562683 -23.5595913 -4.90075302 +v 19.6713505 -26.7484341 -5.69811678 +v 19.5658474 -26.6639442 -5.90118313 +v 19.3907337 -27.4788971 -6.11774397 +v 19.1632404 -28.1364059 -6.06051111 +v 19.4921322 -27.577528 -5.90501499 +v 19.7140789 -25.5075207 -5.27885914 +v 18.2270927 -29.0823059 -6.16942596 +v 18.1557446 -28.9713402 -6.34910488 +v 17.0440655 -29.6323872 -6.12769699 +v 17.6617966 -29.4275074 -6.142097 +v 18.7445984 -28.664732 -6.10705423 +v 16.3810234 -29.6027184 -6.21132088 +v 15.637682 -29.6006413 -5.82334805 +v 12.9054623 -28.2070656 -5.09507513 +v 14.5309973 -29.1767807 -5.43768597 +v 12.924057 -28.3399506 -4.87774992 +v 16.4280262 -29.7408009 -5.99948883 +v 12.9072332 -28.2250671 -5.06923103 +v 10.7493172 -26.9451237 -4.36591721 +v 8.02416325 -25.4099388 -3.34663796 +v 5.41893291 -23.7129059 -2.88036704 +v 3.18720007 -22.389473 -2.38021588 +v 5.39309978 -23.8191776 -2.64983511 +v 3.17881489 -22.4835186 -2.19279099 +v 0.790907025 -21.1752777 -1.81974494 +v 0.802881002 -21.0731869 -2.01854801 +v 0.284579009 -20.8887119 -1.97699499 +v -0.0425539985 -20.9673824 -1.77890396 +v 0.277121007 -21.0026016 -1.74821103 +v 1.69916403 -21.6450653 -1.91979301 +v -0.362111002 -20.8888149 -1.97915697 +v -0.872566998 -21.1764164 -1.82562196 +v -1.77341402 -21.6424065 -1.931831 +v -0.357205987 -21.0031586 -1.74971604 +v -3.27084398 -22.2309093 -2.573915 +v -5.49165106 -23.5882378 -3.0425601 +v -8.04759979 -25.2168083 -3.65729403 +v -10.6015768 -26.7408352 -4.459692 +v -12.8742218 -28.0069199 -5.29329109 +v -12.8742495 -28.0065212 -5.29357195 +v -14.4495497 -28.9599133 -5.73911285 +v -15.5284424 -29.2771816 -6.2021842 +v -16.2963467 -29.4118881 -6.39515114 +v -16.8853683 -29.3018646 -6.50597286 +v -17.5270348 -29.2148476 -6.45203876 +v -18.5918159 -28.4615784 -6.42483902 +v -18.8918839 -27.8425846 -6.47484922 +v -19.2369156 -27.3231125 -6.31847286 +v -19.427454 -26.6197929 -6.13985777 +v -19.6033211 -25.760519 -5.75973701 +v -19.4206505 -24.5071869 -5.51515913 +v -19.4680767 -24.5221119 -5.47413397 +v -19.2825089 -22.9754848 -4.980721 +v -19.0439339 -21.1994247 -4.36837101 +v -18.686903 -19.3621674 -3.78182697 +v -18.1772289 -17.6328068 -3.38205695 +v -18.1070633 -17.6354332 -3.4562161 +v -17.6496506 -16.1749802 -3.05504107 +v -17.2576923 -14.9068975 -2.57199001 +v -16.558382 -13.8611603 -2.43783903 +v -16.6965523 -13.8232803 -2.25758195 +v -15.9493036 -12.9008722 -2.12168694 +v -15.2095318 -12.0326986 -1.828565 +v -14.2082357 -11.1485472 -1.51604795 +v -12.8423901 -10.149251 -1.058743 +v -10.8229322 -9.03164196 -0.746273994 +v -8.0239048 -7.59671211 -0.347451001 +v -5.12492895 -6.07174206 0.101487003 +v -2.20148706 -4.68295097 0.245526999 +v 0.232887998 -3.48171806 0.337686002 +v 0.214551002 -3.46283698 0.387968004 +v 1.68667305 -2.67238402 0.477605999 +v 2.57097602 -2.29060602 0.277381986 +v 2.86141205 -2.02483511 0.343567997 +v 2.94570208 -1.88830101 0.315741986 +v 2.90762997 -1.73308897 0.471893013 +v 2.98732901 -1.59741497 0.338568985 +v 2.97112012 -1.46368599 0.349994004 +v 2.96759295 -1.31996202 0.296649992 +v 2.87753797 -1.36311495 0.477654994 +v 2.87994194 -1.22799003 0.354561001 +v 2.76680493 -1.17782497 0.470082998 +v 2.6805439 -1.11501706 0.488240987 +v 2.63068604 -0.963319004 0.311058015 +v 2.58488202 -1.05696595 0.489995986 +v 2.48897099 -0.951578021 0.371744007 +v 2.102777 -0.937453985 0.337345004 +v 1.17894804 -1.15191603 0.517280996 +v -0.496470004 -1.37327099 0.44183901 +v -0.494495988 -1.34507298 0.393146992 +v -3.21799898 -1.79496503 0.317178994 +v -6.48093891 -2.30487394 0.0697029978 +v -9.81801891 -2.86048794 -0.229479 +v -13.038744 -3.38187099 -0.559504986 +v -13.0384798 -3.38099289 -0.561259985 +v -15.4295111 -3.61949492 -0.970875978 +v -17.2382126 -3.59334493 -1.45701802 +v -18.6799622 -3.44668007 -1.80174506 +v -19.931673 -3.19808292 -2.05814195 +v -21.1235275 -2.77193904 -2.35949111 +v -22.3457432 -2.27070904 -2.59771299 +v -23.5231037 -1.57739997 -2.9247179 +v -24.7207317 -0.744221985 -3.28929591 +v -25.9916859 0.202494994 -3.65132999 +v -27.1655979 1.23927104 -4.08407593 +v -28.3021526 2.24542594 -4.44680405 +v -29.2129402 3.18566394 -4.79772997 +v -29.1646137 3.20658302 -4.83912086 +v -29.969923 3.97225904 -4.97868204 +v -30.4020424 4.70059919 -5.19512987 +v -30.2614098 4.72698402 -5.3125701 +v -29.3038235 3.152632 -4.69751787 +v -30.5609512 5.37558079 -5.4138422 +v -30.6302242 6.00969696 -5.51210499 +v -30.7313175 6.68443012 -5.44331598 +v -30.4663677 7.31260204 -5.53975105 +v -30.3128109 7.96996593 -5.400702 +v -29.8335934 8.46080685 -5.43439913 +v -29.4022312 8.92523384 -5.29590321 +v -28.6694794 9.30815315 -5.08541107 +v -27.5570488 9.72857666 -4.60615587 +v -25.6472054 9.95064163 -4.16734695 +v -25.718483 10.0476685 -4.04089403 +v -25.662096 9.9737072 -4.14493895 +v -22.9382744 10.291934 -3.38033795 +v -19.8627625 10.5611525 -2.66227198 +v -16.8011494 10.8737698 -1.982939 +v -14.1437569 11.099678 -1.54714 +v -14.1843538 11.1874123 -1.43458498 +v -14.1438494 11.0999126 -1.54687405 +v -12.4763947 11.3982859 -1.16651797 +v -11.4145441 11.4797268 -1.162256 +v -10.8793659 11.6456079 -1.10402298 +v -10.5788593 11.8112583 -1.11573505 +v -10.3273821 12.0442963 -1.08950996 +v -10.0578766 12.5672522 -1.02023602 +v -9.97008801 12.4796534 -1.15710497 +v -9.57893372 13.4947567 -1.08956397 +v -8.74928474 15.0072527 -1.40201902 +v -7.66302109 17.48596 -1.74356103 +v -6.43205023 20.4013786 -2.21352196 +v -5.14164495 23.211462 -2.87572002 +v -3.907969 25.6941166 -3.608742 +v -3.92369509 25.7133465 -3.58859706 +v -3.09479809 27.4505634 -3.98054409 +v -2.33858109 28.3500175 -4.44023705 +v -1.78890705 28.9617901 -4.58656406 +v -1.24262202 29.2254219 -4.69790506 +v -0.664802015 29.5249653 -4.63525105 +v -0.0286369994 29.4687023 -4.75232315 +v 0.606795013 29.524931 -4.63067293 +v 1.17681205 29.1942234 -4.70810413 +v 1.21334302 29.3466263 -4.57317114 +v 1.73005199 28.9611969 -4.5744648 +v 2.33899999 28.473177 -4.30091 +v 2.27893305 28.3501644 -4.42348909 +v 3.03175497 27.4505386 -3.95837498 +v 3.85771799 25.7133923 -3.56050396 +v 3.84209609 25.694109 -3.58082509 +v 5.07036781 23.2117805 -2.83894706 +v 6.35612011 20.4013081 -2.1674459 +v 7.56062317 17.4409161 -1.72785902 +v 8.66525745 15.0053205 -1.34207702 +v 8.64992332 14.9919786 -1.36281097 +v 9.49486923 13.4947691 -1.02081001 +v 9.97327328 12.5673943 -0.947974026 +v 9.88658237 12.4797344 -1.08544695 +v 10.2429962 12.0441542 -1.01567197 +v 10.4947538 11.8112736 -1.03982902 +v 10.7952595 11.6455183 -1.02604103 +v 11.332118 11.479557 -1.08055305 +v 12.3980532 11.3978786 -1.077636 +v 14.1238041 11.2035666 -1.31303501 +v 14.0863771 11.1229076 -1.42333698 +v 16.7502918 10.8717518 -1.86712396 +v 19.874157 10.6467428 -2.42873001 +v 22.8983822 10.2732992 -3.24585104 +v 25.6661205 9.97052574 -3.98273802 +v 25.6526108 9.94911289 -4.00335693 +v 27.5693684 9.73059177 -4.43183184 +v 28.6853218 9.31477833 -4.90468979 +v 29.417881 8.93705654 -5.11059904 +v 29.8491116 8.47762299 -5.24642801 +v 30.3295326 7.9911561 -5.21089077 +v 30.5867691 7.36642504 -5.26284599 +v 30.7578201 6.710289 -5.25414324 +v 30.6652336 6.03467989 -5.32876205 +v 30.5985603 5.39908314 -5.22928905 +v 30.2416534 4.64242792 -5.06050587 +v 29.6029911 3.60861492 -4.63419485 +v 28.1985416 2.2555871 -4.25319004 +v 28.2234211 2.24504399 -4.23278999 +v 24.2676201 -1.50478601 -2.95654988 +v 21.5617886 -3.90668702 -2.329005 +v 19.5805721 -5.72963381 -1.90158904 +v 19.504652 -5.68089199 -1.98814797 +v 18.3345032 -6.94335222 -1.64433503 +v 17.4796371 -7.6164732 -1.70154405 +v 17.1709309 -8.08959198 -1.63455999 +v 16.9932289 -8.38994312 -1.68514895 +v 17.125246 -8.43966579 -1.53282201 +v 16.9520645 -8.73904133 -1.65500104 +v 17.0147171 -9.31957626 -1.65327501 +v 17.0226784 -10.326375 -1.92209101 +v 17.3464622 -12.0495434 -2.1884861 +v 17.3914394 -12.0556335 -2.14670205 +v 18.5198498 -17.7751026 -3.32344508 +v 19.3581829 -23.5192986 -5.01378107 +v 19.3275013 -23.5078793 -5.04384422 +v 19.5930233 -25.4154453 -5.51936579 +v 19.4182205 -26.5718403 -6.0556612 +v 19.2926941 -27.4027481 -6.23113012 +v 18.9474049 -27.9152527 -6.3879652 +v 19.0629158 -28.0273113 -6.25974798 +v 18.6467094 -28.5305271 -6.33896303 +v 18.0820122 -28.8536034 -6.4709301 +v 17.5828476 -29.2807751 -6.37209082 +v 16.9318676 -29.3378086 -6.45046091 +v 16.9845829 -29.4930725 -6.3246212 +v 16.352375 -29.4771461 -6.32298422 +v 15.5919085 -29.4189301 -6.07352591 +v 14.4955559 -29.0220184 -5.67443609 +v 12.9071903 -28.0833931 -5.21689415 +v 12.906208 -28.0902767 -5.21394396 +v 10.629199 -26.7482071 -4.44833517 +v 8.03611183 -25.2506313 -3.61493492 +v 5.45599508 -23.609621 -3.01132989 +v 3.20911789 -22.2659893 -2.53372908 +v 3.19092393 -22.359848 -2.42914891 +v 1.70948601 -21.5142612 -2.17291808 +v 0.816502988 -20.9533577 -2.15996504 +v 0.294364989 -20.7751217 -2.10548592 +v -0.0382420011 -20.7017021 -2.14481211 +v -0.0387500003 -20.8465004 -1.99678099 +v -0.370907009 -20.7756939 -2.10740709 +v -0.881003976 -21.0445747 -2.066113 +v -0.898068011 -20.9138794 -2.19666696 +v -1.77852798 -21.5098896 -2.18426108 +v -3.24800491 -22.3486462 -2.4496069 +v -3.31010103 -22.0685844 -2.684587 +v -5.51368713 -23.4428673 -3.14531898 +v -8.08918953 -25.002409 -3.82404995 +v -10.6542301 -26.5385551 -4.62001705 +v -12.8927336 -27.8344841 -5.39440012 +v -12.8926744 -27.8344765 -5.3945508 +v -14.4418516 -28.7545261 -5.90072393 +v -14.4457378 -28.5874386 -5.96493387 +v -15.5142555 -29.0962944 -6.29722214 +v -16.2649231 -29.2591076 -6.4820652 +v -16.8338032 -29.130703 -6.59028101 +v -17.4352837 -29.0216808 -6.6000309 +v -17.3653946 -28.8712006 -6.6531601 +v -17.9791546 -28.7132034 -6.60399389 +v -18.3516521 -28.1727638 -6.628016 +v -18.4574547 -28.2985096 -6.57385492 +v -18.7297592 -27.7023621 -6.55959177 +v -19.1146851 -27.2343388 -6.40758085 +v -19.2482319 -26.5278301 -6.25104284 +v -19.2755146 -25.6243496 -5.99541521 +v -19.4244347 -25.6785641 -5.92629194 +v -19.2852535 -24.4738846 -5.599895 +v -19.1140804 -22.8997974 -5.08847618 +v -18.8396721 -21.1795654 -4.54462624 +v -18.3724766 -19.4620609 -4.08986616 +v -18.5035629 -19.333065 -3.96391797 +v -18.0053158 -17.6468086 -3.53719091 +v -17.9494534 -17.657198 -3.56714606 +v -17.4678783 -16.226511 -3.19324803 +v -17.0817719 -14.9430866 -2.77772689 +v -16.4239445 -13.9056606 -2.5431869 +v -15.1000576 -12.0874243 -1.95621097 +v -14.0569525 -11.2556877 -1.70381403 +v -12.6070471 -10.3193951 -1.38227999 +v -12.717802 -10.2327251 -1.27290201 +v -10.713994 -9.13166523 -0.925840974 +v -10.7685003 -9.07797718 -0.848824978 +v -7.99197578 -7.73341608 -0.508185029 +v -5.02274513 -6.19895077 -0.118813001 +v -2.11283994 -4.75803423 0.106760003 +v 0.336508006 -3.59190798 0.160438001 +v 0.281329989 -3.53267694 0.241457 +v 1.78861403 -2.76161003 0.252909005 +v 2.69699502 -2.38028002 0.130616993 +v 2.96039605 -2.07294297 0.200368002 +v 3.12861609 -1.94510996 0.121454999 +v 3.04069495 -1.74827099 0.248042002 +v 3.17055607 -1.76247299 0.129933 +v 3.14070606 -1.58853102 0.162181005 +v 3.0746479 -1.43328595 0.210522994 +v 3.12042403 -1.250687 0.134286001 +v 3.00689197 -1.14052701 0.178378999 +v 2.85392404 -1.08887506 0.270693988 +v 2.76134706 -1.00120199 0.268009007 +v 2.69627094 -0.836504996 0.174897 +v 2.5280571 -0.843757987 0.233326003 +v 2.13957906 -0.771111012 0.175887004 +v 1.22871697 -0.86935401 0.185519993 +v 1.19920802 -1.00780106 0.29896 +v -0.489300013 -1.26972198 0.299066991 +v -0.483779997 -1.18610406 0.222626001 +v -3.19602299 -1.67604101 0.183506995 +v -6.47328377 -2.129282 -0.0880689994 +v -9.87317467 -2.70518899 -0.405889988 +v -13.0095139 -3.24718189 -0.751245975 +v -12.9899931 -3.0879879 -0.88124001 +v -15.3801489 -3.46818995 -1.17859304 +v -15.341754 -3.327564 -1.281443 +v -17.1882782 -3.4261651 -1.61521101 +v -13.0094137 -3.24650002 -0.751914024 +v -18.6191406 -3.27056408 -1.94560695 +v -19.8395195 -2.99933195 -2.22134995 +v -21.0301285 -2.6312871 -2.47842598 +v -22.2252617 -2.103405 -2.76298308 +v -23.4166813 -1.455387 -3.052176 +v -24.6996117 -0.723586023 -3.31251907 +v -25.8893661 0.394650012 -3.82174301 +v -27.4189014 1.74605596 -4.356915 +v -28.1619129 2.30140591 -4.54433823 +v -29.0361538 3.27017999 -4.92078876 +v -29.007988 3.28588796 -4.93244314 +v -29.7766685 4.029953 -5.14407921 +v -29.6229668 4.08642721 -5.21180201 +v -30.100975 4.76517391 -5.39594412 +v -30.4081116 5.38859081 -5.50371408 +v -30.4537659 6.00796318 -5.60023499 +v -30.523613 6.650105 -5.59666681 +v -30.360136 6.62259912 -5.65408802 +v -30.2658672 7.25032806 -5.64245701 +v -29.9729195 7.81315899 -5.61485386 +v -30.1221447 7.88412809 -5.55591202 +v -29.2851486 8.82875061 -5.38993597 +v -28.5332546 9.15705967 -5.20298576 +v -27.3349037 9.44552708 -4.85227299 +v -27.4306927 9.577775 -4.77890491 +v -25.5628109 9.80828667 -4.27706385 +v -25.5626278 9.8078146 -4.27715015 +v -22.900568 10.16467 -3.4970541 +v -19.7874336 10.3764315 -2.78902698 +v -16.7347965 10.7491713 -2.09357309 +v -14.0847349 10.9525661 -1.66538703 +v -14.0856838 10.9552164 -1.66396701 +v -12.3976545 11.2368326 -1.35733497 +v -12.3301506 11.0960417 -1.44499695 +v -11.3278961 11.3118868 -1.30255699 +v -10.8053188 11.5309744 -1.21821797 +v -10.2392855 11.9397192 -1.203269 +v -9.86300755 12.37181 -1.263345 +v -9.33264828 13.2654953 -1.36530399 +v -9.44604015 13.3729153 -1.27883899 +v -8.71804047 14.9804049 -1.44271004 +v -8.64098167 14.9180365 -1.50778902 +v -7.56404305 17.3859196 -1.85352194 +v -6.28075409 20.2576523 -2.39043403 +v -4.99311018 23.1299839 -3.0369761 +v -3.80022812 25.5690784 -3.71873498 +v -3.80059004 25.569376 -3.71868706 +v -2.99155903 27.2862091 -4.15331602 +v -2.89962697 27.1539803 -4.22619009 +v -2.26068902 28.2092705 -4.5293498 +v -1.73450303 28.8218994 -4.6819582 +v -0.639178991 29.3200703 -4.79455614 +v -0.617918015 29.1575394 -4.85641623 +v -0.0282760002 29.2612057 -4.85976124 +v 0.561374009 29.1575546 -4.85217094 +v 0.582157016 29.3204651 -4.79003477 +v 1.11782503 28.9883575 -4.80244398 +v 1.67646301 28.8216267 -4.6698451 +v 2.20156288 28.2093086 -4.51330614 +v 2.83828306 27.1539268 -4.20551586 +v 2.92963004 27.2861462 -4.13205099 +v 3.73566794 25.5695457 -3.69142509 +v 3.73530293 25.5692425 -3.69147801 +v 4.92310286 23.1300297 -3.00123501 +v 6.20608902 20.2576427 -2.34543109 +v 7.41716194 17.3557072 -1.84363198 +v 8.54351044 14.9046116 -1.45937002 +v 8.51971245 14.8859224 -1.47337902 +v 9.36338997 13.3728962 -1.21104395 +v 9.25051785 13.2654676 -1.29839206 +v 9.78013039 12.3717718 -1.19260895 +v 10.1558599 11.9396448 -1.12988102 +v 10.3600121 11.6258574 -1.18868899 +v 10.7219152 11.5308361 -1.14078295 +v 11.2464476 11.3117638 -1.22141099 +v 12.2538157 11.0956869 -1.357131 +v 12.3207254 11.236454 -1.26899099 +v 14.008152 10.9236975 -1.58012903 +v 14.0605211 11.0609913 -1.484375 +v 14.0181179 10.9516859 -1.56615496 +v 16.6847935 10.7469063 -1.97839999 +v 19.7868767 10.4579592 -2.608495 +v 22.8744698 10.1135139 -3.37754202 +v 25.5694275 9.80689812 -4.11338902 +v 25.5695858 9.80731964 -4.11332512 +v 27.4444942 9.57955742 -4.60545206 +v 27.3495827 9.44713688 -4.67949486 +v 28.5505066 9.16322613 -5.02328491 +v 29.3018513 8.84011269 -5.20546198 +v 30.1402512 7.9044528 -5.36721802 +v 29.9922066 7.83265114 -5.42704105 +v 30.4065113 7.31060219 -5.40281296 +v 30.3887596 6.64606905 -5.4670248 +v 30.5514832 6.6747098 -5.40865803 +v 30.4148426 6.03081179 -5.43523407 +v 30.445715 5.41209507 -5.32032299 +v 30.0403824 4.68916178 -5.17227602 +v 29.2521381 3.71805501 -4.86869001 +v 29.4090557 3.66476488 -4.80082989 +v 28.0434589 2.326612 -4.35740519 +v 28.0430698 2.3268671 -4.35743999 +v 26.0977211 0.389665991 -3.65904593 +v 23.7743149 -1.73293102 -2.99386501 +v 21.4329815 -3.83866405 -2.43414497 +v 19.3378239 -5.56626797 -2.10664392 +v 19.4668674 -5.65548611 -2.02006292 +v 19.362009 -5.58312607 -2.09236097 +v 18.1740971 -6.84740686 -1.82777095 +v 18.0360203 -6.76772881 -1.909235 +v 17.3423958 -7.54279709 -1.80303299 +v 17.042572 -8.03331375 -1.74388099 +v 16.8176498 -8.69899559 -1.76534796 +v 16.8108883 -9.27660847 -1.83035803 +v 16.8422527 -10.2973661 -2.02185106 +v 17.174757 -12.0335703 -2.29721999 +v 17.2047424 -12.0354824 -2.28410196 +v 17.784153 -14.704524 -2.7698319 +v 18.3092613 -17.7242641 -3.49338603 +v 18.8315296 -20.7734356 -4.310987 +v 19.1972599 -23.4691734 -5.13649988 +v 19.1705341 -23.4630661 -5.14844322 +v 19.4143009 -25.3310509 -5.68684101 +v 19.264492 -25.2727547 -5.75593281 +v 19.2280712 -26.4813385 -6.14655209 +v 19.1705818 -27.3137417 -6.32115889 +v 18.7865486 -27.7748451 -6.47326088 +v 18.5135155 -28.3670979 -6.48841715 +v 18.408596 -28.2414913 -6.54293013 +v 17.9697094 -28.6751366 -6.5685339 +v 17.4226627 -28.9368153 -6.573699 +v 17.4921741 -29.0878563 -6.52016783 +v 16.8708553 -29.1269302 -6.53280401 +v 16.3215237 -29.3246441 -6.40956879 +v 15.5696898 -29.1615963 -6.23211622 +v 14.4929562 -28.6495895 -5.89979982 +v 14.4888573 -28.8167152 -5.83576822 +v 12.922514 -27.9212666 -5.32277298 +v 12.9271774 -27.8921757 -5.33390522 +v 10.6680555 -26.5847206 -4.56738615 +v 8.07846546 -25.0359669 -3.78122711 +v 5.47844219 -23.463974 -3.1139009 +v 3.25491309 -22.07864 -2.66468 +v 3.23030496 -22.1738338 -2.60875392 +v 1.74332702 -21.326889 -2.35246611 +v 1.77208698 -21.1679573 -2.43081999 +v 0.846234977 -20.7587223 -2.2885921 +v 0.303097993 -20.6327877 -2.21104693 +v -0.378957003 -20.6327209 -2.21354508 +v -0.919776022 -20.7577534 -2.29460597 +v -1.83949995 -21.1636543 -2.44264197 +v -1.81114602 -21.3225327 -2.36405492 +v -3.31837296 -22.0387573 -2.69694996 +v -3.28128791 -22.1842384 -2.61202097 +v -3.37122297 -21.8575249 -2.7514081 +v -5.58168888 -23.2056332 -3.23274899 +v -8.14959526 -24.7875423 -3.90099192 +v -10.7255087 -26.3015842 -4.69171619 +v -12.9343023 -27.6163006 -5.44992113 +v -12.9343548 -27.6159573 -5.45004177 +v -14.509428 -28.4436569 -6.01138687 +v -15.5142155 -28.8967495 -6.34506512 +v -16.2287426 -29.0075932 -6.53789711 +v -16.767498 -28.8728199 -6.62573385 +v -17.3079262 -28.7131691 -6.67105913 +v -17.8409405 -28.4957485 -6.67815399 +v -18.2349873 -28.0568161 -6.6474061 +v -18.5855045 -27.5875263 -6.574821 +v -18.907053 -27.1032524 -6.46779823 +v -19.071043 -26.452528 -6.29212999 +v -19.1213264 -25.606987 -6.03870201 +v -19.1963654 -24.4572067 -5.63755417 +v -19.0906258 -24.4435596 -5.66065598 +v -18.5314503 -21.197197 -4.65225887 +v -18.2261143 -19.4869881 -4.1410532 +v -17.7753429 -17.6964245 -3.63731909 +v -17.7746029 -17.6966209 -3.63749599 +v -17.3053474 -16.3239231 -3.26834011 +v -16.8592453 -15.0275249 -2.90619206 +v -16.2766724 -14.0136728 -2.63211203 +v -15.7387581 -13.0102816 -2.30892992 +v -14.9099646 -12.2082882 -2.08544993 +v -13.9213324 -11.3743439 -1.80016899 +v -12.6544266 -10.5241108 -1.49585903 +v -10.5504713 -9.31682968 -1.07170498 +v -10.6254797 -9.22846222 -1.01700306 +v -7.87935877 -7.89492702 -0.626533985 +v -4.90206003 -6.35206318 -0.25228101 +v -1.973194 -4.92264318 -0.0422639996 +v 0.467328012 -3.73512912 0.0392110012 +v 0.402808011 -3.66404796 0.0887459964 +v 2.04438305 -2.98451591 0.0269300006 +v 2.82862997 -2.46684098 0.0407559983 +v 1.90308106 -2.86486506 0.117822997 +v 3.1508801 -2.16698909 0.0495470017 +v 3.30515099 -1.89400601 0.0404490009 +v 3.27995491 -1.49636602 0.0704170018 +v 3.36744499 -1.43468201 0.0320109986 +v 3.22063994 -1.10176504 0.0567159988 +v 3.00441909 -0.942498028 0.0991040021 +v 3.05338907 -0.758280993 0.0421580002 +v 2.87049389 -0.856388986 0.120118998 +v 2.61945105 -0.694359004 0.118741997 +v 2.19049001 -0.564019024 0.0742390007 +v 1.231143 -0.724931002 0.107836999 +v -0.477173001 -1.08348894 0.154534996 +v -0.47112599 -0.986809015 0.110179998 +v -3.18635297 -1.45408404 0.0445169993 +v -6.46757317 -1.91201401 -0.184939995 +v -9.87553978 -2.50236702 -0.514410973 +v -12.9877329 -3.0654099 -0.896322012 +v -12.9775906 -2.88670993 -0.965315998 +v -16.9381123 -3.26867104 -1.65366101 +v -18.4960098 -3.07142401 -2.01149511 +v -19.7779751 -2.82925391 -2.285532 +v -21.036911 -2.39372706 -2.5863781 +v -22.1293011 -1.93923199 -2.83795905 +v -23.2370319 -1.31914997 -3.12793803 +v -24.5448303 -0.557537973 -3.44878411 +v -24.5451984 -0.557996988 -3.44857788 +v -26.2688541 0.966179013 -4.047328 +v -28.8419628 3.38418102 -4.98068523 +v -28.8142357 3.40128303 -4.98620796 +v -29.4843483 4.17180395 -5.25065899 +v -29.909729 4.82260799 -5.43587589 +v -30.1575947 5.42252207 -5.5646019 +v -30.1882133 6.01326323 -5.6415472 +v -30.1928749 6.60837317 -5.67586994 +v -30.075943 7.18919802 -5.67378521 +v -29.828825 7.72698498 -5.639112 +v -29.6048698 8.30348873 -5.55255699 +v -29.1032295 8.65613747 -5.45781612 +v -28.3701687 8.95359898 -5.25569105 +v -27.3013458 9.29367256 -4.90770578 +v -25.4684086 9.61141586 -4.34567881 +v -25.4684658 9.61148357 -4.34557009 +v -22.8174419 9.93848515 -3.59598207 +v -19.7133656 10.1579227 -2.850214 +v -16.6545067 10.5210972 -2.19577694 +v -14.0148392 10.7514744 -1.74434602 +v -14.0038815 10.7186785 -1.75350595 +v -12.2852716 10.9446821 -1.49987698 +v -11.2434072 11.1389618 -1.37081099 +v -10.6794624 11.326623 -1.31972897 +v -10.4269381 11.6038914 -1.27631104 +v -10.0844212 11.7567387 -1.30380201 +v -9.72541428 12.2371683 -1.33026099 +v -9.20340157 13.1752243 -1.41793704 +v -8.53530025 14.834981 -1.57102203 +v -8.47831345 14.7914219 -1.59324503 +v -7.37477922 17.2378712 -1.952479 +v -6.11201 20.1209316 -2.47782207 +v -4.81107807 22.9755211 -3.13176394 +v -3.64971995 25.4179039 -3.78525901 +v -3.63330603 25.4020004 -3.79013109 +v -2.76802206 27.0797176 -4.2803359 +v -2.12552094 27.9919796 -4.58231306 +v -1.63240004 28.59552 -4.751297 +v -1.165977 28.9608383 -4.81945515 +v -0.582454979 28.9938431 -4.88323116 +v -0.0280809999 29.0620327 -4.89579582 +v 0.526216984 28.9939747 -4.8792448 +v 1.05977595 28.8087311 -4.82573414 +v 1.57493198 28.5955601 -4.73977518 +v 2.06675792 27.9919415 -4.56722498 +v 2.70717001 27.0796013 -4.26056623 +v 3.56884408 25.4020882 -3.76417398 +v 3.58519006 25.417963 -3.75918603 +v 4.74177694 22.975523 -3.09734297 +v 6.03796196 20.1209297 -2.43405008 +v 7.27370024 17.2212181 -1.90374196 +v 8.37405872 14.7736855 -1.53730595 +v 8.34808254 14.7538929 -1.545856 +v 9.12171841 13.1752338 -1.35192597 +v 9.64291382 12.2370443 -1.26053405 +v 10.0017576 11.7567472 -1.23145795 +v 10.2524118 11.4756279 -1.23920095 +v 10.5967979 11.3264685 -1.24311399 +v 11.16255 11.1389027 -1.29024506 +v 12.2093391 10.9442806 -1.41235006 +v 13.9377108 10.7169142 -1.65505302 +v 13.9481983 10.748457 -1.64611006 +v 16.6051636 10.5190535 -2.08102202 +v 19.7133274 10.251812 -2.69954705 +v 22.8027554 9.90788269 -3.45167589 +v 25.4756813 9.6094656 -4.18301392 +v 25.4756794 9.609519 -4.18309402 +v 27.3190422 9.29441929 -4.7360301 +v 28.3894634 8.95905399 -5.07741785 +v 29.1213493 8.66664791 -5.2746911 +v 29.6220856 8.31906509 -5.36622906 +v 29.8488331 7.74547577 -5.45232821 +v 30.1614914 7.23111486 -5.48555422 +v 30.2213783 6.63049793 -5.48986387 +v 30.2250404 6.033535 -5.45306921 +v 30.1955585 5.44563389 -5.38250494 +v 29.7870064 4.75762701 -5.21698189 +v 29.1336136 3.82297301 -4.91588688 +v 27.8484726 2.43285489 -4.41697884 +v 27.8484306 2.43290496 -4.41688919 +v 25.8994312 0.532191992 -3.74817991 +v 23.5912647 -1.605425 -3.0742569 +v 21.232439 -3.69583511 -2.52528906 +v 19.1841297 -5.45378494 -2.15234995 +v 19.1826897 -5.45323801 -2.15676808 +v 17.9105606 -6.66824818 -1.95680702 +v 17.1719341 -7.44814301 -1.863778 +v 16.8202858 -7.93562698 -1.83686697 +v 16.7756062 -8.3117466 -1.81496894 +v 16.5841846 -8.63560295 -1.85930502 +v 16.6331348 -9.24701977 -1.90579998 +v 16.4673462 -9.22302628 -1.93347299 +v 16.6838493 -10.2972021 -2.07267308 +v 16.9560528 -12.0232067 -2.36562204 +v 16.989809 -12.0244865 -2.35759807 +v 17.5088272 -14.6434908 -2.87052798 +v 18.0918198 -17.7056885 -3.57735991 +v 18.5960045 -20.765543 -4.40732718 +v 18.9601707 -23.4233456 -5.21176577 +v 18.9611835 -23.42346 -5.21174097 +v 19.1139736 -25.280468 -5.80784988 +v 19.0402851 -26.3970261 -6.1707449 +v 18.9631176 -27.1827106 -6.38243103 +v 18.6424294 -27.6593227 -6.48915577 +v 18.2920647 -28.1251621 -6.56290293 +v 17.8640289 -28.5079498 -6.5949688 +v 17.3655758 -28.778904 -6.5917592 +v 16.8245659 -28.9424362 -6.54563522 +v 16.2858047 -29.0733986 -6.46529388 +v 15.562789 -28.8697224 -6.27835417 +v 14.5575857 -28.5060158 -5.94605303 +v 12.9684935 -27.6759109 -5.38944197 +v 12.9684048 -27.6763592 -5.38933182 +v 10.7398281 -26.3475704 -4.63860893 +v 8.13928032 -24.8210793 -3.8576169 +v 5.5471611 -23.2267933 -3.20081902 +v 3.31614399 -21.868782 -2.7306869 +v 3.28638506 -21.9669228 -2.70886397 +v 1.82338405 -21.0160217 -2.47572708 +v 0.878735006 -20.5650196 -2.34514093 +v 0.321271002 -20.3877335 -2.29827595 +v -0.0377210006 -20.4666901 -2.26848292 +v -0.396492988 -20.3877335 -2.30085897 +v -0.951875985 -20.5640659 -2.35144806 +v -1.89050996 -21.011879 -2.48790407 +v -3.3808651 -21.8255539 -2.75824499 +v -3.45861197 -21.5938644 -2.75099802 +v -5.682374 -22.9227638 -3.23253298 +v -8.26450825 -24.4945164 -3.9076879 +v -10.8223743 -26.0516777 -4.6875248 +v -13.0056019 -27.3483562 -5.4375639 +v -13.0056286 -27.3483868 -5.43742085 +v -15.5109482 -28.7167645 -6.33568907 +v -14.5186758 -28.1617146 -5.97620583 +v -15.5307302 -28.5848198 -6.317348 +v -16.2094383 -28.714468 -6.50160599 +v -16.7156544 -28.6464119 -6.58136082 +v -17.2042694 -28.4743404 -6.62687397 +v -17.6594181 -28.2098618 -6.63608503 +v -18.0645142 -27.8650436 -6.60496902 +v -18.4019299 -27.4495602 -6.53985691 +v -18.6547852 -26.9752254 -6.43793392 +v -18.8136921 -26.3644676 -6.27513885 +v -18.8695812 -25.5412235 -6.02120018 +v -18.9729729 -24.4330349 -5.67014217 +v -18.8105183 -24.4250221 -5.66084099 +v -18.8104305 -24.4245319 -5.66254807 +v -18.6222363 -22.9346199 -5.18456078 +v -18.3264275 -21.2248478 -4.65385818 +v -17.9481697 -19.4515934 -4.13309193 +v -17.5177841 -17.776783 -3.66362691 +v -17.5177898 -17.776804 -3.66355991 +v -17.0694656 -16.3626347 -3.27895808 +v -16.5843964 -15.1641207 -2.95608902 +v -16.04245 -14.1372585 -2.66775703 +v -15.5558004 -13.1333971 -2.38071799 +v -15.4218988 -13.2344923 -2.40154099 +v -14.6995754 -12.4082165 -2.14668298 +v -13.7250729 -11.571044 -1.86378205 +v -12.3442612 -10.6356573 -1.52364099 +v -10.3989706 -9.51184464 -1.124717 +v -10.5022993 -9.3777771 -1.09252799 +v -7.72923803 -8.11080265 -0.692133009 +v -10.3989878 -9.51191044 -1.12447703 +v -4.724195 -6.59945297 -0.339047015 +v -1.78927195 -5.14857578 -0.119034998 +v 0.64665103 -3.9353981 -0.0322090015 +v 0.531898975 -3.80694294 0.00626000017 +v 2.18522811 -3.12494993 -0.0183479991 +v 0.646848023 -3.93565202 -0.0307490006 +v 3.03296208 -2.61357188 -0.0257329997 +v 3.40409088 -2.29633188 -0.0295739993 +v 3.51204705 -2.06760812 -0.0283070002 +v 3.57101393 -1.82166302 -0.0248470008 +v 3.58448195 -1.56947398 -0.0189139992 +v 3.55487108 -1.32231104 -0.0152770001 +v 3.48646092 -1.09152699 -0.00982199982 +v 3.38246894 -0.888127983 -0.00349500007 +v 3.2476871 -0.713038027 0.00437500002 +v 3.08629107 -0.567983985 0.00923299976 +v 2.66318989 -0.57080698 0.0634950027 +v 2.90409088 -0.453474998 0.0171879996 +v 2.70507407 -0.371372014 0.0219810009 +v 2.24329805 -0.360731006 0.0351839997 +v 1.27111995 -0.462615997 0.0497270003 +v -0.465079993 -0.887996018 0.0814500004 +v -0.454677999 -0.715499997 0.0526160002 +v -0.45469299 -0.715755999 0.0510779992 +v -3.17676592 -1.15982604 -0.0188909993 +v -6.47154093 -1.69986701 -0.219996005 +v -9.88382339 -2.23684406 -0.567698002 +v -12.9762583 -2.8429389 -0.974795997 +v -12.9731579 -2.67189193 -0.998993993 +v -12.9731102 -2.67192411 -0.999239028 +v -15.3224707 -2.91009998 -1.40448296 +v -17.1107616 -2.96637607 -1.75120294 +v -18.5141239 -2.85770988 -2.04838896 +v -19.7080975 -2.59966612 -2.31421089 +v -20.8642921 -2.20850205 -2.58502793 +v -22.0035629 -1.68428004 -2.86589599 +v -23.1452427 -1.027668 -3.1687541 +v -24.4059105 -0.376255006 -3.49530602 +v -24.3099823 -0.238057002 -3.49669504 +v -24.4059887 -0.376367003 -3.495296 +v -25.5153236 0.683430016 -3.86674905 +v -24.3099823 -0.238057002 -3.49669504 +v -26.6849117 1.66686904 -4.256423 +v -27.743845 2.64202499 -4.63400316 +v -28.6183567 3.537431 -4.97215605 +v -29.2386131 4.28324986 -5.22990417 +v -28.6187267 3.53768301 -4.970397 +v -29.6428871 4.91892385 -5.41577387 +v -29.869812 5.48516607 -5.533916 +v -29.9548283 6.02123308 -5.60209179 +v -29.9327335 6.56668091 -5.63806295 +v -29.8104782 7.10119009 -5.63996506 +v -29.5952454 7.60454702 -5.6040349 +v -29.4207821 8.16288757 -5.56146717 +v -29.2923756 8.05616474 -5.53513384 +v -28.9076557 8.43571568 -5.43623304 +v -28.253952 8.76397228 -5.23761702 +v -27.1389713 9.06209087 -4.88823795 +v -25.3665581 9.35098362 -4.34985209 +v -25.3665409 9.35098076 -4.34993505 +v -22.7381516 9.65077972 -3.61119199 +v -19.6584606 9.94924831 -2.85240507 +v -16.5683327 10.2319202 -2.212883 +v -13.93326 10.4834461 -1.76102602 +v -12.1875658 10.6920643 -1.51355302 +v -13.9333916 10.4833851 -1.75943696 +v -11.1262636 10.8795128 -1.39079499 +v -10.5351658 11.0678091 -1.33605301 +v -10.2936287 11.4178228 -1.32085598 +v -10.1951714 11.2793055 -1.31995201 +v -9.8873148 11.5362883 -1.31868303 +v -9.51903343 12.0424318 -1.34783399 +v -8.99774075 13.0010338 -1.42868304 +v -8.37178993 14.7125568 -1.60950899 +v -8.23197269 14.6113186 -1.60864305 +v -8.23203659 14.6112156 -1.61024106 +v -7.13334084 17.0630894 -1.96533597 +v -5.855268 19.9460278 -2.4977839 +v -4.57293606 22.8082676 -3.14239693 +v -3.44502902 25.2362232 -3.78639007 +v -3.44486809 25.2365551 -3.78473496 +v -2.61345601 26.8453617 -4.25864077 +v -1.99195004 27.8186302 -4.5628581 +v -1.49476504 28.3387318 -4.73043394 +v -1.09462595 28.7413902 -4.83076 +v -1.03862703 28.5848217 -4.80592012 +v -0.543226004 28.731966 -4.852736 +v -0.0282179993 28.7806835 -4.86854601 +v 0.486642003 28.731966 -4.84904194 +v 0.981770992 28.5845776 -4.8004818 +v 1.43718398 28.338728 -4.71989393 +v 1.93308401 27.8186302 -4.548738 +v 2.55232 26.8454647 -4.24004221 +v 3.3802619 25.236557 -3.760144 +v 3.38043499 25.2362232 -3.7618041 +v 4.50366783 22.8082905 -3.10971594 +v 5.78138304 19.9459724 -2.45585799 +v 7.05559921 17.063076 -1.91422701 +v 8.15172195 14.6112165 -1.55122995 +v 8.91611481 13.0010138 -1.36416698 +v 8.15164757 14.6113186 -1.54963899 +v 9.43676281 12.0424423 -1.27955401 +v 9.80480289 11.5362873 -1.24775195 +v 10.1126451 11.2793036 -1.248631 +v 10.4526138 11.0678082 -1.26043797 +v 11.0455484 10.8794127 -1.311077 +v 12.1118517 10.6916561 -1.42674303 +v 13.8676243 10.4823685 -1.66139305 +v 13.867425 10.4824343 -1.662974 +v 16.5196648 10.2298555 -2.0988369 +v 19.6334476 9.94601154 -2.72515988 +v 22.7300949 9.64755821 -3.46295404 +v 25.3745193 9.3488493 -4.18818188 +v 25.3745384 9.34885025 -4.18808508 +v 27.1554623 9.06288528 -4.71714306 +v 28.2732964 8.76880836 -5.06015778 +v 28.9268036 8.44503403 -5.25463676 +v 29.4390507 8.17721462 -5.37650299 +v 29.3112087 8.06967354 -5.35113001 +v 29.6159229 7.62127018 -5.41886187 +v 29.8347187 7.12008905 -5.45472288 +v 29.9613419 6.58658981 -5.45361614 +v 29.9876728 6.04083586 -5.420928 +v 29.9071884 5.50348902 -5.35287905 +v 29.5807934 4.83882618 -5.19068384 +v 28.8652973 3.910151 -4.88688517 +v 27.6118336 2.58100295 -4.40841198 +v 27.6118183 2.58100104 -4.408494 +v 25.6651516 0.717745006 -3.74889207 +v 23.3446064 -1.40589201 -3.0835681 +v 20.9919186 -3.51255703 -2.52811909 +v 18.9883556 -5.30452204 -2.15283608 +v 17.6835861 -6.52003479 -1.95889199 +v 18.9884739 -5.30446577 -2.15124607 +v 16.9253979 -7.30551386 -1.87227702 +v 16.5520515 -7.80969715 -1.84244001 +v 16.5583153 -8.2365942 -1.85250604 +v 16.3976078 -8.18214989 -1.84640801 +v 16.2952652 -8.57178211 -1.866907 +v 16.2876587 -9.19736958 -1.92932999 +v 16.415062 -10.2761717 -2.0790019 +v 16.7139225 -12.0226936 -2.3650701 +v 16.7138882 -12.0224705 -2.36667895 +v 17.2101707 -14.6397152 -2.87951803 +v 17.7807522 -17.7090931 -3.59372592 +v 18.3059902 -20.7682209 -4.41560698 +v 18.690815 -23.3945847 -5.20929003 +v 18.6908169 -23.3945656 -5.20932913 +v 18.8593807 -25.1843967 -5.78072405 +v 18.8551064 -26.3379574 -6.14764404 +v 18.7124157 -27.0523949 -6.35331917 +v 18.4594555 -27.5209217 -6.45507622 +v 18.1220322 -27.9328499 -6.52127123 +v 17.7169666 -28.2760201 -6.55446386 +v 17.261879 -28.5400105 -6.54798698 +v 16.7726879 -28.7118492 -6.50733709 +v 16.2663479 -28.7803345 -6.42888117 +v 15.5849915 -28.6497707 -6.24786091 +v 14.5662174 -28.223814 -5.91046476 +v 13.0405979 -27.4045067 -5.37655687 +v 13.0405731 -27.4044743 -5.37670183 +v 10.8368921 -26.0976753 -4.63372183 +v 8.25445938 -24.5281677 -3.86350799 +v 5.64780998 -22.9439716 -3.19984293 +v 3.40371704 -21.6044521 -2.72970009 +v 3.34829903 -21.7690277 -2.73736691 +v 1.88939202 -20.7531433 -2.47272992 +v 3.40385699 -21.6045856 -2.72814012 +v 0.933108985 -20.2849197 -2.34781098 +v 0.353302985 -20.0922375 -2.29700994 +v -0.0376139991 -20.2350197 -2.30001998 +v -0.0376470014 -20.0647678 -2.2894671 +v -0.428541988 -20.0922375 -2.29982901 +v -1.00623596 -20.2838974 -2.3545239 +v -1.95646703 -20.7488823 -2.48538208 +v -3.45876193 -21.593998 -2.7494359 +v 1.88789403 -20.7528152 -2.46915793 +v 0.933282971 -20.2854195 -2.34317493 +v 3.40385699 -21.6045856 -2.72814012 +v 0.81706202 -19.164217 -2.07895207 +v 0.353412986 -20.0923576 -2.29551411 +v -0.0376470014 -20.0647678 -2.2894671 +v -0.428662002 -20.0925217 -2.29762912 +v -1.95612204 -20.748867 -2.4832499 +v 10.7647076 -26.0543213 -4.60934114 +v 11.6617517 -26.2506123 -4.788445 +v 13.0409584 -27.4047871 -5.37475204 +v 9.11920357 -24.7918015 -4.02423716 +v 8.24853611 -24.5246906 -3.85812712 +v 8.87903118 -24.4084225 -3.88709402 +v 7.32977104 -23.6532459 -3.518291 +v 6.66328192 -23.0952301 -3.31173301 +v 5.62523603 -22.9312229 -3.19046402 +v 6.60455704 -22.307621 -3.09335589 +v 5.69551706 -22.1886978 -2.99684 +v 16.2671471 -28.7814083 -6.42514181 +v 16.7729664 -28.7121792 -6.50555277 +v 15.5851793 -28.6501408 -6.24681187 +v 14.5654182 -28.2247562 -5.90508604 +v 13.502162 -27.2081242 -5.38068819 +v 18.1226273 -27.9337864 -6.51787281 +v 18.459589 -27.5213547 -6.4532752 +v 17.7173214 -28.2767544 -6.55092812 +v 17.2623215 -28.5407543 -6.54520512 +v 18.8558598 -26.3391018 -6.14435911 +v 18.7124748 -27.0528412 -6.35187578 +v 18.8593807 -25.1843967 -5.78072405 +v 17.6812 -25.4692802 -5.60562801 +v 18.6908245 -23.3936195 -5.20656013 +v 17.1661835 -14.4036722 -2.82542491 +v 17.2397423 -16.0032997 -3.13022709 +v 16.7139225 -12.0226936 -2.3650701 +v 16.8095455 -14.8163204 -2.82749701 +v 17.7914505 -17.7661705 -3.60278893 +v 17.3705196 -17.2925797 -3.41623592 +v 17.9452572 -19.3579712 -3.98984098 +v 17.9233437 -20.0243587 -4.14508009 +v 18.3155155 -20.8262253 -4.4293561 +v 17.7027245 -22.1436329 -4.64100885 +v 18.0428295 -23.8903141 -5.20395088 +v 15.7574635 -10.3694458 -1.96296597 +v 16.4150181 -10.2758026 -2.07726407 +v 16.2877941 -9.19739628 -1.92859006 +v 16.2954102 -8.57202339 -1.86477995 +v 16.3976078 -8.18214989 -1.84640801 +v 17.6832981 -6.52088213 -1.95535505 +v 16.9261513 -7.30569887 -1.86591005 +v 18.9884739 -5.30446577 -2.15124607 +v 16.5521507 -7.80965996 -1.84094095 +v 19.8268471 -3.87835503 -2.26079988 +v 21.0385647 -3.47112393 -2.53549504 +v 23.266489 -1.47687602 -3.0588131 +v 25.6158657 0.670234978 -3.72731495 +v 27.6121655 2.58123398 -4.40657377 +v 23.3617897 -0.229674995 -3.07380605 +v 24.5779095 0.830008984 -3.42086196 +v 18.2738857 -5.2533021 -1.99195099 +v 29.909277 5.50401592 -5.34643793 +v 29.988018 6.04098415 -5.41911602 +v 29.5807934 4.83882618 -5.19068384 +v 27.3927078 3.44852304 -4.36171579 +v 28.8652973 3.910151 -4.88688517 +v 25.9901752 2.03344893 -3.86744094 +v 29.6162205 7.62133121 -5.41730404 +v 28.5161705 7.12662315 -4.98139811 +v 28.9278297 8.44514465 -5.25136709 +v 29.3112087 8.06967354 -5.35113001 +v 29.8354073 7.12020016 -5.4511652 +v 29.9616489 6.58662415 -5.45205116 +v 28.2732964 8.76880836 -5.06015778 +v 27.1576786 9.0631752 -4.71319199 +v 25.7639484 8.25847435 -4.18686008 +v 25.3744373 9.34908104 -4.18448591 +v 16.5407505 10.22791 -2.100981 +v 14.5005093 10.2785196 -1.73827195 +v 13.8426418 10.485671 -1.65453506 +v 18.3068161 9.90722847 -2.4221251 +v 19.6298523 9.94624615 -2.72062397 +v 19.3182812 9.86823368 -2.6383009 +v 19.9435558 9.3871851 -2.72699594 +v 21.5382652 9.58525944 -3.13870192 +v 22.8139954 9.63876343 -3.48217797 +v 24.5029297 8.02099895 -3.78470898 +v 23.1914082 7.813972 -3.39250708 +v 10.4528017 11.067831 -1.25836599 +v 10.1126785 11.2793055 -1.24679697 +v 11.0458403 10.8793926 -1.30682898 +v 9.80474472 11.5363665 -1.24624801 +v 8.23177147 11.4491262 -1.07112396 +v 9.43668556 12.0425463 -1.27594399 +v 9.24077511 10.6615076 -1.06952202 +v 8.91651154 13.0005198 -1.36156797 +v 8.00388813 13.0516376 -1.28093004 +v 8.1520071 14.6109304 -1.54861903 +v 4.45514917 22.9171963 -3.1312511 +v 3.38022804 25.2366199 -3.75988507 +v 5.40777397 20.3452301 -2.52217102 +v 5.78304577 19.942091 -2.45134401 +v 5.52793121 19.615799 -2.35740304 +v 6.21637201 18.3702145 -2.12032294 +v 6.40182304 17.5218754 -1.95491695 +v 7.05265999 17.0696316 -1.91382897 +v 6.43897104 16.399435 -1.73288596 +v 7.12181997 15.6013985 -1.63399303 +v 4.10745001 22.9879894 -3.13518596 +v 3.20942807 24.8745327 -3.64219999 +v 1.43700302 28.3390617 -4.71841812 +v 0.981693029 28.5857487 -4.79626179 +v 1.93331099 27.8193855 -4.54581594 +v 2.55232 26.8454647 -4.24004221 +v 2.14661288 25.7758751 -3.88679409 +v -0.0282090008 28.7821503 -4.86255598 +v 0.486638993 28.7322369 -4.84747696 +v -0.54323101 28.7322369 -4.85117197 +v -1.03864706 28.5850334 -4.80529594 +v -1.49464905 28.3394699 -4.7277379 +v -2.61216998 26.8490715 -4.25604582 +v -3.44483495 25.2366199 -3.78447008 +v -1.99195898 27.8186455 -4.56278801 +v -2.59400606 25.9218159 -3.96083689 +v -7.13257122 17.0652122 -1.96344495 +v -6.33424902 18.4374943 -2.185606 +v -8.23197269 14.6113186 -1.60864305 +v -5.52384615 20.3473454 -2.56462407 +v -5.85698891 19.942091 -2.49326491 +v -5.1009841 20.8054123 -2.65165401 +v -4.71205711 22.00173 -2.9325881 +v -3.82900906 23.6065044 -3.32021689 +v -4.56891918 22.8163147 -3.14418006 +v -3.02300406 24.4954967 -3.54240489 +v -7.41130781 11.4917812 -1.04887199 +v -8.00239563 13.2087021 -1.35518599 +v -8.99865913 13.0000219 -1.42463899 +v -9.51994228 12.0422468 -1.34028804 +v -6.98359108 16.12183 -1.76487398 +v -6.25855207 17.6475983 -2.0117929 +v -5.47075701 19.0991707 -2.27012706 +v -9.88728809 11.5363884 -1.31701803 +v -10.1271658 10.4355202 -1.202788 +v -10.1951714 11.2793055 -1.31995201 +v -10.5353069 11.0677528 -1.33456099 +v -11.1265297 10.8794861 -1.38666403 +v -12.1872139 10.6920872 -1.51181495 +v -13.9333916 10.4833851 -1.75943696 +v -22.7538109 9.64931488 -3.61252308 +v -21.4514694 9.69312668 -3.26873994 +v -25.3663692 9.35124874 -4.34610891 +v -19.1798649 9.89543343 -2.73155403 +v -21.5612946 9.39770603 -3.26198912 +v -19.6584606 9.94924831 -2.85240507 +v -18.0405865 9.94200611 -2.4837079 +v -16.5793133 10.23069 -2.21410489 +v -13.8977318 10.3048925 -1.730165 +v -26.2550526 8.35325813 -4.52052116 +v -27.1396885 9.06234932 -4.88604403 +v -23.8999596 8.91339493 -3.85694695 +v -28.9083958 8.43573284 -5.43383217 +v -29.2927303 8.05624008 -5.53420496 +v -28.2542763 8.76408386 -5.23663998 +v -29.5955544 7.60460377 -5.60246801 +v -29.8114338 7.10137796 -5.63572979 +v -29.933136 6.56673002 -5.63626719 +v -29.9552937 6.0212822 -5.60082912 +v -29.2392769 4.28344297 -5.22725105 +v -28.6198368 3.53781104 -4.96732807 +v -29.6441669 4.9193368 -5.41049194 +v -29.8701572 5.48537683 -5.5323391 +v -27.743742 2.64151311 -4.63119793 +v -26.6901131 1.67056203 -4.25143194 +v -27.4955616 3.22644901 -4.56698418 +v -25.5152245 0.683251023 -3.86483002 +v -24.3096905 -0.238635004 -3.49485803 +v -23.1456413 -1.02765 -3.16589999 +v -22.0036182 -1.68430805 -2.86553001 +v -20.8645077 -2.20840311 -2.58289289 +v -19.7084427 -2.59942007 -2.31258893 +v -15.3228426 -2.909904 -1.40296602 +v -15.6506929 -2.84713292 -1.45919597 +v -12.9731579 -2.67189193 -0.998993993 +v -3.18251109 -1.16052902 -0.0176839996 +v -6.26348114 -1.66817498 -0.199269995 +v -2.98564696 -0.841489971 -0.00758600002 +v -0.454677999 -0.715499997 0.0526160002 +v -7.76736403 -1.87105095 -0.331607997 +v -5.88409901 -1.381621 -0.170635998 +v -9.13201618 -1.96440697 -0.475131989 +v -9.89719009 -2.23933506 -0.567696989 +v -12.328702 -2.60082889 -0.898474991 +v -12.338191 -2.60203195 -0.901152015 +v -10.0880461 -2.26298308 -0.589155972 +v -12.9731579 -2.67189193 -0.998993993 +v -13.5775938 -2.74332905 -1.09672999 +v -14.1679344 -2.78028202 -1.19458401 +v -14.1648989 -2.78015208 -1.19508803 +v -13.5778856 -2.74336004 -1.09686601 +v -14.9997091 -2.79226708 -1.34063005 +v -14.1648989 -2.78015208 -1.19508803 +v 2.70511389 -0.37112999 0.0236729998 +v 2.90409088 -0.453474998 0.0171879996 +v 1.385553 0.403111994 0.0524090007 +v -5.14716816 -1.02084196 -0.114450999 +v 3.24772692 -0.713054001 0.005229 +v 3.08654809 -0.567826986 0.0108009996 +v 3.38271308 -0.887960017 -0.00194300001 +v 3.48680902 -1.09144294 -0.00801800005 +v 3.57133889 -1.82166803 -0.0232790001 +v 3.512393 -2.06775403 -0.0265069995 +v 3.58448195 -1.56947398 -0.0189139992 +v 4.38616085 -0.736558974 -0.0404810011 +v 3.55519295 -1.32234001 -0.0137400003 +v 2.18522811 -3.12494993 -0.0183479991 +v 3.03322601 -2.61374688 -0.023937 +v 3.03244901 -4.22830009 -0.084973 +v 0.646848023 -3.93565202 -0.0307490006 +v 3.98898911 -2.37379098 -0.0525939986 +v 4.23422098 -3.90634894 -0.117357001 +v 3.40429497 -2.2965529 -0.0282259993 +v -7.72681093 -8.10992813 -0.69016403 +v -6.00714779 -7.61780596 -0.49950701 +v -8.93152905 -9.16277599 -0.915274978 +v -10.3989878 -9.51191044 -1.12447703 +v -4.60297823 -6.53997803 -0.322515011 +v -1.72067797 -5.11479092 -0.115557998 +v 1.81810498 -4.2496419 -0.0571690015 +v -11.5237675 -11.7085867 -1.54324901 +v -10.7627163 -10.2340174 -1.25139105 +v -14.6997023 -12.408658 -2.14515591 +v -15.4214439 -13.2335625 -2.39741302 +v -13.7249956 -11.5717335 -1.85913301 +v -12.3454523 -10.6367569 -1.52180696 +v -16.043211 -14.13832 -2.66337609 +v -16.5847759 -15.1651869 -2.95141006 +v -17.0695438 -16.3630142 -3.27737403 +v -17.0836067 -17.8307915 -3.57900906 +v -17.5178833 -17.7772846 -3.66182899 +v -17.948637 -19.4532471 -4.13130283 +v -18.3266754 -21.2248249 -4.6523509 +v -18.6222763 -22.9347839 -5.18298292 +v -18.8105183 -24.4250221 -5.66084099 +v -17.3442955 -23.7231979 -5.11852407 +v -18.6548576 -26.9756718 -6.43647099 +v -18.4029675 -27.4514103 -6.53403521 +v -18.8138771 -26.3654461 -6.271667 +v -18.0053558 -25.5529423 -5.82043982 +v -18.8696671 -25.5416298 -6.01960611 +v -17.2044258 -28.4746666 -6.62532091 +v -16.716053 -28.6471043 -6.57941723 +v -17.6601162 -28.2110577 -6.63118792 +v -18.0646858 -27.8653584 -6.60342312 +v -15.5310669 -28.5853977 -6.31574583 +v -16.2097778 -28.7147255 -6.50016117 +v -14.5186872 -28.1618233 -5.9758029 +v -5.68552685 -22.9247074 -3.23180294 +v -8.25870895 -24.4910946 -3.90229702 +vt 0.565008998 0.459580988 +vt 0.558318973 0.468914002 +vt 0.557350993 0.46471101 +vt 0.57209897 0.476336002 +vt 0.55853498 0.473221004 +vt 0.55805397 0.47744301 +vt 0.556931019 0.481386006 +vt 0.550360024 0.490330011 +vt 0.553005993 0.487848997 +vt 0.542542994 0.506143987 +vt 0.54736501 0.492282987 +vt 0.555221975 0.484862 +vt 0.54409802 0.493689001 +vt 0.700343013 0.647772014 +vt 0.711170971 0.666224003 +vt 0.72132802 0.663015008 +vt 0.541260004 0.494109988 +vt 0.539830029 0.505589008 +vt 0.544295013 0.492803991 +vt 0.54953599 0.488685012 +vt 0.547078013 0.490989 +vt 0.551595986 0.485908985 +vt 0.566060007 0.488474011 +vt 0.553183019 0.482679993 +vt 0.554472983 0.471091986 +vt 0.560661972 0.462309986 +vt 0.553574026 0.467186004 +vt 0.554673016 0.475093991 +vt 0.554225981 0.479014993 +vt 0.498564005 0.934010983 +vt 0.485762 0.917596996 +vt 0.460177988 0.92603898 +vt 0.505070984 0.498463988 +vt 0.534213006 0.494275987 +vt 0.519403994 0.492664993 +vt 0.512400985 0.912670016 +vt 0.482223988 0.900354028 +vt 0.499612004 0.883350015 +vt 0.521449029 0.877582014 +vt 0.504261971 0.848842978 +vt 0.527593017 0.848924994 +vt 0.54221499 0.870395005 +vt 0.545386016 0.885169029 +vt 0.465409011 0.856884003 +vt 0.478435993 0.876479983 +vt 0.482903987 0.846711993 +vt 0.551413 0.834410012 +vt 0.555006981 0.856163979 +vt 0.569225013 0.841017008 +vt 0.569921017 0.823513985 +vt 0.533502996 0.823092997 +vt 0.557702005 0.804700971 +vt 0.510017991 0.808597028 +vt 0.492850006 0.823804975 +vt 0.465938985 0.828288972 +vt 0.470245004 0.807591021 +vt 0.429908007 0.824199975 +vt 0.443450987 0.834227979 +vt 0.440730989 0.806713998 +vt 0.437483013 0.853643 +vt 0.577866971 0.782270014 +vt 0.583824992 0.811136007 +vt 0.555087984 0.782495022 +vt 0.528478026 0.792337 +vt 0.510632992 0.777172983 +vt 0.538797021 0.766335011 +vt 0.490530014 0.791175008 +vt 0.471184999 0.777190983 +vt 0.430081993 0.779703975 +vt 0.451512009 0.787217975 +vt 0.560464025 0.756774008 +vt 0.542732 0.738965988 +vt 0.57971102 0.731684983 +vt 0.580165029 0.753109992 +vt 0.520072997 0.741936982 +vt 0.49525401 0.753265977 +vt 0.454860002 0.757369995 +vt 0.471100003 0.73792702 +vt 0.419546992 0.745931983 +vt 0.440607995 0.741868019 +vt 0.405694991 0.781040013 +vt 0.41762501 0.769802988 +vt 0.393406987 0.756303012 +vt 0.387876987 0.730651021 +vt 0.601466 0.711583972 +vt 0.576505005 0.699630976 +vt 0.557129025 0.717716992 +vt 0.546958983 0.690301001 +vt 0.492397994 0.723087013 +vt 0.52452302 0.712854028 +vt 0.502655983 0.698309004 +vt 0.529597998 0.68948102 +vt 0.469011992 0.707552016 +vt 0.434186995 0.722981989 +vt 0.443975002 0.70302099 +vt 0.416523993 0.699617028 +vt 0.402276993 0.717184007 +vt 0.385161996 0.684125006 +vt 0.586471021 0.675952017 +vt 0.607667029 0.679077983 +vt 0.59355402 0.642629981 +vt 0.572477996 0.648213983 +vt 0.621535003 0.707525015 +vt 0.626263976 0.652490973 +vt 0.625398993 0.682157993 +vt 0.562494993 0.683766007 +vt 0.549506009 0.663501978 +vt 0.512238026 0.66464901 +vt 0.46379301 0.674611986 +vt 0.484306991 0.679865003 +vt 0.486846 0.653972983 +vt 0.438493013 0.681123972 +vt 0.432449013 0.658774018 +vt 0.403542995 0.672959983 +vt 0.405416995 0.650004983 +vt 0.384763986 0.646775007 +vt 0.762585998 0.643564999 +vt 0.792500019 0.629297018 +vt 0.773033977 0.614175975 +vt 0.766601026 0.654601991 +vt 0.728788972 0.640900016 +vt 0.755459011 0.625487983 +vt 0.727631986 0.618694007 +vt 0.742273986 0.660139024 +vt 0.726944983 0.655658007 +vt 0.702553988 0.626632988 +vt 0.662052989 0.652324021 +vt 0.682199001 0.631237984 +vt 0.659057021 0.613762021 +vt 0.68473202 0.669663012 +vt 0.636995971 0.628849983 +vt 0.615723014 0.629688025 +vt 0.593071997 0.607757986 +vt 0.569148004 0.614645004 +vt 0.539969027 0.643635988 +vt 0.546319008 0.617371976 +vt 0.517592013 0.628682017 +vt 0.453880012 0.643827975 +vt 0.486809999 0.633499026 +vt 0.470266998 0.612459004 +vt 0.491068989 0.609548986 +vt 0.452960014 0.62351501 +vt 0.428676009 0.630092025 +vt 0.408345997 0.622368991 +vt 0.386047006 0.624517024 +vt 0.36253801 0.65298599 +vt 0.360035986 0.623265982 +vt 0.305610001 0.623179972 +vt 0.329737008 0.638027012 +vt 0.335485995 0.609973013 +vt 0.323857009 0.658784986 +vt 0.345355004 0.665295005 +vt 0.282038987 0.648294985 +vt 0.281605989 0.629112005 +vt 0.304816008 0.65921998 +vt 0.288558006 0.662965 +vt 0.230791003 0.645913005 +vt 0.259169996 0.642530978 +vt 0.261361986 0.619610012 +vt 0.237838998 0.615813971 +vt 0.266079992 0.660251975 +vt 0.245569006 0.656500995 +vt 0.215145007 0.630153 +vt 0.191796005 0.634781003 +vt 0.197414994 0.610293984 +vt 0.170651004 0.626748025 +vt 0.202676997 0.651072025 +vt 0.213342994 0.605692983 +vt 0.886301994 0.583763003 +vt 0.900159001 0.612836003 +vt 0.909474015 0.58168602 +vt 0.874710023 0.608870983 +vt 0.920346975 0.606509984 +vt 0.934810996 0.577848017 +vt 0.832422018 0.591675997 +vt 0.855172992 0.596098006 +vt 0.861661971 0.569096029 +vt 0.854134977 0.624282002 +vt 0.799960017 0.595044017 +vt 0.810454011 0.572884977 +vt 0.836297989 0.62023598 +vt 0.816003025 0.620706975 +vt 0.780113995 0.581959009 +vt 0.752506971 0.596038997 +vt 0.758562028 0.569422007 +vt 0.737628996 0.568812013 +vt 0.726092994 0.593070984 +vt 0.704859972 0.598868012 +vt 0.70430702 0.574286997 +vt 0.687492013 0.606049001 +vt 0.676126003 0.589501023 +vt 0.652661026 0.581732988 +vt 0.611038983 0.593223989 +vt 0.636712015 0.598471999 +vt 0.620472014 0.572367013 +vt 0.582629979 0.583998024 +vt 0.544417977 0.589712024 +vt 0.560956001 0.576543987 +vt 0.51978898 0.599994004 +vt 0.51454699 0.576191008 +vt 0.491160989 0.583507001 +vt 0.467422009 0.579128027 +vt 0.449393004 0.596280992 +vt 0.426757991 0.593370974 +vt 0.396589011 0.604808986 +vt 0.404834986 0.583700001 +vt 0.375340998 0.584160984 +vt 0.351640999 0.590885997 +vt 0.371109992 0.604592025 +vt 0.322535992 0.588382006 +vt 0.275130987 0.600013018 +vt 0.296952993 0.597600996 +vt 0.278333992 0.576987982 +vt 0.308710992 0.567906976 +vt 0.242847994 0.59414798 +vt 0.248892993 0.56901902 +vt 0.227758005 0.578472972 +vt 0.190931007 0.584850013 +vt 0.210449994 0.571910024 +vt 0.176397994 0.60194701 +vt 0.163966 0.579474986 +vt 0.130673006 0.612746 +vt 0.150952995 0.617227972 +vt 0.134066999 0.58434099 +vt 0.0746020004 0.600130975 +vt 0.101186998 0.612695992 +vt 0.105567999 0.594586015 +vt 0.0771400034 0.579876006 +vt 0.0590899996 0.605062008 +vt 0.0478539988 0.578067005 +vt 0.0795179978 0.619740009 +vt 0.104532003 0.563414991 +vt 0.890398026 0.548551023 +vt 0.860791981 0.549372017 +vt 0.871403992 0.529924989 +vt 0.854619026 0.517728984 +vt 0.836677015 0.535848022 +vt 0.828644991 0.557981014 +vt 0.823677003 0.523177981 +vt 0.796486974 0.549452007 +vt 0.773916006 0.542151988 +vt 0.750262022 0.540901005 +vt 0.71223098 0.554126978 +vt 0.702408016 0.535045028 +vt 0.734049976 0.525147974 +vt 0.676672995 0.551746011 +vt 0.660552025 0.56416899 +vt 0.637489974 0.557241976 +vt 0.612141013 0.539382994 +vt 0.645587027 0.533074021 +vt 0.594603002 0.559158027 +vt 0.587089002 0.527535021 +vt 0.568140984 0.545799017 +vt 0.541351974 0.55329901 +vt 0.546989024 0.524667025 +vt 0.517354012 0.552977979 +vt 0.518458009 0.533509016 +vt 0.470941007 0.54114598 +vt 0.483530015 0.560172021 +vt 0.495375007 0.529662013 +vt 0.471767008 0.516278028 +vt 0.495099008 0.503500998 +vt 0.446758002 0.556757987 +vt 0.447115988 0.525134981 +vt 0.428364009 0.526956022 +vt 0.425080001 0.559147 +vt 0.418417007 0.496123999 +vt 0.449858993 0.495321989 +vt 0.482879996 0.494093001 +vt 0.382351011 0.555985987 +vt 0.404051006 0.559261024 +vt 0.408758014 0.526059985 +vt 0.377840996 0.52619803 +vt 0.392673999 0.498555005 +vt 0.356876999 0.563817024 +vt 0.360877991 0.540289998 +vt 0.340216011 0.554942012 +vt 0.374720007 0.504733026 +vt 0.34790799 0.509392023 +vt 0.355293006 0.492684007 +vt 0.31783101 0.541414022 +vt 0.332511991 0.525237978 +vt 0.261411995 0.550305009 +vt 0.295073003 0.548671007 +vt 0.275804013 0.534397006 +vt 0.227960005 0.545000017 +vt 0.205270007 0.550673008 +vt 0.163462996 0.546840012 +vt 0.187520996 0.55567199 +vt 0.187227994 0.527234972 +vt 0.123911001 0.542484999 +vt 0.0929860026 0.526472986 +vt 0.128482997 0.561251998 +vt 0.146430001 0.555831015 +vt 0.117119998 0.522374988 +vt 0.139791995 0.518114984 +vt 0.157995999 0.508298993 +vt 0.809768021 0.504535019 +vt 0.82406199 0.490761012 +vt 0.83780998 0.486393988 +vt 0.813763976 0.469193012 +vt 0.760928988 0.514415026 +vt 0.793682992 0.522306025 +vt 0.778015018 0.498387992 +vt 0.744970024 0.49681899 +vt 0.692745984 0.513422012 +vt 0.716536999 0.507210016 +vt 0.692349017 0.487980008 +vt 0.673291981 0.512637973 +vt 0.652544975 0.510710001 +vt 0.614453018 0.518836021 +vt 0.631304979 0.503458977 +vt 0.560196996 0.509844005 +vt 0.590942979 0.495741993 +vt 0.609411001 0.480785012 +vt 0.30489701 0.51928997 +vt 0.318118006 0.497707009 +vt 0.316650987 0.480587989 +vt 0.341529012 0.478563011 +vt 0.325769007 0.464076012 +vt 0.362156004 0.480315 +vt 0.377196014 0.488341987 +vt 0.390211999 0.481133014 +vt 0.282779992 0.509469986 +vt 0.270875007 0.485134989 +vt 0.289710999 0.476871997 +vt 0.283912003 0.455895007 +vt 0.311960995 0.458775014 +vt 0.251908988 0.513705015 +vt 0.222239003 0.519550025 +vt 0.182747006 0.507148027 +vt 0.209251001 0.498845994 +vt 0.233303994 0.482645988 +vt 0.252635986 0.477234006 +vt 0.239022002 0.452924013 +vt 0.27117601 0.455707997 +vt 0.179008007 0.485821009 +vt 0.205302 0.476007015 +vt 0.217603996 0.454656988 +vt 0.790776014 0.483763993 +vt 0.776323974 0.468012989 +vt 0.788769007 0.451436996 +vt 0.80052501 0.439305991 +vt 0.758656025 0.445524991 +vt 0.770476997 0.423175007 +vt 0.787586987 0.432146013 +vt 0.721826971 0.483561993 +vt 0.751904011 0.474063993 +vt 0.728052974 0.458793014 +vt 0.697261989 0.463685006 +vt 0.645178974 0.481857985 +vt 0.673505008 0.474218994 +vt 0.656045973 0.452932 +vt 0.615269005 0.457168996 +vt 0.638958991 0.450194001 +vt 0.578296006 0.472622007 +vt 0.584203005 0.448293 +vt 0.739763975 0.423972994 +vt 0.71013999 0.434038013 +vt 0.681046009 0.444393009 +vt 0.686340988 0.423693001 +vt 0.659587979 0.414579004 +vt 0.610202014 0.432478011 +vt 0.634211004 0.421849012 +vt 0.593937993 0.412699997 +vt 0.709950984 0.399912 +vt 0.733943999 0.380097002 +vt 0.696371019 0.378277987 +vt 0.738141 0.405012012 +vt 0.675065994 0.394089013 +vt 0.645429015 0.378544003 +vt 0.632353008 0.398077995 +vt 0.622511983 0.363261998 +vt 0.602340996 0.385987997 +vt 0.580236971 0.393023014 +vt 0.578997016 0.36749199 +vt 0.535748005 0.40535 +vt 0.56309402 0.409359992 +vt 0.550889015 0.3829 +vt 0.547297001 0.433297992 +vt 0.493312001 0.39350301 +vt 0.517832994 0.408232003 +vt 0.512754977 0.380082011 +vt 0.504203022 0.424194008 +vt 0.462298006 0.385628998 +vt 0.484297007 0.370368004 +vt 0.457038999 0.39606899 +vt 0.434486002 0.383911014 +vt 0.47784999 0.409943998 +vt 0.725396991 0.363393009 +vt 0.700336993 0.349557996 +vt 0.721067011 0.343279004 +vt 0.67136699 0.363555998 +vt 0.666958988 0.333342999 +vt 0.648338974 0.348367006 +vt 0.686506987 0.325585008 +vt 0.625672996 0.335615009 +vt 0.603686988 0.358821005 +vt 0.581781983 0.344159991 +vt 0.593478024 0.327594012 +vt 0.537126005 0.356018007 +vt 0.557717025 0.358846009 +vt 0.556694984 0.324568987 +vt 0.489982992 0.35018 +vt 0.519424021 0.347849011 +vt 0.503032982 0.328366011 +vt 0.457437009 0.355738014 +vt 0.459928006 0.329228014 +vt 0.426174998 0.364854991 +vt 0.433670998 0.33494401 +vt 0.386413008 0.348004997 +vt 0.410760999 0.347656012 +vt 0.394531995 0.329198003 +vt 0.367511004 0.350845993 +vt 0.361095011 0.333902001 +vt 0.397365987 0.367352992 +vt 0.716904998 0.324636012 +vt 0.708723009 0.304390997 +vt 0.739233017 0.33513999 +vt 0.74051398 0.310494989 +vt 0.671949029 0.303965002 +vt 0.623861015 0.304883987 +vt 0.643029988 0.318877012 +vt 0.641840994 0.287084997 +vt 0.601181984 0.305853009 +vt 0.572130978 0.296613008 +vt 0.547712982 0.28828001 +vt 0.532065988 0.299771994 +vt 0.525041997 0.318448007 +vt 0.49960199 0.296090007 +vt 0.480349988 0.314298004 +vt 0.477443993 0.282840014 +vt 0.449777007 0.299668014 +vt 0.42949599 0.302489012 +vt 0.411208004 0.316630006 +vt 0.394012004 0.299649 +vt 0.344224989 0.309291989 +vt 0.370121002 0.315237999 +vt 0.365781009 0.288834006 +vt 0.689454019 0.279175997 +vt 0.715595007 0.285703003 +vt 0.717854023 0.264156014 +vt 0.693168998 0.254204988 +vt 0.742924988 0.288262993 +vt 0.666580021 0.28183499 +vt 0.666728973 0.244329005 +vt 0.645579994 0.259321988 +vt 0.618795991 0.270143986 +vt 0.568976998 0.273815006 +vt 0.598766983 0.281598002 +vt 0.592612982 0.255858988 +vt 0.537545979 0.25208199 +vt 0.568729997 0.249898002 +vt 0.524537981 0.268009007 +vt 0.503513992 0.271685004 +vt 0.453729987 0.276230991 +vt 0.485390007 0.247412995 +vt 0.453981996 0.254346013 +vt 0.416132987 0.282950014 +vt 0.422307014 0.260526001 +vt 0.392951995 0.268965989 +vt 0.367027998 0.264604986 +vt 0.309320003 0.276030988 +vt 0.337401986 0.274136007 +vt 0.315914005 0.249519005 +vt 0.310508996 0.299784005 +vt 0.282898009 0.271174014 +vt 0.300511003 0.246022999 +vt 0.277054995 0.251612991 +vt 0.290298015 0.299005002 +vt 0.264795005 0.289979011 +vt 0.72320801 0.243522003 +vt 0.744787991 0.232756004 +vt 0.723729014 0.219319999 +vt 0.757838011 0.219494 +vt 0.744351029 0.256087989 +vt 0.695433974 0.232284993 +vt 0.64614898 0.229717001 +vt 0.67532599 0.212199003 +vt 0.618403018 0.244585007 +vt 0.623749018 0.221148998 +vt 0.584981024 0.230161995 +vt 0.584951997 0.210737005 +vt 0.509634018 0.244472995 +vt 0.528971016 0.219711006 +vt 0.499707997 0.219531 +vt 0.55676502 0.234694004 +vt 0.557057977 0.211648002 +vt 0.566299975 0.186765 +vt 0.54451102 0.193508998 +vt 0.548554003 0.171149999 +vt 0.589047015 0.190720007 +vt 0.461154997 0.234678999 +vt 0.476767987 0.213955 +vt 0.434056014 0.235791996 +vt 0.453272015 0.212334007 +vt 0.427603006 0.213247001 +vt 0.430801004 0.191846997 +vt 0.456923008 0.182364002 +vt 0.440391004 0.176130995 +vt 0.478857011 0.195467994 +vt 0.484640986 0.178108007 +vt 0.378019989 0.241917998 +vt 0.410151005 0.240538999 +vt 0.398685008 0.220289007 +vt 0.340442002 0.241995007 +vt 0.359694004 0.220706001 +vt 0.322326988 0.220798999 +vt 0.276246011 0.229891002 +vt 0.292703986 0.207209006 +vt 0.255807996 0.238121003 +vt 0.262154013 0.211713001 +vt 0.239568993 0.216783002 +vt 0.748063982 0.204154998 +vt 0.754141986 0.174932003 +vt 0.728103995 0.185243994 +vt 0.772544026 0.181067005 +vt 0.768423021 0.200668007 +vt 0.770974994 0.167852998 +vt 0.705170989 0.20036 +vt 0.70085001 0.179556996 +vt 0.678431988 0.188590005 +vt 0.651892006 0.175165996 +vt 0.648586988 0.200702995 +vt 0.612771988 0.205357 +vt 0.623028994 0.186215997 +vt 0.606932998 0.167947993 +vt 0.60124898 0.146348998 +vt 0.582548976 0.168272004 +vt 0.616934001 0.136595994 +vt 0.566923976 0.159966007 +vt 0.379877001 0.197520003 +vt 0.408991992 0.187937006 +vt 0.40080899 0.169066995 +vt 0.341412991 0.199882999 +vt 0.374648988 0.168318003 +vt 0.35087201 0.171063006 +vt 0.310977012 0.187591001 +vt 0.330684006 0.174205005 +vt 0.290915012 0.174545005 +vt 0.267374992 0.187360004 +vt 0.244537994 0.196227998 +vt 0.737092972 0.164801002 +vt 0.740550995 0.146081999 +vt 0.769858003 0.147479996 +vt 0.761229992 0.124095 +vt 0.703355014 0.156718001 +vt 0.712513983 0.133559003 +vt 0.682518005 0.143711999 +vt 0.675411999 0.167413995 +vt 0.630207002 0.151190996 +vt 0.654736996 0.145005003 +vt 0.638023973 0.127923995 +vt 0.665431976 0.121757001 +vt 0.656585991 0.103486001 +vt 0.690028012 0.111130998 +vt 0.350252002 0.148236006 +vt 0.373450994 0.138292998 +vt 0.403241009 0.143992007 +vt 0.359625012 0.117473997 +vt 0.38884601 0.132176995 +vt 0.369565994 0.113526002 +vt 0.341179013 0.125292003 +vt 0.319810987 0.148493007 +vt 0.319094002 0.111378998 +vt 0.345683008 0.100043997 +vt 0.296014994 0.153457001 +vt 0.268967986 0.162302002 +vt 0.244078994 0.170984 +vt 0.239989996 0.145761997 +vt 0.263538986 0.132092997 +vt 0.281506985 0.132024005 +vt 0.715946019 0.109406002 +vt 0.738277972 0.122574002 +vt 0.739962995 0.0984859988 +vt 0.769554973 0.0963170007 +vt 0.693647981 0.0865319967 +vt 0.718044996 0.0888239965 +vt 0.705846012 0.0701220036 +vt 0.722176015 0.0521190017 +vt 0.742538989 0.0737989992 +vt 0.761004984 0.073115997 +vt 0.297223985 0.119061001 +vt 0.270615995 0.109142996 +vt 0.248062998 0.102943003 +vt 0.236166 0.123590998 +vt 0.313165009 0.0871889964 +vt 0.285634995 0.0884760022 +vt 0.262181014 0.0864700004 +vt 0.25189501 0.0709320009 +vt 0.287674993 0.0648839995 +vt 0.399123996 0.129117996 +vt 0.423929989 0.153945997 +vt 0.447243989 0.157319993 +vt 0.308851004 0.0748440027 +vt 0.340303004 0.0934500024 +vt 0.301622987 0.0659969971 +vt 0.372536987 0.112154998 +vt 0.336721987 0.0879789963 +vt 0.291527003 0.0248920005 +vt 0.328440011 0.0474289991 +vt 0.327116996 0.0468260013 +vt 0.29083401 0.0245619994 +vt 0.369210988 0.0732769966 +vt 0.411314011 0.0997399986 +vt 0.411610991 0.0999839976 +vt 0.447454005 0.122408003 +vt 0.447088003 0.122285999 +vt 0.225254998 0.00298699993 +vt 0.234680995 0.00116600003 +vt 0.224713996 0.00267100008 +vt 0.247970998 0.00363599998 +vt 0.246999994 0.00348099996 +vt 0.235373005 0.001559 +vt 0.264955997 0.0106629999 +vt 0.265906006 0.0109900003 +vt 0.290924013 0.0245869998 +vt 0.197984993 0.0197020005 +vt 0.190902993 0.0277139992 +vt 0.191091001 0.0283959992 +vt 0.197791994 0.0190559998 +vt 0.206217006 0.012348 +vt 0.205928996 0.0118800001 +vt 0.215045005 0.00635900022 +vt 0.215517998 0.00679699983 +vt 0.181298003 0.0657740012 +vt 0.181963995 0.0853419974 +vt 0.182012007 0.0861700028 +vt 0.181391999 0.0646279976 +vt 0.182620004 0.0500410013 +vt 0.182868004 0.0489789993 +vt 0.185866997 0.0372349992 +vt 0.185929999 0.0380829982 +vt 0.203298002 0.202932999 +vt 0.196091995 0.173885003 +vt 0.196253002 0.172600001 +vt 0.189811006 0.142849997 +vt 0.189851001 0.141734004 +vt 0.185026005 0.111606002 +vt 0.184974998 0.112740003 +vt 0.182055995 0.0850899965 +vt 0.228465006 0.267664999 +vt 0.239074007 0.282911003 +vt 0.239123002 0.283863991 +vt 0.219199002 0.249430001 +vt 0.219129995 0.248651996 +vt 0.228513002 0.266483992 +vt 0.210982993 0.226989999 +vt 0.210916996 0.228132993 +vt 0.203404993 0.202047005 +vt 0.291096985 0.328568995 +vt 0.268000007 0.31329599 +vt 0.268108994 0.311868012 +vt 0.323152006 0.349209994 +vt 0.291069001 0.329807997 +vt 0.323242992 0.347995996 +vt 0.251522988 0.297340006 +vt 0.251504004 0.298566014 +vt 0.463497013 0.421900004 +vt 0.415674001 0.398616999 +vt 0.416242003 0.397049993 +vt 0.502157986 0.442856014 +vt 0.463001013 0.422928989 +vt 0.502632022 0.442173004 +vt 0.367017001 0.371995986 +vt 0.366827011 0.373162001 +vt 0.323242009 0.347997993 +vt 0.544239998 0.466888011 +vt 0.54598999 0.468632013 +vt 0.545219004 0.468928009 +vt 0.545216024 0.466340989 +vt 0.539120018 0.463093996 +vt 0.539965987 0.462442994 +vt 0.527206004 0.454762995 +vt 0.526293993 0.455702007 +vt 0.502632976 0.442171991 +vt 0.545831025 0.47488001 +vt 0.546069026 0.476832986 +vt 0.544924974 0.477521002 +vt 0.546869993 0.475030005 +vt 0.546094 0.472970009 +vt 0.547006011 0.472938001 +vt 0.547092021 0.470708013 +vt 0.545831978 0.470963001 +vt 0.543352008 0.481283009 +vt 0.543798983 0.479407012 +vt 0.54500401 0.479140013 +vt 0.541485012 0.481799006 +vt 0.54193598 0.480719 +vt 0.518914998 0.480675012 +vt 0.533291996 0.481629997 +vt 0.533600986 0.482553005 +vt 0.491598994 0.475171 +vt 0.518709004 0.479553998 +vt 0.491614014 0.475883991 +vt 0.539801002 0.482755989 +vt 0.539357007 0.481866002 +vt 0.339174986 0.449292004 +vt 0.288204998 0.44235 +vt 0.288560003 0.441947013 +vt 0.338705987 0.450170994 +vt 0.394614011 0.458427012 +vt 0.394226998 0.459823996 +vt 0.447701007 0.468481988 +vt 0.447847992 0.467532992 +vt 0.491614014 0.475883007 +vt 0.196137995 0.439042985 +vt 0.175227001 0.444189012 +vt 0.175794005 0.443598002 +vt 0.195476994 0.439868987 +vt 0.219809994 0.437133998 +vt 0.219221994 0.437840998 +vt 0.249044001 0.438975006 +vt 0.249752998 0.437954992 +vt 0.28797099 0.442815989 +vt 0.116586 0.472126991 +vt 0.136511996 0.459928006 +vt 0.135973006 0.460617006 +vt 0.0973709971 0.485399991 +vt 0.117147997 0.471477002 +vt 0.0967900008 0.486220986 +vt 0.155392006 0.451388001 +vt 0.156013995 0.450574994 +vt 0.0386990011 0.537046015 +vt 0.0571350008 0.518996 +vt 0.0566519983 0.520166993 +vt 0.0242699999 0.552523017 +vt 0.0392629988 0.536278009 +vt 0.0237520002 0.553232014 +vt 0.0764330029 0.502236009 +vt 0.0769859999 0.501590014 +vt 0.0968250036 0.486036003 +vt 0.00205500005 0.590218008 +vt 5.40000001e-05 0.601583004 +vt 0.000529000012 0.601312995 +vt 0.00159400003 0.590677023 +vt 0.00614499999 0.578866005 +vt 0.0057000001 0.579358995 +vt 0.0129669998 0.567233026 +vt 0.0134650003 0.566459 +vt 0.0237700008 0.553376019 +vt 0.0138569996 0.644375026 +vt 0.00738799991 0.634765983 +vt 0.0133189997 0.644089997 +vt 0.00794100016 0.634827018 +vt 0.00358700007 0.624131024 +vt 0.00310699991 0.624146998 +vt 0.000616999983 0.612991989 +vt 0.00104500004 0.612800002 +vt 0.0520949997 0.664332986 +vt 0.0334520005 0.659006 +vt 0.0327569991 0.658672988 +vt 0.0828189999 0.670356989 +vt 0.0529789999 0.664910972 +vt 0.0821869969 0.670107007 +vt 0.0208310001 0.651989996 +vt 0.0215360001 0.652320981 +vt 0.176855996 0.681006014 +vt 0.176622003 0.680839002 +vt 0.26958999 0.689845026 +vt 0.226374999 0.685090005 +vt 0.269221008 0.68926698 +vt 0.125649005 0.675298989 +vt 0.126409993 0.675795019 +vt 0.0820410028 0.66988802 +vt 0.32673201 0.701403022 +vt 0.322234005 0.698180974 +vt 0.326855004 0.701063991 +vt 0.313652009 0.695847988 +vt 0.313573003 0.695309997 +vt 0.322255999 0.698513985 +vt 0.297093004 0.692552984 +vt 0.297334999 0.693103015 +vt 0.269232988 0.689396977 +vt 0.344222993 0.728035986 +vt 0.335990012 0.712656021 +vt 0.336492985 0.712312996 +vt 0.355874002 0.754849017 +vt 0.343816012 0.728093982 +vt 0.356112003 0.754923999 +vt 0.330962002 0.704744995 +vt 0.330803007 0.704940975 +vt 0.416078001 0.893965006 +vt 0.394389987 0.844807029 +vt 0.395235002 0.845093012 +vt 0.434570998 0.935320973 +vt 0.415544987 0.893477023 +vt 0.434965014 0.935805023 +vt 0.374455988 0.796284974 +vt 0.356112003 0.754923999 +vt 0.469698012 0.990607977 +vt 0.478964001 0.996004999 +vt 0.478787988 0.995536983 +vt 0.470055997 0.99110198 +vt 0.46023199 0.980898976 +vt 0.460606009 0.981392026 +vt 0.449555993 0.963923991 +vt 0.448945999 0.963349998 +vt 0.434964001 0.935805023 +vt 0.519721985 0.995680988 +vt 0.509598017 0.998969972 +vt 0.519425988 0.996022999 +vt 0.499278992 0.999615014 +vt 0.499264002 0.999907017 +vt 0.509747028 0.998552978 +vt 0.488959998 0.998924971 +vt 0.488810986 0.998556972 +vt 0.548889995 0.963925004 +vt 0.538281024 0.98089999 +vt 0.537883997 0.981391013 +vt 0.563844025 0.935320973 +vt 0.549530983 0.963349998 +vt 0.563440025 0.935801983 +vt 0.528456986 0.99110198 +vt 0.528838992 0.990612984 +vt 0.603869021 0.844806015 +vt 0.642095983 0.754903018 +vt 0.642314017 0.754778981 +vt 0.623705983 0.796285987 +vt 0.603338003 0.845149994 +vt 0.582234025 0.893965006 +vt 0.582794011 0.89347899 +vt 0.563323021 0.935819983 +vt 0.667319 0.704940021 +vt 0.671352029 0.701276004 +vt 0.671378016 0.701417029 +vt 0.667132974 0.704746008 +vt 0.662132978 0.712656021 +vt 0.661916018 0.712576985 +vt 0.653881013 0.728034019 +vt 0.654321015 0.728093028 +vt 0.641795993 0.754850984 +vt 0.701106012 0.692544997 +vt 0.684494972 0.695846021 +vt 0.684491992 0.695101976 +vt 0.728802979 0.689827025 +vt 0.700893998 0.693095982 +vt 0.729107022 0.689505994 +vt 0.675861001 0.698183 +vt 0.675866008 0.698513985 +vt 0.873634994 0.675248027 +vt 0.821816027 0.680895984 +vt 0.822933972 0.680022001 +vt 0.916736007 0.670333982 +vt 0.872891009 0.675747991 +vt 0.917501986 0.669870019 +vt 0.77225399 0.685050011 +vt 0.729107022 0.689507008 +vt 0.978192985 0.652530015 +vt 0.986288011 0.644554019 +vt 0.985704005 0.644686997 +vt 0.978881001 0.65219599 +vt 0.96627897 0.659124017 +vt 0.966959 0.658788979 +vt 0.947577 0.664368987 +vt 0.946716011 0.664946973 +vt 0.917366982 0.670037985 +vt 0.998884022 0.613246024 +vt 0.999948025 0.602028012 +vt 0.999494016 0.601761997 +vt 0.999285996 0.61342901 +vt 0.996334016 0.624559999 +vt 0.996770024 0.624592006 +vt 0.992344022 0.635024011 +vt 0.991828978 0.63519299 +vt 0.980549991 0.561097026 +vt 0.992375016 0.57764101 +vt 0.992789984 0.578121006 +vt 0.959243 0.536741972 +vt 0.980112016 0.560360014 +vt 0.959635019 0.537362993 +vt 0.998483002 0.591096997 +vt 0.998054028 0.590646029 +vt 0.851103008 0.433003992 +vt 0.889165998 0.468077987 +vt 0.889295995 0.468944997 +vt 0.818405986 0.402370989 +vt 0.818536997 0.402631998 +vt 0.927473009 0.504984975 +vt 0.927089989 0.504359007 +vt 0.959641993 0.537244022 +vt 0.778150022 0.357425004 +vt 0.780066013 0.362598002 +vt 0.777908027 0.35738799 +vt 0.785735011 0.370023996 +vt 0.785400987 0.370352 +vt 0.780185997 0.362558991 +vt 0.797556996 0.382665008 +vt 0.797540009 0.382423013 +vt 0.818491995 0.402718991 +vt 0.778697014 0.324995011 +vt 0.776848018 0.342664003 +vt 0.77649498 0.342361987 +vt 0.783905029 0.296602994 +vt 0.779008985 0.32546699 +vt 0.783712983 0.296068013 +vt 0.77659899 0.351913989 +vt 0.776790023 0.352095991 +vt 0.809471011 0.147476003 +vt 0.801194012 0.200569004 +vt 0.800724983 0.199533001 +vt 0.815632999 0.103010997 +vt 0.809584022 0.148436993 +vt 0.815541983 0.102059998 +vt 0.791658998 0.251688987 +vt 0.791998029 0.252501011 +vt 0.783712983 0.296068996 +vt 0.809355021 0.0269719996 +vt 0.814495981 0.035879001 +vt 0.809395015 0.0263030007 +vt 0.814453006 0.0367299989 +vt 0.817485988 0.0505109988 +vt 0.817421019 0.0495040007 +vt 0.817912996 0.0704490021 +vt 0.818036973 0.0715470016 +vt 0.815545022 0.102063999 +vt 0.784883976 0.0056830002 +vt 0.775507987 0.00155699998 +vt 0.775021017 0.00200999994 +vt 0.785332978 0.00523999985 +vt 0.794175982 0.0112180002 +vt 0.794443011 0.0107469996 +vt 0.802574992 0.0178859998 +vt 0.802405 0.0185320005 +vt 0.735265017 0.00961100031 +vt 0.752388 0.00253399997 +vt 0.753051996 0.00217700005 +vt 0.708509028 0.023945 +vt 0.734340012 0.0099419998 +vt 0.70915997 0.0236109998 +vt 0.765695989 4.99999987e-05 +vt 0.765030026 0.000442999997 +vt 0.55111903 0.122229002 +vt 0.590938985 0.0973279998 +vt 0.551393986 0.122080997 +vt 0.630071998 0.0727080032 +vt 0.672564983 0.046046 +vt 0.671775997 0.0463280007 +vt 0.70916301 0.0236109998 +vt 0.504270971 0.147222996 +vt 0.499110997 0.147542998 +vt 0.499123007 0.147614002 +vt 0.504366994 0.147254005 +vt 0.512592971 0.144131005 +vt 0.512899995 0.144446 +vt 0.527543008 0.136518002 +vt 0.527163029 0.136467993 +vt 0.551468015 0.122107998 +vt 0.470865011 0.136502996 +vt 0.485682011 0.144148007 +vt 0.485448986 0.144198999 +vt 0.47121501 0.136539996 +vt 0.447007 0.122348003 +vt 0.493849993 0.147254005 +vt 0.493974 0.147221997 +vt 0.290017009 0.0250080004 +vt 0.326420993 0.047226999 +vt 0.368411988 0.0737340003 +vt 0.410205007 0.100647002 +vt 0.446707994 0.122621998 +vt 0.446058005 0.123667002 +vt 0.234276995 0.00154099998 +vt 0.224481001 0.0035600001 +vt 0.263907999 0.0116959997 +vt 0.290275991 0.024766 +vt 0.289460003 0.0256450009 +vt 0.191493005 0.027555 +vt 0.198456004 0.0194300003 +vt 0.206143007 0.0122349998 +vt 0.215058997 0.00699499995 +vt 0.182868004 0.08433 +vt 0.182679996 0.0638779998 +vt 0.186339006 0.0369650014 +vt 0.203881994 0.201350003 +vt 0.197605997 0.171200007 +vt 0.190841004 0.140607998 +vt 0.185646996 0.11084 +vt 0.182843998 0.0843079984 +vt 0.239856005 0.281170994 +vt 0.219782993 0.247270003 +vt 0.229231998 0.265143991 +vt 0.212179005 0.225404993 +vt 0.204186007 0.200978994 +vt 0.291774988 0.326714009 +vt 0.268718004 0.310375988 +vt 0.323947996 0.346089005 +vt 0.252000004 0.296113998 +vt 0.503555 0.440975994 +vt 0.464174986 0.420769989 +vt 0.367496014 0.370736986 +vt 0.323799014 0.346415997 +vt 0.547038019 0.46825099 +vt 0.546361029 0.465718001 +vt 0.548812985 0.467626989 +vt 0.541491985 0.461299002 +vt 0.52869302 0.453292012 +vt 0.503552973 0.440979004 +vt 0.546616971 0.478170007 +vt 0.548466027 0.472943991 +vt 0.548128009 0.475264996 +vt 0.548771024 0.477892011 +vt 0.549408019 0.470308989 +vt 0.542280972 0.483101994 +vt 0.545578003 0.480886012 +vt 0.544815004 0.48308 +vt 0.547708988 0.479900002 +vt 0.491672993 0.477284998 +vt 0.519252002 0.482841015 +vt 0.534144998 0.484501004 +vt 0.540296972 0.483904988 +vt 0.287685007 0.443758011 +vt 0.338400006 0.451368988 +vt 0.447616011 0.469695002 +vt 0.491650999 0.476803005 +vt 0.491732001 0.478531986 +vt 0.174927995 0.445131987 +vt 0.218858004 0.439184994 +vt 0.195122004 0.441608995 +vt 0.174848005 0.446851999 +vt 0.248522997 0.441175014 +vt 0.287488997 0.44461599 +vt 0.116503 0.473383993 +vt 0.135876 0.461876005 +vt 0.096822001 0.487475008 +vt 0.155226007 0.453074008 +vt 0.0387969986 0.537954986 +vt 0.0239870008 0.554226995 +vt 0.0967900008 0.487204999 +vt 0.0764549971 0.503418028 +vt 0.00035799999 0.601886988 +vt 0.00605600001 0.580022991 +vt 0.00189299998 0.591103017 +vt 0.013665 0.56852299 +vt 0.0242289994 0.554669976 +vt 0.0075869998 0.634324014 +vt 0.0035600001 0.623880029 +vt 0.0136169996 0.643254995 +vt 0.00130100001 0.612995982 +vt 0.0818220004 0.669377029 +vt 0.032621 0.657746017 +vt 0.0519060008 0.662634015 +vt 0.0207790006 0.651304007 +vt 0.175953999 0.679871976 +vt 0.226265997 0.684001982 +vt 0.269253999 0.68748498 +vt 0.125339001 0.674382985 +vt 0.081701003 0.668745995 +vt 0.322526991 0.697405994 +vt 0.313988 0.693782985 +vt 0.327645004 0.699765027 +vt 0.297320992 0.690833986 +vt 0.269183993 0.688248992 +vt 0.356950998 0.754588008 +vt 0.345557004 0.727150023 +vt 0.331470013 0.704133987 +vt 0.435869008 0.935756028 +vt 0.416871995 0.893905997 +vt 0.375683993 0.795768023 +vt 0.356635004 0.754749 +vt 0.357778013 0.75413698 +vt 0.479395002 0.995706022 +vt 0.461342007 0.981153011 +vt 0.470566988 0.990863979 +vt 0.450942993 0.963456988 +vt 0.435539991 0.935845971 +vt 0.436684012 0.935452998 +vt 0.509298027 0.998301983 +vt 0.499253988 0.999294996 +vt 0.518779993 0.995190024 +vt 0.489304006 0.997951984 +vt 0.562844992 0.935846984 +vt 0.537127018 0.981154025 +vt 0.547477007 0.963456988 +vt 0.561688006 0.935456991 +vt 0.527929008 0.990868986 +vt 0.641484022 0.754747987 +vt 0.622458994 0.795767009 +vt 0.640537977 0.754261971 +vt 0.602177978 0.844884992 +vt 0.581422985 0.893905997 +vt 0.562111974 0.93562901 +vt 0.67086798 0.700465024 +vt 0.661194026 0.711969972 +vt 0.666606009 0.704132974 +vt 0.652522027 0.727150023 +vt 0.640534997 0.754260004 +vt 0.729165971 0.68848002 +vt 0.700851023 0.690819979 +vt 0.675550997 0.697409987 +vt 0.873930991 0.674329996 +vt 0.91782701 0.668733001 +vt 0.772342026 0.684279025 +vt 0.729165971 0.688480973 +vt 0.986266017 0.643903017 +vt 0.967083991 0.657859981 +vt 0.978923023 0.651506007 +vt 0.947700024 0.662325978 +vt 0.917739987 0.669331014 +vt 0.917850018 0.667961001 +vt 0.999621987 0.602317989 +vt 0.996416986 0.624365985 +vt 0.998170018 0.613376021 +vt 0.991694987 0.634232998 +vt 0.992406011 0.578764975 +vt 0.959155977 0.538491011 +vt 0.979946971 0.562156975 +vt 0.998167992 0.591507971 +vt 0.818275988 0.403075993 +vt 0.850597978 0.433860004 +vt 0.817439973 0.404013991 +vt 0.927232027 0.505760014 +vt 0.959388018 0.538115025 +vt 0.779424012 0.362890005 +vt 0.776579022 0.357748002 +vt 0.796730995 0.383469999 +vt 0.817924976 0.403495014 +vt 0.782784998 0.295646012 +vt 0.777424991 0.324696004 +vt 0.774711013 0.342292011 +vt 0.775870025 0.351942003 +vt 0.808817983 0.146884993 +vt 0.814494014 0.101291001 +vt 0.789804995 0.250919014 +vt 0.782310009 0.295534015 +vt 0.814011991 0.0356119983 +vt 0.816599011 0.0490619987 +vt 0.808890998 0.0262809992 +vt 0.816592991 0.069761999 +vt 0.814508975 0.101295002 +vt 0.775843978 0.00208700006 +vt 0.785255015 0.00605300022 +vt 0.794215977 0.0110980002 +vt 0.802029014 0.0181460008 +vt 0.709716022 0.0238029994 +vt 0.753629029 0.00267699989 +vt 0.736290991 0.0106410002 +vt 0.710409999 0.0245159995 +vt 0.766086996 0.000426000013 +vt 0.551711977 0.122326002 +vt 0.588609993 0.10024 +vt 0.552460015 0.123484001 +vt 0.630851984 0.0731640011 +vt 0.673386991 0.0465990007 +vt 0.709969997 0.0240400005 +vt 0.499098986 0.148295999 +vt 0.504490018 0.147918001 +vt 0.528110027 0.137603 +vt 0.552020013 0.122732997 +vt 0.470201999 0.137722 +vt 0.44639799 0.123074003 +vt 0.485029012 0.145396993 +vt 0.493643999 0.148342997 +vt 0.325497001 0.0483650006 +vt 0.288874 0.0265350007 +vt 0.367464989 0.0749749988 +vt 0.409265012 0.102155998 +vt 0.444967002 0.125944003 +vt 0.233944997 0.00267100008 +vt 0.246042997 0.00518799992 +vt 0.224564001 0.00579700014 +vt 0.263134003 0.0132099995 +vt 0.288224995 0.0277559999 +vt 0.206762001 0.0132529996 +vt 0.193431005 0.0284499992 +vt 0.199404001 0.0202719998 +vt 0.215377003 0.0082080001 +vt 0.184698999 0.0486209989 +vt 0.185458004 0.083416 +vt 0.184256002 0.0635960028 +vt 0.187430993 0.0370550007 +vt 0.205743 0.199722007 +vt 0.199221998 0.170178995 +vt 0.192340001 0.139679 +vt 0.186957002 0.110079996 +vt 0.184450999 0.0836569965 +vt 0.230436996 0.263677001 +vt 0.221608996 0.245281994 +vt 0.242046997 0.278349012 +vt 0.213917002 0.223912999 +vt 0.205752 0.199717 +vt 0.269832999 0.308454007 +vt 0.292741001 0.324827999 +vt 0.325502992 0.343100011 +vt 0.252995014 0.294479012 +vt 0.465514004 0.418740004 +vt 0.505223989 0.438903004 +vt 0.417582989 0.394542992 +vt 0.368387014 0.368930012 +vt 0.324842989 0.344283998 +vt 0.548129976 0.464765996 +vt 0.551744998 0.466614991 +vt 0.543793023 0.459587008 +vt 0.530318022 0.451709002 +vt 0.505218983 0.438910007 +vt 0.550004005 0.475650996 +vt 0.551119983 0.472992986 +vt 0.55191499 0.477636009 +vt 0.552412987 0.469823986 +vt 0.544445992 0.485195011 +vt 0.547544003 0.482677996 +vt 0.550243974 0.481543005 +vt 0.491798013 0.479864001 +vt 0.534030974 0.48694399 +vt 0.541037977 0.485729992 +vt 0.33810699 0.453274012 +vt 0.287198007 0.446534991 +vt 0.393902987 0.462507993 +vt 0.447533011 0.472041994 +vt 0.491889 0.481617987 +vt 0.218588993 0.441774994 +vt 0.195066005 0.444366008 +vt 0.175081998 0.449804991 +vt 0.248278007 0.443367004 +vt 0.287057996 0.447905004 +vt 0.116971999 0.475672007 +vt 0.0973690003 0.489378989 +vt 0.136223003 0.464278013 +vt 0.155452996 0.455350995 +vt 0.0394579992 0.539319992 +vt 0.0249610003 0.555692017 +vt 0.0573149994 0.522230029 +vt 0.0771749988 0.505528986 +vt 0.0971639976 0.488819987 +vt 0.0979899988 0.490922987 +vt 0.002905 0.591657996 +vt 0.00135699997 0.60215503 +vt 0.0074339998 0.581139982 +vt 0.00307000009 0.602414012 +vt 0.0148440003 0.569702983 +vt 0.0256990008 0.556541979 +vt 0.00873200037 0.633320987 +vt 0.00474199979 0.623386025 +vt 0.0150140002 0.641444027 +vt 0.0025810001 0.612882018 +vt 0.0816690028 0.667756975 +vt 0.0330459997 0.655797005 +vt 0.0521919988 0.660800993 +vt 0.0819289982 0.665323019 +vt 0.0212290008 0.650074005 +vt 0.226366997 0.682008982 +vt 0.175848007 0.677818 +vt 0.269672006 0.684778988 +vt 0.125266999 0.672746003 +vt 0.0817290023 0.666840971 +vt 0.323175013 0.696002007 +vt 0.32920599 0.697349012 +vt 0.314722002 0.691673994 +vt 0.297886997 0.688468993 +vt 0.269405007 0.686335027 +vt 0.358696997 0.753583014 +vt 0.338218004 0.710735023 +vt 0.347377986 0.725776017 +vt 0.332441002 0.702970028 +vt 0.418240994 0.893379986 +vt 0.437361002 0.935127974 +vt 0.397136003 0.844421029 +vt 0.377297997 0.794921994 +vt 0.359941006 0.752784014 +vt 0.471397996 0.989957988 +vt 0.479979992 0.994723022 +vt 0.462769985 0.980022013 +vt 0.480753005 0.993044019 +vt 0.452578992 0.962342024 +vt 0.438773006 0.934285998 +vt 0.499244004 0.997901022 +vt 0.508964002 0.997071028 +vt 0.517715991 0.993044019 +vt 0.489648014 0.996517003 +vt 0.535674989 0.980022013 +vt 0.545822024 0.962342978 +vt 0.559566975 0.934284985 +vt 0.527077973 0.989961982 +vt 0.639050007 0.753371 +vt 0.620828986 0.794921994 +vt 0.600611985 0.844213009 +vt 0.580034971 0.893378973 +vt 0.560164988 0.934653997 +vt 0.665616989 0.702969015 +vt 0.670038998 0.69917202 +vt 0.659623027 0.71053201 +vt 0.668838024 0.697350025 +vt 0.650682986 0.725776017 +vt 0.639042974 0.753367007 +vt 0.728848994 0.685914993 +vt 0.683862984 0.693073988 +vt 0.700272977 0.688475013 +vt 0.674884021 0.69599998 +vt 0.873992026 0.672698975 +vt 0.917788029 0.666803002 +vt 0.82308197 0.677767992 +vt 0.772287011 0.682621002 +vt 0.72884798 0.685913026 +vt 0.978465974 0.650268018 +vt 0.985367 0.642527997 +vt 0.966648996 0.655901015 +vt 0.917590022 0.665315986 +vt 0.998604 0.602568984 +vt 0.995047987 0.623767018 +vt 0.996879995 0.602805018 +vt 0.990579009 0.633336008 +vt 0.99099201 0.579850972 +vt 0.978766024 0.563273013 +vt 0.957991004 0.539883971 +vt 0.997134984 0.592047989 +vt 0.849418998 0.435261011 +vt 0.888203025 0.470631003 +vt 0.815614998 0.405775011 +vt 0.926388979 0.506969988 +vt 0.958223999 0.539629996 +vt 0.778137028 0.363487989 +vt 0.783558011 0.371560007 +vt 0.773994982 0.358559012 +vt 0.795238018 0.384701014 +vt 0.816708028 0.404747009 +vt 0.780633986 0.295217991 +vt 0.775694013 0.324544996 +vt 0.774459004 0.352144986 +vt 0.807442009 0.146332994 +vt 0.798851013 0.198686004 +vt 0.812754989 0.100790001 +vt 0.779583991 0.295073986 +vt 0.812905014 0.035707999 +vt 0.807739973 0.0267539993 +vt 0.815137982 0.0490150005 +vt 0.814701974 0.0695279986 +vt 0.812762976 0.100791998 +vt 0.793583989 0.0121170003 +vt 0.775801003 0.00438199984 +vt 0.784897983 0.00732700014 +vt 0.801110983 0.0189399999 +vt 0.754347026 0.00432100007 +vt 0.711290002 0.0258889999 +vt 0.73705101 0.0121539999 +vt 0.766402006 0.00155100005 +vt 0.589573979 0.101791002 +vt 0.631784022 0.0744069964 +vt 0.553530991 0.125759006 +vt 0.710955024 0.0253120009 +vt 0.674322009 0.0478270017 +vt 0.504675984 0.149317995 +vt 0.499089003 0.149918005 +vt 0.513500988 0.146550998 +vt 0.528726995 0.139223993 +vt 0.552941978 0.124443002 +vt 0.445569009 0.124622002 +vt 0.469300985 0.140094995 +vt 0.484448999 0.147615999 +vt 0.493346989 0.150553003 +vt 0.323983997 0.0510400012 +vt 0.287654012 0.0290099997 +vt 0.365936011 0.0777089968 +vt 0.407983989 0.104700997 +vt 0.443854988 0.128681004 +vt 0.233649999 0.00539099984 +vt 0.245315 0.00803100038 +vt 0.224892005 0.00834599975 +vt 0.262436986 0.0151420003 +vt 0.287093014 0.0303990003 +vt 0.200681001 0.0215349998 +vt 0.207808003 0.0149670001 +vt 0.195701003 0.0298800003 +vt 0.216031 0.0102359997 +vt 0.186196998 0.0635450035 +vt 0.187392995 0.0489189997 +vt 0.188433006 0.0829989985 +vt 0.189855993 0.0377780013 +vt 0.201258004 0.169216007 +vt 0.208324999 0.198177993 +vt 0.194441006 0.138805002 +vt 0.189796001 0.109119996 +vt 0.187047005 0.0831409991 +vt 0.232690006 0.261512995 +vt 0.223752007 0.243525997 +vt 0.244504005 0.275840014 +vt 0.208846003 0.197923005 +vt 0.215862006 0.222611994 +vt 0.294268012 0.322355002 +vt 0.271494001 0.306028008 +vt 0.327423006 0.339935005 +vt 0.255169988 0.291637003 +vt 0.467648 0.415663004 +vt 0.507354021 0.436325997 +vt 0.419330001 0.391662002 +vt 0.370225012 0.365685999 +vt 0.326730013 0.341039985 +vt 0.551366985 0.463037014 +vt 0.554745018 0.465593994 +vt 0.546499014 0.457350999 +vt 0.507346988 0.436333001 +vt 0.554166973 0.474848002 +vt 0.552868009 0.48288101 +vt 0.555508018 0.470950007 +vt 0.549674988 0.484672993 +vt 0.546549022 0.487578988 +vt 0.535640001 0.490269005 +vt 0.491971999 0.48318699 +vt 0.542376995 0.489156008 +vt 0.337754995 0.456916988 +vt 0.286904991 0.449813992 +vt 0.393651009 0.466197014 +vt 0.447465986 0.475735992 +vt 0.492065996 0.484910995 +vt 0.218554005 0.444689989 +vt 0.195262 0.44729799 +vt 0.175589994 0.452910006 +vt 0.248159006 0.445876986 +vt 0.286814988 0.45154801 +vt 0.117991999 0.478578001 +vt 0.0985679999 0.492166013 +vt 0.136944994 0.466966003 +vt 0.156020001 0.458119988 +vt 0.041255001 0.54183197 +vt 0.0270939991 0.55796802 +vt 0.0587630011 0.524690986 +vt 0.0785600021 0.508180976 +vt 0.0993110016 0.493615001 +vt 0.00540499995 0.592589021 +vt 0.00969799981 0.582445979 +vt 0.00557299983 0.602676988 +vt 0.016454 0.570975006 +vt 0.0279489998 0.558770001 +vt 0.0107260002 0.631910026 +vt 0.0066920002 0.622635007 +vt 0.0170399994 0.63932699 +vt 0.00469300011 0.61264199 +vt 0.0527709983 0.658720016 +vt 0.0340950005 0.65319401 +vt 0.0825479999 0.662389994 +vt 0.0226579998 0.647580028 +vt 0.226777002 0.678973019 +vt 0.176174998 0.674475014 +vt 0.270312011 0.681761026 +vt 0.125589997 0.669459999 +vt 0.0821899995 0.663927972 +vt 0.324611992 0.693122983 +vt 0.330835015 0.694895029 +vt 0.315768987 0.689263999 +vt 0.270033985 0.683019996 +vt 0.361113012 0.751999021 +vt 0.340597987 0.708311975 +vt 0.334473997 0.70053798 +vt 0.420910001 0.89191699 +vt 0.439594001 0.933731973 +vt 0.399877012 0.842939019 +vt 0.379213989 0.793778002 +vt 0.36235401 0.751142979 +vt 0.47299999 0.987664998 +vt 0.464603007 0.978080988 +vt 0.481754005 0.990583003 +vt 0.454607993 0.960608006 +vt 0.441073 0.932669997 +vt 0.508485019 0.994893014 +vt 0.499235988 0.995666027 +vt 0.516698003 0.990579009 +vt 0.490056008 0.994552016 +vt 0.533825994 0.978082001 +vt 0.543779016 0.960609019 +vt 0.55726999 0.932681978 +vt 0.525457025 0.987671018 +vt 0.618903995 0.793779016 +vt 0.636982977 0.752013981 +vt 0.59830302 0.842939019 +vt 0.577346981 0.89191699 +vt 0.558063984 0.933269024 +vt 0.66356802 0.700538993 +vt 0.65744102 0.708308995 +vt 0.667196989 0.694895029 +vt 0.636973977 0.752008021 +vt 0.68284601 0.690352023 +vt 0.728155017 0.682446003 +vt 0.67128998 0.689065993 +vt 0.673426986 0.693120003 +vt 0.873658001 0.669411004 +vt 0.917401016 0.664259017 +vt 0.822742999 0.674426973 +vt 0.77186197 0.67928803 +vt 0.728156984 0.682452977 +vt 0.977033019 0.647759974 +vt 0.983286023 0.640226007 +vt 0.965596974 0.653288007 +vt 0.946982026 0.659114003 +vt 0.916963995 0.662356973 +vt 0.995486975 0.613062024 +vt 0.993090987 0.622994006 +vt 0.99435699 0.60303998 +vt 0.988996983 0.632227004 +vt 0.976890981 0.56467402 +vt 0.955837011 0.541840017 +vt 0.988685012 0.581121027 +vt 0.994601011 0.592948973 +vt 0.847401023 0.437323004 +vt 0.886076987 0.472956002 +vt 0.813440025 0.407701999 +vt 0.924292982 0.509235024 +vt 0.956212997 0.541534007 +vt 0.775391996 0.364771992 +vt 0.781911016 0.373742014 +vt 0.771315992 0.359450012 +vt 0.771420002 0.366640002 +vt 0.814616024 0.406675011 +vt 0.772255003 0.341583997 +vt 0.777355015 0.294867992 +vt 0.77026701 0.342534989 +vt 0.771480024 0.352703005 +vt 0.80449599 0.145725995 +vt 0.810737014 0.100536004 +vt 0.795826972 0.198050007 +vt 0.786581993 0.250295997 +vt 0.77670598 0.294824004 +vt 0.810469985 0.0364449993 +vt 0.805329025 0.0281949993 +vt 0.812668025 0.0493500009 +vt 0.812150002 0.0695979968 +vt 0.809998989 0.100505002 +vt 0.784307003 0.00910899974 +vt 0.792366028 0.0140960002 +vt 0.775438011 0.00721500022 +vt 0.799649 0.0203699991 +vt 0.737737 0.0140850004 +vt 0.712487996 0.0285030007 +vt 0.754967988 0.00692200009 +vt 0.766678989 0.00427199993 +vt 0.590839982 0.104339004 +vt 0.633295 0.0771420002 +vt 0.554627001 0.128489003 +vt 0.67564702 0.0502579994 +vt 0.711753011 0.0267849993 +vt 0.712619007 0.0288059991 +vt 0.505026996 0.152375996 +vt 0.499080002 0.152692005 +vt 0.515211999 0.148683995 +vt 0.505496025 0.156847998 +vt 0.554063976 0.127045006 +vt 0.44443199 0.127216995 +vt 0.483960986 0.150132 +vt 0.493046999 0.153139994 +vt 0.322174013 0.0551650003 +vt 0.286177009 0.0329109989 +vt 0.36411199 0.0817179978 +vt 0.406358987 0.108459003 +vt 0.442909002 0.131202996 +vt 0.233582005 0.0095800003 +vt 0.244729996 0.011798 +vt 0.225278005 0.0107460003 +vt 0.261344999 0.0190229993 +vt 0.286177009 0.032912001 +vt 0.203132004 0.0240870006 +vt 0.209778994 0.0181900002 +vt 0.197592005 0.0311810002 +vt 0.217253 0.0136789996 +vt 0.189916 0.0637760013 +vt 0.190831006 0.0497040004 +vt 0.190887004 0.0828479975 +vt 0.193443 0.0392820016 +vt 0.205041006 0.167783007 +vt 0.212108001 0.196391001 +vt 0.198834002 0.137505993 +vt 0.190887004 0.0828479975 +vt 0.193977997 0.1083 +vt 0.236328006 0.258556992 +vt 0.227430001 0.241009995 +vt 0.246517003 0.273981988 +vt 0.219468996 0.220547006 +vt 0.212108001 0.196391001 +vt 0.297037989 0.318363994 +vt 0.274369001 0.302377999 +vt 0.328974992 0.337563992 +vt 0.258372009 0.288087994 +vt 0.470218003 0.412093014 +vt 0.510308981 0.432806998 +vt 0.422154009 0.387322009 +vt 0.372844994 0.361506999 +vt 0.328974992 0.337563992 +vt 0.55557698 0.460803002 +vt 0.557350993 0.46471101 +vt 0.549484015 0.455385 +vt 0.535564005 0.446653992 +vt 0.510308981 0.432806998 +vt 0.55805397 0.47744301 +vt 0.556931019 0.481386006 +vt 0.55853498 0.473221004 +vt 0.558318973 0.468914002 +vt 0.550351977 0.490328014 +vt 0.553005993 0.487848997 +vt 0.555221975 0.484862 +vt 0.520574987 0.492134005 +vt 0.492226005 0.487807006 +vt 0.536517024 0.49386701 +vt 0.54409802 0.493689001 +vt 0.54736501 0.492282987 +vt 0.337484986 0.461840004 +vt 0.286716998 0.454391986 +vt 0.393449992 0.470990002 +vt 0.447443992 0.480159014 +vt 0.492226005 0.487807006 +vt 0.195748001 0.451222003 +vt 0.218792006 0.44936201 +vt 0.176146999 0.455630004 +vt 0.248142004 0.450325996 +vt 0.286716998 0.454391986 +vt 0.119722001 0.482479006 +vt 0.100605004 0.495961994 +vt 0.138467997 0.471260995 +vt 0.157167003 0.462309986 +vt 0.0442290008 0.545157015 +vt 0.0616060011 0.528506994 +vt 0.0298699997 0.560451984 +vt 0.0808089972 0.511705995 +vt 0.100605004 0.495961994 +vt 0.00932700001 0.593719006 +vt 0.0130460002 0.584051013 +vt 0.00793700013 0.602871001 +vt 0.0196889993 0.57318902 +vt 0.0298699997 0.560451984 +vt 0.0138349999 0.629914999 +vt 0.0102939997 0.621318996 +vt 0.0188120008 0.637628019 +vt 0.00829400029 0.612187982 +vt 0.0541619994 0.654810011 +vt 0.0358589999 0.649716973 +vt 0.0832530037 0.659741998 +vt 0.0251219999 0.644109011 +vt 0.227574006 0.674799979 +vt 0.176965997 0.669966996 +vt 0.270951986 0.679085016 +vt 0.0832519978 0.659743011 +vt 0.126480997 0.664865971 +vt 0.326738 0.689065993 +vt 0.332320005 0.692680001 +vt 0.317032009 0.685849011 +vt 0.299618989 0.682649016 +vt 0.270951986 0.679085016 +vt 0.351974994 0.722079992 +vt 0.364549994 0.74958998 +vt 0.343421996 0.705715001 +vt 0.337375998 0.697068989 +vt 0.424576014 0.889491975 +vt 0.443138003 0.931070983 +vt 0.403535008 0.840642989 +vt 0.382615 0.791589975 +vt 0.364549994 0.74958998 +vt 0.475154012 0.984059989 +vt 0.466989011 0.97517103 +vt 0.482639015 0.988259017 +vt 0.456786007 0.958549023 +vt 0.443138003 0.931070983 +vt 0.507678986 0.990776002 +vt 0.499226987 0.991614997 +vt 0.515805006 0.988259017 +vt 0.490772009 0.990776002 +vt 0.531424999 0.975171983 +vt 0.541589975 0.958549023 +vt 0.55518198 0.931070983 +vt 0.523280978 0.984059989 +vt 0.615489006 0.791589975 +vt 0.63351202 0.74958998 +vt 0.594631016 0.840642989 +vt 0.55518198 0.931070983 +vt 0.57366699 0.889491975 +vt 0.660650015 0.697068989 +vt 0.665705025 0.692680001 +vt 0.654606998 0.705715001 +vt 0.646063983 0.722079992 +vt 0.63351202 0.74958998 +vt 0.698517025 0.682641983 +vt 0.681021988 0.685846984 +vt 0.72734803 0.679067016 +vt 0.916258991 0.659705997 +vt 0.872762024 0.664811015 +vt 0.821943998 0.66991502 +vt 0.771001995 0.674763024 +vt 0.72734803 0.679067016 +vt 0.974574029 0.644267976 +vt 0.963837981 0.649800003 +vt 0.980876982 0.637858987 +vt 0.945487022 0.654824018 +vt 0.916258991 0.659704983 +vt 0.991554976 0.612528026 +vt 0.989482999 0.62164098 +vt 0.991988003 0.603208005 +vt 0.985885024 0.630200982 +vt 0.973559022 0.566812992 +vt 0.985302985 0.58267498 +vt 0.952984989 0.54411602 +vt 0.990665972 0.59403199 +vt 0.844421029 0.440102011 +vt 0.882905006 0.475984007 +vt 0.811414003 0.409428 +vt 0.952984989 0.54411602 +vt 0.920982003 0.512251973 +vt 0.768881023 0.360276997 +vt 0.777548015 0.375250012 +vt 0.789987981 0.388660014 +vt 0.811414003 0.409428 +vt 0.769168019 0.324515998 +vt 0.767076015 0.342936993 +vt 0.774074018 0.294679999 +vt 0.76720202 0.353619009 +vt 0.806529999 0.100438997 +vt 0.800203979 0.145342007 +vt 0.791594982 0.197565004 +vt 0.782289982 0.249841005 +vt 0.774074018 0.294679999 +vt 0.806883991 0.0379640013 +vt 0.809225976 0.0501740016 +vt 0.802733004 0.0299629997 +vt 0.809293985 0.069877997 +vt 0.806529999 0.100438997 +vt 0.783071995 0.0125580002 +vt 0.790546 0.0170600004 +vt 0.775043011 0.0096230004 +vt 0.797192991 0.0229279995 +vt 0.738816023 0.0179629996 +vt 0.755541027 0.0106880004 +vt 0.713775992 0.0319540016 +vt 0.766731024 0.00845499989 +vt 0.592453003 0.108098 +vt 0.63510102 0.0811439976 +vt 0.555570006 0.131020993 +vt 0.677441001 0.0543790013 +vt 0.713775992 0.0319540016 +vt 0.499071985 0.157321006 +vt 0.515011013 0.153556004 +vt 0.530703008 0.145565003 +vt 0.555570006 0.131020993 +vt 0.467575997 0.145638004 +vt 0.442909002 0.131202996 +vt 0.483168006 0.153574005 +vt 0.492653012 0.156849995 +vt 0.32408601 0.0565590002 +vt 0.294842988 0.0420899987 +vt 0.286177009 0.032912001 +vt 0.328563988 0.0621000007 +vt 0.36401999 0.0816790015 +vt 0.368622988 0.0863820016 +vt 0.334785998 0.0690149963 +vt 0.360318005 0.0836279988 +vt 0.350077987 0.0875869989 +vt 0.391609997 0.100239001 +vt 0.418603987 0.127179995 +vt 0.40635401 0.108471997 +vt 0.442907006 0.131208003 +vt 0.396952003 0.116279997 +vt 0.381392986 0.10362 +vt 0.398582995 0.148157999 +vt 0.429621011 0.164986998 +vt 0.454183012 0.157929003 +vt 0.477840006 0.173757002 +vt 0.483159989 0.153576002 +vt 0.492653012 0.156849995 +vt 0.467575997 0.145638004 +vt 0.272576004 0.0310619995 +vt 0.300038993 0.0549230017 +vt 0.273865998 0.0600919984 +vt 0.30667001 0.0809049979 +vt 0.233582005 0.0095800003 +vt 0.244729996 0.011798 +vt 0.237783 0.0378810018 +vt 0.261344999 0.0190239996 +vt 0.209778994 0.0181900002 +vt 0.218314007 0.0324340016 +vt 0.203132004 0.0240870006 +vt 0.217253 0.0136789996 +vt 0.225278005 0.0107460003 +vt 0.193443 0.0392820016 +vt 0.197592005 0.0311810002 +vt 0.216094002 0.0617499985 +vt 0.190831006 0.0497040004 +vt 0.189917997 0.0637769997 +vt 0.244723007 0.057852 +vt 0.205046996 0.167775005 +vt 0.219033003 0.187755004 +vt 0.212137997 0.196419999 +vt 0.225384995 0.145010993 +vt 0.225826994 0.172079995 +vt 0.198835 0.137505993 +vt 0.215018004 0.110587001 +vt 0.242106006 0.113388002 +vt 0.211381003 0.0868360028 +vt 0.193977997 0.1083 +vt 0.190887004 0.0828479975 +vt 0.236085996 0.0915250033 +vt 0.236329004 0.258556008 +vt 0.227430001 0.241009995 +vt 0.236705005 0.220601007 +vt 0.255623996 0.240252003 +vt 0.245060995 0.202166006 +vt 0.219468996 0.220547006 +vt 0.276291996 0.283573985 +vt 0.258372009 0.288087994 +vt 0.257672995 0.27482 +vt 0.274378002 0.302381992 +vt 0.246517003 0.273981988 +vt 0.278100014 0.253134012 +vt 0.353056997 0.343791991 +vt 0.401358992 0.369542003 +vt 0.372842997 0.361510009 +vt 0.328974992 0.337563992 +vt 0.424199998 0.387468994 +vt 0.382946998 0.35661 +vt 0.470376015 0.411866993 +vt 0.504401028 0.418451995 +vt 0.510308981 0.432806998 +vt 0.476087004 0.403043002 +vt 0.453803986 0.388024002 +vt 0.429583013 0.374864995 +vt 0.398178995 0.357524008 +vt 0.42375201 0.35631001 +vt 0.357535988 0.339073002 +vt 0.381274998 0.34009099 +vt 0.340543002 0.314877987 +vt 0.366333991 0.325397998 +vt 0.324335992 0.323101997 +vt 0.310389996 0.299937993 +vt 0.323641986 0.28143099 +vt 0.350645006 0.281295002 +vt 0.294086009 0.286413997 +vt 0.297037989 0.318351001 +vt 0.55557698 0.460803002 +vt 0.549484015 0.455385 +vt 0.590514004 0.444738001 +vt 0.570008993 0.433714002 +vt 0.548950016 0.436843991 +vt 0.535564005 0.446653992 +vt 0.585660994 0.411082 +vt 0.543658018 0.411897004 +vt 0.530170023 0.427208006 +vt 0.519263983 0.401318014 +vt 0.580331028 0.497191995 +vt 0.59573698 0.477118999 +vt 0.565841973 0.514397979 +vt 0.536517024 0.49386701 +vt 0.520604014 0.49217701 +vt 0.550149977 0.526763022 +vt 0.509618998 0.50158602 +vt 0.492226005 0.487807006 +vt 0.518521011 0.51037699 +vt 0.489740998 0.493721992 +vt 0.447546005 0.480778992 +vt 0.408156991 0.479077995 +vt 0.393449992 0.470993996 +vt 0.477492988 0.498400986 +vt 0.449712008 0.491443992 +vt 0.337484986 0.461834997 +vt 0.36462301 0.474927008 +vt 0.332145005 0.473531991 +vt 0.286716998 0.454391986 +vt 0.388099998 0.481083989 +vt 0.409666002 0.49259901 +vt 0.393189996 0.493506998 +vt 0.430887014 0.495606005 +vt 0.489223987 0.524254978 +vt 0.519405007 0.536144018 +vt 0.543945014 0.557556987 +vt 0.29036501 0.488007009 +vt 0.314466 0.493065 +vt 0.29439199 0.508773983 +vt 0.284269989 0.46883899 +vt 0.306757003 0.472431988 +vt 0.259272009 0.455265999 +vt 0.330581009 0.485789001 +vt 0.349584013 0.504710019 +vt 0.35522601 0.484154999 +vt 0.377411008 0.494176 +vt 0.229757994 0.454438001 +vt 0.248142004 0.450327009 +vt 0.218805 0.449359 +vt 0.176146999 0.455630004 +vt 0.195748001 0.451225013 +vt 0.182742998 0.474031001 +vt 0.212721005 0.480152011 +vt 0.243395999 0.474987 +vt 0.119722001 0.482479006 +vt 0.127821997 0.490238011 +vt 0.109742001 0.495766014 +vt 0.100562997 0.496026993 +vt 0.138468996 0.471260995 +vt 0.153119996 0.483797997 +vt 0.157170996 0.462312996 +vt 0.0616150014 0.528503001 +vt 0.0555219986 0.542708993 +vt 0.0442360006 0.545163989 +vt 0.0768790022 0.53244102 +vt 0.0808169991 0.511713028 +vt 0.111925997 0.517912984 +vt 0.0648960024 0.562021971 +vt 0.0447760001 0.566935003 +vt 0.029871 0.560455978 +vt 0.0319529995 0.585416019 +vt 0.0541829988 0.586961985 +vt 0.00793700013 0.602871001 +vt 0.00932700001 0.593719006 +vt 0.0130460002 0.584051013 +vt 0.0196889993 0.57318902 +vt 0.0138349999 0.629914999 +vt 0.0102939997 0.621318996 +vt 0.0332290009 0.629750013 +vt 0.0188120008 0.637628019 +vt 0.0302060004 0.606575012 +vt 0.00829400029 0.612187982 +vt 0.0251219999 0.644109011 +vt 0.0358589999 0.649716973 +vt 0.0541619994 0.654808998 +vt 0.0682710037 0.641430974 +vt 0.0832530037 0.659739017 +vt 0.0565319993 0.617147028 +vt 0.227511004 0.674758017 +vt 0.247483 0.67178297 +vt 0.270951986 0.679085016 +vt 0.203277007 0.668470979 +vt 0.176966995 0.669961989 +vt 0.184446007 0.668640971 +vt 0.225311995 0.667564988 +vt 0.179220006 0.661633015 +vt 0.209371999 0.655707002 +vt 0.145732 0.658867002 +vt 0.147383004 0.665296972 +vt 0.126250997 0.664704025 +vt 0.167413995 0.643665016 +vt 0.123204 0.635460973 +vt 0.144678995 0.634945989 +vt 0.101724997 0.619733989 +vt 0.107910998 0.651238978 +vt 0.0816380009 0.617658019 +vt 0.326738 0.689065993 +vt 0.332823992 0.677559972 +vt 0.332320005 0.692680001 +vt 0.317032009 0.685849011 +vt 0.309754997 0.670242012 +vt 0.28906399 0.670363009 +vt 0.299618989 0.682649016 +vt 0.271762997 0.674794018 +vt 0.257230997 0.656332016 +vt 0.351125985 0.664092004 +vt 0.343425006 0.705702007 +vt 0.337375998 0.697068989 +vt 0.424612999 0.889771998 +vt 0.435665011 0.90160799 +vt 0.443138003 0.931070983 +vt 0.421707988 0.875681996 +vt 0.403539002 0.840640008 +vt 0.408450007 0.847642004 +vt 0.431163996 0.880487025 +vt 0.415307999 0.855104029 +vt 0.427228004 0.857963979 +vt 0.395489007 0.813930988 +vt 0.382643998 0.79144299 +vt 0.378208011 0.747869015 +vt 0.385260999 0.776140988 +vt 0.364549994 0.74958998 +vt 0.397244006 0.802199006 +vt 0.410061002 0.826072991 +vt 0.425285995 0.839864016 +vt 0.416078001 0.802909017 +vt 0.436132997 0.822144985 +vt 0.386216015 0.716775 +vt 0.368454993 0.725912988 +vt 0.369356006 0.691830993 +vt 0.395116001 0.685724974 +vt 0.388954014 0.66248697 +vt 0.351978987 0.722068012 +vt 0.456167012 0.943718016 +vt 0.456802994 0.95855999 +vt 0.466989011 0.975169003 +vt 0.482829988 0.949343979 +vt 0.479014993 0.93078202 +vt 0.449653 0.91653502 +vt 0.474950999 0.905110002 +vt 0.46094501 0.883978009 +vt 0.499226987 0.991614997 +vt 0.490772009 0.990776002 +vt 0.507678986 0.990776002 +vt 0.49703899 0.967162013 +vt 0.475154012 0.984059989 +vt 0.482639015 0.988259017 +vt 0.541589975 0.958549023 +vt 0.531424999 0.975171983 +vt 0.541345 0.93957001 +vt 0.55518198 0.931070983 +vt 0.523280978 0.984059989 +vt 0.515805006 0.988259017 +vt 0.615469992 0.791577995 +vt 0.615445971 0.786934972 +vt 0.624045014 0.759485006 +vt 0.63351202 0.74958998 +vt 0.594627023 0.840640008 +vt 0.601795971 0.821318984 +vt 0.610556006 0.795287013 +vt 0.601787984 0.813117981 +vt 0.590577006 0.844825983 +vt 0.585681021 0.859937012 +vt 0.573948026 0.886914015 +vt 0.603913009 0.787734985 +vt 0.594309986 0.823216975 +vt 0.582688987 0.858223021 +vt 0.582837999 0.846026003 +vt 0.564393997 0.894225001 +vt 0.572055995 0.869903982 +vt 0.55893898 0.913055003 +vt 0.543932974 0.911857009 +vt 0.511655986 0.944257975 +vt 0.521628976 0.918238997 +vt 0.497678012 0.912602007 +vt 0.660650015 0.697068989 +vt 0.651158988 0.682866991 +vt 0.665705025 0.692680001 +vt 0.654606998 0.705715001 +vt 0.639348984 0.708800018 +vt 0.627645016 0.688143015 +vt 0.627312005 0.656359971 +vt 0.618121028 0.710412025 +vt 0.634168029 0.731719971 +vt 0.646063983 0.722079992 +vt 0.610427976 0.729398012 +vt 0.698513985 0.682639003 +vt 0.714444995 0.675490975 +vt 0.72734803 0.679067016 +vt 0.693270028 0.661083996 +vt 0.681020021 0.685845017 +vt 0.669983983 0.656777978 +vt 0.649946988 0.653656006 +vt 0.671288013 0.689065993 +vt 0.845632017 0.665518999 +vt 0.805849016 0.669206977 +vt 0.837330997 0.659958005 +vt 0.872730017 0.664911985 +vt 0.821942985 0.669910014 +vt 0.790749013 0.665479004 +vt 0.771481991 0.672474027 +vt 0.741870999 0.673089027 +vt 0.75752902 0.667650998 +vt 0.770998001 0.674741983 +vt 0.72440201 0.656835973 +vt 0.974574029 0.644267976 +vt 0.947577 0.633560002 +vt 0.963029027 0.615761995 +vt 0.963837981 0.649800003 +vt 0.929913998 0.623072982 +vt 0.918156028 0.642305017 +vt 0.945487022 0.654823005 +vt 0.91624099 0.659691989 +vt 0.905691981 0.616822004 +vt 0.891559005 0.63252598 +vt 0.872148991 0.632373989 +vt 0.842229009 0.637377024 +vt 0.813237011 0.641725004 +vt 0.833814025 0.615253985 +vt 0.985885024 0.630200982 +vt 0.980876982 0.637858987 +vt 0.989482999 0.62164098 +vt 0.991554976 0.612528026 +vt 0.990665972 0.59403199 +vt 0.991988003 0.603208005 +vt 0.967354 0.584769011 +vt 0.973558009 0.566814005 +vt 0.955274999 0.567564011 +vt 0.952984989 0.54411602 +vt 0.985302985 0.58267498 +vt 0.84443599 0.440881014 +vt 0.819220006 0.425287008 +vt 0.811414003 0.409428 +vt 0.825755 0.441837013 +vt 0.837539017 0.468151003 +vt 0.881788015 0.476063997 +vt 0.86332798 0.486761987 +vt 0.883185029 0.497076005 +vt 0.902999997 0.515594006 +vt 0.881254971 0.520779014 +vt 0.930368006 0.535974979 +vt 0.92009002 0.511848986 +vt 0.910722017 0.531381011 +vt 0.918053985 0.545877993 +vt 0.941832006 0.553170979 +vt 0.940087974 0.589541972 +vt 0.920826018 0.573810995 +vt 0.80795002 0.454124987 +vt 0.806469977 0.422805995 +vt 0.788097024 0.398238987 +vt 0.791013002 0.418453008 +vt 0.771418989 0.366638988 +vt 0.756552994 0.361537993 +vt 0.768881023 0.360276997 +vt 0.777547002 0.375250012 +vt 0.789987981 0.388660014 +vt 0.753331006 0.389741987 +vt 0.762220979 0.411559999 +vt 0.769168019 0.324515998 +vt 0.757625997 0.323210001 +vt 0.774074018 0.294679999 +vt 0.767076015 0.342936993 +vt 0.740465999 0.322023004 +vt 0.76720202 0.353619009 +vt 0.791586995 0.197564006 +vt 0.791024983 0.192885995 +vt 0.79677701 0.158369005 +vt 0.800240993 0.143663004 +vt 0.788888991 0.169384003 +vt 0.785134971 0.205224007 +vt 0.778451025 0.199194998 +vt 0.783896029 0.227225006 +vt 0.775577009 0.247210994 +vt 0.770884991 0.273083001 +vt 0.782245994 0.249836996 +vt 0.759398997 0.296761006 +vt 0.75396502 0.268702 +vt 0.763671994 0.238499999 +vt 0.764289975 0.213494003 +vt 0.735229015 0.247195005 +vt 0.806883991 0.0379640013 +vt 0.789395988 0.0473800004 +vt 0.797192991 0.0229279995 +vt 0.802733004 0.0299629997 +vt 0.809225976 0.0501740016 +vt 0.770748973 0.0321029983 +vt 0.790123999 0.0848390013 +vt 0.790861011 0.0663319975 +vt 0.809293985 0.069877997 +vt 0.806502998 0.100469001 +vt 0.77599901 0.113086 +vt 0.791260004 0.121495999 +vt 0.780660987 0.145256996 +vt 0.794425011 0.141315997 +vt 0.783071995 0.0125580002 +vt 0.790546 0.0170600004 +vt 0.775043011 0.0096230004 +vt 0.755541027 0.0106880004 +vt 0.766731024 0.00845499989 +vt 0.738816023 0.0179639999 +vt 0.723143995 0.0376439989 +vt 0.713737011 0.0319949985 +vt 0.741962016 0.034593001 +vt 0.59245801 0.108111002 +vt 0.598482013 0.107344002 +vt 0.573095024 0.127662003 +vt 0.555570006 0.131020993 +vt 0.635101974 0.0811460018 +vt 0.62579602 0.0885189995 +vt 0.618726015 0.0963729993 +vt 0.653689981 0.0712760016 +vt 0.595587015 0.115056999 +vt 0.686351001 0.0542210005 +vt 0.677438974 0.0543729998 +vt 0.659597993 0.0711980015 +vt 0.635909021 0.0910329968 +vt 0.671262026 0.0679809973 +vt 0.655826986 0.0831009969 +vt 0.711268008 0.056109 +vt 0.706817985 0.082558997 +vt 0.553238988 0.173270002 +vt 0.529022992 0.164188996 +vt 0.552341998 0.148371994 +vt 0.532651007 0.193201005 +vt 0.505891025 0.177590996 +vt 0.585842013 0.153540999 +vt 0.603013992 0.133386999 +vt 0.505491972 0.156849995 +vt 0.499071985 0.157321006 +vt 0.515011013 0.153556004 +vt 0.530703008 0.145565003 +vt 0.494497001 0.207275003 +vt 0.47369799 0.192537993 +vt 0.504328012 0.887696981 +vt 0.487233013 0.873452008 +vt 0.55132997 0.885963976 +vt 0.532698989 0.880428016 +vt 0.518845975 0.861087978 +vt 0.499047011 0.840562999 +vt 0.479627013 0.855652988 +vt 0.46697101 0.823621988 +vt 0.452982992 0.851624012 +vt 0.560199976 0.853891015 +vt 0.540529013 0.840424001 +vt 0.448918998 0.802775025 +vt 0.421573013 0.781672001 +vt 0.408196986 0.760600984 +vt 0.485621005 0.820105016 +vt 0.521710992 0.819420993 +vt 0.483024001 0.782602012 +vt 0.505150974 0.798530996 +vt 0.563304007 0.824099004 +vt 0.542059004 0.798446 +vt 0.573805988 0.796684027 +vt 0.594623029 0.801285028 +vt 0.456575006 0.783380985 +vt 0.439157009 0.764962018 +vt 0.469857007 0.754365027 +vt 0.521884024 0.77690798 +vt 0.504634023 0.755847991 +vt 0.559828997 0.777037978 +vt 0.525488019 0.743991971 +vt 0.548276007 0.746972024 +vt 0.585524976 0.771129012 +vt 0.580471992 0.74633199 +vt 0.60532701 0.759248972 +vt 0.427848995 0.739691019 +vt 0.460851014 0.725373983 +vt 0.42978099 0.716714978 +vt 0.402922004 0.733545005 +vt 0.402841985 0.708005011 +vt 0.484053999 0.72310698 +vt 0.512736976 0.724209011 +vt 0.494661987 0.702440023 +vt 0.543456018 0.715002 +vt 0.579557002 0.714165986 +vt 0.424703002 0.695563018 +vt 0.426272988 0.670562029 +vt 0.458236009 0.694342971 +vt 0.478311002 0.674471974 +vt 0.457888991 0.657468975 +vt 0.506848991 0.674970984 +vt 0.525933981 0.692004979 +vt 0.557211995 0.688831985 +vt 0.527414978 0.667209983 +vt 0.601571023 0.692516983 +vt 0.573864996 0.672259986 +vt 0.600035012 0.664506018 +vt 0.412883013 0.644181013 +vt 0.438634008 0.643087029 +vt 0.422407001 0.622024 +vt 0.387567014 0.622247994 +vt 0.375315011 0.642901003 +vt 0.351743996 0.627351999 +vt 0.469815999 0.627985001 +vt 0.49041599 0.649145007 +vt 0.522580028 0.637335002 +vt 0.502322972 0.614517987 +vt 0.552191019 0.645057976 +vt 0.582427025 0.644584 +vt 0.572713017 0.616164982 +vt 0.601293027 0.636532009 +vt 0.629140019 0.628008008 +vt 0.666707993 0.628021002 +vt 0.234139994 0.641134024 +vt 0.25198999 0.62093401 +vt 0.219501004 0.625575006 +vt 0.192524001 0.638692975 +vt 0.197911993 0.618399978 +vt 0.165961996 0.616644025 +vt 0.30439499 0.641490996 +vt 0.278854996 0.638067007 +vt 0.222492993 0.600067973 +vt 0.252685994 0.584531009 +vt 0.211053997 0.581920028 +vt 0.172304004 0.594829977 +vt 0.182511002 0.572425008 +vt 0.294373989 0.610906005 +vt 0.273936987 0.605889976 +vt 0.285151005 0.581654012 +vt 0.325529993 0.641609013 +vt 0.340754986 0.611082971 +vt 0.317364007 0.604529977 +vt 0.351680994 0.589836001 +vt 0.328682005 0.572811007 +vt 0.304329991 0.572512984 +vt 0.37400499 0.601961017 +vt 0.386184007 0.580118001 +vt 0.402022988 0.600479007 +vt 0.425891012 0.595206022 +vt 0.448473006 0.606297016 +vt 0.473417997 0.600670993 +vt 0.457286 0.581377029 +vt 0.502767026 0.586353004 +vt 0.481550992 0.578419983 +vt 0.537096024 0.612170994 +vt 0.535191 0.590251029 +vt 0.562129021 0.590016007 +vt 0.589780986 0.588515997 +vt 0.608726025 0.612347007 +vt 0.64221102 0.597429991 +vt 0.612285018 0.579159021 +vt 0.636439979 0.568464994 +vt 0.660748005 0.606344998 +vt 0.670044005 0.579585016 +vt 0.700640023 0.622458994 +vt 0.717140019 0.637314975 +vt 0.686573982 0.602100015 +vt 0.711612999 0.585192025 +vt 0.756308973 0.650191009 +vt 0.743439972 0.62973702 +vt 0.728992999 0.605480015 +vt 0.756878972 0.591373026 +vt 0.736917019 0.58047998 +vt 0.794710994 0.63815999 +vt 0.788906991 0.622174025 +vt 0.776126027 0.645843029 +vt 0.766676009 0.619387984 +vt 0.78640902 0.593917012 +vt 0.0769700035 0.587134004 +vt 0.100675002 0.583904028 +vt 0.0888319984 0.557603002 +vt 0.103545003 0.538788021 +vt 0.134607002 0.607879996 +vt 0.130271003 0.587529004 +vt 0.120325997 0.56261301 +vt 0.150997996 0.560169995 +vt 0.142672002 0.534841001 +vt 0.172352001 0.535212994 +vt 0.201630995 0.551392019 +vt 0.238151997 0.557907999 +vt 0.267412007 0.557601988 +vt 0.250616014 0.534630001 +vt 0.221709996 0.538920999 +vt 0.301625997 0.542867005 +vt 0.278326988 0.526256025 +vt 0.32476899 0.546428978 +vt 0.356148988 0.538721025 +vt 0.355637014 0.563054025 +vt 0.376219988 0.558144987 +vt 0.395447999 0.535439014 +vt 0.401695997 0.562974989 +vt 0.437920988 0.564518988 +vt 0.422322005 0.547385991 +vt 0.468531996 0.550411999 +vt 0.453467995 0.526250005 +vt 0.489800006 0.551392019 +vt 0.518260002 0.56022799 +vt 0.57337302 0.568881989 +vt 0.572202981 0.547257006 +vt 0.593297005 0.535753012 +vt 0.61551702 0.537550986 +vt 0.664117992 0.557591975 +vt 0.641095996 0.530466974 +vt 0.67751199 0.539969027 +vt 0.699343979 0.56398499 +vt 0.716190994 0.531913996 +vt 0.734937012 0.553655028 +vt 0.757488012 0.558986008 +vt 0.752349973 0.527848005 +vt 0.777756989 0.564005017 +vt 0.80457598 0.56449002 +vt 0.783751011 0.536983013 +vt 0.814836025 0.602844 +vt 0.84622997 0.587076008 +vt 0.825578988 0.571159005 +vt 0.839088976 0.552156985 +vt 0.808386981 0.537588 +vt 0.862757027 0.610701978 +vt 0.87667799 0.595704019 +vt 0.873103976 0.56833601 +vt 0.861868024 0.540211022 +vt 0.917870998 0.602050006 +vt 0.895134985 0.586089015 +vt 0.900376022 0.561300993 +vt 0.888265014 0.543569028 +vt 0.132455006 0.514764011 +vt 0.158207998 0.506151974 +vt 0.186750993 0.497343004 +vt 0.199781999 0.521095991 +vt 0.226497993 0.514313996 +vt 0.259718001 0.504355013 +vt 0.230214998 0.49011901 +vt 0.326312006 0.521355987 +vt 0.38152501 0.514181972 +vt 0.432532996 0.521412015 +vt 0.412218004 0.51415199 +vt 0.630882978 0.500150025 +vt 0.60847199 0.499287009 +vt 0.657625973 0.511349976 +vt 0.691129982 0.514627993 +vt 0.729323983 0.502767026 +vt 0.70312202 0.490123987 +vt 0.673003972 0.494758993 +vt 0.765626013 0.502696991 +vt 0.790480971 0.503349006 +vt 0.833600998 0.526579022 +vt 0.816082001 0.509752989 +vt 0.858950019 0.517075002 +vt 0.844615996 0.495249987 +vt 0.26492399 0.482033014 +vt 0.621585011 0.46696201 +vt 0.657971978 0.475937992 +vt 0.662101984 0.447620988 +vt 0.634765029 0.450096011 +vt 0.716991007 0.472761989 +vt 0.708724022 0.447952986 +vt 0.686187983 0.460584015 +vt 0.737197995 0.471711993 +vt 0.760699987 0.471280992 +vt 0.76117003 0.447326005 +vt 0.782761991 0.468243986 +vt 0.812693 0.483925015 +vt 0.618317008 0.428487986 +vt 0.683884025 0.422984004 +vt 0.650498986 0.428806007 +vt 0.662863016 0.395904005 +vt 0.643167973 0.40456 +vt 0.711332977 0.413314015 +vt 0.738649011 0.430593997 +vt 0.727573991 0.396055996 +vt 0.782950997 0.43659699 +vt 0.614472985 0.390971988 +vt 0.602380991 0.363757998 +vt 0.636789978 0.36945501 +vt 0.577602029 0.380609989 +vt 0.556410015 0.396315008 +vt 0.544759989 0.378075987 +vt 0.670572996 0.365186989 +vt 0.688530982 0.393016994 +vt 0.705577016 0.368535995 +vt 0.735930026 0.36076501 +vt 0.51394397 0.370999008 +vt 0.492962986 0.385354012 +vt 0.483361006 0.360408008 +vt 0.459614009 0.376722008 +vt 0.526719987 0.348441988 +vt 0.561574996 0.353722006 +vt 0.539958 0.320398003 +vt 0.501407027 0.315948993 +vt 0.501142025 0.342711985 +vt 0.473199993 0.329064012 +vt 0.454510987 0.344523013 +vt 0.598837972 0.344375014 +vt 0.578899026 0.323556006 +vt 0.562394977 0.331860006 +vt 0.624037981 0.338981986 +vt 0.609256029 0.318156004 +vt 0.649748981 0.342859 +vt 0.676205993 0.340395004 +vt 0.664641976 0.315459996 +vt 0.639656007 0.322158992 +vt 0.694670022 0.336973011 +vt 0.715964019 0.339058012 +vt 0.428279996 0.326534003 +vt 0.403714001 0.337069988 +vt 0.444745004 0.311107993 +vt 0.457170993 0.294708014 +vt 0.421478003 0.285329998 +vt 0.417979002 0.307954013 +vt 0.390296996 0.310552001 +vt 0.389054 0.289137989 +vt 0.515761018 0.299697995 +vt 0.485837996 0.299569011 +vt 0.498023003 0.279662997 +vt 0.470093995 0.278198004 +vt 0.53578198 0.289784014 +vt 0.562273979 0.293684006 +vt 0.58829999 0.301070005 +vt 0.635200977 0.299614012 +vt 0.603374004 0.282685012 +vt 0.636707008 0.268471003 +vt 0.65832603 0.284238011 +vt 0.689795017 0.305667996 +vt 0.719287992 0.311610013 +vt 0.711192012 0.286904991 +vt 0.679939985 0.27956301 +vt 0.734238982 0.280671 +vt 0.360693991 0.30052799 +vt 0.374072999 0.26810801 +vt 0.358720988 0.244298995 +vt 0.382685006 0.238928005 +vt 0.339468986 0.247284994 +vt 0.308717012 0.256246001 +vt 0.317200005 0.232972994 +vt 0.295901 0.229673997 +vt 0.398764014 0.262535989 +vt 0.414236993 0.247077003 +vt 0.442104012 0.264075994 +vt 0.471996009 0.254375011 +vt 0.454834998 0.238849998 +vt 0.43324101 0.232412994 +vt 0.49504599 0.244075999 +vt 0.516533017 0.252247006 +vt 0.527935028 0.269834995 +vt 0.561320007 0.268236011 +vt 0.543578029 0.241555005 +vt 0.592697978 0.253562003 +vt 0.570445001 0.240932003 +vt 0.616845012 0.253338993 +vt 0.66486001 0.247308001 +vt 0.636820018 0.226930007 +vt 0.692582011 0.263312012 +vt 0.713503003 0.253592014 +vt 0.687970996 0.227798998 +vt 0.282076001 0.207334995 +vt 0.311255991 0.198299006 +vt 0.268018007 0.218520001 +vt 0.334621012 0.214393005 +vt 0.339984 0.187258005 +vt 0.357410997 0.207202002 +vt 0.381862998 0.204276994 +vt 0.425505012 0.205033004 +vt 0.397370011 0.208767995 +vt 0.450177014 0.214488998 +vt 0.474561989 0.220090002 +vt 0.450628996 0.190161005 +vt 0.520703018 0.220262006 +vt 0.553833008 0.203383997 +vt 0.567345977 0.214901999 +vt 0.597172976 0.225467995 +vt 0.587468028 0.199615002 +vt 0.618562996 0.215059996 +vt 0.612214029 0.184580997 +vt 0.631478012 0.188087001 +vt 0.667671978 0.20566 +vt 0.650430024 0.190373003 +vt 0.708324015 0.220671996 +vt 0.70026201 0.195298001 +vt 0.729451001 0.219742 +vt 0.748193979 0.214160994 +vt 0.735907972 0.189916998 +vt 0.280663997 0.178271994 +vt 0.318264991 0.169104993 +vt 0.304307014 0.144226998 +vt 0.270817995 0.155396998 +vt 0.245798007 0.171964005 +vt 0.351108998 0.162120998 +vt 0.365069002 0.183436006 +vt 0.380838007 0.168061003 +vt 0.405662 0.178770006 +vt 0.589116991 0.176263005 +vt 0.623578012 0.157500997 +vt 0.682757974 0.174129993 +vt 0.657638013 0.160290003 +vt 0.721873999 0.169256002 +vt 0.703731 0.145834997 +vt 0.681833029 0.152750999 +vt 0.745718002 0.158414006 +vt 0.767993987 0.182733998 +vt 0.763388991 0.152149007 +vt 0.252492994 0.135857999 +vt 0.270565003 0.114946 +vt 0.292100012 0.119459003 +vt 0.341623992 0.141203001 +vt 0.319038987 0.113963999 +vt 0.344630003 0.117973 +vt 0.372007012 0.136851996 +vt 0.365015 0.110059999 +vt 0.638759971 0.136026993 +vt 0.630473018 0.112884998 +vt 0.614277005 0.109948002 +vt 0.661729991 0.127981007 +vt 0.649906993 0.106096998 +vt 0.678855002 0.120830998 +vt 0.710587025 0.114651002 +vt 0.731478989 0.133270994 +vt 0.745454013 0.114689 +vt 0.256814986 0.0837680027 +vt 0.283854008 0.0853670016 +vt 0.328693002 0.0895899981 +vt 0.681035995 0.0937810019 +vt 0.728663027 0.0863189995 +vt 0.757815003 0.0949449986 +vt 0.740926981 0.0655390024 +vt 0.763715029 0.0592200011 +vt 0.416662008 0.124062002 +vt 0.375624001 0.0969249979 +vt 0.377169013 0.0999229997 +vt 0.450607985 0.143398002 +vt 0.416750014 0.122123003 +vt 0.450461 0.145177007 +vt 0.372907996 0.0942849964 +vt 0.303373992 0.0513640009 +vt 0.303638011 0.0533129983 +vt 0.279550999 0.0405139998 +vt 0.262277007 0.0313460007 +vt 0.262798995 0.0332700014 +vt 0.279078007 0.038017001 +vt 0.303638011 0.0533119999 +vt 0.250984997 0.0313359983 +vt 0.250239998 0.0291210003 +vt 0.240777001 0.0305720009 +vt 0.241696998 0.0328020006 +vt 0.231479004 0.0340569988 +vt 0.232688993 0.0364729986 +vt 0.222727999 0.0393589996 +vt 0.224097997 0.0416040011 +vt 0.216490999 0.0483970009 +vt 0.214917004 0.046250999 +vt 0.208395004 0.0544289984 +vt 0.209935993 0.0562199987 +vt 0.203529999 0.0636190027 +vt 0.205127999 0.0652230009 +vt 0.201654002 0.0761210024 +vt 0.201093003 0.0911860019 +vt 0.199146003 0.0897649974 +vt 0.199891001 0.109118 +vt 0.201575994 0.110201001 +vt 0.200331002 0.074924998 +vt 0.204322994 0.135082006 +vt 0.207352996 0.162664995 +vt 0.209643006 0.163534001 +vt 0.202625006 0.134145007 +vt 0.201306 0.110068001 +vt 0.215029001 0.192559004 +vt 0.213130996 0.191907004 +vt 0.221185997 0.220017001 +vt 0.219944 0.219610006 +vt 0.228716999 0.243484005 +vt 0.235092998 0.26324299 +vt 0.236831993 0.263392001 +vt 0.227131993 0.243151993 +vt 0.221642002 0.220091999 +vt 0.245413005 0.28054601 +vt 0.243709996 0.280497015 +vt 0.254819989 0.295787007 +vt 0.253702015 0.295738012 +vt 0.265475988 0.309691995 +vt 0.26680699 0.309579015 +vt 0.281823009 0.323659986 +vt 0.304154992 0.338817 +vt 0.302850008 0.33919999 +vt 0.333927989 0.357324988 +vt 0.281004012 0.323711991 +vt 0.333160013 0.357549995 +vt 0.374992996 0.379954994 +vt 0.420585006 0.404276013 +vt 0.421701998 0.403403997 +vt 0.374419987 0.380163014 +vt 0.334181011 0.357185006 +vt 0.466019988 0.426550001 +vt 0.465321988 0.427150995 +vt 0.502264023 0.446025014 +vt 0.502719998 0.44561401 +vt 0.525063992 0.458131999 +vt 0.525795996 0.457498997 +vt 0.502717972 0.445616007 +vt 0.537182987 0.465113014 +vt 0.537800014 0.46468699 +vt 0.54272598 0.468356013 +vt 0.542017996 0.468699992 +vt 0.543500006 0.470472991 +vt 0.542954028 0.47061801 +vt 0.544466972 0.472474009 +vt 0.543744981 0.474460006 +vt 0.544411004 0.474550992 +vt 0.543519974 0.472548991 +vt 0.544281006 0.476518989 +vt 0.543163002 0.477937013 +vt 0.543931007 0.478381991 +vt 0.543520987 0.476249993 +vt 0.542943001 0.479759991 +vt 0.541576982 0.480536014 +vt 0.542258978 0.481362998 +vt 0.54242301 0.479290009 +vt 0.540955007 0.482405007 +vt 0.539068997 0.482347995 +vt 0.539439023 0.483269006 +vt 0.540407002 0.481458992 +vt 0.537638009 0.483779013 +vt 0.531657994 0.482632011 +vt 0.531804979 0.483592004 +vt 0.537401974 0.482845008 +vt 0.51801002 0.481844991 +vt 0.51789999 0.480664015 +vt 0.492291987 0.476532996 +vt 0.492359996 0.477306008 +vt 0.451126009 0.470436007 +vt 0.439837992 0.467411995 +vt 0.400985986 0.461787999 +vt 0.492361009 0.477308989 +vt 0.348621994 0.452793986 +vt 0.34830901 0.452093005 +vt 0.300494999 0.445152014 +vt 0.301115006 0.446458995 +vt 0.264611006 0.442680001 +vt 0.235691994 0.440625012 +vt 0.236433998 0.441574991 +vt 0.263819993 0.441365004 +vt 0.301119 0.446471006 +vt 0.214148998 0.443486005 +vt 0.213173002 0.442395985 +vt 0.194765002 0.447483003 +vt 0.193858996 0.446680993 +vt 0.175266996 0.453287005 +vt 0.176487997 0.45430699 +vt 0.157759994 0.462603986 +vt 0.139814004 0.473774999 +vt 0.138549998 0.473035008 +vt 0.119860001 0.486173987 +vt 0.121487997 0.48703301 +vt 0.156835005 0.462018013 +vt 0.102063 0.50207901 +vt 0.0821050033 0.517912984 +vt 0.0838370025 0.518554986 +vt 0.121068001 0.486759007 +vt 0.100611001 0.501479983 +vt 0.0668540001 0.534781992 +vt 0.0649790019 0.534251988 +vt 0.0511009991 0.549614012 +vt 0.0525499992 0.549899995 +vt 0.0429489985 0.563108981 +vt 0.053061001 0.550055981 +vt 0.0339680016 0.574437976 +vt 0.0363430008 0.574621022 +vt 0.0405969992 0.562761009 +vt 0.0320039988 0.585168004 +vt 0.0298299994 0.585206985 +vt 0.0283669997 0.595701993 +vt 0.0306059998 0.595480025 +vt 0.031355001 0.606070995 +vt 0.0314700007 0.617187023 +vt 0.033321999 0.616653979 +vt 0.0288679991 0.606540978 +vt 0.037721999 0.626420021 +vt 0.0353900008 0.627345979 +vt 0.0425750017 0.635667026 +vt 0.0409910008 0.636425018 +vt 0.0500789993 0.642768025 +vt 0.0597480014 0.650102973 +vt 0.0612360016 0.649061978 +vt 0.0482349992 0.64387399 +vt 0.0796839967 0.654277027 +vt 0.106470004 0.660674989 +vt 0.107730001 0.659411013 +vt 0.0779249966 0.655771971 +vt 0.107390001 0.65979898 +vt 0.195014998 0.670841992 +vt 0.196066007 0.669449985 +vt 0.147317007 0.666046977 +vt 0.243266001 0.674004972 +vt 0.242314994 0.675431013 +vt 0.283087999 0.678562999 +vt 0.282600999 0.679301977 +vt 0.309727997 0.68077302 +vt 0.324290991 0.684885979 +vt 0.325042993 0.683699012 +vt 0.308766991 0.682406008 +vt 0.283455014 0.67789799 +vt 0.333238989 0.686164021 +vt 0.332316995 0.687506974 +vt 0.337285012 0.689293027 +vt 0.336504012 0.690312982 +vt 0.341437012 0.692332029 +vt 0.345380008 0.700755 +vt 0.346637994 0.699380994 +vt 0.340386003 0.693585992 +vt 0.353983015 0.714091003 +vt 0.352678001 0.715457976 +vt 0.364089012 0.74060601 +vt 0.365155011 0.739455998 +vt 0.381900996 0.77838701 +vt 0.400516987 0.825522006 +vt 0.401980996 0.823655009 +vt 0.380798995 0.779698014 +vt 0.365159988 0.739450991 +vt 0.439242989 0.909443974 +vt 0.42048201 0.871599019 +vt 0.438454986 0.911004007 +vt 0.451992989 0.93770498 +vt 0.452885985 0.935576975 +vt 0.439099014 0.909667015 +vt 0.46261999 0.95422101 +vt 0.463355005 0.952009976 +vt 0.472113013 0.961355984 +vt 0.471601009 0.963438988 +vt 0.480455995 0.966512978 +vt 0.480179012 0.968249023 +vt 0.489883006 0.968442976 +vt 0.499545991 0.971679986 +vt 0.499556988 0.969749987 +vt 0.489654005 0.970959008 +vt 0.509230018 0.968442976 +vt 0.518844008 0.967844009 +vt 0.518486977 0.965677977 +vt 0.509432971 0.970955014 +vt 0.526978016 0.961350024 +vt 0.536369026 0.95400703 +vt 0.535637021 0.951794028 +vt 0.527468979 0.963445008 +vt 0.546151996 0.935577989 +vt 0.547016978 0.937705994 +vt 0.560501993 0.91100502 +vt 0.559876978 0.909667015 +vt 0.559736013 0.909443974 +vt 0.598294973 0.825522006 +vt 0.596859992 0.823653996 +vt 0.578437984 0.871671975 +vt 0.61687398 0.778385997 +vt 0.617945015 0.779690027 +vt 0.634621024 0.740604997 +vt 0.633565009 0.739440978 +vt 0.644734025 0.714091003 +vt 0.653299987 0.700752974 +vt 0.652072012 0.699380994 +vt 0.646010995 0.715458989 +vt 0.633570015 0.739444971 +vt 0.657262027 0.692330003 +vt 0.658285975 0.693579972 +vt 0.661410987 0.689293981 +vt 0.662171006 0.690312982 +vt 0.665462971 0.686164021 +vt 0.674414992 0.684886992 +vt 0.673684001 0.683699012 +vt 0.666361988 0.687511981 +vt 0.68907702 0.680765986 +vt 0.690007985 0.682398021 +vt 0.716305971 0.679252982 +vt 0.715403974 0.677684009 +vt 0.755940974 0.673973024 +vt 0.804467022 0.670786023 +vt 0.803143978 0.668824017 +vt 0.756874025 0.675405979 +vt 0.715614974 0.678120017 +vt 0.892673016 0.659776986 +vt 0.852446973 0.665971994 +vt 0.893764973 0.660817981 +vt 0.920503974 0.654308975 +vt 0.94045198 0.65021199 +vt 0.938988984 0.649172008 +vt 0.922236025 0.655806005 +vt 0.892490983 0.659569025 +vt 0.95014298 0.642961979 +vt 0.951956987 0.644069016 +vt 0.957641006 0.635945022 +vt 0.959201992 0.636705995 +vt 0.962531984 0.626756012 +vt 0.968825996 0.617578983 +vt 0.966476977 0.616877019 +vt 0.964847982 0.627695024 +vt 0.969048977 0.606481016 +vt 0.97150898 0.606961012 +vt 0.970480978 0.595942974 +vt 0.972229004 0.596140981 +vt 0.968554974 0.585560024 +vt 0.96509397 0.57328099 +vt 0.963261008 0.573377013 +vt 0.970721006 0.585609019 +vt 0.951461971 0.557345986 +vt 0.953785002 0.557002008 +vt 0.933890998 0.534671009 +vt 0.93205601 0.535079002 +vt 0.902547002 0.504383028 +vt 0.901682973 0.502197981 +vt 0.865980983 0.470524013 +vt 0.93234098 0.534988999 +vt 0.830066979 0.436993003 +vt 0.831691027 0.436199009 +vt 0.800924003 0.407786995 +vt 0.799870014 0.408340007 +vt 0.781310976 0.388902992 +vt 0.779570997 0.389883012 +vt 0.799463987 0.408583999 +vt 0.770155013 0.37718299 +vt 0.768447995 0.378093988 +vt 0.76336199 0.370891988 +vt 0.762772024 0.365404993 +vt 0.761170983 0.366082013 +vt 0.764908016 0.37014401 +vt 0.760106981 0.360852987 +vt 0.761605978 0.351435989 +vt 0.759398997 0.352106988 +vt 0.761702001 0.360258996 +vt 0.762187004 0.335597992 +vt 0.763800025 0.335099995 +vt 0.768332005 0.307895005 +vt 0.766776025 0.308385015 +vt 0.774084985 0.266883999 +vt 0.784681022 0.217232004 +vt 0.782325983 0.218154997 +vt 0.775967002 0.266240001 +vt 0.767013013 0.308331013 +vt 0.791351974 0.168639004 +vt 0.792661011 0.167881995 +vt 0.798282027 0.125098005 +vt 0.796696007 0.126124993 +vt 0.798749983 0.0966499969 +vt 0.799992025 0.0754970014 +vt 0.798400998 0.0768859982 +vt 0.800674021 0.0952230021 +vt 0.796698987 0.126123995 +vt 0.795704007 0.0639510006 +vt 0.792225003 0.0534939989 +vt 0.790728986 0.0552389994 +vt 0.797276974 0.0623439997 +vt 0.784345984 0.0473020002 +vt 0.777966976 0.0385069996 +vt 0.776916981 0.0402589999 +vt 0.785905004 0.0451399982 +vt 0.768160999 0.0354249999 +vt 0.759934008 0.0298069995 +vt 0.759063005 0.0320019983 +vt 0.769348025 0.0330090001 +vt 0.749858975 0.0302920006 +vt 0.738511026 0.0303130001 +vt 0.737851977 0.0332540013 +vt 0.75059098 0.0280579999 +vt 0.721152008 0.039526999 +vt 0.721605003 0.0370239988 +vt 0.697131991 0.0504550003 +vt 0.696869016 0.0524360016 +vt 0.662360013 0.0741169974 +vt 0.622542977 0.0967279971 +vt 0.622651994 0.0993880033 +vt 0.696880996 0.0521170013 +vt 0.582777023 0.123722002 +vt 0.58266598 0.121795997 +vt 0.548497975 0.143228993 +vt 0.548624992 0.144727007 +vt 0.526111007 0.158684999 +vt 0.512116015 0.163995996 +vt 0.512244999 0.165602997 +vt 0.525885999 0.156552002 +vt 0.548712015 0.145266995 +vt 0.504348993 0.168580994 +vt 0.499397993 0.167327002 +vt 0.499410003 0.169220001 +vt 0.504260004 0.166722 +vt 0.494468004 0.168578997 +vt 0.486708999 0.164009005 +vt 0.486550987 0.166107997 +vt 0.494531989 0.166715994 +vt 0.472833008 0.158752993 +vt 0.473031998 0.156620994 +vt 0.450417995 0.145444006 +vt 0.416397989 0.125770003 +vt 0.450100005 0.147237003 +vt 0.337756008 0.0756699964 +vt 0.303620994 0.0555739999 +vt 0.303620011 0.0555800013 +vt 0.27966401 0.0437109992 +vt 0.263117999 0.0354430005 +vt 0.251459002 0.0332649983 +vt 0.242452994 0.0350239985 +vt 0.234062001 0.039489001 +vt 0.225786999 0.0443740003 +vt 0.218510002 0.0509390011 +vt 0.211888999 0.0581750013 +vt 0.206579998 0.0664340034 +vt 0.203733996 0.0775550008 +vt 0.203770995 0.0924689993 +vt 0.203833997 0.111074999 +vt 0.203114003 0.11084 +vt 0.205920994 0.135597005 +vt 0.217544004 0.192744002 +vt 0.222779006 0.220161006 +vt 0.230856001 0.243322998 +vt 0.239528999 0.262784988 +vt 0.223867998 0.220102996 +vt 0.247451007 0.279994994 +vt 0.256810993 0.295179009 +vt 0.268061012 0.309080988 +vt 0.283349007 0.32297799 +vt 0.305958986 0.33750999 +vt 0.334939986 0.356638998 +vt 0.37658 0.378544003 +vt 0.335801005 0.355839014 +vt 0.466690987 0.425826997 +vt 0.503597975 0.444692999 +vt 0.503283978 0.445033014 +vt 0.527298987 0.456115991 +vt 0.504332006 0.443890005 +vt 0.539319992 0.463595986 +vt 0.543667972 0.467877001 +vt 0.545051992 0.469992995 +vt 0.546440005 0.472232014 +vt 0.545637012 0.474647999 +vt 0.545324028 0.476817995 +vt 0.545328021 0.479077995 +vt 0.543983996 0.480560988 +vt 0.543579996 0.482762009 +vt 0.542146027 0.484171003 +vt 0.540154994 0.48477599 +vt 0.537989974 0.484907001 +vt 0.532127023 0.485188991 +vt 0.518272996 0.484064013 +vt 0.492428005 0.478224993 +vt 0.492540002 0.47991401 +vt 0.492462009 0.478726 +vt 0.451211005 0.471563995 +vt 0.401154995 0.463494986 +vt 0.349014014 0.454984993 +vt 0.301564008 0.448505998 +vt 0.265282989 0.44500199 +vt 0.301566005 0.448516011 +vt 0.237062007 0.443048 +vt 0.215101004 0.445367008 +vt 0.195997 0.449371994 +vt 0.177782997 0.456086993 +vt 0.159192994 0.464074999 +vt 0.141182005 0.475055993 +vt 0.123282999 0.488577008 +vt 0.103664003 0.50316602 +vt 0.0856949985 0.519702017 +vt 0.0684320033 0.535520971 +vt 0.0544600002 0.550598025 +vt 0.122982003 0.488297999 +vt 0.0551920012 0.550927997 +vt 0.0458419994 0.564005971 +vt 0.0384420007 0.575012982 +vt 0.0338770002 0.585277021 +vt 0.032825999 0.595372975 +vt 0.0344699994 0.605543017 +vt 0.0353499986 0.616028011 +vt 0.0405780002 0.625078022 +vt 0.0450140014 0.63419503 +vt 0.0515599996 0.641645014 +vt 0.0627169982 0.647651017 +vt 0.0815740004 0.651933014 +vt 0.108811997 0.657881021 +vt 0.108589999 0.658213019 +vt 0.149483994 0.663190007 +vt 0.197041005 0.667536974 +vt 0.243963003 0.672582984 +vt 0.283652008 0.677519023 +vt 0.284274012 0.676114976 +vt 0.284271985 0.67611903 +vt 0.310891002 0.678265989 +vt 0.325902998 0.682110012 +vt 0.334089994 0.684809983 +vt 0.33870399 0.687343001 +vt 0.34246701 0.691103995 +vt 0.347932011 0.698024988 +vt 0.355973005 0.71221 +vt 0.367101997 0.737681985 +vt 0.383026987 0.777284026 +vt 0.366573989 0.738137007 +vt 0.422356009 0.868893981 +vt 0.440414995 0.90773201 +vt 0.440203995 0.90802002 +vt 0.454430014 0.933019996 +vt 0.464329988 0.94987601 +vt 0.472698987 0.959580004 +vt 0.481085986 0.96370101 +vt 0.490272999 0.965250015 +vt 0.499563009 0.967594028 +vt 0.508857012 0.965255976 +vt 0.517924011 0.963249028 +vt 0.526404023 0.959569991 +vt 0.534759998 0.949878991 +vt 0.544624984 0.933018982 +vt 0.558789015 0.90802002 +vt 0.576564014 0.868893981 +vt 0.558579981 0.907730997 +vt 0.615222991 0.77680099 +vt 0.632146001 0.738107979 +vt 0.631888986 0.737882972 +vt 0.642764986 0.712211013 +vt 0.650794029 0.698026001 +vt 0.65624702 0.691101015 +vt 0.660013974 0.687341988 +vt 0.664626002 0.684808016 +vt 0.672838986 0.682106972 +vt 0.687934995 0.678259015 +vt 0.714457989 0.675499976 +vt 0.714845002 0.676450014 +vt 0.755258024 0.672550976 +vt 0.850108027 0.662675977 +vt 0.891471028 0.658164024 +vt 0.891269028 0.657858014 +vt 0.918637991 0.651961029 +vt 0.937524974 0.647755027 +vt 0.948678017 0.641831994 +vt 0.955223024 0.634460986 +vt 0.959698975 0.625398993 +vt 0.963747978 0.61599803 +vt 0.965954006 0.605934024 +vt 0.967644989 0.595777988 +vt 0.966696978 0.585655987 +vt 0.961229026 0.573715985 +vt 0.948561013 0.558220029 +vt 0.930087984 0.535812974 +vt 0.930445015 0.535668015 +vt 0.869957983 0.476426989 +vt 0.828694999 0.437826991 +vt 0.798611999 0.409117013 +vt 0.79746002 0.409886986 +vt 0.777185977 0.391384989 +vt 0.766595006 0.379130006 +vt 0.761879981 0.371601999 +vt 0.759141982 0.366874993 +vt 0.758558989 0.361348987 +vt 0.759567022 0.336089998 +vt 0.76452601 0.308773994 +vt 0.765208006 0.308678001 +vt 0.77115798 0.267352015 +vt 0.795116007 0.126796007 +vt 0.794772029 0.126943007 +vt 0.796077013 0.0979709998 +vt 0.796108007 0.0783480033 +vt 0.794268012 0.0651659966 +vt 0.788957 0.0570209995 +vt 0.782343984 0.0498489998 +vt 0.775775015 0.0421559997 +vt 0.766806006 0.0384389982 +vt 0.758261025 0.0344769992 +vt 0.749394 0.0322280005 +vt 0.721054018 0.042723 +vt 0.696874976 0.054347001 +vt 0.69688201 0.0542800017 +vt 0.662482023 0.0753619969 +vt 0.583052993 0.125432 +vt 0.548972011 0.146707997 +vt 0.526628017 0.161593005 +vt 0.549293995 0.148162007 +vt 0.512483001 0.167539001 +vt 0.504463017 0.170330003 +vt 0.499417007 0.171534002 +vt 0.494369 0.170322001 +vt 0.486308008 0.168127 +vt 0.472335994 0.161661997 +vt 0.449939013 0.147976995 +vt 0.337244987 0.0789100006 +vt 0.376560986 0.103252001 +vt 0.303324997 0.0583150014 +vt 0.415883988 0.128021002 +vt 0.449492007 0.14982 +vt 0.303324997 0.0583159998 +vt 0.279529989 0.0463149995 +vt 0.263327986 0.0382770002 +vt 0.251902014 0.0357140005 +vt 0.243246004 0.0377449989 +vt 0.235079005 0.0418619998 +vt 0.227881998 0.0478070006 +vt 0.220129997 0.0528820008 +vt 0.214341998 0.0603859983 +vt 0.208487004 0.0678090006 +vt 0.206016004 0.0932639986 +vt 0.207248002 0.111847997 +vt 0.206448004 0.0790069997 +vt 0.219527006 0.190121993 +vt 0.225431994 0.219903007 +vt 0.212687001 0.163879007 +vt 0.208223999 0.135985002 +vt 0.205883995 0.111598998 +vt 0.233622 0.242666006 +vt 0.226236999 0.219777003 +vt 0.249414995 0.279145986 +vt 0.259970009 0.293466002 +vt 0.269688994 0.308108002 +vt 0.307498008 0.336066008 +vt 0.336622 0.354999006 +vt 0.285614997 0.321328998 +vt 0.377932012 0.376985013 +vt 0.337947994 0.353507012 +vt 0.467844009 0.424457997 +vt 0.423350006 0.401526988 +vt 0.505167007 0.442957997 +vt 0.529026985 0.454495996 +vt 0.506170988 0.441819012 +vt 0.541172981 0.462249994 +vt 0.545207024 0.467079997 +vt 0.547766984 0.469114006 +vt 0.548362017 0.471971989 +vt 0.547959983 0.47478801 +vt 0.546958983 0.477247 +vt 0.547640979 0.480170012 +vt 0.54589802 0.481950015 +vt 0.545883 0.485092014 +vt 0.543774009 0.486469001 +vt 0.541139007 0.486746013 +vt 0.538582981 0.486663014 +vt 0.532702029 0.487803996 +vt 0.518546999 0.486223996 +vt 0.492724001 0.482847989 +vt 0.451299995 0.473475993 +vt 0.401279986 0.466259986 +vt 0.349189013 0.45717299 +vt 0.301883012 0.451422989 +vt 0.492624015 0.481229991 +vt 0.301856011 0.451041013 +vt 0.265679985 0.447207004 +vt 0.237820998 0.445681006 +vt 0.216067001 0.448123991 +vt 0.197386995 0.45244801 +vt 0.179030001 0.458335996 +vt 0.161006004 0.466670007 +vt 0.142776996 0.476971 +vt 0.125626996 0.491214007 +vt 0.105547003 0.504801989 +vt 0.125621006 0.491207004 +vt 0.0817819983 0.52799201 +vt 0.0703079998 0.536666989 +vt 0.0571370013 0.551928997 +vt 0.0575550012 0.552160025 +vt 0.0481280014 0.564935982 +vt 0.0408970006 0.57565099 +vt 0.0362199992 0.585542023 +vt 0.035521999 0.595337987 +vt 0.036936 0.605153024 +vt 0.0383859985 0.615046978 +vt 0.0428059995 0.623933971 +vt 0.0484559983 0.63173902 +vt 0.053332001 0.640066981 +vt 0.0829460025 0.649829984 +vt 0.110092998 0.655606985 +vt 0.0647820011 0.645271003 +vt 0.150491998 0.661113977 +vt 0.198189005 0.664629996 +vt 0.244770005 0.670531988 +vt 0.285171986 0.673768997 +vt 0.110096 0.655601025 +vt 0.285156995 0.673808992 +vt 0.31180501 0.676038027 +vt 0.327215999 0.679464996 +vt 0.335202992 0.682941973 +vt 0.340985 0.684100986 +vt 0.34385699 0.689444005 +vt 0.357737005 0.710623026 +vt 0.369839996 0.735408008 +vt 0.349601001 0.696316004 +vt 0.424012005 0.867147982 +vt 0.404249012 0.821466029 +vt 0.442070991 0.905749977 +vt 0.384662986 0.775861979 +vt 0.368221015 0.736725986 +vt 0.442068011 0.90575403 +vt 0.455873013 0.931011975 +vt 0.465557009 0.947650015 +vt 0.473571986 0.957355976 +vt 0.482232988 0.95956099 +vt 0.490630001 0.962714016 +vt 0.499568999 0.964325011 +vt 0.508507013 0.96271503 +vt 0.517032981 0.960004985 +vt 0.525542021 0.957351983 +vt 0.543191016 0.931012988 +vt 0.556940973 0.905755997 +vt 0.533541024 0.947649002 +vt 0.613254011 0.775128007 +vt 0.630276024 0.736506999 +vt 0.594610989 0.821466029 +vt 0.574919999 0.867148995 +vt 0.556937993 0.905752003 +vt 0.629933 0.736221015 +vt 0.641011 0.710622013 +vt 0.64913398 0.696314991 +vt 0.654869974 0.689441979 +vt 0.658001006 0.684463978 +vt 0.663524985 0.682938993 +vt 0.687031984 0.676032007 +vt 0.713662028 0.673331976 +vt 0.671541989 0.679463029 +vt 0.801841021 0.665902972 +vt 0.890007019 0.655586004 +vt 0.849048018 0.660344005 +vt 0.754463017 0.670495987 +vt 0.713811994 0.673753023 +vt 0.890009999 0.655591011 +vt 0.917280972 0.649855971 +vt 0.935481012 0.645368993 +vt 0.946923018 0.640246987 +vt 0.951806009 0.631985009 +vt 0.957489014 0.624243021 +vt 0.960030019 0.614740014 +vt 0.963500023 0.605526984 +vt 0.963890016 0.595713973 +vt 0.964349985 0.585905015 +vt 0.946236014 0.559129 +vt 0.927718997 0.536953986 +vt 0.95818001 0.574450016 +vt 0.826857984 0.439099014 +vt 0.796914995 0.410259992 +vt 0.794933975 0.41169399 +vt 0.862622023 0.472566009 +vt 0.898148 0.506591976 +vt 0.92771399 0.536956012 +vt 0.795313001 0.411412001 +vt 0.775175989 0.392719001 +vt 0.764496028 0.380335987 +vt 0.759909987 0.37254101 +vt 0.75582999 0.368111014 +vt 0.756470978 0.361952007 +vt 0.756883025 0.33644101 +vt 0.761945009 0.30903101 +vt 0.756367028 0.352775007 +vt 0.792746007 0.127538994 +vt 0.786994994 0.169859007 +vt 0.779222012 0.218723997 +vt 0.762381017 0.308993995 +vt 0.792365015 0.127642006 +vt 0.79381001 0.0988110006 +vt 0.793281972 0.0798090026 +vt 0.792367995 0.0665540025 +vt 0.786522985 0.0592360012 +vt 0.780737996 0.0517919995 +vt 0.774074972 0.0449680015 +vt 0.765793025 0.0408199988 +vt 0.757335007 0.0377979986 +vt 0.748960972 0.0346730016 +vt 0.721194029 0.0453279987 +vt 0.697143972 0.0569589995 +vt 0.737505972 0.0372780003 +vt 0.583579004 0.127684996 +vt 0.623277009 0.102719001 +vt 0.549664974 0.149660006 +vt 0.66296798 0.0781750008 +vt 0.6972 0.057399001 +vt 0.527153015 0.164045006 +vt 0.550143003 0.151421994 +vt 0.512933016 0.170606002 +vt 0.50463599 0.172615007 +vt 0.499424011 0.175252005 +vt 0.494206011 0.172616005 +vt 0.471819997 0.164113 +vt 0.449377 0.150275007 +vt 0.485944986 0.170620993 +vt 0.375638008 0.106637001 +vt 0.302697003 0.061751999 +vt 0.336344004 0.0826160014 +vt 0.414813995 0.131738007 +vt 0.448567003 0.153145 +vt 0.252406001 0.0396800004 +vt 0.263357997 0.0414390005 +vt 0.244257003 0.0417979993 +vt 0.279287994 0.0485280007 +vt 0.302695006 0.0617580004 +vt 0.221796006 0.0548090003 +vt 0.216519997 0.0622039996 +vt 0.236077994 0.044298999 +vt 0.211657003 0.0698160008 +vt 0.208252996 0.0938709974 +vt 0.210611001 0.112251997 +vt 0.209126994 0.0801889971 +vt 0.209018007 0.112072997 +vt 0.217356995 0.163633004 +vt 0.223240003 0.192034006 +vt 0.228888005 0.219145 +vt 0.228899002 0.219142005 +vt 0.236527994 0.241659001 +vt 0.242863998 0.261487991 +vt 0.251828015 0.277846009 +vt 0.262724996 0.291559011 +vt 0.272412986 0.306071997 +vt 0.339085013 0.352100015 +vt 0.309074998 0.334363997 +vt 0.287661999 0.319474995 +vt 0.379776001 0.37461701 +vt 0.339812011 0.351166993 +vt 0.469924986 0.421837002 +vt 0.425202012 0.39912799 +vt 0.507146001 0.440701991 +vt 0.531103015 0.452529997 +vt 0.50812 0.439576 +vt 0.543147027 0.460808009 +vt 0.548098028 0.46556899 +vt 0.550390005 0.469983995 +vt 0.55131501 0.477319002 +vt 0.549883008 0.476099998 +vt 0.549085975 0.482535988 +vt 0.546483994 0.487861991 +vt 0.539924026 0.488709986 +vt 0.540673018 0.49059701 +vt 0.533455014 0.491064996 +vt 0.518845975 0.488507003 +vt 0.492906988 0.485918999 +vt 0.451395988 0.476969987 +vt 0.401318014 0.469680995 +vt 0.349298 0.46033299 +vt 0.302067995 0.45489499 +vt 0.492814988 0.484367996 +vt 0.242056996 0.448626995 +vt 0.302044988 0.454207987 +vt 0.218144998 0.450958997 +vt 0.198326007 0.45511201 +vt 0.178911999 0.461971015 +vt 0.144312993 0.479191989 +vt 0.127735004 0.494038999 +vt 0.162459001 0.46924299 +vt 0.0996749997 0.515372992 +vt 0.127734005 0.494037002 +vt 0.0600269996 0.553673983 +vt 0.0399970002 0.586144984 +vt 0.0395420007 0.595408022 +vt 0.0437889993 0.576556027 +vt 0.0503440015 0.565993011 +vt 0.0604909994 0.554014027 +vt 0.0450589992 0.622704029 +vt 0.0512330011 0.629522979 +vt 0.0412589982 0.614085972 +vt 0.0394509993 0.604776978 +vt 0.0560530014 0.637313008 +vt 0.111523002 0.652507007 +vt 0.0840179995 0.647930026 +vt 0.0672060028 0.642028987 +vt 0.151851997 0.657626987 +vt 0.199276 0.66117698 +vt 0.245943993 0.666930974 +vt 0.286229998 0.670599997 +vt 0.111521997 0.652508974 +vt 0.337065011 0.679690003 +vt 0.342994004 0.681190014 +vt 0.328482002 0.676744998 +vt 0.312685013 0.673707008 +vt 0.286390007 0.670065999 +vt 0.346244007 0.686592996 +vt 0.359575987 0.709017992 +vt 0.372310996 0.733479023 +vt 0.351687014 0.694211006 +vt 0.42672199 0.864675999 +vt 0.406818986 0.819333017 +vt 0.444350004 0.903367996 +vt 0.38755101 0.773573995 +vt 0.370712996 0.734718978 +vt 0.475156993 0.95380801 +vt 0.483319014 0.956115007 +vt 0.467664003 0.944262981 +vt 0.457148999 0.929418981 +vt 0.444608986 0.903122008 +vt 0.491030991 0.960103989 +vt 0.508108974 0.960106015 +vt 0.516161978 0.957174003 +vt 0.499570996 0.961190999 +vt 0.523966014 0.95380801 +vt 0.554405987 0.903123021 +vt 0.541920006 0.929419994 +vt 0.53144002 0.944262028 +vt 0.592051029 0.819332004 +vt 0.627712011 0.734444022 +vt 0.610912979 0.773307025 +vt 0.572220027 0.864677012 +vt 0.554665029 0.903369009 +vt 0.652494013 0.686591983 +vt 0.656373978 0.682106018 +vt 0.647054017 0.69420898 +vt 0.639176011 0.709017992 +vt 0.627304018 0.734130025 +vt 0.661673009 0.679687977 +vt 0.712599993 0.670037985 +vt 0.686155975 0.673699975 +vt 0.670283973 0.676743984 +vt 0.800728977 0.662652016 +vt 0.888586998 0.652477026 +vt 0.847868025 0.657175004 +vt 0.753302991 0.666899025 +vt 0.712752998 0.670552015 +vt 0.944222987 0.637480021 +vt 0.949045002 0.629751027 +vt 0.933094978 0.642108977 +vt 0.916225016 0.647966027 +vt 0.888586998 0.652477026 +vt 0.955245018 0.622996986 +vt 0.960983992 0.605129004 +vt 0.961015999 0.595748007 +vt 0.960569978 0.586477995 +vt 0.924767017 0.538626015 +vt 0.944274008 0.560018003 +vt 0.954358995 0.575581014 +vt 0.823857009 0.441381991 +vt 0.792608976 0.413457006 +vt 0.859852016 0.47457999 +vt 0.895093977 0.50861299 +vt 0.924767017 0.538626015 +vt 0.756561995 0.374130994 +vt 0.752557993 0.369289994 +vt 0.761928022 0.381835014 +vt 0.773142993 0.394115001 +vt 0.792582989 0.413484007 +vt 0.752919972 0.362897009 +vt 0.753690004 0.353258014 +vt 0.751172006 0.353653997 +vt 0.754477978 0.336661994 +vt 0.758605003 0.309177995 +vt 0.783576012 0.170273006 +vt 0.775932014 0.218973994 +vt 0.789183021 0.12827 +vt 0.767036974 0.267592013 +vt 0.759127975 0.309168994 +vt 0.791857004 0.0993850008 +vt 0.790419996 0.0810900033 +vt 0.789206028 0.0685790032 +vt 0.784349024 0.0610649996 +vt 0.789197981 0.128267005 +vt 0.779076993 0.0537250005 +vt 0.764800012 0.0432550013 +vt 0.756624997 0.0407040007 +vt 0.772476017 0.0476000011 +vt 0.748462975 0.038635999 +vt 0.697825015 0.0608059987 +vt 0.721437991 0.0475360006 +vt 0.737457991 0.0418480001 +vt 0.584657013 0.131402001 +vt 0.62420702 0.106103003 +vt 0.550592005 0.152965993 +vt 0.663874984 0.0818860009 +vt 0.697822988 0.0607989989 +vt 0.527741015 0.166509002 +vt 0.551075995 0.154540002 +vt 0.504961014 0.176474005 +vt 0.499426007 0.178884998 +vt 0.513433993 0.173648998 +vt 0.493891001 0.176473007 +vt 0.448408008 0.153653994 +vt 0.471237004 0.166575 +vt 0.48545 0.173664004 +vt 0.374040008 0.111323997 +vt 0.301622987 0.0659969971 +vt 0.335083991 0.0866250023 +vt 0.413277 0.136207998 +vt 0.447243989 0.157319993 +vt 0.301622987 0.0659969971 +vt 0.263415009 0.0442700014 +vt 0.278551012 0.0530939996 +vt 0.263112992 0.0463789999 +vt 0.252756 0.0443190001 +vt 0.245040998 0.0454020016 +vt 0.237583995 0.0481280014 +vt 0.230639994 0.0523179993 +vt 0.219317004 0.0643889979 +vt 0.224463999 0.0577969998 +vt 0.212185994 0.0946730003 +vt 0.213088006 0.112392999 +vt 0.213036001 0.0815989971 +vt 0.215462998 0.0719159991 +vt 0.215957999 0.136041 +vt 0.220470995 0.163176998 +vt 0.226238996 0.191306993 +vt 0.232804 0.217886999 +vt 0.213088006 0.112392999 +vt 0.255306989 0.275646001 +vt 0.264773995 0.289977998 +vt 0.247040004 0.259342998 +vt 0.239642993 0.240330994 +vt 0.232804 0.217886999 +vt 0.275788009 0.303083986 +vt 0.311713994 0.331212997 +vt 0.34138599 0.349052995 +vt 0.290652007 0.31636101 +vt 0.382140994 0.371306002 +vt 0.34138599 0.349052995 +vt 0.427967012 0.395274013 +vt 0.509865999 0.437543988 +vt 0.472651005 0.418251008 +vt 0.533330977 0.450408995 +vt 0.509865999 0.437543988 +vt 0.546265006 0.458521992 +vt 0.553574026 0.467186004 +vt 0.551925004 0.463555008 +vt 0.554472983 0.471091986 +vt 0.554673016 0.475093991 +vt 0.554225981 0.479014993 +vt 0.553183019 0.482679993 +vt 0.551595986 0.485908985 +vt 0.54953599 0.488685012 +vt 0.547078013 0.490989 +vt 0.544295013 0.492803991 +vt 0.541260004 0.494109988 +vt 0.534215987 0.494275004 +vt 0.519402981 0.492660999 +vt 0.493065 0.488646001 +vt 0.451458007 0.481593013 +vt 0.401291013 0.473024011 +vt 0.349294007 0.464507014 +vt 0.302123994 0.457599014 +vt 0.493065 0.488646001 +vt 0.266283989 0.453821987 +vt 0.239014 0.45292601 +vt 0.302123994 0.457599014 +vt 0.217603996 0.454654008 +vt 0.199392006 0.458748996 +vt 0.181757003 0.464955986 +vt 0.164385006 0.473271996 +vt 0.146966994 0.483695 +vt 0.129205003 0.496221989 +vt 0.110812999 0.510851026 +vt 0.129205003 0.496221989 +vt 0.0929709971 0.526459992 +vt 0.0634849966 0.556140006 +vt 0.0768250003 0.54193002 +vt 0.0540250018 0.567974985 +vt 0.0634849966 0.556140006 +vt 0.0443979986 0.587049007 +vt 0.0431060009 0.595552981 +vt 0.0478530005 0.578067005 +vt 0.0434379987 0.604209006 +vt 0.0485870019 0.620679021 +vt 0.0532109998 0.627844989 +vt 0.0452970006 0.612693012 +vt 0.0590730011 0.633867025 +vt 0.0690490007 0.639078021 +vt 0.0860550031 0.643809021 +vt 0.113082998 0.648392022 +vt 0.153240994 0.653173983 +vt 0.200154006 0.657886982 +vt 0.247180998 0.662349999 +vt 0.287476987 0.666364014 +vt 0.113082998 0.648392022 +vt 0.314110994 0.669677019 +vt 0.287476987 0.666364014 +vt 0.339307994 0.67563802 +vt 0.344494998 0.678995013 +vt 0.33028999 0.672648013 +vt 0.349191993 0.683072984 +vt 0.362755001 0.706312001 +vt 0.374440014 0.731871009 +vt 0.354808986 0.691106021 +vt 0.410654008 0.816474974 +vt 0.447456986 0.900487006 +vt 0.430189013 0.861872017 +vt 0.391252011 0.770874023 +vt 0.374440014 0.731871009 +vt 0.447456986 0.900487006 +vt 0.460137993 0.926016986 +vt 0.469617009 0.941461027 +vt 0.477203012 0.949719012 +vt 0.484158009 0.95362097 +vt 0.491714001 0.955959976 +vt 0.499568999 0.956739008 +vt 0.514972985 0.95362097 +vt 0.507422984 0.955959976 +vt 0.521917999 0.949720025 +vt 0.529484987 0.94146198 +vt 0.538929999 0.926016986 +vt 0.551558018 0.900487006 +vt 0.588217974 0.816474974 +vt 0.624334991 0.731871009 +vt 0.607562006 0.770874023 +vt 0.568754017 0.861872971 +vt 0.551558018 0.900487006 +vt 0.635999024 0.706312001 +vt 0.624334991 0.731871009 +vt 0.643935978 0.691106021 +vt 0.654245973 0.678995013 +vt 0.649549007 0.683072984 +vt 0.684733987 0.669669986 +vt 0.711519003 0.66634798 +vt 0.668478012 0.672646999 +vt 0.659434021 0.67563802 +vt 0.799412012 0.657850027 +vt 0.887039006 0.648357987 +vt 0.846632004 0.653123021 +vt 0.752071977 0.662317991 +vt 0.711519003 0.66634798 +vt 0.887039006 0.648357987 +vt 0.914195001 0.643821001 +vt 0.931245029 0.639154971 +vt 0.941220999 0.634015024 +vt 0.947076023 0.628059983 +vt 0.951729 0.620944977 +vt 0.955071986 0.612991989 +vt 0.957399011 0.595866024 +vt 0.956997991 0.60452503 +vt 0.956170976 0.587339997 +vt 0.951188982 0.576789021 +vt 0.940276027 0.562050998 +vt 0.921162009 0.540961981 +vt 0.856050014 0.477661014 +vt 0.789627016 0.415821999 +vt 0.820263982 0.444348007 +vt 0.891439021 0.511348009 +vt 0.921162009 0.540961981 +vt 0.769720018 0.396524996 +vt 0.789627016 0.415821999 +vt 0.752466023 0.376065999 +vt 0.750108004 0.370155007 +vt 0.758161008 0.384066999 +vt 0.748547971 0.363968998 +vt 0.748431027 0.35404399 +vt 0.750376999 0.336930007 +vt 0.754933 0.309208989 +vt 0.771207988 0.218977004 +vt 0.785089016 0.128737003 +vt 0.779220998 0.170457006 +vt 0.762512982 0.267544001 +vt 0.754933 0.309208989 +vt 0.785089016 0.128737003 +vt 0.787657022 0.100341998 +vt 0.787593007 0.0820349976 +vt 0.785417974 0.0706909969 +vt 0.781561017 0.0632570013 +vt 0.770237982 0.0512689985 +vt 0.755833983 0.0443589985 +vt 0.763293028 0.0470860004 +vt 0.776413023 0.0567209981 +vt 0.74811101 0.0432740003 +vt 0.737713993 0.0453479998 +vt 0.722175002 0.0521089993 +vt 0.698909998 0.065105997 +vt 0.62580502 0.110789999 +vt 0.55191803 0.157150999 +vt 0.586194992 0.135873005 +vt 0.665135026 0.0858950019 +vt 0.698909998 0.065105997 +vt 0.528814018 0.170662001 +vt 0.55191803 0.157150999 +vt 0.505391002 0.181149006 +vt 0.499426007 0.181586996 +vt 0.51423502 0.178088993 +vt 0.493461996 0.181149006 +vt 0.470162988 0.170729995 +vt 0.447243989 0.157319993 +vt 0.484649003 0.178105995 +vt 0.51423502 0.178088993 +vt 0.512215018 0.196281001 +vt 0.528814971 0.170671999 +vt 0.505391002 0.181149006 +vt 0.499426007 0.181586996 +vt 0.493461996 0.181149006 +vt 0.470162988 0.170733005 +vt 0.664422989 0.0867450014 +vt 0.678156972 0.0835179985 +vt 0.698909998 0.0651070029 +vt 0.638921022 0.106106997 +vt 0.625813007 0.110811003 +vt 0.635628998 0.112905003 +vt 0.611401975 0.123885997 +vt 0.601633012 0.133496001 +vt 0.585916996 0.136207998 +vt 0.587728977 0.148478001 +vt 0.55191803 0.157150999 +vt 0.74811101 0.0432740003 +vt 0.737713993 0.0453479998 +vt 0.770237982 0.0512680002 +vt 0.763293028 0.0470860004 +vt 0.776413023 0.0567209981 +vt 0.755833983 0.0443589985 +vt 0.787593007 0.0820349976 +vt 0.785417974 0.0706909969 +vt 0.781561017 0.0632570013 +vt 0.787657022 0.100341998 +vt 0.776048005 0.121028997 +vt 0.785077989 0.128757 +vt 0.763689995 0.246141002 +vt 0.755757987 0.264483005 +vt 0.760962009 0.270985007 +vt 0.771341026 0.218937993 +vt 0.76453203 0.225420997 +vt 0.773890018 0.192879006 +vt 0.779202998 0.169502005 +vt 0.750374973 0.336930007 +vt 0.754933 0.309208989 +vt 0.748431027 0.35404399 +vt 0.748547971 0.363968998 +vt 0.750108004 0.370155007 +vt 0.752466023 0.376065999 +vt 0.758161008 0.384066999 +vt 0.82030201 0.444940001 +vt 0.789627016 0.415821999 +vt 0.890568018 0.510944009 +vt 0.896681011 0.53153199 +vt 0.921162009 0.540961981 +vt 0.856338978 0.496320009 +vt 0.874729991 0.513513982 +vt 0.854894996 0.477679998 +vt 0.778617024 0.417064995 +vt 0.769716978 0.396526992 +vt 0.951188982 0.576789021 +vt 0.956170976 0.587339997 +vt 0.917369008 0.555458009 +vt 0.940276027 0.562050998 +vt 0.951729 0.620944977 +vt 0.934809029 0.613436997 +vt 0.941220999 0.634015024 +vt 0.947076023 0.628059983 +vt 0.955071986 0.612991989 +vt 0.956997991 0.60452503 +vt 0.957399011 0.595866024 +vt 0.914196014 0.643817008 +vt 0.931245029 0.639154971 +vt 0.893512011 0.630665004 +vt 0.887035012 0.648352027 +vt 0.752288997 0.66228801 +vt 0.779098988 0.656534016 +vt 0.799408972 0.657840014 +vt 0.794636011 0.656619012 +vt 0.803601027 0.647900999 +vt 0.828271985 0.651785016 +vt 0.847434998 0.652329981 +vt 0.874476016 0.627543986 +vt 0.668478012 0.672646999 +vt 0.659434021 0.67563802 +vt 0.641160011 0.669624984 +vt 0.649549007 0.683072984 +vt 0.654245973 0.678995013 +vt 0.643935978 0.691106021 +vt 0.635996997 0.706309021 +vt 0.624337971 0.731871009 +vt 0.567314982 0.863358021 +vt 0.582660019 0.823024988 +vt 0.562223971 0.864539027 +vt 0.588207006 0.816466987 +vt 0.595503986 0.791896999 +vt 0.597819984 0.778495014 +vt 0.606769025 0.748088002 +vt 0.607572973 0.77088201 +vt 0.596639991 0.761439979 +vt 0.548802972 0.894338012 +vt 0.551558018 0.900487006 +vt 0.529483974 0.941460013 +vt 0.514972985 0.95362097 +vt 0.521917999 0.949720025 +vt 0.531826019 0.909309983 +vt 0.538929999 0.926016986 +vt 0.499568999 0.956739008 +vt 0.507422984 0.955959976 +vt 0.484158009 0.95362097 +vt 0.477203012 0.949719012 +vt 0.491714001 0.955959976 +vt 0.460447013 0.912220001 +vt 0.447456986 0.900487006 +vt 0.469617009 0.941461027 +vt 0.410665005 0.816466987 +vt 0.415609002 0.822986007 +vt 0.403766006 0.791814029 +vt 0.391250998 0.770816982 +vt 0.422416002 0.830102026 +vt 0.428128004 0.849151015 +vt 0.44126299 0.873185992 +vt 0.430227011 0.862088978 +vt 0.454531014 0.887139022 +vt 0.378493011 0.710196018 +vt 0.362760007 0.706300974 +vt 0.374440014 0.731871009 +vt 0.354811996 0.691094995 +vt 0.417786002 0.803228974 +vt 0.349191993 0.683072984 +vt 0.344494998 0.678995013 +vt 0.339307994 0.67563802 +vt 0.33028999 0.672648013 +vt 0.314112008 0.669674993 +vt 0.287476987 0.666364014 +vt 0.172805995 0.653823018 +vt 0.207475007 0.657051027 +vt 0.171483994 0.648311973 +vt 0.153017998 0.65300101 +vt 0.200154006 0.657886982 +vt 0.224950001 0.657131016 +vt 0.247118995 0.662347019 +vt 0.0993760005 0.632516026 +vt 0.0860550031 0.643809021 +vt 0.113084003 0.648388028 +vt 0.136282995 0.641269028 +vt 0.0532109998 0.627844989 +vt 0.0590730011 0.633867025 +vt 0.0690490007 0.639078021 +vt 0.0485859998 0.620679021 +vt 0.0452970006 0.612693012 +vt 0.0431069992 0.595552981 +vt 0.0434379987 0.604209006 +vt 0.0443979986 0.587049007 +vt 0.0810210034 0.551898003 +vt 0.0768380016 0.541922987 +vt 0.0634939969 0.556137979 +vt 0.129226997 0.496253997 +vt 0.110813998 0.510852993 +vt 0.0540260002 0.567974985 +vt 0.164385006 0.473271996 +vt 0.146969005 0.483696014 +vt 0.181758001 0.464956999 +vt 0.199392006 0.458748996 +vt 0.266283989 0.453821987 +vt 0.261067986 0.455529988 +vt 0.302123994 0.457599014 +vt 0.451458007 0.481582999 +vt 0.404439002 0.473688006 +vt 0.454587996 0.486357003 +vt 0.493065 0.488646001 +vt 0.381532997 0.470302999 +vt 0.410250992 0.478074998 +vt 0.360612005 0.469170004 +vt 0.349040002 0.464504987 +vt 0.311809003 0.458707988 +vt 0.346143991 0.46401 +vt 0.302123994 0.457599014 +vt 0.29290399 0.456467003 +vt 0.283946007 0.455881 +vt 0.292899996 0.456465006 +vt 0.271212012 0.455689013 +vt 0.283946007 0.455881 +vt 0.522264004 0.506510019 +vt 0.421483994 0.484506994 +vt 0.533330977 0.450408995 +vt 0.546265006 0.458521992 +vt 0.509865999 0.437543988 +vt 0.565136015 0.438156992 +vt 0.551925004 0.463555008 +vt 0.528154016 0.432285994 +vt 0.382144988 0.371300995 +vt 0.408445001 0.37886101 +vt 0.363501012 0.354993999 +vt 0.34138599 0.349052995 +vt 0.429859996 0.395429999 +vt 0.472748011 0.41810599 +vt 0.323540002 0.314713001 +vt 0.336737007 0.335922986 +vt 0.275788009 0.303083986 +vt 0.290661007 0.316365004 +vt 0.311711997 0.331200987 +vt 0.255308002 0.275646001 +vt 0.239642993 0.240330994 +vt 0.232804 0.217886999 +vt 0.247040004 0.259342998 +vt 0.226244003 0.191300005 +vt 0.220471993 0.163176998 +vt 0.215958998 0.136041 +vt 0.213088006 0.112392999 +vt 0.213036001 0.0815989971 +vt 0.225716993 0.0942739993 +vt 0.219317004 0.0643889979 +vt 0.215462998 0.0719159991 +vt 0.212185994 0.0946730003 +vt 0.230639994 0.0523179993 +vt 0.245040998 0.0454020016 +vt 0.237583995 0.0481280014 +vt 0.224463999 0.0577969998 +vt 0.263112992 0.0463789999 +vt 0.252756 0.0443190001 +vt 0.278551012 0.0530939996 +vt 0.41328001 0.136200994 +vt 0.374031991 0.111345001 +vn 0.0399999991 -0.0227000006 0.998899996 +vn 0.0410999991 -0.0210999995 0.998899996 +vn 0.0441000015 -0.0196000002 0.99879998 +vn 0.0403999984 -0.0127999997 0.999100029 +vn 0.0401000008 -0.0162000004 0.999100029 +vn 0.0337000005 -0.00079999998 0.99940002 +vn 0.0350000001 -9.99999975e-05 0.99940002 +vn 0.0416000001 -0.00889999978 0.999100029 +vn 0.0419000015 -0.0083999997 0.999100029 +vn 0.0320000015 -0.0013 0.999499977 +vn -0.159400001 -0.109499998 -0.981100023 +vn -0.0320000015 0.00139999995 -0.999499977 +vn -0.0339000002 0.000699999975 -0.99940002 +vn -0.0348999985 0.000199999995 -0.99940002 +vn -0.0399000011 0.0102000004 -0.999199986 +vn -0.0397999994 0.00949999969 -0.999199986 +vn -0.0401000008 0.0227000006 -0.998899996 +vn -0.0461999997 0.0186000001 -0.99879998 +vn -0.0441999994 0.0196000002 -0.99879998 +vn -0.0410000011 0.0126 -0.999100029 +vn -0.0432999991 0.0157999992 -0.998899996 +vn 0.00439999998 -0.315100014 -0.949100018 +vn 0.0137999998 -0.304699987 -0.952400029 +vn 0.0249000005 -0.307900012 -0.951099992 +vn -0.00219999999 0.00380000006 -1 +vn -0.0208999999 0 -0.999800026 +vn -0.0113000004 -0.0119000003 -0.999899983 +vn -0.00700000022 -0.299400002 -0.954100013 +vn 0.0161000006 -0.291000009 -0.95660001 +vn 0.00270000007 -0.278600007 -0.960399985 +vn -0.0132999998 -0.275700003 -0.961099982 +vn -0.00150000001 -0.254099995 -0.967199981 +vn -0.0184000004 -0.253300011 -0.967199981 +vn -0.0311999992 -0.268900007 -0.962700009 +vn -0.0331999995 -0.280200005 -0.959399998 +vn 0.0304000005 -0.259499997 -0.965300024 +vn 0.0190999992 -0.273299992 -0.961700022 +vn 0.0157999992 -0.252700001 -0.967400014 +vn -0.0355999991 -0.242799997 -0.969399989 +vn -0.0370000005 -0.257800013 -0.965499997 +vn -0.127299994 -0.275900006 -0.952700019 +vn -0.0533999987 -0.237200007 -0.970000029 +vn -0.0217000004 -0.234899998 -0.971800029 +vn -0.0397999994 -0.222599998 -0.974099994 +vn -0.00410000002 -0.225700006 -0.97420001 +vn 0.00779999979 -0.235699996 -0.971800029 +vn 0.0296 -0.238700002 -0.970600009 +vn 0.0260000005 -0.223199993 -0.974399984 +vn 0.0612000003 -0.234999999 -0.970099986 +vn 0.0443000011 -0.243699998 -0.968800008 +vn 0.0505999997 -0.223000005 -0.973500013 +vn 0.069600001 -0.264299989 -0.961899996 +vn -0.0557000004 -0.206 -0.976999998 +vn -0.0691 -0.227599993 -0.971300006 +vn -0.0388000011 -0.204600006 -0.978100002 +vn -0.0184000004 -0.212799996 -0.976899981 +vn -0.00490000006 -0.200399995 -0.979700029 +vn -0.0252 -0.193700001 -0.980700016 +vn 0.0102000004 -0.211500004 -0.977299988 +vn 0.0241 -0.200599998 -0.979399979 +vn 0.0557000004 -0.203400001 -0.977500021 +vn 0.0395000018 -0.208700001 -0.977199972 +vn -0.0436000004 -0.187000006 -0.981400013 +vn -0.0300999992 -0.173299998 -0.984399974 +vn -0.0577000007 -0.169200003 -0.983900011 +vn -0.0577999987 -0.185499996 -0.98089999 +vn -0.0115 -0.176300004 -0.984300017 +vn 0.00749999983 -0.185000002 -0.98269999 +vn 0.0370000005 -0.187700003 -0.98150003 +vn 0.0256999992 -0.172700003 -0.984600008 +vn 0.064000003 -0.179399997 -0.981700003 +vn 0.0467000008 -0.176899999 -0.983099997 +vn 0.0821999982 -0.203899994 -0.975499988 +vn 0.065700002 -0.194800004 -0.978600025 +vn 0.0922999978 -0.186399996 -0.978100002 +vn 0.0894000009 -0.171200007 -0.98119998 +vn -0.0733999982 -0.153899997 -0.985400021 +vn -0.0549999997 -0.146699995 -0.987699986 +vn -0.0390999988 -0.158999994 -0.986500025 +vn -0.0324999988 -0.139899999 -0.989600003 +vn 0.0074 -0.162 -0.986800015 +vn -0.0157999992 -0.1558 -0.987699986 +vn 0.00159999996 -0.1435 -0.989700019 +vn -0.0182000007 -0.136800006 -0.990400016 +vn 0.0269000009 -0.151600003 -0.988099992 +vn 0.052099999 -0.162 -0.985400021 +vn 0.0441999994 -0.148200005 -0.987999976 +vn 0.0666000023 -0.144600004 -0.987200022 +vn 0.0759999976 -0.157900006 -0.984499991 +vn 0.0898000002 -0.133000001 -0.986999989 +vn -0.0623999983 -0.127900004 -0.989799976 +vn -0.0776999965 -0.129899994 -0.988499999 +vn -0.0678000003 -0.104199998 -0.992200017 +vn -0.0502999984 -0.108099997 -0.992900014 +vn -0.0886999965 -0.150900006 -0.984600008 +vn -0.0917999968 -0.111199997 -0.989600003 +vn -0.0916000009 -0.132599995 -0.986899972 +vn -0.0456000008 -0.131500006 -0.9903 +vn -0.0342999995 -0.119199999 -0.992299974 +vn -0.00650000013 -0.1193 -0.992799997 +vn 0.0313999988 -0.127100006 -0.991400003 +vn 0.0141000003 -0.131300002 -0.99119997 +vn 0.0132999998 -0.111500002 -0.993700027 +vn 0.0505000018 -0.130899996 -0.990100026 +vn 0.0542000011 -0.114200003 -0.991999984 +vn 0.074500002 -0.125300005 -0.989300013 +vn 0.074000001 -0.1083 -0.991400003 +vn 0.0908999965 -0.1074 -0.990100026 +vn -0.195999995 -0.103600003 -0.975099981 +vn -0.216999993 -0.0952999964 -0.971499979 +vn -0.203700006 -0.0825000033 -0.975600004 +vn -0.199699998 -0.1329 -0.970799983 +vn -0.170100003 -0.102600001 -0.980099976 +vn -0.188600004 -0.0914999992 -0.977800012 +vn -0.169100001 -0.0864000022 -0.98180002 +vn -0.179800004 -0.133200005 -0.974600017 +vn -0.167999998 -0.113300003 -0.979300022 +vn -0.151099995 -0.0916000009 -0.984300017 +vn -0.148599997 -0.107199997 -0.983099997 +vn -0.119400002 -0.109499998 -0.986800015 +vn -0.133399993 -0.0966000035 -0.986299992 +vn -0.117899999 -0.0834000036 -0.989499986 +vn -0.136000007 -0.113899998 -0.984099984 +vn -0.101800002 -0.0931999981 -0.990400016 +vn -0.0842000023 -0.0923999995 -0.992200017 +vn -0.0670000017 -0.0797000006 -0.994599998 +vn -0.0487999991 -0.0841000006 -0.995299995 +vn -0.0275999997 -0.104500003 -0.994099975 +vn -0.0313999988 -0.0860000029 -0.995800018 +vn -0.00949999969 -0.0949999988 -0.995400012 +vn 0.0364999995 -0.105099998 -0.993799984 +vn 0.0131000001 -0.0978000015 -0.995100021 +vn 0.0263 -0.0819000006 -0.996299982 +vn 0.00939999986 -0.0790999979 -0.996800005 +vn 0.0390999988 -0.0898000002 -0.995199978 +vn 0.0568999983 -0.0935000032 -0.994000018 +vn 0.0715999976 -0.0896999985 -0.993399978 +vn 0.0905999988 -0.0899000019 -0.99180001 +vn 0.107600003 -0.112099998 -0.987900019 +vn 0.108800001 -0.0895999968 -0.99000001 +vn 0.150199994 -0.0900999978 -0.984499991 +vn 0.131600007 -0.101199999 -0.986100018 +vn 0.127299994 -0.0811000019 -0.988499999 +vn 0.136199996 -0.1149 -0.984000027 +vn 0.118900001 -0.1197 -0.985700011 +vn 0.166899994 -0.107199997 -0.980099976 +vn 0.167300001 -0.0935000032 -0.98150003 +vn 0.1523 -0.1162 -0.98150003 +vn 0.163200006 -0.121600002 -0.979099989 +vn 0.208000004 -0.114200003 -0.971400023 +vn 0.186299995 -0.102899998 -0.977100015 +vn 0.184599996 -0.0876000002 -0.978900015 +vn 0.201100007 -0.0863000005 -0.975799978 +vn 0.182899997 -0.160699993 -0.969900012 +vn 0.198500007 -0.141900003 -0.969799995 +vn 0.218199998 -0.0949999988 -0.971300006 +vn 0.2368 -0.0986000001 -0.966499984 +vn 0.233500004 -0.0807999969 -0.968999982 +vn 0.252499998 -0.0926000029 -0.963100016 +vn 0.2333 -0.148900002 -0.960900009 +vn 0.219799995 -0.0754000023 -0.972599983 +vn -0.289400011 -0.0604999997 -0.955299973 +vn -0.299600005 -0.0816999972 -0.950600028 +vn -0.306199998 -0.0593999997 -0.950100005 +vn -0.280299991 -0.0782999992 -0.956700027 +vn -0.315600008 -0.0781000033 -0.94569999 +vn -0.324400008 -0.0582000017 -0.944100022 +vn -0.248400003 -0.0662999973 -0.966400027 +vn -0.266099989 -0.070100002 -0.961399972 +vn -0.270399988 -0.0513000004 -0.961399972 +vn -0.263900012 -0.0921000019 -0.960099995 +vn -0.224099994 -0.0714000016 -0.972000003 +vn -0.2315 -0.0535999984 -0.971400023 +vn -0.251300007 -0.0883999988 -0.96390003 +vn -0.236000001 -0.0878000036 -0.967800021 +vn -0.209000006 -0.0595999993 -0.976100028 +vn -0.187600002 -0.0709000006 -0.979700029 +vn -0.193000004 -0.0502000004 -0.979900002 +vn -0.174999997 -0.0494000018 -0.983299971 +vn -0.168699995 -0.0678000003 -0.983299971 +vn -0.152099997 -0.0719000027 -0.985700011 +vn -0.150299996 -0.0544000007 -0.987100005 +vn -0.138300002 -0.0777999982 -0.987299979 +vn -0.130600005 -0.064199999 -0.989400029 +vn -0.111900002 -0.059799999 -0.991900027 +vn -0.0812000036 -0.0691 -0.994300008 +vn -0.0996000022 -0.072800003 -0.992399991 +vn -0.0881000012 -0.0520000011 -0.994700015 +vn -0.0590999983 -0.060899999 -0.996399999 +vn -0.0306000002 -0.0649999976 -0.997399986 +vn -0.0439999998 -0.0562000014 -0.997500002 +vn -0.0114000002 -0.0729999989 -0.997300029 +vn -0.00820000004 -0.0555000007 -0.998399973 +vn 0.00989999995 -0.0617000014 -0.998000026 +vn 0.0283000004 -0.0577999987 -0.997900009 +vn 0.0423999988 -0.069600001 -0.996699989 +vn 0.0581 -0.0680999979 -0.995999992 +vn 0.0816999972 -0.076700002 -0.993700027 +vn 0.0754000023 -0.0606000014 -0.995299995 +vn 0.0961999968 -0.0606000014 -0.993499994 +vn 0.115800001 -0.0653000027 -0.991100013 +vn 0.100199997 -0.0781999975 -0.991900027 +vn 0.136299998 -0.0647 -0.988600016 +vn 0.1734 -0.0728999972 -0.982200027 +vn 0.155699998 -0.0715999976 -0.985199988 +vn 0.171800002 -0.0559 -0.983500004 +vn 0.147100002 -0.0505999997 -0.987800002 +vn 0.1963 -0.0691 -0.978100002 +vn 0.1928 -0.0509000011 -0.979900002 +vn 0.209600002 -0.0577999987 -0.976100028 +vn 0.236499995 -0.0606999993 -0.969699979 +vn 0.221799999 -0.0527999997 -0.973699987 +vn 0.248799995 -0.0741999969 -0.965699971 +vn 0.2579 -0.0584999993 -0.964399993 +vn 0.283699989 -0.0825999975 -0.95539999 +vn 0.267199993 -0.0857999995 -0.959800005 +vn 0.279300004 -0.0617000014 -0.958199978 +vn 0.324099988 -0.0714000016 -0.943300009 +vn 0.30309999 -0.0820000023 -0.949400008 +vn 0.301099986 -0.0669 -0.951300025 +vn 0.32249999 -0.0581 -0.944800019 +vn 0.335099995 -0.0766000003 -0.939100027 +vn 0.335200012 -0.0617999993 -0.940100014 +vn 0.320800006 -0.0872000009 -0.943099976 +vn 0.302599996 -0.0463999994 -0.952000022 +vn -0.293099999 -0.0373999998 -0.955299973 +vn -0.269600004 -0.0362999998 -0.962300003 +vn -0.27790001 -0.0229000002 -0.960300028 +vn -0.264200002 -0.00960000046 -0.964399993 +vn -0.253199995 -0.0275999997 -0.967000008 +vn -0.245800003 -0.0430000015 -0.968400002 +vn -0.241999999 -0.0179999992 -0.970099986 +vn -0.222200006 -0.0370000005 -0.974300027 +vn -0.204600006 -0.0315999985 -0.978299975 +vn -0.185100004 -0.0296999998 -0.982299984 +vn -0.158000007 -0.0397999994 -0.986599982 +vn -0.149399996 -0.0253999997 -0.988399982 +vn -0.172600001 -0.0199999996 -0.984799981 +vn -0.129899994 -0.0379000008 -0.990800023 +vn -0.1184 -0.0461999997 -0.991900027 +vn -0.101599999 -0.0412000008 -0.994000018 +vn -0.0829999968 -0.0286999997 -0.996100008 +vn -0.1074 -0.0238000005 -0.993900001 +vn -0.0680000037 -0.0441000015 -0.996699989 +vn -0.0625 -0.0198999997 -0.997799993 +vn -0.0482000001 -0.0329999998 -0.998300016 +vn -0.0274 -0.0395999998 -0.99879998 +vn -0.0315000005 -0.0192000009 -0.999300003 +vn -0.00860000029 -0.0392999984 -0.999199986 +vn -0.0110999998 -0.0227000006 -0.99970001 +vn 0.0260000005 -0.0306000002 -0.999199986 +vn 0.0149999997 -0.0430999994 -0.999000013 +vn 0.00630000001 -0.0220999997 -0.99970001 +vn 0.0243999995 -0.0113000004 -0.999599993 +vn 0.0057000001 -0.000600000028 -1 +vn 0.0430000015 -0.0428999998 -0.998199999 +vn 0.0438000001 -0.0184000004 -0.998899996 +vn 0.0573999994 -0.0207000002 -0.998099983 +vn 0.0601000004 -0.0436000004 -0.997200012 +vn 0.0643000007 0.0013 -0.997900009 +vn 0.0417999998 0.00340000005 -0.999100029 +vn 0.0197000001 0.0216000006 -0.999599993 +vn 0.0908999965 -0.0408000015 -0.995000005 +vn 0.0759999976 -0.0428000018 -0.996200025 +vn 0.072800003 -0.0189999994 -0.997200012 +vn 0.0952000022 -0.0189999994 -0.995299995 +vn 0.0820999965 -0.000199999995 -0.996599972 +vn 0.111000001 -0.0480999984 -0.992699981 +vn 0.108999997 -0.0285999998 -0.993600011 +vn 0.124499999 -0.0412000008 -0.991400003 +vn 0.0983999968 -0.00439999998 -0.995100021 +vn 0.118000001 -0.00719999988 -0.992999971 +vn 0.113399997 0.00449999981 -0.993499994 +vn 0.141000003 -0.0306000002 -0.989499986 +vn 0.129099995 -0.0186999999 -0.99150002 +vn 0.183500007 -0.0359000005 -0.9824 +vn 0.158800006 -0.0357999988 -0.986699998 +vn 0.172099993 -0.0243999995 -0.984799981 +vn 0.207300007 -0.0328000002 -0.977699995 +vn 0.224900007 -0.0353999995 -0.973699987 +vn 0.257999986 -0.0329000019 -0.965600014 +vn 0.239899993 -0.0416000001 -0.969900012 +vn 0.239299998 -0.0197999999 -0.970700026 +vn 0.288199991 -0.0307999998 -0.957099974 +vn 0.304699987 -0.0278999992 -0.952099979 +vn 0.283699989 -0.0460000001 -0.957799971 +vn 0.270000011 -0.0405000001 -0.962000012 +vn 0.292899996 -0.0157999992 -0.95599997 +vn 0.274500012 -0.0146000003 -0.961499989 +vn 0.261200011 -0.00639999984 -0.965300024 +vn -0.230499998 -0.00319999992 -0.973100007 +vn -0.241099998 0.00510000018 -0.970499992 +vn -0.252700001 0.0109000001 -0.967499971 +vn -0.234899998 0.0228000004 -0.971800029 +vn -0.194299996 -0.0107000005 -0.98089999 +vn -0.218400002 -0.0165999997 -0.975700021 +vn -0.206799999 0.00039999999 -0.978399992 +vn -0.182099998 0.00230000005 -0.983299971 +vn -0.1426 -0.0105999997 -0.989700019 +vn -0.160799995 -0.00630000001 -0.986999989 +vn -0.142800003 0.00769999996 -0.989700019 +vn -0.127399996 -0.00989999995 -0.99180001 +vn -0.112000003 -0.00689999992 -0.993700027 +vn -0.0841000006 -0.0121999998 -0.996399999 +vn -0.0957999974 -0.00359999994 -0.995400012 +vn -0.0447999984 -0.00810000021 -0.999000013 +vn -0.0653999969 0.000600000028 -0.997900009 +vn -0.0482000001 0.00769999996 -0.99879998 +vn -0.0795999989 0.0126999998 -0.996699989 +vn 0.151199996 -0.0137999998 -0.988399982 +vn 0.140100002 0.00120000006 -0.990100026 +vn 0.143099993 0.0141000003 -0.989600003 +vn 0.119800001 0.0241 -0.992500007 +vn 0.119099997 0.0762000009 -0.989899993 +vn 0.106899999 0.0172000006 -0.994099975 +vn 0.0964000002 0.0105999997 -0.995299995 +vn 0.0847000033 0.0287999995 -0.995999992 +vn 0.167199999 -0.00609999988 -0.985899985 +vn 0.176599994 0.0100999996 -0.984200001 +vn 0.161200002 0.0154999997 -0.986800015 +vn 0.167799994 0.0238000005 -0.985499978 +vn 0.146300003 0.0255999994 -0.988900006 +vn 0.191400006 -0.0103000002 -0.98150003 +vn 0.212699994 -0.0148999998 -0.976999998 +vn 0.242400005 -0.00540000014 -0.970200002 +vn 0.222499996 -0.000500000024 -0.974900007 +vn 0.204899997 0.0115999999 -0.978699982 +vn 0.189799994 0.0157999992 -0.981700003 +vn 0.200499997 0.0267999992 -0.979300022 +vn 0.175400004 0.0219999999 -0.984300017 +vn 0.2456 0.00939999986 -0.969299972 +vn 0.225799993 0.0164999999 -0.973999977 +vn 0.214499995 0.0245999992 -0.976400018 +vn -0.219099998 0.0126999998 -0.975600004 +vn -0.205899999 0.0223999992 -0.978299975 +vn -0.214499995 0.0335999988 -0.976199985 +vn -0.230700001 0.0505000018 -0.971700013 +vn -0.191599995 0.0392999984 -0.980700016 +vn -0.199900001 0.0555999987 -0.978200018 +vn -0.217299998 0.0540999994 -0.974600017 +vn -0.164299995 0.0120999999 -0.986299992 +vn -0.187800005 0.0182000007 -0.981999993 +vn -0.169400007 0.0299999993 -0.985099971 +vn -0.147400007 0.0269000009 -0.988699973 +vn -0.106899999 0.0127999997 -0.994199991 +vn -0.128000006 0.0174000002 -0.991599977 +vn -0.115000002 0.0348999985 -0.992799997 +vn -0.0834999979 0.0297999997 -0.996100008 +vn -0.101300001 0.0370999984 -0.994199991 +vn -0.0577999987 0.0203000009 -0.998099983 +vn -0.0612000003 0.0379999988 -0.997399986 +vn -0.178000003 0.0553000011 -0.982500017 +vn -0.156499997 0.0480000004 -0.986500025 +vn -0.133499995 0.0399999991 -0.990199983 +vn -0.137500003 0.0562999994 -0.988900006 +vn -0.117799997 0.0612999983 -0.991100013 +vn -0.0808999985 0.0498999991 -0.995500028 +vn -0.0979999974 0.0571000017 -0.993600011 +vn -0.068599999 0.0617000014 -0.995700002 +vn -0.155499995 0.0714000016 -0.985300004 +vn -0.171700001 0.0874999985 -0.981299996 +vn -0.146200001 0.0886999965 -0.985300004 +vn -0.179499999 0.0688999966 -0.981299996 +vn -0.129600003 0.0768000036 -0.988600016 +vn -0.107100002 0.0874999985 -0.990400016 +vn -0.0952999964 0.0744000003 -0.992699981 +vn -0.0887999982 0.0984999985 -0.99119997 +vn -0.075000003 0.0812999979 -0.993900001 +vn -0.056400001 0.0784000009 -0.995299995 +vn -0.0566999987 0.0952000022 -0.993799984 +vn -0.0236000009 0.070100002 -0.997300029 +vn -0.0449000001 0.0653999969 -0.996800005 +vn -0.0346999988 0.0850000009 -0.995800018 +vn -0.0311999992 0.0489000008 -0.998300016 +vn 0.00820000004 0.0763999969 -0.996999979 +vn -0.0092000002 0.066200003 -0.997799993 +vn -0.00800000038 0.0876000002 -0.996100008 +vn 0.00499999989 0.0516000018 -0.998700023 +vn 0.0322000012 0.0838000029 -0.995999992 +vn 0.0152000003 0.0926000029 -0.995599985 +vn 0.0403999984 0.0625 -0.997200012 +vn 0.060899999 0.0579000004 -0.996500015 +vn 0.0221999995 0.0647 -0.997699976 +vn -0.165700004 0.0996999964 -0.981100023 +vn -0.147300005 0.108800001 -0.983099997 +vn -0.163900003 0.112800002 -0.980000019 +vn -0.126800001 0.0988999978 -0.986999989 +vn -0.123199999 0.120399997 -0.985099971 +vn -0.108400002 0.109200001 -0.988099992 +vn -0.139300004 0.126399994 -0.982200027 +vn -0.0907000005 0.118799999 -0.988799989 +vn -0.0741000026 0.103299998 -0.991900027 +vn -0.057599999 0.113300003 -0.991900027 +vn -0.0681999996 0.125300005 -0.989799976 +vn -0.0241 0.104900002 -0.994199991 +vn -0.0412000008 0.103399999 -0.993799984 +vn -0.0397000015 0.127000004 -0.991100013 +vn 0.0109999999 0.109300002 -0.993900001 +vn -0.0108000003 0.109999999 -0.993900001 +vn 0.00209999993 0.125400007 -0.9921 +vn 0.0357999988 0.103100002 -0.994000018 +vn 0.0322000012 0.124300003 -0.991699994 +vn 0.058699999 0.0953999981 -0.993700027 +vn 0.0527999997 0.120300002 -0.991299987 +vn 0.0900999978 0.109399997 -0.989899993 +vn 0.0706999972 0.110699996 -0.991299987 +vn 0.0842999965 0.1237 -0.988699973 +vn 0.102200001 0.105899997 -0.989099979 +vn 0.108599998 0.120399997 -0.986800015 +vn 0.0957999974 0.0741000026 -0.992600024 +vn -0.160300002 0.127299994 -0.978799999 +vn -0.153699994 0.141499996 -0.977900028 +vn -0.178499997 0.1197 -0.976599991 +vn -0.178900003 0.138099998 -0.974099994 +vn -0.126900002 0.142100006 -0.981700003 +vn -0.0899000019 0.141399994 -0.985899985 +vn -0.105999999 0.131899998 -0.985599995 +vn -0.1039 0.153799996 -0.982599974 +vn -0.0732000023 0.139699996 -0.987500012 +vn -0.0518000014 0.145400003 -0.987999976 +vn -0.0333000012 0.154100001 -0.987500012 +vn -0.0208999999 0.145999998 -0.989099979 +vn -0.0161000006 0.130600005 -0.991299987 +vn 0.00170000002 0.148000002 -0.989000022 +vn 0.0184000004 0.133300006 -0.99089998 +vn 0.0205000006 0.158099994 -0.987200022 +vn 0.0410000011 0.145300001 -0.988499999 +vn 0.0559 0.144800007 -0.987900019 +vn 0.0693000033 0.132400006 -0.988799989 +vn 0.0826999992 0.145099998 -0.986000001 +vn 0.120999999 0.139400005 -0.982800007 +vn 0.100199997 0.133900002 -0.985899985 +vn 0.104400001 0.153200001 -0.98269999 +vn -0.140000001 0.160300002 -0.977100015 +vn -0.160899997 0.154699996 -0.974799991 +vn -0.161699995 0.170200005 -0.972000003 +vn -0.142700002 0.177300006 -0.973800004 +vn -0.188199997 0.150199994 -0.970600009 +vn -0.1228 0.160300002 -0.979399979 +vn -0.124399997 0.185000002 -0.974799991 +vn -0.106799997 0.174799994 -0.978799999 +vn -0.0869000033 0.166500002 -0.982200027 +vn -0.0482000001 0.164700001 -0.985199988 +vn -0.0713 0.158800006 -0.984700024 +vn -0.067400001 0.177200004 -0.981899977 +vn -0.0256999992 0.179800004 -0.983399987 +vn -0.0478999987 0.181600004 -0.982200027 +vn -0.0153000001 0.166099995 -0.986000001 +vn 0.000300000014 0.165700004 -0.986199975 +vn 0.0388000011 0.163100004 -0.985899985 +vn 0.0152000003 0.182500005 -0.983099997 +vn 0.0377999991 0.178000003 -0.983299971 +vn 0.0658999979 0.157700002 -0.985300004 +vn 0.0612999983 0.172999993 -0.98299998 +vn 0.0841000006 0.167600006 -0.982299984 +vn 0.104400001 0.171900004 -0.979600012 +vn 0.147300005 0.162499994 -0.975700021 +vn 0.126200005 0.163000003 -0.978500009 +vn 0.140799999 0.180800006 -0.973399997 +vn 0.144500002 0.144600004 -0.978900015 +vn 0.165800005 0.165900007 -0.972100019 +vn 0.153799996 0.185699999 -0.970499992 +vn 0.1734 0.179299995 -0.968400002 +vn 0.161500007 0.147100002 -0.975799978 +vn 0.172399998 0.157900006 -0.972299993 +vn -0.165099993 0.186199993 -0.968500018 +vn -0.182999998 0.193800002 -0.963800013 +vn -0.165900007 0.204400003 -0.964699984 +vn -0.191 0.203299999 -0.960300028 +vn -0.182400003 0.177100003 -0.967100024 +vn -0.145099998 0.195099995 -0.970000029 +vn -0.107699998 0.1963 -0.974600017 +vn -0.128999993 0.209099993 -0.969399989 +vn -0.0856000036 0.185599998 -0.978900015 +vn -0.0892999992 0.202999994 -0.975099981 +vn -0.0623999983 0.195600003 -0.978699982 +vn -0.0615000017 0.209700003 -0.975799978 +vn -0.00439999998 0.185699999 -0.982599974 +vn -0.0188999996 0.202800006 -0.978999972 +vn 0.00340000005 0.203299999 -0.979099989 +vn -0.0388999991 0.193599999 -0.980300009 +vn -0.0403999984 0.208800003 -0.977100015 +vn -0.0471000001 0.227300003 -0.9727 +vn -0.0296999998 0.222200006 -0.9745 +vn -0.0335000008 0.239199996 -0.970399976 +vn -0.0649000034 0.224999994 -0.972199976 +vn 0.0320000015 0.193499997 -0.980599999 +vn 0.0201999992 0.206 -0.978399992 +vn 0.0527999997 0.191799998 -0.980000019 +vn 0.0390999988 0.2095 -0.976999998 +vn 0.0582000017 0.208100006 -0.976400018 +vn 0.0544000007 0.224099994 -0.97299999 +vn 0.0357999988 0.229800001 -0.972599983 +vn 0.0491999984 0.236699998 -0.970300019 +vn 0.0190999992 0.2192 -0.975499988 +vn 0.0183000006 0.226600006 -0.973800004 +vn 0.0949999988 0.187399998 -0.977699995 +vn 0.0715000033 0.188099995 -0.979499996 +vn 0.0800999999 0.2042 -0.975700021 +vn 0.122599997 0.186499998 -0.974799991 +vn 0.1087 0.202600002 -0.973200023 +vn 0.1373 0.202999994 -0.969500005 +vn 0.1708 0.196400002 -0.965499997 +vn 0.159099996 0.211999997 -0.96420002 +vn 0.187099993 0.189600006 -0.96390003 +vn 0.1822 0.209399998 -0.960699975 +vn 0.200399995 0.204699993 -0.958100021 +vn -0.183200002 0.214900002 -0.959299982 +vn -0.188899994 0.235400006 -0.953400016 +vn -0.168400005 0.228200004 -0.958899975 +vn -0.206300005 0.228799999 -0.951399982 +vn -0.251800001 0.203799993 -0.946099997 +vn -0.200800002 0.242500007 -0.949100018 +vn -0.151700005 0.216100007 -0.96450001 +vn -0.149000004 0.232999995 -0.961000025 +vn -0.130500004 0.224600002 -0.965699971 +vn -0.111199997 0.235699996 -0.965399981 +vn -0.110100001 0.216800004 -0.970000029 +vn -0.0806000009 0.213699996 -0.97359997 +vn -0.0903000012 0.228 -0.969500005 +vn -0.0784000009 0.240799993 -0.967400014 +vn -0.0714000016 0.262699991 -0.962199986 +vn -0.059700001 0.240999997 -0.968699992 +vn -0.0838999972 0.267199993 -0.959999979 +vn -0.0259000007 0.270799994 -0.962300003 +vn 0.0952000022 0.219699994 -0.970899999 +vn 0.0713 0.226099998 -0.971499979 +vn 0.0773999989 0.239899993 -0.967700005 +vn 0.121799998 0.218400002 -0.968200028 +vn 0.0976999998 0.240500003 -0.965699971 +vn 0.114600003 0.2368 -0.9648 +vn 0.145400003 0.226999998 -0.963 +vn 0.132300004 0.2377 -0.962300003 +vn 0.160699993 0.235599995 -0.958500028 +vn 0.178299993 0.226799995 -0.957499981 +vn 0.197099999 0.220599994 -0.955299973 +vn -0.173999995 0.244200006 -0.953999996 +vn -0.178499997 0.256999999 -0.949800014 +vn -0.203299999 0.254799992 -0.9454 +vn -0.193299994 0.272799999 -0.942499995 +vn -0.1523 0.248999998 -0.956499994 +vn -0.157299995 0.265700012 -0.951200008 +vn -0.135299996 0.259000003 -0.956399977 +vn -0.129700005 0.241400003 -0.961700022 +vn -0.0956000015 0.251700014 -0.963100016 +vn -0.114 0.257200003 -0.959599972 +vn -0.101099998 0.270999998 -0.957199991 +vn -0.121600002 0.274699986 -0.953800023 +vn -0.0964000002 0.315699995 -0.943899989 +vn -0.140000001 0.281800002 -0.949199975 +vn 0.116400003 0.255299985 -0.959800005 +vn 0.0980999991 0.262499988 -0.959900022 +vn 0.0731000006 0.261700004 -0.962400019 +vn 0.107699998 0.278299987 -0.954400003 +vn 0.0883999988 0.268299997 -0.959299982 +vn 0.103500001 0.279900014 -0.954400003 +vn 0.123099998 0.271100014 -0.954699993 +vn 0.139799997 0.255199999 -0.956700027 +vn 0.139599994 0.280800015 -0.949599981 +vn 0.114200003 0.290800005 -0.949899971 +vn 0.156299993 0.251700014 -0.9551 +vn 0.1778 0.245900005 -0.952899992 +vn 0.196500003 0.238900006 -0.950999975 +vn 0.199000001 0.256799996 -0.945800006 +vn 0.182799995 0.266600013 -0.94630003 +vn 0.168799996 0.265500009 -0.949199975 +vn -0.159700006 0.283499986 -0.945599973 +vn -0.176599994 0.274300009 -0.945299983 +vn -0.179299995 0.291599989 -0.939599991 +vn -0.199100003 0.293000013 -0.935100019 +vn -0.143600002 0.302300006 -0.942300022 +vn -0.161799997 0.299299985 -0.940400004 +vn -0.153400004 0.3134 -0.93720001 +vn -0.169499993 0.315699995 -0.933600008 +vn -0.179499999 0.309300005 -0.933899999 +vn -0.193800002 0.309799999 -0.930800021 +vn 0.155699998 0.276899993 -0.948199987 +vn 0.176699996 0.283399999 -0.942600012 +vn 0.193399996 0.287800014 -0.938000023 +vn 0.202999994 0.273900002 -0.940100014 +vn 0.140300006 0.30250001 -0.942799985 +vn 0.164700001 0.298500001 -0.940100014 +vn 0.180299997 0.301899999 -0.936100006 +vn 0.190200001 0.310299993 -0.931400001 +vn 0.161400005 0.32280001 -0.932600021 +vn 0.0562999994 0.308999985 -0.949400008 +vn 0.0557000004 0.257600009 -0.964699984 +vn 0.0434000008 0.247299999 -0.967999995 +vn 0.132699996 0.326799989 -0.935699999 +vn 0.105099998 0.313899994 -0.943599999 +vn 0.155200005 0.312400013 -0.93720001 +vn 0.0979000032 0.2852 -0.953499973 +vn 0.134000003 0.300199986 -0.944400012 +vn 0.507799983 -0.856100023 -0.0964000002 +vn 0.529399991 -0.844399989 -0.0820999965 +vn 0.466500014 -0.869899988 0.159999996 +vn 0.469599992 -0.874000013 0.124499999 +vn 0.485799998 -0.863499999 0.135199994 +vn 0.478500009 -0.860000014 0.177599996 +vn 0.530300021 -0.843699992 -0.0829999968 +vn 0.516200006 -0.852400005 -0.0837000012 +vn 0.490099996 -0.861899972 0.129999995 +vn -0.210199997 -0.947000027 -0.243000001 +vn 0.0013 -0.99970001 0.0223999992 +vn -0.248999998 -0.967800021 0.0357000008 +vn 0.298099995 -0.944700003 -0.136399999 +vn 0.208399996 -0.958800018 0.193100005 +vn 0.0520000011 -0.97299999 -0.224900007 +vn 0.361699998 -0.917299986 0.1664 +vn 0.442299992 -0.889699996 -0.112999998 +vn 0.435400009 -0.894999981 0.0966000035 +vn -0.696699977 -0.662599981 -0.274899989 +vn -0.830200016 -0.554700017 -0.0555000007 +vn -0.817600012 -0.507799983 -0.271600008 +vn -0.707000017 -0.707099974 0.0131000001 +vn -0.560699999 -0.783800006 -0.26699999 +vn -0.576399982 -0.817099988 0.0138999997 +vn -0.424299985 -0.905499995 0.00820000004 +vn -0.396400005 -0.874100029 -0.280800015 +vn -0.992699981 0.00939999986 -0.119999997 +vn -0.995100021 0.00960000046 0.0980999991 +vn -0.989700019 0.105899997 -0.0964000002 +vn -0.985400021 -0.0736000016 0.153400004 +vn -0.979799986 -0.140499994 -0.142000005 +vn -0.957099974 -0.226600006 0.180299997 +vn -0.922800004 -0.3847 0.0201999992 +vn -0.920700014 -0.324600011 -0.216800004 +vn -0.961000025 0.2764 0.00400000019 +vn -0.970099986 0.238900006 -0.0421000011 +vn -0.958299994 0.153899997 0.240899995 +vn -0.977400005 0.202800006 -0.0604999997 +vn -0.974600017 0.128000006 0.183699995 +vn -0.982500017 0.0885000005 0.164000005 +vn -0.984200001 0.162100002 -0.0716999993 +vn -0.985899985 0.0653000027 0.154400006 +vn -0.859200001 0.510900021 0.0291000009 +vn -0.799099982 0.541199982 0.261799991 +vn -0.79519999 0.605099976 0.0394000001 +vn -0.904500008 0.426099986 -0.0200999994 +vn -0.9102 0.375200003 0.1752 +vn -0.854700029 0.437700003 0.279000014 +vn -0.932200015 0.2685 0.242500007 +vn -0.939199984 0.342900008 -0.0193000007 +vn -0.940999985 0.233899996 0.244499996 +vn -0.552600026 0.739499986 0.38440001 +vn -0.626299977 0.768299997 0.132200003 +vn -0.623399973 0.66960001 0.403699994 +vn -0.499900013 0.853900015 0.144800007 +vn -0.550599992 0.824400008 0.130799994 +vn -0.520399988 0.753400028 0.40200001 +vn -0.717299998 0.614899993 0.327600002 +vn -0.715200007 0.693700016 0.0850000009 +vn -0.423500001 0.816200018 0.393000007 +vn -0.452100009 0.874000013 0.178200006 +vn -0.424299985 0.754100025 0.50120002 +vn -0.444700003 0.88319999 0.148900002 +vn -0.442999989 0.883899987 0.149900004 +vn -0.419999987 0.827899992 0.371699989 +vn -0.464899987 0.793799996 0.39199999 +vn -0.469000012 0.870599985 0.148599997 +vn -0.478399992 0.784799993 0.393999994 +vn -0.745700002 0.649100006 0.150399998 +vn -0.877099991 0.350400001 0.328500003 +vn -0.9199 0.370799989 0.127900004 +vn -0.69599998 0.593699992 0.403899997 +vn -0.526799977 0.838400006 0.140100002 +vn -0.492799997 0.783800006 0.377799988 +vn -0.428900003 0.800100029 0.419400007 +vn -0.464399993 0.873199999 0.148000002 +vn -0.42750001 0.829999983 0.358200014 +vn -0.969900012 -0.203700006 0.133499995 +vn -0.886600018 -0.342900008 0.310499996 +vn -0.885399997 -0.448100001 0.123499997 +vn -0.901300013 -0.192599997 0.388099998 +vn -0.985899985 -0.00710000005 0.167199999 +vn -0.924799979 -0.0125000002 0.380299985 +vn -0.88440001 0.156399995 0.439799994 +vn -0.971499979 0.1884 0.143600002 +vn -0.522300005 -0.745500028 0.414000005 +vn -0.706399977 -0.690100014 0.157600001 +vn -0.730499983 -0.584399998 0.353399992 +vn -0.371600002 -0.881399989 0.291599989 +vn -0.487399995 -0.86619997 0.110299997 +vn 0.132499993 -0.912999988 0.385800004 +vn 0.0882000029 -0.989799976 0.111400001 +vn 0.0878999978 -0.925700009 0.367900014 +vn 0.157900006 -0.982299984 0.100599997 +vn 0.143000007 -0.984499991 0.1017 +vn 0.145300001 -0.937900007 0.315100014 +vn -0.132200003 -0.920899987 0.36680001 +vn -0.157199994 -0.981899977 0.105899997 +vn 0.134499997 -0.985800028 0.100500003 +vn 0.0949999988 -0.959200025 0.266200006 +vn 0.104000002 -0.990800023 0.0868000016 +vn 0.0962999985 -0.932600021 0.347799987 +vn 0.147 -0.980499983 0.130199999 +vn 0.1043 -0.882099986 0.459399998 +vn 0.138099998 -0.926900029 0.349099994 +vn 0.158500001 -0.98210001 0.1021 +vn 0.148300007 -0.937699974 0.314300001 +vn -0.153699994 -0.987200022 0.0430999994 +vn -0.30430001 -0.9296 0.208100006 +vn -0.266499996 -0.963800013 0.000699999975 +vn -0.202000007 -0.931999981 0.301099986 +vn -0.0416000001 -0.995999992 0.0786999986 +vn -0.0873999968 -0.946399987 0.31099999 +vn -0.00930000003 -0.926299989 0.376599997 +vn 0.0460000001 -0.994899988 0.0900999978 +vn 0.0313000008 -0.933399975 0.3574 +vn -0.556500018 -0.811399996 0.178800002 +vn -0.455199987 -0.890299976 -0.0153000001 +vn -0.49059999 -0.842999995 0.220500007 +vn -0.571799994 -0.818300009 -0.0584999993 +vn -0.518599987 -0.853600025 -0.0487999991 +vn -0.5898 -0.780399978 0.207499996 +vn -0.409999996 -0.875899971 0.254299998 +vn -0.369599998 -0.929199994 -0.0057000001 +vn -0.722100019 -0.675499976 0.149399996 +vn -0.65109998 -0.757000029 -0.0549999997 +vn -0.689100027 -0.6699 0.2764 +vn -0.726599991 -0.678499997 -0.107900001 +vn -0.679899991 -0.727100015 -0.0954999998 +vn -0.740199983 -0.657100022 0.142900005 +vn -0.645500004 -0.748199999 0.152999997 +vn -0.610199988 -0.788699985 -0.0749000013 +vn -0.628700018 -0.761900008 0.155599996 +vn -0.935299993 -0.279900014 -0.216299996 +vn -0.997799993 -0.0520000011 -0.0417999998 +vn -0.96329999 -0.0693999976 -0.259200007 +vn -0.968299985 -0.247500002 0.0346000008 +vn -0.869300008 -0.465600014 -0.166099995 +vn -0.902199984 -0.426099986 0.0662999973 +vn -0.827600002 -0.540700018 0.150399998 +vn -0.790099978 -0.597500026 -0.1373 +vn -0.793299973 -0.582799971 0.175799996 +vn -0.741999984 0.641099989 -0.196099997 +vn -0.880699992 0.47330001 -0.0207000002 +vn -0.781199992 0.619499981 0.0768000036 +vn -0.844299972 0.475400001 -0.247299999 +vn -0.917699993 0.307399988 -0.251800001 +vn -0.951300025 0.307399988 0.0241999999 +vn -0.989000022 0.1426 0.0388999991 +vn -0.959399998 0.130099997 -0.250200003 +vn -0.282799989 0.934099972 0.217800006 +vn -0.350300014 0.931299984 -0.0996000022 +vn -0.408699989 0.903699994 0.127399996 +vn -0.134299994 0.99000001 -0.0434000008 +vn -0.208299994 0.976100028 -0.0623999983 +vn -0.209900007 0.968500018 0.134100005 +vn -0.609799981 0.788999975 0.0749000013 +vn -0.558300018 0.811200023 -0.173899993 +vn -0.0833000019 0.995100021 -0.0526000001 +vn -0.124700002 0.9824 0.1391 +vn -0.0976999998 0.995000005 -0.0225000009 +vn -0.133100003 0.96359998 0.231900007 +vn -0.134800002 0.950399995 0.280200005 +vn -0.162599996 0.96329999 0.213799998 +vn -0.100900002 0.994400024 -0.0307 +vn -0.1734 0.963 0.206400007 +vn -0.583400011 0.810899973 -0.0443000011 +vn -0.412900001 0.888700008 0.199399993 +vn -0.572899997 0.783100009 0.241699994 +vn -0.221000001 0.975000024 -0.0252 +vn -0.246999994 0.930199981 0.271600008 +vn -0.404500008 0.912299991 -0.0640999973 +vn -0.166099995 0.950299978 0.263399988 +vn -0.131600007 0.990800023 -0.0309999995 +vn -0.142100006 0.964399993 0.222900003 +vn -0.858399987 0.453200012 0.240400001 +vn -0.853500009 0.519800007 -0.0370000005 +vn -0.800000012 0.514100015 0.309399992 +vn -0.910700023 0.409700006 -0.0520000011 +vn -0.896000028 0.440899998 -0.0533000007 +vn -0.885399997 0.435299993 0.163100004 +vn -0.721599996 0.666000009 0.188700005 +vn -0.735599995 0.673500001 -0.0731000006 +vn -0.873399973 0.450100005 0.185900003 +vn -0.913900018 0.405200005 -0.0240000002 +vn -0.843400002 0.441700011 0.305999994 +vn -0.898500025 0.434199989 -0.063699998 +vn -0.911300004 0.407999992 -0.0551000014 +vn -0.878000021 0.455000013 0.148499995 +vn -0.868799984 0.428799987 0.247700006 +vn -0.89230001 0.421700001 0.161200002 +vn -0.6074 0.7755 -0.172499999 +vn -0.372999996 0.9278 -0.00860000029 +vn -0.385500014 0.894699991 -0.225400001 +vn -0.578499973 0.812399983 0.0734999999 +vn -0.782500029 0.612900019 -0.110299997 +vn -0.748300016 0.653699994 0.112899996 +vn -0.814800024 0.546000004 0.195099995 +vn -0.86500001 0.495299995 -0.0806000009 +vn -0.8574 0.495999992 0.136999995 +vn 0.386700004 0.903900027 -0.182999998 +vn 0.181799993 0.982599974 0.0388999991 +vn 0.358700007 0.928499997 0.0960000008 +vn 0.000500000024 0.977500021 -0.210700005 +vn -0.00150000001 0.997099996 0.0766000003 +vn 0.192599997 0.954699993 -0.226799995 +vn -0.176699996 0.98150003 0.0738999993 +vn -0.187800005 0.956700027 -0.222299993 +vn 0.813300014 0.546000004 0.200900003 +vn 0.783800006 0.612200022 -0.104699999 +vn 0.747099996 0.654100001 0.118100002 +vn 0.898800015 0.434599996 -0.0562000014 +vn 0.865700006 0.495000005 -0.0744000003 +vn 0.856999993 0.495700002 0.140900001 +vn 0.577600002 0.812699974 0.0769999996 +vn 0.607599974 0.776000023 -0.169400007 +vn 0.914799988 0.402700007 -0.0302000009 +vn 0.896200001 0.419099987 0.145300001 +vn 0.911300004 0.408399999 -0.0515000001 +vn 0.864099979 0.429699987 0.262100011 +vn 0.870299995 0.43720001 0.226899996 +vn 0.872099996 0.449600011 0.192900002 +vn 0.91170001 0.407900006 -0.0485000014 +vn 0.868300021 0.459500015 0.186900005 +vn 0.734700024 0.675199986 -0.0661000013 +vn 0.581799984 0.801199973 0.140300006 +vn 0.582799971 0.808300018 -0.0841000006 +vn 0.717599988 0.668299973 0.1963 +vn 0.854499996 0.517499983 -0.0452999994 +vn 0.83099997 0.522499979 0.191 +vn 0.855499983 0.4551 0.247099996 +vn 0.896600008 0.440299988 -0.0465000011 +vn 0.867900014 0.434899986 0.239899993 +vn 0.1646 0.950299978 0.264099985 +vn 0.222499996 0.974799991 -0.0172000006 +vn 0.243300006 0.914399981 0.323599994 +vn 0.0967999995 0.994899988 -0.0285999998 +vn 0.131799996 0.990800023 -0.0297999997 +vn 0.136600003 0.972299993 0.189500004 +vn 0.411599994 0.888899982 0.201100007 +vn 0.408899993 0.910700023 -0.0590000004 +vn 0.160400003 0.96329999 0.215100005 +vn 0.0947000012 0.995500028 0.0013 +vn 0.161699995 0.928799987 0.333299994 +vn 0.131600007 0.9903 -0.0443999991 +vn 0.100000001 0.994499981 -0.0297999997 +vn 0.170900002 0.963400006 0.206699997 +vn 0.129899994 0.966099977 0.222900003 +vn 0.123000003 0.971700013 0.201700002 +vn 0.554899991 0.814700007 -0.168300003 +vn 0.771499991 0.636099994 -0.0132999998 +vn 0.730400026 0.643100023 -0.230299994 +vn 0.605300009 0.791999996 0.0797000006 +vn 0.347799987 0.932600021 -0.0965000018 +vn 0.403899997 0.905399978 0.131099999 +vn 0.280900002 0.932500005 0.226999998 +vn 0.206799999 0.976499975 -0.0606000014 +vn 0.209700003 0.966199994 0.150099993 +vn 0.959399998 0.138699993 -0.245499998 +vn 0.998399973 -0.0432999991 -0.0359000005 +vn 0.965300024 -0.0606000014 -0.253899992 +vn 0.987200022 0.149499997 0.0555000007 +vn 0.91930002 0.314999998 -0.236000001 +vn 0.948199987 0.317600012 -0.00359999994 +vn 0.879800022 0.471500009 0.0606000014 +vn 0.845399976 0.480300009 -0.233799994 +vn 0.786499977 -0.595399976 0.164399996 +vn 0.834999979 -0.530700028 -0.145300001 +vn 0.868799984 -0.488000005 0.0843999982 +vn 0.69630003 -0.711700022 -0.0931999981 +vn 0.748000026 -0.653800011 -0.114100002 +vn 0.753199995 -0.636399984 0.166199997 +vn 0.960900009 -0.273400009 0.0447000004 +vn 0.929199994 -0.307300001 -0.205300003 +vn 0.676500022 -0.716000021 0.172499999 +vn 0.663200021 -0.746599972 -0.0533000007 +vn 0.684700012 -0.671599984 0.282999992 +vn 0.661899984 -0.745899975 -0.075000003 +vn 0.672599971 -0.726400018 0.141100004 +vn 0.70389998 -0.691600025 0.161599994 +vn 0.669499993 -0.737999976 -0.0851000026 +vn 0.711899996 -0.691299975 0.1241 +vn 0.945100009 -0.317900002 -0.0751999989 +vn 0.854900002 -0.494300008 0.157900006 +vn 0.924099982 -0.318399996 0.211400002 +vn 0.747799993 -0.660399973 -0.067900002 +vn 0.735800028 -0.61619997 0.280999988 +vn 0.858500004 -0.501900017 -0.104999997 +vn 0.697000027 -0.695299983 0.175099999 +vn 0.684000015 -0.724099994 -0.0882999972 +vn 0.680700004 -0.710600019 0.178299993 +vn 0.966199994 0.113300003 0.2315 +vn 0.995999992 0.0727000013 -0.0511999987 +vn 0.967299998 0.0293000005 0.252000004 +vn 0.980499983 0.189500004 -0.0516000018 +vn 0.985499978 0.1602 -0.0560999997 +vn 0.971599996 0.1382 0.1919 +vn 0.976000011 -0.136299998 0.169799998 +vn 0.988699973 -0.117799997 -0.0926999971 +vn 0.979399979 0.0988999978 0.176200002 +vn 0.983299971 0.180000007 -0.0284000002 +vn 0.949999988 0.0921000019 0.298400015 +vn 0.988900006 0.129999995 -0.0720999986 +vn 0.983799994 0.168200001 -0.0621000007 +vn 0.98150003 0.0839999989 0.172199994 +vn 0.960300028 0.1259 0.248899996 +vn 0.979799986 0.194000006 -0.0478000008 +vn 0.965600014 0.1417 0.217999995 +vn 0.820599973 -0.523100019 -0.230199993 +vn 0.930999994 -0.363900006 0.0278999992 +vn 0.821399987 -0.569599986 0.0271000005 +vn 0.928900003 -0.306899995 -0.207499996 +vn 0.985899985 -0.0842000023 -0.144600004 +vn 0.981400013 -0.156900004 0.110399999 +vn 0.984499991 -0.0274999999 0.173199996 +vn 0.993600011 0.056499999 -0.0978000015 +vn 0.984899998 0.0259000007 0.171399996 +vn 0.399199992 -0.87349999 -0.278800011 +vn 0.236599997 -0.969900012 -0.0579999983 +vn 0.203700006 -0.9375 -0.282000005 +vn 0.426800013 -0.904299974 0.0140000004 +vn 0.560199976 -0.785000026 -0.264600009 +vn 0.575299978 -0.817700028 0.0163000003 +vn 0.706099987 -0.708000004 0.0111999996 +vn 0.69690001 -0.663500011 -0.272100002 +vn -0.364800006 -0.916700006 0.163100004 +vn -0.301699996 -0.941100001 -0.152600005 +vn -0.243499994 -0.967100024 0.0738999993 +vn -0.506500006 -0.85680002 -0.0964000002 +vn -0.442799985 -0.889100015 -0.115599997 +vn -0.433200002 -0.895500004 0.102200001 +vn -0.00319999992 -0.99970001 0.0241 +vn -0.0537 -0.97299999 -0.224399999 +vn -0.51639998 -0.851800025 -0.0877000019 +vn -0.534500003 -0.837199986 -0.115599997 +vn -0.498199999 -0.861899972 0.0944000036 +vn -0.486999989 -0.863399982 0.131799996 +vn -0.466399997 -0.869599998 0.161899999 +vn -0.529500008 -0.843900025 -0.0870999992 +vn -0.47209999 -0.87379998 0.116700001 +vn -0.206699997 -0.970799983 -0.121699996 +vn -0.00039999999 -0.996599972 0.0817999989 +vn 0.00039999999 -0.989600003 -0.143700004 +vn -0.195500001 -0.970600009 0.140200004 +vn -0.399699986 -0.913200021 -0.0800999999 +vn -0.351000011 -0.897300005 0.26789999 +vn -0.439900011 -0.876600027 0.195099995 +vn -0.483700007 -0.870400012 -0.0917000026 +vn -0.4736 -0.871399999 0.127700001 +vn 0.443199992 -0.879100025 0.175600007 +vn 0.400400013 -0.912100017 -0.0882999972 +vn 0.367599994 -0.91170001 0.183400005 +vn 0.483999997 -0.870100021 -0.0930999964 +vn 0.466800004 -0.868600011 0.166299999 +vn 0.187099993 -0.969799995 0.156800002 +vn 0.206200004 -0.971099973 -0.119800001 +vn 0.375999987 -0.853900015 0.360000014 +vn 0.378100008 -0.843299985 0.381999999 +vn 0.396400005 -0.833500028 0.384799987 +vn 0.390300006 -0.815299988 0.427899987 +vn 0.434700012 -0.83950001 0.325899988 +vn 0.341199994 -0.763100028 0.548900008 +vn -0.0439999998 -0.9648 0.25940001 +vn -0.26910001 -0.898699999 0.346300006 +vn 0.237000003 -0.855199993 0.460999995 +vn 0.359800011 -0.884199977 0.298099995 +vn 0.249500006 -0.819000006 0.516799986 +vn -0.775300026 -0.574699998 0.262100011 +vn -0.649200022 -0.682500005 0.335799992 +vn -0.550800025 -0.790000021 0.269400001 +vn -0.416999996 -0.856599987 0.303900003 +vn -0.914300025 -0.0829000026 0.396499991 +vn -0.880100012 -0.157600001 0.447899997 +vn -0.874300003 -0.415399998 0.251199991 +vn -0.921700001 0.142000005 0.360900015 +vn -0.846700013 0.0447999984 0.530099988 +vn -0.893899977 0.0340000018 0.446999997 +vn -0.924799979 0.0119000003 0.380299985 +vn -0.919499993 -0.0190999992 0.3926 +vn -0.731599987 0.401199996 0.551199973 +vn -0.852400005 0.275599986 0.444299996 +vn -0.788999975 0.329699993 0.518400013 +vn -0.833000004 0.154599994 0.531300008 +vn -0.871800005 0.149200007 0.466500014 +vn -0.509899974 0.583299994 0.632300019 +vn -0.573899984 0.530099988 0.624199986 +vn -0.470999986 0.578999996 0.665600002 +vn -0.675100029 0.503400028 0.539200008 +vn -0.367399991 0.704299986 0.6074 +vn -0.377900004 0.697600007 0.608699977 +vn -0.431699991 0.671999991 0.601700008 +vn -0.442900002 0.644800007 0.622900009 +vn -0.774999976 0.306100011 0.552900016 +vn -0.607900023 0.516499996 0.603100002 +vn -0.625800014 0.236900002 0.743099988 +vn -0.418799996 0.649800003 0.63440001 +vn -0.353500009 0.653199971 0.669700027 +vn -0.377700001 0.718900025 0.583500028 +vn -0.756500006 -0.387300014 0.52700001 +vn -0.789300025 -0.0255999994 0.613499999 +vn -0.784099996 -0.165199995 0.59829998 +vn -0.631399989 -0.25850001 0.731100023 +vn -0.679499984 0.102799997 0.726400018 +vn -0.32159999 -0.752099991 0.575200021 +vn -0.579599977 -0.553900003 0.5977 +vn -0.422399998 -0.593200028 0.685299993 +vn -0.575100005 -0.391099989 0.718599975 +vn 0.123400003 -0.826900005 0.548600018 +vn 0.109899998 -0.751999974 0.649999976 +vn 0.0691 -0.771399975 0.632600009 +vn -0.1206 -0.814300001 0.567799985 +vn 0.0441999994 -0.855199993 0.516499996 +vn 0.053199999 -0.824199975 0.563799977 +vn 0.108599998 -0.813799977 0.570900023 +vn 0.130099997 -0.854799986 0.502399981 +vn 0.100599997 -0.703999996 0.703000009 +vn -0.329299986 -0.846899986 0.417499989 +vn -0.132100001 -0.828499973 0.544200003 +vn -0.238499999 -0.78579998 0.570599973 +vn -0.337000012 -0.701099992 0.628300011 +vn -0.0705000013 -0.754100025 0.652999997 +vn -0.0296 -0.775200009 0.630999982 +vn -0.558700025 -0.706900001 0.433800012 +vn -0.497700006 -0.734300017 0.461699992 +vn -0.584200025 -0.67110002 0.456499994 +vn -0.421600014 -0.739400029 0.524900019 +vn -0.717100024 -0.586199999 0.377000004 +vn -0.73360002 -0.557900012 0.388000011 +vn -0.630400002 -0.66869998 0.394199997 +vn -0.646099985 -0.644800007 0.408300012 +vn -0.972800016 -0.0278999992 0.229800001 +vn -0.879000008 -0.355199993 0.318199992 +vn -0.941500008 -0.2007 0.270700008 +vn -0.784799993 -0.421000004 0.45480001 +vn -0.757499993 -0.459800005 0.463499993 +vn -0.864000022 0.436500013 0.2509 +vn -0.908599973 0.28459999 0.305700004 +vn -0.757000029 0.545099974 0.360300004 +vn -0.930599988 0.142100006 0.337300003 +vn -0.260100007 0.900200009 0.349299997 +vn -0.443599999 0.816500008 0.369599998 +vn -0.336899996 0.792100012 0.509000003 +vn -0.621900022 0.721599996 0.30430001 +vn -0.174999997 0.905399978 0.386799991 +vn -0.170399994 0.859700024 0.481599987 +vn -0.171900004 0.792500019 0.585200012 +vn -0.211300001 0.875400007 0.434799999 +vn -0.229300007 0.866100013 0.444299996 +vn -0.395900011 0.812300026 0.428299993 +vn -0.248600006 0.793099999 0.555999994 +vn -0.501800001 0.674000025 0.542200029 +vn -0.186199993 0.800400019 0.569700003 +vn -0.169400007 0.86619997 0.470099986 +vn -0.803900003 0.430000007 0.411000013 +vn -0.721400023 0.418300003 0.551900029 +vn -0.657199979 0.623099983 0.424100012 +vn -0.79369998 0.476700008 0.377900004 +vn -0.785700023 0.46450001 0.408399999 +vn -0.729700029 0.415100008 0.543299973 +vn -0.831499994 0.423099995 0.359899998 +vn -0.709299982 0.40079999 0.579900026 +vn -0.333499998 0.906099975 0.260399997 +vn -0.665000021 0.657400012 0.354299992 +vn -0.516900003 0.800300002 0.303799987 +vn -0.675100029 0.548900008 0.493000001 +vn -0.791199982 0.515699983 0.328799993 +vn -0.665600002 0.511300027 0.543600023 +vn 0.153699994 0.932299972 0.327499986 +vn -0.00139999995 0.943000019 0.332800001 +vn 0.295399994 0.866599977 0.402200013 +vn -0.147400007 0.907000005 0.394600004 +vn 0.789799988 0.515600026 0.332300007 +vn 0.662500024 0.657400012 0.358999997 +vn 0.671599984 0.548799992 0.497799993 +vn 0.662500024 0.511500001 0.547299981 +vn 0.51700002 0.800000012 0.304500014 +vn 0.833700001 0.423700005 0.354099989 +vn 0.725600004 0.415399998 0.548699975 +vn 0.731400013 0.407400012 0.546899974 +vn 0.759500027 0.440200001 0.478899986 +vn 0.783699989 0.464300007 0.412499994 +vn 0.754899979 0.477800012 0.449299991 +vn 0.538900018 0.734499991 0.412299991 +vn 0.749300003 0.493999988 0.441000015 +vn 0.658500016 0.619799972 0.426899999 +vn 0.716400027 0.420100003 0.557099998 +vn 0.751800001 0.414099991 0.513100028 +vn 0.165299997 0.882000029 0.441300005 +vn 0.182999998 0.799600005 0.572000027 +vn 0.394899994 0.807500005 0.438199997 +vn 0.207900003 0.875199974 0.43689999 +vn 0.225500003 0.866400003 0.4454 +vn 0.163100004 0.878499985 0.449099988 +vn 0.155399993 0.873300016 0.461600006 +vn 0.768700004 0.582400024 0.264299989 +vn 0.43810001 0.817799985 0.373299986 +vn 0.617600024 0.722800016 0.310099989 +vn 0.335299999 0.759599984 0.557299972 +vn 0.257999986 0.899399996 0.352899998 +vn 0.297800004 0.768999994 0.565599978 +vn 0.971700013 -0.0200999994 0.235200003 +vn 0.918799996 0.298299998 0.258700013 +vn 0.894299984 0.141200006 0.424499989 +vn 0.827000022 0.411500007 0.382999986 +vn 0.848100007 -0.412800014 0.332100004 +vn 0.730499983 -0.518899977 0.444000006 +vn 0.757000029 -0.48120001 0.442000002 +vn 0.933700025 -0.224800006 0.278800011 +vn 0.65990001 -0.672399998 0.335200012 +vn 0.648599982 -0.623399973 0.436699986 +vn 0.606000006 -0.565500021 0.559400022 +vn 0.694100022 -0.605300009 0.389600009 +vn 0.709599972 -0.603200018 0.364199996 +vn 0.800700009 -0.449299991 0.396299988 +vn 0.806800008 -0.288800001 0.515399992 +vn 0.650399983 -0.604399979 0.460000008 +vn 0.648500025 -0.63319999 0.422500014 +vn 0.891799986 0.0790999979 0.4454 +vn 0.861100018 0.054299999 0.505500019 +vn 0.815800011 -0.0214000009 0.578000009 +vn 0.904399991 -0.147 0.400700003 +vn 0.917999983 0.0275999997 0.395599991 +vn 0.90200001 -0.00590000022 0.431699991 +vn 0.81190002 0.0272000004 0.583199978 +vn 0.845499992 0.0592 0.530600011 +vn 0.879599988 -0.400900006 0.256000012 +vn 0.904399991 -0.218700007 0.366299987 +vn 0.771200001 -0.578599989 0.265399992 +vn 0.875999987 -0.119900003 0.467200011 +vn 0.895299971 -0.0643000007 0.440899998 +vn 0.263300002 -0.927299976 0.266099989 +vn 0.410100013 -0.846300006 0.34009999 +vn 0.549199998 -0.790300012 0.271499991 +vn 0.653999984 -0.692099988 0.305500001 +vn -0.363200009 -0.88499999 0.291399986 +vn -0.167199999 -0.932900012 0.318899989 +vn -0.240400001 -0.855099976 0.459399998 +vn -0.273400009 -0.835399985 0.476799995 +vn 0.0416000001 -0.965799987 0.256099999 +vn -0.449200004 -0.846099973 0.287 +vn -0.405200005 -0.824800014 0.394400001 +vn -0.347600013 -0.765200019 0.541899979 +vn -0.399199992 -0.833400011 0.382299989 +vn -0.366600007 -0.835900009 0.408399999 +vn -0.382699996 -0.855799973 0.348100007 +vn 9.99999975e-05 -0.932900012 0.360100001 +vn -0.165099993 -0.909200013 0.382200003 +vn -0.355399996 -0.803600013 0.477400005 +vn -0.404399991 -0.833299994 0.377000004 +vn 0.34830001 -0.798799992 0.490500003 +vn 0.39320001 -0.822000027 0.412 +vn 0.29429999 -0.829400003 0.474900007 +vn 0.149499997 -0.879199982 0.452300012 +vn 0.262899995 -0.761200011 0.592800021 +vn 0.239399999 -0.761099994 0.602900028 +vn 0.277099997 -0.742399991 0.610000014 +vn 0.2676 -0.70569998 0.656099975 +vn 0.201499999 -0.609300017 0.766900003 +vn -0.0837000012 -0.871699989 0.482800007 +vn 0.0786999986 -0.83829999 0.539499998 +vn -0.258399993 -0.729700029 0.633000016 +vn 0.118900001 -0.744099975 0.657400012 +vn 0.100500003 -0.673900008 0.731899977 +vn -0.488999993 -0.711499989 0.504700005 +vn -0.609000027 -0.522199988 0.597000003 +vn -0.555100024 -0.61559999 0.559499979 +vn -0.380800009 -0.754800022 0.534099996 +vn -0.797999978 -0.291700006 0.5273 +vn -0.697300017 -0.184100002 0.692700028 +vn -0.731899977 -0.215200007 0.646600008 +vn -0.773599982 -0.420700014 0.473800004 +vn -0.756500006 0.0153000001 0.653800011 +vn -0.6972 -0.0412999988 0.715699971 +vn -0.757700026 -0.0522999987 0.650399983 +vn -0.804899991 -0.0693000033 0.589299977 +vn -0.784500003 -0.104199998 0.611400008 +vn -0.678200006 0.205300003 0.705600023 +vn -0.697000027 0.130799994 0.7051 +vn -0.556900024 0.188199997 0.809000015 +vn -0.674799979 0.0428999998 0.736800015 +vn -0.726800025 0.0397000015 0.685699999 +vn -0.490399987 0.363700002 0.791999996 +vn -0.437099993 0.408800006 0.801100016 +vn -0.365200013 0.328799993 0.870899975 +vn -0.587300003 0.35800001 0.725899994 +vn -0.299299985 0.513999999 0.803799987 +vn -0.275900006 0.498899996 0.82160002 +vn -0.340499997 0.532000005 0.775300026 +vn -0.368099988 0.504599988 0.781000018 +vn -0.373299986 0.45449999 0.808799982 +vn -0.466199994 0.391600013 0.793299973 +vn -0.401199996 0.140499994 0.905200005 +vn -0.297600001 0.440400004 0.847100019 +vn -0.265599996 0.485000014 0.833199978 +vn -0.285100013 0.533100009 0.796599984 +vn -0.626600027 -0.126200005 0.768999994 +vn -0.561600029 -0.0302000009 0.826900005 +vn -0.43689999 -0.147300005 0.887399971 +vn -0.430900007 0.0511999987 0.901000023 +vn -0.274500012 -0.511200011 0.814400017 +vn -0.391400009 -0.418799996 0.819400012 +vn -0.361099988 -0.262800008 0.894699991 +vn 0.0922000036 -0.629400015 0.771600008 +vn 0.0549000017 -0.554400027 0.83039999 +vn -0.0890000015 -0.642499983 0.761099994 +vn 0.00800000038 -0.660300016 0.750899971 +vn -0.0200999994 -0.640200019 0.76789999 +vn 0.0449000001 -0.662 0.748199999 +vn 0.0671000034 -0.627799988 0.7755 +vn 0.0568000004 -0.467400014 0.882200003 +vn -0.169100001 -0.626900017 0.760500014 +vn -0.252000004 -0.568599999 0.782999992 +vn -0.318899989 -0.479600012 0.817499995 +vn -0.112800002 -0.566500008 0.816299975 +vn -0.0912000015 -0.520299971 0.849099994 +vn -0.513999999 -0.527199984 0.676699996 +vn -0.536499977 -0.513400018 0.669799984 +vn -0.462700009 -0.551100016 0.694400012 +vn -0.397199988 -0.55339998 0.73210001 +vn -0.665700018 -0.459800005 0.587800026 +vn -0.671899974 -0.413700014 0.614300013 +vn -0.640799999 -0.491899997 0.589399993 +vn -0.588699996 -0.473399997 0.655300021 +vn -0.591099977 -0.536300004 0.602500021 +vn -0.515799999 -0.372999996 0.771200001 +vn -0.855599999 -0.143199995 0.497399986 +vn -0.883899987 -0.00289999996 0.467599988 +vn -0.775399983 -0.245700002 0.581700027 +vn -0.745400012 0.0221999995 0.666299999 +vn -0.69599998 -0.293799996 0.655200005 +vn -0.644900024 -0.293500006 0.705600023 +vn -0.770200014 0.355399996 0.529600024 +vn -0.806699991 0.245700002 0.537500024 +vn -0.651899993 0.405099988 0.641099989 +vn -0.816600025 0.130600005 0.56220001 +vn -0.304500014 0.755299985 0.58039999 +vn -0.444799989 0.646799982 0.619499981 +vn -0.355300009 0.623000026 0.69690001 +vn -0.324699998 0.547800004 0.771000028 +vn -0.594399989 0.608900011 0.525300026 +vn -0.198100001 0.685400009 0.700699985 +vn -0.217600003 0.729700029 0.648199975 +vn -0.188700005 0.563600004 0.804199994 +vn -0.246600002 0.728799999 0.638800025 +vn -0.26910001 0.70630002 0.654799998 +vn -0.352299988 0.675599992 0.647599995 +vn -0.374900013 0.489399999 0.787400007 +vn -0.229200006 0.602400005 0.764599979 +vn -0.187399998 0.60589999 0.773100019 +vn -0.183899999 0.702400029 0.687600017 +vn -0.64410001 0.38440001 0.66140002 +vn -0.61680001 0.431800008 0.658100009 +vn -0.55250001 0.355800003 0.753799975 +vn -0.5528 0.535300016 0.638599992 +vn -0.645699978 0.448799998 0.617799997 +vn -0.646200001 0.465600014 0.604700029 +vn -0.658100009 0.423999995 0.622099996 +vn -0.568499982 0.373699993 0.73299998 +vn -0.507499993 0.340000004 0.791700006 +vn -0.424699992 0.738200009 0.524100006 +vn -0.273400009 0.824800014 0.495000005 +vn -0.512700021 0.605799973 0.608399987 +vn -0.198400006 0.698300004 0.68779999 +vn -0.493400007 0.503899992 0.708999991 +vn -0.460900009 0.463099986 0.757000029 +vn -0.00179999997 0.829400003 0.558600008 +vn 0.120099999 0.823499978 0.554499984 +vn 0.198300004 0.708199978 0.677600026 +vn -0.112199999 0.782000005 0.613099992 +vn 0.508300006 0.605700016 0.612200022 +vn 0.488299996 0.503899992 0.712499976 +vn 0.456200004 0.46329999 0.759800017 +vn 0.422300011 0.736599982 0.528299987 +vn 0.583999991 0.367300004 0.72390002 +vn 0.56279999 0.37349999 0.737399995 +vn 0.603100002 0.412099987 0.682900012 +vn 0.640699983 0.449000001 0.622799993 +vn 0.569700003 0.454100013 0.685000002 +vn 0.55250001 0.531599998 0.64200002 +vn 0.463499993 0.62440002 0.628700018 +vn 0.581900001 0.418199986 0.69749999 +vn 0.363900006 0.481700003 0.797200024 +vn 0.547299981 0.356000006 0.757399976 +vn 0.602800012 0.370000005 0.706900001 +vn 0.180000007 0.682200015 0.708700001 +vn 0.232700005 0.733500004 0.638599992 +vn 0.181299999 0.583199978 0.791899979 +vn 0.352600008 0.676500022 0.646600008 +vn 0.241500005 0.729200006 0.640299976 +vn 0.263500005 0.709299982 0.653800011 +vn 0.211400002 0.735700011 0.64349997 +vn 0.189799994 0.731299996 0.655200005 +vn 0.179199994 0.656700015 0.732599974 +vn 0.590900004 0.609000027 0.529100001 +vn 0.693199992 0.466300011 0.549499989 +vn 0.439200014 0.647700012 0.622600019 +vn 0.318500012 0.55369997 0.769400001 +vn 0.880299985 0.00410000002 0.474299997 +vn 0.806599975 0.252600014 0.534399986 +vn 0.742600024 0.0288999993 0.669099987 +vn 0.72329998 0.333700001 0.604600012 +vn 0.751699984 -0.292299986 0.591199994 +vn 0.67900002 -0.351799995 0.644400001 +vn 0.650200009 -0.366100013 0.665799975 +vn 0.84859997 -0.164100006 0.502900004 +vn 0.571699977 -0.479799986 0.665499985 +vn 0.616999984 -0.503899992 0.604499996 +vn 0.493099988 -0.39320001 0.776000023 +vn 0.638800025 -0.479499996 0.601599991 +vn 0.647800028 -0.453500003 0.611999989 +vn 0.691399992 -0.379400015 0.614899993 +vn 0.616400003 -0.474900007 0.628000021 +vn 0.599300027 -0.226799995 0.767700016 +vn 0.56099999 -0.481099993 0.673600018 +vn 0.568899989 -0.507099986 0.647499979 +vn 0.724399984 0.00760000013 0.689300001 +vn 0.773299992 0.0220999997 0.633700013 +vn 0.764199972 -0.153300002 0.62650001 +vn 0.795799971 -0.0476000011 0.603699982 +vn 0.791000009 -0.0142000001 0.611599982 +vn 0.75849998 -0.0891999975 0.645500004 +vn 0.625999987 -0.0310999993 0.779200017 +vn 0.774200022 -0.41170001 0.480800003 +vn 0.661199987 -0.548099995 0.512300014 +vn 0.767799973 -0.262899995 0.584299982 +vn 0.703000009 -0.191599995 0.684899986 +vn 0.735899985 -0.145899996 0.661199987 +vn 0.485900015 -0.709599972 0.510200024 +vn 0.259000003 -0.755900025 0.601300001 +vn 0.370200008 -0.738799989 0.563199997 +vn 0.564800024 -0.62650001 0.537100017 +vn -0.0658999979 -0.813899994 0.577300012 +vn -0.157199994 -0.733200014 0.661599994 +vn -0.124499999 -0.743900001 0.656599998 +vn 0.0844999999 -0.871399999 0.483200014 +vn -0.275099993 -0.708999991 0.649399996 +vn -0.281199992 -0.742299974 0.608299971 +vn -0.207100004 -0.609700024 0.765100002 +vn -0.266600013 -0.780200005 0.565800011 +vn -0.250800014 -0.746800005 0.61589998 +vn -0.131600007 -0.790099978 0.59859997 +vn 0.00400000019 -0.791400015 0.611199975 +vn -0.242799997 -0.74940002 0.61589998 +vn -0.266900003 -0.69690001 0.665700018 +vn -0.295399994 -0.717899978 0.630299985 +vn 0.28670001 -0.715200007 0.637399971 +vn 0.228499994 -0.647300005 0.727199972 +vn 0.190799996 -0.664200008 0.722800016 +vn 0.0961000025 -0.704699993 0.703000009 +vn 0.0922000036 -0.58890003 0.802900016 +vn 0.0803999975 -0.600000024 0.796000004 +vn 0.111900002 -0.565800011 0.816900015 +vn 0.119099997 -0.532400012 0.838100016 +vn 0.0758000016 -0.4472 0.891200006 +vn -0.137199998 -0.673799992 0.726100028 +vn -0.0436999984 -0.630299985 0.775099993 +vn -0.234099999 -0.549199998 0.802200019 +vn 0.00260000001 -0.601899981 0.798600018 +vn -0.0293000005 -0.513300002 0.85769999 +vn -0.432700008 -0.523599982 0.733900011 +vn -0.39199999 -0.578800023 0.71509999 +vn -0.422100008 -0.430900007 0.797500014 +vn -0.324600011 -0.600600004 0.730700016 +vn -0.55250001 -0.260399997 0.791800022 +vn -0.557600021 -0.31220001 0.769200027 +vn -0.467299998 -0.245800003 0.849300027 +vn -0.564599991 -0.395700008 0.724300027 +vn -0.520399988 -0.120899998 0.845300019 +vn -0.536199987 -0.0956000015 0.83859998 +vn -0.569400012 -0.136500001 0.810699999 +vn -0.577899992 -0.166600004 0.798900008 +vn -0.571200013 -0.187199995 0.799199998 +vn -0.493099988 0.0467999987 0.868700027 +vn -0.501600027 -0.0055999998 0.865100026 +vn -0.375 0.0118000004 0.926999986 +vn -0.497500002 -0.0791999996 0.863799989 +vn -0.500800014 -0.0522000007 0.864000022 +vn -0.332899988 0.196999997 0.922100008 +vn -0.36680001 0.158800006 0.916700006 +vn -0.254000008 0.103100002 0.961700022 +vn -0.425099999 0.142299995 0.893899977 +vn -0.179700002 0.257800013 0.949299991 +vn -0.158800006 0.251100004 0.95480001 +vn -0.227500007 0.274300009 0.934400022 +vn -0.25819999 0.248300001 0.933600008 +vn -0.264499992 0.208900005 0.941500008 +vn -0.247199997 0.1954 0.949100018 +vn -0.188899994 0.0540000014 0.980499983 +vn -0.150199994 0.219500005 0.963999987 +vn -0.165299997 0.299499989 0.939700007 +vn -0.290100008 -0.0546000004 0.95539999 +vn -0.176799998 -0.141599998 0.973999977 +vn -0.206300005 -0.00109999999 0.978500009 +vn -0.280800015 -0.330500007 0.90109998 +vn -0.147799999 -0.287499994 0.94630003 +vn 0.0311999992 -0.314200014 0.948800027 +vn 0.0555000007 -0.376199991 0.924899995 +vn -0.0438000001 -0.356599987 0.933200002 +vn -0.0520000011 -0.395599991 0.916899979 +vn -0.0759000033 -0.38440001 0.920099974 +vn -0.0143999998 -0.381900012 0.924099982 +vn 0.0163000003 -0.357300013 0.933799982 +vn 0.0167999994 -0.248199999 0.968599975 +vn -0.190599993 -0.387400001 0.90200001 +vn -0.245900005 -0.33919999 0.907999992 +vn -0.284200013 -0.268900007 0.920300007 +vn -0.147 -0.364600003 0.919499993 +vn -0.1338 -0.27759999 0.951300025 +vn -0.424499989 -0.306699991 0.851899981 +vn -0.442699999 -0.299800009 0.845000029 +vn -0.392399997 -0.335799992 0.856299996 +vn -0.349000007 -0.33919999 0.873600006 +vn -0.543600023 -0.254000008 0.800000012 +vn -0.543099999 -0.218999997 0.810599983 +vn -0.525699973 -0.279799998 0.803300023 +vn -0.480100006 -0.264699996 0.836300015 +vn -0.4208 -0.201100007 0.884599984 +vn -0.66869998 -0.0579999983 0.74119997 +vn -0.608399987 -0.116899997 0.785000026 +vn -0.572300017 0.0461999997 0.818700016 +vn -0.577899992 -0.159899995 0.800300002 +vn -0.502499998 -0.1259 0.855400026 +vn -0.609899998 0.244499996 0.753799975 +vn -0.645600021 0.185699999 0.740700006 +vn -0.50940001 0.254400015 0.822099984 +vn -0.647700012 0.116800003 0.752900004 +vn -0.354699999 0.432900012 0.828700006 +vn -0.409200013 0.423200011 0.808399975 +vn -0.323599994 0.342400014 0.882099986 +vn -0.507799983 0.409799993 0.757700026 +vn -0.208199993 0.448900014 0.869000018 +vn -0.237800002 0.471500009 0.84920001 +vn -0.189500004 0.353599995 0.916000009 +vn -0.271400005 0.478100002 0.835300028 +vn -0.289700001 0.476500005 0.8301 +vn -0.264999986 0.445100009 0.855400026 +vn -0.254200011 0.317799985 0.913500011 +vn -0.188299999 0.392699987 0.900200009 +vn -0.180999994 0.460599989 0.869000018 +vn -0.426800013 0.301999986 0.852400005 +vn -0.374900013 0.296200007 0.878400028 +vn -0.369599998 0.371199995 0.851800025 +vn -0.405600011 0.389699996 0.826799989 +vn -0.431499988 0.415499985 0.800800025 +vn -0.407999992 0.355500013 0.840900004 +vn -0.384499997 0.315899998 0.867399991 +vn -0.311100006 0.270099998 0.911199987 +vn -0.26789999 0.591099977 0.760800004 +vn -0.31189999 0.502099991 0.806599975 +vn -0.114200003 0.537800014 0.835300028 +vn -0.297399998 0.430400014 0.852299988 +vn -0.260899991 0.394199997 0.881200016 +vn 0.0724000037 0.655600011 0.751699984 +vn -0.0046000001 0.65109998 0.759000003 +vn 0.108000003 0.537599981 0.836300015 +vn -0.069600001 0.628799975 0.774399996 +vn 0.305999994 0.501999974 0.808899999 +vn 0.291299999 0.430400014 0.854300022 +vn 0.255100012 0.394499987 0.882799983 +vn 0.262600005 0.591400027 0.762499988 +vn 0.378800005 0.315100014 0.870199978 +vn 0.381599993 0.299800009 0.874300003 +vn 0.393000007 0.352299988 0.849399984 +vn 0.399300009 0.389400005 0.829999983 +vn 0.357499987 0.395300001 0.846099973 +vn 0.363700002 0.370900005 0.854499996 +vn 0.364100009 0.293599993 0.883899987 +vn 0.247899994 0.317000002 0.915400028 +vn 0.414200008 0.298900008 0.859700024 +vn 0.201399997 0.4903 0.84799999 +vn 0.172800004 0.425799996 0.888199985 +vn 0.205200002 0.305599988 0.929799974 +vn 0.264099985 0.438600004 0.859000027 +vn 0.265399992 0.478500009 0.837000012 +vn 0.283699989 0.492000014 0.823000014 +vn 0.2315 0.472000003 0.850700021 +vn 0.202299997 0.481099993 0.852999985 +vn 0.183799997 0.397700012 0.898899972 +vn 0.503700018 0.41080001 0.75999999 +vn 0.548099995 0.300500005 0.780600011 +vn 0.403800011 0.422300011 0.811500013 +vn 0.349000007 0.48120001 0.804199994 +vn 0.317400008 0.342799991 0.884199977 +vn 0.678600013 0.121600002 0.724399984 +vn 0.638100028 0.189899996 0.746100008 +vn 0.567099988 0.0487999991 0.8222 +vn 0.597800016 0.244499996 0.763499975 +vn 0.557600021 -0.193100005 0.807399988 +vn 0.515900016 -0.182300001 0.837000012 +vn 0.592499971 -0.145799994 0.792299986 +vn 0.663699985 -0.0724000037 0.744499981 +vn 0.443199992 -0.293099999 0.847100019 +vn 0.48120001 -0.291599989 0.826699972 +vn 0.372599989 -0.230199993 0.898999989 +vn 0.513300002 -0.275299996 0.812900007 +vn 0.528100014 -0.262899995 0.807399988 +vn 0.484699994 -0.252499998 0.837400019 +vn 0.457300007 -0.332300007 0.824899971 +vn 0.402700007 -0.164000005 0.9005 +vn 0.360300004 -0.180800006 0.915099978 +vn 0.435499996 -0.332199991 0.836600006 +vn 0.60860002 -0.053199999 0.791700006 +vn 0.485799998 -0.0631999969 0.871800005 +vn 0.431899995 -0.0872000009 0.897700012 +vn 0.528100014 -0.139799997 0.837599993 +vn 0.566799998 -0.1426 0.811399996 +vn 0.576699972 -0.160400003 0.801100016 +vn 0.549399972 -0.109999999 0.828299999 +vn 0.553600013 -0.0758000016 0.829299986 +vn 0.421400011 -0.0954999998 0.901799977 +vn 0.567099988 -0.389400005 0.725799978 +vn 0.471700013 -0.462799996 0.750500023 +vn 0.552999973 -0.293799996 0.779699981 +vn 0.500800014 -0.243000001 0.830799997 +vn 0.510399997 -0.2192 0.831499994 +vn 0.319000006 -0.597100019 0.736000001 +vn 0.378199995 -0.567700028 0.73119998 +vn 0.230299994 -0.554899991 0.799399972 +vn 0.434500009 -0.52640003 0.730799973 +vn -0.00949999969 -0.601999998 0.798500001 +vn -0.00960000046 -0.560899973 0.827799976 +vn 0.0414000005 -0.626100004 0.778599977 +vn 0.132300004 -0.673500001 0.727199972 +vn -0.124799997 -0.532500029 0.837199986 +vn -0.116999999 -0.565800011 0.816200018 +vn -0.0821999982 -0.447699994 0.890399992 +vn -0.0947000012 -0.584999979 0.805499971 +vn -0.155399993 -0.677699983 0.718800008 +vn -0.0414999984 -0.549199998 0.834599972 +vn -0.0698999986 -0.558399975 0.826699972 +vn 0.00359999994 -0.574400008 0.818599999 +vn -0.154400006 -0.560400009 0.81370002 +vn -0.0388999991 -0.425900012 0.903900027 +vn -0.161300004 -0.539499998 0.826399982 +vn 0.153799996 -0.539499998 0.827799976 +vn 0.100699998 -0.478700012 0.872200012 +vn 0.0516999997 -0.505200028 0.861400008 +vn -0.00150000001 -0.470099986 0.882600009 +vn -0.0159000009 -0.48120001 0.87650001 +vn 0.0225000009 -0.448700011 0.893400013 +vn 0.0438000001 -0.429800004 0.901899993 +vn 0.0320999995 -0.386599988 0.921700001 +vn -0.160300002 -0.521399975 0.838100016 +vn -0.100100003 -0.50940001 0.854700029 +vn -0.221300006 -0.47330001 0.852599978 +vn -0.0617999993 -0.498800009 0.864499986 +vn -0.0729999989 -0.452899992 0.888599992 +vn -0.348500013 -0.445899993 0.824400008 +vn -0.323799998 -0.485399991 0.812099993 +vn -0.34830001 -0.389999986 0.852400005 +vn -0.275999993 -0.493299991 0.824899971 +vn -0.4278 -0.274500012 0.861199975 +vn -0.422300011 -0.311300009 0.851300001 +vn -0.382800013 -0.262800008 0.885699987 +vn -0.413700014 -0.360300004 0.836099982 +vn -0.406300008 -0.162499994 0.899200022 +vn -0.406699985 -0.147799999 0.901499987 +vn -0.4384 -0.181899995 0.880200028 +vn -0.429899991 -0.225999996 0.874100029 +vn -0.428499997 -0.212400004 0.878199995 +vn -0.365500003 -0.0476000011 0.9296 +vn -0.390300006 -0.070600003 0.917999983 +vn -0.304699987 -0.0500999987 0.951099992 +vn -0.387400001 -0.108499996 0.915499985 +vn -0.36559999 -0.135499999 0.920899987 +vn -0.255600005 0.063000001 0.964699984 +vn -0.292600006 0.0494999997 0.954900026 +vn -0.212899998 0.0243999995 0.976800025 +vn -0.315600008 0.0146000003 0.948800027 +vn -0.116099998 0.128099993 0.984899998 +vn -0.105099998 0.141000003 0.984399974 +vn -0.160600007 0.135199994 0.977699995 +vn -0.186399996 0.100599997 0.977299988 +vn -0.194600001 0.0667999983 0.978600025 +vn -0.126200005 0.0883999988 0.988099992 +vn -0.114799999 0.0295000002 0.992999971 +vn -0.0807000026 0.1175 0.989799976 +vn -0.0847000033 0.146799996 0.985499978 +vn -0.0896999985 0.153899997 0.984000027 +vn -0.148000002 -0.0573999994 0.987299979 +vn -0.115900002 -0.0640999973 0.99119997 +vn -0.124200001 -0.0298999995 0.99180001 +vn -0.132499993 0.0092000002 0.991100013 +vn -0.0839999989 -0.152700007 0.984700024 +vn -0.0776000023 -0.119199999 0.989799976 +vn -0.1241 -0.111699998 0.986000001 +vn 0.0337000005 -0.219400004 0.975000024 +vn 0.0333000012 -0.221100003 0.974699974 +vn 0.0260000005 -0.187700003 0.981899977 +vn -0.0192000009 -0.201199993 0.979399979 +vn 0.0487999991 0.0254999995 0.99849999 +vn -0.0803000033 -0.237000003 0.968200028 +vn -0.1017 -0.238499999 0.965799987 +vn -0.0414999984 -0.226300001 0.973200023 +vn -0.00760000013 -0.216700003 0.976199985 +vn 0.00289999996 -0.170399994 0.985400021 +vn -0.235499993 -0.225999996 0.945200026 +vn -0.196500003 -0.255899996 0.946500003 +vn -0.266799986 -0.183699995 0.946099997 +vn -0.159500003 -0.237599999 0.958199978 +vn -0.146799996 -0.190899998 0.970600009 +vn -0.367399991 -0.187399998 0.911000013 +vn -0.380899996 -0.182999998 0.906300008 +vn -0.345999986 -0.217500001 0.912699997 +vn -0.310099989 -0.213599995 0.926400006 +vn -0.452800006 -0.130400002 0.882000029 +vn -0.445100009 -0.159799993 0.881099999 +vn -0.449200004 -0.102300003 0.887600005 +vn -0.41080001 -0.153999999 0.898599982 +vn -0.377099991 -0.128199995 0.917299986 +vn -0.527499974 -0.00480000023 0.849600017 +vn -0.505500019 -0.0469999984 0.861599982 +vn -0.494800001 0.054299999 0.867299974 +vn -0.488299996 -0.0790999979 0.869099975 +vn -0.44690001 -0.065200001 0.892199993 +vn -0.503199995 0.180600002 0.845099986 +vn -0.53490001 0.146599993 0.832099974 +vn -0.448900014 0.195899993 0.871800005 +vn -0.529600024 0.100000001 0.842299998 +vn -0.340499997 0.310000002 0.887700021 +vn -0.377900004 0.295700014 0.877399981 +vn -0.319599986 0.266000003 0.909399986 +vn -0.431800008 0.270599991 0.860400021 +vn -0.205599993 0.319000006 0.925199986 +vn -0.236399993 0.321099997 0.917100012 +vn -0.187600002 0.277200013 0.942300022 +vn -0.290499985 0.323399991 0.900600016 +vn -0.271100014 0.323500007 0.906599998 +vn -0.207300007 0.305900007 0.929199994 +vn -0.211099997 0.256999999 0.943099976 +vn -0.171399996 0.289099991 0.941799998 +vn -0.169 0.316700011 0.933300018 +vn -0.170300007 0.307900012 0.936100006 +vn -0.28639999 0.240199998 0.92750001 +vn -0.29339999 0.244499996 0.924199998 +vn -0.25909999 0.232999995 0.937300026 +vn -0.25819999 0.270000011 0.927600026 +vn -0.259200007 0.338200003 0.904699981 +vn -0.262400001 0.361099988 0.894800007 +vn -0.266600013 0.304100007 0.914600015 +vn -0.273400009 0.273900002 0.922100008 +vn -0.239299998 0.242799997 0.940100014 +vn -0.159299999 0.472900003 0.866599977 +vn -0.197099999 0.430799991 0.880599976 +vn -0.0800999999 0.465600014 0.881399989 +vn -0.193599999 0.382699996 0.903400004 +vn -0.188999996 0.3662 0.91109997 +vn 0.0412000008 0.520399988 0.852900028 +vn -0.00139999995 0.533599973 0.845799983 +vn 0.0737999976 0.465700001 0.881900012 +vn -0.0469000004 0.512899995 0.857200027 +vn 0.190899998 0.430900007 0.882000029 +vn 0.187299997 0.382699996 0.904699981 +vn 0.182999998 0.366400003 0.912299991 +vn 0.153300002 0.473100007 0.867500007 +vn 0.268299997 0.273200005 0.923799992 +vn 0.277500004 0.261000007 0.924600005 +vn 0.25999999 0.304100007 0.916499972 +vn 0.238999993 0.352999985 0.904600024 +vn 0.252700001 0.338200003 0.906499982 +vn 0.251599997 0.270099998 0.929400027 +vn 0.205400005 0.256199986 0.944599986 +vn 0.252299994 0.232999995 0.939199984 +vn 0.279700011 0.240199998 0.9296 +vn 0.287099987 0.2447 0.926100016 +vn 0.162200004 0.303499997 0.938899994 +vn 0.137700006 0.180399999 0.97390002 +vn 0.162900001 0.298400015 0.940400004 +vn 0.286300004 0.346899986 0.893100023 +vn 0.264600009 0.323700011 0.908399999 +vn 0.230199993 0.321099997 0.91869998 +vn 0.199200004 0.327600002 0.923600018 +vn 0.181799993 0.293799996 0.938399971 +vn 0.425799996 0.270999998 0.863300025 +vn 0.371600002 0.295899987 0.879999995 +vn 0.451200008 0.2051 0.868499994 +vn 0.334699988 0.318100005 0.887000024 +vn 0.313600004 0.266099989 0.911499977 +vn 0.531000018 0.104000002 0.841000021 +vn 0.530499995 0.147499993 0.834699988 +vn 0.489600003 0.0560000017 0.870199978 +vn 0.497299999 0.182500005 0.848200023 +vn 0.470800012 -0.101000004 0.876399994 +vn 0.492399991 -0.0666000023 0.867799997 +vn 0.437400013 -0.0903000012 0.894699991 +vn 0.520500004 -0.0133999996 0.853699982 +vn 0.365500003 -0.1928 0.910600007 +vn 0.392199993 -0.1752 0.902999997 +vn 0.326499999 -0.171100006 0.9296 +vn 0.438499987 -0.143299997 0.887300014 +vn 0.422399998 -0.155399993 0.893000007 +vn 0.333200008 -0.140900001 0.932200015 +vn 0.234599993 -0.119599998 0.964699984 +vn 0.328099996 -0.205599993 0.921999991 +vn 0.344199985 -0.222299993 0.912199974 +vn 0.378899992 -0.0850000009 0.921500027 +vn 0.360100001 -0.0931999981 0.928300023 +vn 0.361499995 -0.0917999968 0.9278 +vn 0.379299998 -0.124499999 0.916899979 +vn 0.445100009 -0.2007 0.872699976 +vn 0.417499989 -0.185599998 0.889500022 +vn 0.404199988 -0.152199998 0.901899993 +vn 0.400599986 -0.119800001 0.908399999 +vn 0.346899986 -0.1162 0.930700004 +vn 0.411900014 -0.355100006 0.83920002 +vn 0.41870001 -0.300099999 0.85710001 +vn 0.353199989 -0.397000015 0.847100019 +vn 0.388799995 -0.261500001 0.883400023 +vn 0.389200002 -0.247600004 0.887199998 +vn 0.272300005 -0.493200004 0.826200008 +vn 0.309500009 -0.473699987 0.824500024 +vn 0.216399997 -0.4745 0.853299975 +vn 0.341800004 -0.44749999 0.826399982 +vn 0.056499999 -0.499300003 0.864600003 +vn 0.0948000029 -0.510100007 0.854900002 +vn 0.0593999997 -0.465000004 0.883300006 +vn 0.155200005 -0.521899998 0.838800013 +vn -0.0498999991 -0.429899991 0.901499987 +vn -0.0283000004 -0.448799998 0.89319998 +vn -0.0386000015 -0.386999995 0.921299994 +vn -0.00430000015 -0.469700009 0.882799983 +vn 0.0291000009 -0.456900001 0.888999999 +vn 0.00120000006 -0.432599992 0.901600003 +vn -0.0151000004 -0.287099987 0.957799971 +vn -0.0644999966 -0.391299993 0.917999983 +vn -0.0803000033 -0.419400007 0.904299974 +vn 0.0684999973 -0.407200009 0.91079998 +vn 0.0746000037 -0.41960001 0.904600024 +vn 0.0491999984 -0.387199998 0.920700014 +vn 0.0307 -0.412600011 0.910399973 +vn -0.137899995 -0.29550001 0.945299983 +vn -0.126900002 -0.334899992 0.933700025 +vn -0.156900004 -0.309399992 0.937900007 +vn -0.0988000035 -0.32280001 0.941299975 +vn -0.126499996 -0.256199986 0.958299994 +vn -0.0872000009 -0.27759999 0.956700027 +vn -0.112099998 -0.292899996 0.949500024 +vn -0.0886000022 -0.30399999 0.948499978 +vn -0.107000001 -0.278899997 0.954400003 +vn -0.0443000011 -0.326499999 0.944199979 +vn -0.0560000017 -0.256599993 0.964900017 +vn -0.0807000026 -0.249799997 0.964900017 +vn -0.0483000018 -0.241500005 0.969200015 +vn -0.0710999966 -0.263000011 0.962199986 +vn -0.0873999968 -0.268700004 0.959200025 +vn -0.0727000013 -0.237399995 0.968699992 +vn -0.0527000017 -0.225400001 0.972800016 +vn -0.0357999988 -0.229699999 0.972599983 +vn -0.0190999992 -0.219099998 0.975499988 +vn -0.0183000006 -0.226899996 0.973699987 +vn -0.00769999996 -0.224099994 0.9745 +vn -0.0286999997 -0.234999999 0.971599996 +vn -0.158500001 -0.325700015 0.932099998 +vn -0.1382 -0.30430001 0.942499995 +vn -0.161899999 -0.297100008 0.940999985 +vn -0.139200002 -0.281399995 0.949400008 +vn -0.191100001 -0.321799994 0.927299976 +vn -0.183200002 -0.320899993 0.929199994 +vn -0.185399994 -0.311399996 0.931999981 +vn -0.174099997 -0.3204 0.931100011 +vn -0.203600004 -0.322100013 0.924499989 +vn -0.200399995 -0.314099997 0.927999973 +vn -0.207599998 -0.317499995 0.925199986 +vn -0.198899999 -0.323199987 0.925199986 +vn -0.197099999 -0.326900005 0.924300015 +vn -0.209600002 -0.307200015 0.928300023 +vn -0.218600005 -0.319799989 0.921899974 +vn -0.201900005 -0.295399994 0.933799982 +vn -0.2148 -0.30219999 0.92869997 +vn -0.211400002 -0.294499993 0.931999981 +vn -0.181899995 -0.297699988 0.93720001 +vn -0.205300003 -0.226899996 0.952000022 +vn -0.199300006 -0.210800007 0.957000017 +vn -0.198599994 -0.204699993 0.958400011 +vn -0.195800006 -0.240099996 0.950800002 +vn -0.195700005 -0.221100003 0.95539999 +vn -0.207200006 -0.246000007 0.94690001 +vn -0.202700004 -0.261400014 0.943700016 +vn -0.183799997 -0.260399997 0.947799981 +vn -0.205500007 -0.279199988 0.938000023 +vn -0.213499993 -0.265100002 0.940299988 +vn -0.2139 -0.281300008 0.935500026 +vn -0.188700005 -0.2773 0.942099988 +vn -0.179800004 -0.168400005 0.969200015 +vn -0.183799997 -0.178599998 0.966600001 +vn -0.186800003 -0.188500002 0.964100003 +vn -0.174600005 -0.175500005 0.968900025 +vn -0.180700004 -0.201900005 0.962599993 +vn -0.194499999 -0.192499995 0.961799979 +vn -0.160099998 -0.147200003 0.976100028 +vn -0.170599997 -0.143199995 0.974900007 +vn -0.173099995 -0.152999997 0.972899973 +vn -0.157000005 -0.135900006 0.978200018 +vn -0.179299995 -0.154699996 0.971599996 +vn -0.158299997 -0.165999994 0.97329998 +vn -0.113600001 -0.0930000022 0.989199996 +vn -0.0746999979 -0.0852999985 0.993499994 +vn -0.0793000013 -0.116400003 0.99000001 +vn -0.119599998 -0.1096 0.986800015 +vn -0.0476999991 -0.0929000005 0.994499981 +vn -0.107000001 -0.0601000004 0.992399991 +vn -0.0263 -0.0590999983 0.997900009 +vn -0.00480000023 -0.0518000014 0.998600006 +vn 0.00549999997 -0.0454000011 0.999000013 +vn -0.0219999999 -0.0647 0.997699976 +vn -0.0401000008 -0.0627999976 0.997200012 +vn -0.060800001 -0.0577000007 0.996500015 +vn -0.0816999972 -0.0678000003 0.994400024 +vn -0.0553000011 -0.0951000005 0.993900001 +vn -0.102899998 -0.106600001 0.989000022 +vn -0.0861999989 -0.109499998 0.990199983 +vn -0.115699999 -0.124799997 0.985400021 +vn -0.0965000018 -0.118500002 0.988300025 +vn -0.129999995 -0.114799999 0.984799981 +vn -0.135100007 -0.134000003 0.981700003 +vn -0.126000002 -0.147400007 0.981000006 +vn -0.108000003 -0.147100002 0.983200014 +vn -0.147200003 -0.144500002 0.978500009 +vn -0.143199995 -0.126300007 0.981599987 +vn 0.0379000008 -0.0287999995 0.998899996 +vn 0.0447000004 -0.0287999995 0.998600006 +vn 0.0392999984 -0.0240000002 0.998899996 +vn 0.0355000012 -0.0350000001 0.99879998 +vn 0.0487999991 -0.0156999994 0.998700023 +vn 0.0610999987 -0.0364999995 0.997500002 +vn 0.0452000014 -0.0456000008 0.997900009 +vn 0.0311999992 -0.0427000001 0.998600006 +vn 0.0216000006 -0.0441000015 0.99879998 +vn 0.0573000014 -0.0595999993 0.996599972 +vn 0.0283000004 -0.0593000017 0.997799993 +vn 0.0164999999 -0.0423999988 0.999000013 +vn 0.0109000001 -0.0671000034 0.997699976 +vn 0.0540000014 -0.00039999999 0.99849999 +vn 0.0649999976 -0.0167999994 0.997699976 +vn 0.0262000002 0.0057000001 0.999599993 +vn 0.0408000015 -0.00260000001 0.999199986 +vn 0.0450999998 0.0100999996 0.998899996 +vn 0.0201999992 -9.99999975e-05 0.999800026 +vn 0.0294000003 -0.00179999997 0.999599993 +vn 0.0120999999 -0.00270000007 0.999899983 +vn 0.0310999993 0.0199999996 0.999300003 +vn 0.00190000003 -0.000199999995 1 +vn -0.00999999978 -0.0118000004 0.999899983 +vn 0.0122999996 0.00949999969 0.999899983 +vn -0.0120000001 -0.00410000002 0.999899983 +vn -0.0405000001 -0.0164999999 0.999000013 +vn -0.0621999986 -0.0239000004 0.997799993 +vn -0.0819000006 -0.00810000021 0.996599972 +vn -0.0197999999 -0.00689999992 0.999800026 +vn -0.0377999991 -0.00639999984 0.999300003 +vn -0.116999999 -0.0263999999 0.992799997 +vn -0.0952999964 -0.0304000005 0.995000005 +vn -0.123599999 -0.0220999997 0.9921 +vn -0.150000006 -0.0331999995 0.988099992 +vn -0.0913000032 0.00200000009 0.995800018 +vn -0.0606999993 -0.0233999994 0.997900009 +vn -0.0754999965 -0.0157999992 0.996999979 +vn -0.050999999 -0.00769999996 0.998700023 +vn -0.0104999999 0.0162000004 0.999800026 +vn 0.0104999999 0.0233999994 0.99970001 +vn 0.0278999992 0.0390999988 0.99879998 +vn -0.151299998 -0.00719999988 0.988499999 +vn -0.133000001 -0.00419999985 0.991100013 +vn -0.146799996 0.00680000009 0.989199996 +vn -0.155900002 -0.0220999997 0.987500012 +vn -0.135399997 -0.0291000009 0.990400016 +vn -0.167699993 -0.0500000007 0.984600008 +vn -0.121399999 -0.00769999996 0.992600024 +vn -0.1083 0.00389999989 0.994099975 +vn -0.105700001 -0.0120999999 0.994300008 +vn -0.0878999978 -0.0055999998 0.996100008 +vn -0.191400006 -0.0461999997 0.980400026 +vn -0.181199998 -0.0280000009 0.983099997 +vn -0.204899997 -0.00579999993 0.978799999 +vn -0.229499996 -0.0252 0.97299999 +vn -0.213799998 -0.0245999992 0.976599991 +vn -0.225799993 -0.0164000001 0.973999977 +vn -0.204899997 -0.0131999999 0.978699982 +vn -0.183599994 -0.0165999997 0.982900023 +vn -0.269400001 -0.0108000003 0.963 +vn -0.262899995 -0.00540000014 0.9648 +vn -0.274599999 -0.00359999994 0.961499989 +vn -0.282000005 0.00549999997 0.959399998 +vn -0.255400002 -0.0153000001 0.966700017 +vn -0.245499998 -0.00970000029 0.969399989 +vn -0.240600005 -0.0197999999 0.970399976 +vn -0.309100002 0.0208999999 0.950800002 +vn -0.316199988 0.0274999999 0.948300004 +vn -0.312700003 0.0500000007 0.948499978 +vn -0.298700005 0.0208000001 0.954100013 +vn -0.288300008 0.0144999996 0.957400024 +vn -0.2764 0.0127999997 0.961000025 +vn -0.307500005 0.0421000011 0.950600028 +vn -0.32249999 0.0439000018 0.945500016 +vn -0.328900009 0.0445000008 0.943300009 +vn -0.33070001 0.0590000004 0.941900015 +vn -0.316199988 0.0599999987 0.946799994 +vn -0.34009999 0.0702999979 0.93779999 +vn -0.328599989 0.0693000033 0.941900015 +vn -0.338999987 0.0614 0.938799977 +vn -0.336100012 0.0516999997 0.940400004 +vn -0.339700013 0.0859000012 0.936600029 +vn -0.339899987 0.079400003 0.937099993 +vn -0.329400003 0.0872000009 0.940199971 +vn -0.338499993 0.0901999995 0.936600029 +vn -0.331800014 0.0715999976 0.940599978 +vn -0.333600014 0.074500002 0.939800024 +vn -0.333900005 0.0948000029 0.93779999 +vn -0.325700015 0.0961000025 0.940599978 +vn -0.315299988 0.0962999985 0.944100022 +vn -0.304899991 0.0948000029 0.947600007 +vn -0.293000013 0.100400001 0.950800002 +vn -0.314700007 0.0785000026 0.945900023 +vn -0.193299994 0.0759000033 0.978200018 +vn -0.182999998 0.149599999 0.971700013 +vn -0.164000005 0.1294 0.977900028 +vn -0.211799994 0.126300007 0.969099998 +vn -0.235499993 0.0980999991 0.966899991 +vn -0.216900006 0.0848999992 0.972500026 +vn -0.197899997 0.138400003 0.970399976 +vn -0.2333 0.145199999 0.961499989 +vn -0.209099993 0.113200001 0.971300006 +vn -0.251700014 0.107600003 0.961799979 +vn -0.249699995 0.175799996 0.952199996 +vn -0.265599996 0.101999998 0.958700001 +vn -0.236900002 0.0979000032 0.966600001 +vn -0.268400013 0.0934000015 0.958800018 +vn -0.252999991 0.0910999998 0.963199973 +vn -0.282400012 0.0807999969 0.955900013 +vn -0.279799998 0.1052 0.954299986 +vn -0.296499997 0.0806000009 0.951600015 +vn -0.123800002 0.125200003 0.984399974 +vn -0.1193 0.119099997 0.985700011 +vn -0.117399998 0.127000004 0.984899998 +vn -0.130600005 0.1219 0.983900011 +vn -0.135600001 0.113600001 0.984200001 +vn -0.153099999 0.115800001 0.981400013 +vn -0.144199997 0.1219 0.981999993 +vn -0.163900003 0.119000003 0.979300022 +vn -0.173999995 0.1052 0.979099989 +vn -0.107500002 0.111199997 0.987999976 +vn -0.1039 0.1329 0.985700011 +vn -0.112400003 0.126499996 0.985599995 +vn -0.0555999987 0.262600005 0.96329999 +vn -0.0621000007 0.280099988 0.958000004 +vn -0.0310999993 0.282499999 0.958800018 +vn -0.0434999987 0.245499998 0.968400002 +vn -0.0610000007 0.220599994 0.973399997 +vn -0.0785000026 0.240500003 0.967499971 +vn -0.0724000037 0.264999986 0.961499989 +vn -0.064000003 0.240700006 0.968500018 +vn -0.0562999994 0.244000003 0.968100011 +vn -0.118600003 0.230499998 0.965799987 +vn -0.0768000036 0.196600005 0.977500021 +vn -0.0900999978 0.170599997 0.98119998 +vn -0.0914999992 0.187600002 0.977999985 +vn -0.0995000005 0.170399994 0.980300009 +vn -0.0816000029 0.202099994 0.976000011 +vn -0.0713 0.2245 0.971899986 +vn -0.0582000017 0.230499998 0.971300006 +vn -0.0615999997 0.205400005 0.976700008 +vn -0.0483000018 0.217700005 0.974799991 +vn -0.0829000026 0.146400005 0.985700011 +vn -0.0957000032 0.151700005 0.983799994 +vn -0.0952000022 0.129199997 0.986999989 +vn -0.0758000016 0.124899998 0.989300013 +vn -0.0808999985 0.109399997 0.990700006 +vn -0.0997999981 0.146799996 0.984099984 +vn -0.0366000012 0.299199998 0.953499973 +vn -0.0274999999 0.307700008 0.951099992 +vn -0.0192000009 0.315100014 0.948899984 +vn -0.0147000002 0.303900003 0.952600002 +vn -0.0171000008 0.290899992 0.95660001 +vn -0.0480000004 0.287999988 0.956399977 +vn -0.0206000004 0.273499995 0.961600006 +vn -0.0305000003 0.258899987 0.965399981 +vn -0.00480000023 0.344700009 0.93870002 +vn -0.00899999961 0.325100005 0.945599973 +vn 0.00200000009 0.323700011 0.946200013 +vn -0.00449999981 0.315100014 0.949000001 +vn -0.0149999997 0.32100001 0.947000027 +vn -0.0129000004 0.323599994 0.946099997 +vn 0.0131999999 0.3046 0.952400029 +vn 0.0107000005 0.3134 0.949500024 +vn 0.0294000003 0.299600005 0.953599989 +vn 0.0326000005 0.287999988 0.957099974 +vn 0.00820000004 0.32280001 0.946399987 +vn 0.0103000002 0.327499986 0.944800019 +vn 0.0593999997 0.203299999 0.977299988 +vn 0.0744000003 0.186499998 0.979600012 +vn 0.0961000025 0.182999998 0.978399992 +vn 0.0895000026 0.166999996 0.981899977 +vn -0.0623000003 0.179299995 0.98180002 +vn 0.206599995 0.263799995 0.942200005 +vn 0.0143999998 0.173600003 0.984700024 +vn 0.125499994 0.234599993 0.963999987 +vn 0.0724999979 0.233700007 0.969600022 +vn 0.0954999998 0.26789999 0.958700001 +vn 0.0454000011 0.255400002 0.965799987 +vn 0.0859000012 0.198899999 0.976300001 +vn 0.0930000022 0.234799996 0.967599988 +vn 0.0590999983 0.249300003 0.966600001 +vn 0.0654999986 0.2359 0.969600022 +vn 0.0498000011 0.270700008 0.961399972 +vn 0.107500002 0.272500008 0.956099987 +vn 0.0601000004 0.287 0.95599997 +vn 0.0280000009 0.277999997 0.960200012 +vn 0.00659999996 0.300099999 0.95389998 +vn 0.0130000003 0.281599998 0.959399998 +vn -0.00380000006 0.279100001 0.960300028 +vn 0.105700001 0.130999997 0.985700011 +vn 0.103 0.122500002 0.987100005 +vn 0.1105 0.126599997 0.985800028 +vn 0.101999998 0.136399999 0.985400021 +vn 0.0947000012 0.140200004 0.985599995 +vn 0.0861999989 0.125699997 0.988300025 +vn 0.0874999985 0.105599999 0.990499973 +vn 0.0790999979 0.142499998 0.986599982 +vn 0.0932999998 0.158299997 0.98299998 +vn 0.0975999981 0.149800003 0.983900011 +vn 0.0733999982 0.155000001 0.985199988 +vn 0.136600003 0.119099997 0.983399987 +vn 0.1479 0.125200003 0.981000006 +vn 0.157399997 0.116099998 0.980700016 +vn 0.132699996 0.106399998 0.985400021 +vn 0.1215 0.1171 0.985700011 +vn 0.118000001 0.105899997 0.987299979 +vn 0.100699998 0.101000004 0.989799976 +vn 0.1131 0.120200001 0.986299992 +vn 0.246399999 0.160999998 0.95569998 +vn 0.211300001 0.1602 0.96420002 +vn 0.232899994 0.1127 0.965900004 +vn 0.258899987 0.1061 0.960099995 +vn 0.223700002 0.0724000037 0.972000003 +vn 0.201499999 0.115599997 0.972599983 +vn 0.185000002 0.0864999965 0.978900015 +vn 0.170599997 0.137199998 0.975700021 +vn 0.185900003 0.145799994 0.971700013 +vn 0.189500004 0.130899996 0.973100007 +vn 0.154300004 0.104400001 0.982500017 +vn 0.325500011 0.0896999985 0.941299975 +vn 0.310900003 0.0895000026 0.946200013 +vn 0.322299987 0.0775000006 0.943499982 +vn 0.319599986 0.0952999964 0.942799985 +vn 0.300199986 0.0817999989 0.950399995 +vn 0.290199995 0.0971999988 0.952000022 +vn 0.307599992 0.100400001 0.946200013 +vn 0.286199987 0.104500003 0.952400029 +vn 0.280699998 0.0790000036 0.956499994 +vn 0.271200001 0.0916000009 0.958199978 +vn 0.257200003 0.0909999982 0.962100029 +vn 0.239600003 0.0949999988 0.966199994 +vn 0.216600001 0.0987000018 0.971300006 +vn 0.231999993 0.0785000026 0.969500005 +vn 0.331800014 0.0846000016 0.939499974 +vn 0.326099992 0.0829000026 0.941699982 +vn 0.333799988 0.0791999996 0.939300001 +vn 0.329699993 0.0760999992 0.940999985 +vn 0.333099991 0.0676999986 0.940500021 +vn 0.359800011 0.0734999999 0.930100024 +vn 0.324299991 0.0584000014 0.944100022 +vn 0.327399999 0.0461000018 0.943799973 +vn 0.316500008 0.0452000014 0.94749999 +vn 0.315200001 0.0296999998 0.948599994 +vn 0.332899988 0.054299999 0.941399992 +vn 0.240700006 -0.0361000001 0.969900012 +vn 0.228799999 -0.0566000007 0.971800029 +vn 0.206699997 -0.0511999987 0.977100015 +vn 0.2271 -0.0401000008 0.97299999 +vn 0.235699996 -0.0219000001 0.971599996 +vn 0.263300002 -0.0161000006 0.964600027 +vn 0.252700001 -0.0107000005 0.967499971 +vn 0.259499997 0.0148 0.965600014 +vn 0.278800011 0.00700000022 0.960300028 +vn 0.265599996 0.0110999998 0.963999987 +vn 0.302700013 0.0211999994 0.952899992 +vn 0.288899988 0.0118000004 0.957300007 +vn 0.288199991 0.0166999996 0.957400024 +vn 0.290499985 0.0326000005 0.95630002 +vn 0.306600004 0.0364000015 0.951200008 +vn 0.306699991 0.0615999997 0.949800014 +vn 0.29339999 0.0498999991 0.954699993 +vn 0.213 -0.0304000005 0.976599991 +vn 0.217700005 -0.059799999 0.97420001 +vn 0.200100005 -0.068400003 0.977400005 +vn 0.202099994 -0.0551999994 0.977800012 +vn 0.183699995 -0.0864999965 0.979200006 +vn 0.176100001 -0.0942000002 0.979900002 +vn 0.185399994 -0.0927999988 0.978299975 +vn 0.186399996 -0.0777999982 0.979399979 +vn 0.200000003 -0.0744000003 0.976999998 +vn 0.174999997 -0.0733999982 0.98180002 +vn 0.181299999 -0.0584999993 0.981700003 +vn 0.186900005 -0.120899998 0.974900007 +vn 0.179000005 -0.118600003 0.976700008 +vn 0.190200001 -0.138400003 0.971899986 +vn 0.176100001 -0.106799997 0.978600025 +vn 0.165900007 -0.119199999 0.978900015 +vn 0.182099998 -0.0993999988 0.978200018 +vn 0.201700002 -0.198200002 0.959200025 +vn 0.181199998 -0.222599998 0.957899988 +vn 0.301200002 -0.204699993 0.931299984 +vn 0.212300003 -0.240899995 0.947000027 +vn 0.217899993 -0.214399993 0.952099979 +vn 0.210600004 -0.193299994 0.958299994 +vn 0.192699999 -0.202999994 0.959999979 +vn 0.2016 -0.185299993 0.961799979 +vn 0.237000003 -0.155499995 0.958999991 +vn 0.197799996 -0.151700005 0.968400002 +vn 0.170499995 -0.174099997 0.969900012 +vn 0.178800002 -0.136000007 0.974399984 +vn 0.175899997 -0.156200007 0.971899986 +vn 0.182699993 -0.176699996 0.967199981 +vn 0.185200006 -0.193900004 0.963400006 +vn 0.162900001 -0.170399994 0.971800029 +vn 0.207100004 -0.310099989 0.927900016 +vn 0.200200006 -0.305599988 0.930899978 +vn 0.200000003 -0.315499991 0.927600026 +vn 0.200200006 -0.309300005 0.929700017 +vn 0.2095 -0.301999986 0.930000007 +vn 0.186399996 -0.314999998 0.930599988 +vn 0.199399993 -0.279300004 0.939300001 +vn 0.201100007 -0.292699993 0.934800029 +vn 0.210600004 -0.289700001 0.933600008 +vn 0.207800001 -0.269199997 0.940400004 +vn 0.190899998 -0.259499997 0.946699977 +vn 0.206699997 -0.254099995 0.944800019 +vn 0.194600001 -0.240099996 0.950999975 +vn 0.203700006 -0.242699996 0.948499978 +vn 0.199300006 -0.333000004 0.921599984 +vn 0.195899993 -0.319999993 0.926900029 +vn 0.188800007 -0.323599994 0.927200019 +vn 0.178499997 -0.321399987 0.930000007 +vn 0.182400003 -0.330500007 0.925999999 +vn 0.168799996 -0.320199996 0.932200015 +vn 0.153699994 -0.312900007 0.937300026 +vn 0.145600006 -0.308600008 0.939999998 +vn 0.168899998 -0.312999994 0.934599996 +vn 0.0568999983 -0.264299989 0.962800026 +vn 0.0939999968 -0.233899996 0.967700005 +vn 0.0441999994 -0.260199994 0.96450001 +vn 0.0346000008 -0.248899996 0.967899978 +vn 0.115400001 -0.253800005 0.960399985 +vn 0.0487000011 -0.326900005 0.943799973 +vn 0.0763000026 -0.275299996 0.958299994 +vn 0.0573999994 -0.35710001 0.932299972 +vn 0.0289999992 -0.309899986 0.950299978 +vn 0.125100002 -0.310900003 0.942200005 +vn 0.123199999 -0.287999988 0.949699998 +vn 0.117700003 -0.279300004 0.953000009 +vn 0.0839999989 -0.288500011 0.953800023 +vn 0.114 -0.299199998 0.947399974 +vn 0.0975999981 -0.294 0.950800002 +vn 0.144199997 -0.301999986 0.942300022 +vn 0.140900001 -0.281800002 0.949100018 +vn 0.0366000012 -0.220300004 0.974799991 +vn 0.0175000001 -0.226199999 0.97390002 +vn 0.0293000005 -0.240099996 0.970300019 +vn 0.0181000009 -0.206400007 0.978299975 +vn 0.00100000005 -0.215499997 0.976499975 +vn 0.056499999 -0.232700005 0.970899999 +vn 0.0700000003 -0.246399999 0.966600001 +vn 0.00329999998 -0.226500005 0.973999977 +vn -0.00300000003 -0.225899994 0.974099994 +vn 0.00910000037 -0.229499996 0.97329998 +vn 0.0285999998 -0.232800007 0.972100019 +vn -0.00540000014 -0.197300002 0.980300009 +vn -0.0214000009 -0.206400007 0.978200018 +vn 0.00139999995 0.262600005 0.964900017 +vn -0.0119000003 0.252700001 0.967499971 +vn 0.0329000019 0.259799987 0.96509999 +vn 0.0203000009 0.257299989 0.966099977 +vn 0.0104999999 0.243200004 0.969900012 +vn -0.00300000003 0.230199993 0.973100007 +vn -0.0186999999 0.240799993 0.970399976 +vn -0.0258000009 0.218700007 0.975399971 +vn -0.0353999995 0.237900004 0.970600009 +vn 0.0384000018 0.239600003 0.970099986 +vn 0.0234999992 0.230399996 0.972800016 +vn -0.0377999991 0.203999996 0.978200018 +vn -0.0593000017 0.1919 0.979600012 +vn -0.067900002 0.176400006 0.981999993 +vn -0.0126999998 0.214499995 0.976599991 +vn 0.0119000003 0.215800002 0.976400018 +vn -0.0142999999 0.191699997 0.981400013 +vn 0.00079999998 0.201800004 0.979399979 +vn 0.0410000011 0.218700007 0.974900007 +vn 0.0260000005 0.202999994 0.978799999 +vn 0.0493000001 0.201399997 0.978299975 +vn 0.068599999 0.204600006 0.976400018 +vn -0.0331000015 0.191599995 0.98089999 +vn -0.0447999984 0.178499997 0.982900023 +vn -0.0239000004 0.171100006 0.985000014 +vn 0.0127999997 0.186499998 0.9824 +vn -0.00109999999 0.172600001 0.985000014 +vn 0.0375999995 0.1866 0.981700003 +vn 0.0160000008 0.165399998 0.986100018 +vn 0.0324999988 0.167199999 0.985400021 +vn 0.056499999 0.184200004 0.981299996 +vn 0.0527000017 0.166099995 0.984700024 +vn 0.0709000006 0.174700007 0.98210001 +vn -0.0520000011 0.162300006 0.985400021 +vn -0.0318000019 0.153099999 0.987699986 +vn -0.0513999984 0.145600006 0.987999976 +vn -0.0703999996 0.158700004 0.984799981 +vn -0.0702000037 0.140300006 0.987600029 +vn -0.0141000003 0.151700005 0.988300025 +vn 0.00600000005 0.150900006 0.988499999 +vn -0.00700000022 0.136199996 0.990700006 +vn 0.0291000009 0.145899996 0.988900006 +vn 0.0524999984 0.145500004 0.987999976 +vn -0.0555000007 0.131300002 0.989799976 +vn -0.0538000017 0.115199998 0.991900027 +vn -0.0326000005 0.131099999 0.990800023 +vn -0.0179999992 0.118600003 0.992799997 +vn -0.0320999995 0.107299998 0.993700027 +vn 0.00179999997 0.117899999 0.992999971 +vn 0.0156999994 0.130999997 0.991299987 +vn 0.0381000005 0.126300007 0.991299987 +vn 0.0174000002 0.1118 0.993600011 +vn 0.0671999976 0.1303 0.989199996 +vn 0.0500999987 0.116400003 0.991900027 +vn 0.0683000013 0.1118 0.991400003 +vn -0.0654999986 0.0979000032 0.992999971 +vn -0.0450000018 0.0960000008 0.994400024 +vn -0.0573000014 0.0824000016 0.995000005 +vn -0.0812999979 0.0833000019 0.993200004 +vn -0.0908000022 0.0961999968 0.99119997 +vn -0.1065 0.0865999982 0.990499973 +vn -0.0240000002 0.0869999975 0.995899975 +vn -0.00879999995 0.100400001 0.994899988 +vn 0.0124000004 0.0927999988 0.995599985 +vn -0.0013 0.0777999982 0.996999979 +vn 0.0337000005 0.0983999968 0.994599998 +vn 0.0537 0.0974999964 0.993799984 +vn 0.0483000018 0.0781999975 0.995800018 +vn 0.0681999996 0.0923999995 0.993399978 +vn 0.0890000015 0.0865999982 0.992299974 +vn 0.113499999 0.0882000029 0.989600003 +vn -0.189300001 0.0961999968 0.977199972 +vn -0.177200004 0.0813999996 0.980799973 +vn -0.199399993 0.0851999968 0.976199985 +vn -0.219300002 0.0945999995 0.971099973 +vn -0.215499997 0.0798999965 0.973200023 +vn -0.237900004 0.0791999996 0.968100011 +vn -0.140900001 0.0952999964 0.985400021 +vn -0.158899993 0.0944000036 0.982800007 +vn -0.1963 0.0681999996 0.978200018 +vn -0.177499995 0.057599999 0.9824 +vn -0.205500007 0.0566000007 0.976999998 +vn -0.233199999 0.0636000037 0.970300019 +vn -0.226500005 0.0487000011 0.972800016 +vn -0.146200001 0.0755999982 0.986400008 +vn -0.162100002 0.0716999993 0.984200001 +vn -0.153400004 0.0542000011 0.986699998 +vn -0.124899998 0.0957999974 0.987500012 +vn -0.115000002 0.075000003 0.990499973 +vn -0.130700007 0.0709000006 0.988900006 +vn -0.107000001 0.060899999 0.992399991 +vn -0.122599997 0.0496000014 0.99119997 +vn -0.139899999 0.0494000018 0.988900006 +vn -0.0926999971 0.0693000033 0.993300021 +vn -0.0828000009 0.0551999994 0.995000005 +vn -0.0709000006 0.0672999993 0.995199978 +vn -0.0559 0.063000001 0.996399999 +vn -0.0395000018 0.0732999966 0.996500015 +vn -0.0204000007 0.0689999983 0.997399986 +vn -0.0333000012 0.0555999987 0.997900009 +vn 0.000500000024 0.0584999993 0.998300016 +vn -0.0156999994 0.0522999987 0.99849999 +vn 0.0233999994 0.0760999992 0.996800005 +vn 0.0214000009 0.060800001 0.997900009 +vn 0.0395000018 0.0615000017 0.997300029 +vn 0.0599000007 0.0593000017 0.996399999 +vn 0.0732999966 0.075000003 0.994499981 +vn 0.0961999968 0.0665000007 0.993099988 +vn 0.0747999996 0.0527999997 0.995800018 +vn 0.0930000022 0.0456000008 0.994599998 +vn 0.109800003 0.072300002 0.991299987 +vn 0.117200002 0.0538999997 0.991599977 +vn 0.137799993 0.0842999965 0.986899972 +vn 0.144899994 0.0953999981 0.984799981 +vn 0.128999993 0.0683000013 0.989300013 +vn 0.145300001 0.0577000007 0.987699986 +vn 0.176699996 0.101199999 0.979099989 +vn 0.168500006 0.0878000036 0.98180002 +vn 0.158700004 0.0722000003 0.984700024 +vn 0.177900001 0.0623000003 0.98210001 +vn 0.163000003 0.0533999987 0.985199988 +vn 0.204500005 0.0970000029 0.973999977 +vn 0.201000005 0.0817999989 0.976199985 +vn 0.191 0.0998999998 0.976499975 +vn 0.183599994 0.0803000033 0.979700029 +vn 0.199000001 0.0623999983 0.977999985 +vn -0.299299985 0.0601000004 0.952300012 +vn -0.282400012 0.0579000004 0.957599998 +vn -0.291500002 0.0395000018 0.955799997 +vn -0.280600011 0.0251000002 0.959500015 +vn -0.259900004 0.0716999993 0.963 +vn -0.262100011 0.0584000014 0.96329999 +vn -0.269800007 0.0414999984 0.962000012 +vn -0.247600004 0.0421000011 0.967899978 +vn -0.254599988 0.0241 0.966799974 +vn -0.232500002 0.0239000004 0.972299993 +vn -0.212400004 0.0357999988 0.976499975 +vn -0.187700003 0.0416000001 0.981299996 +vn -0.165199995 0.0390000008 0.985499978 +vn -0.178299993 0.0232999995 0.983699977 +vn -0.198200002 0.0270000007 0.979799986 +vn -0.143600002 0.0293000005 0.989199996 +vn -0.158399999 0.0188999996 0.987200022 +vn -0.125200003 0.0324000008 0.991599977 +vn -0.104099996 0.0265999995 0.994199991 +vn -0.1052 0.0438000001 0.993499994 +vn -0.0892999992 0.0386000015 0.995299995 +vn -0.0762000009 0.0239000004 0.996800005 +vn -0.0705000013 0.0439000018 0.996500015 +vn -0.0469999984 0.0432999991 0.998000026 +vn -0.0573000014 0.0308999997 0.997900009 +vn -0.0263 0.0344999991 0.999100029 +vn -0.0348000005 0.0175000001 0.999199986 +vn -0.00879999995 0.0357999988 0.999300003 +vn 0.00939999986 0.0417000018 0.999100029 +vn 0.0494999997 0.0461000018 0.997699976 +vn 0.0478999987 0.0315000005 0.998399973 +vn 0.0626000017 0.0245999992 0.997699976 +vn 0.0781999975 0.0262000002 0.996599972 +vn 0.111299999 0.0388999991 0.992999971 +vn 0.0957999974 0.0216000006 0.995199978 +vn 0.1219 0.0278999992 0.9921 +vn 0.136999995 0.0421999991 0.989700019 +vn 0.148800001 0.0215000007 0.988600016 +vn 0.160500005 0.0366000012 0.986400008 +vn 0.178100005 0.0395999998 0.983200014 +vn 0.174700007 0.0189999994 0.984399974 +vn 0.192300007 0.0436999984 0.980400026 +vn 0.211199999 0.0436999984 0.976499975 +vn 0.196199998 0.0249000005 0.980199993 +vn 0.217600003 0.0693999976 0.97359997 +vn 0.239999995 0.0584999993 0.968999982 +vn 0.2245 0.0494999997 0.973200023 +vn 0.235200003 0.0362999998 0.971300006 +vn 0.2139 0.0254999995 0.976499975 +vn 0.251599997 0.0754000023 0.964900017 +vn 0.261700004 0.0654999986 0.962899983 +vn 0.258100003 0.0454999991 0.964999974 +vn 0.251399994 0.0280000009 0.967499971 +vn 0.291099995 0.0681999996 0.954299986 +vn 0.275400013 0.0590000004 0.959500015 +vn 0.278699994 0.0414000005 0.959500015 +vn 0.270200014 0.0297999997 0.962300003 +vn -0.260899991 0.00939999986 0.965300024 +vn -0.242200002 0.00490000006 0.970200002 +vn -0.222399995 -0.000300000014 0.975000024 +vn -0.214499995 0.0135000004 0.976599991 +vn -0.194999993 0.00930000003 0.980799973 +vn -0.171100006 0.0037 0.985199988 +vn -0.190899998 -0.00590000022 0.981599987 +vn -0.125100002 0.0141000003 0.991999984 +vn -0.0855000019 0.0092000002 0.996299982 +vn -0.0509000011 0.0142000001 0.998600006 +vn -0.063699998 0.00930000003 0.997900009 +vn 0.0891000032 -0.00039999999 0.995999992 +vn 0.0731000006 0.00100000005 0.997300029 +vn 0.107699998 0.00829999987 0.994199991 +vn 0.131799996 0.0104999999 0.99119997 +vn 0.158000007 0.00240000011 0.987399995 +vn 0.138799995 -0.00710000005 0.9903 +vn 0.119499996 -0.00389999989 0.992799997 +vn 0.182799995 0.00159999996 0.983099997 +vn 0.201499999 0.0027999999 0.979499996 +vn 0.231999993 0.0184000004 0.972500026 +vn 0.219699994 0.00730000017 0.975499988 +vn 0.248999998 0.0111999996 0.968400002 +vn 0.237800002 -0.00209999993 0.971300006 +vn -0.167899996 -0.0116999997 0.985700011 +vn 0.0834000036 -0.0229000002 0.996299982 +vn 0.106799997 -0.0163000003 0.994099975 +vn 0.110600002 -0.0353999995 0.993200004 +vn 0.0942000002 -0.0307999998 0.995100021 +vn 0.148100004 -0.0185000002 0.988799989 +vn 0.144199997 -0.0348000005 0.988900006 +vn 0.128099993 -0.0259000007 0.991400003 +vn 0.164399996 -0.0194000006 0.986199975 +vn 0.180000007 -0.0177999996 0.983500004 +vn 0.179900005 -0.0362000018 0.98299998 +vn 0.1963 -0.0203000009 0.980300009 +vn 0.216900006 -0.0110999998 0.976100028 +vn 0.0790000036 -0.0483000018 0.995700002 +vn 0.126000002 -0.0513000004 0.990700006 +vn 0.102600001 -0.0485999994 0.993499994 +vn 0.110699996 -0.0697000027 0.991400003 +vn 0.0956000015 -0.0644000024 0.993300021 +vn 0.144800007 -0.0571999997 0.987800002 +vn 0.164199993 -0.0463999994 0.985300004 +vn 0.156399995 -0.0702999979 0.985199988 +vn 0.195800006 -0.0421000011 0.979700029 +vn 0.077200003 -0.0725999996 0.994400024 +vn 0.0688999966 -0.0917000026 0.993399978 +vn 0.0935000032 -0.0883999988 0.991699994 +vn 0.0527999997 -0.0807999969 0.995299995 +vn 0.0381999984 -0.0687000006 0.996900022 +vn 0.0280000009 -0.0822999999 0.996200025 +vn 0.1171 -0.0896999985 0.989099979 +vn 0.130700007 -0.0724000037 0.988799989 +vn 0.141100004 -0.0891000032 0.986000001 +vn 0.162400007 -0.0942000002 0.982200027 +vn 0.00789999962 -0.0864000022 0.996200025 +vn -0.00789999962 -0.0764999986 0.996999979 +vn -0.0148999998 -0.0930000022 0.995599985 +vn -0.0315999985 -0.0837000012 0.995999992 +vn 0.0164000001 -0.102799997 0.994599998 +vn 0.0406999998 -0.0983000025 0.994300008 +vn 0.0252999999 -0.1206 0.992399991 +vn -0.000699999975 -0.122400001 0.992500007 +vn -0.00179999997 -0.1061 0.994400024 +vn -0.0227000006 -0.114799999 0.993099988 +vn -0.0359000005 -0.103600003 0.994000018 +vn 0.0649000034 -0.104999997 0.992399991 +vn 0.0520000011 -0.118500002 0.991599977 +vn 0.0403000005 -0.113499999 0.992699981 +vn 0.0839999989 -0.107900001 0.99059999 +vn 0.074000001 -0.123499997 0.989600003 +vn 0.102899998 -0.104900002 0.989099979 +vn 0.121100001 -0.107500002 0.986800015 +vn 0.113300003 -0.124700002 0.985700011 +vn 0.0952000022 -0.120200001 0.988200009 +vn 0.133900002 -0.110299997 0.984799981 +vn 0.149700001 -0.1087 0.98269999 +vn -0.0516999997 -0.115900002 0.991900027 +vn -0.0713 -0.109999999 0.991400003 +vn -0.0410999991 -0.126900002 0.991100013 +vn -0.0322999991 -0.138899997 0.989799976 +vn -0.0571000017 -0.145300001 0.987699986 +vn -0.0614 -0.1294 0.989700019 +vn -0.0795999989 -0.126300007 0.988799989 +vn -0.0806000009 -0.142299995 0.986500025 +vn 0.00860000029 -0.135700002 0.990700006 +vn -0.0142000001 -0.135100007 0.990700006 +vn -0.00430000015 -0.148699999 0.988900006 +vn -0.0221999995 -0.148300007 0.988699973 +vn 0.0222999994 -0.142000005 0.989600003 +vn 0.0410999991 -0.138400003 0.989499986 +vn 0.0584999993 -0.135700002 0.989000022 +vn 0.0913000032 -0.135800004 0.986500025 +vn 0.0693999976 -0.146899998 0.986699998 +vn 0.0923999995 -0.156900004 0.983299971 +vn 0.109300002 -0.145500004 0.983299971 +vn 0.130500004 -0.130500004 0.982800007 +vn 0.151099995 -0.127499998 0.980300009 +vn 0.144899994 -0.144400001 0.978900015 +vn 0.123499997 -0.1479 0.981299996 +vn 0.161699995 -0.147 0.975799978 +vn -0.0997999981 -0.134800002 0.985800028 +vn -0.0922999978 -0.156100005 0.983399987 +vn -0.102700002 -0.173199996 0.979499996 +vn -0.0852999985 -0.176899999 0.980499983 +vn -0.116599999 -0.169400007 0.978600025 +vn -0.137600005 -0.163900003 0.976800025 +vn -0.130899996 -0.181500003 0.974600017 +vn -0.146599993 -0.182799995 0.972199976 +vn -0.0734999999 -0.159099996 0.984499991 +vn -0.063000001 -0.171499997 0.983200014 +vn -0.0432000011 -0.158700004 0.986400008 +vn -0.0215000007 -0.165399998 0.986000001 +vn -0.0340000018 -0.175300002 0.983900011 +vn -0.0500999987 -0.180700004 0.982299984 +vn -0.00590000022 -0.172299996 0.985000014 +vn 0.00899999961 -0.167400002 0.985899985 +vn 0.0167999994 -0.155000001 0.987800002 +vn 0.0408999994 -0.1558 0.986899972 +vn 0.0274999999 -0.174899995 0.984200001 +vn 0.061900001 -0.165199995 0.984300017 +vn 0.0463000014 -0.175400004 0.983399987 +vn 0.0793000013 -0.167600006 0.98269999 +vn 0.111900002 -0.170599997 0.978999972 +vn 0.0930000022 -0.184499994 0.978399992 +vn 0.132200003 -0.160500005 0.978100002 +vn 0.147200003 -0.166500002 0.975000024 +vn 0.129099995 -0.183300003 0.9745 +vn -0.1558 -0.198400006 0.967599988 +vn -0.135900006 -0.203899994 0.969500005 +vn -0.165999994 -0.189099997 0.967800021 +vn -0.119199999 -0.1919 0.974099994 +vn -0.115800001 -0.210600004 0.970700026 +vn -0.102799997 -0.195299998 0.975300014 +vn -0.0850000009 -0.198300004 0.976400018 +vn -0.0562999994 -0.198899999 0.978399992 +vn -0.071800001 -0.194499999 0.978299975 +vn -0.0370000005 -0.192900002 0.980499983 +vn -0.0205000006 -0.188600004 0.98180002 +vn -0.0379000008 -0.209399998 0.977100015 +vn 0.0114000002 -0.188899994 0.981899977 +vn 0.0351999998 -0.199499995 0.979300022 +vn 0.0441000015 -0.189899996 0.980799973 +vn 0.065700002 -0.185000002 0.980499983 +vn 0.0581 -0.202299997 0.977599978 +vn 0.0790999979 -0.191699997 0.978299975 +vn 0.0753000006 -0.212599993 0.97420001 +vn 0.0910999998 -0.210299999 0.973399997 +vn 0.114699997 -0.197699994 0.973500013 +vn 0.103100002 -0.209800005 0.972299993 +vn 0.144800007 -0.188099995 0.971400023 +vn 0.138400003 -0.205500007 0.968800008 +vn 0.157499999 -0.188600004 0.969299972 +vn 0.172499999 -0.193399996 0.965900004 +vn 0.162400007 -0.209800005 0.96420002 +vn -0.156499997 -0.216100007 0.963800013 +vn -0.131300002 -0.223399997 0.965799987 +vn -0.139799997 -0.239099994 0.960900009 +vn -0.162499994 -0.233099997 0.958800018 +vn -0.180399999 -0.221699998 0.958299994 +vn -0.107199997 -0.227899998 0.967800021 +vn -0.0973000005 -0.213400006 0.972100019 +vn -0.0864999965 -0.223900005 0.970799983 +vn -0.0689999983 -0.216399997 0.97390002 +vn 0.057500001 -0.219099998 0.973999977 +vn 0.0830999985 -0.230100006 0.969600022 +vn 0.126000002 -0.218700007 0.967599988 +vn 0.106899999 -0.229499996 0.967400014 +vn 0.151299998 -0.223199993 0.963 +vn 0.140400007 -0.238299996 0.961000025 +vn 0.124899998 -0.235300004 0.96390003 +vn 0.169599995 -0.230399996 0.958199978 +vn 0.183799997 -0.213699996 0.959399998 +vn 0.182400003 -0.236000001 0.95450002 +vn -0.175999999 -0.244900003 0.953400016 +vn -0.164299995 -0.259900004 0.951499999 +vn -0.150199994 -0.256199986 0.954900026 +vn -0.115199998 -0.242200002 0.963400006 +vn -0.130700007 -0.259900004 0.956799984 +vn -0.112000003 -0.25850001 0.959500015 +vn -0.0925000012 -0.244900003 0.96509999 +vn -0.0966999978 -0.263500005 0.959800005 +vn 0.0932999998 -0.245499998 0.964900017 +vn 0.0886000022 -0.25999999 0.961499989 +vn 0.0756999999 -0.264299989 0.961499989 +vn 0.109700002 -0.249899998 0.962000012 +vn 0.103200004 -0.264899999 0.958700001 +vn 0.122900002 -0.255199999 0.959100008 +vn 0.144999996 -0.259799987 0.954699993 +vn 0.1602 -0.246600002 0.955799997 +vn 0.169799998 -0.258899987 0.950900018 +vn -0.173099995 -0.279799998 0.944299996 +vn -0.154499993 -0.279100001 0.947799981 +vn -0.122900002 -0.275999993 0.953299999 +vn 0.123999998 -0.273600012 0.953800023 +vn 0.157900006 -0.278200001 0.947399974 +vn 0.178399995 -0.274199992 0.944999993 +vn 0.165800005 -0.293000013 0.941600025 +vn 0.1822 -0.296600014 0.9375 +vn 0.532800019 -0.676199973 -0.50880003 +vn 0.554400027 -0.715900004 -0.424400002 +vn 0.529399991 -0.591099977 -0.60860002 +vn 0.526300013 -0.793600023 -0.305400014 +vn 0.546500027 -0.783299983 -0.296400011 +vn 0.521000028 -0.675499976 -0.521799982 +vn 0.556500018 -0.781499982 -0.282200009 +vn 0.540600002 -0.785399973 -0.301400006 +vn 0.558499992 -0.664200008 -0.496800005 +vn 0.496600002 -0.668500006 -0.553600013 +vn 0.345299989 -0.861400008 -0.372399986 +vn 0.368099988 -0.742999971 -0.558899999 +vn 0.481099993 -0.815400004 -0.321999997 +vn 0.534500003 -0.667299986 -0.518599987 +vn 0.1347 -0.769699991 -0.624100029 +vn 0.0947000012 -0.898999989 -0.427700013 +vn -0.1611 -0.866100013 -0.473199993 +vn -0.102799997 -0.734000027 -0.671299994 +vn -0.349299997 -0.798799992 -0.489800006 +vn -0.269899994 -0.650399983 -0.709999979 +vn -0.511200011 -0.706799984 -0.488999993 +vn -0.420899987 -0.57099998 -0.70480001 +vn -0.547299981 -0.459699988 -0.699400008 +vn -0.649900019 -0.588400006 -0.481000006 +vn -0.771399975 -0.442999989 -0.456900001 +vn -0.673900008 -0.338800013 -0.656499982 +vn -0.871999979 -0.261400014 -0.414000005 +vn -0.776300013 -0.174899995 -0.605599999 +vn -0.852999985 -0.00999999978 -0.521799982 +vn -0.821399987 0.152999997 -0.549499989 +vn -0.943700016 0.0760999992 -0.321799994 +vn -0.941699982 0.165399998 -0.293099999 +vn -0.833100021 0.197799996 -0.516499996 +vn -0.935400009 -0.0759999976 -0.345299989 +vn -0.836300015 0.268000007 -0.478300005 +vn -0.921199977 0.260899991 -0.288500011 +vn -0.764900029 0.313800007 -0.5625 +vn -0.936200023 0.218899995 -0.275000006 +vn -0.854399979 0.245199993 -0.458099991 +vn -0.796199977 0.330900013 -0.506500006 +vn -0.921299994 0.288599998 -0.260399997 +vn -0.842400014 0.336100012 -0.421200007 +vn -0.913699985 0.328099996 -0.239800006 +vn -0.791100025 0.407499999 -0.456200004 +vn -0.857200027 0.461299986 -0.2289 +vn -0.730700016 0.476399988 -0.488999993 +vn -0.893899977 0.3847 -0.230000004 +vn -0.796899974 0.379599988 -0.469999999 +vn -0.70599997 0.549899995 -0.4463 +vn -0.815999985 0.54549998 -0.191400006 +vn -0.685599983 0.638100028 -0.350499988 +vn -0.759899974 0.633400023 -0.145999998 +vn -0.676800013 0.72390002 -0.133399993 +vn -0.597100019 0.716400027 -0.360900015 +vn -0.540899992 0.796999991 -0.268900007 +vn -0.435900003 0.823199987 -0.363799989 +vn -0.516499996 0.851199985 -0.0934000015 +vn -0.439399987 0.855599999 -0.273499995 +vn -0.593400002 0.800400019 -0.0847999975 +vn -0.47299999 0.877900004 -0.0741000026 +vn -0.399599999 0.860499978 -0.316100001 +vn -0.435000002 0.894900024 -0.100000001 +vn -0.372799993 0.830200016 -0.414499998 +vn -0.447299987 0.891099989 -0.0763000026 +vn -0.399800003 0.857800007 -0.323100001 +vn -0.405999988 0.860800028 -0.307000011 +vn -0.436699986 0.896700025 -0.0731000006 +vn -0.444099993 0.893000007 -0.0731000006 +vn -0.413399994 0.864400029 -0.286199987 +vn -0.468100011 0.880699992 -0.0729999989 +vn -0.442799985 0.829599977 -0.34009999 +vn -0.436199993 0.860199988 -0.264299989 +vn -0.532400012 0.843999982 -0.0650999993 +vn -0.511500001 0.805599988 -0.298900008 +vn -0.717000008 0.618700027 -0.32100001 +vn -0.753099978 0.653900027 -0.072300002 +vn -0.895799994 0.362300009 -0.257499993 +vn -0.926299989 0.373100013 -0.0526000001 +vn -0.918900013 0.166299999 -0.35769999 +vn -0.994899988 -0.00549999997 -0.100900002 +vn -0.952600002 0.000300000014 -0.304199994 +vn -0.979200006 0.189899996 -0.0711999983 +vn -0.933000028 -0.168300003 -0.317999989 +vn -0.927999973 -0.354000002 -0.116400003 +vn -0.872699976 -0.320100009 -0.368600011 +vn -0.981299996 -0.181400001 -0.063699998 +vn -0.816100001 -0.500400007 -0.289200008 +vn -0.717899978 -0.684899986 -0.124600001 +vn -0.668299973 -0.626299977 -0.4014 +vn -0.845499992 -0.529900014 -0.0666000023 +vn -0.535000026 -0.752900004 -0.383100003 +vn -0.404199988 -0.903400004 -0.142800003 +vn -0.397199988 -0.83039999 -0.390700012 +vn -0.562099993 -0.822399974 -0.0872000009 +vn -0.148000002 -0.923699975 -0.353300005 +vn 0.0847000033 -0.987699986 -0.131200001 +vn 0.0738999993 -0.927399993 -0.366699994 +vn -0.141000003 -0.984499991 -0.104199998 +vn 0.126900002 -0.911800027 -0.390599996 +vn 0.141599998 -0.982200027 -0.123499997 +vn 0.160799995 -0.979399979 -0.122100003 +vn 0.146699995 -0.93809998 -0.313699991 +vn 0.166500002 -0.917400002 -0.361600012 +vn 0.168400005 -0.977699995 -0.125200003 +vn 0.179700002 -0.90170002 -0.393299997 +vn 0.1646 -0.927999973 -0.334300011 +vn 0.178100005 -0.925999999 -0.332899988 +vn 0.1611 -0.979600012 -0.120200001 +vn 0.137700006 -0.981299996 -0.134499997 +vn 0.179299995 -0.901300013 -0.394199997 +vn 0.134399995 -0.902999997 -0.407999992 +vn 0.0083999997 -0.986599982 -0.162599996 +vn 0.0529000014 -0.924300015 -0.377900004 +vn 0.0882999972 -0.986100018 -0.140900001 +vn 0.159600005 -0.898400009 -0.409200013 +vn -0.0443999991 -0.910099983 -0.412099987 +vn -0.104599997 -0.978799999 -0.175999999 +vn -0.1611 -0.906300008 -0.390700012 +vn -0.221799999 -0.957199991 -0.186100006 +vn -0.313199997 -0.922999978 -0.223499998 +vn -0.2315 -0.860000014 -0.454899997 +vn -0.337700009 -0.852100015 -0.399800003 +vn -0.379900008 -0.804400027 -0.456800014 +vn -0.460399985 -0.849900007 -0.256399989 +vn -0.509500027 -0.817399979 -0.268700004 +vn -0.386999995 -0.774699986 -0.500100017 +vn -0.401499987 -0.888599992 -0.221799999 +vn -0.449800014 -0.754000008 -0.478700012 +vn -0.568499982 -0.762899995 -0.307799995 +vn -0.453700006 -0.717400014 -0.528699994 +vn -0.442600012 -0.76730001 -0.464100003 +vn -0.545400023 -0.789699972 -0.281100005 +vn -0.493600011 -0.699899971 -0.516099989 +vn -0.60710001 -0.733399987 -0.305900007 +vn -0.644400001 -0.687900007 -0.333900005 +vn -0.524299979 -0.688899994 -0.500500023 +vn -0.563300014 -0.579699993 -0.588699996 +vn -0.530099988 -0.626699984 -0.571099997 +vn -0.780200005 -0.479799986 -0.401499987 +vn -0.628600001 -0.461800009 -0.625800014 +vn -0.711099982 -0.608900011 -0.351599991 +vn -0.726800025 -0.289900005 -0.622699976 +vn -0.85769999 -0.292600006 -0.422800004 +vn -0.887499988 -0.0835999995 -0.453200012 +vn -0.74879998 -0.0965000018 -0.655700028 +vn -0.718699992 0.0789000019 -0.690900028 +vn -0.834200025 0.28670001 -0.471100003 +vn -0.71329999 0.254099995 -0.653199971 +vn -0.878600001 0.110200003 -0.464700013 +vn -0.61500001 0.398200005 -0.680599988 +vn -0.767499983 0.453900009 -0.452800006 +vn -0.563300014 0.573800027 -0.594500005 +vn -0.667200029 0.620999992 -0.411300004 +vn -0.37470001 0.721899986 -0.581799984 +vn -0.271200001 0.905799985 -0.325500011 +vn -0.182500005 0.836099982 -0.51730001 +vn -0.483099997 0.789699972 -0.377999991 +vn -0.0372000001 0.855499983 -0.51639998 +vn -0.0640999973 0.962899983 -0.262100011 +vn 0.00419999985 0.86500001 -0.501800001 +vn -0.136500001 0.951499999 -0.27579999 +vn 0.0165999997 0.900099993 -0.435299993 +vn -0.0333000012 0.965399981 -0.258700013 +vn 0.0274999999 0.873300016 -0.486400008 +vn -0.0366000012 0.965200007 -0.2588 +vn 0.00680000009 0.88440001 -0.466600001 +vn -0.0425000004 0.968400002 -0.245900005 +vn -0.0178999994 0.922699988 -0.385199994 +vn -0.0610000007 0.968599975 -0.240999997 +vn -0.0515000001 0.856299996 -0.513899982 +vn -0.187199995 0.943099976 -0.274800003 +vn -0.146200001 0.864199996 -0.481400013 +vn -0.0976999998 0.961399972 -0.256999999 +vn -0.0317000002 0.879299998 -0.475300014 +vn -0.323500007 0.797399998 -0.509500027 +vn -0.380100012 0.881200016 -0.281199992 +vn -0.506600022 0.718400002 -0.476700008 +vn -0.558700025 0.782800019 -0.273900002 +vn -0.639199972 0.57069999 -0.515500009 +vn -0.824199975 0.481099993 -0.298599988 +vn -0.731899977 0.40990001 -0.544399977 +vn -0.707899988 0.644599974 -0.288700014 +vn -0.778400004 0.334199995 -0.531400025 +vn -0.871500015 0.404599994 -0.277099997 +vn -0.888100028 0.370400012 -0.272300005 +vn -0.804499984 0.320899993 -0.499799997 +vn -0.826799989 0.287400007 -0.483500004 +vn -0.893899977 0.341500014 -0.290199995 +vn -0.78579998 0.234099999 -0.57249999 +vn -0.896000028 0.354200006 -0.267699987 +vn -0.817700028 0.291799992 -0.496100008 +vn -0.840200007 0.273000002 -0.468499988 +vn -0.897499979 0.348100007 -0.270700008 +vn -0.888400018 0.370700002 -0.270900011 +vn -0.857299984 0.426099986 -0.288899988 +vn -0.791899979 0.313600004 -0.523899972 +vn -0.832199991 0.303900003 -0.463699996 +vn -0.776300013 0.540799975 -0.324000001 +vn -0.724300027 0.424899995 -0.542999983 +vn -0.565800011 0.588999987 -0.577099979 +vn -0.603100002 0.702899992 -0.377000004 +vn -0.363700002 0.724099994 -0.586000025 +vn -0.382699996 0.831799984 -0.401899993 +vn -0.171700001 0.72329998 -0.668900013 +vn 0.00120000006 0.893000007 -0.449999988 +vn 0.00260000001 0.771899998 -0.635699987 +vn -0.186100006 0.880100012 -0.43689999 +vn 0.178599998 0.722500026 -0.667900026 +vn 0.382600009 0.815900028 -0.433499992 +vn 0.354999989 0.673099995 -0.648800015 +vn 0.190799996 0.879999995 -0.434899986 +vn 0.568700016 0.589100003 -0.574100018 +vn 0.777499974 0.533699989 -0.332500011 +vn 0.722199976 0.411799997 -0.555800021 +vn 0.603600025 0.70569998 -0.371100008 +vn 0.795799971 0.313600004 -0.518100023 +vn 0.859099984 0.426800013 -0.282499999 +vn 0.890299976 0.370700002 -0.264499992 +vn 0.835600019 0.303900003 -0.457700014 +vn 0.843500018 0.273000002 -0.462500006 +vn 0.896200001 0.341399997 -0.283499986 +vn 0.789799988 0.234799996 -0.566600025 +vn 0.899399996 0.348199993 -0.264099985 +vn 0.825699985 0.282799989 -0.488200009 +vn 0.897800028 0.354699999 -0.261099994 +vn 0.889900029 0.370200008 -0.266400009 +vn 0.819700003 0.291700006 -0.492900014 +vn 0.782400012 0.334100008 -0.525600016 +vn 0.825999975 0.48179999 -0.292699993 +vn 0.736100018 0.40959999 -0.538800001 +vn 0.873300016 0.405000001 -0.270700008 +vn 0.809499979 0.321799994 -0.491100013 +vn 0.642799973 0.570500016 -0.511200011 +vn 0.709200025 0.645099998 -0.284299999 +vn 0.50999999 0.718400002 -0.47299999 +vn 0.560299993 0.782999992 -0.270099998 +vn 0.326999992 0.797399998 -0.507099986 +vn 0.187999994 0.943400025 -0.273200005 +vn 0.149700001 0.864199996 -0.480399996 +vn 0.381199986 0.881699979 -0.277999997 +vn 0.0557000004 0.855799973 -0.514199972 +vn 0.0989999995 0.961499989 -0.256199986 +vn 0.0610000007 0.966000021 -0.251199991 +vn 0.0281000007 0.863600016 -0.503400028 +vn -0.00480000023 0.884899974 -0.465700001 +vn 0.0322999991 0.962499976 -0.269400001 +vn -0.0450000018 0.829299986 -0.556900024 +vn 0.0436000004 0.968400002 -0.245700002 +vn 0.00760000013 0.89380002 -0.448500007 +vn -0.0130000003 0.90259999 -0.430200011 +vn 0.0375999995 0.965600014 -0.257200003 +vn 0.0688000023 0.965799987 -0.249799997 +vn 0.0375999995 0.85619998 -0.515299976 +vn 0.26879999 0.907599986 -0.322600007 +vn 0.182600006 0.837300003 -0.515299976 +vn 0.135299996 0.951900005 -0.274800003 +vn 0.00600000005 0.876800001 -0.480699986 +vn 0.374900013 0.724399984 -0.578499973 +vn 0.481099993 0.792699993 -0.374300003 +vn 0.56400001 0.577799976 -0.589999974 +vn 0.666299999 0.625 -0.406599998 +vn 0.616100013 0.400700003 -0.67809999 +vn 0.83099997 0.29429999 -0.47209999 +vn 0.670400023 0.247600004 -0.699500024 +vn 0.767099977 0.460200012 -0.446999997 +vn 0.719299972 0.0918999985 -0.688600004 +vn 0.880200028 0.121699996 -0.458799988 +vn 0.785300016 -0.0864000022 -0.613099992 +vn 0.899600029 -0.0734999999 -0.430599988 +vn 0.7227 -0.317999989 -0.613699973 +vn 0.753099978 -0.543299973 -0.371100008 +vn 0.637700021 -0.528400004 -0.560599983 +vn 0.853100002 -0.320600003 -0.41170001 +vn 0.532100022 -0.631200016 -0.564400017 +vn 0.672500014 -0.663299978 -0.328399986 +vn 0.624100029 -0.717899978 -0.308400005 +vn 0.51849997 -0.66930002 -0.532199979 +vn 0.501600027 -0.700500011 -0.507700026 +vn 0.601199985 -0.740800023 -0.299600005 +vn 0.488000005 -0.687900007 -0.537400007 +vn 0.51789999 -0.705200016 -0.484299988 +vn 0.507399976 -0.691200018 -0.514500022 +vn 0.599399984 -0.742699981 -0.298599988 +vn 0.611199975 -0.7324 -0.300199986 +vn 0.537100017 -0.703400016 -0.465499997 +vn 0.637700021 -0.70599997 -0.307900012 +vn 0.534799993 -0.63349998 -0.559099972 +vn 0.531700015 -0.663399994 -0.526600003 +vn 0.699800014 -0.64200002 -0.3134 +vn 0.603299975 -0.576099992 -0.551400006 +vn 0.722899973 -0.429899991 -0.540899992 +vn 0.89410001 -0.289900005 -0.341500014 +vn 0.779100001 -0.244900003 -0.577099979 +vn 0.817099988 -0.479900002 -0.319299996 +vn 0.846800029 -0.064000003 -0.528100014 +vn 0.944999993 0.102399997 -0.310600013 +vn 0.791100025 0.126900002 -0.59829998 +vn 0.947799981 -0.0944999978 -0.304500014 +vn 0.838100016 0.203999996 -0.505999982 +vn 0.943099976 0.187800005 -0.274300009 +vn 0.936500013 0.221200004 -0.272100002 +vn 0.836199999 0.230900005 -0.497500002 +vn 0.807299972 0.264099985 -0.527800024 +vn 0.927100003 0.236100003 -0.291200012 +vn 0.772199988 0.280200005 -0.570299983 +vn 0.933499992 0.233700007 -0.272000015 +vn 0.847100019 0.254500002 -0.466500014 +vn 0.83859998 0.263599992 -0.476799995 +vn 0.93720001 0.221200004 -0.269499987 +vn 0.940900028 0.189700007 -0.280499995 +vn 0.845099986 0.258700013 -0.467799991 +vn 0.826200008 0.191599995 -0.529799998 +vn 0.935699999 -0.0136000002 -0.352499992 +vn 0.828000009 0.0603 -0.557399988 +vn 0.945900023 0.119800001 -0.301400006 +vn 0.837700009 0.227300003 -0.496600002 +vn 0.788600028 -0.151899993 -0.595899999 +vn 0.766300023 -0.439799994 -0.468400002 +vn 0.662199974 -0.33039999 -0.672500014 +vn 0.88349998 -0.240799993 -0.401899993 +vn 0.550800025 -0.460299999 -0.69630003 +vn 0.510599971 -0.703199983 -0.494700015 +vn 0.440699995 -0.596800029 -0.67049998 +vn 0.651499987 -0.591300011 -0.475300014 +vn 0.271499991 -0.65109998 -0.708800018 +vn 0.160600007 -0.857299984 -0.489199996 +vn 0.0987000018 -0.711300015 -0.69599998 +vn 0.353399992 -0.799300015 -0.486099988 +vn -0.131200001 -0.769500017 -0.625 +vn -0.345699996 -0.854600012 -0.387400001 +vn -0.372200012 -0.664200008 -0.648299992 +vn -0.0916000009 -0.899500012 -0.427300006 +vn -0.493499994 -0.667900026 -0.557200015 +vn -0.479200006 -0.814999998 -0.325800002 +vn -0.539200008 -0.78609997 -0.302300006 +vn -0.532000005 -0.670700014 -0.516700029 +vn -0.555400014 -0.659200013 -0.507000029 +vn -0.554400027 -0.767700016 -0.321399987 +vn -0.530499995 -0.600199997 -0.598699987 +vn -0.558300018 -0.680400014 -0.474599987 +vn -0.529200017 -0.676299989 -0.512399971 +vn -0.54369998 -0.783800006 -0.300199986 +vn -0.524200022 -0.79430002 -0.307099998 +vn -0.522400022 -0.693300009 -0.496399999 +vn -0.456 -0.689700007 -0.5625 +vn -0.402500004 -0.852999985 -0.332199991 +vn -0.384000003 -0.754100025 -0.532800019 +vn -0.487899989 -0.814700007 -0.313300014 +vn -0.4824 -0.6778 -0.554899991 +vn -0.196199998 -0.805400014 -0.559400022 +vn 0.00249999994 -0.930299997 -0.366699994 +vn 0.00200000009 -0.800100029 -0.599900007 +vn -0.209099993 -0.918600023 -0.335399985 +vn 0.201700002 -0.806200027 -0.556200027 +vn 0.404399991 -0.850499988 -0.336199999 +vn 0.376399994 -0.724900007 -0.576900005 +vn 0.212400004 -0.91839999 -0.333799988 +vn 0.460399985 -0.689199984 -0.559499979 +vn 0.490099996 -0.814700007 -0.309899986 +vn 0.487399995 -0.679899991 -0.547900021 +vn 0.490099996 -0.53490001 -0.688300014 +vn 0.468300015 -0.514999986 -0.717899978 +vn 0.53640002 -0.539799988 -0.648699999 +vn 0.527800024 -0.501800001 -0.685299993 +vn 0.512000024 -0.490700006 -0.7051 +vn 0.467599988 -0.439599991 -0.766900003 +vn 0.37439999 -0.57160002 -0.730099976 +vn 0.167600006 -0.604300022 -0.778900027 +vn -0.0342000015 -0.548399985 -0.835500002 +vn -0.156299993 -0.417299986 -0.895200014 +vn -0.269899994 -0.349599987 -0.897199988 +vn -0.375499994 -0.270200014 -0.886600018 +vn -0.504800022 -0.188500002 -0.842400014 +vn -0.645900011 -0.0813999996 -0.75910002 +vn -0.688300014 0.0800999999 -0.721000016 +vn -0.606700003 0.226799995 -0.761900008 +vn -0.633400023 0.262100011 -0.728100002 +vn -0.705900013 0.287600011 -0.647300005 +vn -0.688300014 0.3046 -0.658399999 +vn -0.579100013 0.34709999 -0.737699986 +vn -0.70450002 0.35679999 -0.613399982 +vn -0.607500017 0.402200013 -0.685000002 +vn -0.5 0.439200014 -0.746399999 +vn -0.610899985 0.382299989 -0.693300009 +vn -0.531899989 0.511900008 -0.674600005 +vn -0.521799982 0.589999974 -0.61619997 +vn -0.487100005 0.67049998 -0.559599996 +vn -0.429100007 0.739700019 -0.518400013 +vn -0.305500001 0.712199986 -0.632000029 +vn -0.35589999 0.793600023 -0.493499994 +vn -0.319499999 0.769500017 -0.552999973 +vn -0.292800009 0.739300013 -0.606400013 +vn -0.352899998 0.778800011 -0.51849997 +vn -0.359400004 0.773899972 -0.521399975 +vn -0.406300008 0.799499989 -0.442400008 +vn -0.373100013 0.689599991 -0.620700002 +vn -0.349599987 0.689800024 -0.634000003 +vn -0.450500011 0.683600008 -0.574299991 +vn -0.662199974 0.549199998 -0.509800017 +vn -0.769200027 0.309199989 -0.559199989 +vn -0.74849999 0.131999999 -0.649900019 +vn -0.842899978 -0.00549999997 -0.538100004 +vn -0.841600001 -0.142499998 -0.521000028 +vn -0.746399999 -0.267100006 -0.609499991 +vn -0.725799978 -0.430500001 -0.536599994 +vn -0.560500026 -0.497900009 -0.66170001 +vn -0.439500004 -0.594500005 -0.673399985 +vn -0.346500009 -0.707899988 -0.615499973 +vn -0.146500006 -0.824100018 -0.547200024 +vn 0.0522999987 -0.798699975 -0.599399984 +vn 0.0976999998 -0.745400012 -0.659399986 +vn 0.132599995 -0.861199975 -0.490700006 +vn 0.111199997 -0.727599978 -0.676900029 +vn 0.152199998 -0.810899973 -0.564999998 +vn 0.160999998 -0.812600017 -0.560100019 +vn 0.174899995 -0.765399992 -0.619400024 +vn 0.187099993 -0.802399993 -0.566799998 +vn 0.191699997 -0.735000014 -0.650399983 +vn 0.171499997 -0.723699987 -0.668500006 +vn 0.1866 -0.729900002 -0.657599986 +vn 0.0998999998 -0.798900008 -0.592999995 +vn 0.0297999997 -0.755200028 -0.654900014 +vn -0.0617000014 -0.760100007 -0.646799982 +vn -0.119599998 -0.718699992 -0.685000002 +vn -0.223199993 -0.742799997 -0.631200016 +vn -0.25819999 -0.705299973 -0.6602 +vn -0.233600006 -0.640999973 -0.731100023 +vn -0.309300005 -0.658999979 -0.685599983 +vn -0.290100008 -0.61500001 -0.733299971 +vn -0.359699994 -0.628400028 -0.689800024 +vn -0.381399989 -0.618700027 -0.686800003 +vn -0.279799998 -0.653599977 -0.703199983 +vn -0.349900007 -0.547200024 -0.760399997 +vn -0.343499988 -0.493400007 -0.799099982 +vn -0.446500003 -0.415300012 -0.792599976 +vn -0.560000002 -0.273400009 -0.782100022 +vn -0.555199981 -0.104800001 -0.825100005 +vn -0.466800004 0.0383000001 -0.88349998 +vn -0.532700002 0.200599998 -0.8222 +vn -0.380100012 0.296799988 -0.875999987 +vn -0.366600007 0.457100004 -0.810299993 +vn -0.2509 0.620599985 -0.742900014 +vn -0.0720999986 0.710099995 -0.700399995 +vn 0.0786999986 0.666800022 -0.741100013 +vn 0.0846000016 0.727699995 -0.680700004 +vn 0.0870999992 0.76849997 -0.63380003 +vn 0.0877000019 0.758300006 -0.646000028 +vn 0.0926999971 0.708599985 -0.699500024 +vn 0.0557999983 0.755500019 -0.652800024 +vn 0.0154999997 0.844799995 -0.534799993 +vn 0.0546000004 0.717800021 -0.694100022 +vn 0.0118000004 0.731899977 -0.681299984 +vn 0.00659999996 0.655399978 -0.755299985 +vn -0.0938000008 0.722000003 -0.685500026 +vn -0.259600013 0.675599992 -0.690100014 +vn -0.386599988 0.56190002 -0.731299996 +vn -0.53640002 0.475899994 -0.697000027 +vn -0.599300027 0.315800011 -0.735599995 +vn -0.59920001 0.223499998 -0.768700004 +vn -0.632000029 0.215000004 -0.744499981 +vn -0.714100003 0.206300005 -0.668900013 +vn -0.688399971 0.204300001 -0.69599998 +vn -0.64349997 0.113399997 -0.757000029 +vn -0.727999985 0.168500006 -0.664499998 +vn -0.726899981 0.184799999 -0.66140002 +vn -0.646499991 0.155399993 -0.746900022 +vn -0.621800005 0.273900002 -0.733799994 +vn -0.50150001 0.4463 -0.74119997 +vn -0.305000007 0.5079 -0.805599988 +vn -0.140200004 0.473100007 -0.869799972 +vn 0.00289999996 0.587300003 -0.809300005 +vn 0.147300005 0.474999994 -0.867600024 +vn 0.298299998 0.472799987 -0.829100013 +vn 0.505800009 0.445800006 -0.738499999 +vn 0.625 0.270700008 -0.732200027 +vn 0.651799977 0.155399993 -0.742299974 +vn 0.731700003 0.184799999 -0.656099975 +vn 0.648299992 0.112999998 -0.752900004 +vn 0.732800007 0.168500006 -0.659200013 +vn 0.6778 0.179900005 -0.712899983 +vn 0.688399971 0.201800004 -0.696699977 +vn 0.66170001 0.229100004 -0.71390003 +vn 0.605499983 0.223000005 -0.763999999 +vn 0.604700029 0.315800011 -0.731100023 +vn 0.541199982 0.475600004 -0.693400025 +vn 0.39410001 0.564899981 -0.725000024 +vn 0.264200002 0.675700009 -0.688199997 +vn 0.0983999968 0.722000003 -0.684800029 +vn -9.99999975e-05 0.655399978 -0.755299985 +vn -0.0200999994 0.689400017 -0.724099994 +vn -0.0417999998 0.750199974 -0.659799993 +vn -0.0516999997 0.754299998 -0.654500008 +vn -0.0992000028 0.713400006 -0.693700016 +vn -0.0846000016 0.768999994 -0.633599997 +vn -0.0804999992 0.731899977 -0.676699996 +vn -0.0760999992 0.667200029 -0.740999997 +vn 0.0732999966 0.711300015 -0.699000001 +vn 0.252499998 0.622799993 -0.740499973 +vn 0.369100004 0.460399985 -0.807399988 +vn 0.384900004 0.29429999 -0.874800026 +vn 0.430999994 0.173999995 -0.885399997 +vn 0.467099994 0.0505000018 -0.882700026 +vn 0.559300005 -0.0969000012 -0.823300004 +vn 0.560599983 -0.300799996 -0.771499991 +vn 0.471199989 -0.481900007 -0.738799989 +vn 0.323399991 -0.534699976 -0.780700028 +vn 0.359200001 -0.593599975 -0.720200002 +vn 0.382200003 -0.630800009 -0.675199986 +vn 0.376199991 -0.623099983 -0.685699999 +vn 0.389499992 -0.603600025 -0.69569999 +vn 0.43900001 -0.631699979 -0.638999999 +vn 0.391900003 -0.542999983 -0.742699981 +vn 0.368600011 -0.490500003 -0.789600015 +vn 0.461400002 -0.466100007 -0.754899979 +vn 0.593599975 -0.358399987 -0.720600009 +vn 0.595300019 -0.175799996 -0.784099996 +vn 0.699800014 -0.0379999988 -0.71329999 +vn 0.632700026 0.207699999 -0.745999992 +vn 0.6602 0.234400004 -0.71359998 +vn 0.690599978 0.2676 -0.671899974 +vn 0.549300015 0.274599999 -0.789200008 +vn 0.712899983 0.292100012 -0.637600005 +vn 0.674899995 0.277099997 -0.684000015 +vn 0.613799989 0.256099999 -0.746800005 +vn 0.638000011 0.148300007 -0.755599976 +vn 0.661199987 -0.0623000003 -0.747600019 +vn 0.503899992 -0.188899994 -0.842899978 +vn 0.384900004 -0.267399997 -0.883400023 +vn 0.334899992 -0.438600004 -0.833999991 +vn 0.152899995 -0.4208 -0.894200027 +vn 0.0277999993 -0.503899992 -0.863300025 +vn -0.165800005 -0.600499988 -0.782199979 +vn -0.463400006 -0.439799994 -0.769299984 +vn -0.513000011 -0.514599979 -0.686999977 +vn -0.531199992 -0.528100014 -0.662500024 +vn -0.520600021 -0.509199977 -0.685400009 +vn -0.485399991 -0.534699976 -0.691799998 +vn -0.472900003 -0.537400007 -0.698300004 +vn -0.377400011 -0.482600003 -0.790400028 +vn -0.40169999 -0.470499992 -0.785700023 +vn -0.337099999 -0.598200023 -0.726999998 +vn -0.173700005 -0.657199979 -0.733399987 +vn 0.00289999996 -0.59829998 -0.801299989 +vn 0.1796 -0.656899989 -0.732299984 +vn 0.327100009 -0.560400009 -0.760900021 +vn 0.38409999 -0.482899994 -0.787 +vn 0.420599997 -0.499599993 -0.757300019 +vn 0.457300007 -0.309899986 -0.833599985 +vn 0.446500003 -0.348199993 -0.824299991 +vn 0.460900009 -0.296200007 -0.836600006 +vn 0.413700014 -0.352999985 -0.83920002 +vn 0.377000004 -0.307000011 -0.87379998 +vn 0.447699994 -0.265599996 -0.853799999 +vn 0.412 -0.224399999 -0.883099973 +vn 0.356099993 -0.344999999 -0.868399978 +vn 0.1954 -0.392500013 -0.898800015 +vn 0.0405000001 -0.31099999 -0.949500024 +vn -0.0494999997 -0.210199997 -0.976400018 +vn -0.0719000027 -0.0648000017 -0.995299995 +vn -0.221399993 -0.102399997 -0.969799995 +vn -0.2861 -0.0166999996 -0.958100021 +vn -0.465900004 0.0272000004 -0.88440001 +vn -0.390199989 0.26879999 -0.880599976 +vn -0.336100012 0.305799991 -0.890799999 +vn -0.447400004 0.1734 -0.877399981 +vn -0.360500008 0.333200008 -0.871200025 +vn -0.474799991 0.352100015 -0.806599975 +vn -0.520500004 0.334800005 -0.78549999 +vn -0.469700009 0.328099996 -0.819599986 +vn -0.476799995 0.318100005 -0.819400012 +vn -0.363599986 0.361499995 -0.85860002 +vn -0.396600008 0.354799986 -0.846700013 +vn -0.344500005 0.44749999 -0.825299978 +vn -0.262899995 0.458200008 -0.849099994 +vn -0.339899987 0.583999991 -0.737200022 +vn -0.177699998 0.572300017 -0.800499976 +vn -0.220200002 0.645200014 -0.731599987 +vn -0.254299998 0.59859997 -0.759599984 +vn -0.210600004 0.619599998 -0.756200016 +vn -0.148200005 0.533800006 -0.832499981 +vn -0.278499991 0.644200027 -0.712400019 +vn -0.259499997 0.661499977 -0.703599989 +vn -0.267500013 0.601300001 -0.752900004 +vn -0.2773 0.507099986 -0.816100001 +vn -0.253600001 0.499199986 -0.828599989 +vn -0.351200014 0.50940001 -0.785600007 +vn -0.530900002 0.448500007 -0.718999982 +vn -0.550800025 0.218400002 -0.805599988 +vn -0.565800011 0.102499999 -0.818199992 +vn -0.639400005 0.0148 -0.76880002 +vn -0.689499974 -0.125100002 -0.713400006 +vn -0.54430002 -0.173299998 -0.820800006 +vn -0.552299976 -0.321500003 -0.769200027 +vn -0.386400014 -0.299400002 -0.872399986 +vn -0.308200002 -0.404300004 -0.861100018 +vn -0.300000012 -0.538200021 -0.787599981 +vn -0.125400007 -0.660700023 -0.740100026 +vn 0.0182000007 -0.574599981 -0.818199992 +vn 0.0687000006 -0.560000002 -0.825600028 +vn 0.0736000016 -0.505500019 -0.859700024 +vn 0.141399994 -0.648899972 -0.747600019 +vn 0.155000001 -0.542500019 -0.825699985 +vn 0.184699997 -0.593500018 -0.783399999 +vn 0.186299995 -0.49939999 -0.846099973 +vn 0.123000003 -0.60589999 -0.785899997 +vn 0.195999995 -0.510699987 -0.837100029 +vn 0.189600006 -0.515699983 -0.835600019 +vn 0.143099993 -0.592199981 -0.792999983 +vn 0.101899996 -0.52670002 -0.843900025 +vn 0.0529000014 -0.518899977 -0.853200018 +vn -0.00109999999 -0.525399983 -0.850899994 +vn -0.0643000007 -0.541199982 -0.838400006 +vn -0.113200001 -0.546500027 -0.82980001 +vn -0.0406999998 -0.426800013 -0.903400004 +vn -0.112999998 -0.482800007 -0.868399978 +vn -0.0504000001 -0.433099985 -0.899900019 +vn -0.0868000016 -0.443699986 -0.89200002 +vn -0.202800006 -0.520399988 -0.82950002 +vn -0.176200002 -0.487399995 -0.855199993 +vn -0.142700002 -0.426400006 -0.89319998 +vn -0.142199993 -0.387899995 -0.910700023 +vn -0.238000005 -0.342700005 -0.908800006 +vn -0.350400001 -0.242599994 -0.904600024 +vn -0.309500009 -0.1052 -0.944999993 +vn -0.231099993 0.00179999997 -0.972899973 +vn -0.278600007 0.122000001 -0.952600002 +vn -0.173600003 0.194100007 -0.965499997 +vn -0.0881000012 0.261099994 -0.961300015 +vn -0.1021 0.476399988 -0.873300016 +vn 0.165299997 0.467200011 -0.868600011 +vn 0.168699995 0.523599982 -0.835099995 +vn 0.0700000003 0.498299986 -0.864099979 +vn 0.149399996 0.577700019 -0.802399993 +vn 0.154499993 0.465299994 -0.871599972 +vn 0.103 0.570900023 -0.814499974 +vn 0.100400001 0.514800012 -0.851400018 +vn 0.161699995 0.558000028 -0.813899994 +vn 0.0577000007 0.525399983 -0.84890002 +vn 0.0527000017 0.448000014 -0.892499983 +vn -0.0274999999 0.494700015 -0.868600011 +vn -0.168599993 0.499000013 -0.850000024 +vn -0.193499997 0.302300006 -0.933399975 +vn -0.391799986 0.338800013 -0.855400026 +vn -0.414299995 0.117399998 -0.902499974 +vn -0.379999995 0.0781999975 -0.921700001 +vn -0.425099999 0.2016 -0.882399976 +vn -0.587499976 0.0697000027 -0.806200027 +vn -0.574599981 0.0892999992 -0.813499987 +vn -0.56190002 0.0458999984 -0.825999975 +vn -0.550000012 0.108900003 -0.828000009 +vn -0.517099977 0.106700003 -0.84920001 +vn -0.535300016 0.0282000005 -0.844200015 +vn -0.481400013 0.0144999996 -0.876399994 +vn -0.475800008 0.1043 -0.873300016 +vn -0.406100005 0.266099989 -0.874199986 +vn -0.196600005 0.187600002 -0.962400019 +vn -0.1021 0.247899994 -0.963400006 +vn 0.00340000005 0.324400008 -0.945900023 +vn 0.108900003 0.246199995 -0.963100016 +vn 0.213699996 0.217099994 -0.952499986 +vn 0.412400007 0.265300006 -0.871500015 +vn 0.487699986 0.0144999996 -0.872900009 +vn 0.541599989 0.0284000002 -0.840200007 +vn 0.48210001 0.1043 -0.869899988 +vn 0.483399987 0.0654999986 -0.872900009 +vn 0.498800009 0.0918999985 -0.861800015 +vn 0.579699993 0.0904000029 -0.809800029 +vn 0.593400002 0.0697000027 -0.801900029 +vn 0.567900002 0.0460000001 -0.821799994 +vn 0.466899991 0.119000003 -0.876299977 +vn 0.41960001 0.1175 -0.900099993 +vn 0.431400001 0.2016 -0.879400015 +vn 0.397100002 0.338999987 -0.852900028 +vn 0.223199993 0.333600014 -0.915899992 +vn 0.174999997 0.498199999 -0.84920001 +vn -0.0460000001 0.448199987 -0.892700016 +vn -0.0606999993 0.487199992 -0.871200025 +vn 0.0335000008 0.494700015 -0.868399978 +vn -0.122900002 0.585200012 -0.801500022 +vn -0.157900006 0.557699978 -0.814899981 +vn -0.159500003 0.523699999 -0.836899996 +vn -0.0960000008 0.570800006 -0.815500021 +vn -0.0935999975 0.520299971 -0.848800004 +vn -0.164399996 0.524399996 -0.835399985 +vn -0.161400005 0.467400014 -0.869199991 +vn -0.0669 0.4991 -0.863900006 +vn 0.105099998 0.477999985 -0.871999979 +vn 0.0923999995 0.263099998 -0.960300028 +vn 0.184900001 0.197899997 -0.962599993 +vn 0.108800001 0.0697999969 -0.991599977 +vn 0.245499998 0.0109000001 -0.969299972 +vn 0.25029999 -0.0970000029 -0.96329999 +vn 0.353100002 -0.265899986 -0.897000015 +vn 0.133100003 -0.418500006 -0.898400009 +vn 0.164700001 -0.468199998 -0.868099988 +vn 0.228400007 -0.386799991 -0.893400013 +vn 0.242300004 -0.471799999 -0.847800016 +vn 0.310099989 -0.519900024 -0.796000004 +vn 0.154400006 -0.370599985 -0.915899992 +vn 0.222200006 -0.484899998 -0.845899999 +vn 0.192000002 -0.490500003 -0.850000024 +vn 0.190099999 -0.493999988 -0.848399997 +vn 0.223900005 -0.384900004 -0.895399988 +vn 0.208900005 -0.340900004 -0.916599989 +vn 0.293900013 -0.328399986 -0.897700012 +vn 0.417400002 -0.256099999 -0.871900022 +vn 0.316799998 -0.0766000003 -0.9454 +vn 0.504499972 -0.00609999988 -0.863399982 +vn 0.400099993 0.193299994 -0.895900011 +vn 0.433200002 0.218899995 -0.874300003 +vn 0.537100017 0.139699996 -0.831900001 +vn 0.514299989 0.31310001 -0.798399985 +vn 0.497500002 0.309500009 -0.810400009 +vn 0.512300014 0.293099999 -0.807299972 +vn 0.450300008 0.256799996 -0.855099976 +vn 0.451299995 0.308999985 -0.837100029 +vn 0.398299992 0.292199999 -0.869499981 +vn 0.378199995 0.228400007 -0.897099972 +vn 0.478300005 0.0406999998 -0.877300024 +vn 0.289799988 -0.0184000004 -0.956900001 +vn 0.222599998 -0.0966999978 -0.970099986 +vn 0.182699993 -0.216900006 -0.958899975 +vn 0.0511000007 -0.207000002 -0.976999998 +vn -0.0526000001 -0.239399999 -0.969500005 +vn -0.195299998 -0.394400001 -0.897899985 +vn -0.404700011 -0.222900003 -0.886900008 +vn -0.453399986 -0.294099987 -0.841400027 +vn -0.353799999 -0.349099994 -0.867699981 +vn -0.408199996 -0.352800012 -0.842000008 +vn -0.441100001 -0.347799987 -0.827300012 +vn -0.373400003 -0.3116 -0.87379998 +vn -0.448300004 -0.299699992 -0.842199981 +vn -0.459399998 -0.3046 -0.834399998 +vn -0.290300012 -0.281500012 -0.914600015 +vn -0.282700002 -0.222800002 -0.933000028 +vn -0.251199991 -0.36649999 -0.895900011 +vn -0.136199996 -0.453500003 -0.880800009 +vn 0.00340000005 -0.298799992 -0.954299986 +vn 0.142800003 -0.453299999 -0.879800022 +vn 0.295300007 -0.281199992 -0.913100004 +vn 0.331900001 -0.305999994 -0.89230001 +vn 0.254200011 -0.358500004 -0.898299992 +vn 0.314099997 -0.0736000016 -0.946500003 +vn 0.34709999 -0.0430000015 -0.936800003 +vn 0.321200013 -0.0241 -0.946699977 +vn 0.278499991 -0.0822999999 -0.956900001 +vn 0.243200004 -0.0529999994 -0.968500018 +vn 0.211400002 -0.0851000026 -0.973699987 +vn 0.310799986 -0.0966000035 -0.945599973 +vn 0.120700002 -0.00779999979 -0.992699981 +vn 0.331200004 -0.00800000038 -0.943499982 +vn 0.335900009 0.00249999994 -0.941900015 +vn -0.050999999 0.0734999999 -0.995999992 +vn -0.0671000034 0.138899997 -0.987999976 +vn 0.0542000011 0.0027999999 -0.99849999 +vn -0.179000005 0.165800005 -0.969799995 +vn -0.157600001 0.288899988 -0.944299996 +vn -0.0571000017 0.310699999 -0.948800027 +vn -0.177900001 0.245900005 -0.952799976 +vn -0.195800006 0.320499986 -0.926800013 +vn -0.065700002 0.302399993 -0.950900018 +vn -0.140100002 0.304199994 -0.942300022 +vn -0.171900004 0.308899999 -0.935400009 +vn -0.150700003 0.300900012 -0.941699982 +vn -0.1162 0.297800004 -0.94749999 +vn -0.195899993 0.343199998 -0.918600023 +vn -0.127900004 0.340000004 -0.931699991 +vn -0.0370000005 0.314300001 -0.948599994 +vn -0.119400002 0.416700006 -0.901199996 +vn -0.0476999991 0.408499986 -0.911499977 +vn -0.0562999994 0.411199987 -0.909799993 +vn -0.0772999972 0.419400007 -0.904500008 +vn -0.0794999972 0.405699998 -0.91049999 +vn -0.0248000007 0.335799992 -0.941600025 +vn -0.152999997 0.410299987 -0.898999989 +vn -0.127200007 0.430799991 -0.893400013 +vn -0.148300007 0.367900014 -0.917999983 +vn -0.169499993 0.30340001 -0.937699974 +vn -0.146699995 0.291999996 -0.945100009 +vn -0.229900002 0.317000002 -0.920099974 +vn -0.322600007 0.249799997 -0.912999988 +vn -0.350499988 0.0969000012 -0.931500018 +vn -0.311300009 -0.0168999992 -0.950200021 +vn -0.480800003 -0.00419999985 -0.876800001 +vn -0.330799997 -0.145500004 -0.932399988 +vn -0.200399995 -0.182999998 -0.962499976 +vn -0.140499994 -0.440400004 -0.886699975 +vn -0.0948999971 -0.262699991 -0.960200012 +vn -0.00689999992 -0.309199989 -0.950999975 +vn 0.0401999988 -0.349900007 -0.935899973 +vn 0.0375999995 -0.271800011 -0.961600006 +vn 0.1039 -0.377700001 -0.920099974 +vn 0.123199999 -0.285899997 -0.950299978 +vn 0.162499994 -0.331200004 -0.929400027 +vn 0.166999996 -0.247199997 -0.95450002 +vn 0.0812999979 -0.340799987 -0.936600029 +vn 0.185399994 -0.355599999 -0.916100025 +vn 0.189799994 -0.2685 -0.944400012 +vn 0.167099997 -0.277099997 -0.946200013 +vn 0.138600007 -0.285600007 -0.948300004 +vn 0.109099999 -0.289400011 -0.950999975 +vn 0.0577999987 -0.324099988 -0.944299996 +vn 0.124200001 -0.215200007 -0.968599975 +vn 0.0741999969 -0.324000001 -0.943099976 +vn 0.0810000002 -0.281800002 -0.95599997 +vn 0.128199995 -0.222200006 -0.966499984 +vn 0.0489999987 -0.308999985 -0.949800014 +vn -0.0454000011 -0.179399997 -0.98269999 +vn 0.00219999999 -0.0936999992 -0.995599985 +vn 0.00609999988 -0.238999993 -0.971000016 +vn 0.0471000001 -0.264999986 -0.963100016 +vn 0.0838999972 -0.262899995 -0.961199999 +vn 0.0285999998 0.0816000029 -0.996299982 +vn 0.138400003 0.0834999979 -0.986800015 +vn -0.0166999996 0.0381999984 -0.999100029 +vn -0.00810000021 -0.0256999992 -0.999599993 +vn 0.0991000012 0.244499996 -0.964600027 +vn 0.245000005 0.250999987 -0.936500013 +vn 0.232800007 0.256099999 -0.938199997 +vn 0.209199995 0.225500003 -0.951499999 +vn 0.214499995 0.288700014 -0.933099985 +vn 0.199300006 0.195500001 -0.960300028 +vn 0.155000001 0.281100005 -0.947099984 +vn 0.143199995 0.240099996 -0.960099995 +vn 0.227899998 0.2861 -0.930700004 +vn -0.0406000018 0.217899993 -0.975099981 +vn -0.0306000002 0.0793000013 -0.996399999 +vn 0.0425000004 0.237200007 -0.970499992 +vn 0.0953999981 0.231000006 -0.968299985 +vn 0.1074 0.240500003 -0.964699984 +vn -0.160300002 0.132699996 -0.978100002 +vn -0.217199996 0.00800000038 -0.976100028 +vn -0.151600003 -0.0373000018 -0.987699986 +vn -0.206 0.0606999993 -0.976700008 +vn -0.318599999 -0.0864000022 -0.944000006 +vn -0.3116 -0.0559 -0.948599994 +vn -0.325599998 -0.0967999995 -0.940500021 +vn -0.286599994 -0.0305000003 -0.957599998 +vn -0.265899986 -0.0216000006 -0.963699996 +vn -0.241400003 0.00609999988 -0.970399976 +vn -0.0929000005 -0.0816999972 -0.992299974 +vn -0.258700013 -0.103 -0.960500002 +vn -0.305700004 -0.107600003 -0.94599998 +vn -0.2755 -0.138600007 -0.951200008 +vn -0.0549999997 0.0219999999 -0.998199999 +vn 0.0649999976 0.0210999995 -0.997699976 +vn 0.120899998 -0.0296 -0.992200017 +vn 0.00359999994 0.0516999997 -0.998700023 +vn 0.245399997 0.0057000001 -0.969399989 +vn 0.282499999 -0.138600007 -0.949199975 +vn 0.312599987 -0.107600003 -0.943799973 +vn 0.265599996 -0.103 -0.958599985 +vn 0.317799985 -0.0553000011 -0.946500003 +vn 0.247500002 -0.0348000005 -0.968299985 +vn 0.254299998 -0.0491999984 -0.965900004 +vn 0.325399995 -0.0864000022 -0.941600025 +vn 0.332500011 -0.0967999995 -0.93809998 +vn 0.168300003 0.128800005 -0.977299988 +vn 0.0672999993 0.120200001 -0.990499973 +vn 0.212899998 0.0606000014 -0.975199997 +vn 0.224399999 0.00899999961 -0.9745 +vn 0.231999993 -0.00310000009 -0.9727 +vn 0.0434999987 0.217500001 -0.975099981 +vn -0.102700002 0.232700005 -0.967100024 +vn -0.0885000005 0.231099993 -0.968900025 +vn -0.0357000008 0.237299994 -0.970799983 +vn -0.179800004 0.294200003 -0.93870002 +vn -0.222200006 0.28670001 -0.931900024 +vn -0.215100005 0.252900004 -0.943300009 +vn -0.147400007 0.280800015 -0.948400021 +vn -0.136700004 0.238999993 -0.961399972 +vn -0.0944999978 0.245499998 -0.9648 +vn -0.133100003 0.0842999965 -0.987500012 +vn -0.204400003 0.225899994 -0.952499986 +vn -0.227699995 0.256500006 -0.939300001 +vn -0.239700004 0.251399994 -0.937699974 +vn -0.0286999997 0.0852999985 -0.995899975 +vn 0.00789999962 -0.0281000007 -0.999599993 +vn -0.0203000009 -0.0899000019 -0.995700002 +vn 0.0513000004 -0.1919 -0.980099976 +vn -0.0610000007 -0.288399994 -0.955600023 +vn -0.0480999984 -0.284999996 -0.957300007 +vn -0.0472000018 -0.245800003 -0.968200028 +vn 0.0284000002 -0.259799987 -0.965200007 +vn -0.0151000004 -0.190699995 -0.98150003 +vn 0.0114000002 -0.289600015 -0.957099974 +vn -0.0151000004 -0.303000003 -0.952899992 +vn -0.0264999997 -0.307500005 -0.951200008 +vn 0.148300007 -0.0957000032 -0.984300017 +vn 0.0590999983 0.0120999999 -0.998199999 +vn 0.0922999978 -0.159700006 -0.982800007 +vn 0.0460000001 -0.185399994 -0.981599987 +vn 0.0353999995 -0.194800004 -0.980199993 +vn 0.198899999 0.0436999984 -0.978999972 +vn 0.29519999 0.134200007 -0.94599998 +vn 0.0820000023 0.122599997 -0.989099979 +vn 0.168799996 0.171800002 -0.970600009 +vn 0.167699993 0.188199997 -0.967700005 +vn 0.201800004 0.298500001 -0.932799995 +vn 0.211799994 0.273299992 -0.938300014 +vn 0.207699999 0.309300005 -0.927999973 +vn 0.210600004 0.241799995 -0.9472 +vn 0.1646 0.220899999 -0.961300015 +vn 0.167500004 0.305599988 -0.937300026 +vn 0.112300001 0.278200001 -0.95389998 +vn 0.1928 0.172900006 -0.965900004 +vn 0.0715999976 0.138300002 -0.987800002 +vn 0.161400005 0.314799994 -0.935299993 +vn 0.0555999987 0.0681999996 -0.996100008 +vn -0.0445000008 0.00439999998 -0.999000013 +vn -0.120899998 0.0166999996 -0.992500007 +vn 0.0238000005 0.0109999999 -0.99970001 +vn -0.204600006 -0.0806000009 -0.975499988 +vn -0.334899992 -0.00480000023 -0.942300022 +vn -0.325300008 -0.0125000002 -0.945500016 +vn -0.288300008 -0.00659999996 -0.957499981 +vn -0.272000015 -0.0820000023 -0.958800018 +vn -0.307900012 -0.0731000006 -0.948599994 +vn -0.236699998 -0.0529999994 -0.970099986 +vn -0.315400004 -0.0234999992 -0.948700011 +vn -0.342799991 -0.0452999994 -0.938300014 +vn -0.192900002 -0.0755999982 -0.978299975 +vn -0.165700004 -0.000699999975 -0.986199975 +vn -0.0715000033 -0.144400001 -0.986899972 +vn 0.00359999994 -0.0253999997 -0.99970001 +vn -0.152199998 -0.112199999 -0.981999993 +vn 0.0785000026 -0.144400001 -0.986400008 +vn 0.213100001 -0.0719000027 -0.974399984 +vn 0.199300006 -0.076700002 -0.976899981 +vn 0.158999994 -0.112400003 -0.98089999 +vn 0.230700001 0.0697999969 -0.970499992 +vn 0.261299998 0.113899998 -0.958500028 +vn 0.243699998 0.110600002 -0.963500023 +vn 0.189999998 0.068400003 -0.979399979 +vn 0.177000001 0.0604999997 -0.9824 +vn 0.276800007 0.119800001 -0.953400016 +vn 0.2597 0.103799999 -0.960099995 +vn 0.194299996 0.264499992 -0.944599986 +vn 0.240400001 0.168899998 -0.955900013 +vn 0.208199993 0.1149 -0.971300006 +vn 0.1602 0.154400006 -0.974900007 +vn 0.113799997 0.137700006 -0.983900011 +vn 0.0610999987 0.122299999 -0.99059999 +vn 0.0281000007 0.205200002 -0.978299975 +vn 0.0493000001 0.173800007 -0.983500004 +vn -0.00930000003 0.298200011 -0.954400003 +vn 0.0394000001 0.305099994 -0.951499999 +vn -0.0263 0.272399992 -0.961799979 +vn -0.00439999998 0.235300004 -0.971899986 +vn 0.0306000002 0.292100012 -0.955900013 +vn 0.0307 0.283300012 -0.958500028 +vn -0.00659999996 0.277500004 -0.960699975 +vn -0.0122999996 0.273600012 -0.961799979 +vn -0.0274 0.308499992 -0.950800002 +vn -0 0.272700012 -0.962100029 +vn 0.0438000001 0.256900012 -0.965399981 +vn -0.0311999992 0.278299987 -0.959999979 +vn -0.000600000028 0.261400014 -0.965200007 +vn -0.0137999998 0.264299989 -0.964299977 +vn 0.0120000001 0.300900012 -0.953599989 +vn 0.0231999997 0.300399989 -0.953499973 +vn 0.0318000019 0.285100013 -0.958000004 +vn 0.0118000004 0.316100001 -0.948599994 +vn -0.0110999998 0.282700002 -0.959200025 +vn 0.0254999995 0.250099987 -0.967899978 +vn -0.055399999 0.293300003 -0.954400003 +vn -0.0936999992 0.258300006 -0.961499989 +vn -0.079400003 0.262199998 -0.961799979 +vn -0.114200003 0.198599994 -0.973399997 +vn -0.0952000022 0.195600003 -0.976000011 +vn -0.160699993 0.208399996 -0.9648 +vn -0.245399997 0.0966000035 -0.964600027 +vn -0.214699998 0.160600007 -0.963400006 +vn -0.241600007 0.0406999998 -0.969500005 +vn -0.216900006 0.0194000006 -0.976000011 +vn -0.226799995 -0.0313000008 -0.973399997 +vn -0.2359 -0.0582999997 -0.970000029 +vn -0.203999996 -0.108599998 -0.972899973 +vn -0.166199997 -0.105099998 -0.980499983 +vn -0.136000007 -0.142700002 -0.980400026 +vn -0.117799997 -0.145899996 -0.982299984 +vn -0.0631999969 -0.188299999 -0.980099976 +vn -0.0172000006 -0.184300005 -0.98269999 +vn 0.0161000006 -0.215100005 -0.976499975 +vn 0.0227000006 -0.166199997 -0.985800028 +vn 0.0763000026 -0.211400002 -0.974399984 +vn 0.104099996 -0.1602 -0.981599987 +vn 0.144700006 -0.188899994 -0.971300006 +vn 0.156399995 -0.141100004 -0.977599978 +vn 0.0606999993 -0.215900004 -0.9745 +vn 0.184 -0.154499993 -0.970700026 +vn 0.192699999 -0.131999999 -0.972299993 +vn 0.183300003 -0.157100007 -0.970399976 +vn 0.187700003 -0.151600003 -0.970499992 +vn 0.173600003 -0.171800002 -0.969699979 +vn 0.173099995 -0.154300004 -0.9727 +vn 0.173800007 -0.146799996 -0.973800004 +vn 0.154599994 -0.181899995 -0.971099973 +vn 0.181899995 -0.136099994 -0.973800004 +vn 0.172199994 -0.162599996 -0.971599996 +vn 0.189899996 -0.1417 -0.971499979 +vn 0.178200006 -0.182500005 -0.966899991 +vn 0.171700001 -0.196799994 -0.965300024 +vn 0.194800004 -0.163499996 -0.967100024 +vn 0.167600006 -0.182899997 -0.968800008 +vn 0.197699994 -0.171499997 -0.96509999 +vn 0.138600007 -0.133300006 -0.981299996 +vn 0.168899998 -0.0851000026 -0.981999993 +vn 0.138899997 -0.170599997 -0.975499988 +vn 0.140499994 -0.0476000011 -0.988900006 +vn 0.160600007 0.0141000003 -0.986899972 +vn 0.215800002 0.0187999997 -0.976300001 +vn 0.1294 -0.00960000046 -0.99150002 +vn 0.216800004 0.0851000026 -0.972500026 +vn 0.270099998 0.074000001 -0.959999979 +vn 0.279799998 0.0684999973 -0.957599998 +vn 0.271800011 0.120499998 -0.95480001 +vn 0.238900006 0.115900002 -0.964100003 +vn 0.212699994 0.066200003 -0.974900007 +vn 0.174500003 0.1087 -0.978600025 +vn 0.158299997 0.108900003 -0.981400013 +vn 0.256799996 0.116899997 -0.959399998 +vn 0.112800002 0.0953999981 -0.989000022 +vn 0.131600007 0.0644000024 -0.989199996 +vn 0.0281000007 0.0694999993 -0.997200012 +vn 0.0208999999 0.00829999987 -0.99970001 +vn 0.0760999992 0.107299998 -0.991299987 +vn -0.0397000015 0.0211999994 -0.999000013 +vn -0.0926000029 -0.0504000001 -0.994400024 +vn -0.0553000011 -0.0848999992 -0.994899988 +vn -0.0917999968 -0.0119000003 -0.995700002 +vn -0.1611 -0.126800001 -0.978699982 +vn -0.165900007 -0.180500001 -0.969500005 +vn -0.158800006 -0.164199993 -0.97359997 +vn -0.127299994 -0.103200004 -0.986500025 +vn -0.129999995 -0.0863000005 -0.987699986 +vn -0.152899995 -0.206499994 -0.966400027 +vn -0.130600005 -0.217500001 -0.967299998 +vn -0.1294 -0.208499998 -0.969399989 +vn -0.121799998 -0.155399993 -0.980300009 +vn -0.0544000007 -0.175400004 -0.98299998 +vn -0.0307999998 -0.124799997 -0.991699994 +vn 0.00359999994 -0.102600001 -0.994700015 +vn 0.0806000009 -0.135700002 -0.987500012 +vn 0.0439000018 -0.120999999 -0.991699994 +vn 0.127100006 -0.149100006 -0.980599999 +vn 0.136399999 -0.208499998 -0.968500018 +vn 0.137199998 -0.217700005 -0.966300011 +vn 0.159899995 -0.206499994 -0.965300024 +vn 0.167799994 -0.126599997 -0.977699995 +vn 0.127100006 -0.0907000005 -0.987699986 +vn 0.125300005 -0.1074 -0.986299992 +vn 0.165800005 -0.164199993 -0.97240001 +vn 0.172900006 -0.180500001 -0.968299985 +vn 0.0997999981 -0.0504000001 -0.993700027 +vn 0.0759999976 -0.0784000009 -0.994000018 +vn 0.0989999995 -0.0120000001 -0.995000005 +vn 0.0140000004 0.0465000011 -0.99879998 +vn 0.0573999994 0.0225000009 -0.998099983 +vn -0.105800003 0.0954999998 -0.989799976 +vn -0.124700002 0.064000003 -0.990100026 +vn -0.0691 0.1074 -0.99180001 +vn -0.023 0.0795999989 -0.996599972 +vn -0.1998 0.132100001 -0.970899999 +vn -0.250099987 0.115900002 -0.961300015 +vn -0.234599993 0.107299998 -0.966099977 +vn -0.166700006 0.1096 -0.979900002 +vn -0.151500002 0.108000003 -0.982500017 +vn -0.266200006 0.120499998 -0.95630002 +vn -0.274599999 0.0649999976 -0.959399998 +vn -0.264699996 0.0744000003 -0.961499989 +vn -0.211500004 0.0856000036 -0.97359997 +vn -0.210199997 0.0193000007 -0.977500021 +vn -0.155000001 0.0152000003 -0.987800002 +vn -0.106399998 -0.00209999993 -0.994300008 +vn -0.133100003 -0.0868000016 -0.987299979 +vn -0.129600003 -0.0496999994 -0.9903 +vn -0.131999999 -0.132799998 -0.982299984 +vn -0.186700001 -0.160099998 -0.969299972 +vn -0.197099999 -0.162200004 -0.966899991 +vn -0.160500005 -0.198599994 -0.966899991 +vn -0.101199999 -0.169100001 -0.980400026 +vn -0.0832000002 -0.113200001 -0.990100026 +vn -0.0885000005 -0.131400004 -0.987399995 +vn -0.136999995 -0.176799998 -0.974699974 +vn -0.150999993 -0.186700001 -0.970799983 +vn -0.0478000008 -0.0842999965 -0.995299995 +vn -0.0816000029 -0.0697000027 -0.994199991 +vn 0.0057000001 -0.0137 -0.999899983 +vn -0.0242999997 0.0401999988 -0.998899996 +vn -0.0110999998 -0.0725999996 -0.997300029 +vn 0.0280000009 0.068400003 -0.997300029 +vn -0.00639999984 0.115800001 -0.993300021 +vn 0.0315000005 0.150999993 -0.987999976 +vn -0.00190000003 0.161300004 -0.986899972 +vn 0.0469999984 0.249300003 -0.967299998 +vn 0.0226000007 0.292400002 -0.95599997 +vn 0.0249000005 0.27669999 -0.960600019 +vn 0.0320999995 0.213100001 -0.976499975 +vn 0.0335999988 0.198500007 -0.979499996 +vn 0.0233999994 0.307300001 -0.951300025 +vn -0.0405000001 0.300399989 -0.952899992 +vn -0.0253999997 0.294 -0.955500007 +vn 0.00689999992 0.239700004 -0.970799983 +vn -0.0231999997 0.204999998 -0.978500009 +vn -0.0661000013 0.137999997 -0.988200009 +vn -0.149000004 0.1259 -0.980799973 +vn -0.110299997 0.132599995 -0.985000014 +vn -0.0439999998 0.173999995 -0.983799994 +vn -0.196099997 0.112599999 -0.974099994 +vn -0.239600003 0.157000005 -0.958100021 +vn -0.245900005 0.1646 -0.955200016 +vn -0.272300005 0.118900001 -0.95480001 +vn -0.224399999 0.0702000037 -0.972000003 +vn -0.170399994 0.0604000017 -0.983500004 +vn -0.183400005 0.068599999 -0.980599999 +vn -0.2377 0.111199997 -0.964999974 +vn -0.256099999 0.112899996 -0.959999979 +vn -0.124600001 0.0441999994 -0.99119997 +vn -0.113899998 0.0939999968 -0.989000022 +vn -0.0386999995 0.0165999997 -0.999100029 +vn 0.00359999994 0.061900001 -0.998099983 +vn -0.101300001 0.0132999998 -0.994799972 +vn 0.0458000004 0.0165999997 -0.99879998 +vn 0.131500006 0.0441999994 -0.9903 +vn 0.128299996 0.0807999969 -0.988399982 +vn 0.1083 0.0132999998 -0.994000018 +vn -0.0124000004 0.228100002 -0.973500013 +vn -0.00689999992 0.219799995 -0.975499988 +vn -0.0218000002 0.231800005 -0.972500026 +vn -0.00109999999 0.227699995 -0.973699987 +vn 0.00350000011 0.2245 -0.9745 +vn 0.00710000005 0.222299993 -0.975000024 +vn 0.0297999997 0.235200003 -0.971499979 +vn -0.127299994 0.280600011 -0.951399982 +vn -0.123400003 0.314700007 -0.941100001 +vn -0.143700004 0.31400001 -0.938499987 +vn -0.1074 0.293500006 -0.949899971 +vn -0.0952000022 0.276600003 -0.95630002 +vn -0.0978000015 0.280699998 -0.95480001 +vn -0.0904999971 0.260600001 -0.961199999 +vn -0.0737999976 0.263799995 -0.961799979 +vn -0.0706999972 0.244499996 -0.967100024 +vn -0.0478999987 0.275099993 -0.960200012 +vn -0.0535000004 0.238100007 -0.969799995 +vn -0.185699999 0.321200013 -0.928600013 +vn -0.179900005 0.32069999 -0.929899991 +vn -0.201199993 0.326799989 -0.923399985 +vn -0.192399994 0.321099997 -0.927299976 +vn -0.200200006 0.315800011 -0.92750001 +vn -0.192599997 0.336600006 -0.921700001 +vn -0.205300003 0.3037 -0.930400014 +vn -0.202700004 0.310699999 -0.92869997 +vn -0.200499997 0.309399992 -0.9296 +vn -0.210299999 0.288700014 -0.934099972 +vn -0.206400007 0.275900006 -0.938799977 +vn -0.211999997 0.26730001 -0.939999998 +vn -0.199900001 0.185900003 -0.962000012 +vn -0.188500002 0.172900006 -0.966700017 +vn -0.193800002 0.163499996 -0.967299998 +vn -0.123099998 0.221900001 -0.967299998 +vn -0.243000001 0.188099995 -0.951600015 +vn -0.237200007 0.211899996 -0.948099971 +vn -0.211999997 0.241999999 -0.946799994 +vn -0.186700001 0.1206 -0.975000024 +vn -0.186199993 0.142700002 -0.972100019 +vn -0.177499995 0.1065 -0.978299975 +vn -0.1787 0.0988000035 -0.978900015 +vn -0.182500005 0.0921000019 -0.978900015 +vn -0.183300003 0.0886000022 -0.978999972 +vn -0.185399994 0.0755999982 -0.979799986 +vn -0.240500003 0.0361000001 -0.970000029 +vn -0.216299996 0.057599999 -0.974600017 +vn -0.289400011 -0.0104 -0.957199991 +vn -0.299699992 -0.0208999999 -0.953800023 +vn -0.31400001 -0.0278999992 -0.949000001 +vn -0.25850001 -0.0155999996 -0.965900004 +vn -0.279599994 -0.00529999984 -0.960099995 +vn -0.263700008 0.0164000001 -0.96450001 +vn -0.209700003 0.0627000034 -0.975799978 +vn -0.193000004 0.0676999986 -0.978900015 +vn -0.333000004 -0.0544999987 -0.941299975 +vn -0.331800014 -0.0681999996 -0.940900028 +vn -0.312900007 -0.0401000008 -0.948899984 +vn -0.326900005 -0.0463999994 -0.943899989 +vn -0.333400011 -0.0852999985 -0.938899994 +vn -0.325899988 -0.0817999989 -0.941799998 +vn -0.324200004 -0.0904999971 -0.941600025 +vn -0.326400012 -0.0829999968 -0.941600025 +vn -0.334300011 -0.0788000003 -0.939199984 +vn -0.328900009 -0.0759000033 -0.941299975 +vn -0.35800001 -0.0734999999 -0.930800021 +vn -0.309199989 -0.0942000002 -0.94630003 +vn -0.314700007 -0.0773999989 -0.94599998 +vn -0.294400007 -0.0949999988 -0.950999975 +vn -0.286000013 -0.104099996 -0.952600002 +vn -0.192000002 -0.0934000015 -0.976899981 +vn -0.169200003 -0.138300002 -0.975799978 +vn -0.155300006 -0.1162 -0.981000006 +vn -0.210600004 -0.148800001 -0.966199994 +vn -0.229100004 -0.111000001 -0.967100024 +vn -0.209700003 -0.0261000004 -0.977400005 +vn -0.227200001 -0.125799999 -0.965699971 +vn -0.245900005 -0.150199994 -0.957599998 +vn -0.261599988 -0.106799997 -0.959200025 +vn -0.278400004 -0.0957999974 -0.95569998 +vn -0.1237 -0.120099999 -0.985000014 +vn -0.114799999 -0.122299999 -0.985800028 +vn -0.103799999 -0.122100003 -0.987100005 +vn -0.105700001 -0.130999997 -0.985700011 +vn -0.110600002 -0.126800001 -0.985700011 +vn -0.100400001 -0.136800006 -0.985499978 +vn -0.0961000025 -0.1479 -0.984300017 +vn -0.0857999995 -0.167099997 -0.982200027 +vn -0.0427000001 -0.255499989 -0.965900004 +vn -0.0441999994 -0.240099996 -0.969699979 +vn -0.0417999998 -0.26699999 -0.962800026 +vn -0.0614 -0.225400001 -0.972299993 +vn -0.0750999972 -0.216700003 -0.97329998 +vn -0.114699997 -0.212599993 -0.970399976 +vn -0.0844999999 -0.181700006 -0.979700029 +vn -0.0625 -0.192100003 -0.979399979 +vn -0.0737000033 -0.190699995 -0.978900015 +vn -0.0386000015 -0.286900014 -0.957199991 +vn -0.0383000001 -0.294200003 -0.954999983 +vn -0.0115 -0.313899994 -0.949400008 +vn -0.0057000001 -0.32280001 -0.946500003 +vn -0.0175999999 -0.330199987 -0.943700016 +vn -0.0219000001 -0.297500014 -0.95450002 +vn -0.0201999992 -0.306600004 -0.951600015 +vn 0.00410000002 -0.325599998 -0.945500016 +vn -0.00639999984 -0.349299997 -0.936999977 +vn 0.0115999999 -0.323799998 -0.94599998 +vn 0.0156999994 -0.317400008 -0.948099971 +vn 0.0113000004 -0.332100004 -0.943199992 +vn 0.0342000015 -0.297699988 -0.954100013 +vn 0.0326000005 -0.283399999 -0.958500028 +vn -0.00100000005 -0.300999999 -0.953599989 +vn 0.0647 -0.2227 -0.9727 +vn 0.0725999996 -0.238100007 -0.968500018 +vn 0.108900003 -0.226699993 -0.967800021 +vn 0.0751999989 -0.195899993 -0.977699995 +vn 0.0658999979 -0.244599998 -0.967400014 +vn 0.0421000011 -0.245199993 -0.968599975 +vn 0.0593000017 -0.279100001 -0.958400011 +vn 0.0562000014 -0.263099998 -0.963100016 +vn 0.0465999991 -0.287900001 -0.956499994 +vn 0.0943000019 -0.151700005 -0.983900011 +vn 0.0998999998 -0.147 -0.984099984 +vn 0.0991000012 -0.170200005 -0.980400026 +vn 0.103399999 -0.132300004 -0.985800028 +vn 0.0684999973 -0.224199995 -0.972100019 +vn 0.112800002 -0.126800001 -0.985499978 +vn 0.117600001 -0.127299994 -0.984899998 +vn 0.123899996 -0.125200003 -0.984399974 +vn 0.130500004 -0.122100003 -0.983900011 +vn 0.144099995 -0.122100003 -0.981999993 +vn 0.164399996 -0.130400002 -0.977699995 +vn 0.25029999 -0.189300001 -0.949500024 +vn 0.216700003 -0.0912000015 -0.972000003 +vn 0.251800001 -0.108000003 -0.961700022 +vn 0.265300006 -0.101000004 -0.958899975 +vn 0.235200003 -0.0886000022 -0.967899978 +vn 0.211600006 -0.130799994 -0.968599975 +vn 0.191799998 -0.0617999993 -0.979499996 +vn 0.303200006 -0.0961000025 -0.948099971 +vn 0.314799994 -0.0984999985 -0.944000006 +vn 0.293099999 -0.100299999 -0.950800002 +vn 0.279000014 -0.105999999 -0.954400003 +vn 0.334899992 -0.0877000019 -0.938199997 +vn 0.331099987 -0.0898000002 -0.939300001 +vn 0.326299995 -0.0939000025 -0.940599978 +vn 0.3398 -0.0851999968 -0.936600029 +vn 0.341899991 -0.0803000033 -0.93629998 +vn 0.340799987 -0.0697999969 -0.9375 +vn 0.331800014 -0.0746000037 -0.940400004 +vn 0.341100007 -0.0656000003 -0.937699974 +vn 0.317600012 -0.0384999998 -0.947399974 +vn 0.319499999 -0.0315999985 -0.947099984 +vn 0.328000009 -0.0483000018 -0.943400025 +vn 0.278800011 -0.00329999998 -0.960300028 +vn 0.296700001 -0.00779999979 -0.954900026 +vn 0.296299994 -0.0855000019 -0.951300025 +vn 0.255299985 0.0121999998 -0.966799974 +vn 0.265100002 0.00419999985 -0.96420002 +vn 0.240799993 0.0199999996 -0.970399976 +vn 0.229900002 0.0251000002 -0.972899973 +vn 0.170599997 0.0083999997 -0.985300004 +vn 0.185200006 0.0592 -0.98089999 +vn 0.101599999 0.576600015 -0.810699999 +vn 0.0496999994 0.00100000005 -0.99879998 +vn 0.0838000029 -0.00860000029 -0.996399999 +vn 0.0344999991 0.0130000003 -0.999300003 +vn 0.00860000029 0.0055999998 -0.999899983 +vn 0.0617000014 0.218199998 -0.973999977 +vn 0.0653999969 0.0171000008 -0.997699976 +vn 0.106799997 0.0335000008 -0.993700027 +vn 0.141800001 -0.0604999997 -0.987999976 +vn -0.180099994 0.347600013 0.92019999 +vn -0.180099994 0.349099994 0.91960001 +vn 0.184799999 -0.471700013 -0.862200022 +vn 0.157800004 0.0252999999 -0.987200022 +vn -0.167300001 0.0970000029 0.981100023 +vn -0.173299998 0.178399995 0.968599975 +vn 0.169200003 0.109399997 -0.979499996 +vn -0.180299997 0.2764 0.944000006 +vn -0.0270000007 -0.00639999984 -0.999599993 +vn -0.0309999995 0.00150000001 -0.999499977 +vn -0.0116999997 -0.00549999997 -0.999899983 +vn 0.0551999994 0.0331999995 -0.997900009 +vn -0.0379999988 0.00340000005 -0.999300003 +vn -0.0441999994 0.0281000007 -0.998600006 +vn -0.0226000007 0.0430000015 -0.99879998 +vn -0.0353000015 0.0362000018 -0.998700023 +vn -0.00579999993 0.0454999991 -0.998899996 +vn -0.0467999987 0.0456000008 -0.997900009 +vn -0.0377999991 0.0286999997 -0.998899996 +vn -0.0392999984 0.0239000004 -0.998899996 +vn -0.0196000002 0.0299999993 -0.99940002 +vn 0.0785999969 0.117799997 -0.989899993 +vn 0.0773999989 0.0764999986 -0.994099975 +vn 0.114 0.0922000036 -0.989199996 +vn 0.1193 0.109899998 -0.986699998 +vn 0.0476999991 0.0931999981 -0.994499981 +vn 0.0263 0.0588999987 -0.997900009 +vn 0.135499999 0.134900004 -0.981599987 +vn 0.129999995 0.114600003 -0.984899998 +vn 0.169300005 0.145799994 -0.974699974 +vn 0.156800002 0.135800004 -0.978200018 +vn 0.143399999 0.126100004 -0.981599987 +vn 0.179800004 0.170399994 -0.968800008 +vn 0.195899993 0.190599993 -0.961899996 +vn 0.207699999 0.205599993 -0.95630002 +vn 0.1831 0.178900003 -0.966700017 +vn 0.205699995 0.226300001 -0.952099979 +vn 0.208700001 0.2456 -0.946699977 +vn 0.213200003 0.264699996 -0.940500021 +vn 0.214100003 0.281100005 -0.935500026 +vn 0.214100003 0.303200006 -0.928600013 +vn 0.209000006 0.294800013 -0.932399988 +vn 0.206400007 0.310699999 -0.9278 +vn 0.223499998 0.307999998 -0.924799979 +vn 0.2183 0.29370001 -0.930599988 +vn 0.201100007 0.318399996 -0.926400006 +vn 0.194299996 0.321399987 -0.926800013 +vn 0.203600004 0.331499994 -0.921199977 +vn 0.225899994 0.337799996 -0.913699985 +vn 0.184300005 0.320600003 -0.929099977 +vn 0.188800007 0.328799993 -0.925300002 +vn 0.174899995 0.319499999 -0.931299984 +vn 0.0771000013 0.254200011 -0.964100003 +vn 0.0878000036 0.294699997 -0.951600015 +f 1763/1/1 1774/2/1 1764/3/1 +f 1763/1/2 1775/4/2 1776/5/2 +f 1763/1/3 1776/5/3 1774/2/3 +f 1777/6/4 1775/4/4 1778/7/4 +f 1776/5/5 1775/4/5 1777/6/5 +f 1781/8/6 1782/9/6 1788/10/6 +f 1781/8/7 1788/10/7 1783/11/7 +f 1775/4/8 1782/9/8 1784/12/8 +f 1775/4/9 1784/12/9 1778/7/9 +f 1788/10/10 1786/13/10 1783/11/10 +f 90/14/11 3711/15/11 3710/16/11 +f 3833/17/12 274/18/12 3834/19/12 +f 274/18/13 3837/20/13 3838/21/13 +f 274/18/14 3838/21/14 3834/19/14 +f 3839/22/15 3837/20/15 3844/23/15 +f 3839/22/16 3844/23/16 3840/24/16 +f 3841/25/17 3850/26/17 3842/27/17 +f 3843/28/18 3844/23/18 3850/26/18 +f 3843/28/19 3850/26/19 3841/25/19 +f 3844/23/20 3845/29/20 3840/24/20 +f 3844/23/21 3843/28/21 3845/29/21 +f 1/30/22 2/31/23 3752/32/24 +f 7/33/25 8/34/26 9/35/27 +f 6/36/28 2/31/23 1/30/22 +f 3/37/29 2/31/23 6/36/28 +f 11/38/30 3/37/29 6/36/28 +f 11/38/30 10/39/31 12/40/32 +f 12/40/32 10/39/31 13/41/33 +f 14/42/34 10/39/31 22/43/35 +f 13/41/33 10/39/31 14/42/34 +f 16/44/36 15/45/37 17/46/38 +f 3/37/29 11/38/30 15/45/37 +f 15/45/37 11/38/30 12/40/32 +f 17/46/38 15/45/37 12/40/32 +f 20/47/39 18/48/40 19/49/41 +f 20/47/39 19/49/41 21/50/42 +f 14/42/34 18/48/40 13/41/33 +f 18/48/40 14/42/34 22/43/35 +f 20/47/39 21/50/42 23/51/43 +f 23/51/43 21/50/42 31/52/44 +f 18/48/40 20/47/39 13/41/33 +f 12/40/32 23/51/43 24/53/45 +f 13/41/33 23/51/43 12/40/32 +f 20/47/39 23/51/43 13/41/33 +f 17/46/38 25/54/46 26/55/47 +f 26/55/47 25/54/46 38/56/48 +f 17/46/38 26/55/47 16/44/36 +f 25/54/46 17/46/38 12/40/32 +f 25/54/46 12/40/32 24/53/45 +f 29/57/49 27/58/50 28/59/51 +f 30/60/52 27/58/50 29/57/49 +f 26/55/47 27/58/50 30/60/52 +f 26/55/47 30/60/52 16/44/36 +f 28/59/51 27/58/50 26/55/47 +f 33/61/53 31/52/44 3734/62/54 +f 3734/62/54 31/52/44 21/50/42 +f 32/63/55 31/52/44 33/61/53 +f 24/53/45 34/64/56 35/65/57 +f 23/51/43 34/64/56 24/53/45 +f 31/52/44 34/64/56 23/51/43 +f 34/64/56 31/52/44 32/63/55 +f 43/66/58 34/64/56 32/63/55 +f 38/56/48 36/67/59 37/68/60 +f 25/54/46 36/67/59 38/56/48 +f 24/53/45 36/67/59 25/54/46 +f 36/67/59 24/53/45 35/65/57 +f 40/69/61 28/59/51 39/70/62 +f 39/70/62 28/59/51 38/56/48 +f 38/56/48 28/59/51 26/55/47 +f 39/70/62 38/56/48 37/68/60 +f 43/66/58 41/71/63 42/72/64 +f 42/72/64 41/71/63 44/73/65 +f 32/63/55 41/71/63 43/66/58 +f 45/74/66 41/71/63 32/63/55 +f 45/74/66 32/63/55 33/61/53 +f 41/71/63 45/74/66 44/73/65 +f 43/66/58 46/75/67 35/65/57 +f 34/64/56 43/66/58 35/65/57 +f 35/65/57 46/75/67 47/76/68 +f 46/75/67 43/66/58 42/72/64 +f 49/77/69 47/76/68 48/78/70 +f 36/67/59 47/76/68 37/68/60 +f 37/68/60 47/76/68 49/77/69 +f 35/65/57 47/76/68 36/67/59 +f 53/79/71 49/77/69 50/80/72 +f 39/70/62 49/77/69 40/69/61 +f 40/69/61 49/77/69 53/79/71 +f 49/77/69 39/70/62 37/68/60 +f 50/80/72 49/77/69 48/78/70 +f 3771/81/73 51/82/74 3770/83/75 +f 40/69/61 51/82/74 3771/81/73 +f 51/82/74 53/79/71 3770/83/75 +f 3770/83/75 53/79/71 52/84/76 +f 40/69/61 53/79/71 51/82/74 +f 44/73/65 54/85/77 55/86/78 +f 56/87/79 55/86/78 57/88/80 +f 44/73/65 56/87/79 42/72/64 +f 55/86/78 56/87/79 44/73/65 +f 61/89/81 58/90/82 60/91/83 +f 58/90/82 59/92/84 60/91/83 +f 46/75/67 58/90/82 61/89/81 +f 46/75/67 61/89/81 47/76/68 +f 58/90/82 46/75/67 42/72/64 +f 56/87/79 58/90/82 42/72/64 +f 58/90/82 56/87/79 57/88/80 +f 59/92/84 58/90/82 57/88/80 +f 61/89/81 62/93/85 48/78/70 +f 61/89/81 48/78/70 47/76/68 +f 62/93/85 61/89/81 60/91/83 +f 63/94/86 64/95/87 66/96/88 +f 50/80/72 63/94/86 53/79/71 +f 62/93/85 63/94/86 48/78/70 +f 48/78/70 63/94/86 50/80/72 +f 64/95/87 63/94/86 62/93/85 +f 65/97/89 66/96/88 3766/98/90 +f 53/79/71 65/97/89 52/84/76 +f 63/94/86 65/97/89 53/79/71 +f 66/96/88 65/97/89 63/94/86 +f 67/99/91 68/100/92 97/101/93 +f 67/99/91 97/101/93 100/102/94 +f 54/85/77 67/99/91 55/86/78 +f 68/100/92 67/99/91 54/85/77 +f 68/100/92 54/85/77 3728/103/95 +f 97/101/93 68/100/92 69/104/96 +f 69/104/96 68/100/92 3724/105/97 +f 57/88/80 70/106/98 71/107/99 +f 55/86/78 70/106/98 57/88/80 +f 67/99/91 70/106/98 55/86/78 +f 71/107/99 70/106/98 67/99/91 +f 71/107/99 67/99/91 100/102/94 +f 59/92/84 72/108/100 60/91/83 +f 72/108/100 59/92/84 71/107/99 +f 71/107/99 59/92/84 57/88/80 +f 74/109/101 73/110/102 75/111/103 +f 60/91/83 73/110/102 62/93/85 +f 62/93/85 73/110/102 74/109/101 +f 72/108/100 73/110/102 60/91/83 +f 75/111/103 73/110/102 72/108/100 +f 64/95/87 76/112/104 66/96/88 +f 74/109/101 76/112/104 64/95/87 +f 74/109/101 64/95/87 62/93/85 +f 77/113/105 76/112/104 74/109/101 +f 78/114/106 79/115/107 111/116/108 +f 66/96/88 78/114/106 3766/98/90 +f 76/112/104 78/114/106 66/96/88 +f 79/115/107 78/114/106 77/113/105 +f 78/114/106 76/112/104 77/113/105 +f 81/117/109 80/118/110 82/119/111 +f 80/118/110 81/117/109 83/120/112 +f 84/121/113 81/117/109 85/122/114 +f 84/121/113 85/122/114 86/123/115 +f 87/124/116 81/117/109 84/121/113 +f 87/124/116 84/121/113 88/125/117 +f 85/122/114 81/117/109 82/119/111 +f 89/126/118 84/121/113 86/123/115 +f 84/121/113 89/126/118 90/14/119 +f 84/121/113 90/14/119 88/125/117 +f 91/127/120 92/128/121 93/129/122 +f 92/128/121 91/127/120 94/130/123 +f 90/14/119 92/128/121 94/130/123 +f 92/128/121 90/14/119 89/126/118 +f 69/104/96 95/131/124 96/132/125 +f 95/131/124 69/104/96 91/127/120 +f 95/131/124 91/127/120 93/129/122 +f 97/101/93 98/133/126 101/134/127 +f 96/132/125 97/101/93 69/104/96 +f 98/133/126 97/101/93 96/132/125 +f 99/135/128 100/102/94 101/134/127 +f 99/135/128 101/134/127 102/136/129 +f 71/107/99 100/102/94 99/135/128 +f 101/134/127 100/102/94 97/101/93 +f 99/135/128 103/137/130 72/108/100 +f 99/135/128 72/108/100 71/107/99 +f 103/137/130 99/135/128 102/136/129 +f 107/138/131 104/139/132 105/140/133 +f 104/139/132 106/141/134 105/140/133 +f 75/111/103 104/139/132 107/138/131 +f 75/111/103 107/138/131 74/109/101 +f 103/137/130 104/139/132 75/111/103 +f 103/137/130 75/111/103 72/108/100 +f 106/141/134 104/139/132 103/137/130 +f 107/138/131 108/142/135 109/143/136 +f 107/138/131 109/143/136 77/113/105 +f 107/138/131 77/113/105 74/109/101 +f 108/142/135 107/138/131 105/140/133 +f 111/116/108 79/115/107 110/144/137 +f 111/116/108 110/144/137 112/145/138 +f 109/143/136 79/115/107 77/113/105 +f 110/144/137 79/115/107 109/143/136 +f 114/146/139 111/116/108 113/147/140 +f 113/147/140 111/116/108 112/145/138 +f 116/148/141 115/149/142 172/150/143 +f 115/149/142 117/151/144 3774/152/145 +f 114/146/139 115/149/142 3774/152/145 +f 172/150/143 115/149/142 113/147/140 +f 115/149/142 114/146/139 113/147/140 +f 118/153/146 116/148/141 119/154/147 +f 120/155/148 116/148/141 118/153/146 +f 120/155/148 118/153/146 3788/156/149 +f 115/149/142 116/148/141 120/155/148 +f 115/149/142 120/155/148 117/151/144 +f 123/157/150 121/158/151 122/159/152 +f 123/157/150 122/159/152 124/160/153 +f 125/161/154 121/158/151 126/162/155 +f 126/162/155 121/158/151 123/157/150 +f 119/154/147 121/158/151 118/153/146 +f 121/158/151 125/161/154 118/153/146 +f 122/159/152 121/158/151 119/154/147 +f 127/163/156 123/157/150 124/160/153 +f 128/164/157 130/165/158 129/166/159 +f 127/163/156 128/164/157 131/167/160 +f 123/157/150 127/163/156 131/167/160 +f 130/165/158 128/164/157 127/163/156 +f 130/165/158 127/163/156 178/168/161 +f 132/169/162 133/170/163 134/171/164 +f 133/170/163 132/169/162 138/172/165 +f 134/171/164 133/170/163 135/173/166 +f 134/171/164 135/173/166 136/174/167 +f 140/175/168 137/176/169 139/177/170 +f 3719/178/171 138/172/165 137/176/169 +f 137/176/169 138/172/165 132/169/162 +f 139/177/170 137/176/169 132/169/162 +f 141/179/172 140/175/168 142/180/173 +f 144/181/174 140/175/168 143/182/175 +f 143/182/175 140/175/168 141/179/172 +f 137/176/169 140/175/168 144/181/174 +f 137/176/169 144/181/174 3719/178/171 +f 141/179/172 145/183/176 82/119/111 +f 80/118/110 141/179/172 82/119/111 +f 143/182/175 141/179/172 80/118/110 +f 145/183/176 141/179/172 142/180/173 +f 146/184/177 148/185/178 202/186/179 +f 147/187/180 146/184/177 202/186/179 +f 85/122/114 146/184/177 86/123/115 +f 146/184/177 147/187/180 86/123/115 +f 145/183/176 146/184/177 82/119/111 +f 82/119/111 146/184/177 85/122/114 +f 148/185/178 146/184/177 145/183/176 +f 149/188/181 147/187/180 150/189/182 +f 86/123/115 149/188/181 89/126/118 +f 89/126/118 149/188/181 152/190/183 +f 147/187/180 149/188/181 86/123/115 +f 150/189/182 147/187/180 202/186/179 +f 93/129/122 151/191/184 156/192/185 +f 152/190/183 151/191/184 93/129/122 +f 92/128/121 152/190/183 93/129/122 +f 152/190/183 92/128/121 89/126/118 +f 151/191/184 152/190/183 149/188/181 +f 150/189/182 151/191/184 149/188/181 +f 154/193/186 153/194/187 155/195/188 +f 153/194/187 156/192/185 155/195/188 +f 95/131/124 153/194/187 154/193/186 +f 95/131/124 154/193/186 96/132/125 +f 93/129/122 153/194/187 95/131/124 +f 156/192/185 153/194/187 93/129/122 +f 98/133/126 154/193/186 157/196/189 +f 154/193/186 98/133/126 96/132/125 +f 158/197/190 101/134/127 159/198/191 +f 101/134/127 158/197/190 102/136/129 +f 159/198/191 101/134/127 157/196/189 +f 157/196/189 101/134/127 98/133/126 +f 103/137/130 160/199/192 106/141/134 +f 158/197/190 160/199/192 102/136/129 +f 102/136/129 160/199/192 103/137/130 +f 161/200/193 160/199/192 158/197/190 +f 106/141/134 162/201/194 163/202/195 +f 105/140/133 106/141/134 163/202/195 +f 162/201/194 106/141/134 160/199/192 +f 162/201/194 160/199/192 161/200/193 +f 108/142/135 164/203/196 109/143/136 +f 109/143/136 164/203/196 165/204/197 +f 105/140/133 164/203/196 108/142/135 +f 164/203/196 105/140/133 163/202/195 +f 166/205/198 167/206/199 170/207/200 +f 110/144/137 166/205/198 112/145/138 +f 165/204/197 166/205/198 110/144/137 +f 165/204/197 110/144/137 109/143/136 +f 167/206/199 166/205/198 165/204/197 +f 168/208/201 169/209/202 170/207/200 +f 169/209/202 168/208/201 172/150/143 +f 113/147/140 169/209/202 172/150/143 +f 166/205/198 169/209/202 113/147/140 +f 166/205/198 113/147/140 112/145/138 +f 170/207/200 169/209/202 166/205/198 +f 172/150/143 171/210/203 116/148/141 +f 171/210/203 172/150/143 168/208/201 +f 175/211/204 173/212/205 174/213/206 +f 174/213/206 173/212/205 176/214/207 +f 116/148/141 173/212/205 175/211/204 +f 116/148/141 175/211/204 119/154/147 +f 119/154/147 175/211/204 122/159/152 +f 171/210/203 173/212/205 116/148/141 +f 176/214/207 173/212/205 171/210/203 +f 177/215/208 242/216/209 179/217/210 +f 122/159/152 177/215/208 124/160/153 +f 175/211/204 177/215/208 122/159/152 +f 242/216/209 177/215/208 175/211/204 +f 242/216/209 175/211/204 174/213/206 +f 130/165/158 178/168/161 182/218/211 +f 178/168/161 179/217/210 180/219/212 +f 182/218/211 178/168/161 180/219/212 +f 177/215/208 178/168/161 124/160/153 +f 124/160/153 178/168/161 127/163/156 +f 179/217/210 178/168/161 177/215/208 +f 181/220/213 182/218/211 183/221/214 +f 130/165/158 181/220/213 129/166/159 +f 182/218/211 181/220/213 130/165/158 +f 184/222/215 185/223/216 186/224/217 +f 181/220/213 185/223/216 129/166/159 +f 186/224/217 185/223/216 183/221/214 +f 183/221/214 185/223/216 181/220/213 +f 188/225/218 189/226/219 190/227/220 +f 187/228/221 188/225/218 190/227/220 +f 188/225/218 187/228/221 192/229/222 +f 192/229/222 187/228/221 3801/230/223 +f 191/231/224 188/225/218 192/229/222 +f 189/226/219 188/225/218 191/231/224 +f 184/222/215 190/227/220 189/226/219 +f 249/232/225 190/227/220 186/224/217 +f 190/227/220 184/222/215 186/224/217 +f 195/233/226 139/177/170 132/169/162 +f 193/234/227 139/177/170 195/233/226 +f 197/235/228 196/236/229 193/234/227 +f 193/234/227 196/236/229 194/237/230 +f 197/235/228 193/234/227 195/233/226 +f 198/238/231 194/237/230 199/239/232 +f 200/240/233 198/238/231 199/239/232 +f 140/175/168 198/238/231 142/180/173 +f 142/180/173 198/238/231 200/240/233 +f 193/234/227 198/238/231 139/177/170 +f 139/177/170 198/238/231 140/175/168 +f 194/237/230 198/238/231 193/234/227 +f 145/183/176 200/240/233 201/241/234 +f 200/240/233 145/183/176 142/180/173 +f 202/186/179 148/185/178 203/242/235 +f 201/241/234 148/185/178 145/183/176 +f 203/242/235 148/185/178 201/241/234 +f 204/243/236 150/189/182 202/186/179 +f 205/244/237 204/243/236 262/245/238 +f 204/243/236 202/186/179 203/242/235 +f 262/245/238 204/243/236 203/242/235 +f 151/191/184 207/246/239 206/247/240 +f 151/191/184 206/247/240 156/192/185 +f 204/243/236 207/246/239 150/189/182 +f 207/246/239 151/191/184 150/189/182 +f 207/246/239 204/243/236 205/244/237 +f 155/195/188 208/248/241 211/249/242 +f 208/248/241 209/250/243 211/249/242 +f 156/192/185 208/248/241 155/195/188 +f 206/247/240 208/248/241 156/192/185 +f 208/248/241 206/247/240 207/246/239 +f 209/250/243 208/248/241 207/246/239 +f 210/251/244 211/249/242 275/252/245 +f 212/253/246 210/251/244 275/252/245 +f 154/193/186 210/251/244 157/196/189 +f 155/195/188 210/251/244 154/193/186 +f 211/249/242 210/251/244 155/195/188 +f 213/254/247 212/253/246 273/255/248 +f 159/198/191 212/253/246 213/254/247 +f 159/198/191 213/254/247 158/197/190 +f 210/251/244 212/253/246 159/198/191 +f 210/251/244 159/198/191 157/196/189 +f 213/254/247 214/256/249 161/200/193 +f 213/254/247 161/200/193 158/197/190 +f 215/257/250 214/256/249 213/254/247 +f 217/258/251 216/259/252 218/260/253 +f 162/201/194 216/259/252 163/202/195 +f 214/256/249 216/259/252 161/200/193 +f 161/200/193 216/259/252 162/201/194 +f 218/260/253 216/259/252 214/256/249 +f 218/260/253 214/256/249 215/257/250 +f 218/260/253 219/261/254 217/258/251 +f 219/261/254 218/260/253 220/262/255 +f 222/263/256 224/264/257 225/265/258 +f 223/266/259 222/263/256 225/265/258 +f 164/203/196 222/263/256 165/204/197 +f 165/204/197 222/263/256 223/266/259 +f 216/259/252 222/263/256 163/202/195 +f 163/202/195 222/263/256 164/203/196 +f 222/263/256 216/259/252 217/258/251 +f 224/264/257 222/263/256 217/258/251 +f 225/265/258 224/264/257 226/267/260 +f 224/264/257 227/268/261 226/267/260 +f 219/261/254 224/264/257 217/258/251 +f 227/268/261 224/264/257 219/261/254 +f 227/268/261 219/261/254 221/269/262 +f 232/270/263 228/271/264 229/272/265 +f 167/206/199 228/271/264 232/270/263 +f 167/206/199 232/270/263 170/207/200 +f 223/266/259 228/271/264 167/206/199 +f 223/266/259 167/206/199 165/204/197 +f 229/272/265 228/271/264 223/266/259 +f 229/272/265 223/266/259 225/265/258 +f 234/273/266 229/272/265 230/274/267 +f 229/272/265 234/273/266 232/270/263 +f 230/274/267 229/272/265 226/267/260 +f 229/272/265 225/265/258 226/267/260 +f 231/275/268 232/270/263 233/276/269 +f 231/275/268 233/276/269 237/277/270 +f 170/207/200 232/270/263 231/275/268 +f 170/207/200 231/275/268 168/208/201 +f 234/273/266 233/276/269 232/270/263 +f 234/273/266 235/278/271 236/279/272 +f 236/279/272 235/278/271 282/280/273 +f 233/276/269 234/273/266 236/279/272 +f 235/278/271 234/273/266 230/274/267 +f 176/214/207 237/277/270 238/281/274 +f 238/281/274 237/277/270 239/282/275 +f 171/210/203 237/277/270 176/214/207 +f 237/277/270 171/210/203 168/208/201 +f 231/275/268 237/277/270 168/208/201 +f 243/283/276 240/284/277 241/285/278 +f 176/214/207 240/284/277 174/213/206 +f 174/213/206 240/284/277 243/283/276 +f 240/284/277 176/214/207 238/281/274 +f 242/216/209 243/283/276 245/286/279 +f 243/283/276 242/216/209 174/213/206 +f 180/219/212 245/286/279 244/287/280 +f 245/286/279 180/219/212 179/217/210 +f 242/216/209 245/286/279 179/217/210 +f 246/288/281 247/289/282 248/290/283 +f 247/289/282 246/288/281 183/221/214 +f 182/218/211 247/289/282 183/221/214 +f 244/287/280 247/289/282 180/219/212 +f 180/219/212 247/289/282 182/218/211 +f 249/232/225 250/291/284 3804/292/285 +f 190/227/220 249/232/225 187/228/221 +f 251/293/286 249/232/225 186/224/217 +f 250/291/284 249/232/225 251/293/286 +f 251/293/286 252/294/287 250/291/284 +f 186/224/217 252/294/287 251/293/286 +f 246/288/281 252/294/287 183/221/214 +f 252/294/287 186/224/217 183/221/214 +f 250/291/284 253/295/288 3804/292/285 +f 252/294/287 254/296/289 250/291/284 +f 254/296/289 253/295/288 250/291/284 +f 255/297/290 254/296/289 246/288/281 +f 246/288/281 254/296/289 252/294/287 +f 256/298/291 199/239/232 257/299/292 +f 196/236/229 199/239/232 194/237/230 +f 257/299/292 199/239/232 259/300/293 +f 259/300/293 199/239/232 196/236/229 +f 258/301/294 257/299/292 259/300/293 +f 257/299/292 258/301/294 256/298/291 +f 263/302/295 260/303/296 261/304/297 +f 200/240/233 260/303/296 201/241/234 +f 201/241/234 260/303/296 263/302/295 +f 256/298/291 260/303/296 199/239/232 +f 199/239/232 260/303/296 200/240/233 +f 261/304/297 260/303/296 256/298/291 +f 262/245/238 263/302/295 264/305/298 +f 203/242/235 263/302/295 262/245/238 +f 263/302/295 203/242/235 201/241/234 +f 264/305/298 263/302/295 261/304/297 +f 266/306/299 265/307/300 267/308/301 +f 205/244/237 265/307/300 266/306/299 +f 262/245/238 265/307/300 205/244/237 +f 265/307/300 262/245/238 264/305/298 +f 268/309/302 269/310/303 209/250/243 +f 207/246/239 268/309/302 209/250/243 +f 266/306/299 268/309/302 205/244/237 +f 205/244/237 268/309/302 207/246/239 +f 268/309/302 266/306/299 267/308/301 +f 270/311/304 269/310/303 271/312/305 +f 209/250/243 269/310/303 270/311/304 +f 211/249/242 209/250/243 270/311/304 +f 212/253/246 272/313/306 273/255/248 +f 275/252/245 272/313/306 212/253/246 +f 272/313/306 275/252/245 276/314/307 +f 3844/23/308 272/313/306 276/314/307 +f 275/252/245 270/311/304 276/314/307 +f 211/249/242 270/311/304 275/252/245 +f 276/314/307 270/311/304 315/315/309 +f 315/315/309 270/311/304 271/312/305 +f 278/316/310 239/282/275 277/317/311 +f 239/282/275 278/316/310 238/281/274 +f 236/279/272 239/282/275 233/276/269 +f 233/276/269 239/282/275 237/277/270 +f 277/317/311 239/282/275 236/279/272 +f 279/318/312 280/319/313 281/320/314 +f 280/319/313 279/318/312 277/317/311 +f 280/319/313 277/317/311 282/280/273 +f 282/280/273 277/317/311 236/279/272 +f 280/319/313 282/280/273 283/321/315 +f 283/321/315 282/280/273 284/322/316 +f 284/322/316 282/280/273 235/278/271 +f 283/321/315 284/322/316 285/323/317 +f 280/319/313 283/321/315 281/320/314 +f 230/274/267 284/322/316 235/278/271 +f 285/323/317 284/322/316 230/274/267 +f 285/323/317 230/274/267 226/267/260 +f 278/316/310 286/324/318 241/285/278 +f 240/284/277 278/316/310 241/285/278 +f 238/281/274 278/316/310 240/284/277 +f 286/324/318 278/316/310 277/317/311 +f 288/325/319 287/326/320 3828/327/321 +f 286/324/318 287/326/320 288/325/319 +f 279/318/312 287/326/320 277/317/311 +f 277/317/311 287/326/320 286/324/318 +f 287/326/320 279/318/312 3823/328/322 +f 3823/328/322 279/318/312 281/320/314 +f 289/329/323 292/330/324 245/286/279 +f 243/283/276 289/329/323 245/286/279 +f 286/324/318 289/329/323 241/285/278 +f 241/285/278 289/329/323 243/283/276 +f 289/329/323 286/324/318 288/325/319 +f 255/297/290 248/290/283 290/331/325 +f 248/290/283 255/297/290 246/288/281 +f 248/290/283 247/289/282 244/287/280 +f 290/331/325 248/290/283 291/332/326 +f 248/290/283 292/330/324 291/332/326 +f 245/286/279 292/330/324 244/287/280 +f 244/287/280 292/330/324 248/290/283 +f 291/332/326 292/330/324 294/333/327 +f 294/333/327 292/330/324 289/329/323 +f 294/333/327 293/334/328 295/335/329 +f 289/329/323 293/334/328 294/333/327 +f 288/325/319 293/334/328 289/329/323 +f 293/334/328 288/325/319 296/336/330 +f 296/336/330 288/325/319 3828/327/321 +f 290/331/325 297/337/331 255/297/290 +f 298/338/332 297/337/331 291/332/326 +f 291/332/326 297/337/331 290/331/325 +f 294/333/327 298/338/332 291/332/326 +f 299/339/333 298/338/332 294/333/327 +f 300/340/334 301/341/335 261/304/297 +f 258/301/294 300/340/334 256/298/291 +f 300/340/334 261/304/297 256/298/291 +f 301/341/335 300/340/334 258/301/294 +f 302/342/336 301/341/335 258/301/294 +f 302/342/336 258/301/294 3685/343/337 +f 307/344/338 302/342/336 303/345/339 +f 303/345/339 302/342/336 304/346/340 +f 301/341/335 302/342/336 307/344/338 +f 308/347/341 305/348/342 306/349/343 +f 305/348/342 307/344/338 306/349/343 +f 264/305/298 305/348/342 308/347/341 +f 305/348/342 264/305/298 261/304/297 +f 301/341/335 305/348/342 261/304/297 +f 307/344/338 305/348/342 301/341/335 +f 308/347/341 306/349/343 309/350/344 +f 267/308/301 308/347/341 309/350/344 +f 265/307/300 308/347/341 267/308/301 +f 308/347/341 265/307/300 264/305/298 +f 312/351/345 310/352/346 311/353/347 +f 268/309/302 310/352/346 312/351/345 +f 268/309/302 312/351/345 269/310/303 +f 267/308/301 310/352/346 268/309/302 +f 310/352/346 267/308/301 309/350/344 +f 315/315/309 312/351/345 313/354/348 +f 313/354/348 312/351/345 314/355/349 +f 271/312/305 312/351/345 315/315/309 +f 312/351/345 271/312/305 269/310/303 +f 314/355/349 312/351/345 311/353/347 +f 316/356/350 315/315/309 313/354/348 +f 316/356/350 313/354/348 323/357/351 +f 276/314/307 315/315/309 316/356/350 +f 307/344/338 317/358/352 306/349/343 +f 317/358/352 307/344/338 303/345/339 +f 306/349/343 318/359/353 309/350/344 +f 309/350/344 318/359/353 319/360/354 +f 317/358/352 318/359/353 306/349/343 +f 319/360/354 320/361/355 321/362/356 +f 311/353/347 319/360/354 321/362/356 +f 310/352/346 319/360/354 311/353/347 +f 319/360/354 310/352/346 309/350/344 +f 320/361/355 319/360/354 318/359/353 +f 324/363/357 314/355/349 322/364/358 +f 314/355/349 324/363/357 313/354/348 +f 322/364/358 314/355/349 311/353/347 +f 322/364/358 311/353/347 321/362/356 +f 323/357/351 324/363/357 325/365/359 +f 313/354/348 324/363/357 323/357/351 +f 326/366/360 328/367/361 327/368/362 +f 318/359/353 326/366/360 320/361/355 +f 326/366/360 318/359/353 317/358/352 +f 329/369/363 326/366/360 317/358/352 +f 328/367/361 326/366/360 329/369/363 +f 321/362/356 330/370/364 332/371/365 +f 320/361/355 330/370/364 321/362/356 +f 326/366/360 330/370/364 320/361/355 +f 327/368/362 330/370/364 326/366/360 +f 331/372/366 332/371/365 354/373/367 +f 321/362/356 331/372/366 322/364/358 +f 332/371/365 331/372/366 321/362/356 +f 325/365/359 333/374/368 334/375/369 +f 334/375/369 333/374/368 335/376/370 +f 331/372/366 333/374/368 325/365/359 +f 331/372/366 325/365/359 322/364/358 +f 322/364/358 325/365/359 324/363/357 +f 354/373/367 333/374/368 331/372/366 +f 338/377/371 336/378/372 337/379/373 +f 3848/380/374 336/378/372 338/377/371 +f 334/375/369 336/378/372 325/365/359 +f 337/379/373 336/378/372 334/375/369 +f 337/379/373 334/375/369 335/376/370 +f 342/381/375 339/382/376 340/383/377 +f 339/382/376 342/381/375 341/384/378 +f 340/383/377 339/382/376 338/377/371 +f 340/383/377 338/377/371 337/379/373 +f 343/385/379 342/381/375 364/386/380 +f 342/381/375 340/383/377 364/386/380 +f 344/387/381 343/385/379 345/388/382 +f 342/381/375 343/385/379 347/389/383 +f 347/389/383 343/385/379 344/387/381 +f 342/381/375 347/389/383 341/384/378 +f 349/390/384 348/391/385 327/368/362 +f 328/367/361 349/390/384 327/368/362 +f 348/391/385 349/390/384 350/392/386 +f 351/393/387 352/394/388 353/395/389 +f 330/370/364 351/393/387 332/371/365 +f 348/391/385 351/393/387 327/368/362 +f 351/393/387 330/370/364 327/368/362 +f 352/394/388 351/393/387 348/391/385 +f 352/394/388 348/391/385 375/396/390 +f 354/373/367 353/395/389 355/397/391 +f 332/371/365 353/395/389 354/373/367 +f 351/393/387 353/395/389 332/371/365 +f 335/376/370 356/398/392 357/399/393 +f 357/399/393 356/398/392 380/400/394 +f 333/374/368 356/398/392 335/376/370 +f 356/398/392 333/374/368 354/373/367 +f 380/400/394 356/398/392 355/397/391 +f 355/397/391 356/398/392 354/373/367 +f 359/401/395 358/402/396 360/403/397 +f 337/379/373 358/402/396 359/401/395 +f 335/376/370 358/402/396 337/379/373 +f 358/402/396 335/376/370 357/399/393 +f 360/403/397 358/402/396 357/399/393 +f 363/404/398 361/405/399 362/406/400 +f 359/401/395 361/405/399 340/383/377 +f 340/383/377 361/405/399 363/404/398 +f 359/401/395 340/383/377 337/379/373 +f 365/407/401 363/404/398 366/408/402 +f 364/386/380 363/404/398 365/407/401 +f 363/404/398 364/386/380 340/383/377 +f 346/409/403 365/407/401 367/410/404 +f 343/385/379 365/407/401 346/409/403 +f 343/385/379 346/409/403 345/388/382 +f 364/386/380 365/407/401 343/385/379 +f 367/410/404 365/407/401 366/408/402 +f 369/411/405 368/412/406 390/413/407 +f 368/412/406 346/409/403 367/410/404 +f 370/414/408 369/411/405 371/415/409 +f 368/412/406 369/411/405 372/416/410 +f 368/412/406 372/416/410 346/409/403 +f 375/396/390 373/417/411 374/418/412 +f 350/392/386 373/417/411 348/391/385 +f 348/391/385 373/417/411 375/396/390 +f 373/417/411 350/392/386 3676/419/413 +f 374/418/412 373/417/411 376/420/414 +f 352/394/388 375/396/390 377/421/415 +f 377/421/415 375/396/390 374/418/412 +f 378/422/416 379/423/417 401/424/418 +f 379/423/417 378/422/416 355/397/391 +f 379/423/417 355/397/391 353/395/389 +f 352/394/388 379/423/417 353/395/389 +f 379/423/417 352/394/388 377/421/415 +f 401/424/418 379/423/417 377/421/415 +f 380/400/394 381/425/419 382/426/420 +f 378/422/416 381/425/419 380/400/394 +f 378/422/416 380/400/394 355/397/391 +f 360/403/397 382/426/420 406/427/421 +f 385/428/422 360/403/397 406/427/421 +f 380/400/394 360/403/397 357/399/393 +f 382/426/420 360/403/397 380/400/394 +f 362/406/400 383/429/423 384/430/424 +f 383/429/423 385/428/422 384/430/424 +f 361/405/399 383/429/423 362/406/400 +f 383/429/423 361/405/399 359/401/395 +f 360/403/397 383/429/423 359/401/395 +f 385/428/422 383/429/423 360/403/397 +f 386/431/425 384/430/424 411/432/426 +f 386/431/425 411/432/426 387/433/427 +f 362/406/400 386/431/425 366/408/402 +f 363/404/398 362/406/400 366/408/402 +f 386/431/425 362/406/400 384/430/424 +f 367/410/404 366/408/402 387/433/427 +f 367/410/404 387/433/427 388/434/428 +f 387/433/427 366/408/402 386/431/425 +f 390/413/407 389/435/429 391/436/430 +f 368/412/406 389/435/429 390/413/407 +f 389/435/429 368/412/406 367/410/404 +f 391/436/430 389/435/429 388/434/428 +f 389/435/429 367/410/404 388/434/428 +f 393/437/431 392/438/432 417/439/433 +f 371/415/409 392/438/432 393/437/431 +f 392/438/432 371/415/409 390/413/407 +f 390/413/407 371/415/409 369/411/405 +f 417/439/433 392/438/432 391/436/430 +f 391/436/430 392/438/432 390/413/407 +f 396/440/434 394/441/435 395/442/436 +f 396/440/434 395/442/436 397/443/437 +f 374/418/412 394/441/435 396/440/434 +f 394/441/435 374/418/412 376/420/414 +f 395/442/436 394/441/435 398/444/438 +f 399/445/439 431/446/440 402/447/441 +f 396/440/434 399/445/439 377/421/415 +f 396/440/434 377/421/415 374/418/412 +f 431/446/440 399/445/439 396/440/434 +f 431/446/440 396/440/434 397/443/437 +f 400/448/442 401/424/418 402/447/441 +f 401/424/418 400/448/442 378/422/416 +f 399/445/439 401/424/418 377/421/415 +f 402/447/441 401/424/418 399/445/439 +f 404/449/443 403/450/444 405/451/445 +f 403/450/444 404/449/443 382/426/420 +f 381/425/419 403/450/444 382/426/420 +f 400/448/442 403/450/444 378/422/416 +f 378/422/416 403/450/444 381/425/419 +f 405/451/445 403/450/444 400/448/442 +f 406/427/421 404/449/443 407/452/446 +f 404/449/443 408/453/447 407/452/446 +f 382/426/420 404/449/443 406/427/421 +f 408/453/447 404/449/443 405/451/445 +f 385/428/422 410/454/448 409/455/449 +f 385/428/422 409/455/449 384/430/424 +f 406/427/421 410/454/448 385/428/422 +f 410/454/448 406/427/421 407/452/446 +f 412/456/450 411/432/426 446/457/451 +f 412/456/450 446/457/451 414/458/452 +f 409/455/449 411/432/426 384/430/424 +f 446/457/451 411/432/426 409/455/449 +f 415/459/453 412/456/450 413/460/454 +f 412/456/450 414/458/452 413/460/454 +f 387/433/427 412/456/450 415/459/453 +f 387/433/427 415/459/453 388/434/428 +f 411/432/426 412/456/450 387/433/427 +f 415/459/453 413/460/454 416/461/455 +f 391/436/430 415/459/453 416/461/455 +f 415/459/453 391/436/430 388/434/428 +f 416/461/455 417/439/433 391/436/430 +f 418/462/456 417/439/433 416/461/455 +f 420/463/457 419/464/458 461/465/459 +f 393/437/431 419/464/458 424/466/460 +f 424/466/460 419/464/458 420/463/457 +f 417/439/433 419/464/458 393/437/431 +f 419/464/458 417/439/433 418/462/456 +f 421/467/461 462/468/462 422/469/463 +f 423/470/464 420/463/457 421/467/461 +f 423/470/464 421/467/461 3863/471/465 +f 420/463/457 423/470/464 424/466/460 +f 462/468/462 421/467/461 420/463/457 +f 462/468/462 420/463/457 461/465/459 +f 426/472/466 425/473/467 428/474/468 +f 425/473/467 427/475/469 428/474/468 +f 429/476/470 425/473/467 426/472/466 +f 429/476/470 426/472/466 395/442/436 +f 426/472/466 430/477/471 397/443/437 +f 395/442/436 426/472/466 397/443/437 +f 430/477/471 426/472/466 428/474/468 +f 432/478/472 431/446/440 433/479/473 +f 430/477/471 431/446/440 397/443/437 +f 433/479/473 431/446/440 430/477/471 +f 434/480/474 432/478/472 435/481/475 +f 402/447/441 432/478/472 434/480/474 +f 402/447/441 434/480/474 400/448/442 +f 431/446/440 432/478/472 402/447/441 +f 405/451/445 436/482/476 408/453/447 +f 434/480/474 436/482/476 405/451/445 +f 434/480/474 405/451/445 400/448/442 +f 436/482/476 434/480/474 435/481/475 +f 437/483/477 436/482/476 435/481/475 +f 438/484/478 407/452/446 439/485/479 +f 438/484/478 439/485/479 440/486/480 +f 410/454/448 407/452/446 438/484/478 +f 410/454/448 438/484/478 409/455/449 +f 407/452/446 441/487/481 439/485/479 +f 439/485/479 441/487/481 442/488/482 +f 408/453/447 441/487/481 407/452/446 +f 441/487/481 408/453/447 436/482/476 +f 442/488/482 441/487/481 436/482/476 +f 442/488/482 443/489/483 444/490/484 +f 444/490/484 443/489/483 445/491/485 +f 442/488/482 444/490/484 439/485/479 +f 437/483/477 442/488/482 436/482/476 +f 443/489/483 442/488/482 437/483/477 +f 443/489/483 437/483/477 476/492/486 +f 447/493/487 446/457/451 448/494/488 +f 446/457/451 447/493/487 414/458/452 +f 438/484/478 446/457/451 409/455/449 +f 448/494/488 446/457/451 440/486/480 +f 440/486/480 446/457/451 438/484/478 +f 449/495/489 447/493/487 450/496/490 +f 449/495/489 450/496/490 451/497/491 +f 414/458/452 447/493/487 449/495/489 +f 414/458/452 449/495/489 413/460/454 +f 450/496/490 447/493/487 448/494/488 +f 452/498/492 450/496/490 453/499/493 +f 452/498/492 453/499/493 541/500/494 +f 450/496/490 452/498/492 451/497/491 +f 454/501/495 450/496/490 448/494/488 +f 453/499/493 450/496/490 454/501/495 +f 453/499/493 454/501/495 542/502/496 +f 457/503/497 455/504/498 456/505/499 +f 413/460/454 455/504/498 416/461/455 +f 416/461/455 455/504/498 457/503/497 +f 449/495/489 455/504/498 413/460/454 +f 456/505/499 455/504/498 451/497/491 +f 451/497/491 455/504/498 449/495/489 +f 459/506/500 457/503/497 458/507/501 +f 418/462/456 457/503/497 459/506/500 +f 457/503/497 418/462/456 416/461/455 +f 458/507/501 457/503/497 456/505/499 +f 461/465/459 459/506/500 460/508/502 +f 419/464/458 459/506/500 461/465/459 +f 459/506/500 419/464/458 418/462/456 +f 460/508/502 459/506/500 458/507/501 +f 463/509/503 462/468/462 487/510/504 +f 422/469/463 462/468/462 463/509/503 +f 487/510/504 462/468/462 460/508/502 +f 462/468/462 461/465/459 460/508/502 +f 464/511/505 463/509/503 465/512/506 +f 464/511/505 465/512/506 3869/513/507 +f 422/469/463 463/509/503 464/511/505 +f 466/514/508 467/515/509 468/516/510 +f 427/475/469 466/514/508 428/474/468 +f 466/514/508 468/516/510 428/474/468 +f 3672/517/511 466/514/508 469/518/512 +f 469/518/512 466/514/508 427/475/469 +f 467/515/509 466/514/508 3672/517/511 +f 467/515/509 3672/517/511 470/519/513 +f 428/474/468 471/520/514 430/477/471 +f 430/477/471 471/520/514 433/479/473 +f 468/516/510 471/520/514 428/474/468 +f 472/521/515 471/520/514 468/516/510 +f 433/479/473 473/522/516 474/523/517 +f 475/524/518 433/479/473 474/523/517 +f 433/479/473 475/524/518 432/478/472 +f 471/520/514 473/522/516 433/479/473 +f 473/522/516 471/520/514 472/521/515 +f 477/525/519 476/492/486 437/483/477 +f 477/525/519 437/483/477 435/481/475 +f 476/492/486 477/525/519 478/526/520 +f 481/527/521 476/492/486 478/526/520 +f 435/481/475 475/524/518 478/526/520 +f 435/481/475 478/526/520 477/525/519 +f 475/524/518 435/481/475 432/478/472 +f 478/526/520 475/524/518 474/523/517 +f 3649/528/522 479/529/523 481/527/521 +f 3649/528/522 481/527/521 501/530/524 +f 443/489/483 479/529/523 480/531/525 +f 443/489/483 480/531/525 445/491/485 +f 479/529/523 443/489/483 476/492/486 +f 476/492/486 481/527/521 479/529/523 +f 483/532/526 482/533/527 508/534/528 +f 456/505/499 482/533/527 483/532/526 +f 451/497/491 482/533/527 456/505/499 +f 482/533/527 451/497/491 452/498/492 +f 485/535/529 483/532/526 484/536/530 +f 485/535/529 484/536/530 506/537/531 +f 458/507/501 483/532/526 485/535/529 +f 483/532/526 458/507/501 456/505/499 +f 488/538/532 485/535/529 486/539/533 +f 460/508/502 485/535/529 488/538/532 +f 485/535/529 460/508/502 458/507/501 +f 486/539/533 485/535/529 506/537/531 +f 487/510/504 488/538/532 489/540/534 +f 491/541/535 487/510/504 489/540/534 +f 488/538/532 487/510/504 460/508/502 +f 490/542/536 465/512/506 491/541/535 +f 487/510/504 465/512/506 463/509/503 +f 491/541/535 465/512/506 487/510/504 +f 467/515/509 492/543/537 468/516/510 +f 493/544/538 492/543/537 467/515/509 +f 493/544/538 467/515/509 3674/545/539 +f 493/544/538 3674/545/539 524/546/540 +f 494/547/541 495/548/542 499/549/543 +f 472/521/515 494/547/541 498/550/544 +f 494/547/541 472/521/515 468/516/510 +f 492/543/537 494/547/541 468/516/510 +f 494/547/541 492/543/537 493/544/538 +f 495/548/542 494/547/541 493/544/538 +f 481/527/521 496/551/545 501/530/524 +f 474/523/517 496/551/545 478/526/520 +f 478/526/520 496/551/545 481/527/521 +f 496/551/545 474/523/517 497/552/546 +f 498/550/544 499/549/543 497/552/546 +f 474/523/517 498/550/544 497/552/546 +f 473/522/516 498/550/544 474/523/517 +f 498/550/544 473/522/516 472/521/515 +f 499/549/543 498/550/544 494/547/541 +f 500/553/547 502/554/548 503/555/549 +f 496/551/545 500/553/547 501/530/524 +f 502/554/548 500/553/547 497/552/546 +f 500/553/547 496/551/545 497/552/546 +f 502/554/548 504/556/550 503/555/549 +f 499/549/543 502/554/548 497/552/546 +f 504/556/550 502/554/548 499/549/543 +f 506/537/531 484/536/530 505/557/551 +f 505/557/551 484/536/530 507/558/552 +f 508/534/528 484/536/530 483/532/526 +f 484/536/530 508/534/528 537/559/553 +f 507/558/552 484/536/530 537/559/553 +f 509/560/554 507/558/552 510/561/555 +f 509/560/554 510/561/555 511/562/556 +f 507/558/552 509/560/554 513/563/557 +f 505/557/551 507/558/552 513/563/557 +f 510/561/555 507/558/552 537/559/553 +f 486/539/533 505/557/551 512/564/558 +f 486/539/533 512/564/558 488/538/532 +f 505/557/551 486/539/533 506/537/531 +f 505/557/551 513/563/557 512/564/558 +f 513/563/557 514/565/559 512/564/558 +f 514/565/559 513/563/557 515/566/560 +f 515/566/560 513/563/557 509/560/554 +f 489/540/534 516/567/561 517/568/562 +f 516/567/561 489/540/534 488/538/532 +f 512/564/558 516/567/561 488/538/532 +f 519/569/563 517/568/562 518/570/564 +f 518/570/564 517/568/562 520/571/565 +f 491/541/535 517/568/562 519/569/563 +f 491/541/535 519/569/563 490/542/536 +f 489/540/534 517/568/562 491/541/535 +f 520/571/565 517/568/562 530/572/566 +f 530/572/566 517/568/562 516/567/561 +f 504/556/550 495/548/542 521/573/567 +f 495/548/542 504/556/550 499/549/543 +f 521/573/567 495/548/542 522/574/568 +f 521/573/567 522/574/568 523/575/569 +f 493/544/538 522/574/568 495/548/542 +f 524/546/540 522/574/568 493/544/538 +f 523/575/569 522/574/568 524/546/540 +f 523/575/569 524/546/540 3663/576/570 +f 526/577/571 525/578/572 3655/579/573 +f 3655/579/573 525/578/572 3654/580/574 +f 521/573/567 525/578/572 526/577/571 +f 521/573/567 526/577/571 504/556/550 +f 523/575/569 525/578/572 521/573/567 +f 525/578/572 527/581/575 3654/580/574 +f 523/575/569 527/581/575 525/578/572 +f 528/582/576 527/581/575 523/575/569 +f 528/582/576 523/575/569 3663/576/570 +f 516/567/561 529/583/577 530/572/566 +f 514/565/559 529/583/577 512/564/558 +f 529/583/577 516/567/561 512/564/558 +f 520/571/565 531/584/578 532/585/579 +f 3875/586/580 520/571/565 532/585/579 +f 518/570/564 520/571/565 3875/586/580 +f 531/584/578 520/571/565 530/572/566 +f 531/584/578 530/572/566 529/583/577 +f 534/587/581 533/588/582 529/583/577 +f 534/587/581 529/583/577 514/565/559 +f 531/584/578 535/589/583 532/585/579 +f 535/589/583 536/590/584 532/585/579 +f 533/588/582 535/589/583 531/584/578 +f 533/588/582 531/584/578 529/583/577 +f 536/590/584 535/589/583 533/588/582 +f 536/590/584 533/588/582 544/591/585 +f 539/592/586 537/559/553 538/593/587 +f 510/561/555 537/559/553 539/592/586 +f 510/561/555 539/592/586 511/562/556 +f 508/534/528 538/593/587 537/559/553 +f 482/533/527 538/593/587 508/534/528 +f 541/500/494 538/593/587 482/533/527 +f 541/500/494 482/533/527 452/498/492 +f 538/593/587 541/500/494 540/594/588 +f 541/500/494 453/499/493 540/594/588 +f 534/587/581 543/595/589 533/588/582 +f 533/588/582 543/595/589 544/591/585 +f 545/596/590 534/587/581 514/565/559 +f 545/596/590 514/565/559 515/566/560 +f 543/595/589 534/587/581 545/596/590 +f 544/591/585 543/595/589 546/597/591 +f 545/596/590 547/598/592 548/599/593 +f 545/596/590 548/599/593 543/595/589 +f 550/600/594 549/601/595 553/602/596 +f 550/600/594 553/602/596 552/603/597 +f 549/601/595 551/604/598 553/602/596 +f 554/605/599 556/606/600 557/607/601 +f 554/605/599 557/607/601 555/608/602 +f 551/604/598 556/606/600 554/605/599 +f 556/606/600 551/604/598 549/601/595 +f 559/609/603 561/610/604 562/611/605 +f 561/610/604 560/612/606 563/613/607 +f 559/609/603 558/614/608 561/610/604 +f 558/614/608 560/612/606 561/610/604 +f 892/615/609 565/616/610 564/617/611 +f 563/613/607 560/612/606 892/615/609 +f 560/612/606 565/616/610 892/615/609 +f 564/617/611 565/616/610 550/600/594 +f 566/618/612 568/619/613 567/620/614 +f 570/621/615 569/622/616 571/623/617 +f 568/619/613 566/618/612 570/621/615 +f 566/618/612 569/622/616 570/621/615 +f 898/624/618 559/609/603 562/611/605 +f 898/624/618 572/625/619 559/609/603 +f 571/623/617 569/622/616 898/624/618 +f 569/622/616 572/625/619 898/624/618 +f 573/626/620 575/627/621 574/628/622 +f 577/629/623 576/630/624 578/631/625 +f 575/627/621 573/626/620 577/629/623 +f 573/626/620 576/630/624 577/629/623 +f 579/632/626 580/633/627 568/619/613 +f 578/631/625 576/630/624 579/632/626 +f 576/630/624 580/633/627 579/632/626 +f 568/619/613 580/633/627 567/620/614 +f 582/634/628 581/635/629 584/636/630 +f 581/635/629 583/637/631 585/638/632 +f 581/635/629 585/638/632 584/636/630 +f 586/639/633 588/640/634 574/628/622 +f 586/639/633 574/628/622 587/641/635 +f 585/638/632 583/637/631 588/640/634 +f 585/638/632 588/640/634 586/639/633 +f 589/642/636 591/643/637 590/644/638 +f 592/645/639 593/646/640 589/642/636 +f 591/643/637 589/642/636 594/647/641 +f 589/642/636 593/646/640 594/647/641 +f 595/648/642 597/649/643 582/634/628 +f 595/648/642 582/634/628 596/650/644 +f 593/646/640 597/649/643 595/648/642 +f 597/649/643 593/646/640 592/645/639 +f 602/651/645 600/652/646 603/653/647 +f 599/654/648 598/655/649 602/651/645 +f 599/654/648 602/651/645 601/656/650 +f 598/655/649 600/652/646 602/651/645 +f 604/657/651 605/658/652 591/643/637 +f 603/653/647 600/652/646 604/657/651 +f 600/652/646 605/658/652 604/657/651 +f 591/643/637 605/658/652 590/644/638 +f 610/659/653 608/660/654 611/661/655 +f 607/662/656 606/663/657 610/659/653 +f 607/662/656 610/659/653 609/664/658 +f 606/663/657 608/660/654 610/659/653 +f 612/665/659 614/666/660 599/654/648 +f 612/665/659 599/654/648 613/667/661 +f 611/661/655 608/660/654 612/665/659 +f 608/660/654 614/666/660 612/665/659 +f 615/668/662 617/669/663 616/670/664 +f 617/669/663 615/668/662 619/671/665 +f 615/668/662 618/672/666 620/673/667 +f 615/668/662 620/673/667 619/671/665 +f 924/674/668 622/675/669 607/662/656 +f 924/674/668 607/662/656 621/676/670 +f 620/673/667 618/672/666 622/675/669 +f 620/673/667 622/675/669 924/674/668 +f 623/677/671 625/678/672 624/679/673 +f 627/680/674 626/681/675 628/682/676 +f 625/678/672 623/677/671 627/680/674 +f 623/677/671 626/681/675 627/680/674 +f 930/683/677 629/684/678 617/669/663 +f 628/682/676 626/681/675 930/683/677 +f 626/681/675 629/684/678 930/683/677 +f 617/669/663 629/684/678 616/670/664 +f 633/685/679 632/686/680 634/687/681 +f 631/688/682 630/689/683 633/685/679 +f 630/689/683 632/686/680 633/685/679 +f 634/687/681 624/679/673 625/678/672 +f 632/686/680 624/679/673 634/687/681 +f 639/690/684 637/691/685 640/692/686 +f 636/693/687 635/694/688 639/690/684 +f 636/693/687 639/690/684 638/695/689 +f 635/694/688 637/691/685 639/690/684 +f 641/696/690 642/697/691 631/688/682 +f 640/692/686 637/691/685 641/696/690 +f 637/691/685 642/697/691 641/696/690 +f 631/688/682 642/697/691 630/689/683 +f 643/698/692 644/699/693 645/700/694 +f 647/701/695 646/702/696 648/703/697 +f 644/699/693 643/698/692 647/701/695 +f 643/698/692 646/702/696 647/701/695 +f 649/704/698 651/705/699 636/693/687 +f 649/704/698 636/693/687 650/706/700 +f 648/703/697 646/702/696 649/704/698 +f 646/702/696 651/705/699 649/704/698 +f 652/707/701 654/708/702 653/709/703 +f 656/710/704 655/711/705 657/712/706 +f 654/708/702 652/707/701 656/710/704 +f 652/707/701 655/711/705 656/710/704 +f 946/713/707 659/714/708 645/700/694 +f 946/713/707 645/700/694 658/715/709 +f 657/712/706 655/711/705 946/713/707 +f 655/711/705 659/714/708 946/713/707 +f 664/716/710 662/717/711 665/718/712 +f 661/719/713 660/720/714 664/716/710 +f 661/719/713 664/716/710 663/721/715 +f 660/720/714 662/717/711 664/716/710 +f 666/722/716 667/723/717 654/708/702 +f 665/718/712 662/717/711 666/722/716 +f 662/717/711 667/723/717 666/722/716 +f 654/708/702 667/723/717 653/709/703 +f 672/724/718 670/725/719 673/726/720 +f 669/727/721 668/728/722 672/724/718 +f 669/727/721 672/724/718 671/729/723 +f 668/728/722 670/725/719 672/724/718 +f 674/730/724 676/731/725 661/719/713 +f 674/730/724 661/719/713 675/732/726 +f 673/726/720 670/725/719 674/730/724 +f 670/725/719 676/731/725 674/730/724 +f 677/733/727 679/734/728 678/735/729 +f 681/736/730 680/737/731 682/738/732 +f 679/734/728 677/733/727 681/736/730 +f 677/733/727 680/737/731 681/736/730 +f 959/739/733 683/740/734 669/727/721 +f 959/739/733 669/727/721 960/741/735 +f 682/738/732 680/737/731 959/739/733 +f 680/737/731 683/740/734 959/739/733 +f 685/742/736 687/743/737 688/744/738 +f 685/742/736 684/745/739 687/743/737 +f 684/745/739 686/746/740 689/747/741 +f 684/745/739 689/747/741 687/743/737 +f 965/748/742 690/749/743 679/734/728 +f 689/747/741 686/746/740 690/749/743 +f 689/747/741 690/749/743 965/748/742 +f 679/734/728 690/749/743 678/735/729 +f 695/750/744 693/751/745 696/752/746 +f 692/753/747 691/754/748 695/750/744 +f 692/753/747 695/750/744 694/755/749 +f 691/754/748 693/751/745 695/750/744 +f 697/756/750 685/742/736 688/744/738 +f 697/756/750 698/757/751 685/742/736 +f 696/752/746 693/751/745 697/756/750 +f 693/751/745 698/757/751 697/756/750 +f 701/758/752 700/759/753 699/760/754 +f 699/760/754 700/759/753 703/761/755 +f 699/760/754 703/761/755 702/762/756 +f 704/763/757 706/764/758 692/753/747 +f 704/763/757 692/753/747 705/765/759 +f 700/759/753 706/764/758 704/763/757 +f 706/764/758 700/759/753 701/758/752 +f 708/766/760 710/767/761 711/768/762 +f 710/767/761 709/769/763 712/770/764 +f 708/766/760 707/771/765 710/767/761 +f 707/771/765 709/769/763 710/767/761 +f 978/772/766 714/773/767 699/760/754 +f 978/772/766 699/760/754 713/774/768 +f 712/770/764 709/769/763 978/772/766 +f 709/769/763 714/773/767 978/772/766 +f 719/775/769 717/776/770 720/777/771 +f 716/778/772 715/779/773 719/775/769 +f 716/778/772 719/775/769 718/780/774 +f 715/779/773 717/776/770 719/775/769 +f 721/781/775 708/766/760 711/768/762 +f 721/781/775 722/782/776 708/766/760 +f 720/777/771 717/776/770 721/781/775 +f 717/776/770 722/782/776 721/781/775 +f 727/783/777 725/784/778 728/785/779 +f 724/786/780 723/787/781 727/783/777 +f 724/786/780 727/783/777 726/788/782 +f 723/787/781 725/784/778 727/783/777 +f 985/789/783 716/778/772 729/790/784 +f 728/785/779 725/784/778 985/789/783 +f 725/784/778 716/778/772 985/789/783 +f 730/791/785 732/792/786 731/793/787 +f 734/794/788 733/795/789 735/796/790 +f 732/792/786 730/791/785 734/794/788 +f 730/791/785 733/795/789 734/794/788 +f 990/797/791 737/798/792 724/786/780 +f 990/797/791 724/786/780 736/799/793 +f 735/796/790 733/795/789 990/797/791 +f 733/795/789 737/798/792 990/797/791 +f 739/800/794 741/801/795 742/802/796 +f 741/801/795 740/803/797 743/804/798 +f 739/800/794 738/805/799 741/801/795 +f 738/805/799 740/803/797 741/801/795 +f 995/806/800 744/807/801 732/792/786 +f 743/804/798 740/803/797 995/806/800 +f 740/803/797 744/807/801 995/806/800 +f 732/792/786 744/807/801 731/793/787 +f 749/808/802 747/809/803 750/810/804 +f 746/811/805 745/812/806 749/808/802 +f 746/811/805 749/808/802 748/813/807 +f 745/812/806 747/809/803 749/808/802 +f 751/814/808 739/800/794 742/802/796 +f 751/814/808 752/815/809 739/800/794 +f 750/810/804 747/809/803 751/814/808 +f 747/809/803 752/815/809 751/814/808 +f 755/816/810 754/817/811 753/818/812 +f 756/819/813 755/816/810 757/820/814 +f 754/817/811 755/816/810 756/819/813 +f 758/821/815 760/822/816 746/811/805 +f 758/821/815 746/811/805 759/823/817 +f 757/820/814 755/816/810 758/821/815 +f 755/816/810 760/822/816 758/821/815 +f 761/824/818 763/825/819 762/826/820 +f 765/827/821 764/828/822 766/829/823 +f 763/825/819 761/824/818 765/827/821 +f 761/824/818 764/828/822 765/827/821 +f 1010/830/824 767/831/825 753/818/812 +f 1010/830/824 753/818/812 1011/832/826 +f 766/829/823 764/828/822 1010/830/824 +f 764/828/822 767/831/825 1010/830/824 +f 772/833/827 770/834/828 773/835/829 +f 769/836/830 768/837/831 772/833/827 +f 769/836/830 772/833/827 771/838/832 +f 768/837/831 770/834/828 772/833/827 +f 774/839/833 775/840/834 763/825/819 +f 773/835/829 770/834/828 774/839/833 +f 770/834/828 775/840/834 774/839/833 +f 763/825/819 775/840/834 762/826/820 +f 780/841/835 778/842/836 781/843/837 +f 777/844/838 776/845/839 780/841/835 +f 777/844/838 780/841/835 779/846/840 +f 776/845/839 778/842/836 780/841/835 +f 782/847/841 778/842/836 769/836/830 +f 782/847/841 769/836/830 783/848/842 +f 781/843/837 778/842/836 782/847/841 +f 784/849/843 786/850/844 785/851/845 +f 788/852/846 787/853/847 789/854/848 +f 786/850/844 784/849/843 788/852/846 +f 784/849/843 787/853/847 788/852/846 +f 1022/855/849 791/856/850 790/857/851 +f 789/854/848 787/853/847 1022/855/849 +f 787/853/847 791/856/850 1022/855/849 +f 790/857/851 791/856/850 777/844/838 +f 792/858/852 794/859/853 793/860/854 +f 796/861/855 795/862/856 797/863/857 +f 794/859/853 792/858/852 796/861/855 +f 792/858/852 795/862/856 796/861/855 +f 1027/864/858 798/865/859 786/850/844 +f 797/863/857 795/862/856 1027/864/858 +f 795/862/856 798/865/859 1027/864/858 +f 786/850/844 798/865/859 785/851/845 +f 803/866/860 801/867/861 804/868/862 +f 800/869/863 799/870/864 803/866/860 +f 800/869/863 803/866/860 802/871/865 +f 799/870/864 801/867/861 803/866/860 +f 805/872/866 806/873/867 794/859/853 +f 804/868/862 801/867/861 805/872/866 +f 801/867/861 806/873/867 805/872/866 +f 794/859/853 806/873/867 793/860/854 +f 810/874/868 808/875/869 811/876/870 +f 807/877/871 810/874/868 809/878/872 +f 807/877/871 808/875/869 810/874/868 +f 812/879/873 814/880/874 800/869/863 +f 812/879/873 800/869/863 813/881/875 +f 811/876/870 808/875/869 812/879/873 +f 808/875/869 814/880/874 812/879/873 +f 816/882/876 818/883/877 819/884/878 +f 818/883/877 817/885/879 820/886/880 +f 816/882/876 815/887/881 818/883/877 +f 815/887/881 817/885/879 818/883/877 +f 821/888/882 823/889/883 807/877/871 +f 821/888/882 807/877/871 822/890/884 +f 820/886/880 817/885/879 821/888/882 +f 817/885/879 823/889/883 821/888/882 +f 828/891/885 826/892/886 829/893/887 +f 825/894/888 824/895/889 828/891/885 +f 825/894/888 828/891/885 827/896/890 +f 824/895/889 826/892/886 828/891/885 +f 830/897/891 816/882/876 819/884/878 +f 830/897/891 831/898/892 816/882/876 +f 829/893/887 826/892/886 830/897/891 +f 826/892/886 831/898/892 830/897/891 +f 836/899/893 834/900/894 837/901/895 +f 833/902/896 832/903/897 836/899/893 +f 833/902/896 836/899/893 835/904/898 +f 832/903/897 834/900/894 836/899/893 +f 1047/905/899 839/906/900 825/894/888 +f 1047/905/899 825/894/888 838/907/901 +f 837/901/895 834/900/894 1047/905/899 +f 834/900/894 839/906/900 1047/905/899 +f 841/908/902 843/909/903 844/910/904 +f 841/908/902 840/911/905 843/909/903 +f 840/911/905 842/912/906 845/913/907 +f 840/911/905 845/913/907 843/909/903 +f 1051/914/908 847/915/909 833/902/896 +f 1051/914/908 833/902/896 846/916/910 +f 845/913/907 842/912/906 847/915/909 +f 845/913/907 847/915/909 1051/914/908 +f 848/917/911 850/918/912 849/919/913 +f 852/920/914 851/921/915 853/922/916 +f 850/918/912 848/917/911 852/920/914 +f 848/917/911 851/921/915 852/920/914 +f 1057/923/917 841/908/902 844/910/904 +f 1057/923/917 854/924/918 841/908/902 +f 853/922/916 851/921/915 1057/923/917 +f 851/921/915 854/924/918 1057/923/917 +f 859/925/919 857/926/920 860/927/921 +f 856/928/922 855/929/923 859/925/919 +f 856/928/922 859/925/919 858/930/924 +f 855/929/923 857/926/920 859/925/919 +f 861/931/925 862/932/926 850/918/912 +f 860/927/921 857/926/920 861/931/925 +f 857/926/920 862/932/926 861/931/925 +f 850/918/912 862/932/926 849/919/913 +f 864/933/927 863/934/928 866/935/929 +f 863/934/928 865/936/930 866/935/929 +f 867/937/931 869/938/932 856/928/922 +f 867/937/931 856/928/922 868/939/933 +f 865/936/930 869/938/932 867/937/931 +f 869/938/932 865/936/930 863/934/928 +f 870/940/934 872/941/935 871/942/936 +f 874/943/937 873/944/938 875/945/939 +f 872/941/935 870/940/934 874/943/937 +f 870/940/934 873/944/938 874/943/937 +f 1072/946/940 877/947/941 864/933/927 +f 1072/946/940 864/933/927 876/948/942 +f 875/945/939 873/944/938 1072/946/940 +f 873/944/938 877/947/941 1072/946/940 +f 881/949/943 879/950/944 882/951/945 +f 557/607/601 878/952/946 881/949/943 +f 557/607/601 881/949/943 880/953/947 +f 878/952/946 879/950/944 881/949/943 +f 883/954/948 884/955/949 872/941/935 +f 882/951/945 879/950/944 883/954/948 +f 879/950/944 884/955/949 883/954/948 +f 872/941/935 884/955/949 871/942/936 +f 553/602/596 885/956/950 552/603/597 +f 885/956/950 553/602/596 886/957/951 +f 553/602/596 551/604/598 887/958/952 +f 553/602/596 887/958/952 886/957/951 +f 888/959/953 889/960/954 1082/961/955 +f 888/959/953 554/605/599 889/960/954 +f 887/958/952 551/604/598 554/605/599 +f 887/958/952 554/605/599 888/959/953 +f 889/960/954 554/605/599 555/608/602 +f 562/611/605 561/610/604 891/962/956 +f 562/611/605 891/962/956 890/963/957 +f 561/610/604 563/613/607 891/962/956 +f 1087/964/958 892/615/609 893/965/959 +f 1087/964/958 893/965/959 1088/966/960 +f 563/613/607 892/615/609 1087/964/958 +f 893/965/959 892/615/609 564/617/611 +f 570/621/615 894/967/961 568/619/613 +f 894/967/961 570/621/615 895/968/962 +f 570/621/615 571/623/617 896/969/963 +f 570/621/615 896/969/963 895/968/962 +f 897/970/964 898/624/618 890/963/957 +f 898/624/618 562/611/605 890/963/957 +f 896/969/963 571/623/617 898/624/618 +f 896/969/963 898/624/618 897/970/964 +f 577/629/623 899/971/965 575/627/621 +f 899/971/965 577/629/623 900/972/966 +f 577/629/623 578/631/625 900/972/966 +f 901/973/967 579/632/626 894/967/961 +f 578/631/625 579/632/626 901/973/967 +f 894/967/961 579/632/626 568/619/613 +f 584/636/630 902/974/968 582/634/628 +f 902/974/968 584/636/630 903/975/969 +f 584/636/630 904/976/970 903/975/969 +f 584/636/630 585/638/632 904/976/970 +f 905/977/971 586/639/633 587/641/635 +f 905/977/971 587/641/635 906/978/972 +f 904/976/970 585/638/632 586/639/633 +f 904/976/970 586/639/633 905/977/971 +f 594/647/641 907/979/973 591/643/637 +f 593/646/640 908/980/974 594/647/641 +f 907/979/973 594/647/641 909/981/975 +f 594/647/641 908/980/974 909/981/975 +f 910/982/976 595/648/642 596/650/644 +f 910/982/976 596/650/644 911/983/977 +f 908/980/974 595/648/642 910/982/976 +f 595/648/642 908/980/974 593/646/640 +f 913/984/978 603/653/647 914/985/979 +f 601/656/650 602/651/645 913/984/978 +f 601/656/650 913/984/978 912/986/980 +f 602/651/645 603/653/647 913/984/978 +f 915/987/981 604/657/651 907/979/973 +f 914/985/979 603/653/647 915/987/981 +f 603/653/647 604/657/651 915/987/981 +f 907/979/973 604/657/651 591/643/637 +f 610/659/653 916/988/982 609/664/658 +f 916/988/982 610/659/653 917/989/983 +f 610/659/653 611/661/655 917/989/983 +f 918/990/984 612/665/659 613/667/661 +f 918/990/984 613/667/661 919/991/985 +f 611/661/655 612/665/659 918/990/984 +f 619/671/665 920/992/986 617/669/663 +f 920/992/986 921/993/987 922/994/988 +f 920/992/986 619/671/665 921/993/987 +f 619/671/665 620/673/667 923/995/989 +f 619/671/665 923/995/989 921/993/987 +f 1121/996/990 924/674/668 925/997/991 +f 923/995/989 924/674/668 1121/996/990 +f 923/995/989 620/673/667 924/674/668 +f 925/997/991 924/674/668 621/676/670 +f 627/680/674 926/998/992 625/678/672 +f 628/682/676 927/999/993 627/680/674 +f 926/998/992 928/1000/994 929/1001/995 +f 926/998/992 627/680/674 928/1000/994 +f 627/680/674 927/999/993 928/1000/994 +f 1124/1002/996 930/683/677 920/992/986 +f 1124/1002/996 920/992/986 922/994/988 +f 927/999/993 930/683/677 1124/1002/996 +f 920/992/986 930/683/677 617/669/663 +f 930/683/677 927/999/993 628/682/676 +f 633/685/679 931/1003/997 631/688/682 +f 634/687/681 932/1004/998 633/685/679 +f 931/1003/997 633/685/679 933/1005/999 +f 633/685/679 932/1004/998 933/1005/999 +f 1127/1006/1000 926/998/992 929/1001/995 +f 932/1004/998 926/998/992 1127/1006/1000 +f 926/998/992 634/687/681 625/678/672 +f 926/998/992 932/1004/998 634/687/681 +f 639/690/684 934/1007/1001 638/695/689 +f 934/1007/1001 639/690/684 935/1008/1002 +f 639/690/684 936/1009/1003 935/1008/1002 +f 639/690/684 640/692/686 936/1009/1003 +f 937/1010/1004 641/696/690 931/1003/997 +f 936/1009/1003 640/692/686 641/696/690 +f 936/1009/1003 641/696/690 937/1010/1004 +f 931/1003/997 641/696/690 631/688/682 +f 647/701/695 938/1011/1005 644/699/693 +f 938/1011/1005 647/701/695 939/1012/1006 +f 647/701/695 648/703/697 939/1012/1006 +f 940/1013/1007 941/1014/1008 1135/1015/1009 +f 940/1013/1007 649/704/698 941/1014/1008 +f 648/703/697 649/704/698 940/1013/1007 +f 941/1014/1008 649/704/698 650/706/700 +f 656/710/704 942/1016/1010 654/708/702 +f 657/712/706 943/1017/1011 656/710/704 +f 942/1016/1010 944/1018/1012 945/1019/1013 +f 942/1016/1010 656/710/704 944/1018/1012 +f 656/710/704 943/1017/1011 944/1018/1012 +f 1140/1020/1014 946/713/707 947/1021/1015 +f 946/713/707 658/715/709 947/1021/1015 +f 943/1017/1011 946/713/707 1140/1020/1014 +f 946/713/707 943/1017/1011 657/712/706 +f 664/716/710 948/1022/1016 663/721/715 +f 665/718/712 949/1023/1017 948/1022/1016 +f 665/718/712 948/1022/1016 664/716/710 +f 663/721/715 948/1022/1016 950/1024/1018 +f 951/1025/1019 942/1016/1010 945/1019/1013 +f 951/1025/1019 666/722/716 942/1016/1010 +f 949/1023/1017 666/722/716 951/1025/1019 +f 942/1016/1010 666/722/716 654/708/702 +f 666/722/716 949/1023/1017 665/718/712 +f 671/729/723 672/724/718 953/1026/1020 +f 671/729/723 953/1026/1020 952/1027/1021 +f 672/724/718 673/726/720 953/1026/1020 +f 955/1028/1022 954/1029/1023 674/730/724 +f 955/1028/1022 674/730/724 675/732/726 +f 954/1029/1023 673/726/720 674/730/724 +f 681/736/730 956/1030/1024 679/734/728 +f 682/738/732 957/1031/1025 681/736/730 +f 956/1030/1024 681/736/730 958/1032/1026 +f 681/736/730 957/1031/1025 958/1032/1026 +f 1155/1033/1027 959/739/733 960/741/735 +f 1155/1033/1027 960/741/735 1156/1034/1028 +f 957/1031/1025 959/739/733 1155/1033/1027 +f 959/739/733 957/1031/1025 682/738/732 +f 687/743/737 961/1035/1029 688/744/738 +f 689/747/741 961/1035/1029 687/743/737 +f 961/1035/1029 689/747/741 963/1036/1030 +f 688/744/738 961/1035/1029 962/1037/1031 +f 964/1038/1032 965/748/742 956/1030/1024 +f 963/1036/1030 689/747/741 965/748/742 +f 963/1036/1030 965/748/742 964/1038/1032 +f 956/1030/1024 965/748/742 679/734/728 +f 695/750/744 966/1039/1033 694/755/749 +f 696/752/746 967/1040/1034 695/750/744 +f 966/1039/1033 695/750/744 968/1041/1035 +f 695/750/744 967/1040/1034 968/1041/1035 +f 969/1042/1036 697/756/750 688/744/738 +f 969/1042/1036 688/744/738 962/1037/1031 +f 967/1040/1034 697/756/750 969/1042/1036 +f 697/756/750 967/1040/1034 696/752/746 +f 700/759/753 970/1043/1037 703/761/755 +f 702/762/756 971/1044/1038 972/1045/1039 +f 702/762/756 703/761/755 971/1044/1038 +f 703/761/755 970/1043/1037 971/1044/1038 +f 973/1046/1040 704/763/757 705/765/759 +f 973/1046/1040 705/765/759 974/1047/1041 +f 970/1043/1037 704/763/757 973/1046/1040 +f 704/763/757 970/1043/1037 700/759/753 +f 976/1048/1042 712/770/764 977/1049/1043 +f 711/768/762 710/767/761 976/1048/1042 +f 711/768/762 976/1048/1042 975/1050/1044 +f 710/767/761 712/770/764 976/1048/1042 +f 1174/1051/1045 978/772/766 979/1052/1046 +f 978/772/766 713/774/768 979/1052/1046 +f 977/1049/1043 712/770/764 978/772/766 +f 977/1049/1043 978/772/766 1174/1051/1045 +f 719/775/769 980/1053/1047 718/780/774 +f 980/1053/1047 719/775/769 981/1054/1048 +f 719/775/769 720/777/771 981/1054/1048 +f 982/1055/1049 721/781/775 711/768/762 +f 982/1055/1049 711/768/762 975/1050/1044 +f 720/777/771 721/781/775 982/1055/1049 +f 727/783/777 983/1056/1050 726/788/782 +f 983/1056/1050 727/783/777 984/1057/1051 +f 727/783/777 728/785/779 984/1057/1051 +f 1184/1058/1052 985/789/783 986/1059/1053 +f 1184/1058/1052 986/1059/1053 1185/1060/1054 +f 728/785/779 985/789/783 1184/1058/1052 +f 986/1059/1053 985/789/783 729/790/784 +f 734/794/788 987/1061/1055 732/792/786 +f 735/796/790 988/1062/1056 734/794/788 +f 987/1061/1055 734/794/788 989/1063/1057 +f 734/794/788 988/1062/1056 989/1063/1057 +f 1191/1064/1058 990/797/791 991/1065/1059 +f 1191/1064/1058 991/1065/1059 1192/1066/1060 +f 988/1062/1056 990/797/791 1191/1064/1058 +f 991/1065/1059 990/797/791 736/799/793 +f 990/797/791 988/1062/1056 735/796/790 +f 993/1067/1061 743/804/798 994/1068/1062 +f 742/802/796 741/801/795 993/1067/1061 +f 742/802/796 993/1067/1061 992/1069/1063 +f 741/801/795 743/804/798 993/1067/1061 +f 1197/1070/1064 995/806/800 987/1061/1055 +f 994/1068/1062 743/804/798 995/806/800 +f 994/1068/1062 995/806/800 1197/1070/1064 +f 987/1061/1055 995/806/800 732/792/786 +f 749/808/802 996/1071/1065 748/813/807 +f 750/810/804 997/1072/1066 749/808/802 +f 996/1071/1065 749/808/802 998/1073/1067 +f 996/1071/1065 998/1073/1067 999/1074/1068 +f 749/808/802 997/1072/1066 998/1073/1067 +f 1000/1075/1069 751/814/808 742/802/796 +f 1000/1075/1069 742/802/796 992/1069/1063 +f 997/1072/1066 751/814/808 1000/1075/1069 +f 751/814/808 997/1072/1066 750/810/804 +f 756/819/813 1001/1076/1070 754/817/811 +f 1001/1076/1070 756/819/813 1002/1077/1071 +f 1001/1076/1070 1002/1077/1071 1003/1078/1072 +f 756/819/813 1004/1079/1073 1002/1077/1071 +f 756/819/813 757/820/814 1004/1079/1073 +f 1005/1080/1074 758/821/815 759/823/817 +f 1005/1080/1074 759/823/817 1006/1081/1075 +f 1004/1079/1073 757/820/814 758/821/815 +f 1004/1079/1073 758/821/815 1005/1080/1074 +f 765/827/821 1007/1082/1076 763/825/819 +f 766/829/823 1008/1083/1077 765/827/821 +f 1007/1082/1076 765/827/821 1009/1084/1078 +f 765/827/821 1008/1083/1077 1009/1084/1078 +f 1211/1085/1079 1010/830/824 1011/832/826 +f 1211/1085/1079 1011/832/826 1213/1086/1080 +f 1008/1083/1077 1010/830/824 1211/1085/1079 +f 1010/830/824 1008/1083/1077 766/829/823 +f 772/833/827 1012/1087/1081 771/838/832 +f 1012/1087/1081 772/833/827 1013/1088/1082 +f 772/833/827 773/835/829 1013/1088/1082 +f 1014/1089/1083 774/839/833 1007/1082/1076 +f 773/835/829 774/839/833 1014/1089/1083 +f 1007/1082/1076 774/839/833 763/825/819 +f 779/846/840 780/841/835 1016/1090/1084 +f 779/846/840 1016/1090/1084 1015/1091/1085 +f 780/841/835 781/843/837 1016/1090/1084 +f 1017/1092/1086 782/847/841 1018/1093/1087 +f 781/843/837 782/847/841 1017/1092/1086 +f 1018/1093/1087 782/847/841 783/848/842 +f 788/852/846 1019/1094/1088 786/850/844 +f 789/854/848 1020/1095/1089 788/852/846 +f 1019/1094/1088 788/852/846 1021/1096/1090 +f 788/852/846 1020/1095/1089 1021/1096/1090 +f 1226/1097/1091 1022/855/849 1023/1098/1092 +f 1226/1097/1091 1023/1098/1092 1227/1099/1093 +f 1020/1095/1089 1022/855/849 1226/1097/1091 +f 1023/1098/1092 1022/855/849 790/857/851 +f 1022/855/849 1020/1095/1089 789/854/848 +f 796/861/855 1024/1100/1094 794/859/853 +f 797/863/857 1025/1101/1095 796/861/855 +f 1024/1100/1094 796/861/855 1026/1102/1096 +f 796/861/855 1025/1101/1095 1026/1102/1096 +f 1232/1103/1097 1027/864/858 1019/1094/1088 +f 1025/1101/1095 1027/864/858 1232/1103/1097 +f 1019/1094/1088 1027/864/858 786/850/844 +f 1027/864/858 1025/1101/1095 797/863/857 +f 804/868/862 1028/1104/1098 803/866/860 +f 1029/1105/1099 803/866/860 1030/1106/1100 +f 803/866/860 1028/1104/1098 1030/1106/1100 +f 802/871/865 803/866/860 1029/1105/1099 +f 1031/1107/1101 805/872/866 1024/1100/1094 +f 1028/1104/1098 805/872/866 1031/1107/1101 +f 1024/1100/1094 805/872/866 794/859/853 +f 805/872/866 1028/1104/1098 804/868/862 +f 810/874/868 1032/1108/1102 809/878/872 +f 1032/1108/1102 1033/1109/1103 1034/1110/1104 +f 1032/1108/1102 810/874/868 1033/1109/1103 +f 810/874/868 811/876/870 1033/1109/1103 +f 1035/1111/1105 812/879/873 1036/1112/1106 +f 811/876/870 812/879/873 1035/1111/1105 +f 1036/1112/1106 812/879/873 813/881/875 +f 819/884/878 818/883/877 1038/1113/1107 +f 819/884/878 1038/1113/1107 1037/1114/1108 +f 818/883/877 820/886/880 1038/1113/1107 +f 1039/1115/1109 821/888/882 822/890/884 +f 1039/1115/1109 822/890/884 1040/1116/1110 +f 820/886/880 821/888/882 1039/1115/1109 +f 1041/1117/1111 828/891/885 1042/1118/1112 +f 828/891/885 829/893/887 1043/1119/1113 +f 828/891/885 1043/1119/1113 1042/1118/1112 +f 827/896/890 828/891/885 1041/1117/1111 +f 1044/1120/1114 830/897/891 819/884/878 +f 1044/1120/1114 819/884/878 1037/1114/1108 +f 1043/1119/1113 829/893/887 1044/1120/1114 +f 829/893/887 830/897/891 1044/1120/1114 +f 835/904/898 836/899/893 1046/1121/1115 +f 835/904/898 1046/1121/1115 1045/1122/1116 +f 836/899/893 837/901/895 1046/1121/1115 +f 1253/1123/1117 1047/905/899 1254/1124/1118 +f 1047/905/899 838/907/901 1254/1124/1118 +f 837/901/895 1047/905/899 1253/1123/1117 +f 1049/1125/1119 845/913/907 1050/1126/1120 +f 844/910/904 843/909/903 1049/1125/1119 +f 844/910/904 1049/1125/1119 1048/1127/1121 +f 843/909/903 845/913/907 1049/1125/1119 +f 1259/1128/1122 1051/914/908 1052/1129/1123 +f 1051/914/908 846/916/910 1052/1129/1123 +f 1050/1126/1120 845/913/907 1051/914/908 +f 1050/1126/1120 1051/914/908 1259/1128/1122 +f 852/920/914 1053/1130/1124 850/918/912 +f 1053/1130/1124 852/920/914 1054/1131/1125 +f 852/920/914 853/922/916 1055/1132/1126 +f 852/920/914 1055/1132/1126 1054/1131/1125 +f 1056/1133/1127 1057/923/917 1048/1127/1121 +f 1057/923/917 844/910/904 1048/1127/1121 +f 1055/1132/1126 853/922/916 1057/923/917 +f 1055/1132/1126 1057/923/917 1056/1133/1127 +f 859/925/919 1058/1134/1128 858/930/924 +f 860/927/921 1059/1135/1129 859/925/919 +f 1058/1134/1128 859/925/919 1060/1136/1130 +f 1058/1134/1128 1060/1136/1130 1061/1137/1131 +f 859/925/919 1059/1135/1129 1060/1136/1130 +f 1062/1138/1132 861/931/925 1053/1130/1124 +f 1059/1135/1129 861/931/925 1062/1138/1132 +f 1053/1130/1124 861/931/925 850/918/912 +f 861/931/925 1059/1135/1129 860/927/921 +f 865/936/930 1063/1139/1133 866/935/929 +f 865/936/930 1064/1140/1134 1063/1139/1133 +f 1063/1139/1133 1064/1140/1134 1065/1141/1135 +f 1064/1140/1134 865/936/930 1066/1142/1136 +f 1067/1143/1137 867/937/931 1068/1144/1138 +f 1066/1142/1136 865/936/930 867/937/931 +f 1066/1142/1136 867/937/931 1067/1143/1137 +f 1068/1144/1138 867/937/931 868/939/933 +f 874/943/937 1069/1145/1139 872/941/935 +f 1069/1145/1139 874/943/937 1070/1146/1140 +f 874/943/937 875/945/939 1070/1146/1140 +f 1071/1147/1141 1072/946/940 1073/1148/1142 +f 875/945/939 1072/946/940 1071/1147/1141 +f 1073/1148/1142 1072/946/940 876/948/942 +f 880/953/947 881/949/943 1075/1149/1143 +f 880/953/947 1075/1149/1143 1074/1150/1144 +f 881/949/943 882/951/945 1076/1151/1145 +f 881/949/943 1076/1151/1145 1075/1149/1143 +f 1077/1152/1146 883/954/948 1069/1145/1139 +f 1076/1151/1145 882/951/945 883/954/948 +f 1076/1151/1145 883/954/948 1077/1152/1146 +f 1069/1145/1139 883/954/948 872/941/935 +f 886/957/951 1078/1153/1147 885/956/950 +f 1078/1153/1147 1079/1154/1148 885/956/950 +f 887/958/952 1080/1155/1149 1078/1153/1147 +f 887/958/952 1078/1153/1147 886/957/951 +f 1081/1156/1150 1082/961/955 1287/1157/1151 +f 888/959/953 1080/1155/1149 887/958/952 +f 1082/961/955 1081/1156/1150 888/959/953 +f 1081/1156/1150 1080/1155/1149 888/959/953 +f 891/962/956 1083/1158/1152 890/963/957 +f 563/613/607 1084/1159/1153 1083/1158/1152 +f 563/613/607 1083/1158/1152 891/962/956 +f 890/963/957 1083/1158/1152 1085/1160/1154 +f 1086/1161/1155 1087/964/958 1088/966/960 +f 1086/1161/1155 1088/966/960 1292/1162/1156 +f 1084/1159/1153 1087/964/958 1086/1161/1155 +f 1087/964/958 1084/1159/1153 563/613/607 +f 896/969/963 1089/1163/1157 895/968/962 +f 1090/1164/1158 895/968/962 1091/1165/1159 +f 895/968/962 1089/1163/1157 1091/1165/1159 +f 894/967/961 895/968/962 1090/1164/1158 +f 1092/1166/1160 890/963/957 1085/1160/1154 +f 1092/1166/1160 897/970/964 890/963/957 +f 1089/1163/1157 897/970/964 1092/1166/1160 +f 897/970/964 1089/1163/1157 896/969/963 +f 578/631/625 1093/1167/1161 900/972/966 +f 1094/1168/1162 900/972/966 1095/1169/1163 +f 900/972/966 1093/1167/1161 1095/1169/1163 +f 899/971/965 900/972/966 1094/1168/1162 +f 1096/1170/1164 894/967/961 1090/1164/1158 +f 894/967/961 1096/1170/1164 901/973/967 +f 1096/1170/1164 1093/1167/1161 578/631/625 +f 1096/1170/1164 578/631/625 901/973/967 +f 903/975/969 1097/1171/1165 902/974/968 +f 1097/1171/1165 903/975/969 1098/1172/1166 +f 903/975/969 1099/1173/1167 1098/1172/1166 +f 903/975/969 904/976/970 1099/1173/1167 +f 1099/1173/1167 904/976/970 1100/1174/1168 +f 906/978/972 1100/1174/1168 905/977/971 +f 1100/1174/1168 904/976/970 905/977/971 +f 1101/1175/1169 1100/1174/1168 906/978/972 +f 909/981/975 1102/1176/1170 907/979/973 +f 908/980/974 1103/1177/1171 1102/1176/1170 +f 908/980/974 1102/1176/1170 909/981/975 +f 907/979/973 1102/1176/1170 1104/1178/1172 +f 1105/1179/1173 910/982/976 1106/1180/1174 +f 1103/1177/1171 910/982/976 1105/1179/1173 +f 1106/1180/1174 910/982/976 911/983/977 +f 910/982/976 1103/1177/1171 908/980/974 +f 914/985/979 1107/1181/1175 913/984/978 +f 912/986/980 913/984/978 1109/1182/1176 +f 912/986/980 1109/1182/1176 1108/1183/1177 +f 913/984/978 1107/1181/1175 1109/1182/1176 +f 1110/1184/1178 907/979/973 1104/1178/1172 +f 907/979/973 1110/1184/1178 915/987/981 +f 1110/1184/1178 914/985/979 915/987/981 +f 1110/1184/1178 1107/1181/1175 914/985/979 +f 917/989/983 1111/1185/1179 916/988/982 +f 1111/1185/1179 1112/1186/1180 916/988/982 +f 611/661/655 1113/1187/1181 1111/1185/1179 +f 611/661/655 1111/1185/1179 917/989/983 +f 919/991/985 1114/1188/1182 918/990/984 +f 1114/1188/1182 1113/1187/1181 611/661/655 +f 1114/1188/1182 611/661/655 918/990/984 +f 1115/1189/1183 1114/1188/1182 919/991/985 +f 921/993/987 1116/1190/1184 922/994/988 +f 923/995/989 1116/1190/1184 921/993/987 +f 922/994/988 1116/1190/1184 1117/1191/1185 +f 1116/1190/1184 923/995/989 1118/1192/1186 +f 1119/1193/1187 1121/996/990 1120/1194/1188 +f 1118/1192/1186 923/995/989 1121/996/990 +f 1118/1192/1186 1121/996/990 1119/1193/1187 +f 1120/1194/1188 1121/996/990 925/997/991 +f 928/1000/994 1122/1195/1189 929/1001/995 +f 927/999/993 1122/1195/1189 928/1000/994 +f 927/999/993 1123/1196/1190 1122/1195/1189 +f 929/1001/995 1122/1195/1189 1324/1197/1191 +f 1327/1198/1192 1124/1002/996 922/994/988 +f 1327/1198/1192 922/994/988 1117/1191/1185 +f 1123/1196/1190 1124/1002/996 1327/1198/1192 +f 1124/1002/996 1123/1196/1190 927/999/993 +f 933/1005/999 1125/1199/1193 931/1003/997 +f 932/1004/998 1126/1200/1194 933/1005/999 +f 933/1005/999 1126/1200/1194 1125/1199/1193 +f 1330/1201/1195 1127/1006/1000 929/1001/995 +f 1330/1201/1195 929/1001/995 1324/1197/1191 +f 1126/1200/1194 1127/1006/1000 1330/1201/1195 +f 1127/1006/1000 1126/1200/1194 932/1004/998 +f 935/1008/1002 1128/1202/1196 934/1007/1001 +f 1128/1202/1196 935/1008/1002 1129/1203/1197 +f 935/1008/1002 936/1009/1003 1129/1203/1197 +f 1129/1203/1197 936/1009/1003 1130/1204/1198 +f 931/1003/997 1130/1204/1198 937/1010/1004 +f 1130/1204/1198 936/1009/1003 937/1010/1004 +f 1125/1199/1193 1130/1204/1198 931/1003/997 +f 939/1012/1006 1131/1205/1199 938/1011/1005 +f 1131/1205/1199 1132/1206/1200 938/1011/1005 +f 648/703/697 1133/1207/1201 1131/1205/1199 +f 648/703/697 1131/1205/1199 939/1012/1006 +f 1134/1208/1202 1135/1015/1009 1338/1209/1203 +f 1135/1015/1009 1134/1208/1202 940/1013/1007 +f 1134/1208/1202 1133/1207/1201 648/703/697 +f 1134/1208/1202 648/703/697 940/1013/1007 +f 943/1017/1011 1136/1210/1204 944/1018/1012 +f 945/1019/1013 1137/1211/1205 1138/1212/1206 +f 945/1019/1013 944/1018/1012 1137/1211/1205 +f 944/1018/1012 1136/1210/1204 1137/1211/1205 +f 1139/1213/1207 1140/1020/1014 1343/1214/1208 +f 1140/1020/1014 947/1021/1015 1343/1214/1208 +f 1136/1210/1204 1140/1020/1014 1139/1213/1207 +f 1140/1020/1014 1136/1210/1204 943/1017/1011 +f 948/1022/1016 1141/1215/1209 1142/1216/1210 +f 948/1022/1016 1142/1216/1210 950/1024/1018 +f 949/1023/1017 1143/1217/1211 1141/1215/1209 +f 949/1023/1017 1141/1215/1209 948/1022/1016 +f 1144/1218/1212 945/1019/1013 1138/1212/1206 +f 1144/1218/1212 951/1025/1019 945/1019/1013 +f 1143/1217/1211 951/1025/1019 1144/1218/1212 +f 951/1025/1019 1143/1217/1211 949/1023/1017 +f 953/1026/1020 1145/1219/1213 952/1027/1021 +f 1145/1219/1213 1146/1220/1214 952/1027/1021 +f 673/726/720 1147/1221/1215 1145/1219/1213 +f 673/726/720 1145/1219/1213 953/1026/1020 +f 1148/1222/1216 1149/1223/1217 1352/1224/1218 +f 954/1029/1023 1147/1221/1215 673/726/720 +f 1149/1223/1217 1148/1222/1216 954/1029/1023 +f 1149/1223/1217 954/1029/1023 955/1028/1022 +f 1148/1222/1216 1147/1221/1215 954/1029/1023 +f 958/1032/1026 1150/1225/1219 956/1030/1024 +f 1150/1225/1219 1151/1226/1220 956/1030/1024 +f 957/1031/1025 1150/1225/1219 958/1032/1026 +f 957/1031/1025 1152/1227/1221 1150/1225/1219 +f 1151/1226/1220 1150/1225/1219 1153/1228/1222 +f 1154/1229/1223 1155/1033/1027 1357/1230/1224 +f 1155/1033/1027 1156/1034/1028 1357/1230/1224 +f 1152/1227/1221 1155/1033/1027 1154/1229/1223 +f 1155/1033/1027 1152/1227/1221 957/1031/1025 +f 961/1035/1029 1157/1231/1225 962/1037/1031 +f 963/1036/1030 1158/1232/1226 1157/1231/1225 +f 963/1036/1030 1157/1231/1225 961/1035/1029 +f 962/1037/1031 1157/1231/1225 1159/1233/1227 +f 1160/1234/1228 1151/1226/1220 1153/1228/1222 +f 1160/1234/1228 964/1038/1032 1151/1226/1220 +f 1158/1232/1226 964/1038/1032 1160/1234/1228 +f 1151/1226/1220 964/1038/1032 956/1030/1024 +f 964/1038/1032 1158/1232/1226 963/1036/1030 +f 968/1041/1035 1161/1235/1229 966/1039/1033 +f 967/1040/1034 1162/1236/1230 968/1041/1035 +f 1161/1235/1229 968/1041/1035 1163/1237/1231 +f 1161/1235/1229 1163/1237/1231 1164/1238/1232 +f 968/1041/1035 1162/1236/1230 1163/1237/1231 +f 1165/1239/1233 962/1037/1031 1159/1233/1227 +f 962/1037/1031 1165/1239/1233 969/1042/1036 +f 1165/1239/1233 967/1040/1034 969/1042/1036 +f 1165/1239/1233 1162/1236/1230 967/1040/1034 +f 971/1044/1038 1166/1240/1234 972/1045/1039 +f 970/1043/1037 1167/1241/1235 971/1044/1038 +f 1167/1241/1235 1166/1240/1234 971/1044/1038 +f 972/1045/1039 1166/1240/1234 1168/1242/1236 +f 974/1047/1041 1169/1243/1237 973/1046/1040 +f 1169/1243/1237 1167/1241/1235 970/1043/1037 +f 1169/1243/1237 970/1043/1037 973/1046/1040 +f 1170/1244/1238 1169/1243/1237 974/1047/1041 +f 976/1048/1042 1171/1245/1239 975/1050/1044 +f 977/1049/1043 1171/1245/1239 976/1048/1042 +f 975/1050/1044 1171/1245/1239 1172/1246/1240 +f 1171/1245/1239 977/1049/1043 1173/1247/1241 +f 1374/1248/1242 1174/1051/1045 1175/1249/1243 +f 1173/1247/1241 977/1049/1043 1174/1051/1045 +f 1173/1247/1241 1174/1051/1045 1374/1248/1242 +f 1175/1249/1243 1174/1051/1045 979/1052/1046 +f 981/1054/1048 1176/1250/1244 980/1053/1047 +f 720/777/771 1177/1251/1245 981/1054/1048 +f 1176/1250/1244 981/1054/1048 1178/1252/1246 +f 981/1054/1048 1177/1251/1245 1178/1252/1246 +f 1179/1253/1247 975/1050/1044 1172/1246/1240 +f 975/1050/1044 1179/1253/1247 982/1055/1049 +f 1179/1253/1247 1177/1251/1245 720/777/771 +f 1179/1253/1247 720/777/771 982/1055/1049 +f 984/1057/1051 1180/1254/1248 983/1056/1050 +f 1180/1254/1248 1181/1255/1249 983/1056/1050 +f 728/785/779 1182/1256/1250 1180/1254/1248 +f 728/785/779 1180/1254/1248 984/1057/1051 +f 1183/1257/1251 1184/1058/1052 1185/1060/1054 +f 1183/1257/1251 1185/1060/1054 1383/1258/1252 +f 1182/1256/1250 1184/1058/1052 1183/1257/1251 +f 1184/1058/1052 1182/1256/1250 728/785/779 +f 989/1063/1057 1186/1259/1253 987/1061/1055 +f 1186/1259/1253 1187/1260/1254 987/1061/1055 +f 988/1062/1056 1186/1259/1253 989/1063/1057 +f 988/1062/1056 1188/1261/1255 1186/1259/1253 +f 1187/1260/1254 1186/1259/1253 1189/1262/1256 +f 1190/1263/1257 1191/1064/1058 1192/1066/1060 +f 1190/1263/1257 1192/1066/1060 1388/1264/1258 +f 1188/1261/1255 1191/1064/1058 1190/1263/1257 +f 1191/1064/1058 1188/1261/1255 988/1062/1056 +f 994/1068/1062 1193/1265/1259 993/1067/1061 +f 992/1069/1063 1194/1266/1260 1195/1267/1261 +f 992/1069/1063 993/1067/1061 1194/1266/1260 +f 993/1067/1061 1193/1265/1259 1194/1266/1260 +f 1196/1268/1262 1197/1070/1064 1187/1260/1254 +f 1196/1268/1262 1187/1260/1254 1189/1262/1256 +f 1193/1265/1259 1197/1070/1064 1196/1268/1262 +f 1187/1260/1254 1197/1070/1064 987/1061/1055 +f 1197/1070/1064 1193/1265/1259 994/1068/1062 +f 997/1072/1066 1198/1269/1263 998/1073/1067 +f 999/1074/1068 998/1073/1067 1199/1270/1264 +f 999/1074/1068 1199/1270/1264 1200/1271/1265 +f 998/1073/1067 1198/1269/1263 1199/1270/1264 +f 1201/1272/1266 992/1069/1063 1195/1267/1261 +f 992/1069/1063 1201/1272/1266 1000/1075/1069 +f 1201/1272/1266 997/1072/1066 1000/1075/1069 +f 1201/1272/1266 1198/1269/1263 997/1072/1066 +f 1202/1273/1267 1002/1077/1071 1203/1274/1268 +f 1002/1077/1071 1204/1275/1269 1203/1274/1268 +f 1003/1078/1072 1002/1077/1071 1202/1273/1267 +f 1002/1077/1071 1004/1079/1073 1204/1275/1269 +f 1205/1276/1270 1006/1081/1075 1206/1277/1271 +f 1204/1275/1269 1004/1079/1073 1205/1276/1270 +f 1006/1081/1075 1205/1276/1270 1005/1080/1074 +f 1205/1276/1270 1004/1079/1073 1005/1080/1074 +f 1009/1084/1078 1207/1278/1272 1007/1082/1076 +f 1207/1278/1272 1208/1279/1273 1007/1082/1076 +f 1008/1083/1077 1207/1278/1272 1009/1084/1078 +f 1008/1083/1077 1209/1280/1274 1207/1278/1272 +f 1208/1279/1273 1207/1278/1272 1210/1281/1275 +f 1405/1282/1276 1211/1085/1079 1212/1283/1277 +f 1211/1085/1079 1213/1086/1080 1212/1283/1277 +f 1209/1280/1274 1211/1085/1079 1405/1282/1276 +f 1211/1085/1079 1209/1280/1274 1008/1083/1077 +f 1013/1088/1082 1214/1284/1278 1012/1087/1081 +f 773/835/829 1215/1285/1279 1013/1088/1082 +f 1214/1284/1278 1013/1088/1082 1216/1286/1280 +f 1013/1088/1082 1215/1285/1279 1216/1286/1280 +f 1217/1287/1281 1208/1279/1273 1210/1281/1275 +f 1007/1082/1076 1217/1287/1281 1014/1089/1083 +f 1217/1287/1281 1215/1285/1279 773/835/829 +f 1217/1287/1281 773/835/829 1014/1089/1083 +f 1208/1279/1273 1217/1287/1281 1007/1082/1076 +f 1016/1090/1084 1218/1288/1282 1015/1091/1085 +f 1218/1288/1282 1219/1289/1283 1015/1091/1085 +f 781/843/837 1220/1290/1284 1218/1288/1282 +f 781/843/837 1218/1288/1282 1016/1090/1084 +f 1018/1093/1087 1221/1291/1285 1017/1092/1086 +f 1221/1291/1285 1220/1290/1284 781/843/837 +f 1221/1291/1285 781/843/837 1017/1092/1086 +f 1222/1292/1286 1221/1291/1285 1018/1093/1087 +f 1021/1096/1090 1223/1293/1287 1019/1094/1088 +f 1223/1293/1287 1224/1294/1288 1019/1094/1088 +f 1020/1095/1089 1223/1293/1287 1021/1096/1090 +f 1020/1095/1089 1225/1295/1289 1223/1293/1287 +f 1226/1097/1091 1227/1099/1093 1420/1296/1290 +f 1226/1097/1091 1225/1295/1289 1020/1095/1089 +f 1026/1102/1096 1228/1297/1291 1024/1100/1094 +f 1025/1101/1095 1229/1298/1292 1026/1102/1096 +f 1228/1297/1291 1026/1102/1096 1230/1299/1293 +f 1231/1300/1294 1232/1103/1097 1224/1294/1288 +f 1229/1298/1292 1232/1103/1097 1231/1300/1294 +f 1224/1294/1288 1232/1103/1097 1019/1094/1088 +f 1232/1103/1097 1229/1298/1292 1025/1101/1095 +f 1028/1104/1098 1233/1301/1295 1030/1106/1100 +f 1029/1105/1099 1030/1106/1100 1235/1302/1296 +f 1029/1105/1099 1235/1302/1296 1234/1303/1297 +f 1030/1106/1100 1233/1301/1295 1235/1302/1296 +f 1236/1304/1298 1228/1297/1291 1230/1299/1293 +f 1024/1100/1094 1236/1304/1298 1031/1107/1101 +f 1236/1304/1298 1028/1104/1098 1031/1107/1101 +f 1228/1297/1291 1236/1304/1298 1024/1100/1094 +f 1236/1304/1298 1233/1301/1295 1028/1104/1098 +f 1033/1109/1103 1237/1305/1299 1034/1110/1104 +f 811/876/870 1238/1306/1300 1033/1109/1103 +f 1238/1306/1300 1237/1305/1299 1033/1109/1103 +f 1034/1110/1104 1237/1305/1299 1239/1307/1301 +f 1036/1112/1106 1240/1308/1302 1035/1111/1105 +f 1240/1308/1302 1238/1306/1300 811/876/870 +f 1240/1308/1302 811/876/870 1035/1111/1105 +f 1241/1309/1303 1240/1308/1302 1036/1112/1106 +f 1038/1113/1107 1242/1310/1304 1037/1114/1108 +f 820/886/880 1243/1311/1305 1242/1310/1304 +f 820/886/880 1242/1310/1304 1038/1113/1107 +f 1037/1114/1108 1242/1310/1304 1244/1312/1306 +f 1245/1313/1307 1039/1115/1109 1246/1314/1308 +f 1243/1311/1305 1039/1115/1109 1245/1313/1307 +f 1039/1115/1109 1243/1311/1305 820/886/880 +f 1246/1314/1308 1039/1115/1109 1040/1116/1110 +f 1042/1118/1112 1247/1315/1309 1041/1117/1111 +f 1247/1315/1309 1042/1118/1112 1248/1316/1310 +f 1042/1118/1112 1043/1119/1113 1248/1316/1310 +f 1249/1317/1311 1037/1114/1108 1244/1312/1306 +f 1037/1114/1108 1249/1317/1311 1044/1120/1114 +f 1249/1317/1311 1043/1119/1113 1044/1120/1114 +f 1046/1121/1115 1250/1318/1312 1045/1122/1116 +f 837/901/895 1251/1319/1313 1250/1318/1312 +f 837/901/895 1250/1318/1312 1046/1121/1115 +f 1045/1122/1116 1250/1318/1312 1252/1320/1314 +f 1253/1123/1117 1254/1124/1118 1446/1321/1315 +f 1253/1123/1117 1251/1319/1313 837/901/895 +f 1049/1125/1119 1255/1322/1316 1048/1127/1121 +f 1255/1322/1316 1256/1323/1317 1048/1127/1121 +f 1050/1126/1120 1255/1322/1316 1049/1125/1119 +f 1050/1126/1120 1257/1324/1318 1255/1322/1316 +f 1258/1325/1319 1259/1128/1122 1260/1326/1320 +f 1259/1128/1122 1052/1129/1123 1260/1326/1320 +f 1257/1324/1318 1259/1128/1122 1258/1325/1319 +f 1259/1128/1122 1257/1324/1318 1050/1126/1120 +f 1055/1132/1126 1261/1327/1321 1054/1131/1125 +f 1262/1328/1322 1054/1131/1125 1263/1329/1323 +f 1054/1131/1125 1261/1327/1321 1263/1329/1323 +f 1053/1130/1124 1054/1131/1125 1262/1328/1322 +f 1264/1330/1324 1056/1133/1127 1256/1323/1317 +f 1261/1327/1321 1056/1133/1127 1264/1330/1324 +f 1256/1323/1317 1056/1133/1127 1048/1127/1121 +f 1056/1133/1127 1261/1327/1321 1055/1132/1126 +f 1059/1135/1129 1265/1331/1325 1060/1136/1130 +f 1266/1332/1326 1060/1136/1130 1267/1333/1327 +f 1060/1136/1130 1265/1331/1325 1267/1333/1327 +f 1061/1137/1131 1060/1136/1130 1266/1332/1326 +f 1268/1334/1328 1053/1130/1124 1262/1328/1322 +f 1053/1130/1124 1268/1334/1328 1062/1138/1132 +f 1268/1334/1328 1059/1135/1129 1062/1138/1132 +f 1268/1334/1328 1265/1331/1325 1059/1135/1129 +f 1064/1140/1134 1269/1335/1329 1065/1141/1135 +f 1066/1142/1136 1270/1336/1330 1064/1140/1134 +f 1270/1336/1330 1269/1335/1329 1064/1140/1134 +f 1065/1141/1135 1269/1335/1329 1271/1337/1331 +f 1067/1143/1137 1270/1336/1330 1066/1142/1136 +f 1273/1338/1332 1272/1339/1333 1067/1143/1137 +f 1273/1338/1332 1067/1143/1137 1068/1144/1138 +f 1272/1339/1333 1270/1336/1330 1067/1143/1137 +f 1070/1146/1140 1274/1340/1334 1069/1145/1139 +f 1274/1340/1334 1275/1341/1335 1069/1145/1139 +f 875/945/939 1276/1342/1336 1274/1340/1334 +f 875/945/939 1274/1340/1334 1070/1146/1140 +f 1277/1343/1337 1071/1147/1141 1278/1344/1338 +f 1276/1342/1336 1071/1147/1141 1277/1343/1337 +f 1071/1147/1141 1276/1342/1336 875/945/939 +f 1278/1344/1338 1071/1147/1141 1073/1148/1142 +f 1075/1149/1143 1279/1345/1339 1074/1150/1144 +f 1279/1345/1339 1075/1149/1143 1280/1346/1340 +f 1075/1149/1143 1076/1151/1145 1281/1347/1341 +f 1075/1149/1143 1281/1347/1341 1280/1346/1340 +f 1282/1348/1342 1077/1152/1146 1275/1341/1335 +f 1281/1347/1341 1076/1151/1145 1282/1348/1342 +f 1076/1151/1145 1077/1152/1146 1282/1348/1342 +f 1275/1341/1335 1077/1152/1146 1069/1145/1139 +f 1078/1153/1147 1283/1349/1343 1079/1154/1148 +f 1283/1349/1343 1284/1350/1344 1079/1154/1148 +f 1080/1155/1149 1285/1351/1345 1283/1349/1343 +f 1080/1155/1149 1283/1349/1343 1078/1153/1147 +f 1286/1352/1346 1287/1157/1151 1477/1353/1347 +f 1081/1156/1150 1285/1351/1345 1080/1155/1149 +f 1287/1157/1151 1286/1352/1346 1081/1156/1150 +f 1286/1352/1346 1285/1351/1345 1081/1156/1150 +f 1083/1158/1152 1288/1354/1348 1085/1160/1154 +f 1084/1159/1153 1288/1354/1348 1083/1158/1152 +f 1084/1159/1153 1289/1355/1349 1288/1354/1348 +f 1085/1160/1154 1288/1354/1348 1290/1356/1350 +f 1291/1357/1351 1292/1162/1156 1483/1358/1352 +f 1292/1162/1156 1291/1357/1351 1086/1161/1155 +f 1291/1357/1351 1289/1355/1349 1084/1159/1153 +f 1291/1357/1351 1084/1159/1153 1086/1161/1155 +f 1091/1165/1159 1293/1359/1353 1090/1164/1158 +f 1089/1163/1157 1294/1360/1354 1293/1359/1353 +f 1089/1163/1157 1293/1359/1353 1091/1165/1159 +f 1090/1164/1158 1293/1359/1353 1295/1361/1355 +f 1296/1362/1356 1085/1160/1154 1290/1356/1350 +f 1085/1160/1154 1296/1362/1356 1092/1166/1160 +f 1296/1362/1356 1294/1360/1354 1089/1163/1157 +f 1296/1362/1356 1089/1163/1157 1092/1166/1160 +f 1095/1169/1163 1297/1363/1357 1094/1168/1162 +f 1093/1167/1161 1298/1364/1358 1297/1363/1357 +f 1093/1167/1161 1297/1363/1357 1095/1169/1163 +f 1094/1168/1162 1297/1363/1357 1299/1365/1359 +f 1300/1366/1360 1090/1164/1158 1295/1361/1355 +f 1090/1164/1158 1300/1366/1360 1096/1170/1164 +f 1300/1366/1360 1093/1167/1161 1096/1170/1164 +f 1300/1366/1360 1298/1364/1358 1093/1167/1161 +f 1098/1172/1166 1301/1367/1361 1302/1368/1362 +f 1098/1172/1166 1302/1368/1362 1097/1171/1165 +f 1099/1173/1167 1303/1369/1363 1098/1172/1166 +f 1303/1369/1363 1301/1367/1361 1098/1172/1166 +f 1101/1175/1169 1304/1370/1364 1100/1174/1168 +f 1304/1370/1364 1303/1369/1363 1099/1173/1167 +f 1304/1370/1364 1099/1173/1167 1100/1174/1168 +f 1305/1371/1365 1304/1370/1364 1101/1175/1169 +f 1102/1176/1170 1306/1372/1366 1104/1178/1172 +f 1103/1177/1171 1306/1372/1366 1102/1176/1170 +f 1103/1177/1171 1307/1373/1367 1306/1372/1366 +f 1104/1178/1172 1306/1372/1366 1308/1374/1368 +f 1310/1375/1369 1309/1376/1370 1105/1179/1173 +f 1310/1375/1369 1105/1179/1173 1106/1180/1174 +f 1309/1376/1370 1307/1373/1367 1103/1177/1171 +f 1309/1376/1370 1103/1177/1171 1105/1179/1173 +f 1109/1182/1176 1311/1377/1371 1108/1183/1177 +f 1107/1181/1175 1311/1377/1371 1109/1182/1176 +f 1107/1181/1175 1312/1378/1372 1311/1377/1371 +f 1108/1183/1177 1311/1377/1371 1313/1379/1373 +f 1314/1380/1374 1104/1178/1172 1308/1374/1368 +f 1104/1178/1172 1314/1380/1374 1110/1184/1178 +f 1314/1380/1374 1107/1181/1175 1110/1184/1178 +f 1314/1380/1374 1312/1378/1372 1107/1181/1175 +f 1111/1185/1179 1315/1381/1375 1316/1382/1376 +f 1111/1185/1179 1316/1382/1376 1112/1186/1180 +f 1113/1187/1181 1317/1383/1377 1111/1185/1179 +f 1317/1383/1377 1315/1381/1375 1111/1185/1179 +f 1115/1189/1183 1318/1384/1378 1114/1188/1182 +f 1318/1384/1378 1317/1383/1377 1113/1187/1181 +f 1318/1384/1378 1113/1187/1181 1114/1188/1182 +f 1319/1385/1379 1318/1384/1378 1115/1189/1183 +f 1116/1190/1184 1320/1386/1380 1117/1191/1185 +f 1118/1192/1186 1320/1386/1380 1116/1190/1184 +f 1117/1191/1185 1320/1386/1380 1321/1387/1381 +f 1320/1386/1380 1118/1192/1186 1322/1388/1382 +f 1323/1389/1383 1322/1388/1382 1119/1193/1187 +f 1323/1389/1383 1119/1193/1187 1120/1194/1188 +f 1322/1388/1382 1118/1192/1186 1119/1193/1187 +f 1123/1196/1190 1324/1197/1191 1122/1195/1189 +f 1123/1196/1190 1325/1390/1384 1324/1197/1191 +f 1324/1197/1191 1325/1390/1384 1326/1391/1385 +f 1518/1392/1386 1327/1198/1192 1117/1191/1185 +f 1518/1392/1386 1117/1191/1185 1321/1387/1381 +f 1325/1390/1384 1327/1198/1192 1518/1392/1386 +f 1327/1198/1192 1325/1390/1384 1123/1196/1190 +f 1126/1200/1194 1329/1393/1387 1328/1394/1388 +f 1126/1200/1194 1328/1394/1388 1125/1199/1193 +f 1326/1391/1385 1330/1201/1195 1324/1197/1191 +f 1329/1393/1387 1330/1201/1195 1326/1391/1385 +f 1330/1201/1195 1329/1393/1387 1126/1200/1194 +f 1129/1203/1197 1331/1395/1389 1332/1396/1390 +f 1129/1203/1197 1332/1396/1390 1128/1202/1196 +f 1125/1199/1193 1333/1397/1391 1130/1204/1198 +f 1333/1397/1391 1129/1203/1197 1130/1204/1198 +f 1328/1394/1388 1333/1397/1391 1125/1199/1193 +f 1333/1397/1391 1331/1395/1389 1129/1203/1197 +f 1131/1205/1199 1334/1398/1392 1132/1206/1200 +f 1334/1398/1392 1335/1399/1393 1132/1206/1200 +f 1133/1207/1201 1336/1400/1394 1334/1398/1392 +f 1133/1207/1201 1334/1398/1392 1131/1205/1199 +f 1337/1401/1395 1338/1209/1203 1534/1402/1396 +f 1134/1208/1202 1336/1400/1394 1133/1207/1201 +f 1338/1209/1203 1337/1401/1395 1134/1208/1202 +f 1337/1401/1395 1336/1400/1394 1134/1208/1202 +f 1136/1210/1204 1339/1403/1397 1137/1211/1205 +f 1138/1212/1206 1340/1404/1398 1341/1405/1399 +f 1138/1212/1206 1137/1211/1205 1340/1404/1398 +f 1137/1211/1205 1339/1403/1397 1340/1404/1398 +f 1342/1406/1400 1343/1214/1208 1540/1407/1401 +f 1343/1214/1208 1342/1406/1400 1139/1213/1207 +f 1342/1406/1400 1339/1403/1397 1136/1210/1204 +f 1342/1406/1400 1136/1210/1204 1139/1213/1207 +f 1141/1215/1209 1344/1408/1402 1345/1409/1403 +f 1141/1215/1209 1345/1409/1403 1142/1216/1210 +f 1143/1217/1211 1346/1410/1404 1344/1408/1402 +f 1143/1217/1211 1344/1408/1402 1141/1215/1209 +f 1347/1411/1405 1138/1212/1206 1341/1405/1399 +f 1138/1212/1206 1347/1411/1405 1144/1218/1212 +f 1347/1411/1405 1346/1410/1404 1143/1217/1211 +f 1347/1411/1405 1143/1217/1211 1144/1218/1212 +f 1145/1219/1213 1348/1412/1406 1146/1220/1214 +f 1348/1412/1406 1349/1413/1407 1146/1220/1214 +f 1147/1221/1215 1350/1414/1408 1348/1412/1406 +f 1147/1221/1215 1348/1412/1406 1145/1219/1213 +f 1351/1415/1409 1352/1224/1218 1550/1416/1410 +f 1148/1222/1216 1350/1414/1408 1147/1221/1215 +f 1352/1224/1218 1351/1415/1409 1148/1222/1216 +f 1351/1415/1409 1350/1414/1408 1148/1222/1216 +f 1150/1225/1219 1353/1417/1411 1153/1228/1222 +f 1152/1227/1221 1353/1417/1411 1150/1225/1219 +f 1152/1227/1221 1354/1418/1412 1353/1417/1411 +f 1153/1228/1222 1353/1417/1411 1355/1419/1413 +f 1356/1420/1414 1357/1230/1224 1556/1421/1415 +f 1357/1230/1224 1356/1420/1414 1154/1229/1223 +f 1356/1420/1414 1354/1418/1412 1152/1227/1221 +f 1356/1420/1414 1152/1227/1221 1154/1229/1223 +f 1157/1231/1225 1358/1422/1416 1159/1233/1227 +f 1158/1232/1226 1359/1423/1417 1358/1422/1416 +f 1158/1232/1226 1358/1422/1416 1157/1231/1225 +f 1159/1233/1227 1358/1422/1416 1360/1424/1418 +f 1361/1425/1419 1153/1228/1222 1355/1419/1413 +f 1153/1228/1222 1361/1425/1419 1160/1234/1228 +f 1361/1425/1419 1359/1423/1417 1158/1232/1226 +f 1361/1425/1419 1158/1232/1226 1160/1234/1228 +f 1163/1237/1231 1362/1426/1420 1164/1238/1232 +f 1162/1236/1230 1363/1427/1421 1362/1426/1420 +f 1162/1236/1230 1362/1426/1420 1163/1237/1231 +f 1164/1238/1232 1362/1426/1420 1364/1428/1422 +f 1365/1429/1423 1159/1233/1227 1360/1424/1418 +f 1159/1233/1227 1365/1429/1423 1165/1239/1233 +f 1365/1429/1423 1162/1236/1230 1165/1239/1233 +f 1365/1429/1423 1363/1427/1421 1162/1236/1230 +f 1166/1240/1234 1366/1430/1424 1168/1242/1236 +f 1167/1241/1235 1367/1431/1425 1166/1240/1234 +f 1367/1431/1425 1366/1430/1424 1166/1240/1234 +f 1168/1242/1236 1366/1430/1424 1368/1432/1426 +f 1170/1244/1238 1369/1433/1427 1169/1243/1237 +f 1369/1433/1427 1367/1431/1425 1167/1241/1235 +f 1369/1433/1427 1167/1241/1235 1169/1243/1237 +f 1370/1434/1428 1369/1433/1427 1170/1244/1238 +f 1171/1245/1239 1371/1435/1429 1172/1246/1240 +f 1173/1247/1241 1371/1435/1429 1171/1245/1239 +f 1172/1246/1240 1371/1435/1429 1372/1436/1430 +f 1371/1435/1429 1173/1247/1241 1373/1437/1431 +f 1373/1437/1431 1374/1248/1242 1375/1438/1432 +f 1373/1437/1431 1173/1247/1241 1374/1248/1242 +f 1375/1438/1432 1374/1248/1242 1175/1249/1243 +f 1178/1252/1246 1376/1439/1433 1176/1250/1244 +f 1177/1251/1245 1377/1440/1434 1178/1252/1246 +f 1376/1439/1433 1178/1252/1246 1377/1440/1434 +f 1378/1441/1435 1172/1246/1240 1372/1436/1430 +f 1172/1246/1240 1378/1441/1435 1179/1253/1247 +f 1378/1441/1435 1177/1251/1245 1179/1253/1247 +f 1378/1441/1435 1377/1440/1434 1177/1251/1245 +f 1180/1254/1248 1379/1442/1436 1380/1443/1437 +f 1180/1254/1248 1380/1443/1437 1181/1255/1249 +f 1182/1256/1250 1381/1444/1438 1379/1442/1436 +f 1182/1256/1250 1379/1442/1436 1180/1254/1248 +f 1382/1445/1439 1383/1258/1252 1584/1446/1440 +f 1183/1257/1251 1381/1444/1438 1182/1256/1250 +f 1383/1258/1252 1382/1445/1439 1183/1257/1251 +f 1382/1445/1439 1381/1444/1438 1183/1257/1251 +f 1186/1259/1253 1384/1447/1441 1189/1262/1256 +f 1188/1261/1255 1384/1447/1441 1186/1259/1253 +f 1188/1261/1255 1385/1448/1442 1384/1447/1441 +f 1189/1262/1256 1384/1447/1441 1386/1449/1443 +f 1387/1450/1444 1388/1264/1258 1590/1451/1445 +f 1387/1450/1444 1190/1263/1257 1388/1264/1258 +f 1385/1448/1442 1190/1263/1257 1387/1450/1444 +f 1190/1263/1257 1385/1448/1442 1188/1261/1255 +f 1194/1266/1260 1389/1452/1446 1195/1267/1261 +f 1193/1265/1259 1389/1452/1446 1194/1266/1260 +f 1193/1265/1259 1390/1453/1447 1389/1452/1446 +f 1195/1267/1261 1389/1452/1446 1391/1454/1448 +f 1392/1455/1449 1189/1262/1256 1386/1449/1443 +f 1189/1262/1256 1392/1455/1449 1196/1268/1262 +f 1392/1455/1449 1390/1453/1447 1193/1265/1259 +f 1392/1455/1449 1193/1265/1259 1196/1268/1262 +f 1198/1269/1263 1393/1456/1450 1199/1270/1264 +f 1200/1271/1265 1394/1457/1451 1395/1458/1452 +f 1200/1271/1265 1199/1270/1264 1394/1457/1451 +f 1199/1270/1264 1393/1456/1450 1394/1457/1451 +f 1396/1459/1453 1195/1267/1261 1391/1454/1448 +f 1195/1267/1261 1396/1459/1453 1201/1272/1266 +f 1396/1459/1453 1198/1269/1263 1201/1272/1266 +f 1396/1459/1453 1393/1456/1450 1198/1269/1263 +f 1203/1274/1268 1397/1460/1454 1398/1461/1455 +f 1203/1274/1268 1398/1461/1455 1202/1273/1267 +f 1204/1275/1269 1399/1462/1456 1203/1274/1268 +f 1399/1462/1456 1397/1460/1454 1203/1274/1268 +f 1400/1463/1457 1206/1277/1271 1401/1464/1458 +f 1206/1277/1271 1400/1463/1457 1205/1276/1270 +f 1400/1463/1457 1399/1462/1456 1204/1275/1269 +f 1400/1463/1457 1204/1275/1269 1205/1276/1270 +f 1207/1278/1272 1402/1465/1459 1210/1281/1275 +f 1209/1280/1274 1402/1465/1459 1207/1278/1272 +f 1209/1280/1274 1403/1466/1460 1402/1465/1459 +f 1210/1281/1275 1402/1465/1459 1404/1467/1461 +f 1403/1466/1460 1405/1282/1276 1406/1468/1462 +f 1406/1468/1462 1405/1282/1276 1212/1283/1277 +f 1405/1282/1276 1403/1466/1460 1209/1280/1274 +f 1215/1285/1279 1407/1469/1463 1216/1286/1280 +f 1408/1470/1464 1216/1286/1280 1409/1471/1465 +f 1216/1286/1280 1407/1469/1463 1409/1471/1465 +f 1214/1284/1278 1216/1286/1280 1408/1470/1464 +f 1410/1472/1466 1210/1281/1275 1404/1467/1461 +f 1210/1281/1275 1410/1472/1466 1217/1287/1281 +f 1410/1472/1466 1407/1469/1463 1215/1285/1279 +f 1410/1472/1466 1215/1285/1279 1217/1287/1281 +f 1218/1288/1282 1411/1473/1467 1219/1289/1283 +f 1411/1473/1467 1412/1474/1468 1219/1289/1283 +f 1220/1290/1284 1413/1475/1469 1411/1473/1467 +f 1220/1290/1284 1411/1473/1467 1218/1288/1282 +f 1414/1476/1470 1222/1292/1286 1415/1477/1471 +f 1222/1292/1286 1414/1476/1470 1221/1291/1285 +f 1414/1476/1470 1413/1475/1469 1220/1290/1284 +f 1414/1476/1470 1220/1290/1284 1221/1291/1285 +f 1223/1293/1287 1416/1478/1472 1224/1294/1288 +f 1416/1478/1472 1417/1479/1473 1224/1294/1288 +f 1225/1295/1289 1416/1478/1472 1223/1293/1287 +f 1225/1295/1289 1418/1480/1474 1416/1478/1472 +f 1419/1481/1475 1420/1296/1290 1622/1482/1476 +f 1420/1296/1290 1419/1481/1475 1226/1097/1091 +f 1419/1481/1475 1225/1295/1289 1226/1097/1091 +f 1419/1481/1475 1418/1480/1474 1225/1295/1289 +f 1026/1102/1096 1421/1483/1477 1230/1299/1293 +f 1229/1298/1292 1421/1483/1477 1026/1102/1096 +f 1229/1298/1292 1422/1484/1478 1421/1483/1477 +f 1230/1299/1293 1421/1483/1477 1423/1485/1479 +f 1417/1479/1473 1424/1486/1480 1231/1300/1294 +f 1417/1479/1473 1231/1300/1294 1224/1294/1288 +f 1424/1486/1480 1422/1484/1478 1229/1298/1292 +f 1424/1486/1480 1229/1298/1292 1231/1300/1294 +f 1235/1302/1296 1425/1487/1481 1426/1488/1482 +f 1235/1302/1296 1426/1488/1482 1234/1303/1297 +f 1233/1301/1295 1427/1489/1483 1425/1487/1481 +f 1233/1301/1295 1425/1487/1481 1235/1302/1296 +f 1428/1490/1484 1230/1299/1293 1423/1485/1479 +f 1230/1299/1293 1428/1490/1484 1236/1304/1298 +f 1428/1490/1484 1233/1301/1295 1236/1304/1298 +f 1428/1490/1484 1427/1489/1483 1233/1301/1295 +f 1237/1305/1299 1429/1491/1485 1239/1307/1301 +f 1238/1306/1300 1430/1492/1486 1237/1305/1299 +f 1430/1492/1486 1429/1491/1485 1237/1305/1299 +f 1239/1307/1301 1429/1491/1485 1431/1493/1487 +f 1241/1309/1303 1432/1494/1488 1240/1308/1302 +f 1432/1494/1488 1430/1492/1486 1238/1306/1300 +f 1432/1494/1488 1238/1306/1300 1240/1308/1302 +f 1433/1495/1489 1432/1494/1488 1241/1309/1303 +f 1242/1310/1304 1434/1496/1490 1244/1312/1306 +f 1243/1311/1305 1434/1496/1490 1242/1310/1304 +f 1243/1311/1305 1435/1497/1491 1434/1496/1490 +f 1244/1312/1306 1434/1496/1490 1436/1498/1492 +f 1637/1499/1493 1435/1497/1491 1437/1500/1494 +f 1437/1500/1494 1435/1497/1491 1245/1313/1307 +f 1437/1500/1494 1245/1313/1307 1246/1314/1308 +f 1435/1497/1491 1243/1311/1305 1245/1313/1307 +f 1248/1316/1310 1438/1501/1495 1247/1315/1309 +f 1043/1119/1113 1438/1501/1495 1248/1316/1310 +f 1439/1502/1496 1438/1501/1495 1440/1503/1497 +f 1247/1315/1309 1438/1501/1495 1439/1502/1496 +f 1441/1504/1498 1244/1312/1306 1436/1498/1492 +f 1440/1503/1497 1438/1501/1495 1441/1504/1498 +f 1244/1312/1306 1441/1504/1498 1249/1317/1311 +f 1441/1504/1498 1438/1501/1495 1043/1119/1113 +f 1441/1504/1498 1043/1119/1113 1249/1317/1311 +f 1250/1318/1312 1442/1505/1499 1252/1320/1314 +f 1442/1505/1499 1443/1506/1500 1252/1320/1314 +f 1251/1319/1313 1444/1507/1501 1442/1505/1499 +f 1251/1319/1313 1442/1505/1499 1250/1318/1312 +f 1445/1508/1502 1446/1321/1315 1650/1509/1503 +f 1446/1321/1315 1445/1508/1502 1253/1123/1117 +f 1445/1508/1502 1444/1507/1501 1251/1319/1313 +f 1445/1508/1502 1251/1319/1313 1253/1123/1117 +f 1255/1322/1316 1447/1510/1504 1256/1323/1317 +f 1447/1510/1504 1448/1511/1505 1256/1323/1317 +f 1257/1324/1318 1447/1510/1504 1255/1322/1316 +f 1257/1324/1318 1449/1512/1506 1447/1510/1504 +f 1450/1513/1507 1258/1325/1319 1451/1514/1508 +f 1449/1512/1506 1258/1325/1319 1450/1513/1507 +f 1451/1514/1508 1258/1325/1319 1260/1326/1320 +f 1258/1325/1319 1449/1512/1506 1257/1324/1318 +f 1263/1329/1323 1452/1515/1509 1262/1328/1322 +f 1261/1327/1321 1453/1516/1510 1452/1515/1509 +f 1261/1327/1321 1452/1515/1509 1263/1329/1323 +f 1262/1328/1322 1452/1515/1509 1454/1517/1511 +f 1448/1511/1505 1455/1518/1512 1264/1330/1324 +f 1448/1511/1505 1264/1330/1324 1256/1323/1317 +f 1455/1518/1512 1453/1516/1510 1261/1327/1321 +f 1455/1518/1512 1261/1327/1321 1264/1330/1324 +f 1267/1333/1327 1456/1519/1513 1457/1520/1514 +f 1267/1333/1327 1457/1520/1514 1266/1332/1326 +f 1265/1331/1325 1458/1521/1515 1456/1519/1513 +f 1265/1331/1325 1456/1519/1513 1267/1333/1327 +f 1459/1522/1516 1262/1328/1322 1454/1517/1511 +f 1262/1328/1322 1459/1522/1516 1268/1334/1328 +f 1459/1522/1516 1265/1331/1325 1268/1334/1328 +f 1459/1522/1516 1458/1521/1515 1265/1331/1325 +f 1269/1335/1329 1460/1523/1517 1271/1337/1331 +f 1270/1336/1330 1461/1524/1518 1269/1335/1329 +f 1461/1524/1518 1460/1523/1517 1269/1335/1329 +f 1271/1337/1331 1460/1523/1517 1462/1525/1519 +f 1463/1526/1520 1465/1527/1521 1464/1528/1522 +f 1465/1527/1521 1463/1526/1520 1272/1339/1333 +f 1465/1527/1521 1272/1339/1333 1273/1338/1332 +f 1463/1526/1520 1461/1524/1518 1270/1336/1330 +f 1463/1526/1520 1270/1336/1330 1272/1339/1333 +f 1274/1340/1334 1466/1529/1523 1467/1530/1524 +f 1274/1340/1334 1467/1530/1524 1275/1341/1335 +f 1276/1342/1336 1466/1529/1523 1274/1340/1334 +f 1276/1342/1336 1468/1531/1525 1466/1529/1523 +f 1670/1532/1526 1468/1531/1525 1469/1533/1527 +f 1469/1533/1527 1468/1531/1525 1277/1343/1337 +f 1469/1533/1527 1277/1343/1337 1278/1344/1338 +f 1468/1531/1525 1276/1342/1336 1277/1343/1337 +f 1280/1346/1340 1470/1534/1528 1279/1345/1339 +f 1470/1534/1528 1280/1346/1340 1471/1535/1529 +f 1280/1346/1340 1281/1347/1341 1471/1535/1529 +f 1472/1536/1530 1282/1348/1342 1467/1530/1524 +f 1471/1535/1529 1281/1347/1341 1282/1348/1342 +f 1471/1535/1529 1282/1348/1342 1472/1536/1530 +f 1467/1530/1524 1282/1348/1342 1275/1341/1335 +f 1283/1349/1343 1473/1537/1531 1474/1538/1532 +f 1283/1349/1343 1474/1538/1532 1284/1350/1344 +f 1285/1351/1345 1475/1539/1533 1473/1537/1531 +f 1285/1351/1345 1473/1537/1531 1283/1349/1343 +f 1476/1540/1534 1477/1353/1347 1478/1541/1535 +f 1477/1353/1347 1476/1540/1534 1286/1352/1346 +f 1476/1540/1534 1475/1539/1533 1285/1351/1345 +f 1476/1540/1534 1285/1351/1345 1286/1352/1346 +f 1288/1354/1348 1479/1542/1536 1290/1356/1350 +f 1289/1355/1349 1480/1543/1537 1479/1542/1536 +f 1289/1355/1349 1479/1542/1536 1288/1354/1348 +f 1290/1356/1350 1479/1542/1536 1481/1544/1538 +f 1482/1545/1539 1483/1358/1352 1484/1546/1540 +f 1483/1358/1352 1482/1545/1539 1291/1357/1351 +f 1482/1545/1539 1480/1543/1537 1289/1355/1349 +f 1482/1545/1539 1289/1355/1349 1291/1357/1351 +f 1293/1359/1353 1485/1547/1541 1295/1361/1355 +f 1294/1360/1354 1486/1548/1542 1293/1359/1353 +f 1486/1548/1542 1485/1547/1541 1293/1359/1353 +f 1295/1361/1355 1485/1547/1541 1487/1549/1543 +f 1488/1550/1544 1290/1356/1350 1481/1544/1538 +f 1296/1362/1356 1486/1548/1542 1294/1360/1354 +f 1290/1356/1350 1488/1550/1544 1296/1362/1356 +f 1488/1550/1544 1486/1548/1542 1296/1362/1356 +f 1297/1363/1357 1489/1551/1545 1299/1365/1359 +f 1298/1364/1358 1490/1552/1546 1489/1551/1545 +f 1298/1364/1358 1489/1551/1545 1297/1363/1357 +f 1299/1365/1359 1489/1551/1545 1491/1553/1547 +f 1492/1554/1548 1295/1361/1355 1487/1549/1543 +f 1295/1361/1355 1492/1554/1548 1300/1366/1360 +f 1492/1554/1548 1490/1552/1546 1298/1364/1358 +f 1492/1554/1548 1298/1364/1358 1300/1366/1360 +f 1301/1367/1361 1493/1555/1549 1494/1556/1550 +f 1301/1367/1361 1494/1556/1550 1302/1368/1362 +f 1303/1369/1363 1495/1557/1551 1301/1367/1361 +f 1495/1557/1551 1493/1555/1549 1301/1367/1361 +f 1304/1370/1364 1495/1557/1551 1303/1369/1363 +f 1497/1558/1552 1496/1559/1553 1304/1370/1364 +f 1497/1558/1552 1304/1370/1364 1305/1371/1365 +f 1496/1559/1553 1495/1557/1551 1304/1370/1364 +f 1306/1372/1366 1498/1560/1554 1308/1374/1368 +f 1307/1373/1367 1499/1561/1555 1306/1372/1366 +f 1499/1561/1555 1498/1560/1554 1306/1372/1366 +f 1308/1374/1368 1498/1560/1554 1500/1562/1556 +f 1501/1563/1557 1310/1375/1369 1502/1564/1558 +f 1309/1376/1370 1499/1561/1555 1307/1373/1367 +f 1310/1375/1369 1501/1563/1557 1309/1376/1370 +f 1501/1563/1557 1499/1561/1555 1309/1376/1370 +f 1311/1377/1371 1503/1565/1559 1313/1379/1373 +f 1312/1378/1372 1504/1566/1560 1311/1377/1371 +f 1504/1566/1560 1503/1565/1559 1311/1377/1371 +f 1313/1379/1373 1503/1565/1559 1505/1567/1561 +f 1506/1568/1562 1308/1374/1368 1500/1562/1556 +f 1314/1380/1374 1504/1566/1560 1312/1378/1372 +f 1308/1374/1368 1506/1568/1562 1314/1380/1374 +f 1506/1568/1562 1504/1566/1560 1314/1380/1374 +f 1315/1381/1375 1507/1569/1563 1508/1570/1564 +f 1315/1381/1375 1508/1570/1564 1316/1382/1376 +f 1317/1383/1377 1509/1571/1565 1315/1381/1375 +f 1509/1571/1565 1507/1569/1563 1315/1381/1375 +f 1510/1572/1566 1319/1385/1379 1511/1573/1567 +f 1318/1384/1378 1509/1571/1565 1317/1383/1377 +f 1319/1385/1379 1510/1572/1566 1318/1384/1378 +f 1510/1572/1566 1509/1571/1565 1318/1384/1378 +f 1320/1386/1380 1512/1574/1568 1321/1387/1381 +f 1322/1388/1382 1512/1574/1568 1320/1386/1380 +f 1321/1387/1381 1512/1574/1568 1513/1575/1569 +f 1512/1574/1568 1322/1388/1382 1514/1576/1570 +f 1514/1576/1570 1322/1388/1382 1515/1577/1571 +f 1516/1578/1572 1515/1577/1571 1322/1388/1382 +f 1516/1578/1572 1322/1388/1382 1323/1389/1383 +f 1325/1390/1384 1517/1579/1573 1326/1391/1385 +f 1518/1392/1386 1517/1579/1573 1325/1390/1384 +f 1326/1391/1385 1517/1579/1573 1519/1580/1574 +f 1517/1579/1573 1518/1392/1386 1520/1581/1575 +f 1521/1582/1576 1321/1387/1381 1513/1575/1569 +f 1520/1581/1575 1518/1392/1386 1521/1582/1576 +f 1321/1387/1381 1521/1582/1576 1518/1392/1386 +f 1329/1393/1387 1326/1391/1385 1328/1394/1388 +f 1326/1391/1385 1522/1583/1577 1328/1394/1388 +f 1522/1583/1577 1326/1391/1385 1524/1584/1578 +f 1525/1585/1579 1326/1391/1385 1519/1580/1574 +f 1524/1584/1578 1326/1391/1385 1525/1585/1579 +f 1331/1395/1389 1526/1586/1580 1527/1587/1581 +f 1331/1395/1389 1527/1587/1581 1332/1396/1390 +f 1526/1586/1580 1331/1395/1389 1528/1588/1582 +f 1529/1589/1583 1522/1583/1577 1523/1590/1584 +f 1528/1588/1582 1331/1395/1389 1529/1589/1583 +f 1522/1583/1577 1529/1589/1583 1333/1397/1391 +f 1522/1583/1577 1333/1397/1391 1328/1394/1388 +f 1529/1589/1583 1331/1395/1389 1333/1397/1391 +f 1334/1398/1392 1530/1591/1585 1531/1592/1586 +f 1334/1398/1392 1531/1592/1586 1335/1399/1393 +f 1336/1400/1394 1532/1593/1587 1530/1591/1585 +f 1336/1400/1394 1530/1591/1585 1334/1398/1392 +f 1533/1594/1588 1534/1402/1396 1535/1595/1589 +f 1534/1402/1396 1533/1594/1588 1337/1401/1395 +f 1533/1594/1588 1532/1593/1587 1336/1400/1394 +f 1533/1594/1588 1336/1400/1394 1337/1401/1395 +f 1340/1404/1398 1536/1596/1590 1341/1405/1399 +f 1339/1403/1397 1537/1597/1591 1340/1404/1398 +f 1537/1597/1591 1536/1596/1590 1340/1404/1398 +f 1341/1405/1399 1536/1596/1590 1538/1598/1592 +f 1539/1599/1593 1540/1407/1401 1541/1600/1594 +f 1342/1406/1400 1537/1597/1591 1339/1403/1397 +f 1540/1407/1401 1539/1599/1593 1342/1406/1400 +f 1539/1599/1593 1537/1597/1591 1342/1406/1400 +f 1344/1408/1402 1542/1601/1595 1543/1602/1596 +f 1344/1408/1402 1543/1602/1596 1345/1409/1403 +f 1346/1410/1404 1544/1603/1597 1344/1408/1402 +f 1544/1603/1597 1542/1601/1595 1344/1408/1402 +f 1545/1604/1598 1341/1405/1399 1538/1598/1592 +f 1347/1411/1405 1544/1603/1597 1346/1410/1404 +f 1341/1405/1399 1545/1604/1598 1347/1411/1405 +f 1545/1604/1598 1544/1603/1597 1347/1411/1405 +f 1348/1412/1406 1546/1605/1599 1349/1413/1407 +f 1350/1414/1408 1547/1606/1600 1348/1412/1406 +f 1547/1606/1600 1546/1605/1599 1348/1412/1406 +f 1349/1413/1407 1546/1605/1599 1548/1607/1601 +f 1549/1608/1602 1550/1416/1410 1551/1609/1603 +f 1351/1415/1409 1547/1606/1600 1350/1414/1408 +f 1550/1416/1410 1549/1608/1602 1351/1415/1409 +f 1549/1608/1602 1547/1606/1600 1351/1415/1409 +f 1353/1417/1411 1552/1610/1604 1355/1419/1413 +f 1354/1418/1412 1553/1611/1605 1552/1610/1604 +f 1354/1418/1412 1552/1610/1604 1353/1417/1411 +f 1355/1419/1413 1552/1610/1604 1554/1612/1606 +f 1555/1613/1607 1556/1421/1415 1557/1614/1608 +f 1556/1421/1415 1555/1613/1607 1356/1420/1414 +f 1555/1613/1607 1553/1611/1605 1354/1418/1412 +f 1555/1613/1607 1354/1418/1412 1356/1420/1414 +f 1358/1422/1416 1558/1615/1609 1360/1424/1418 +f 1359/1423/1417 1559/1616/1610 1358/1422/1416 +f 1559/1616/1610 1558/1615/1609 1358/1422/1416 +f 1360/1424/1418 1558/1615/1609 1560/1617/1611 +f 1561/1618/1612 1355/1419/1413 1554/1612/1606 +f 1361/1425/1419 1559/1616/1610 1359/1423/1417 +f 1355/1419/1413 1561/1618/1612 1361/1425/1419 +f 1561/1618/1612 1559/1616/1610 1361/1425/1419 +f 1362/1426/1420 1562/1619/1613 1364/1428/1422 +f 1363/1427/1421 1563/1620/1614 1562/1619/1613 +f 1363/1427/1421 1562/1619/1613 1362/1426/1420 +f 1364/1428/1422 1562/1619/1613 1564/1621/1615 +f 1565/1622/1616 1360/1424/1418 1560/1617/1611 +f 1360/1424/1418 1565/1622/1616 1365/1429/1423 +f 1565/1622/1616 1563/1620/1614 1363/1427/1421 +f 1565/1622/1616 1363/1427/1421 1365/1429/1423 +f 1366/1430/1424 1566/1623/1617 1368/1432/1426 +f 1367/1431/1425 1567/1624/1618 1566/1623/1617 +f 1367/1431/1425 1566/1623/1617 1366/1430/1424 +f 1368/1432/1426 1566/1623/1617 1568/1625/1619 +f 1570/1626/1620 1569/1627/1621 1369/1433/1427 +f 1570/1626/1620 1369/1433/1427 1370/1434/1428 +f 1569/1627/1621 1567/1624/1618 1367/1431/1425 +f 1569/1627/1621 1367/1431/1425 1369/1433/1427 +f 1371/1435/1429 1571/1628/1622 1372/1436/1430 +f 1373/1437/1431 1571/1628/1622 1371/1435/1429 +f 1372/1436/1430 1571/1628/1622 1572/1629/1623 +f 1571/1628/1622 1373/1437/1431 1573/1630/1624 +f 1574/1631/1625 1375/1438/1432 1575/1632/1626 +f 1573/1630/1624 1373/1437/1431 1574/1631/1625 +f 1375/1438/1432 1574/1631/1625 1373/1437/1431 +f 1377/1440/1434 1576/1633/1627 1577/1634/1628 +f 1377/1440/1434 1577/1634/1628 1376/1439/1433 +f 1576/1633/1627 1377/1440/1434 1578/1635/1629 +f 1579/1636/1630 1372/1436/1430 1572/1629/1623 +f 1578/1635/1629 1377/1440/1434 1579/1636/1630 +f 1372/1436/1430 1579/1636/1630 1378/1441/1435 +f 1579/1636/1630 1377/1440/1434 1378/1441/1435 +f 1379/1442/1436 1580/1637/1631 1581/1638/1632 +f 1379/1442/1436 1581/1638/1632 1380/1443/1437 +f 1381/1444/1438 1582/1639/1633 1580/1637/1631 +f 1381/1444/1438 1580/1637/1631 1379/1442/1436 +f 1583/1640/1634 1584/1446/1440 1585/1641/1635 +f 1584/1446/1440 1583/1640/1634 1382/1445/1439 +f 1583/1640/1634 1582/1639/1633 1381/1444/1438 +f 1583/1640/1634 1381/1444/1438 1382/1445/1439 +f 1384/1447/1441 1586/1642/1636 1386/1449/1443 +f 1385/1448/1442 1587/1643/1637 1586/1642/1636 +f 1385/1448/1442 1586/1642/1636 1384/1447/1441 +f 1386/1449/1443 1586/1642/1636 1588/1644/1638 +f 1589/1645/1639 1387/1450/1444 1590/1451/1445 +f 1589/1645/1639 1590/1451/1445 1591/1646/1640 +f 1587/1643/1637 1387/1450/1444 1589/1645/1639 +f 1387/1450/1444 1587/1643/1637 1385/1448/1442 +f 1389/1452/1446 1592/1647/1641 1391/1454/1448 +f 1390/1453/1447 1593/1648/1642 1389/1452/1446 +f 1593/1648/1642 1592/1647/1641 1389/1452/1446 +f 1391/1454/1448 1592/1647/1641 1594/1649/1643 +f 1595/1650/1644 1386/1449/1443 1588/1644/1638 +f 1392/1455/1449 1593/1648/1642 1390/1453/1447 +f 1386/1449/1443 1595/1650/1644 1392/1455/1449 +f 1595/1650/1644 1593/1648/1642 1392/1455/1449 +f 1393/1456/1450 1596/1651/1645 1394/1457/1451 +f 1395/1458/1452 1394/1457/1451 1597/1652/1646 +f 1395/1458/1452 1597/1652/1646 1598/1653/1647 +f 1394/1457/1451 1596/1651/1645 1597/1652/1646 +f 1599/1654/1648 1391/1454/1448 1594/1649/1643 +f 1391/1454/1448 1599/1654/1648 1396/1459/1453 +f 1599/1654/1648 1596/1651/1645 1393/1456/1450 +f 1599/1654/1648 1393/1456/1450 1396/1459/1453 +f 1397/1460/1454 1600/1655/1649 1601/1656/1650 +f 1397/1460/1454 1601/1656/1650 1398/1461/1455 +f 1399/1462/1456 1602/1657/1651 1600/1655/1649 +f 1399/1462/1456 1600/1655/1649 1397/1460/1454 +f 1604/1658/1652 1603/1659/1653 1400/1463/1457 +f 1604/1658/1652 1400/1463/1457 1401/1464/1458 +f 1603/1659/1653 1602/1657/1651 1399/1462/1456 +f 1603/1659/1653 1399/1462/1456 1400/1463/1457 +f 1402/1465/1459 1605/1660/1654 1404/1467/1461 +f 1403/1466/1460 1605/1660/1654 1402/1465/1459 +f 1404/1467/1461 1605/1660/1654 1606/1661/1655 +f 1605/1660/1654 1403/1466/1460 1607/1662/1656 +f 1607/1662/1656 1403/1466/1460 1608/1663/1657 +f 1609/1664/1658 1608/1663/1657 1403/1466/1460 +f 1609/1664/1658 1403/1466/1460 1406/1468/1462 +f 1409/1471/1465 1610/1665/1659 1408/1470/1464 +f 1610/1665/1659 1409/1471/1465 1612/1666/1660 +f 1408/1470/1464 1610/1665/1659 1611/1667/1661 +f 1409/1471/1465 1404/1467/1461 1606/1661/1655 +f 1410/1472/1466 1409/1471/1465 1407/1469/1463 +f 1404/1467/1461 1409/1471/1465 1410/1472/1466 +f 1411/1473/1467 1613/1668/1662 1412/1474/1468 +f 1411/1473/1467 1614/1669/1663 1613/1668/1662 +f 1413/1475/1469 1615/1670/1664 1614/1669/1663 +f 1413/1475/1469 1614/1669/1663 1411/1473/1467 +f 1616/1671/1665 1415/1477/1471 1617/1672/1666 +f 1415/1477/1471 1616/1671/1665 1414/1476/1470 +f 1616/1671/1665 1615/1670/1664 1413/1475/1469 +f 1616/1671/1665 1413/1475/1469 1414/1476/1470 +f 1416/1478/1472 1618/1673/1667 1417/1479/1473 +f 1418/1480/1474 1619/1674/1668 1618/1673/1667 +f 1418/1480/1474 1618/1673/1667 1416/1478/1472 +f 1417/1479/1473 1618/1673/1667 1620/1675/1669 +f 1621/1676/1670 1622/1482/1476 1623/1677/1671 +f 1622/1482/1476 1621/1676/1670 1419/1481/1475 +f 1621/1676/1670 1619/1674/1668 1418/1480/1474 +f 1621/1676/1670 1418/1480/1474 1419/1481/1475 +f 1421/1483/1477 1624/1678/1672 1423/1485/1479 +f 1422/1484/1478 1625/1679/1673 1421/1483/1477 +f 1625/1679/1673 1624/1678/1672 1421/1483/1477 +f 1423/1485/1479 1624/1678/1672 1626/1680/1674 +f 1627/1681/1675 1417/1479/1473 1620/1675/1669 +f 1424/1486/1480 1625/1679/1673 1422/1484/1478 +f 1417/1479/1473 1627/1681/1675 1424/1486/1480 +f 1627/1681/1675 1625/1679/1673 1424/1486/1480 +f 1425/1487/1481 1628/1682/1676 1426/1488/1482 +f 1427/1489/1483 1629/1683/1677 1628/1682/1676 +f 1427/1489/1483 1628/1682/1676 1425/1487/1481 +f 1426/1488/1482 1628/1682/1676 1630/1684/1678 +f 1631/1685/1679 1423/1485/1479 1626/1680/1674 +f 1423/1485/1479 1631/1685/1679 1428/1490/1484 +f 1631/1685/1679 1629/1683/1677 1427/1489/1483 +f 1631/1685/1679 1427/1489/1483 1428/1490/1484 +f 1429/1491/1485 1632/1686/1680 1431/1493/1487 +f 1430/1492/1486 1633/1687/1681 1632/1686/1680 +f 1430/1492/1486 1632/1686/1680 1429/1491/1485 +f 1431/1493/1487 1632/1686/1680 1634/1688/1682 +f 1636/1689/1683 1635/1690/1684 1432/1494/1488 +f 1636/1689/1683 1432/1494/1488 1433/1495/1489 +f 1635/1690/1684 1633/1687/1681 1430/1492/1486 +f 1635/1690/1684 1430/1492/1486 1432/1494/1488 +f 1434/1496/1490 1637/1499/1493 1436/1498/1492 +f 1435/1497/1491 1637/1499/1493 1434/1496/1490 +f 1436/1498/1492 1637/1499/1493 1638/1691/1685 +f 1639/1692/1686 1637/1499/1493 1640/1693/1687 +f 1641/1694/1688 1640/1693/1687 1637/1499/1493 +f 1641/1694/1688 1637/1499/1493 1437/1500/1494 +f 1440/1503/1497 1642/1695/1689 1439/1502/1496 +f 1440/1503/1497 1643/1696/1690 1642/1695/1689 +f 1439/1502/1496 1642/1695/1689 1644/1697/1691 +f 1645/1698/1692 1436/1498/1492 1638/1691/1685 +f 1436/1498/1492 1645/1698/1692 1441/1504/1498 +f 1645/1698/1692 1643/1696/1690 1440/1503/1497 +f 1645/1698/1692 1440/1503/1497 1441/1504/1498 +f 1442/1505/1499 1646/1699/1693 1443/1506/1500 +f 1442/1505/1499 1647/1700/1694 1646/1699/1693 +f 1444/1507/1501 1648/1701/1695 1647/1700/1694 +f 1444/1507/1501 1647/1700/1694 1442/1505/1499 +f 1649/1702/1696 1650/1509/1503 1651/1703/1697 +f 1650/1509/1503 1649/1702/1696 1445/1508/1502 +f 1649/1702/1696 1648/1701/1695 1444/1507/1501 +f 1649/1702/1696 1444/1507/1501 1445/1508/1502 +f 1447/1510/1504 1652/1704/1698 1448/1511/1505 +f 1449/1512/1506 1653/1705/1699 1652/1704/1698 +f 1449/1512/1506 1652/1704/1698 1447/1510/1504 +f 1448/1511/1505 1652/1704/1698 1654/1706/1700 +f 1655/1707/1701 1450/1513/1507 1451/1514/1508 +f 1655/1707/1701 1451/1514/1508 1656/1708/1702 +f 1653/1705/1699 1450/1513/1507 1655/1707/1701 +f 1450/1513/1507 1653/1705/1699 1449/1512/1506 +f 1452/1515/1509 1657/1709/1703 1454/1517/1511 +f 1453/1516/1510 1658/1710/1704 1657/1709/1703 +f 1453/1516/1510 1657/1709/1703 1452/1515/1509 +f 1454/1517/1511 1657/1709/1703 1659/1711/1705 +f 1660/1712/1706 1448/1511/1505 1654/1706/1700 +f 1448/1511/1505 1660/1712/1706 1455/1518/1512 +f 1660/1712/1706 1658/1710/1704 1453/1516/1510 +f 1660/1712/1706 1453/1516/1510 1455/1518/1512 +f 1456/1519/1513 1661/1713/1707 1457/1520/1514 +f 1458/1521/1515 1662/1714/1708 1661/1713/1707 +f 1458/1521/1515 1661/1713/1707 1456/1519/1513 +f 1457/1520/1514 1661/1713/1707 1663/1715/1709 +f 1664/1716/1710 1454/1517/1511 1659/1711/1705 +f 1454/1517/1511 1664/1716/1710 1459/1522/1516 +f 1664/1716/1710 1662/1714/1708 1458/1521/1515 +f 1664/1716/1710 1458/1521/1515 1459/1522/1516 +f 1460/1523/1517 1665/1717/1711 1462/1525/1519 +f 1461/1524/1518 1666/1718/1712 1665/1717/1711 +f 1461/1524/1518 1665/1717/1711 1460/1523/1517 +f 1462/1525/1519 1665/1717/1711 1667/1719/1713 +f 1668/1720/1714 1464/1528/1522 1669/1721/1715 +f 1464/1528/1522 1668/1720/1714 1463/1526/1520 +f 1668/1720/1714 1666/1718/1712 1461/1524/1518 +f 1668/1720/1714 1461/1524/1518 1463/1526/1520 +f 1466/1529/1523 1670/1532/1526 1671/1722/1716 +f 1466/1529/1523 1671/1722/1716 1467/1530/1524 +f 1468/1531/1525 1670/1532/1526 1466/1529/1523 +f 1672/1723/1717 1670/1532/1526 1673/1724/1718 +f 1674/1725/1719 1673/1724/1718 1670/1532/1526 +f 1674/1725/1719 1670/1532/1526 1469/1533/1527 +f 1471/1535/1529 1675/1726/1720 1676/1727/1721 +f 1471/1535/1529 1676/1727/1721 1470/1534/1528 +f 1675/1726/1720 1471/1535/1529 1677/1728/1722 +f 1677/1728/1722 1471/1535/1529 1678/1729/1723 +f 1671/1722/1716 1678/1729/1723 1472/1536/1530 +f 1671/1722/1716 1472/1536/1530 1467/1530/1524 +f 1678/1729/1723 1471/1535/1529 1472/1536/1530 +f 1679/1730/1724 1680/1731/1725 1681/1732/1726 +f 1679/1730/1724 1682/1733/1727 1680/1731/1725 +f 1683/1734/1728 1684/1735/1729 1682/1733/1727 +f 1683/1734/1728 1682/1733/1727 1679/1730/1724 +f 1685/1736/1730 1686/1737/1731 1687/1738/1732 +f 1686/1737/1731 1684/1735/1729 1688/1739/1733 +f 1682/1733/1727 1686/1737/1731 1685/1736/1730 +f 1682/1733/1727 1684/1735/1729 1686/1737/1731 +f 1693/1740/1734 1688/1739/1733 1689/1741/1735 +f 1693/1740/1734 1689/1741/1735 1690/1742/1736 +f 1688/1739/1733 1683/1734/1728 1689/1741/1735 +f 1684/1735/1729 1683/1734/1728 1688/1739/1733 +f 1688/1739/1733 1692/1743/1737 1691/1744/1738 +f 1688/1739/1733 1691/1744/1738 1686/1737/1731 +f 1693/1740/1734 1692/1743/1737 1688/1739/1733 +f 1693/1740/1734 1694/1745/1739 1692/1743/1737 +f 1694/1745/1739 1693/1740/1734 1690/1742/1736 +f 1694/1745/1739 1690/1742/1736 2463/1746/1740 +f 1690/1742/1736 1695/1747/1741 2463/1746/1740 +f 1696/1748/1742 1699/1749/1743 1697/1750/1744 +f 1700/1751/1745 1699/1749/1743 1695/1747/1741 +f 1700/1751/1745 1695/1747/1741 1690/1742/1736 +f 1680/1731/1725 1701/1752/1746 1681/1732/1726 +f 1682/1733/1727 1702/1753/1747 1680/1731/1725 +f 1701/1752/1746 1680/1731/1725 2494/1754/1748 +f 1680/1731/1725 1702/1753/1747 2494/1754/1748 +f 1703/1755/1749 1702/1753/1747 1682/1733/1727 +f 1703/1755/1749 1682/1733/1727 1685/1736/1730 +f 1704/1756/1750 1706/1757/1751 1707/1758/1752 +f 1707/1758/1752 1706/1757/1751 1708/1759/1753 +f 1707/1758/1752 1708/1759/1753 1701/1752/1746 +f 1701/1752/1746 1708/1759/1753 1681/1732/1726 +f 1712/1760/1754 1711/1761/1755 1709/1762/1756 +f 1711/1761/1755 1704/1756/1750 1707/1758/1752 +f 1704/1756/1750 1713/1763/1757 1705/1764/1758 +f 1713/1763/1757 1711/1761/1755 1712/1760/1754 +f 1704/1756/1750 1711/1761/1755 1713/1763/1757 +f 1709/1762/1756 1714/1765/1759 1710/1766/1760 +f 1711/1761/1755 1714/1765/1759 1709/1762/1756 +f 1711/1761/1755 1716/1767/1761 1714/1765/1759 +f 1715/1768/1762 1714/1765/1759 1717/1769/1763 +f 1714/1765/1759 1716/1767/1761 1717/1769/1763 +f 1716/1767/1761 1707/1758/1752 2504/1770/1764 +f 1707/1758/1752 1716/1767/1761 1711/1761/1755 +f 1719/1771/1765 1720/1772/1766 1721/1773/1767 +f 1719/1771/1765 1722/1774/1768 1723/1775/1769 +f 1719/1771/1765 1723/1775/1769 1720/1772/1766 +f 1724/1776/1770 1722/1774/1768 1719/1771/1765 +f 1722/1774/1768 1725/1777/1771 2476/1778/1772 +f 1722/1774/1768 1724/1776/1770 1725/1777/1771 +f 1727/1779/1773 1725/1777/1771 1726/1780/1774 +f 1727/1779/1773 1726/1780/1774 1718/1781/1775 +f 1725/1777/1771 1724/1776/1770 1726/1780/1774 +f 1725/1777/1771 1727/1779/1773 1728/1782/1776 +f 1728/1782/1776 1727/1779/1773 1716/1767/1761 +f 1728/1782/1776 1716/1767/1761 2504/1770/1764 +f 1717/1769/1763 1727/1779/1773 1718/1781/1775 +f 1716/1767/1761 1727/1779/1773 1717/1769/1763 +f 1729/1783/1777 1731/1784/1778 1733/1785/1779 +f 1729/1783/1777 1733/1785/1779 1732/1786/1780 +f 1733/1785/1779 1721/1773/1767 2428/1787/1781 +f 1721/1773/1767 1733/1785/1779 1734/1788/1782 +f 1733/1785/1779 1731/1784/1778 1734/1788/1782 +f 1720/1772/1766 1723/1775/1769 2428/1787/1781 +f 1721/1773/1767 1720/1772/1766 2428/1787/1781 +f 1761/1789/1783 1739/1790/1784 1738/1791/1785 +f 1736/1792/1786 1739/1790/1784 1761/1789/1783 +f 1738/1791/1785 1739/1790/1784 1730/1793/1787 +f 1732/1786/1780 1738/1791/1785 1729/1783/1777 +f 2425/1794/1788 1738/1791/1785 1732/1786/1780 +f 1729/1783/1777 1738/1791/1785 1730/1793/1787 +f 1740/1795/1789 1741/1796/1790 1742/1797/1791 +f 1740/1795/1789 1742/1797/1791 1737/1798/1792 +f 1741/1796/1790 1743/1799/1793 1742/1797/1791 +f 1744/1800/1794 1741/1796/1790 1740/1795/1789 +f 1745/1801/1795 1746/1802/1796 1747/1803/1797 +f 1745/1801/1795 1748/1804/1798 1746/1802/1796 +f 1743/1799/1793 1748/1804/1798 1745/1801/1795 +f 1748/1804/1798 1743/1799/1793 1749/1805/1799 +f 1750/1806/1800 1751/1807/1801 1752/1808/1802 +f 1750/1806/1800 1741/1796/1790 1744/1800/1794 +f 1750/1806/1800 1744/1800/1794 1751/1807/1801 +f 1749/1805/1799 1743/1799/1793 1741/1796/1790 +f 1749/1805/1799 1741/1796/1790 1750/1806/1800 +f 1744/1800/1794 1753/1809/1803 1754/1810/1804 +f 1744/1800/1794 1754/1810/1804 1751/1807/1801 +f 1753/1809/1803 1755/1811/1805 1756/1812/1806 +f 1753/1809/1803 1757/1813/1807 1755/1811/1805 +f 1757/1813/1807 1740/1795/1789 1737/1798/1792 +f 1740/1795/1789 1753/1809/1803 1744/1800/1794 +f 1757/1813/1807 1753/1809/1803 1740/1795/1789 +f 1757/1813/1807 1758/1814/1808 1755/1811/1805 +f 1755/1811/1805 1759/1815/1809 2398/1816/1810 +f 1755/1811/1805 1758/1814/1808 1759/1815/1809 +f 1759/1815/1809 1758/1814/1808 1760/1817/1811 +f 1761/1789/1783 1760/1817/1811 1736/1792/1786 +f 1735/1818/1812 1757/1813/1807 1737/1798/1792 +f 1735/1818/1812 1760/1817/1811 1758/1814/1808 +f 1735/1818/1812 1758/1814/1808 1757/1813/1807 +f 1736/1792/1786 1760/1817/1811 1735/1818/1812 +f 1762/1819/1813 1763/1/1814 1764/3/1815 +f 1765/1820/1816 1763/1/1814 1762/1819/1813 +f 1775/4/1817 1763/1/1814 1766/1821/1818 +f 1763/1/1814 1769/1822/1819 1766/1821/1818 +f 1767/1823/1820 1765/1820/1816 1768/1824/1821 +f 1767/1823/1820 1768/1824/1821 1747/1803/1797 +f 1769/1822/1819 1763/1/1814 1765/1820/1816 +f 1769/1822/1819 1765/1820/1816 1767/1823/1820 +f 1766/1821/1818 1769/1822/1819 1770/1825/1822 +f 1770/1825/1822 1769/1822/1819 1771/1826/1823 +f 1769/1822/1819 1767/1823/1820 1771/1826/1823 +f 1772/1827/1824 1767/1823/1820 1747/1803/1797 +f 1767/1823/1820 1772/1827/1824 1771/1826/1823 +f 1773/1828/1825 1772/1827/1824 1746/1802/1796 +f 1771/1826/1823 1772/1827/1824 1773/1828/1825 +f 1746/1802/1796 1772/1827/1824 1747/1803/1797 +f 1779/1829/1826 1775/4/1817 1780/1830/1827 +f 1780/1830/1827 1775/4/1817 1766/1821/1818 +f 1788/10/1828 1782/9/1829 2302/1831/1830 +f 2302/1831/1830 1782/9/1829 1775/4/1817 +f 2302/1831/1830 1775/4/1817 1779/1829/1826 +f 5/1832/1831 1786/13/1832 1788/10/1828 +f 5/1832/1831 1788/10/1828 4/1833/1833 +f 1787/1834/1834 1788/10/1828 2302/1831/1830 +f 4/1833/1833 1802/1835/1835 1785/1836/1836 +f 4/1833/1833 1788/10/1828 1803/1837/1837 +f 1789/1838/1838 1790/1839/1839 1785/1836/1836 +f 1790/1839/1839 1791/1840/1840 1792/1841/1841 +f 1793/1842/1842 1794/1843/1843 1790/1839/1839 +f 1793/1842/1842 1790/1839/1839 1789/1838/1838 +f 1794/1843/1843 1791/1840/1840 1790/1839/1839 +f 1795/1844/1844 1796/1845/1845 1814/1846/1846 +f 1795/1844/1844 1814/1846/1846 1797/1847/1847 +f 1792/1841/1841 1791/1840/1840 1796/1845/1845 +f 1792/1841/1841 1796/1845/1845 1795/1844/1844 +f 1791/1840/1840 1798/1848/1848 1796/1845/1845 +f 1796/1845/1845 1798/1848/1848 1800/1849/1849 +f 1796/1845/1845 1800/1849/1849 1799/1850/1850 +f 1798/1848/1848 1791/1840/1840 1801/1851/1851 +f 1798/1848/1848 1801/1851/1851 1800/1849/1849 +f 1801/1851/1851 1791/1840/1840 1794/1843/1843 +f 1793/1842/1842 1789/1838/1838 1802/1835/1835 +f 1802/1835/1835 1789/1838/1838 1785/1836/1836 +f 1803/1837/1837 1804/1852/1852 1802/1835/1835 +f 1804/1852/1852 1803/1837/1837 1805/1853/1853 +f 2259/1854/1854 1805/1853/1853 1787/1834/1834 +f 1805/1853/1853 1803/1837/1837 1788/10/1828 +f 1805/1853/1853 1788/10/1828 1787/1834/1834 +f 1808/1855/1855 1809/1856/1856 2297/1857/1857 +f 1808/1855/1855 1807/1858/1858 1810/1859/1859 +f 1808/1855/1855 1810/1859/1859 1809/1856/1856 +f 1807/1858/1858 1806/1860/1860 1797/1847/1847 +f 1809/1856/1856 1811/1861/1861 1812/1862/1862 +f 1810/1859/1859 1811/1861/1861 1809/1856/1856 +f 1812/1862/1862 1811/1861/1861 1813/1863/1863 +f 1811/1861/1861 1814/1846/1846 1813/1863/1863 +f 1810/1859/1859 1807/1858/1858 1797/1847/1847 +f 1814/1846/1846 1811/1861/1861 1810/1859/1859 +f 1813/1863/1863 1815/1864/1864 1812/1862/1862 +f 1815/1864/1864 1813/1863/1863 1796/1845/1845 +f 1815/1864/1864 1796/1845/1845 1799/1850/1850 +f 1814/1846/1846 1810/1859/1859 1797/1847/1847 +f 1796/1845/1845 1813/1863/1863 1814/1846/1846 +f 1806/1860/1860 1816/1865/1865 1817/1866/1866 +f 1806/1860/1860 1817/1866/1866 1797/1847/1847 +f 1816/1865/1865 1818/1867/1867 1817/1866/1866 +f 1820/1868/1868 1819/1869/1869 1821/1870/1870 +f 1819/1869/1869 1816/1865/1865 2293/1871/1871 +f 1819/1869/1869 1818/1867/1867 1816/1865/1865 +f 2317/1872/1872 1816/1865/1865 1806/1860/1860 +f 1822/1873/1873 1823/1874/1874 1824/1875/1875 +f 1822/1873/1873 1824/1875/1875 1825/1876/1876 +f 1827/1877/1877 1823/1874/1874 1822/1873/1873 +f 1827/1877/1877 1826/1878/1878 1823/1874/1874 +f 1828/1879/1879 1820/1868/1868 1821/1870/1870 +f 1828/1879/1879 1826/1878/1878 1827/1877/1877 +f 1830/1880/1880 1833/1881/1881 1831/1882/1882 +f 1833/1881/1881 1830/1880/1880 2233/1883/1883 +f 2233/1883/1883 1830/1880/1880 1829/1884/1884 +f 1825/1876/1876 1824/1875/1875 2289/1885/1885 +f 2289/1885/1885 1824/1875/1875 1823/1874/1874 +f 1834/1886/1886 1835/1887/1887 1833/1881/1881 +f 1834/1886/1886 1833/1881/1881 2233/1883/1883 +f 1833/1881/1881 1835/1887/1887 1832/1888/1888 +f 1833/1881/1881 1832/1888/1888 1831/1882/1882 +f 1832/1888/1888 1835/1887/1887 1836/1889/1889 +f 1835/1887/1887 1834/1886/1886 1837/1890/1890 +f 1836/1889/1889 1835/1887/1887 1837/1890/1890 +f 1839/1891/1891 1838/1892/1892 1840/1893/1893 +f 1836/1889/1889 1840/1893/1893 1841/1894/1894 +f 1836/1889/1889 1841/1894/1894 1832/1888/1888 +f 1842/1895/1895 1843/1896/1896 1848/1897/1897 +f 1842/1895/1895 1848/1897/1897 1844/1898/1898 +f 1843/1896/1896 1845/1899/1899 1848/1897/1897 +f 1845/1899/1899 1839/1891/1891 1840/1893/1893 +f 1845/1899/1899 1843/1896/1896 1839/1891/1891 +f 1839/1891/1891 1843/1896/1896 1846/1900/1900 +f 1848/1897/1897 1847/1901/1901 1844/1898/1898 +f 1847/1901/1901 1848/1897/1897 1849/1902/1902 +f 1850/1903/1903 1871/1904/1904 1851/1905/1905 +f 1849/1902/1902 1848/1897/1897 1850/1903/1903 +f 1850/1903/1903 1848/1897/1897 1852/1906/1906 +f 1853/1907/1907 1854/1908/1908 1855/1909/1909 +f 1853/1907/1907 1856/1910/1910 1854/1908/1908 +f 1857/1911/1911 1858/1912/1912 1853/1907/1907 +f 1858/1912/1912 1856/1910/1910 1853/1907/1907 +f 1859/1913/1913 1856/1910/1910 1860/1914/1914 +f 1859/1913/1913 1860/1914/1914 1861/1915/1915 +f 1856/1910/1910 1858/1912/1912 1862/1916/1916 +f 1856/1910/1910 1862/1916/1916 1860/1914/1914 +f 1854/1908/1908 1856/1910/1910 1859/1913/1913 +f 1864/1917/1917 1858/1912/1912 1857/1911/1911 +f 1864/1917/1917 1857/1911/1911 1863/1918/1918 +f 1862/1916/1916 1858/1912/1912 1864/1917/1917 +f 1862/1916/1916 1864/1917/1917 1863/1918/1918 +f 1862/1916/1916 1865/1919/1919 1860/1914/1914 +f 1862/1916/1916 1866/1920/1920 1867/1921/1921 +f 1862/1916/1916 1867/1921/1921 1865/1919/1919 +f 1868/1922/1922 1869/1923/1923 1851/1905/1905 +f 1866/1920/1920 1869/1923/1923 1868/1922/1922 +f 1851/1905/1905 1869/1923/1923 1863/1918/1918 +f 1869/1923/1923 1862/1916/1916 1863/1918/1918 +f 1869/1923/1923 1866/1920/1920 1862/1916/1916 +f 1851/1905/1905 1871/1904/1904 1870/1924/1924 +f 1851/1905/1905 1870/1924/1924 1868/1922/1922 +f 1871/1904/1904 1850/1903/1903 1852/1906/1906 +f 1870/1924/1924 1871/1904/1904 1852/1906/1906 +f 1872/1925/1925 1873/1926/1926 1874/1927/1927 +f 1875/1928/1928 1873/1926/1926 1872/1925/1925 +f 1873/1926/1926 1875/1928/1928 1876/1929/1929 +f 1878/1930/1930 1876/1929/1929 1877/1931/1931 +f 1878/1930/1930 1877/1931/1931 1855/1909/1909 +f 1876/1929/1929 1875/1928/1928 1877/1931/1931 +f 1879/1932/1932 1878/1930/1930 1855/1909/1909 +f 1879/1932/1932 1854/1908/1908 2173/1933/1933 +f 1854/1908/1908 1879/1932/1932 1855/1909/1909 +f 1905/1934/1934 1881/1935/1935 1873/1926/1926 +f 1873/1926/1926 1883/1936/1936 1874/1927/1927 +f 1873/1926/1926 1881/1935/1935 1883/1936/1936 +f 1884/1937/1937 1885/1938/1938 1886/1939/1939 +f 1884/1937/1937 1887/1940/1940 1885/1938/1938 +f 1888/1941/1941 1889/1942/1942 1884/1937/1937 +f 1889/1942/1942 1887/1940/1940 1884/1937/1937 +f 1890/1943/1943 1891/1944/1944 1892/1945/1945 +f 1891/1944/1944 1889/1942/1942 1893/1946/1946 +f 1885/1938/1938 1887/1940/1940 1891/1944/1944 +f 1885/1938/1938 1891/1944/1944 1890/1943/1943 +f 1887/1940/1940 1889/1942/1942 1891/1944/1944 +f 1893/1946/1946 1889/1942/1942 1888/1941/1941 +f 1893/1946/1946 1888/1941/1941 1894/1947/1947 +f 1895/1948/1948 1896/1949/1949 1894/1947/1947 +f 1895/1948/1948 1894/1947/1947 1882/1950/1950 +f 1896/1949/1949 1897/1951/1951 1893/1946/1946 +f 1896/1949/1949 1893/1946/1946 1894/1947/1947 +f 1891/1944/1944 1898/1952/1952 1892/1945/1945 +f 1898/1952/1952 1899/1953/1953 1892/1945/1945 +f 1893/1946/1946 1897/1951/1951 1898/1952/1952 +f 1893/1946/1946 1898/1952/1952 1891/1944/1944 +f 1899/1953/1953 1898/1952/1952 2112/1954/1954 +f 1899/1953/1953 2112/1954/1954 2110/1955/1955 +f 1898/1952/1952 1897/1951/1951 2112/1954/1954 +f 1900/1956/1956 1895/1948/1948 1901/1957/1957 +f 1901/1957/1957 1895/1948/1948 1882/1950/1950 +f 1902/1958/1958 1903/1959/1959 1900/1956/1956 +f 1901/1957/1957 1902/1958/1958 1900/1956/1956 +f 1903/1959/1959 1902/1958/1958 1904/1960/1960 +f 1902/1958/1958 1905/1934/1934 1904/1960/1960 +f 1902/1958/1958 1881/1935/1935 1905/1934/1934 +f 1880/1961/1961 1901/1957/1957 1882/1950/1950 +f 1880/1961/1961 1902/1958/1958 1901/1957/1957 +f 1881/1935/1935 1902/1958/1958 1880/1961/1961 +f 1910/1962/1962 1909/1963/1963 1886/1939/1939 +f 1908/1964/1964 1909/1963/1963 2096/1965/1965 +f 1909/1963/1963 1910/1962/1962 2096/1965/1965 +f 2096/1965/1965 1910/1962/1962 2097/1966/1966 +f 1885/1938/1938 1911/1967/1967 1910/1962/1962 +f 1885/1938/1938 1910/1962/1962 1886/1939/1939 +f 2097/1966/1966 1911/1967/1967 2098/1968/1968 +f 1910/1962/1962 1911/1967/1967 2097/1966/1966 +f 1912/1969/1969 1885/1938/1938 1890/1943/1943 +f 2098/1968/1968 1911/1967/1967 1912/1969/1969 +f 1911/1967/1967 1885/1938/1938 1912/1969/1969 +f 1915/1970/1970 1916/1971/1971 1913/1972/1972 +f 1913/1972/1972 1916/1971/1971 1917/1973/1973 +f 1917/1973/1973 1906/1974/1974 1908/1964/1964 +f 1906/1974/1974 1916/1971/1971 1907/1975/1975 +f 1906/1974/1974 1917/1973/1973 1916/1971/1971 +f 1918/1976/1976 1919/1977/1977 1920/1978/1978 +f 1918/1976/1976 1920/1978/1978 1921/1979/1979 +f 1917/1973/1973 1919/1977/1977 1913/1972/1972 +f 1913/1972/1972 1919/1977/1977 1922/1980/1980 +f 1913/1972/1972 1922/1980/1980 1914/1981/1981 +f 1923/1982/1982 1924/1983/1983 1925/1984/1984 +f 1923/1982/1982 1925/1984/1984 1926/1985/1985 +f 1927/1986/1986 1928/1987/1987 1924/1983/1983 +f 1927/1986/1986 1924/1983/1983 1923/1982/1982 +f 1925/1984/1984 1929/1988/1988 1930/1989/1989 +f 1929/1988/1988 1928/1987/1987 1931/1990/1990 +f 1929/1988/1988 1931/1990/1990 1930/1989/1989 +f 1925/1984/1984 1924/1983/1983 1929/1988/1988 +f 1924/1983/1983 1928/1987/1987 1929/1988/1988 +f 1933/1991/1991 1928/1987/1987 1927/1986/1986 +f 1933/1991/1991 1927/1986/1986 1932/1992/1992 +f 1931/1990/1990 1928/1987/1987 1933/1991/1991 +f 1931/1990/1990 1933/1991/1991 1932/1992/1992 +f 1930/1989/1989 1934/1993/1993 1925/1984/1984 +f 1930/1989/1989 1935/1994/1994 1934/1993/1993 +f 1931/1990/1990 1936/1995/1995 1930/1989/1989 +f 1936/1995/1995 1935/1994/1994 1930/1989/1989 +f 1937/1996/1996 1936/1995/1995 1939/1997/1997 +f 1937/1996/1996 1939/1997/1997 1938/1998/1998 +f 1935/1994/1994 1936/1995/1995 1937/1996/1996 +f 1940/1999/1999 1932/1992/1992 1921/1979/1979 +f 1932/1992/1992 1936/1995/1995 1931/1990/1990 +f 1939/1997/1997 1936/1995/1995 1932/1992/1992 +f 1939/1997/1997 1932/1992/1992 1940/1999/1999 +f 1941/2000/2000 1940/1999/1999 1920/1978/1978 +f 1939/1997/1997 1940/1999/1999 1941/2000/2000 +f 1920/1978/1978 1940/1999/1999 1921/1979/1979 +f 1919/1977/1977 1943/2001/2001 1920/1978/1978 +f 1920/1978/1978 1942/2002/2002 1941/2000/2000 +f 1920/1978/1978 1943/2001/2001 1942/2002/2002 +f 1942/2002/2002 1943/2001/2001 1944/2003/2003 +f 1943/2001/2001 1919/1977/1977 1917/1973/1973 +f 1945/2004/2004 1947/2005/2005 1946/2006/2006 +f 1948/2007/2007 1949/2008/2008 1947/2005/2005 +f 1948/2007/2007 1947/2005/2005 1945/2004/2004 +f 1947/2005/2005 1950/2009/2009 1951/2010/2010 +f 1947/2005/2005 1949/2008/2008 1950/2009/2009 +f 1950/2009/2009 1949/2008/2008 1952/2011/2011 +f 1954/2012/2012 1949/2008/2008 1953/2013/2013 +f 1954/2012/2012 1953/2013/2013 1926/1985/1985 +f 1949/2008/2008 1948/2007/2007 1953/2013/2013 +f 1952/2011/2011 1954/2012/2012 1955/2014/2014 +f 1949/2008/2008 1954/2012/2012 1952/2011/2011 +f 1955/2014/2014 1954/2012/2012 1925/1984/1984 +f 1925/1984/1984 1954/2012/2012 1926/1985/1985 +f 1958/2015/2015 1959/2016/2016 1960/2017/2017 +f 1959/2016/2016 1958/2015/2015 1961/2018/2018 +f 1958/2015/2015 1957/2019/2019 1961/2018/2018 +f 1957/2019/2019 1947/2005/2005 1962/2020/2020 +f 1947/2005/2005 2171/2021/2021 1962/2020/2020 +f 2171/2021/2021 1947/2005/2005 1951/2010/2010 +f 1947/2005/2005 1956/2022/2022 1946/2006/2006 +f 1947/2005/2005 1957/2019/2019 1956/2022/2022 +f 1964/2023/2023 1966/2024/2024 1967/2025/2025 +f 1964/2023/2023 1967/2025/2025 1963/2026/2026 +f 1968/2027/2027 1966/2024/2024 1964/2023/2023 +f 1968/2027/2027 1964/2023/2023 1963/2026/2026 +f 1969/2028/2028 1971/2029/2029 1972/2030/2030 +f 1969/2028/2028 1972/2030/2030 1970/2031/2031 +f 1967/2025/2025 1966/2024/2024 1969/2028/2028 +f 1966/2024/2024 1971/2029/2029 1969/2028/2028 +f 1972/2030/2030 1971/2029/2029 1973/2032/2032 +f 1972/2030/2030 1973/2032/2032 1960/2017/2017 +f 1971/2029/2029 1966/2024/2024 1968/2027/2027 +f 1971/2029/2029 1968/2027/2027 1973/2032/2032 +f 1972/2030/2030 1959/2016/2016 1974/2033/2033 +f 1959/2016/2016 1972/2030/2030 1960/2017/2017 +f 1975/2034/2034 1977/2035/2035 1992/2036/2036 +f 1978/2037/2037 1977/2035/2035 1975/2034/2034 +f 1979/2038/2038 1977/2035/2035 1980/2039/2039 +f 1980/2039/2039 1977/2035/2035 1981/2040/2040 +f 1980/2039/2039 1981/2040/2040 1965/2041/2041 +f 1977/2035/2035 1978/2037/2037 1981/2040/2040 +f 1979/2038/2038 1980/2039/2039 2283/2042/2042 +f 1980/2039/2039 1982/2043/2043 2283/2042/2042 +f 1982/2043/2043 1980/2039/2039 1965/2041/2041 +f 1963/2026/2026 1983/2044/2044 1982/2043/2043 +f 1963/2026/2026 1982/2043/2043 1965/2041/2041 +f 1967/2025/2025 1984/2045/2045 1983/2044/2044 +f 1967/2025/2025 1983/2044/2044 1963/2026/2026 +f 1984/2045/2045 1985/2046/2046 1986/2047/2047 +f 1985/2046/2046 1967/2025/2025 1969/2028/2028 +f 1984/2045/2045 1967/2025/2025 1985/2046/2046 +f 1975/2034/2034 1992/2036/2036 1987/2048/2048 +f 1975/2034/2034 1987/2048/2048 1976/2049/2049 +f 1992/2036/2036 1988/2050/2050 1987/2048/2048 +f 1989/2051/2051 1990/2052/2052 1991/2053/2053 +f 1988/2050/2050 1992/2036/2036 1990/2052/2052 +f 1988/2050/2050 1990/2052/2052 1989/2051/2051 +f 1990/2052/2052 1992/2036/2036 1994/2054/2054 +f 1993/2055/2055 1994/2054/2054 1995/2056/2056 +f 1993/2055/2055 1995/2056/2056 1996/2057/2057 +f 1997/2058/2058 1994/2054/2054 1993/2055/2055 +f 1990/2052/2052 1994/2054/2054 1997/2058/2058 +f 1998/2059/2059 1999/2060/2060 2000/2061/2061 +f 1998/2059/2059 2001/2062/2062 1999/2060/2060 +f 2001/2062/2062 1998/2059/2059 2331/2063/2063 +f 1998/2059/2059 2002/2064/2064 2003/2065/2065 +f 1998/2059/2059 2003/2065/2065 2331/2063/2063 +f 2003/2065/2065 2004/2066/2066 2005/2067/2067 +f 2003/2065/2065 2005/2067/2067 2006/2068/2068 +f 2003/2065/2065 2002/2064/2064 2004/2066/2066 +f 2007/2069/2069 2008/2070/2070 1996/2057/2057 +f 2005/2067/2067 2004/2066/2066 2008/2070/2070 +f 2005/2067/2067 2008/2070/2070 2007/2069/2069 +f 2004/2066/2066 2002/2064/2064 2008/2070/2070 +f 2005/2067/2067 2009/2071/2071 2006/2068/2068 +f 2010/2072/2072 2007/2069/2069 2011/2073/2073 +f 2009/2071/2071 2007/2069/2069 2010/2072/2072 +f 2011/2073/2073 2007/2069/2069 1996/2057/2057 +f 2007/2069/2069 2009/2071/2071 2005/2067/2067 +f 1995/2056/2056 2011/2073/2073 1996/2057/2057 +f 1995/2056/2056 2012/2074/2074 2011/2073/2073 +f 2011/2073/2073 2013/2075/2075 2010/2072/2072 +f 2011/2073/2073 2012/2074/2074 2013/2075/2075 +f 2012/2074/2074 1995/2056/2056 1994/2054/2054 +f 2016/2076/2076 2015/2077/2077 2001/2062/2062 +f 1999/2060/2060 2015/2077/2077 2017/2078/2078 +f 1999/2060/2060 2017/2078/2078 2000/2061/2061 +f 2015/2077/2077 2014/2079/2079 2017/2078/2078 +f 2001/2062/2062 2015/2077/2077 1999/2060/2060 +f 2018/2080/2080 2019/2081/2081 2020/2082/2082 +f 2021/2083/2083 2019/2081/2081 2018/2080/2080 +f 2017/2078/2078 2021/2083/2083 2022/2084/2084 +f 2017/2078/2078 2022/2084/2084 2000/2061/2061 +f 2021/2083/2083 2023/2085/2085 2019/2081/2081 +f 2024/2086/2086 2023/2085/2085 2017/2078/2078 +f 2024/2086/2086 2017/2078/2078 2014/2079/2079 +f 2017/2078/2078 2023/2085/2085 2021/2083/2083 +f 2025/2087/2087 2026/2088/2088 2027/2089/2089 +f 2028/2090/2090 2026/2088/2088 2025/2087/2087 +f 2026/2088/2088 2019/2081/2081 2047/2091/2091 +f 2026/2088/2088 2028/2090/2090 2019/2081/2081 +f 2019/2081/2081 2028/2090/2090 2029/2092/2092 +f 2019/2081/2081 2029/2092/2092 2020/2082/2082 +f 2034/2093/2093 2033/2094/2094 2031/2095/2095 +f 2034/2093/2093 2031/2095/2095 2030/2096/2096 +f 2035/2097/2097 2036/2098/2098 2037/2099/2099 +f 2036/2098/2098 2038/2100/2100 2039/2101/2101 +f 2031/2095/2095 2033/2094/2094 2036/2098/2098 +f 2031/2095/2095 2036/2098/2098 2035/2097/2097 +f 2033/2094/2094 2038/2100/2100 2036/2098/2098 +f 2040/2102/2102 2038/2100/2100 2041/2103/2103 +f 2040/2102/2102 2041/2103/2103 2027/2089/2089 +f 2038/2100/2100 2033/2094/2094 2034/2093/2093 +f 2038/2100/2100 2034/2093/2093 2041/2103/2103 +f 2039/2101/2101 2038/2100/2100 2040/2102/2102 +f 2042/2104/2104 2040/2102/2102 2027/2089/2089 +f 2040/2102/2102 2043/2105/2105 2039/2101/2101 +f 2036/2098/2098 2044/2106/2106 2045/2107/2107 +f 2036/2098/2098 2045/2107/2107 2037/2099/2099 +f 2039/2101/2101 2044/2106/2106 2036/2098/2098 +f 2039/2101/2101 2043/2105/2105 2044/2106/2106 +f 2044/2106/2106 2043/2105/2105 2046/2108/2108 +f 2043/2105/2105 2040/2102/2102 2042/2104/2104 +f 2042/2104/2104 2026/2088/2088 2047/2091/2091 +f 2026/2088/2088 2042/2104/2104 2027/2089/2089 +f 2048/2109/2109 2049/2110/2110 2050/2111/2111 +f 2048/2109/2109 2050/2111/2111 2051/2112/2112 +f 2052/2113/2113 2049/2110/2110 2048/2109/2109 +f 2050/2111/2111 2049/2110/2110 2053/2114/2114 +f 2056/2115/2115 2054/2116/2116 2055/2117/2117 +f 2056/2115/2115 2055/2117/2117 2032/2118/2118 +f 2054/2116/2116 2049/2110/2110 2052/2113/2113 +f 2054/2116/2116 2052/2113/2113 2055/2117/2117 +f 2056/2115/2115 2032/2118/2118 2057/2119/2119 +f 2032/2118/2118 2058/2120/2120 2057/2119/2119 +f 2059/2121/2121 2058/2120/2120 2060/2122/2122 +f 2060/2122/2122 2035/2097/2097 2059/2121/2121 +f 2035/2097/2097 2060/2122/2122 2031/2095/2095 +f 2030/2096/2096 2060/2122/2122 2058/2120/2120 +f 2030/2096/2096 2058/2120/2120 2032/2118/2118 +f 2031/2095/2095 2060/2122/2122 2030/2096/2096 +f 2061/2123/2123 2062/2124/2124 2063/2125/2125 +f 2063/2125/2125 2062/2124/2124 2053/2114/2114 +f 2062/2124/2124 2050/2111/2111 2053/2114/2114 +f 2063/2125/2125 2065/2126/2126 2064/2127/2127 +f 2066/2128/2128 2067/2129/2129 2068/2130/2130 +f 2066/2128/2128 2069/2131/2131 2067/2129/2129 +f 2066/2128/2128 2065/2126/2126 2053/2114/2114 +f 2066/2128/2128 2053/2114/2114 2069/2131/2131 +f 2053/2114/2114 2065/2126/2126 2063/2125/2125 +f 2070/2132/2132 2071/2133/2133 2072/2134/2134 +f 2070/2132/2132 2072/2134/2134 2073/2135/2135 +f 2074/2136/2136 2075/2137/2137 2071/2133/2133 +f 2074/2136/2136 2071/2133/2133 2070/2132/2132 +f 2077/2138/2138 2075/2137/2137 2078/2139/2139 +f 2072/2134/2134 2071/2133/2133 2077/2138/2138 +f 2072/2134/2134 2077/2138/2138 2076/2140/2140 +f 2071/2133/2133 2075/2137/2137 2077/2138/2138 +f 2083/2141/2141 2078/2139/2139 2079/2142/2142 +f 2083/2141/2141 2079/2142/2142 2068/2130/2130 +f 2078/2139/2139 2075/2137/2137 2074/2136/2136 +f 2078/2139/2139 2074/2136/2136 2079/2142/2142 +f 2078/2139/2139 2080/2143/2143 2497/2144/2144 +f 2078/2139/2139 2497/2144/2144 2077/2138/2138 +f 2497/2144/2144 2080/2143/2143 2082/2145/2145 +f 2497/2144/2144 2082/2145/2145 2081/2146/2146 +f 2083/2141/2141 2080/2143/2143 2078/2139/2139 +f 2082/2145/2145 2080/2143/2143 2083/2141/2141 +f 2083/2141/2141 2084/2147/2147 2499/2148/2148 +f 2083/2141/2141 2499/2148/2148 2082/2145/2145 +f 2067/2129/2129 2084/2147/2147 2083/2141/2141 +f 2067/2129/2129 2083/2141/2141 2068/2130/2130 +f 2085/2149/2149 2086/2150/2150 2087/2151/2151 +f 2087/2151/2151 2086/2150/2150 2073/2135/2135 +f 2086/2150/2150 2088/2152/2152 2091/2153/2153 +f 2088/2152/2152 2086/2150/2150 2085/2149/2149 +f 2072/2134/2134 2087/2151/2151 2073/2135/2135 +f 2089/2154/2154 2072/2134/2134 2483/2155/2155 +f 2087/2151/2151 2072/2134/2134 2089/2154/2154 +f 2483/2155/2155 2072/2134/2134 2076/2140/2140 +f 2090/2156/2156 2091/2153/2153 1698/2157/2157 +f 2092/2158/2158 2091/2153/2153 2090/2156/2156 +f 2086/2150/2150 2093/2159/2159 2073/2135/2135 +f 2086/2150/2150 2091/2153/2153 2092/2158/2158 +f 2086/2150/2150 2092/2158/2158 2093/2159/2159 +f 2095/2160/2160 2094/2161/2161 2091/2153/2153 +f 2094/2161/2161 1696/1748/1742 2091/2153/2153 +f 2091/2153/2153 1696/1748/1742 1697/1750/1744 +f 2091/2153/2153 1697/1750/1744 1698/2157/2157 +f 1917/1973/1973 2096/1965/1965 1943/2001/2001 +f 1943/2001/2001 2097/1966/1966 1944/2003/2003 +f 1943/2001/2001 2096/1965/1965 2097/1966/1966 +f 2096/1965/1965 1917/1973/1973 1908/1964/1964 +f 2099/2162/2162 2098/1968/1968 2100/2163/2163 +f 1944/2003/2003 2098/1968/1968 2099/2162/2162 +f 2100/2163/2163 2098/1968/1968 1912/1969/1969 +f 2098/1968/1968 1944/2003/2003 2097/1966/1966 +f 2109/2164/2164 1941/2000/2000 2101/2165/2165 +f 1941/2000/2000 1942/2002/2002 2101/2165/2165 +f 2099/2162/2162 1942/2002/2002 1944/2003/2003 +f 2101/2165/2165 1942/2002/2002 2099/2162/2162 +f 2106/2166/2166 2100/2163/2163 2103/2167/2167 +f 2100/2163/2163 2102/2168/2168 2103/2167/2167 +f 2104/2169/2169 2102/2168/2168 2105/2170/2170 +f 2103/2167/2167 2102/2168/2168 2104/2169/2169 +f 2105/2170/2170 2102/2168/2168 2100/2163/2163 +f 2105/2170/2170 2100/2163/2163 1912/1969/1969 +f 1890/1943/1943 2105/2170/2170 1912/1969/1969 +f 2105/2170/2170 1892/1945/1945 1899/1953/1953 +f 1892/1945/1945 2105/2170/2170 1890/1943/1943 +f 2101/2165/2165 2106/2166/2166 2108/2171/2171 +f 2108/2171/2171 2106/2166/2166 2107/2172/2172 +f 2107/2172/2172 2106/2166/2166 2103/2167/2167 +f 2100/2163/2163 2106/2166/2166 2099/2162/2162 +f 2106/2166/2166 2101/2165/2165 2099/2162/2162 +f 1938/1998/1998 2108/2171/2171 1937/1996/1996 +f 1938/1998/1998 2109/2164/2164 2108/2171/2171 +f 1939/1997/1997 2109/2164/2164 1938/1998/1998 +f 2109/2164/2164 1939/1997/1997 1941/2000/2000 +f 2101/2165/2165 2108/2171/2171 2109/2164/2164 +f 2105/2170/2170 2110/1955/1955 2104/2169/2169 +f 2104/2169/2169 2110/1955/1955 2111/2173/2173 +f 2113/2174/2174 2111/2173/2173 2112/1954/1954 +f 2111/2173/2173 2110/1955/1955 2112/1954/1954 +f 2110/1955/1955 2105/2170/2170 1899/1953/1953 +f 2123/2175/2175 1896/1949/1949 1895/1948/1948 +f 1896/1949/1949 2112/1954/1954 1897/1951/1951 +f 2113/2174/2174 2112/1954/1954 1896/1949/1949 +f 2113/2174/2174 1896/1949/1949 2123/2175/2175 +f 2103/2167/2167 2114/2176/2176 2116/2177/2177 +f 2114/2176/2176 2124/2178/2178 2115/2179/2179 +f 2116/2177/2177 2114/2176/2176 2115/2179/2179 +f 2124/2178/2178 2104/2169/2169 2111/2173/2173 +f 2124/2178/2178 2114/2176/2176 2104/2169/2169 +f 2114/2176/2176 2103/2167/2167 2104/2169/2169 +f 2107/2172/2172 2116/2177/2177 2118/2180/2180 +f 2118/2180/2180 2116/2177/2177 2117/2181/2181 +f 2117/2181/2181 2116/2177/2177 2115/2179/2179 +f 2116/2177/2177 2107/2172/2172 2103/2167/2167 +f 2108/2171/2171 2118/2180/2180 1937/1996/1996 +f 1935/1994/1994 2118/2180/2180 2120/2182/2182 +f 1935/1994/1994 2120/2182/2182 2119/2183/2183 +f 2120/2182/2182 2118/2180/2180 2117/2181/2181 +f 2118/2180/2180 2108/2171/2171 2107/2172/2172 +f 2121/2184/2184 2122/2185/2185 2126/2186/2186 +f 2113/2174/2174 2121/2184/2184 2111/2173/2173 +f 2122/2185/2185 2121/2184/2184 2113/2174/2174 +f 2113/2174/2174 2123/2175/2175 2122/2185/2185 +f 2115/2179/2179 2124/2178/2178 2127/2187/2187 +f 2124/2178/2178 2126/2186/2186 2125/2188/2188 +f 2127/2187/2187 2124/2178/2178 2125/2188/2188 +f 2126/2186/2186 2124/2178/2178 2121/2184/2184 +f 2121/2184/2184 2124/2178/2178 2111/2173/2173 +f 2117/2181/2181 2127/2187/2187 2129/2189/2189 +f 2127/2187/2187 2141/2190/2190 2128/2191/2191 +f 2129/2189/2189 2127/2187/2187 2128/2191/2191 +f 2141/2190/2190 2127/2187/2187 2125/2188/2188 +f 2127/2187/2187 2117/2181/2181 2115/2179/2179 +f 2120/2182/2182 2129/2189/2189 2130/2192/2192 +f 2130/2192/2192 2129/2189/2189 2131/2193/2193 +f 2131/2193/2193 2129/2189/2189 2128/2191/2191 +f 2129/2189/2189 2120/2182/2182 2117/2181/2181 +f 1935/1994/1994 2119/2183/2183 1934/1993/1993 +f 2118/2180/2180 1935/1994/1994 1937/1996/1996 +f 1934/1993/1993 2132/2194/2194 1925/1984/1984 +f 2131/2193/2193 2132/2194/2194 2130/2192/2192 +f 2120/2182/2182 2130/2192/2192 2119/2183/2183 +f 2130/2192/2192 2132/2194/2194 1934/1993/1993 +f 2130/2192/2192 1934/1993/1993 2119/2183/2183 +f 2122/2185/2185 2135/2195/2195 2126/2186/2186 +f 2135/2195/2195 2133/2196/2196 2126/2186/2186 +f 2133/2196/2196 2135/2195/2195 2134/2197/2197 +f 2135/2195/2195 2122/2185/2185 2123/2175/2175 +f 2123/2175/2175 2136/2198/2198 2135/2195/2195 +f 2136/2198/2198 2137/2199/2199 2134/2197/2197 +f 2135/2195/2195 2136/2198/2198 2134/2197/2197 +f 2137/2199/2199 2136/2198/2198 1900/1956/1956 +f 1895/1948/1948 2136/2198/2198 2123/2175/2175 +f 1900/1956/1956 2136/2198/2198 1895/1948/1948 +f 2125/2188/2188 2138/2200/2200 2139/2201/2201 +f 2126/2186/2186 2138/2200/2200 2125/2188/2188 +f 2139/2201/2201 2138/2200/2200 2140/2202/2202 +f 2133/2196/2196 2138/2200/2200 2126/2186/2186 +f 2128/2191/2191 2141/2190/2190 2142/2203/2203 +f 2141/2190/2190 2139/2201/2201 2142/2203/2203 +f 2139/2201/2201 2141/2190/2190 2125/2188/2188 +f 2131/2193/2193 2128/2191/2191 2143/2204/2204 +f 2143/2204/2204 2128/2191/2191 2142/2203/2203 +f 2132/2194/2194 1955/2014/2014 1925/1984/1984 +f 1955/2014/2014 2131/2193/2193 2143/2204/2204 +f 1952/2011/2011 1955/2014/2014 2143/2204/2204 +f 1955/2014/2014 2132/2194/2194 2131/2193/2193 +f 2134/2197/2197 2137/2199/2199 2144/2205/2205 +f 2137/2199/2199 1903/1959/1959 2144/2205/2205 +f 1903/1959/1959 1904/1960/1960 2145/2206/2206 +f 2144/2205/2205 1903/1959/1959 2145/2206/2206 +f 1903/1959/1959 2137/2199/2199 1900/1956/1956 +f 2134/2197/2197 2146/2207/2207 2133/2196/2196 +f 2149/2208/2208 2146/2207/2207 2147/2209/2209 +f 2146/2207/2207 2145/2206/2206 2147/2209/2209 +f 2146/2207/2207 2144/2205/2205 2145/2206/2206 +f 2144/2205/2205 2146/2207/2207 2134/2197/2197 +f 2148/2210/2210 2140/2202/2202 2149/2208/2208 +f 2149/2208/2208 2140/2202/2202 2146/2207/2207 +f 2146/2207/2207 2138/2200/2200 2133/2196/2196 +f 2146/2207/2207 2140/2202/2202 2138/2200/2200 +f 2142/2203/2203 2150/2211/2211 2152/2212/2212 +f 2139/2201/2201 2150/2211/2211 2142/2203/2203 +f 2152/2212/2212 2150/2211/2211 2151/2213/2213 +f 2151/2213/2213 2150/2211/2211 2148/2210/2210 +f 2140/2202/2202 2150/2211/2211 2139/2201/2201 +f 2148/2210/2210 2150/2211/2211 2140/2202/2202 +f 2143/2204/2204 2152/2212/2212 2154/2214/2214 +f 2154/2214/2214 2152/2212/2212 2153/2215/2215 +f 2152/2212/2212 2143/2204/2204 2142/2203/2203 +f 1952/2011/2011 2154/2214/2214 1950/2009/2009 +f 1950/2009/2009 2154/2214/2214 2155/2216/2216 +f 1950/2009/2009 2155/2216/2216 1951/2010/2010 +f 2155/2216/2216 2154/2214/2214 2153/2215/2215 +f 2154/2214/2214 1952/2011/2011 2143/2204/2204 +f 1904/1960/1960 2156/2217/2217 2145/2206/2206 +f 2161/2218/2218 2156/2217/2217 2157/2219/2219 +f 2156/2217/2217 2158/2220/2220 2157/2219/2219 +f 2158/2220/2220 2156/2217/2217 2159/2221/2221 +f 2156/2217/2217 1904/1960/1960 2159/2221/2221 +f 1905/1934/1934 2159/2221/2221 1904/1960/1960 +f 2159/2221/2221 2160/2222/2222 2158/2220/2220 +f 2159/2221/2221 1905/1934/1934 2160/2222/2222 +f 2145/2206/2206 2161/2218/2218 2147/2209/2209 +f 2147/2209/2209 2161/2218/2218 2164/2223/2223 +f 2156/2217/2217 2161/2218/2218 2145/2206/2206 +f 2149/2208/2208 2162/2224/2224 2148/2210/2210 +f 2166/2225/2225 2162/2224/2224 2163/2226/2226 +f 2162/2224/2224 2164/2223/2223 2163/2226/2226 +f 2164/2223/2223 2162/2224/2224 2147/2209/2209 +f 2147/2209/2209 2162/2224/2224 2149/2208/2208 +f 2151/2213/2213 2165/2227/2227 2152/2212/2212 +f 2151/2213/2213 2166/2225/2225 2165/2227/2227 +f 2162/2224/2224 2166/2225/2225 2148/2210/2210 +f 2166/2225/2225 2151/2213/2213 2148/2210/2210 +f 2153/2215/2215 2167/2228/2228 2155/2216/2216 +f 2153/2215/2215 2165/2227/2227 2167/2228/2228 +f 2167/2228/2228 2168/2229/2229 2169/2230/2230 +f 2167/2228/2228 2165/2227/2227 2168/2229/2229 +f 2165/2227/2227 2153/2215/2215 2152/2212/2212 +f 2155/2216/2216 2169/2230/2230 1951/2010/2010 +f 1951/2010/2010 2169/2230/2230 2170/2231/2231 +f 2167/2228/2228 2169/2230/2230 2155/2216/2216 +f 1962/2020/2020 2171/2021/2021 2172/2232/2232 +f 2172/2232/2232 2171/2021/2021 1951/2010/2010 +f 2172/2232/2232 1951/2010/2010 2170/2231/2231 +f 1854/1908/1908 2174/2233/2233 2173/1933/1933 +f 2173/1933/1933 2174/2233/2233 2175/2234/2234 +f 2174/2233/2233 1861/1915/1915 2176/2235/2235 +f 1861/1915/1915 2174/2233/2233 1859/1913/1913 +f 2174/2233/2233 1854/1908/1908 1859/1913/1913 +f 2176/2235/2235 2178/2236/2236 2177/2237/2237 +f 1861/1915/1915 2178/2236/2236 2176/2235/2235 +f 2178/2236/2236 2179/2238/2238 2177/2237/2237 +f 2178/2236/2236 1865/1919/1919 2179/2238/2238 +f 2179/2238/2238 1865/1919/1919 1867/1921/1921 +f 1860/1914/1914 2178/2236/2236 1861/1915/1915 +f 1865/1919/1919 2178/2236/2236 1860/1914/1914 +f 1878/1930/1930 2180/2239/2239 1876/1929/1929 +f 1878/1930/1930 2181/2240/2240 2180/2239/2239 +f 2181/2240/2240 2173/1933/1933 2175/2234/2234 +f 2173/1933/1933 2181/2240/2240 1879/1932/1932 +f 2181/2240/2240 1878/1930/1930 1879/1932/1932 +f 2175/2234/2234 2182/2241/2241 2183/2242/2242 +f 2183/2242/2242 2182/2241/2241 2184/2243/2243 +f 2176/2235/2235 2175/2234/2234 2174/2233/2233 +f 2182/2241/2241 2175/2234/2234 2176/2235/2235 +f 2177/2237/2237 2182/2241/2241 2176/2235/2235 +f 2182/2241/2241 2177/2237/2237 2184/2243/2243 +f 2184/2243/2243 2186/2244/2244 2185/2245/2245 +f 2184/2243/2243 2177/2237/2237 2186/2244/2244 +f 2186/2244/2244 2177/2237/2237 2179/2238/2238 +f 2181/2240/2240 2187/2246/2246 2180/2239/2239 +f 2181/2240/2240 2188/2247/2247 2187/2246/2246 +f 2187/2246/2246 2188/2247/2247 2189/2248/2248 +f 2188/2247/2247 2175/2234/2234 2183/2242/2242 +f 2189/2248/2248 2188/2247/2247 2183/2242/2242 +f 2175/2234/2234 2188/2247/2247 2181/2240/2240 +f 1873/1926/1926 2191/2249/2249 1905/1934/1934 +f 1876/1929/1929 2191/2249/2249 1873/1926/1926 +f 1905/1934/1934 2191/2249/2249 2160/2222/2222 +f 2180/2239/2239 2191/2249/2249 1876/1929/1929 +f 2191/2249/2249 2192/2250/2250 2160/2222/2222 +f 2191/2249/2249 2193/2251/2251 2192/2250/2250 +f 2194/2252/2252 2193/2251/2251 2195/2253/2253 +f 2192/2250/2250 2193/2251/2251 2194/2252/2252 +f 2190/2254/2254 2193/2251/2251 2189/2248/2248 +f 2193/2251/2251 2187/2246/2246 2189/2248/2248 +f 2195/2253/2253 2193/2251/2251 2190/2254/2254 +f 2187/2246/2246 2193/2251/2251 2180/2239/2239 +f 2193/2251/2251 2191/2249/2249 2180/2239/2239 +f 2160/2222/2222 2196/2255/2255 2158/2220/2220 +f 2196/2255/2255 2194/2252/2252 2197/2256/2256 +f 2194/2252/2252 2196/2255/2255 2192/2250/2250 +f 2192/2250/2250 2196/2255/2255 2160/2222/2222 +f 2157/2219/2219 2198/2257/2257 2199/2258/2258 +f 2158/2220/2220 2198/2257/2257 2157/2219/2219 +f 2198/2257/2257 2197/2256/2256 2199/2258/2258 +f 2197/2256/2256 2198/2257/2257 2196/2255/2255 +f 2196/2255/2255 2198/2257/2257 2158/2220/2220 +f 2200/2259/2259 2201/2260/2260 2164/2223/2223 +f 2161/2218/2218 2200/2259/2259 2164/2223/2223 +f 2201/2260/2260 2200/2259/2259 2202/2261/2261 +f 2202/2261/2261 2200/2259/2259 2199/2258/2258 +f 2157/2219/2219 2200/2259/2259 2161/2218/2218 +f 2199/2258/2258 2200/2259/2259 2157/2219/2219 +f 2203/2262/2262 2201/2260/2260 2204/2263/2263 +f 2163/2226/2226 2201/2260/2260 2203/2262/2262 +f 2204/2263/2263 2201/2260/2260 2202/2261/2261 +f 2201/2260/2260 2163/2226/2226 2164/2223/2223 +f 2166/2225/2225 2205/2264/2264 2165/2227/2227 +f 2206/2265/2265 2205/2264/2264 2163/2226/2226 +f 2206/2265/2265 2163/2226/2226 2203/2262/2262 +f 2163/2226/2226 2205/2264/2264 2166/2225/2225 +f 2168/2229/2229 2208/2266/2266 2207/2267/2267 +f 2208/2266/2266 2168/2229/2229 2205/2264/2264 +f 2208/2266/2266 2205/2264/2264 2206/2265/2265 +f 2205/2264/2264 2168/2229/2229 2165/2227/2227 +f 2209/2268/2268 2213/2269/2269 2170/2231/2231 +f 2169/2230/2230 2209/2268/2268 2170/2231/2231 +f 2213/2269/2269 2210/2270/2270 2211/2271/2271 +f 2213/2269/2269 2209/2268/2268 2210/2270/2270 +f 2207/2267/2267 2209/2268/2268 2168/2229/2229 +f 2210/2270/2270 2209/2268/2268 2207/2267/2267 +f 2168/2229/2229 2209/2268/2268 2169/2230/2230 +f 2212/2272/2272 2213/2269/2269 2214/2273/2273 +f 2170/2231/2231 2212/2272/2272 2172/2232/2232 +f 2213/2269/2269 2212/2272/2272 2170/2231/2231 +f 1961/2018/2018 1974/2033/2033 1959/2016/2016 +f 1974/2033/2033 1961/2018/2018 2216/2274/2274 +f 1974/2033/2033 2216/2274/2274 2215/2275/2275 +f 2216/2274/2274 1961/2018/2018 1962/2020/2020 +f 2216/2274/2274 1962/2020/2020 2172/2232/2232 +f 1962/2020/2020 1961/2018/2018 1957/2019/2019 +f 2216/2274/2274 2217/2276/2276 2218/2277/2277 +f 2214/2273/2273 2217/2276/2276 2212/2272/2272 +f 2217/2276/2276 2216/2274/2274 2172/2232/2232 +f 2212/2272/2272 2217/2276/2276 2172/2232/2232 +f 1972/2030/2030 1974/2033/2033 2219/2278/2278 +f 1972/2030/2030 2219/2278/2278 1970/2031/2031 +f 2219/2278/2278 1974/2033/2033 2220/2279/2279 +f 2220/2279/2279 1974/2033/2033 2215/2275/2275 +f 2220/2279/2279 2221/2280/2280 2222/2281/2281 +f 2222/2281/2281 2221/2280/2280 2223/2282/2282 +f 2221/2280/2280 2216/2274/2274 2218/2277/2277 +f 2223/2282/2282 2221/2280/2280 2218/2277/2277 +f 2216/2274/2274 2221/2280/2280 2215/2275/2275 +f 2221/2280/2280 2220/2279/2279 2215/2275/2275 +f 1985/2046/2046 2224/2283/2283 2225/2284/2284 +f 2226/2285/2285 1969/2028/2028 1970/2031/2031 +f 2226/2285/2285 2224/2283/2283 1985/2046/2046 +f 2226/2285/2285 1985/2046/2046 1969/2028/2028 +f 2224/2283/2283 2226/2285/2285 2225/2284/2284 +f 2226/2285/2285 2227/2286/2286 2225/2284/2284 +f 2227/2286/2286 2226/2285/2285 2220/2279/2279 +f 2226/2285/2285 2219/2278/2278 2220/2279/2279 +f 2219/2278/2278 2226/2285/2285 1970/2031/2031 +f 2227/2286/2286 2228/2287/2287 2225/2284/2284 +f 2228/2287/2287 2227/2286/2286 2222/2281/2281 +f 2222/2281/2281 2227/2286/2286 2220/2279/2279 +f 1848/1897/1897 1845/1899/1899 1852/1906/1906 +f 1852/1906/1906 1845/1899/1899 1836/1889/1889 +f 1852/1906/1906 1836/1889/1889 1837/1890/1890 +f 1836/1889/1889 1845/1899/1899 1840/1893/1893 +f 1868/1922/1922 1870/1924/1924 2230/2288/2288 +f 1868/1922/1922 2230/2288/2288 2229/2289/2289 +f 2230/2288/2288 1852/1906/1906 1837/1890/1890 +f 2230/2288/2288 1870/1924/1924 1852/1906/1906 +f 2230/2288/2288 2231/2290/2290 2229/2289/2289 +f 2231/2290/2290 2233/1883/1883 2232/2291/2291 +f 2233/1883/1883 2231/2290/2290 1834/1886/1886 +f 1837/1890/1890 1834/1886/1886 2230/2288/2288 +f 1834/1886/1886 2231/2290/2290 2230/2288/2288 +f 1866/1920/1920 2234/2292/2292 1867/1921/1921 +f 2235/2293/2293 2234/2292/2292 1868/1922/1922 +f 2235/2293/2293 1868/1922/1922 2229/2289/2289 +f 1868/1922/1922 2234/2292/2292 1866/1920/1920 +f 2236/2294/2294 2238/2295/2295 2235/2293/2293 +f 2236/2294/2294 2232/2291/2291 2237/2296/2296 +f 2238/2295/2295 2236/2294/2294 2237/2296/2296 +f 2232/2291/2291 2236/2294/2294 2231/2290/2290 +f 2229/2289/2289 2236/2294/2294 2235/2293/2293 +f 2231/2290/2290 2236/2294/2294 2229/2289/2289 +f 2186/2244/2244 2234/2292/2292 2235/2293/2293 +f 2186/2244/2244 2179/2238/2238 2234/2292/2292 +f 2234/2292/2292 2179/2238/2238 1867/1921/1921 +f 2186/2244/2244 2238/2295/2295 2185/2245/2245 +f 2239/2297/2297 2238/2295/2295 2237/2296/2296 +f 2185/2245/2245 2238/2295/2295 2239/2297/2297 +f 2238/2295/2295 2186/2244/2244 2235/2293/2293 +f 2185/2245/2245 2240/2298/2298 2184/2243/2243 +f 2240/2298/2298 2185/2245/2245 2239/2297/2297 +f 2183/2242/2242 2242/2299/2299 2243/2300/2300 +f 2243/2300/2300 2242/2299/2299 2244/2301/2301 +f 2242/2299/2299 2240/2298/2298 2241/2302/2302 +f 2244/2301/2301 2242/2299/2299 2241/2302/2302 +f 2184/2243/2243 2242/2299/2299 2183/2242/2242 +f 2240/2298/2298 2242/2299/2299 2184/2243/2243 +f 2245/2303/2303 2243/2300/2300 2246/2304/2304 +f 2190/2254/2254 2189/2248/2248 2245/2303/2303 +f 2189/2248/2248 2243/2300/2300 2245/2303/2303 +f 2246/2304/2304 2243/2300/2300 2244/2301/2301 +f 2243/2300/2300 2189/2248/2248 2183/2242/2242 +f 2195/2253/2253 2247/2305/2305 2250/2306/2306 +f 2247/2305/2305 2195/2253/2253 2190/2254/2254 +f 2247/2305/2305 2190/2254/2254 2245/2303/2303 +f 2194/2252/2252 2249/2307/2307 2197/2256/2256 +f 2249/2307/2307 2248/2308/2308 2197/2256/2256 +f 2248/2308/2308 2250/2306/2306 2253/2309/2309 +f 2248/2308/2308 2249/2307/2307 2250/2306/2306 +f 2249/2307/2307 2195/2253/2253 2250/2306/2306 +f 2195/2253/2253 2249/2307/2307 2194/2252/2252 +f 2199/2258/2258 2251/2310/2310 2254/2311/2311 +f 2197/2256/2256 2251/2310/2310 2199/2258/2258 +f 2252/2312/2312 2251/2310/2310 2253/2309/2309 +f 2254/2311/2311 2251/2310/2310 2252/2312/2312 +f 2253/2309/2309 2251/2310/2310 2248/2308/2308 +f 2248/2308/2308 2251/2310/2310 2197/2256/2256 +f 2202/2261/2261 2254/2311/2311 2255/2313/2313 +f 2255/2313/2313 2254/2311/2311 2256/2314/2314 +f 2256/2314/2314 2254/2311/2311 2252/2312/2312 +f 2254/2311/2311 2202/2261/2261 2199/2258/2258 +f 2203/2262/2262 2257/2315/2315 2258/2316/2316 +f 2204/2263/2263 2257/2315/2315 2203/2262/2262 +f 2258/2316/2316 2257/2315/2315 1805/1853/1853 +f 2255/2313/2313 2204/2263/2263 2202/2261/2261 +f 2255/2313/2313 2257/2315/2315 2204/2263/2263 +f 2206/2265/2265 2258/2316/2316 2259/1854/1854 +f 2258/2316/2316 1805/1853/1853 2259/1854/1854 +f 2258/2316/2316 2206/2265/2265 2203/2262/2262 +f 2208/2266/2266 2260/2317/2317 2207/2267/2267 +f 2260/2317/2317 2262/2318/2318 2261/2319/2319 +f 2262/2318/2318 2259/1854/1854 1787/1834/1834 +f 2262/2318/2318 2260/2317/2317 2259/1854/1854 +f 2259/1854/1854 2260/2317/2317 2208/2266/2266 +f 2259/1854/1854 2208/2266/2266 2206/2265/2265 +f 2211/2271/2271 2210/2270/2270 2263/2320/2320 +f 2261/2319/2319 2210/2270/2270 2260/2317/2317 +f 2263/2320/2320 2210/2270/2270 2261/2319/2319 +f 2260/2317/2317 2210/2270/2270 2207/2267/2267 +f 2213/2269/2269 2211/2271/2271 2214/2273/2273 +f 2211/2271/2271 2264/2321/2321 2214/2273/2273 +f 2264/2321/2321 2266/2322/2322 2265/2323/2323 +f 2266/2322/2322 2211/2271/2271 2263/2320/2320 +f 2266/2322/2322 2264/2321/2321 2211/2271/2271 +f 2217/2276/2276 2267/2324/2324 2218/2277/2277 +f 2267/2324/2324 2265/2323/2323 2268/2325/2325 +f 2265/2323/2323 2267/2324/2324 2264/2321/2321 +f 2214/2273/2273 2267/2324/2324 2217/2276/2276 +f 2264/2321/2321 2267/2324/2324 2214/2273/2273 +f 2223/2282/2282 2269/2326/2326 2270/2327/2327 +f 2223/2282/2282 2270/2327/2327 2222/2281/2281 +f 2270/2327/2327 2269/2326/2326 2309/2328/2328 +f 2268/2325/2325 2269/2326/2326 2267/2324/2324 +f 2267/2324/2324 2269/2326/2326 2218/2277/2277 +f 2269/2326/2326 2223/2282/2282 2218/2277/2277 +f 2271/2329/2329 2272/2330/2330 2228/2287/2287 +f 2272/2330/2330 2271/2329/2329 2273/2331/2331 +f 2273/2331/2331 2271/2329/2329 2270/2327/2327 +f 2273/2331/2331 2270/2327/2327 2309/2328/2328 +f 2222/2281/2281 2271/2329/2329 2228/2287/2287 +f 2270/2327/2327 2271/2329/2329 2222/2281/2281 +f 1985/2046/2046 2274/2332/2332 1986/2047/2047 +f 2280/2333/2333 2274/2332/2332 2275/2334/2334 +f 1986/2047/2047 2274/2332/2332 2280/2333/2333 +f 2225/2284/2284 2274/2332/2332 1985/2046/2046 +f 2228/2287/2287 2274/2332/2332 2225/2284/2284 +f 2274/2332/2332 2272/2330/2330 2275/2334/2334 +f 2276/2335/2335 2272/2330/2330 2277/2336/2336 +f 2275/2334/2334 2272/2330/2330 2276/2335/2335 +f 2277/2336/2336 2272/2330/2330 2273/2331/2331 +f 2272/2330/2330 2274/2332/2332 2228/2287/2287 +f 1984/2045/2045 2278/2337/2337 1983/2044/2044 +f 2278/2337/2337 2280/2333/2333 2279/2338/2338 +f 2280/2333/2333 2278/2337/2337 1986/2047/2047 +f 1986/2047/2047 2278/2337/2337 1984/2045/2045 +f 2280/2333/2333 2281/2339/2339 2279/2338/2338 +f 2280/2333/2333 2276/2335/2335 2281/2339/2339 +f 2281/2339/2339 2276/2335/2335 2282/2340/2340 +f 2275/2334/2334 2276/2335/2335 2280/2333/2333 +f 2284/2341/2341 2285/2342/2342 2013/2075/2075 +f 2284/2341/2341 2283/2042/2042 2285/2342/2342 +f 2283/2042/2042 2279/2338/2338 2285/2342/2342 +f 2278/2337/2337 2279/2338/2338 1982/2043/2043 +f 2278/2337/2337 1982/2043/2043 1983/2044/2044 +f 2279/2338/2338 2283/2042/2042 1982/2043/2043 +f 2285/2342/2342 2286/2343/2343 2013/2075/2075 +f 2013/2075/2075 2286/2343/2343 2010/2072/2072 +f 2287/2344/2344 2281/2339/2339 2282/2340/2340 +f 2286/2343/2343 2281/2339/2339 2287/2344/2344 +f 2281/2339/2339 2285/2342/2342 2279/2338/2338 +f 2281/2339/2339 2286/2343/2343 2285/2342/2342 +f 1977/2035/2035 2012/2074/2074 1992/2036/2036 +f 1979/2038/2038 2012/2074/2074 1977/2035/2035 +f 1992/2036/2036 2012/2074/2074 1994/2054/2054 +f 2012/2074/2074 2284/2341/2341 2013/2075/2075 +f 2012/2074/2074 1979/2038/2038 2284/2341/2341 +f 2284/2341/2341 1979/2038/2038 2283/2042/2042 +f 1825/1876/1876 2233/1883/1883 1829/1884/1884 +f 2232/2291/2291 2233/1883/1883 1825/1876/1876 +f 2232/2291/2291 2289/1885/1885 2237/2296/2296 +f 2289/1885/1885 2288/2345/2345 2237/2296/2296 +f 2288/2345/2345 2289/1885/1885 1823/1874/1874 +f 2289/1885/1885 2232/2291/2291 1825/1876/1876 +f 2237/2296/2296 2290/2346/2346 2239/2297/2297 +f 2239/2297/2297 2290/2346/2346 2294/2347/2347 +f 2290/2346/2346 2288/2345/2345 1823/1874/1874 +f 2290/2346/2346 2237/2296/2296 2288/2345/2345 +f 2240/2298/2298 2291/2348/2348 2241/2302/2302 +f 2291/2348/2348 2292/2349/2349 2241/2302/2302 +f 2292/2349/2349 2291/2348/2348 2293/1871/1871 +f 2291/2348/2348 2294/2347/2347 2293/1871/1871 +f 2294/2347/2347 2291/2348/2348 2239/2297/2297 +f 2239/2297/2297 2291/2348/2348 2240/2298/2298 +f 2295/2350/2350 2292/2349/2349 2296/2351/2351 +f 2244/2301/2301 2292/2349/2349 2295/2350/2350 +f 2296/2351/2351 2292/2349/2349 2293/1871/1871 +f 2292/2349/2349 2244/2301/2301 2241/2302/2302 +f 2246/2304/2304 2297/1857/1857 2245/2303/2303 +f 2297/1857/1857 2246/2304/2304 2295/2350/2350 +f 2295/2350/2350 2246/2304/2304 2244/2301/2301 +f 2247/2305/2305 2298/2352/2352 2250/2306/2306 +f 1812/1862/1862 2298/2352/2352 1809/1856/1856 +f 1809/1856/1856 2298/2352/2352 2297/1857/1857 +f 2245/2303/2303 2298/2352/2352 2247/2305/2305 +f 2297/1857/1857 2298/2352/2352 2245/2303/2303 +f 2250/2306/2306 2299/2353/2353 2253/2309/2309 +f 2250/2306/2306 1812/1862/1862 2299/2353/2353 +f 2298/2352/2352 1812/1862/1862 2250/2306/2306 +f 2252/2312/2312 2253/2309/2309 2301/2354/2354 +f 2253/2309/2309 2300/2355/2355 2301/2354/2354 +f 2253/2309/2309 2299/2353/2353 2300/2355/2355 +f 2300/2355/2355 2299/2353/2353 1815/1864/1864 +f 2300/2355/2355 1815/1864/1864 1799/1850/1850 +f 1815/1864/1864 2299/2353/2353 1812/1862/1862 +f 2256/2314/2314 1794/1843/1843 1793/1842/1842 +f 2256/2314/2314 2301/2354/2354 1801/1851/1851 +f 2256/2314/2314 1801/1851/1851 1794/1843/1843 +f 1800/1849/1849 2301/2354/2354 2300/2355/2355 +f 1800/1849/1849 2300/2355/2355 1799/1850/1850 +f 1801/1851/1851 2301/2354/2354 1800/1849/1849 +f 2301/2354/2354 2256/2314/2314 2252/2312/2312 +f 2257/2315/2315 1804/1852/1852 1805/1853/1853 +f 1802/1835/1835 1804/1852/1852 1793/1842/1842 +f 1793/1842/1842 1804/1852/1852 2256/2314/2314 +f 1804/1852/1852 2257/2315/2315 2255/2313/2313 +f 2256/2314/2314 1804/1852/1852 2255/2313/2313 +f 2262/2318/2318 2302/1831/1830 2261/2319/2319 +f 2261/2319/2319 2302/1831/1830 1779/1829/1826 +f 1787/1834/1834 2302/1831/1830 2262/2318/2318 +f 2266/2322/2322 2263/2320/2320 2303/2356/2356 +f 2263/2320/2320 2304/2357/2357 2303/2356/2356 +f 2304/2357/2357 2261/2319/2319 1779/1829/1826 +f 2304/2357/2357 2263/2320/2320 2261/2319/2319 +f 2266/2322/2322 2305/2358/2358 2265/2323/2323 +f 2305/2358/2358 2266/2322/2322 2303/2356/2356 +f 2265/2323/2323 2307/2359/2359 2268/2325/2325 +f 2310/2360/2360 2307/2359/2359 2308/2361/2361 +f 2268/2325/2325 2307/2359/2359 2310/2360/2360 +f 2306/2362/2362 2307/2359/2359 2305/2358/2358 +f 2305/2358/2358 2307/2359/2359 2265/2323/2323 +f 2269/2326/2326 2268/2325/2325 2309/2328/2328 +f 2309/2328/2328 2310/2360/2360 2312/2363/2363 +f 2309/2328/2328 2268/2325/2325 2310/2360/2360 +f 2273/2331/2331 2312/2363/2363 2311/2364/2364 +f 2312/2363/2363 2273/2331/2331 2309/2328/2328 +f 2277/2336/2336 2313/2365/2365 2276/2335/2335 +f 2277/2336/2336 2314/2366/2366 2313/2365/2365 +f 2311/2364/2364 2314/2366/2366 2277/2336/2336 +f 2311/2364/2364 2277/2336/2336 2273/2331/2331 +f 2282/2340/2340 2315/2367/2367 2006/2068/2068 +f 2006/2068/2068 2315/2367/2367 2003/2065/2065 +f 2315/2367/2367 2316/2368/2368 2003/2065/2065 +f 2316/2368/2368 2313/2365/2365 2314/2366/2366 +f 2315/2367/2367 2313/2365/2365 2316/2368/2368 +f 2313/2365/2365 2282/2340/2340 2276/2335/2335 +f 2313/2365/2365 2315/2367/2367 2282/2340/2340 +f 2286/2343/2343 2287/2344/2344 2009/2071/2071 +f 2286/2343/2343 2009/2071/2071 2010/2072/2072 +f 2287/2344/2344 2006/2068/2068 2009/2071/2071 +f 2282/2340/2340 2006/2068/2068 2287/2344/2344 +f 2290/2346/2346 1826/1878/1878 2294/2347/2347 +f 1826/1878/1878 1821/1870/1870 2294/2347/2347 +f 1821/1870/1870 1826/1878/1878 1828/1879/1879 +f 1826/1878/1878 2290/2346/2346 1823/1874/1874 +f 2294/2347/2347 1821/1870/1870 2293/1871/1871 +f 2293/1871/1871 1821/1870/1870 1819/1869/1869 +f 2317/1872/1872 2318/2369/2369 2295/2350/2350 +f 2296/2351/2351 2317/1872/1872 2295/2350/2350 +f 2318/2369/2369 2317/1872/1872 1806/1860/1860 +f 1816/1865/1865 2296/2351/2351 2293/1871/1871 +f 2317/1872/1872 2296/2351/2351 1816/1865/1865 +f 1808/1855/1855 2318/2369/2369 1807/1858/1858 +f 2295/2350/2350 2318/2369/2369 1808/1855/1855 +f 2295/2350/2350 1808/1855/1855 2297/1857/1857 +f 1807/1858/1858 2318/2369/2369 1806/1860/1860 +f 2304/2357/2357 2319/2370/2370 2303/2356/2356 +f 2304/2357/2357 1780/1830/1827 2319/2370/2370 +f 2319/2370/2370 1780/1830/1827 1766/1821/1818 +f 1780/1830/1827 2304/2357/2357 1779/1829/1826 +f 2305/2358/2358 2321/2371/2371 2306/2362/2362 +f 2322/2372/2372 2321/2371/2371 2319/2370/2370 +f 2322/2372/2372 2319/2370/2370 2320/2373/2373 +f 2303/2356/2356 2321/2371/2371 2305/2358/2358 +f 2319/2370/2370 2321/2371/2371 2303/2356/2356 +f 2323/2374/2374 2308/2361/2361 2325/2375/2375 +f 2308/2361/2361 2324/2376/2376 2325/2375/2375 +f 2324/2376/2376 2321/2371/2371 2322/2372/2372 +f 2306/2362/2362 2308/2361/2361 2307/2359/2359 +f 2321/2371/2371 2324/2376/2376 2306/2362/2362 +f 2324/2376/2376 2308/2361/2361 2306/2362/2362 +f 2310/2360/2360 2326/2377/2377 2312/2363/2363 +f 2326/2377/2377 2327/2378/2378 2312/2363/2363 +f 2327/2378/2378 2326/2377/2377 2328/2379/2379 +f 2326/2377/2377 2323/2374/2374 2325/2375/2375 +f 2323/2374/2374 2310/2360/2360 2308/2361/2361 +f 2323/2374/2374 2326/2377/2377 2310/2360/2360 +f 2312/2363/2363 2329/2380/2380 2311/2364/2364 +f 2329/2380/2380 2327/2378/2378 2328/2379/2379 +f 2327/2378/2378 2329/2380/2380 2312/2363/2363 +f 2314/2366/2366 2330/2381/2381 2316/2368/2368 +f 2330/2381/2381 2331/2063/2063 2316/2368/2368 +f 2331/2063/2063 2016/2076/2076 2001/2062/2062 +f 2330/2381/2381 2016/2076/2076 2331/2063/2063 +f 2016/2076/2076 2330/2381/2381 2329/2380/2380 +f 2330/2381/2381 2314/2366/2366 2311/2364/2364 +f 2329/2380/2380 2330/2381/2381 2311/2364/2364 +f 2003/2065/2065 2316/2368/2368 2331/2063/2063 +f 2319/2370/2370 2332/2382/2382 2320/2373/2373 +f 2332/2382/2382 1766/1821/1818 1770/1825/1822 +f 1766/1821/1818 2332/2382/2382 2319/2370/2370 +f 2336/2383/2383 2333/2384/2384 2334/2385/2385 +f 2333/2384/2384 2335/2386/2386 2334/2385/2385 +f 2322/2372/2372 2333/2384/2384 2336/2383/2383 +f 2335/2386/2386 2333/2384/2384 2332/2382/2382 +f 2332/2382/2382 2333/2384/2384 2320/2373/2373 +f 2333/2384/2384 2322/2372/2372 2320/2373/2373 +f 2324/2376/2376 2336/2383/2383 2325/2375/2375 +f 2325/2375/2375 2336/2383/2383 2337/2387/2387 +f 2322/2372/2372 2336/2383/2383 2324/2376/2376 +f 2326/2377/2377 2338/2388/2388 2328/2379/2379 +f 2338/2388/2388 2325/2375/2375 2337/2387/2387 +f 2339/2389/2389 2338/2388/2388 2337/2387/2387 +f 2325/2375/2375 2338/2388/2388 2326/2377/2377 +f 2329/2380/2380 2340/2390/2390 2016/2076/2076 +f 2329/2380/2380 2328/2379/2379 2340/2390/2390 +f 2340/2390/2390 2024/2086/2086 2014/2079/2079 +f 2024/2086/2086 2328/2379/2379 2338/2388/2388 +f 2340/2390/2390 2328/2379/2379 2024/2086/2086 +f 2014/2079/2079 2016/2076/2076 2340/2390/2390 +f 2015/2077/2077 2016/2076/2076 2014/2079/2079 +f 2332/2382/2382 2341/2391/2391 2335/2386/2386 +f 2341/2391/2391 2342/2392/2392 2347/2393/2393 +f 2342/2392/2392 2341/2391/2391 2343/2394/2394 +f 1770/1825/1822 2341/2391/2391 2332/2382/2382 +f 2343/2394/2394 2341/2391/2391 1770/1825/1822 +f 2344/2395/2395 2343/2394/2394 1770/1825/1822 +f 2343/2394/2394 2344/2395/2395 2345/2396/2396 +f 2344/2395/2395 1770/1825/1822 1771/1826/1823 +f 2334/2385/2385 2347/2393/2393 2346/2397/2397 +f 2347/2393/2393 2335/2386/2386 2341/2391/2391 +f 2334/2385/2385 2335/2386/2386 2347/2393/2393 +f 2348/2398/2398 2339/2389/2389 2337/2387/2387 +f 2336/2383/2383 2348/2398/2398 2337/2387/2387 +f 2339/2389/2389 2348/2398/2398 2349/2399/2399 +f 2348/2398/2398 2334/2385/2385 2346/2397/2397 +f 2349/2399/2399 2348/2398/2398 2346/2397/2397 +f 2334/2385/2385 2348/2398/2398 2336/2383/2383 +f 2338/2388/2388 2339/2389/2389 2024/2086/2086 +f 2339/2389/2389 2023/2085/2085 2024/2086/2086 +f 2023/2085/2085 2350/2400/2400 2019/2081/2081 +f 2023/2085/2085 2339/2389/2389 2350/2400/2400 +f 2350/2400/2400 2339/2389/2389 2349/2399/2399 +f 1771/1826/1823 2345/2396/2396 2344/2395/2395 +f 1773/1828/1825 2345/2396/2396 1771/1826/1823 +f 2345/2396/2396 1773/1828/1825 2351/2401/2401 +f 2351/2401/2401 1773/1828/1825 2353/2402/2402 +f 2353/2402/2402 1773/1828/1825 1746/1802/1796 +f 2351/2401/2401 2353/2402/2402 2352/2403/2403 +f 2352/2403/2403 2353/2402/2402 2354/2404/2404 +f 2354/2404/2404 1749/1805/1799 1750/1806/1800 +f 1748/1804/1798 2353/2402/2402 1746/1802/1796 +f 1748/1804/1798 2354/2404/2404 2353/2402/2402 +f 1749/1805/1799 2354/2404/2404 1748/1804/1798 +f 2345/2396/2396 2355/2405/2405 2361/2406/2406 +f 2351/2401/2401 2355/2405/2405 2345/2396/2396 +f 2361/2406/2406 2355/2405/2405 2356/2407/2407 +f 2357/2408/2408 2355/2405/2405 2358/2409/2409 +f 2356/2407/2407 2355/2405/2405 2357/2408/2408 +f 2358/2409/2409 2355/2405/2405 2351/2401/2401 +f 2352/2403/2403 2358/2409/2409 2351/2401/2401 +f 2358/2409/2409 2359/2410/2410 2357/2408/2408 +f 2358/2409/2409 2352/2403/2403 2359/2410/2410 +f 2360/2411/2411 2359/2410/2410 2352/2403/2403 +f 2343/2394/2394 2361/2406/2406 2342/2392/2392 +f 2361/2406/2406 2362/2412/2412 2342/2392/2392 +f 2362/2412/2412 2361/2406/2406 2363/2413/2413 +f 2361/2406/2406 2364/2414/2414 2363/2413/2413 +f 2364/2414/2414 2361/2406/2406 2356/2407/2407 +f 2361/2406/2406 2343/2394/2394 2345/2396/2396 +f 2342/2392/2392 2365/2415/2415 2347/2393/2393 +f 2342/2392/2392 2362/2412/2412 2365/2415/2415 +f 2365/2415/2415 2362/2412/2412 2366/2416/2416 +f 2366/2416/2416 2362/2412/2412 2363/2413/2413 +f 2346/2397/2397 2367/2417/2417 2368/2418/2418 +f 2347/2393/2393 2367/2417/2417 2346/2397/2397 +f 2368/2418/2418 2367/2417/2417 2369/2419/2419 +f 2367/2417/2417 2370/2420/2420 2369/2419/2419 +f 2370/2420/2420 2367/2417/2417 2365/2415/2415 +f 2365/2415/2415 2367/2417/2417 2347/2393/2393 +f 2349/2399/2399 2371/2421/2421 2372/2422/2422 +f 2368/2418/2418 2371/2421/2421 2349/2399/2399 +f 2368/2418/2418 2349/2399/2399 2346/2397/2397 +f 2019/2081/2081 2350/2400/2400 2047/2091/2091 +f 2350/2400/2400 2372/2422/2422 2047/2091/2091 +f 2372/2422/2422 2350/2400/2400 2349/2399/2399 +f 2354/2404/2404 2360/2411/2411 2352/2403/2403 +f 2360/2411/2411 2373/2423/2423 2359/2410/2410 +f 2360/2411/2411 1752/1808/1802 2373/2423/2423 +f 1752/1808/1802 2360/2411/2411 2354/2404/2404 +f 1752/1808/1802 2354/2404/2404 1750/1806/1800 +f 1752/1808/1802 2374/2424/2424 2373/2423/2423 +f 1754/1810/1804 1756/1812/1806 2374/2424/2424 +f 1756/1812/1806 1754/1810/1804 1753/1809/1803 +f 1751/1807/1801 2374/2424/2424 1752/1808/1802 +f 1751/1807/1801 1754/1810/1804 2374/2424/2424 +f 2375/2425/2425 2376/2426/2426 2359/2410/2410 +f 2373/2423/2423 2375/2425/2425 2359/2410/2410 +f 2378/2427/2427 2375/2425/2425 2379/2428/2428 +f 2376/2426/2426 2375/2425/2425 2378/2427/2427 +f 2379/2428/2428 2375/2425/2425 2373/2423/2423 +f 2374/2424/2424 2380/2429/2429 2379/2428/2428 +f 2374/2424/2424 2379/2428/2428 2373/2423/2423 +f 2379/2428/2428 2380/2429/2429 2381/2430/2430 +f 2379/2428/2428 2381/2430/2430 2378/2427/2427 +f 2383/2431/2431 2382/2432/2432 2384/2433/2433 +f 2357/2408/2408 2382/2432/2432 2383/2431/2431 +f 2384/2433/2433 2382/2432/2432 2376/2426/2426 +f 2384/2433/2433 2376/2426/2426 2377/2434/2434 +f 2376/2426/2426 2382/2432/2432 2359/2410/2410 +f 2382/2432/2432 2357/2408/2408 2359/2410/2410 +f 2356/2407/2407 2383/2431/2431 2385/2435/2435 +f 2385/2435/2435 2383/2431/2431 2384/2433/2433 +f 2383/2431/2431 2356/2407/2407 2357/2408/2408 +f 2363/2413/2413 2387/2436/2436 2386/2437/2437 +f 2387/2436/2436 2356/2407/2407 2385/2435/2435 +f 2356/2407/2407 2363/2413/2413 2364/2414/2414 +f 2387/2436/2436 2363/2413/2413 2356/2407/2407 +f 2365/2415/2415 2366/2416/2416 2370/2420/2420 +f 2366/2416/2416 2388/2438/2438 2370/2420/2420 +f 2388/2438/2438 2390/2439/2439 2389/2440/2440 +f 2366/2416/2416 2390/2439/2439 2388/2438/2438 +f 2390/2439/2439 2366/2416/2416 2386/2437/2437 +f 2386/2437/2437 2366/2416/2416 2363/2413/2413 +f 2369/2419/2419 2388/2438/2438 2391/2441/2441 +f 2391/2441/2441 2388/2438/2438 2389/2440/2440 +f 2388/2438/2438 2369/2419/2419 2370/2420/2420 +f 2371/2421/2421 2392/2442/2442 2393/2443/2443 +f 2371/2421/2421 2393/2443/2443 2372/2422/2422 +f 2394/2444/2444 2392/2442/2442 2395/2445/2445 +f 2393/2443/2443 2392/2442/2442 2394/2444/2444 +f 2395/2445/2445 2392/2442/2442 2391/2441/2441 +f 2392/2442/2442 2369/2419/2419 2391/2441/2441 +f 2369/2419/2419 2392/2442/2442 2371/2421/2421 +f 2369/2419/2419 2371/2421/2421 2368/2418/2418 +f 2047/2091/2091 2396/2446/2446 2042/2104/2104 +f 2047/2091/2091 2393/2443/2443 2396/2446/2446 +f 2396/2446/2446 2393/2443/2443 2394/2444/2444 +f 2393/2443/2443 2047/2091/2091 2372/2422/2422 +f 1756/1812/1806 2380/2429/2429 2374/2424/2424 +f 1756/1812/1806 2397/2447/2447 2380/2429/2429 +f 2381/2430/2430 2397/2447/2447 2398/1816/1810 +f 2380/2429/2429 2397/2447/2447 2381/2430/2430 +f 2397/2447/2447 1755/1811/1805 2398/1816/1810 +f 1755/1811/1805 2397/2447/2447 1756/1812/1806 +f 2398/1816/1810 2399/2448/2448 2381/2430/2430 +f 2399/2448/2448 2401/2449/2449 2400/2450/2450 +f 2402/2451/2451 2401/2449/2449 2398/1816/1810 +f 2401/2449/2449 2399/2448/2448 2398/1816/1810 +f 1759/1815/1809 2402/2451/2451 2398/1816/1810 +f 1759/1815/1809 2403/2452/2452 2402/2451/2451 +f 2402/2451/2451 2403/2452/2452 2404/2453/2453 +f 2404/2453/2453 2403/2452/2452 2405/2454/2454 +f 2403/2452/2452 1759/1815/1809 1760/1817/1811 +f 2381/2430/2430 2407/2455/2455 2378/2427/2427 +f 2407/2455/2455 2406/2456/2456 2378/2427/2427 +f 2407/2455/2455 2399/2448/2448 2400/2450/2450 +f 2406/2456/2456 2407/2455/2455 2400/2450/2450 +f 2399/2448/2448 2407/2455/2455 2381/2430/2430 +f 2376/2426/2426 2409/2457/2457 2377/2434/2434 +f 2409/2457/2457 2410/2458/2458 2377/2434/2434 +f 2410/2458/2458 2409/2457/2457 2411/2459/2459 +f 2408/2460/2460 2409/2457/2457 2406/2456/2456 +f 2411/2459/2459 2409/2457/2457 2408/2460/2460 +f 2409/2457/2457 2376/2426/2426 2378/2427/2427 +f 2406/2456/2456 2409/2457/2457 2378/2427/2427 +f 2384/2433/2433 2412/2461/2461 2415/2462/2462 +f 2384/2433/2433 2410/2458/2458 2412/2461/2461 +f 2410/2458/2458 2384/2433/2433 2377/2434/2434 +f 2385/2435/2435 2413/2463/2463 2416/2464/2464 +f 2416/2464/2464 2413/2463/2463 2414/2465/2465 +f 2413/2463/2463 2415/2462/2462 2414/2465/2465 +f 2413/2463/2463 2385/2435/2435 2384/2433/2433 +f 2415/2462/2462 2413/2463/2463 2384/2433/2433 +f 2386/2437/2437 2416/2464/2464 2390/2439/2439 +f 2416/2464/2464 2417/2466/2466 2390/2439/2439 +f 2387/2436/2436 2416/2464/2464 2386/2437/2437 +f 2417/2466/2466 2416/2464/2464 2418/2467/2467 +f 2418/2467/2467 2416/2464/2464 2414/2465/2465 +f 2416/2464/2464 2387/2436/2436 2385/2435/2435 +f 2390/2439/2439 2419/2468/2468 2389/2440/2440 +f 2417/2466/2466 2419/2468/2468 2390/2439/2439 +f 2391/2441/2441 2421/2469/2469 2395/2445/2445 +f 2391/2441/2441 2389/2440/2440 2421/2469/2469 +f 2420/2470/2470 2389/2440/2440 2419/2468/2468 +f 2421/2469/2469 2389/2440/2440 2420/2470/2470 +f 2422/2471/2471 2423/2472/2472 2394/2444/2444 +f 2395/2445/2445 2422/2471/2471 2394/2444/2444 +f 2423/2472/2472 2422/2471/2471 2424/2473/2473 +f 2424/2473/2473 2422/2471/2471 2421/2469/2469 +f 2421/2469/2469 2422/2471/2471 2395/2445/2445 +f 2396/2446/2446 2043/2105/2105 2042/2104/2104 +f 2043/2105/2105 2396/2446/2446 2046/2108/2108 +f 2046/2108/2108 2396/2446/2446 2423/2472/2472 +f 2423/2472/2472 2396/2446/2446 2394/2444/2444 +f 1760/1817/1811 2425/1794/1788 2403/2452/2452 +f 1761/1789/1783 2425/1794/1788 1760/1817/1811 +f 2403/2452/2452 2425/1794/1788 2405/2454/2454 +f 2425/1794/1788 1761/1789/1783 1738/1791/1785 +f 2405/2454/2454 2426/2474/2474 2431/2475/2475 +f 2426/2474/2474 2427/2476/2476 2428/1787/1781 +f 2427/2476/2476 2425/1794/1788 1732/1786/1780 +f 2427/2476/2476 2405/2454/2454 2425/1794/1788 +f 2426/2474/2474 2405/2454/2454 2427/2476/2476 +f 1732/1786/1780 2428/1787/1781 2427/2476/2476 +f 1733/1785/1779 2428/1787/1781 1732/1786/1780 +f 2404/2453/2453 2429/2477/2477 2402/2451/2451 +f 2429/2477/2477 2430/2478/2478 2433/2479/2479 +f 2429/2477/2477 2431/2475/2475 2430/2478/2478 +f 2431/2475/2475 2404/2453/2453 2405/2454/2454 +f 2429/2477/2477 2404/2453/2453 2431/2475/2475 +f 2401/2449/2449 2433/2479/2479 2432/2480/2480 +f 2401/2449/2449 2432/2480/2480 2400/2450/2450 +f 2429/2477/2477 2433/2479/2479 2401/2449/2449 +f 2429/2477/2477 2401/2449/2449 2402/2451/2451 +f 2406/2456/2456 2434/2481/2481 2408/2460/2460 +f 2406/2456/2456 2435/2482/2482 2434/2481/2481 +f 2432/2480/2480 2435/2482/2482 2400/2450/2450 +f 2435/2482/2482 2406/2456/2456 2400/2450/2450 +f 2411/2459/2459 2436/2483/2483 2437/2484/2484 +f 2408/2460/2460 2436/2483/2483 2411/2459/2459 +f 2437/2484/2484 2436/2483/2483 2438/2485/2485 +f 2437/2484/2484 2438/2485/2485 2094/2161/2161 +f 2438/2485/2485 2436/2483/2483 2434/2481/2481 +f 2436/2483/2483 2408/2460/2460 2434/2481/2481 +f 2412/2461/2461 2095/2160/2160 2439/2486/2486 +f 2437/2484/2484 2095/2160/2160 2412/2461/2461 +f 2095/2160/2160 2437/2484/2484 2094/2161/2161 +f 2411/2459/2459 2437/2484/2484 2410/2458/2458 +f 2437/2484/2484 2412/2461/2461 2410/2458/2458 +f 2414/2465/2465 2439/2486/2486 2440/2487/2487 +f 2415/2462/2462 2439/2486/2486 2414/2465/2465 +f 2440/2487/2487 2439/2486/2486 2088/2152/2152 +f 2088/2152/2152 2439/2486/2486 2095/2160/2160 +f 2088/2152/2152 2095/2160/2160 2091/2153/2153 +f 2412/2461/2461 2439/2486/2486 2415/2462/2462 +f 2418/2467/2467 2441/2488/2488 2442/2489/2489 +f 2441/2488/2488 2440/2487/2487 2443/2490/2490 +f 2442/2489/2489 2441/2488/2488 2443/2490/2490 +f 2440/2487/2487 2441/2488/2488 2418/2467/2467 +f 2440/2487/2487 2418/2467/2467 2414/2465/2465 +f 2419/2468/2468 2442/2489/2489 2420/2470/2470 +f 2442/2489/2489 2444/2491/2491 2420/2470/2470 +f 2444/2491/2491 2445/2492/2492 2446/2493/2493 +f 2445/2492/2492 2444/2491/2491 2443/2490/2490 +f 2444/2491/2491 2442/2489/2489 2443/2490/2490 +f 2418/2467/2467 2442/2489/2489 2417/2466/2466 +f 2442/2489/2489 2419/2468/2468 2417/2466/2466 +f 2420/2470/2470 2447/2494/2494 2421/2469/2469 +f 2447/2494/2494 2420/2470/2470 2448/2495/2495 +f 2446/2493/2493 2420/2470/2470 2444/2491/2491 +f 2448/2495/2495 2420/2470/2470 2446/2493/2493 +f 2424/2473/2473 2449/2496/2496 2423/2472/2472 +f 2449/2496/2496 2424/2473/2473 2450/2497/2497 +f 2450/2497/2497 2424/2473/2473 2447/2494/2494 +f 2447/2494/2494 2424/2473/2473 2421/2469/2469 +f 2046/2108/2108 2451/2498/2498 2452/2499/2499 +f 2452/2499/2499 2451/2498/2498 2453/2500/2500 +f 2450/2497/2497 2451/2498/2498 2449/2496/2496 +f 2453/2500/2500 2451/2498/2498 2450/2497/2497 +f 2449/2496/2496 2451/2498/2498 2046/2108/2108 +f 2449/2496/2496 2046/2108/2108 2423/2472/2472 +f 2037/2099/2099 2045/2107/2107 2452/2499/2499 +f 2452/2499/2499 2044/2106/2106 2046/2108/2108 +f 2452/2499/2499 2045/2107/2107 2044/2106/2106 +f 2454/2501/2501 2458/2502/2502 2431/2475/2475 +f 2426/2474/2474 2454/2501/2501 2431/2475/2475 +f 2458/2502/2502 2454/2501/2501 2455/2503/2503 +f 2454/2501/2501 2456/2504/2504 2455/2503/2503 +f 2454/2501/2501 2426/2474/2474 2428/1787/1781 +f 2428/1787/1781 2457/2505/2505 2454/2501/2501 +f 2454/2501/2501 2457/2505/2505 2456/2504/2504 +f 1723/1775/1769 2457/2505/2505 2428/1787/1781 +f 2431/2475/2475 2458/2502/2502 2430/2478/2478 +f 2430/2478/2478 2458/2502/2502 2459/2506/2506 +f 2460/2507/2507 2461/2508/2508 2432/2480/2480 +f 2433/2479/2479 2460/2507/2507 2432/2480/2480 +f 2460/2507/2507 2459/2506/2506 2461/2508/2508 +f 2459/2506/2506 2460/2507/2507 2430/2478/2478 +f 2430/2478/2478 2460/2507/2507 2433/2479/2479 +f 2462/2509/2509 2463/1746/1740 2434/2481/2481 +f 2435/2482/2482 2462/2509/2509 2434/2481/2481 +f 2463/1746/1740 2462/2509/2509 1694/1745/1739 +f 1694/1745/1739 2462/2509/2509 2461/2508/2508 +f 2432/2480/2480 2462/2509/2509 2435/2482/2482 +f 2461/2508/2508 2462/2509/2509 2432/2480/2480 +f 2438/2485/2485 1696/1748/1742 2094/2161/2161 +f 1696/1748/1742 1695/1747/1741 1699/1749/1743 +f 1696/1748/1742 2438/2485/2485 1695/1747/1741 +f 1695/1747/1741 2438/2485/2485 2463/1746/1740 +f 2463/1746/1740 2438/2485/2485 2434/2481/2481 +f 2440/2487/2487 2085/2149/2149 2464/2510/2510 +f 2440/2487/2487 2464/2510/2510 2443/2490/2490 +f 2464/2510/2510 2085/2149/2149 2087/2151/2151 +f 2464/2510/2510 2087/2151/2151 2089/2154/2154 +f 2085/2149/2149 2440/2487/2487 2088/2152/2152 +f 2446/2493/2493 2445/2492/2492 2465/2511/2511 +f 2465/2511/2511 2445/2492/2492 2464/2510/2510 +f 2465/2511/2511 2464/2510/2510 2089/2154/2154 +f 2464/2510/2510 2445/2492/2492 2443/2490/2490 +f 2448/2495/2495 2467/2512/2512 2447/2494/2494 +f 2467/2512/2512 2448/2495/2495 2466/2513/2513 +f 2466/2513/2513 2448/2495/2495 2446/2493/2493 +f 2466/2513/2513 2446/2493/2493 2465/2511/2511 +f 2450/2497/2497 2467/2512/2512 2470/2514/2514 +f 2470/2514/2514 2467/2512/2512 2468/2515/2515 +f 2467/2512/2512 2469/2516/2516 2468/2515/2515 +f 2469/2516/2516 2467/2512/2512 2466/2513/2513 +f 2467/2512/2512 2450/2497/2497 2447/2494/2494 +f 2453/2500/2500 2470/2514/2514 2471/2517/2517 +f 2470/2514/2514 2453/2500/2500 2450/2497/2497 +f 2037/2099/2099 2472/2518/2518 2035/2097/2097 +f 2035/2097/2097 2472/2518/2518 2473/2519/2519 +f 2035/2097/2097 2473/2519/2519 2059/2121/2121 +f 2471/2517/2517 2472/2518/2518 2453/2500/2500 +f 2473/2519/2519 2472/2518/2518 2471/2517/2517 +f 2453/2500/2500 2472/2518/2518 2452/2499/2499 +f 2472/2518/2518 2037/2099/2099 2452/2499/2499 +f 2457/2505/2505 2474/2520/2520 2456/2504/2504 +f 2474/2520/2520 2476/1778/1772 2475/2521/2521 +f 2476/1778/1772 2474/2520/2520 1722/1774/1768 +f 1722/1774/1768 2457/2505/2505 1723/1775/1769 +f 1722/1774/1768 2474/2520/2520 2457/2505/2505 +f 2456/2504/2504 2477/2522/2522 2455/2503/2503 +f 2475/2521/2521 2477/2522/2522 2474/2520/2520 +f 2474/2520/2520 2477/2522/2522 2456/2504/2504 +f 2458/2502/2502 2479/2523/2523 2459/2506/2506 +f 2479/2523/2523 2478/2524/2524 2480/2525/2525 +f 2478/2524/2524 2455/2503/2503 2477/2522/2522 +f 2478/2524/2524 2479/2523/2523 2455/2503/2503 +f 2455/2503/2503 2479/2523/2523 2458/2502/2502 +f 2461/2508/2508 2481/2526/2526 1694/1745/1739 +f 2459/2506/2506 2481/2526/2526 2461/2508/2508 +f 1692/1743/1737 2481/2526/2526 2482/2527/2527 +f 1694/1745/1739 2481/2526/2526 1692/1743/1737 +f 2482/2527/2527 2481/2526/2526 2480/2525/2525 +f 2481/2526/2526 2479/2523/2523 2480/2525/2525 +f 2479/2523/2523 2481/2526/2526 2459/2506/2506 +f 2465/2511/2511 2483/2155/2155 2484/2528/2528 +f 2484/2528/2528 2483/2155/2155 2485/2529/2529 +f 2483/2155/2155 2486/2530/2530 2485/2529/2529 +f 2486/2530/2530 2483/2155/2155 2076/2140/2140 +f 2089/2154/2154 2483/2155/2155 2465/2511/2511 +f 2484/2528/2528 2487/2531/2531 2466/2513/2513 +f 2487/2531/2531 2484/2528/2528 2488/2532/2532 +f 2488/2532/2532 2484/2528/2528 2485/2529/2529 +f 2484/2528/2528 2466/2513/2513 2465/2511/2511 +f 2469/2516/2516 2489/2533/2533 2468/2515/2515 +f 2468/2515/2515 2489/2533/2533 2490/2534/2534 +f 2487/2531/2531 2489/2533/2533 2469/2516/2516 +f 2487/2531/2531 2469/2516/2516 2466/2513/2513 +f 2470/2514/2514 2491/2535/2535 2471/2517/2517 +f 2491/2535/2535 2490/2534/2534 2492/2536/2536 +f 2491/2535/2535 2468/2515/2515 2490/2534/2534 +f 2468/2515/2515 2491/2535/2535 2470/2514/2514 +f 2058/2120/2120 2059/2121/2121 2057/2119/2119 +f 2059/2121/2121 2473/2519/2519 2057/2119/2119 +f 2492/2536/2536 2473/2519/2519 2491/2535/2535 +f 2057/2119/2119 2473/2519/2519 2492/2536/2536 +f 2491/2535/2535 2473/2519/2519 2471/2517/2517 +f 2476/1778/1772 2493/2537/2537 2475/2521/2521 +f 2476/1778/1772 1728/1782/1776 2493/2537/2537 +f 2504/1770/1764 2493/2537/2537 1728/1782/1776 +f 1725/1777/1771 1728/1782/1776 2476/1778/1772 +f 2477/2522/2522 1703/1755/1749 2478/2524/2524 +f 2477/2522/2522 2495/2538/2538 1703/1755/1749 +f 2494/1754/1748 2495/2538/2538 2493/2537/2537 +f 2493/2537/2537 2495/2538/2538 2475/2521/2521 +f 2495/2538/2538 2477/2522/2522 2475/2521/2521 +f 2480/2525/2525 2496/2539/2539 1687/1738/1732 +f 2478/2524/2524 2496/2539/2539 2480/2525/2525 +f 1687/1738/1732 2496/2539/2539 1685/1736/1730 +f 2496/2539/2539 1703/1755/1749 1685/1736/1730 +f 1703/1755/1749 2496/2539/2539 2478/2524/2524 +f 2482/2527/2527 1691/1744/1738 1692/1743/1737 +f 1691/1744/1738 2482/2527/2527 1687/1738/1732 +f 1691/1744/1738 1687/1738/1732 1686/1737/1731 +f 1687/1738/1732 2482/2527/2527 2480/2525/2525 +f 2485/2529/2529 2497/2144/2144 2488/2532/2532 +f 2486/2530/2530 2497/2144/2144 2485/2529/2529 +f 2488/2532/2532 2497/2144/2144 2081/2146/2146 +f 2077/2138/2138 2486/2530/2530 2076/2140/2140 +f 2497/2144/2144 2486/2530/2530 2077/2138/2138 +f 2487/2531/2531 2488/2532/2532 2489/2533/2533 +f 2488/2532/2532 2498/2540/2540 2489/2533/2533 +f 2498/2540/2540 2488/2532/2532 2081/2146/2146 +f 2498/2540/2540 2499/2148/2148 2490/2534/2534 +f 2489/2533/2533 2498/2540/2540 2490/2534/2534 +f 2082/2145/2145 2498/2540/2540 2081/2146/2146 +f 2499/2148/2148 2498/2540/2540 2082/2145/2145 +f 2492/2536/2536 2500/2541/2541 2502/2542/2542 +f 2490/2534/2534 2500/2541/2541 2492/2536/2536 +f 2502/2542/2542 2500/2541/2541 2501/2543/2543 +f 2499/2148/2148 2500/2541/2541 2490/2534/2534 +f 2057/2119/2119 2502/2542/2542 2056/2115/2115 +f 2056/2115/2115 2503/2544/2544 2054/2116/2116 +f 2056/2115/2115 2502/2542/2542 2503/2544/2544 +f 2503/2544/2544 2502/2542/2542 2501/2543/2543 +f 2502/2542/2542 2057/2119/2119 2492/2536/2536 +f 2493/2537/2537 2504/1770/1764 2494/1754/1748 +f 2494/1754/1748 2504/1770/1764 1701/1752/1746 +f 2504/1770/1764 1707/1758/1752 1701/1752/1746 +f 2495/2538/2538 2494/1754/1748 1702/1753/1747 +f 2495/2538/2538 1702/1753/1747 1703/1755/1749 +f 2501/2543/2543 2067/2129/2129 2069/2131/2131 +f 2501/2543/2543 2084/2147/2147 2067/2129/2129 +f 2084/2147/2147 2501/2543/2543 2500/2541/2541 +f 2084/2147/2147 2500/2541/2541 2499/2148/2148 +f 2503/2544/2544 2049/2110/2110 2054/2116/2116 +f 2049/2110/2110 2503/2544/2544 2053/2114/2114 +f 2069/2131/2131 2503/2544/2544 2501/2543/2543 +f 2053/2114/2114 2503/2544/2544 2069/2131/2131 +f 2506/2545/2545 2507/2546/2546 2800/2547/2547 +f 2508/2548/2548 2512/2549/2549 2506/2545/2545 +f 2508/2548/2548 2506/2545/2545 2505/2550/2550 +f 2512/2549/2549 2507/2546/2546 2506/2545/2545 +f 2507/2546/2546 2510/2551/2551 2511/2552/2552 +f 2507/2546/2546 2511/2552/2552 2509/2553/2553 +f 2510/2551/2551 2507/2546/2546 2512/2549/2549 +f 2804/2554/2554 2514/2555/2555 2515/2556/2556 +f 2511/2552/2552 2520/2557/2557 2804/2554/2554 +f 2511/2552/2552 2804/2554/2554 2513/2558/2558 +f 2520/2557/2557 2514/2555/2555 2804/2554/2554 +f 2516/2559/2559 2518/2560/2560 2519/2561/2561 +f 2516/2559/2559 2519/2561/2561 2517/2562/2562 +f 2515/2556/2556 2514/2555/2555 2516/2559/2559 +f 2514/2555/2555 2518/2560/2560 2516/2559/2559 +f 2519/2561/2561 2526/2563/2563 2808/2564/2564 +f 2519/2561/2561 2808/2564/2564 2517/2562/2562 +f 2526/2563/2563 2522/2565/2565 2521/2566/2566 +f 2526/2563/2563 2521/2566/2566 2808/2564/2564 +f 2809/2567/2567 2524/2568/2568 2525/2569/2569 +f 2809/2567/2567 2525/2569/2569 2523/2570/2570 +f 2521/2566/2566 2522/2565/2565 2524/2568/2568 +f 2521/2566/2566 2524/2568/2568 2809/2567/2567 +f 2525/2569/2569 2533/2571/2571 2527/2572/2572 +f 2525/2569/2569 2527/2572/2572 2523/2570/2570 +f 2533/2571/2571 2528/2573/2573 2527/2572/2572 +f 2813/2574/2574 2530/2575/2575 2531/2576/2576 +f 2813/2574/2574 2531/2576/2576 2529/2577/2577 +f 2528/2573/2573 2530/2575/2575 2813/2574/2574 +f 2530/2575/2575 2528/2573/2573 2532/2578/2578 +f 2532/2578/2578 2528/2573/2573 2533/2571/2571 +f 2535/2579/2579 2536/2580/2580 2817/2581/2581 +f 2531/2576/2576 2540/2582/2582 2535/2579/2579 +f 2531/2576/2576 2535/2579/2579 2534/2583/2583 +f 2540/2582/2582 2536/2580/2580 2535/2579/2579 +f 2818/2584/2584 2538/2585/2585 2537/2586/2586 +f 2817/2581/2581 2536/2580/2580 2818/2584/2584 +f 2536/2580/2580 2538/2585/2585 2818/2584/2584 +f 2537/2586/2586 2538/2585/2585 2539/2587/2587 +f 2542/2588/2588 2543/2589/2589 2822/2590/2590 +f 2539/2587/2587 2547/2591/2591 2542/2588/2588 +f 2539/2587/2587 2542/2588/2588 2541/2592/2592 +f 2547/2591/2591 2543/2589/2589 2542/2588/2588 +f 2824/2593/2593 2545/2594/2594 2544/2595/2595 +f 2822/2590/2590 2543/2589/2589 2824/2593/2593 +f 2543/2589/2589 2545/2594/2594 2824/2593/2593 +f 2544/2595/2595 2545/2594/2594 2546/2596/2596 +f 2544/2595/2595 2554/2597/2597 2548/2598/2598 +f 2554/2597/2597 2549/2599/2599 2548/2598/2598 +f 2828/2600/2600 2551/2601/2601 2550/2602/2602 +f 2549/2599/2599 2551/2601/2601 2828/2600/2600 +f 2551/2601/2601 2549/2599/2599 2553/2603/2603 +f 2550/2602/2602 2551/2601/2601 2552/2604/2604 +f 2554/2597/2597 2544/2595/2595 2546/2596/2596 +f 2553/2603/2603 2549/2599/2599 2554/2597/2597 +f 2556/2605/2605 2557/2606/2606 2831/2607/2607 +f 2552/2604/2604 2562/2608/2608 2556/2605/2605 +f 2552/2604/2604 2556/2605/2605 2555/2609/2609 +f 2562/2608/2608 2557/2606/2606 2556/2605/2605 +f 2558/2610/2610 2560/2611/2611 2561/2612/2612 +f 2558/2610/2610 2561/2612/2612 2559/2613/2613 +f 2831/2607/2607 2557/2606/2606 2558/2610/2610 +f 2557/2606/2606 2560/2611/2611 2558/2610/2610 +f 2561/2612/2612 2570/2614/2614 2835/2615/2615 +f 2561/2612/2612 2835/2615/2615 2563/2616/2616 +f 2570/2614/2614 2565/2617/2617 2564/2618/2618 +f 2570/2614/2614 2564/2618/2618 2835/2615/2615 +f 2566/2619/2619 2568/2620/2620 2567/2621/2621 +f 2564/2618/2618 2565/2617/2617 2568/2620/2620 +f 2564/2618/2618 2568/2620/2620 2566/2619/2619 +f 2567/2621/2621 2568/2620/2620 2569/2622/2622 +f 2839/2623/2623 2571/2624/2624 2572/2625/2625 +f 2567/2621/2621 2576/2626/2626 2839/2623/2623 +f 2576/2626/2626 2571/2624/2624 2839/2623/2623 +f 2573/2627/2627 2574/2628/2628 2843/2629/2629 +f 2573/2627/2627 2575/2630/2630 2574/2628/2628 +f 2572/2625/2625 2571/2624/2624 2573/2627/2627 +f 2571/2624/2624 2575/2630/2630 2573/2627/2627 +f 2576/2626/2626 2567/2621/2621 2569/2622/2622 +f 2574/2628/2628 2577/2631/2631 2843/2629/2629 +f 2577/2631/2631 2578/2632/2632 2845/2633/2633 +f 2574/2628/2628 2581/2634/2634 2577/2631/2631 +f 2581/2634/2634 2578/2632/2632 2577/2631/2631 +f 2846/2635/2635 2579/2636/2636 2848/2637/2637 +f 2846/2635/2635 2580/2638/2638 2579/2636/2636 +f 2845/2633/2633 2578/2632/2632 2846/2635/2635 +f 2578/2632/2632 2580/2638/2638 2846/2635/2635 +f 2579/2636/2636 2582/2639/2639 2848/2637/2637 +f 2582/2639/2639 2583/2640/2640 2584/2641/2641 +f 2579/2636/2636 2588/2642/2642 2582/2639/2639 +f 2588/2642/2642 2583/2640/2640 2582/2639/2639 +f 2851/2643/2643 2586/2644/2644 2587/2645/2645 +f 2851/2643/2643 2587/2645/2645 2585/2646/2646 +f 2584/2641/2641 2583/2640/2640 2851/2643/2643 +f 2583/2640/2640 2586/2644/2644 2851/2643/2643 +f 2590/2647/2647 2591/2648/2648 2592/2649/2649 +f 2587/2645/2645 2591/2648/2648 2590/2647/2647 +f 2587/2645/2645 2590/2647/2647 2589/2650/2650 +f 2593/2651/2651 2594/2652/2652 2595/2653/2653 +f 2593/2651/2651 2595/2653/2653 2857/2654/2654 +f 2592/2649/2649 2591/2648/2648 2593/2651/2651 +f 2591/2648/2648 2594/2652/2652 2593/2651/2651 +f 2859/2655/2655 2596/2656/2656 2597/2657/2657 +f 2595/2653/2653 2602/2658/2658 2859/2655/2655 +f 2595/2653/2653 2859/2655/2655 2858/2659/2659 +f 2602/2658/2658 2596/2656/2656 2859/2655/2655 +f 2598/2660/2660 2600/2661/2661 2599/2662/2662 +f 2597/2657/2657 2596/2656/2656 2598/2660/2660 +f 2596/2656/2656 2600/2661/2661 2598/2660/2660 +f 2599/2662/2662 2600/2661/2661 2601/2663/2663 +f 2599/2662/2662 2610/2664/2664 2603/2665/2665 +f 2610/2664/2664 2604/2666/2666 2603/2665/2665 +f 2605/2667/2667 2607/2668/2668 2608/2669/2669 +f 2605/2667/2667 2608/2669/2669 2606/2670/2670 +f 2604/2666/2666 2607/2668/2668 2605/2667/2667 +f 2607/2668/2668 2604/2666/2666 2609/2671/2671 +f 2610/2664/2664 2599/2662/2662 2601/2663/2663 +f 2609/2671/2671 2604/2666/2666 2610/2664/2664 +f 2612/2672/2672 2613/2673/2673 2614/2674/2674 +f 2611/2675/2675 2618/2676/2676 2612/2672/2672 +f 2618/2676/2676 2613/2673/2673 2612/2672/2672 +f 2615/2677/2677 2617/2678/2678 2619/2679/2679 +f 2615/2677/2677 2619/2679/2679 2616/2680/2680 +f 2614/2674/2674 2613/2673/2673 2615/2677/2677 +f 2613/2673/2673 2617/2678/2678 2615/2677/2677 +f 2618/2676/2676 2611/2675/2675 2608/2669/2669 +f 2619/2679/2679 2872/2681/2681 2875/2682/2682 +f 2872/2681/2681 2620/2683/2683 2873/2684/2684 +f 2619/2679/2679 2625/2685/2685 2872/2681/2681 +f 2625/2685/2685 2620/2683/2683 2872/2681/2681 +f 2621/2686/2686 2623/2687/2687 2624/2688/2688 +f 2621/2686/2686 2624/2688/2688 2622/2689/2689 +f 2873/2684/2684 2620/2683/2683 2621/2686/2686 +f 2620/2683/2683 2623/2687/2687 2621/2686/2686 +f 2878/2690/2690 2626/2691/2691 2627/2692/2692 +f 2624/2688/2688 2631/2693/2693 2878/2690/2690 +f 2624/2688/2688 2878/2690/2690 2622/2689/2689 +f 2631/2693/2693 2626/2691/2691 2878/2690/2690 +f 2880/2694/2694 2629/2695/2695 2628/2696/2696 +f 2627/2692/2692 2626/2691/2691 2880/2694/2694 +f 2626/2691/2691 2629/2695/2695 2880/2694/2694 +f 2628/2696/2696 2629/2695/2695 2630/2697/2697 +f 2632/2698/2698 2633/2699/2699 2634/2700/2700 +f 2628/2696/2696 2637/2701/2701 2632/2698/2698 +f 2637/2701/2701 2633/2699/2699 2632/2698/2698 +f 2884/2702/2702 2635/2703/2703 2886/2704/2704 +f 2884/2702/2702 2636/2705/2705 2635/2703/2703 +f 2634/2700/2700 2633/2699/2699 2884/2702/2702 +f 2633/2699/2699 2636/2705/2705 2884/2702/2702 +f 2637/2701/2701 2628/2696/2696 2630/2697/2697 +f 2638/2706/2706 2639/2707/2707 2640/2708/2708 +f 2635/2703/2703 2645/2709/2709 2638/2706/2706 +f 2645/2709/2709 2639/2707/2707 2638/2706/2706 +f 2641/2710/2710 2643/2711/2711 2642/2712/2712 +f 2640/2708/2708 2639/2707/2707 2641/2710/2710 +f 2639/2707/2707 2643/2711/2711 2641/2710/2710 +f 2642/2712/2712 2643/2711/2711 2644/2713/2713 +f 2894/2714/2714 2647/2715/2715 2648/2716/2716 +f 2644/2713/2713 2653/2717/2717 2894/2714/2714 +f 2644/2713/2713 2894/2714/2714 2646/2718/2718 +f 2653/2717/2717 2647/2715/2715 2894/2714/2714 +f 2649/2719/2719 2651/2720/2720 2650/2721/2721 +f 2648/2716/2716 2647/2715/2715 2649/2719/2719 +f 2647/2715/2715 2651/2720/2720 2649/2719/2719 +f 2650/2721/2721 2651/2720/2720 2652/2722/2722 +f 2654/2723/2723 2655/2724/2724 2899/2725/2725 +f 2650/2721/2721 2659/2726/2726 2654/2723/2723 +f 2659/2726/2726 2655/2724/2724 2654/2723/2723 +f 2901/2727/2727 2657/2728/2728 2658/2729/2729 +f 2901/2727/2727 2658/2729/2729 2656/2730/2730 +f 2899/2725/2725 2655/2724/2724 2901/2727/2727 +f 2655/2724/2724 2657/2728/2728 2901/2727/2727 +f 2659/2726/2726 2650/2721/2721 2652/2722/2722 +f 2661/2731/2731 2662/2732/2732 2904/2733/2733 +f 2658/2729/2729 2666/2734/2734 2661/2731/2731 +f 2658/2729/2729 2661/2731/2731 2660/2735/2735 +f 2666/2734/2734 2662/2732/2732 2661/2731/2731 +f 2663/2736/2736 2664/2737/2737 2665/2738/2738 +f 2904/2733/2733 2662/2732/2732 2663/2736/2736 +f 2662/2732/2732 2664/2737/2737 2663/2736/2736 +f 2665/2738/2738 2674/2739/2739 2908/2740/2740 +f 2665/2738/2738 2908/2740/2740 2667/2741/2741 +f 2674/2739/2739 2669/2742/2742 2668/2743/2743 +f 2674/2739/2739 2668/2743/2743 2908/2740/2740 +f 2670/2744/2744 2672/2745/2745 2671/2746/2746 +f 2668/2743/2743 2669/2742/2742 2672/2745/2745 +f 2668/2743/2743 2672/2745/2745 2670/2744/2744 +f 2671/2746/2746 2672/2745/2745 2673/2747/2747 +f 2912/2748/2748 2675/2749/2749 2676/2750/2750 +f 2671/2746/2746 2679/2751/2751 2912/2748/2748 +f 2679/2751/2751 2675/2749/2749 2912/2748/2748 +f 2914/2752/2752 2677/2753/2753 2916/2754/2754 +f 2914/2752/2752 2678/2755/2755 2677/2753/2753 +f 2676/2750/2750 2675/2749/2749 2914/2752/2752 +f 2675/2749/2749 2678/2755/2755 2914/2752/2752 +f 2679/2751/2751 2671/2746/2746 2673/2747/2747 +f 2677/2753/2753 2680/2756/2756 2916/2754/2754 +f 2680/2756/2756 2681/2757/2757 2918/2758/2758 +f 2677/2753/2753 2685/2759/2759 2680/2756/2756 +f 2685/2759/2759 2681/2757/2757 2680/2756/2756 +f 2920/2760/2760 2683/2761/2761 2684/2762/2762 +f 2920/2760/2760 2684/2762/2762 2682/2763/2763 +f 2918/2758/2758 2681/2757/2757 2920/2760/2760 +f 2681/2757/2757 2683/2761/2761 2920/2760/2760 +f 2686/2764/2764 2687/2765/2765 2924/2766/2766 +f 2684/2762/2762 2692/2767/2767 2686/2764/2764 +f 2692/2767/2767 2687/2765/2765 2686/2764/2764 +f 2688/2768/2768 2690/2769/2769 2691/2770/2770 +f 2688/2768/2768 2691/2770/2770 2689/2771/2771 +f 2924/2766/2766 2687/2765/2765 2688/2768/2768 +f 2687/2765/2765 2690/2769/2769 2688/2768/2768 +f 2928/2772/2772 2694/2773/2773 2929/2774/2774 +f 2691/2770/2770 2699/2775/2775 2928/2772/2772 +f 2691/2770/2770 2928/2772/2772 2693/2776/2776 +f 2699/2775/2775 2694/2773/2773 2928/2772/2772 +f 2695/2777/2777 2697/2778/2778 2696/2779/2779 +f 2929/2774/2774 2694/2773/2773 2695/2777/2777 +f 2694/2773/2773 2697/2778/2778 2695/2777/2777 +f 2696/2779/2779 2697/2778/2778 2698/2780/2780 +f 2700/2781/2781 2701/2782/2782 2702/2783/2783 +f 2696/2779/2779 2705/2784/2784 2700/2781/2781 +f 2705/2784/2784 2701/2782/2782 2700/2781/2781 +f 2935/2785/2785 2703/2786/2786 2704/2787/2787 +f 2935/2785/2785 2704/2787/2787 2936/2788/2788 +f 2702/2783/2783 2701/2782/2782 2935/2785/2785 +f 2701/2782/2782 2703/2786/2786 2935/2785/2785 +f 2705/2784/2784 2696/2779/2779 2698/2780/2780 +f 2707/2789/2789 2708/2790/2790 2939/2791/2791 +f 2704/2787/2787 2712/2792/2792 2707/2789/2789 +f 2704/2787/2787 2707/2789/2789 2706/2793/2793 +f 2712/2792/2792 2708/2790/2790 2707/2789/2789 +f 2709/2794/2794 2710/2795/2795 2711/2796/2796 +f 2939/2791/2791 2708/2790/2790 2709/2794/2794 +f 2708/2790/2790 2710/2795/2795 2709/2794/2794 +f 2943/2797/2797 2714/2798/2798 2715/2799/2799 +f 2711/2796/2796 2720/2800/2800 2943/2797/2797 +f 2711/2796/2796 2943/2797/2797 2713/2801/2801 +f 2720/2800/2800 2714/2798/2798 2943/2797/2797 +f 2716/2802/2802 2718/2803/2803 2717/2804/2804 +f 2715/2799/2799 2714/2798/2798 2716/2802/2802 +f 2714/2798/2798 2718/2803/2803 2716/2802/2802 +f 2717/2804/2804 2718/2803/2803 2719/2805/2805 +f 2947/2806/2806 2721/2807/2807 2948/2808/2808 +f 2717/2804/2804 2725/2809/2809 2947/2806/2806 +f 2725/2809/2809 2721/2807/2807 2947/2806/2806 +f 2949/2810/2810 2723/2811/2811 2722/2812/2812 +f 2948/2808/2808 2721/2807/2807 2949/2810/2810 +f 2721/2807/2807 2723/2811/2811 2949/2810/2810 +f 2722/2812/2812 2723/2811/2811 2724/2813/2813 +f 2725/2809/2809 2717/2804/2804 2719/2805/2805 +f 2726/2814/2814 2727/2815/2815 2728/2816/2816 +f 2722/2812/2812 2732/2817/2817 2726/2814/2814 +f 2732/2817/2817 2727/2815/2815 2726/2814/2814 +f 2953/2818/2818 2730/2819/2819 2731/2820/2820 +f 2953/2818/2818 2731/2820/2820 2729/2821/2821 +f 2728/2816/2816 2727/2815/2815 2953/2818/2818 +f 2727/2815/2815 2730/2819/2819 2953/2818/2818 +f 2732/2817/2817 2722/2812/2812 2724/2813/2813 +f 2734/2822/2822 2735/2823/2823 2736/2824/2824 +f 2731/2820/2820 2735/2823/2823 2734/2822/2822 +f 2731/2820/2820 2734/2822/2822 2733/2825/2825 +f 2737/2826/2826 2739/2827/2827 2740/2828/2828 +f 2737/2826/2826 2740/2828/2828 2738/2829/2829 +f 2736/2824/2824 2735/2823/2823 2737/2826/2826 +f 2735/2823/2823 2739/2827/2827 2737/2826/2826 +f 2740/2828/2828 2747/2830/2830 2960/2831/2831 +f 2740/2828/2828 2960/2831/2831 2741/2832/2832 +f 2747/2830/2830 2743/2833/2833 2742/2834/2834 +f 2747/2830/2830 2742/2834/2834 2960/2831/2831 +f 2744/2835/2835 2745/2836/2836 2964/2837/2837 +f 2744/2835/2835 2746/2838/2838 2745/2836/2836 +f 2742/2834/2834 2743/2833/2833 2746/2838/2838 +f 2742/2834/2834 2746/2838/2838 2744/2835/2835 +f 2745/2836/2836 2748/2839/2839 2964/2837/2837 +f 2748/2839/2839 2749/2840/2840 2966/2841/2841 +f 2745/2836/2836 2754/2842/2842 2748/2839/2839 +f 2754/2842/2842 2749/2840/2840 2748/2839/2839 +f 2750/2843/2843 2752/2844/2844 2753/2845/2845 +f 2750/2843/2843 2753/2845/2845 2751/2846/2846 +f 2966/2841/2841 2749/2840/2840 2750/2843/2843 +f 2749/2840/2840 2752/2844/2844 2750/2843/2843 +f 2756/2847/2847 2757/2848/2848 2970/2849/2849 +f 2753/2845/2845 2762/2850/2850 2756/2847/2847 +f 2753/2845/2845 2756/2847/2847 2755/2851/2851 +f 2762/2850/2850 2757/2848/2848 2756/2847/2847 +f 2758/2852/2852 2760/2853/2853 2761/2854/2854 +f 2758/2852/2852 2761/2854/2854 2759/2855/2855 +f 2970/2849/2849 2757/2848/2848 2758/2852/2852 +f 2757/2848/2848 2760/2853/2853 2758/2852/2852 +f 2973/2856/2856 2764/2857/2857 2765/2858/2858 +f 2761/2854/2854 2769/2859/2859 2973/2856/2856 +f 2761/2854/2854 2973/2856/2856 2763/2860/2860 +f 2769/2859/2859 2764/2857/2857 2973/2856/2856 +f 2766/2861/2861 2767/2862/2862 2977/2863/2863 +f 2766/2861/2861 2768/2864/2864 2767/2862/2862 +f 2765/2858/2858 2764/2857/2857 2766/2861/2861 +f 2764/2857/2857 2768/2864/2864 2766/2861/2861 +f 2767/2862/2862 2978/2865/2865 2977/2863/2863 +f 2978/2865/2865 2770/2866/2866 2771/2867/2867 +f 2767/2862/2862 2774/2868/2868 2978/2865/2865 +f 2774/2868/2868 2770/2866/2866 2978/2865/2865 +f 2980/2869/2869 2772/2870/2870 2982/2871/2871 +f 2980/2869/2869 2773/2872/2872 2772/2870/2870 +f 2771/2867/2867 2770/2866/2866 2980/2869/2869 +f 2770/2866/2866 2773/2872/2872 2980/2869/2869 +f 2772/2870/2870 2775/2873/2873 2982/2871/2871 +f 2775/2873/2873 2776/2874/2874 2984/2875/2875 +f 2772/2870/2870 2780/2876/2876 2775/2873/2873 +f 2780/2876/2876 2776/2874/2874 2775/2873/2873 +f 2985/2877/2877 2778/2878/2878 2779/2879/2879 +f 2985/2877/2877 2779/2879/2879 2777/2880/2880 +f 2984/2875/2875 2776/2874/2874 2985/2877/2877 +f 2776/2874/2874 2778/2878/2878 2985/2877/2877 +f 2782/2881/2881 2783/2882/2882 2989/2883/2883 +f 2779/2879/2879 2782/2881/2881 2781/2884/2884 +f 2779/2879/2879 2783/2882/2882 2782/2881/2881 +f 2784/2885/2885 2786/2886/2886 2787/2887/2887 +f 2784/2885/2885 2787/2887/2887 2785/2888/2888 +f 2989/2883/2883 2783/2882/2882 2784/2885/2885 +f 2783/2882/2882 2786/2886/2886 2784/2885/2885 +f 2993/2889/2889 2788/2890/2890 2789/2891/2891 +f 2787/2887/2887 2793/2892/2892 2993/2889/2889 +f 2787/2887/2887 2993/2889/2889 2992/2893/2893 +f 2793/2892/2892 2788/2890/2890 2993/2889/2889 +f 2790/2894/2894 2791/2895/2895 2997/2896/2896 +f 2790/2894/2894 2792/2897/2897 2791/2895/2895 +f 2789/2891/2891 2788/2890/2890 2790/2894/2894 +f 2788/2890/2890 2792/2897/2897 2790/2894/2894 +f 2791/2895/2895 2794/2898/2898 2997/2896/2896 +f 2794/2898/2898 2795/2899/2899 2999/2900/2900 +f 2791/2895/2895 2797/2901/2901 2794/2898/2898 +f 2797/2901/2901 2795/2899/2899 2794/2898/2898 +f 3001/2902/2902 2796/2903/2903 2508/2548/2548 +f 3001/2902/2902 2508/2548/2548 3002/2904/2904 +f 2999/2900/2900 2795/2899/2899 3001/2902/2902 +f 2795/2899/2899 2796/2903/2903 3001/2902/2902 +f 2505/2550/2550 2506/2545/2545 2799/2905/2905 +f 2505/2550/2550 2799/2905/2905 2798/2906/2906 +f 2506/2545/2545 2800/2547/2547 2799/2905/2905 +f 2801/2907/2907 2509/2553/2553 2802/2908/2908 +f 2509/2553/2553 2801/2907/2907 2507/2546/2546 +f 2801/2907/2907 2800/2547/2547 2507/2546/2546 +f 2803/2909/2909 2804/2554/2554 3009/2910/2910 +f 2804/2554/2554 2805/2911/2911 3009/2910/2910 +f 2513/2558/2558 2804/2554/2554 2803/2909/2909 +f 2806/2912/2912 2516/2559/2559 2517/2562/2562 +f 2806/2912/2912 2517/2562/2562 2807/2913/2913 +f 2805/2911/2911 2516/2559/2559 2806/2912/2912 +f 2516/2559/2559 2805/2911/2911 2515/2556/2556 +f 2515/2556/2556 2805/2911/2911 2804/2554/2554 +f 2807/2913/2913 2808/2564/2564 3014/2914/2914 +f 2808/2564/2564 2521/2566/2566 3016/2915/2915 +f 2808/2564/2564 3016/2915/2915 3014/2914/2914 +f 2517/2562/2562 2808/2564/2564 2807/2913/2913 +f 3018/2916/2916 2809/2567/2567 2810/2917/2917 +f 2809/2567/2567 2523/2570/2570 2810/2917/2917 +f 3016/2915/2915 2521/2566/2566 2809/2567/2567 +f 3016/2915/2915 2809/2567/2567 3018/2916/2916 +f 2523/2570/2570 2527/2572/2572 2811/2918/2918 +f 2523/2570/2570 2811/2918/2918 2810/2917/2917 +f 2527/2572/2572 2812/2919/2919 2811/2918/2918 +f 3023/2920/2920 2813/2574/2574 2814/2921/2921 +f 2813/2574/2574 2529/2577/2577 2814/2921/2921 +f 2812/2919/2919 2813/2574/2574 3023/2920/2920 +f 2813/2574/2574 2812/2919/2919 2528/2573/2573 +f 2528/2573/2573 2812/2919/2919 2527/2572/2572 +f 2815/2922/2922 2535/2579/2579 2816/2923/2923 +f 2535/2579/2579 2817/2581/2581 2816/2923/2923 +f 3028/2924/2924 2818/2584/2584 2819/2925/2925 +f 2817/2581/2581 2818/2584/2584 3028/2924/2924 +f 2819/2925/2925 2818/2584/2584 2537/2586/2586 +f 2535/2579/2579 2815/2922/2922 2534/2583/2583 +f 2821/2926/2926 2822/2590/2590 3032/2927/2927 +f 2541/2592/2592 2542/2588/2588 2821/2926/2926 +f 2541/2592/2592 2821/2926/2926 2820/2928/2928 +f 2542/2588/2588 2822/2590/2590 2821/2926/2926 +f 2823/2929/2929 2824/2593/2593 2825/2930/2930 +f 3032/2927/2927 2822/2590/2590 2824/2593/2593 +f 3032/2927/2927 2824/2593/2593 2823/2929/2929 +f 2825/2930/2930 2824/2593/2593 2544/2595/2595 +f 2825/2930/2930 2548/2598/2598 2826/2931/2931 +f 2548/2598/2598 2827/2932/2932 2826/2931/2931 +f 3037/2933/2933 2828/2600/2600 2829/2934/2934 +f 2827/2932/2932 2828/2600/2600 3037/2933/2933 +f 2828/2600/2600 2827/2932/2932 2549/2599/2599 +f 2829/2934/2934 2828/2600/2600 2550/2602/2602 +f 2548/2598/2598 2825/2930/2930 2544/2595/2595 +f 2549/2599/2599 2827/2932/2932 2548/2598/2598 +f 2555/2609/2609 2556/2605/2605 2830/2935/2935 +f 2555/2609/2609 2830/2935/2935 3039/2936/2936 +f 2556/2605/2605 2831/2607/2607 2830/2935/2935 +f 2832/2937/2937 2558/2610/2610 2833/2938/2938 +f 2831/2607/2607 2558/2610/2610 2832/2937/2937 +f 2833/2938/2938 2558/2610/2610 2559/2613/2613 +f 2834/2939/2939 2835/2615/2615 3045/2940/2940 +f 2834/2939/2939 3045/2940/2940 3044/2941/2941 +f 2835/2615/2615 2836/2942/2942 3045/2940/2940 +f 2835/2615/2615 2564/2618/2618 2836/2942/2942 +f 2837/2943/2943 2566/2619/2619 2838/2944/2944 +f 2836/2942/2942 2564/2618/2618 2566/2619/2619 +f 2836/2942/2942 2566/2619/2619 2837/2943/2943 +f 2838/2944/2944 2566/2619/2619 2567/2621/2621 +f 2835/2615/2615 2834/2939/2939 2563/2616/2616 +f 2838/2944/2944 2839/2623/2623 3049/2945/2945 +f 2839/2623/2623 2840/2946/2946 3049/2945/2945 +f 2841/2947/2947 2573/2627/2627 2843/2629/2629 +f 2841/2947/2947 2843/2629/2629 2842/2948/2948 +f 2840/2946/2946 2573/2627/2627 2841/2947/2947 +f 2573/2627/2627 2840/2946/2946 2572/2625/2625 +f 2839/2623/2623 2838/2944/2944 2567/2621/2621 +f 2572/2625/2625 2840/2946/2946 2839/2623/2623 +f 2844/2949/2949 2845/2633/2633 3055/2950/2950 +f 2843/2629/2629 2844/2949/2949 2842/2948/2948 +f 3056/2951/2951 2846/2635/2635 2847/2952/2952 +f 2846/2635/2635 2848/2637/2637 2847/2952/2952 +f 3055/2950/2950 2845/2633/2633 2846/2635/2635 +f 3055/2950/2950 2846/2635/2635 3056/2951/2951 +f 2577/2631/2631 2844/2949/2949 2843/2629/2629 +f 2845/2633/2633 2844/2949/2949 2577/2631/2631 +f 2848/2637/2637 2582/2639/2639 2849/2953/2953 +f 2848/2637/2637 2849/2953/2953 2847/2952/2952 +f 2582/2639/2639 2584/2641/2641 2850/2954/2954 +f 2582/2639/2639 2850/2954/2954 2849/2953/2953 +f 3061/2955/2955 2851/2643/2643 2852/2956/2956 +f 3061/2955/2955 2852/2956/2956 3062/2957/2957 +f 2850/2954/2954 2851/2643/2643 3061/2955/2955 +f 2850/2954/2954 2584/2641/2641 2851/2643/2643 +f 2852/2956/2956 2851/2643/2643 2585/2646/2646 +f 2853/2958/2958 2590/2647/2647 2854/2959/2959 +f 2590/2647/2647 2592/2649/2649 2855/2960/2960 +f 2590/2647/2647 2855/2960/2960 2854/2959/2959 +f 2856/2961/2961 2857/2654/2654 3067/2962/2962 +f 2855/2960/2960 2592/2649/2649 2856/2961/2961 +f 2857/2654/2654 2856/2961/2961 2593/2651/2651 +f 2856/2961/2961 2592/2649/2649 2593/2651/2651 +f 2590/2647/2647 2853/2958/2958 2589/2650/2650 +f 2858/2659/2659 2859/2655/2655 3069/2963/2963 +f 2858/2659/2659 3069/2963/2963 3072/2964/2964 +f 2859/2655/2655 2860/2965/2965 3069/2963/2963 +f 2861/2966/2966 2598/2660/2660 2862/2967/2967 +f 2860/2965/2965 2598/2660/2660 2861/2966/2966 +f 2862/2967/2967 2598/2660/2660 2599/2662/2662 +f 2598/2660/2660 2860/2965/2965 2597/2657/2657 +f 2597/2657/2657 2860/2965/2965 2859/2655/2655 +f 2862/2967/2967 2603/2665/2665 2863/2968/2968 +f 2603/2665/2665 2864/2969/2969 2863/2968/2968 +f 2865/2970/2970 2606/2670/2670 3078/2971/2971 +f 2605/2667/2667 2864/2969/2969 2604/2666/2666 +f 2606/2670/2670 2865/2970/2970 2605/2667/2667 +f 2865/2970/2970 2864/2969/2969 2605/2667/2667 +f 2603/2665/2665 2862/2967/2967 2599/2662/2662 +f 2604/2666/2666 2864/2969/2969 2603/2665/2665 +f 2867/2972/2972 2614/2674/2674 2868/2973/2973 +f 2869/2974/2974 2615/2677/2677 2870/2975/2975 +f 2868/2973/2973 2614/2674/2674 2615/2677/2677 +f 2868/2973/2973 2615/2677/2677 2869/2974/2974 +f 2870/2975/2975 2615/2677/2677 2616/2680/2680 +f 2612/2672/2672 2867/2972/2972 2866/2976/2976 +f 2612/2672/2672 2866/2976/2976 2611/2675/2675 +f 2614/2674/2674 2867/2972/2972 2612/2672/2672 +f 2871/2977/2977 2872/2681/2681 3084/2978/2978 +f 2872/2681/2681 2873/2684/2684 2874/2979/2979 +f 2872/2681/2681 2874/2979/2979 3084/2978/2978 +f 2875/2682/2682 2872/2681/2681 2871/2977/2977 +f 2876/2980/2980 2621/2686/2686 2622/2689/2689 +f 2876/2980/2980 2622/2689/2689 2877/2981/2981 +f 2874/2979/2979 2873/2684/2684 2876/2980/2980 +f 2873/2684/2684 2621/2686/2686 2876/2980/2980 +f 2877/2981/2981 2878/2690/2690 3089/2982/2982 +f 2878/2690/2690 2879/2983/2983 3089/2982/2982 +f 2622/2689/2689 2878/2690/2690 2877/2981/2981 +f 3093/2984/2984 2880/2694/2694 2881/2985/2985 +f 2879/2983/2983 2880/2694/2694 3093/2984/2984 +f 2881/2985/2985 2880/2694/2694 2628/2696/2696 +f 2880/2694/2694 2879/2983/2983 2627/2692/2692 +f 2627/2692/2692 2879/2983/2983 2878/2690/2690 +f 2881/2985/2985 2632/2698/2698 2882/2986/2986 +f 2632/2698/2698 2883/2987/2987 2882/2986/2986 +f 3097/2988/2988 2884/2702/2702 2885/2989/2989 +f 2884/2702/2702 2886/2704/2704 2885/2989/2989 +f 2883/2987/2987 2884/2702/2702 3097/2988/2988 +f 2884/2702/2702 2883/2987/2987 2634/2700/2700 +f 2632/2698/2698 2881/2985/2985 2628/2696/2696 +f 2634/2700/2700 2883/2987/2987 2632/2698/2698 +f 2887/2990/2990 2638/2706/2706 2888/2991/2991 +f 2638/2706/2706 2640/2708/2708 2889/2992/2992 +f 2638/2706/2706 2889/2992/2992 2888/2991/2991 +f 2890/2993/2993 2641/2710/2710 2892/2994/2994 +f 2890/2993/2993 2892/2994/2994 2891/2995/2995 +f 2889/2992/2992 2640/2708/2708 2641/2710/2710 +f 2889/2992/2992 2641/2710/2710 2890/2993/2993 +f 2892/2994/2994 2641/2710/2710 2642/2712/2712 +f 2893/2996/2996 2894/2714/2714 3105/2997/2997 +f 2894/2714/2714 2895/2998/2998 3105/2997/2997 +f 2646/2718/2718 2894/2714/2714 2893/2996/2996 +f 2896/2999/2999 2649/2719/2719 2897/3000/3000 +f 2895/2998/2998 2649/2719/2719 2896/2999/2999 +f 2897/3000/3000 2649/2719/2719 2650/2721/2721 +f 2649/2719/2719 2895/2998/2998 2648/2716/2716 +f 2648/2716/2716 2895/2998/2998 2894/2714/2714 +f 2898/3001/3001 2899/2725/2725 2900/3002/3002 +f 2897/3000/3000 2654/2723/2723 2898/3001/3001 +f 2654/2723/2723 2899/2725/2725 2898/3001/3001 +f 3112/3003/3003 2901/2727/2727 3113/3004/3004 +f 2901/2727/2727 2656/2730/2730 3113/3004/3004 +f 2900/3002/3002 2899/2725/2725 2901/2727/2727 +f 2900/3002/3002 2901/2727/2727 3112/3003/3003 +f 2654/2723/2723 2897/3000/3000 2650/2721/2721 +f 2660/2735/2735 2661/2731/2731 2903/3005/3005 +f 2660/2735/2735 2903/3005/3005 2902/3006/3006 +f 2661/2731/2731 2904/2733/2733 2903/3005/3005 +f 2905/3007/3007 2663/2736/2736 2906/3008/3008 +f 2905/3007/3007 2904/2733/2733 2663/2736/2736 +f 2907/3009/3009 2908/2740/2740 3120/3010/3010 +f 2908/2740/2740 2668/2743/2743 2909/3011/3011 +f 2908/2740/2740 2909/3011/3011 3120/3010/3010 +f 2910/3012/3012 2670/2744/2744 2911/3013/3013 +f 2909/3011/3011 2668/2743/2743 2910/3012/3012 +f 2668/2743/2743 2670/2744/2744 2910/3012/3012 +f 2911/3013/3013 2670/2744/2744 2671/2746/2746 +f 2908/2740/2740 2907/3009/3009 2667/2741/2741 +f 2911/3013/3013 2912/2748/2748 3124/3014/3014 +f 2912/2748/2748 2913/3015/3015 3124/3014/3014 +f 3128/3016/3016 2914/2752/2752 2915/3017/3017 +f 2914/2752/2752 2916/2754/2754 2915/3017/3017 +f 2913/3015/3015 2914/2752/2752 3128/3016/3016 +f 2914/2752/2752 2913/3015/3015 2676/2750/2750 +f 2912/2748/2748 2911/3013/3013 2671/2746/2746 +f 2676/2750/2750 2913/3015/3015 2912/2748/2748 +f 2917/3018/3018 2918/2758/2758 2919/3019/3019 +f 2916/2754/2754 2680/2756/2756 2917/3018/3018 +f 2916/2754/2754 2917/3018/3018 2915/3017/3017 +f 2680/2756/2756 2918/2758/2758 2917/3018/3018 +f 3133/3020/3020 2920/2760/2760 2921/3021/3021 +f 2919/3019/3019 2918/2758/2758 2920/2760/2760 +f 2919/3019/3019 2920/2760/2760 3133/3020/3020 +f 2921/3021/3021 2920/2760/2760 2682/2763/2763 +f 2686/2764/2764 2923/3022/3022 2922/3023/3023 +f 2925/3024/3024 2688/2768/2768 2689/2771/2771 +f 2925/3024/3024 2689/2771/2771 2926/3025/3025 +f 2924/2766/2766 2688/2768/2768 2925/3024/3024 +f 2924/2766/2766 2923/3022/3022 2686/2764/2764 +f 2927/3026/3026 2928/2772/2772 3141/3027/3027 +f 2928/2772/2772 2929/2774/2774 2930/3028/3028 +f 2928/2772/2772 2930/3028/3028 3141/3027/3027 +f 2693/2776/2776 2928/2772/2772 2927/3026/3026 +f 2931/3029/3029 2695/2777/2777 2932/3030/3030 +f 2930/3028/3028 2929/2774/2774 2931/3029/3029 +f 2929/2774/2774 2695/2777/2777 2931/3029/3029 +f 2932/3030/3030 2695/2777/2777 2696/2779/2779 +f 2932/3030/3030 2700/2781/2781 2933/3031/3031 +f 2700/2781/2781 2934/3032/3032 2933/3031/3031 +f 3149/3033/3033 2935/2785/2785 2936/2788/2788 +f 3149/3033/3033 2936/2788/2788 3151/3034/3034 +f 2934/3032/3032 2935/2785/2785 3149/3033/3033 +f 2935/2785/2785 2934/3032/3032 2702/2783/2783 +f 2700/2781/2781 2932/3030/3030 2696/2779/2779 +f 2702/2783/2783 2934/3032/3032 2700/2781/2781 +f 2937/3035/3035 2707/2789/2789 2938/3036/3036 +f 2707/2789/2789 2939/2791/2791 2938/3036/3036 +f 2940/3037/3037 2709/2794/2794 2941/3038/3038 +f 2939/2791/2791 2709/2794/2794 2940/3037/3037 +f 2707/2789/2789 2937/3035/3035 2706/2793/2793 +f 2942/3039/3039 2943/2797/2797 3158/3040/3040 +f 2943/2797/2797 2944/3041/3041 3158/3040/3040 +f 2713/2801/2801 2943/2797/2797 2942/3039/3039 +f 2945/3042/3042 2716/2802/2802 2946/3043/3043 +f 2944/3041/3041 2716/2802/2802 2945/3042/3042 +f 2946/3043/3043 2716/2802/2802 2717/2804/2804 +f 2716/2802/2802 2944/3041/3041 2715/2799/2799 +f 2715/2799/2799 2944/3041/3041 2943/2797/2797 +f 2946/3043/3043 2947/2806/2806 3162/3044/3044 +f 2947/2806/2806 2948/2808/2808 3164/3045/3045 +f 2947/2806/2806 3164/3045/3045 3162/3044/3044 +f 3166/3046/3046 2949/2810/2810 2950/3047/3047 +f 3164/3045/3045 2948/2808/2808 2949/2810/2810 +f 3164/3045/3045 2949/2810/2810 3166/3046/3046 +f 2950/3047/3047 2949/2810/2810 2722/2812/2812 +f 2947/2806/2806 2946/3043/3043 2717/2804/2804 +f 2950/3047/3047 2726/2814/2814 2951/3048/3048 +f 2726/2814/2814 2952/3049/3049 2951/3048/3048 +f 3171/3050/3050 2953/2818/2818 2954/3051/3051 +f 2953/2818/2818 2729/2821/2821 2954/3051/3051 +f 2952/3049/3049 2953/2818/2818 3171/3050/3050 +f 2953/2818/2818 2952/3049/3049 2728/2816/2816 +f 2726/2814/2814 2950/3047/3047 2722/2812/2812 +f 2728/2816/2816 2952/3049/3049 2726/2814/2814 +f 2955/3052/3052 2734/2822/2822 2956/3053/3053 +f 2734/2822/2822 2736/2824/2824 2956/3053/3053 +f 2957/3054/3054 2737/2826/2826 2958/3055/3055 +f 2956/3053/3053 2736/2824/2824 2737/2826/2826 +f 2956/3053/3053 2737/2826/2826 2957/3054/3054 +f 2958/3055/3055 2737/2826/2826 2738/2829/2829 +f 2734/2822/2822 2955/3052/3052 2733/2825/2825 +f 2959/3056/3056 2960/2831/2831 3180/3057/3057 +f 2960/2831/2831 2742/2834/2834 2961/3058/3058 +f 2960/2831/2831 2961/3058/3058 3180/3057/3057 +f 2741/2832/2832 2960/2831/2831 2959/3056/3056 +f 2962/3059/3059 2744/2835/2835 2964/2837/2837 +f 2962/3059/3059 2964/2837/2837 2963/3060/3060 +f 2961/3058/3058 2742/2834/2834 2962/3059/3059 +f 2742/2834/2834 2744/2835/2835 2962/3059/3059 +f 2964/2837/2837 2748/2839/2839 2965/3061/3061 +f 2964/2837/2837 2965/3061/3061 2963/3060/3060 +f 2748/2839/2839 2966/2841/2841 2965/3061/3061 +f 2967/3062/3062 2750/2843/2843 2751/2846/2846 +f 2967/3062/3062 2751/2846/2846 2968/3063/3063 +f 2966/2841/2841 2750/2843/2843 2967/3062/3062 +f 2969/3064/3064 2756/2847/2847 3189/3065/3065 +f 2756/2847/2847 2970/2849/2849 3189/3065/3065 +f 2971/3066/3066 2758/2852/2852 2759/2855/2855 +f 2970/2849/2849 2758/2852/2852 2971/3066/3066 +f 2756/2847/2847 2969/3064/3064 2755/2851/2851 +f 2972/3067/3067 2973/2856/2856 3194/3068/3068 +f 2973/2856/2856 2974/3069/3069 3194/3068/3068 +f 2763/2860/2860 2973/2856/2856 2972/3067/3067 +f 2973/2856/2856 2765/2858/2858 2974/3069/3069 +f 2975/3070/3070 2766/2861/2861 2977/2863/2863 +f 2975/3070/3070 2977/2863/2863 2976/3071/3071 +f 2974/3069/3069 2765/2858/2858 2766/2861/2861 +f 2974/3069/3069 2766/2861/2861 2975/3070/3070 +f 2976/3071/3071 2978/2865/2865 3199/3072/3072 +f 2978/2865/2865 2979/3073/3073 3199/3072/3072 +f 2977/2863/2863 2978/2865/2865 2976/3071/3071 +f 3203/3074/3074 2980/2869/2869 2981/3075/3075 +f 2980/2869/2869 2982/2871/2871 2981/3075/3075 +f 2979/3073/3073 2980/2869/2869 3203/3074/3074 +f 2980/2869/2869 2979/3073/3073 2771/2867/2867 +f 2771/2867/2867 2979/3073/3073 2978/2865/2865 +f 2982/2871/2871 2775/2873/2873 2983/3076/3076 +f 2982/2871/2871 2983/3076/3076 2981/3075/3075 +f 2775/2873/2873 2984/2875/2875 2983/3076/3076 +f 3208/3077/3077 2985/2877/2877 2986/3078/3078 +f 2985/2877/2877 2777/2880/2880 2986/3078/3078 +f 2984/2875/2875 2985/2877/2877 3208/3077/3077 +f 2987/3079/3079 2782/2881/2881 2988/3080/3080 +f 2782/2881/2881 2989/2883/2883 2988/3080/3080 +f 2990/3081/3081 2784/2885/2885 2991/3082/3082 +f 2989/2883/2883 2784/2885/2885 2990/3081/3081 +f 2991/3082/3082 2784/2885/2885 2785/2888/2888 +f 2782/2881/2881 2987/3079/3079 2781/2884/2884 +f 2992/2893/2893 2993/2889/2889 3216/3083/3083 +f 2992/2893/2893 3216/3083/3083 3215/3084/3084 +f 2993/2889/2889 2994/3085/3085 3216/3083/3083 +f 2995/3086/3086 2790/2894/2894 2997/2896/2896 +f 2995/3086/3086 2997/2896/2896 2996/3087/3087 +f 2994/3085/3085 2790/2894/2894 2995/3086/3086 +f 2790/2894/2894 2994/3085/3085 2789/2891/2891 +f 2789/2891/2891 2994/3085/3085 2993/2889/2889 +f 2998/3088/3088 2999/2900/2900 3000/3089/3089 +f 2997/2896/2896 2794/2898/2898 2998/3088/3088 +f 2997/2896/2896 2998/3088/3088 2996/3087/3087 +f 2794/2898/2898 2999/2900/2900 2998/3088/3088 +f 3223/3090/3090 3001/2902/2902 3002/2904/2904 +f 3223/3090/3090 3002/2904/2904 3225/3091/3091 +f 3000/3089/3089 2999/2900/2900 3001/2902/2902 +f 3000/3089/3089 3001/2902/2902 3223/3090/3090 +f 2802/2908/2908 3006/3092/3092 2801/2907/2907 +f 3006/3092/3092 3005/3093/3093 2800/2547/2547 +f 3006/3092/3092 2800/2547/2547 2801/2907/2907 +f 3007/3094/3094 3006/3092/3092 2802/2908/2908 +f 2799/2905/2905 3004/3095/3095 2798/2906/2906 +f 3004/3095/3095 3003/3096/3096 2798/2906/2906 +f 2800/2547/2547 3005/3093/3093 3004/3095/3095 +f 2800/2547/2547 3004/3095/3095 2799/2905/2905 +f 3008/3097/3097 3009/2910/2910 3010/3098/3098 +f 3009/2910/2910 3011/3099/3099 3010/3098/3098 +f 2807/2913/2913 3012/3100/3100 2806/2912/2912 +f 3012/3100/3100 2805/2911/2911 2806/2912/2912 +f 3013/3101/3101 3012/3100/3100 2807/2913/2913 +f 3012/3100/3100 3011/3099/3099 2805/2911/2911 +f 3009/2910/2910 3008/3097/3097 2803/2909/2909 +f 2805/2911/2911 3011/3099/3099 3009/2910/2910 +f 3013/3101/3101 3014/2914/2914 3015/3102/3102 +f 3014/2914/2914 3016/2915/2915 3237/3103/3103 +f 3014/2914/2914 3237/3103/3103 3015/3102/3102 +f 3017/3104/3104 3018/2916/2916 3019/3105/3105 +f 3018/2916/2916 2810/2917/2917 3019/3105/3105 +f 3237/3103/3103 3016/2915/2915 3018/2916/2916 +f 3237/3103/3103 3018/2916/2916 3017/3104/3104 +f 3014/2914/2914 3013/3101/3101 2807/2913/2913 +f 2810/2917/2917 3020/3106/3106 3019/3105/3105 +f 3022/3107/3107 3023/2920/2920 3243/3108/3108 +f 3023/2920/2920 2814/2921/2921 3243/3108/3108 +f 3021/3109/3109 3023/2920/2920 3022/3107/3107 +f 3023/2920/2920 3021/3109/3109 2812/2919/2919 +f 2811/2918/2918 3020/3106/3106 2810/2917/2917 +f 2812/2919/2919 3020/3106/3106 2811/2918/2918 +f 2812/2919/2919 3021/3109/3109 3020/3106/3106 +f 3027/3110/3110 3028/2924/2924 3029/3111/3111 +f 3026/3112/3112 3028/2924/2924 3027/3110/3110 +f 3029/3111/3111 3028/2924/2924 2819/2925/2925 +f 3028/2924/2924 3026/3112/3112 2817/2581/2581 +f 2816/2923/2923 3025/3113/3113 2815/2922/2922 +f 3025/3113/3113 3024/3114/3114 2815/2922/2922 +f 2817/2581/2581 3026/3112/3112 3025/3113/3113 +f 2817/2581/2581 3025/3113/3113 2816/2923/2923 +f 2820/2928/2928 2821/2926/2926 3031/3115/3115 +f 2820/2928/2928 3031/3115/3115 3030/3116/3116 +f 2821/2926/2926 3032/2927/2927 3031/3115/3115 +f 3033/3117/3117 2823/2929/2929 2825/2930/2930 +f 3033/3117/3117 2825/2930/2930 3252/3118/3118 +f 3032/2927/2927 2823/2929/2929 3033/3117/3117 +f 2825/2930/2930 3034/3119/3119 3252/3118/3118 +f 3036/3120/3120 3037/2933/2933 3038/3121/3121 +f 3035/3122/3122 3037/2933/2933 3036/3120/3120 +f 3038/3121/3121 3037/2933/2933 2829/2934/2934 +f 3037/2933/2933 3035/3122/3122 2827/2932/2932 +f 2826/2931/2931 3034/3119/3119 2825/2930/2930 +f 2827/2932/2932 3034/3119/3119 2826/2931/2931 +f 2827/2932/2932 3035/3122/3122 3034/3119/3119 +f 3039/2936/2936 3040/3123/3123 3257/3124/3124 +f 2833/2938/2938 3042/3125/3125 2832/2937/2937 +f 3042/3125/3125 3041/3126/3126 2831/2607/2607 +f 3042/3125/3125 2831/2607/2607 2832/2937/2937 +f 3043/3127/3127 3042/3125/3125 2833/2938/2938 +f 2830/2935/2935 3040/3123/3123 3039/2936/2936 +f 2831/2607/2607 3041/3126/3126 2830/2935/2935 +f 3041/3126/3126 3040/3123/3123 2830/2935/2935 +f 3044/2941/2941 3045/2940/2940 3265/3128/3128 +f 3044/2941/2941 3265/3128/3128 3262/3129/3129 +f 3045/2940/2940 2836/2942/2942 3046/3130/3130 +f 3045/2940/2940 3046/3130/3130 3265/3128/3128 +f 3047/3131/3131 2838/2944/2944 3048/3132/3132 +f 3046/3130/3130 2836/2942/2942 3047/3131/3131 +f 2838/2944/2944 3047/3131/3131 2837/2943/2943 +f 3047/3131/3131 2836/2942/2942 2837/2943/2943 +f 3048/3132/3132 3049/2945/2945 3050/3133/3133 +f 3049/2945/2945 3051/3134/3134 3050/3133/3133 +f 2838/2944/2944 3049/2945/2945 3048/3132/3132 +f 3052/3135/3135 2842/2948/2948 3053/3136/3136 +f 2842/2948/2948 3052/3135/3135 2841/2947/2947 +f 3052/3135/3135 2840/2946/2946 2841/2947/2947 +f 3052/3135/3135 3051/3134/3134 2840/2946/2946 +f 2840/2946/2946 3051/3134/3134 3049/2945/2945 +f 3054/3137/3137 3055/2950/2950 3271/3138/3138 +f 2842/2948/2948 3054/3137/3137 3053/3136/3136 +f 3273/3139/3139 3056/2951/2951 3057/3140/3140 +f 3056/2951/2951 2847/2952/2952 3057/3140/3140 +f 3271/3138/3138 3055/2950/2950 3056/2951/2951 +f 3271/3138/3138 3056/2951/2951 3273/3139/3139 +f 2844/2949/2949 3054/3137/3137 2842/2948/2948 +f 3055/2950/2950 3054/3137/3137 2844/2949/2949 +f 2847/2952/2952 3058/3141/3141 3057/3140/3140 +f 3058/3141/3141 2850/2954/2954 3059/3142/3142 +f 3060/3143/3143 3061/2955/2955 3062/2957/2957 +f 3060/3143/3143 3062/2957/2957 3277/3144/3144 +f 3059/3142/3142 3061/2955/2955 3060/3143/3143 +f 3059/3142/3142 2850/2954/2954 3061/2955/2955 +f 2849/2953/2953 3058/3141/3141 2847/2952/2952 +f 2850/2954/2954 3058/3141/3141 2849/2953/2953 +f 3064/3145/3145 2855/2960/2960 3065/3146/3146 +f 3066/3147/3147 3067/2962/2962 3282/3148/3148 +f 3065/3146/3146 2855/2960/2960 3066/3147/3147 +f 3067/2962/2962 3066/3147/3147 2856/2961/2961 +f 3066/3147/3147 2855/2960/2960 2856/2961/2961 +f 2854/2959/2959 3064/3145/3145 2853/2958/2958 +f 3064/3145/3145 3063/3149/3149 2853/2958/2958 +f 2855/2960/2960 3064/3145/3145 2854/2959/2959 +f 3068/3150/3150 3069/2963/2963 3070/3151/3151 +f 3069/2963/2963 3071/3152/3152 3070/3151/3151 +f 3072/2964/2964 3069/2963/2963 3068/3150/3150 +f 3073/3153/3153 2861/2966/2966 2862/2967/2967 +f 3073/3153/3153 2862/2967/2967 3074/3154/3154 +f 3071/3152/3152 2861/2966/2966 3073/3153/3153 +f 2861/2966/2966 3071/3152/3152 2860/2965/2965 +f 2860/2965/2965 3071/3152/3152 3069/2963/2963 +f 2862/2967/2967 2863/2968/2968 3075/3155/3155 +f 2862/2967/2967 3075/3155/3155 3074/3154/3154 +f 2863/2968/2968 3076/3156/3156 3075/3155/3155 +f 3077/3157/3157 3078/2971/2971 3290/3158/3158 +f 3078/2971/2971 3077/3157/3157 2865/2970/2970 +f 3077/3157/3157 3076/3156/3156 2864/2969/2969 +f 3077/3157/3157 2864/2969/2969 2865/2970/2970 +f 2864/2969/2969 3076/3156/3156 2863/2968/2968 +f 2866/2976/2976 3079/3159/3159 3291/3160/3160 +f 3079/3159/3159 2868/2973/2973 3080/3161/3161 +f 3080/3161/3161 2868/2973/2973 3081/3162/3162 +f 2870/2975/2975 3081/3162/3162 2869/2974/2974 +f 3081/3162/3162 2868/2973/2973 2869/2974/2974 +f 3082/3163/3163 3081/3162/3162 2870/2975/2975 +f 2867/2972/2972 3079/3159/3159 2866/2976/2976 +f 2868/2973/2973 3079/3159/3159 2867/2972/2972 +f 3083/3164/3164 3084/2978/2978 3085/3165/3165 +f 3084/2978/2978 3086/3166/3166 3085/3165/3165 +f 2871/2977/2977 3084/2978/2978 3083/3164/3164 +f 2877/2981/2981 3087/3167/3167 2876/2980/2980 +f 3087/3167/3167 2874/2979/2979 2876/2980/2980 +f 3088/3168/3168 3087/3167/3167 2877/2981/2981 +f 3087/3167/3167 3086/3166/3166 2874/2979/2979 +f 2874/2979/2979 3086/3166/3166 3084/2978/2978 +f 3088/3168/3168 3089/2982/2982 3090/3169/3169 +f 3089/2982/2982 3091/3170/3170 3090/3169/3169 +f 3092/3171/3171 3093/2984/2984 3302/3172/3172 +f 3093/2984/2984 2881/2985/2985 3302/3172/3172 +f 3091/3170/3170 3093/2984/2984 3092/3171/3171 +f 3093/2984/2984 3091/3170/3170 2879/2983/2983 +f 3089/2982/2982 3088/3168/3168 2877/2981/2981 +f 2879/2983/2983 3091/3170/3170 3089/2982/2982 +f 2881/2985/2985 3094/3173/3173 3302/3172/3172 +f 3096/3174/3174 3097/2988/2988 3098/3175/3175 +f 3095/3176/3176 3097/2988/2988 3096/3174/3174 +f 3098/3175/3175 3097/2988/2988 2885/2989/2989 +f 3097/2988/2988 3095/3176/3176 2883/2987/2987 +f 2882/2986/2986 3094/3173/3173 2881/2985/2985 +f 2883/2987/2987 3094/3173/3173 2882/2986/2986 +f 2883/2987/2987 3095/3176/3176 3094/3173/3173 +f 3100/3177/3177 2889/2992/2992 3101/3178/3178 +f 3101/3178/3178 2889/2992/2992 3102/3179/3179 +f 2891/2995/2995 3102/3179/3179 2890/2993/2993 +f 3102/3179/3179 2889/2992/2992 2890/2993/2993 +f 3103/3180/3180 3102/3179/3179 2891/2995/2995 +f 2888/2991/2991 3100/3177/3177 2887/2990/2990 +f 3100/3177/3177 3099/3181/3181 2887/2990/2990 +f 2889/2992/2992 3100/3177/3177 2888/2991/2991 +f 3104/3182/3182 3105/2997/2997 3106/3183/3183 +f 3105/2997/2997 3107/3184/3184 3106/3183/3183 +f 3108/3185/3185 2897/3000/3000 3316/3186/3186 +f 2897/3000/3000 3108/3185/3185 2896/2999/2999 +f 3108/3185/3185 2895/2998/2998 2896/2999/2999 +f 3108/3185/3185 3107/3184/3184 2895/2998/2998 +f 3105/2997/2997 3104/3182/3182 2893/2996/2996 +f 2895/2998/2998 3107/3184/3184 3105/2997/2997 +f 2897/3000/3000 3109/3187/3187 3316/3186/3186 +f 3111/3188/3188 3112/3003/3003 3113/3004/3004 +f 3111/3188/3188 3113/3004/3004 3320/3189/3189 +f 3110/3190/3190 3112/3003/3003 3111/3188/3188 +f 3112/3003/3003 3110/3190/3190 2900/3002/3002 +f 2898/3001/3001 3109/3187/3187 2897/3000/3000 +f 2900/3002/3002 3109/3187/3187 2898/3001/3001 +f 2900/3002/3002 3110/3190/3190 3109/3187/3187 +f 2906/3008/3008 3117/3191/3191 2905/3007/3007 +f 3117/3191/3191 3116/3192/3192 2904/2733/2733 +f 3117/3191/3191 2904/2733/2733 2905/3007/3007 +f 3118/3193/3193 3117/3191/3191 2906/3008/3008 +f 2903/3005/3005 3115/3194/3194 2902/3006/3006 +f 3115/3194/3194 3114/3195/3195 2902/3006/3006 +f 2904/2733/2733 3116/3192/3192 3115/3194/3194 +f 2904/2733/2733 3115/3194/3194 2903/3005/3005 +f 3119/3196/3196 3120/3010/3010 3121/3197/3197 +f 3120/3010/3010 3122/3198/3198 3121/3197/3197 +f 3123/3199/3199 2911/3013/3013 3330/3200/3200 +f 2911/3013/3013 3123/3199/3199 2910/3012/3012 +f 3123/3199/3199 2909/3011/3011 2910/3012/3012 +f 3123/3199/3199 3122/3198/3198 2909/3011/3011 +f 3120/3010/3010 3119/3196/3196 2907/3009/3009 +f 2909/3011/3011 3122/3198/3198 3120/3010/3010 +f 3330/3200/3200 3124/3014/3014 3125/3201/3201 +f 3124/3014/3014 3126/3202/3202 3125/3201/3201 +f 2911/3013/3013 3124/3014/3014 3330/3200/3200 +f 3127/3203/3203 3128/3016/3016 3129/3204/3204 +f 3128/3016/3016 2915/3017/3017 3129/3204/3204 +f 3126/3202/3202 3128/3016/3016 3127/3203/3203 +f 3128/3016/3016 3126/3202/3202 2913/3015/3015 +f 2913/3015/3015 3126/3202/3202 3124/3014/3014 +f 2915/3017/3017 3130/3205/3205 3129/3204/3204 +f 3132/3206/3206 3133/3020/3020 3134/3207/3207 +f 3131/3208/3208 3133/3020/3020 3132/3206/3206 +f 3134/3207/3207 3133/3020/3020 2921/3021/3021 +f 3133/3020/3020 3131/3208/3208 2919/3019/3019 +f 2917/3018/3018 3130/3205/3205 2915/3017/3017 +f 2919/3019/3019 3130/3205/3205 2917/3018/3018 +f 2919/3019/3019 3131/3208/3208 3130/3205/3205 +f 3138/3209/3209 2925/3024/3024 3139/3210/3210 +f 3137/3211/3211 2925/3024/3024 3138/3209/3209 +f 2925/3024/3024 3137/3211/3211 2924/2766/2766 +f 3139/3210/3210 2925/3024/3024 2926/3025/3025 +f 2923/3022/3022 3136/3212/3212 2922/3023/3023 +f 3136/3212/3212 3135/3213/3213 2922/3023/3023 +f 2924/2766/2766 3137/3211/3211 3136/3212/3212 +f 2924/2766/2766 3136/3212/3212 2923/3022/3022 +f 3140/3214/3214 3141/3027/3027 3142/3215/3215 +f 3141/3027/3027 3143/3216/3216 3142/3215/3215 +f 2927/3026/3026 3141/3027/3027 3140/3214/3214 +f 3144/3217/3217 2932/3030/3030 3145/3218/3218 +f 2932/3030/3030 3144/3217/3217 2931/3029/3029 +f 3144/3217/3217 2930/3028/3028 2931/3029/3029 +f 3144/3217/3217 3143/3216/3216 2930/3028/3028 +f 2930/3028/3028 3143/3216/3216 3141/3027/3027 +f 2932/3030/3030 3146/3219/3219 3145/3218/3218 +f 3148/3220/3220 3149/3033/3033 3150/3221/3221 +f 3149/3033/3033 3151/3034/3034 3150/3221/3221 +f 3147/3222/3222 3149/3033/3033 3148/3220/3220 +f 3149/3033/3033 3147/3222/3222 2934/3032/3032 +f 2933/3031/3031 3146/3219/3219 2932/3030/3030 +f 2934/3032/3032 3146/3219/3219 2933/3031/3031 +f 2934/3032/3032 3147/3222/3222 3146/3219/3219 +f 2940/3037/3037 3154/3223/3223 2939/2791/2791 +f 3156/3224/3224 3155/3225/3225 2940/3037/3037 +f 3156/3224/3224 2940/3037/3037 2941/3038/3038 +f 3155/3225/3225 3154/3223/3223 2940/3037/3037 +f 2938/3036/3036 3153/3226/3226 2937/3035/3035 +f 3153/3226/3226 3152/3227/3227 2937/3035/3035 +f 2939/2791/2791 3154/3223/3223 3153/3226/3226 +f 2939/2791/2791 3153/3226/3226 2938/3036/3036 +f 3157/3228/3228 3158/3040/3040 3159/3229/3229 +f 3158/3040/3040 3160/3230/3230 3159/3229/3229 +f 3161/3231/3231 2946/3043/3043 3362/3232/3232 +f 2946/3043/3043 3161/3231/3231 2945/3042/3042 +f 3161/3231/3231 2944/3041/3041 2945/3042/3042 +f 3161/3231/3231 3160/3230/3230 2944/3041/3041 +f 3158/3040/3040 3157/3228/3228 2942/3039/3039 +f 2944/3041/3041 3160/3230/3230 3158/3040/3040 +f 3362/3232/3232 3162/3044/3044 3163/3233/3233 +f 3162/3044/3044 3164/3045/3045 3364/3234/3234 +f 3162/3044/3044 3364/3234/3234 3163/3233/3233 +f 2946/3043/3043 3162/3044/3044 3362/3232/3232 +f 3165/3235/3235 3166/3046/3046 3167/3236/3236 +f 3166/3046/3046 2950/3047/3047 3167/3236/3236 +f 3364/3234/3234 3164/3045/3045 3166/3046/3046 +f 3364/3234/3234 3166/3046/3046 3165/3235/3235 +f 2950/3047/3047 3168/3237/3237 3167/3236/3236 +f 3170/3238/3238 3171/3050/3050 3172/3239/3239 +f 3169/3240/3240 3171/3050/3050 3170/3238/3238 +f 3172/3239/3239 3171/3050/3050 2954/3051/3051 +f 3171/3050/3050 3169/3240/3240 2952/3049/3049 +f 2951/3048/3048 3168/3237/3237 2950/3047/3047 +f 2952/3049/3049 3168/3237/3237 2951/3048/3048 +f 2952/3049/3049 3169/3240/3240 3168/3237/3237 +f 3176/3241/3241 3178/3242/3242 3177/3243/3243 +f 3178/3242/3242 3176/3241/3241 2957/3054/3054 +f 3178/3242/3242 2957/3054/3054 2958/3055/3055 +f 3176/3241/3241 3175/3244/3244 2956/3053/3053 +f 3176/3241/3241 2956/3053/3053 2957/3054/3054 +f 2956/3053/3053 3174/3245/3245 2955/3052/3052 +f 3174/3245/3245 3173/3246/3246 2955/3052/3052 +f 2956/3053/3053 3175/3244/3244 3174/3245/3245 +f 3179/3247/3247 3180/3057/3057 3181/3248/3248 +f 3180/3057/3057 3182/3249/3249 3181/3248/3248 +f 2959/3056/3056 3180/3057/3057 3179/3247/3247 +f 3183/3250/3250 2963/3060/3060 3380/3251/3251 +f 2963/3060/3060 3183/3250/3250 2962/3059/3059 +f 3183/3250/3250 2961/3058/3058 2962/3059/3059 +f 3183/3250/3250 3182/3249/3249 2961/3058/3058 +f 2961/3058/3058 3182/3249/3249 3180/3057/3057 +f 2963/3060/3060 3184/3252/3252 3380/3251/3251 +f 3186/3253/3253 2967/3062/3062 3187/3254/3254 +f 2967/3062/3062 2968/3063/3063 3187/3254/3254 +f 3185/3255/3255 2967/3062/3062 3186/3253/3253 +f 2967/3062/3062 3185/3255/3255 2966/2841/2841 +f 2965/3061/3061 3184/3252/3252 2963/3060/3060 +f 2966/2841/2841 3185/3255/3255 3184/3252/3252 +f 2966/2841/2841 3184/3252/3252 2965/3061/3061 +f 3192/3256/3256 3191/3257/3257 2971/3066/3066 +f 3191/3257/3257 3190/3258/3258 2970/2849/2849 +f 3191/3257/3257 2970/2849/2849 2971/3066/3066 +f 3189/3065/3065 3188/3259/3259 2969/3064/3064 +f 2970/2849/2849 3190/3258/3258 3189/3065/3065 +f 3193/3260/3260 3194/3068/3068 3195/3261/3261 +f 3194/3068/3068 2974/3069/3069 3196/3262/3262 +f 3194/3068/3068 3196/3262/3262 3195/3261/3261 +f 3197/3263/3263 2976/3071/3071 3198/3264/3264 +f 3196/3262/3262 2974/3069/3069 3197/3263/3263 +f 2976/3071/3071 3197/3263/3263 2975/3070/3070 +f 3197/3263/3263 2974/3069/3069 2975/3070/3070 +f 3194/3068/3068 3193/3260/3260 2972/3067/3067 +f 3198/3264/3264 3199/3072/3072 3200/3265/3265 +f 3199/3072/3072 3201/3266/3266 3200/3265/3265 +f 2976/3071/3071 3199/3072/3072 3198/3264/3264 +f 3202/3267/3267 3203/3074/3074 3204/3268/3268 +f 3203/3074/3074 2981/3075/3075 3204/3268/3268 +f 3201/3266/3266 3203/3074/3074 3202/3267/3267 +f 3203/3074/3074 3201/3266/3266 2979/3073/3073 +f 2979/3073/3073 3201/3266/3266 3199/3072/3072 +f 2981/3075/3075 3205/3269/3269 3204/3268/3268 +f 3207/3270/3270 3208/3077/3077 3209/3271/3271 +f 3206/3272/3272 3208/3077/3077 3207/3270/3270 +f 3209/3271/3271 3208/3077/3077 2986/3078/3078 +f 3208/3077/3077 3206/3272/3272 2984/2875/2875 +f 2983/3076/3076 3205/3269/3269 2981/3075/3075 +f 2984/2875/2875 3206/3272/3272 3205/3269/3269 +f 2984/2875/2875 3205/3269/3269 2983/3076/3076 +f 2991/3082/3082 3213/3273/3273 2990/3081/3081 +f 3213/3273/3273 3212/3274/3274 2989/2883/2883 +f 3213/3273/3273 2989/2883/2883 2990/3081/3081 +f 3214/3275/3275 3213/3273/3273 2991/3082/3082 +f 2988/3080/3080 3211/3276/3276 2987/3079/3079 +f 3211/3276/3276 3210/3277/3277 2987/3079/3079 +f 2989/2883/2883 3212/3274/3274 3211/3276/3276 +f 2989/2883/2883 3211/3276/3276 2988/3080/3080 +f 3215/3084/3084 3216/3083/3083 3217/3278/3278 +f 3215/3084/3084 3217/3278/3278 3409/3279/3279 +f 3216/3083/3083 3218/3280/3280 3217/3278/3278 +f 3219/3281/3281 2996/3087/3087 3413/3282/3282 +f 2996/3087/3087 3219/3281/3281 2995/3086/3086 +f 3219/3281/3281 2994/3085/3085 2995/3086/3086 +f 3219/3281/3281 3218/3280/3280 2994/3085/3085 +f 2994/3085/3085 3218/3280/3280 3216/3083/3083 +f 2996/3087/3087 3220/3283/3283 3413/3282/3282 +f 3222/3284/3284 3223/3090/3090 3224/3285/3285 +f 3223/3090/3090 3225/3091/3091 3224/3285/3285 +f 3221/3286/3286 3223/3090/3090 3222/3284/3284 +f 3223/3090/3090 3221/3286/3286 3000/3089/3089 +f 2998/3088/3088 3220/3283/3283 2996/3087/3087 +f 3000/3089/3089 3220/3283/3283 2998/3088/3088 +f 3000/3089/3089 3221/3286/3286 3220/3283/3283 +f 3006/3092/3092 3228/3287/3287 3005/3093/3093 +f 3230/3288/3288 3229/3289/3289 3006/3092/3092 +f 3230/3288/3288 3006/3092/3092 3007/3094/3094 +f 3229/3289/3289 3228/3287/3287 3006/3092/3092 +f 3004/3095/3095 3227/3290/3290 3003/3096/3096 +f 3227/3290/3290 3226/3291/3291 3003/3096/3096 +f 3005/3093/3093 3228/3287/3287 3227/3290/3290 +f 3005/3093/3093 3227/3290/3290 3004/3095/3095 +f 3013/3101/3101 3234/3292/3292 3012/3100/3100 +f 3234/3292/3292 3233/3293/3293 3011/3099/3099 +f 3234/3292/3292 3011/3099/3099 3012/3100/3100 +f 3235/3294/3294 3234/3292/3292 3013/3101/3101 +f 3010/3098/3098 3232/3295/3295 3231/3296/3296 +f 3010/3098/3098 3231/3296/3296 3008/3097/3097 +f 3011/3099/3099 3233/3293/3293 3010/3098/3098 +f 3233/3293/3293 3232/3295/3295 3010/3098/3098 +f 3238/3297/3297 3019/3105/3105 3239/3298/3298 +f 3019/3105/3105 3238/3297/3297 3017/3104/3104 +f 3238/3297/3297 3237/3103/3103 3017/3104/3104 +f 3015/3102/3102 3236/3299/3299 3235/3294/3294 +f 3015/3102/3102 3235/3294/3294 3013/3101/3101 +f 3237/3103/3103 3236/3299/3299 3015/3102/3102 +f 3019/3105/3105 3240/3300/3300 3239/3298/3298 +f 3242/3301/3301 3243/3108/3108 3436/3302/3302 +f 3243/3108/3108 3242/3301/3301 3022/3107/3107 +f 3242/3301/3301 3241/3303/3303 3021/3109/3109 +f 3242/3301/3301 3021/3109/3109 3022/3107/3107 +f 3020/3106/3106 3240/3300/3300 3019/3105/3105 +f 3021/3109/3109 3240/3300/3300 3020/3106/3106 +f 3021/3109/3109 3241/3303/3303 3240/3300/3300 +f 3244/3304/3304 3027/3110/3110 3245/3305/3305 +f 3245/3305/3305 3027/3110/3110 3246/3306/3306 +f 3247/3307/3307 3246/3306/3306 3027/3110/3110 +f 3247/3307/3307 3027/3110/3110 3029/3111/3111 +f 3025/3113/3113 3244/3304/3304 3024/3114/3114 +f 3026/3112/3112 3027/3110/3110 3025/3113/3113 +f 3027/3110/3110 3244/3304/3304 3025/3113/3113 +f 3248/3308/3308 3031/3115/3115 3249/3309/3309 +f 3031/3115/3115 3250/3310/3310 3249/3309/3309 +f 3251/3311/3311 3252/3118/3118 3447/3312/3312 +f 3033/3117/3117 3250/3310/3310 3032/2927/2927 +f 3252/3118/3118 3251/3311/3311 3033/3117/3117 +f 3251/3311/3311 3250/3310/3310 3033/3117/3117 +f 3031/3115/3115 3248/3308/3308 3030/3116/3116 +f 3032/2927/2927 3250/3310/3310 3031/3115/3115 +f 3252/3118/3118 3253/3313/3313 3447/3312/3312 +f 3256/3314/3314 3255/3315/3315 3036/3120/3120 +f 3256/3314/3314 3036/3120/3120 3038/3121/3121 +f 3255/3315/3315 3254/3316/3316 3035/3122/3122 +f 3255/3315/3315 3035/3122/3122 3036/3120/3120 +f 3034/3119/3119 3253/3313/3313 3252/3118/3118 +f 3035/3122/3122 3253/3313/3313 3034/3119/3119 +f 3035/3122/3122 3254/3316/3316 3253/3313/3313 +f 3257/3124/3124 3258/3317/3317 3453/3318/3318 +f 3043/3127/3127 3260/3319/3319 3042/3125/3125 +f 3260/3319/3319 3259/3320/3320 3041/3126/3126 +f 3260/3319/3319 3041/3126/3126 3042/3125/3125 +f 3261/3321/3321 3260/3319/3319 3043/3127/3127 +f 3040/3123/3123 3258/3317/3317 3257/3124/3124 +f 3041/3126/3126 3259/3320/3320 3040/3123/3123 +f 3259/3320/3320 3258/3317/3317 3040/3123/3123 +f 3262/3129/3129 3263/3322/3322 3459/3323/3323 +f 3262/3129/3129 3265/3128/3128 3263/3322/3322 +f 3265/3128/3128 3264/3324/3324 3263/3322/3322 +f 3266/3325/3325 3048/3132/3132 3267/3326/3326 +f 3048/3132/3132 3266/3325/3325 3047/3131/3131 +f 3266/3325/3325 3046/3130/3130 3047/3131/3131 +f 3266/3325/3325 3264/3324/3324 3046/3130/3130 +f 3046/3130/3130 3264/3324/3324 3265/3128/3128 +f 3269/3327/3327 3268/3328/3328 3053/3136/3136 +f 3269/3327/3327 3053/3136/3136 3270/3329/3329 +f 3053/3136/3136 3268/3328/3328 3052/3135/3135 +f 3268/3328/3328 3051/3134/3134 3052/3135/3135 +f 3050/3133/3133 3267/3326/3326 3048/3132/3132 +f 3051/3134/3134 3268/3328/3328 3267/3326/3326 +f 3051/3134/3134 3267/3326/3326 3050/3133/3133 +f 3272/3330/3330 3273/3139/3139 3274/3331/3331 +f 3272/3330/3330 3274/3331/3331 3472/3332/3332 +f 3271/3138/3138 3273/3139/3139 3272/3330/3330 +f 3274/3331/3331 3273/3139/3139 3057/3140/3140 +f 3054/3137/3137 3270/3329/3329 3053/3136/3136 +f 3271/3138/3138 3270/3329/3329 3054/3137/3137 +f 3274/3331/3331 3059/3142/3142 3275/3333/3333 +f 3274/3331/3331 3275/3333/3333 3472/3332/3332 +f 3276/3334/3334 3277/3144/3144 3477/3335/3335 +f 3275/3333/3333 3059/3142/3142 3276/3334/3334 +f 3277/3144/3144 3276/3334/3334 3060/3143/3143 +f 3276/3334/3334 3059/3142/3142 3060/3143/3143 +f 3058/3141/3141 3274/3331/3331 3057/3140/3140 +f 3059/3142/3142 3274/3331/3331 3058/3141/3141 +f 3279/3336/3336 3065/3146/3146 3280/3337/3337 +f 3281/3338/3338 3282/3148/3148 3483/3339/3339 +f 3280/3337/3337 3065/3146/3146 3281/3338/3338 +f 3282/3148/3148 3281/3338/3338 3066/3147/3147 +f 3281/3338/3338 3065/3146/3146 3066/3147/3147 +f 3064/3145/3145 3279/3336/3336 3063/3149/3149 +f 3279/3336/3336 3278/3340/3340 3063/3149/3149 +f 3065/3146/3146 3279/3336/3336 3064/3145/3145 +f 3068/3150/3150 3284/3341/3341 3283/3342/3342 +f 3285/3343/3343 3073/3153/3153 3074/3154/3154 +f 3285/3343/3343 3074/3154/3154 3286/3344/3344 +f 3284/3341/3341 3073/3153/3153 3285/3343/3343 +f 3073/3153/3153 3284/3341/3341 3071/3152/3152 +f 3070/3151/3151 3284/3341/3341 3068/3150/3150 +f 3071/3152/3152 3284/3341/3341 3070/3151/3151 +f 3074/3154/3154 3287/3345/3345 3286/3344/3344 +f 3289/3346/3346 3290/3158/3158 3493/3347/3347 +f 3290/3158/3158 3289/3346/3346 3077/3157/3157 +f 3289/3346/3346 3076/3156/3156 3077/3157/3157 +f 3289/3346/3346 3288/3348/3348 3076/3156/3156 +f 3075/3155/3155 3287/3345/3345 3074/3154/3154 +f 3076/3156/3156 3288/3348/3348 3287/3345/3345 +f 3076/3156/3156 3287/3345/3345 3075/3155/3155 +f 3291/3160/3160 3292/3349/3349 3495/3350/3350 +f 3291/3160/3160 3079/3159/3159 3292/3349/3349 +f 3079/3159/3159 3080/3161/3161 3292/3349/3349 +f 3292/3349/3349 3080/3161/3161 3293/3351/3351 +f 3082/3163/3163 3080/3161/3161 3081/3162/3162 +f 3293/3351/3351 3080/3161/3161 3082/3163/3163 +f 3088/3168/3168 3297/3352/3352 3087/3167/3167 +f 3297/3352/3352 3086/3166/3166 3087/3167/3167 +f 3298/3353/3353 3297/3352/3352 3088/3168/3168 +f 3297/3352/3352 3296/3354/3354 3086/3166/3166 +f 3085/3165/3165 3295/3355/3355 3294/3356/3356 +f 3085/3165/3165 3294/3356/3356 3083/3164/3164 +f 3086/3166/3166 3296/3354/3354 3295/3355/3355 +f 3086/3166/3166 3295/3355/3355 3085/3165/3165 +f 3301/3357/3357 3302/3172/3172 3509/3358/3358 +f 3302/3172/3172 3301/3357/3357 3092/3171/3171 +f 3301/3357/3357 3300/3359/3359 3091/3170/3170 +f 3301/3357/3357 3091/3170/3170 3092/3171/3171 +f 3090/3169/3169 3299/3360/3360 3298/3353/3353 +f 3090/3169/3169 3298/3353/3353 3088/3168/3168 +f 3091/3170/3170 3300/3359/3359 3299/3360/3360 +f 3091/3170/3170 3299/3360/3360 3090/3169/3169 +f 3302/3172/3172 3303/3361/3361 3509/3358/3358 +f 3306/3362/3362 3305/3363/3363 3096/3174/3174 +f 3306/3362/3362 3096/3174/3174 3098/3175/3175 +f 3305/3363/3363 3304/3364/3364 3095/3176/3176 +f 3305/3363/3363 3095/3176/3176 3096/3174/3174 +f 3094/3173/3173 3303/3361/3361 3302/3172/3172 +f 3095/3176/3176 3303/3361/3361 3094/3173/3173 +f 3095/3176/3176 3304/3364/3364 3303/3361/3361 +f 3308/3365/3365 3101/3178/3178 3309/3366/3366 +f 3309/3366/3366 3101/3178/3178 3310/3367/3367 +f 3103/3180/3180 3310/3367/3367 3102/3179/3179 +f 3310/3367/3367 3101/3178/3178 3102/3179/3179 +f 3311/3368/3368 3310/3367/3367 3103/3180/3180 +f 3100/3177/3177 3308/3365/3365 3099/3181/3181 +f 3308/3365/3365 3307/3369/3369 3099/3181/3181 +f 3101/3178/3178 3308/3365/3365 3100/3177/3177 +f 3315/3370/3370 3316/3186/3186 3524/3371/3371 +f 3316/3186/3186 3315/3370/3370 3108/3185/3185 +f 3315/3370/3370 3107/3184/3184 3108/3185/3185 +f 3315/3370/3370 3314/3372/3372 3107/3184/3184 +f 3106/3183/3183 3313/3373/3373 3312/3374/3374 +f 3106/3183/3183 3312/3374/3374 3104/3182/3182 +f 3107/3184/3184 3314/3372/3372 3313/3373/3373 +f 3107/3184/3184 3313/3373/3373 3106/3183/3183 +f 3316/3186/3186 3317/3375/3375 3524/3371/3371 +f 3319/3376/3376 3320/3189/3189 3529/3377/3377 +f 3320/3189/3189 3319/3376/3376 3111/3188/3188 +f 3319/3376/3376 3318/3378/3378 3110/3190/3190 +f 3319/3376/3376 3110/3190/3190 3111/3188/3188 +f 3109/3187/3187 3317/3375/3375 3316/3186/3186 +f 3110/3190/3190 3317/3375/3375 3109/3187/3187 +f 3110/3190/3190 3318/3378/3378 3317/3375/3375 +f 3118/3193/3193 3324/3379/3379 3117/3191/3191 +f 3324/3379/3379 3323/3380/3380 3116/3192/3192 +f 3324/3379/3379 3116/3192/3192 3117/3191/3191 +f 3325/3381/3381 3324/3379/3379 3118/3193/3193 +f 3115/3194/3194 3322/3382/3382 3321/3383/3383 +f 3115/3194/3194 3321/3383/3383 3114/3195/3195 +f 3116/3192/3192 3323/3380/3380 3322/3382/3382 +f 3116/3192/3192 3322/3382/3382 3115/3194/3194 +f 3329/3384/3384 3330/3200/3200 3540/3385/3385 +f 3330/3200/3200 3329/3384/3384 3123/3199/3199 +f 3329/3384/3384 3122/3198/3198 3123/3199/3199 +f 3329/3384/3384 3328/3386/3386 3122/3198/3198 +f 3121/3197/3197 3327/3387/3387 3326/3388/3388 +f 3121/3197/3197 3326/3388/3388 3119/3196/3196 +f 3122/3198/3198 3328/3386/3386 3327/3387/3387 +f 3122/3198/3198 3327/3387/3387 3121/3197/3197 +f 3330/3200/3200 3331/3389/3389 3540/3385/3385 +f 3333/3390/3390 3129/3204/3204 3334/3391/3391 +f 3129/3204/3204 3333/3390/3390 3127/3203/3203 +f 3333/3390/3390 3332/3392/3392 3126/3202/3202 +f 3333/3390/3390 3126/3202/3202 3127/3203/3203 +f 3125/3201/3201 3331/3389/3389 3330/3200/3200 +f 3126/3202/3202 3332/3392/3392 3331/3389/3389 +f 3126/3202/3202 3331/3389/3389 3125/3201/3201 +f 3129/3204/3204 3335/3393/3393 3334/3391/3391 +f 3338/3394/3394 3337/3395/3395 3132/3206/3206 +f 3338/3394/3394 3132/3206/3206 3134/3207/3207 +f 3337/3395/3395 3336/3396/3396 3131/3208/3208 +f 3337/3395/3395 3131/3208/3208 3132/3206/3206 +f 3130/3205/3205 3335/3393/3393 3129/3204/3204 +f 3131/3208/3208 3335/3393/3393 3130/3205/3205 +f 3131/3208/3208 3336/3396/3396 3335/3393/3393 +f 3138/3209/3209 3341/3397/3397 3137/3211/3211 +f 3343/3398/3398 3342/3399/3399 3138/3209/3209 +f 3343/3398/3398 3138/3209/3209 3139/3210/3210 +f 3342/3399/3399 3341/3397/3397 3138/3209/3209 +f 3136/3212/3212 3340/3400/3400 3135/3213/3213 +f 3340/3400/3400 3339/3401/3401 3135/3213/3213 +f 3137/3211/3211 3341/3397/3397 3340/3400/3400 +f 3137/3211/3211 3340/3400/3400 3136/3212/3212 +f 3347/3402/3402 3145/3218/3218 3348/3403/3403 +f 3145/3218/3218 3347/3402/3402 3144/3217/3217 +f 3347/3402/3402 3143/3216/3216 3144/3217/3217 +f 3347/3402/3402 3346/3404/3404 3143/3216/3216 +f 3142/3215/3215 3345/3405/3405 3344/3406/3406 +f 3142/3215/3215 3344/3406/3406 3140/3214/3214 +f 3143/3216/3216 3346/3404/3404 3345/3405/3405 +f 3143/3216/3216 3345/3405/3405 3142/3215/3215 +f 3145/3218/3218 3349/3407/3407 3348/3403/3403 +f 3352/3408/3408 3351/3409/3409 3148/3220/3220 +f 3352/3408/3408 3148/3220/3220 3150/3221/3221 +f 3351/3409/3409 3350/3410/3410 3147/3222/3222 +f 3351/3409/3409 3147/3222/3222 3148/3220/3220 +f 3146/3219/3219 3349/3407/3407 3145/3218/3218 +f 3147/3222/3222 3349/3407/3407 3146/3219/3219 +f 3147/3222/3222 3350/3410/3410 3349/3407/3407 +f 3155/3225/3225 3355/3411/3411 3154/3223/3223 +f 3357/3412/3412 3356/3413/3413 3155/3225/3225 +f 3357/3412/3412 3155/3225/3225 3156/3224/3224 +f 3356/3413/3413 3355/3411/3411 3155/3225/3225 +f 3153/3226/3226 3354/3414/3414 3152/3227/3227 +f 3354/3414/3414 3353/3415/3415 3152/3227/3227 +f 3154/3223/3223 3355/3411/3411 3354/3414/3414 +f 3154/3223/3223 3354/3414/3414 3153/3226/3226 +f 3361/3416/3416 3362/3232/3232 3573/3417/3417 +f 3362/3232/3232 3361/3416/3416 3161/3231/3231 +f 3361/3416/3416 3160/3230/3230 3161/3231/3231 +f 3361/3416/3416 3360/3418/3418 3160/3230/3230 +f 3159/3229/3229 3359/3419/3419 3358/3420/3420 +f 3159/3229/3229 3358/3420/3420 3157/3228/3228 +f 3160/3230/3230 3360/3418/3418 3359/3419/3419 +f 3160/3230/3230 3359/3419/3419 3159/3229/3229 +f 3362/3232/3232 3363/3421/3421 3573/3417/3417 +f 3365/3422/3422 3167/3236/3236 3366/3423/3423 +f 3167/3236/3236 3365/3422/3422 3165/3235/3235 +f 3365/3422/3422 3364/3234/3234 3165/3235/3235 +f 3163/3233/3233 3363/3421/3421 3362/3232/3232 +f 3364/3234/3234 3363/3421/3421 3163/3233/3233 +f 3167/3236/3236 3367/3424/3424 3366/3423/3423 +f 3370/3425/3425 3369/3426/3426 3170/3238/3238 +f 3370/3425/3425 3170/3238/3238 3172/3239/3239 +f 3369/3426/3426 3368/3427/3427 3169/3240/3240 +f 3369/3426/3426 3169/3240/3240 3170/3238/3238 +f 3168/3237/3237 3367/3424/3424 3167/3236/3236 +f 3169/3240/3240 3367/3424/3424 3168/3237/3237 +f 3169/3240/3240 3368/3427/3427 3367/3424/3424 +f 3374/3428/3428 3177/3243/3243 3375/3429/3429 +f 3177/3243/3243 3374/3428/3428 3176/3241/3241 +f 3374/3428/3428 3373/3430/3430 3175/3244/3244 +f 3374/3428/3428 3175/3244/3244 3176/3241/3241 +f 3174/3245/3245 3372/3431/3431 3173/3246/3246 +f 3372/3431/3431 3371/3432/3432 3173/3246/3246 +f 3175/3244/3244 3373/3430/3430 3372/3431/3431 +f 3175/3244/3244 3372/3431/3431 3174/3245/3245 +f 3379/3433/3433 3380/3251/3251 3592/3434/3434 +f 3380/3251/3251 3379/3433/3433 3183/3250/3250 +f 3379/3433/3433 3182/3249/3249 3183/3250/3250 +f 3379/3433/3433 3378/3435/3435 3182/3249/3249 +f 3181/3248/3248 3377/3436/3436 3376/3437/3437 +f 3181/3248/3248 3376/3437/3437 3179/3247/3247 +f 3182/3249/3249 3378/3435/3435 3377/3436/3436 +f 3182/3249/3249 3377/3436/3436 3181/3248/3248 +f 3380/3251/3251 3381/3438/3438 3592/3434/3434 +f 3381/3438/3438 3382/3439/3439 3383/3440/3440 +f 3383/3440/3440 3382/3439/3439 3384/3441/3441 +f 3186/3253/3253 3382/3439/3439 3185/3255/3255 +f 3385/3442/3442 3384/3441/3441 3186/3253/3253 +f 3385/3442/3442 3186/3253/3253 3187/3254/3254 +f 3384/3441/3441 3382/3439/3439 3186/3253/3253 +f 3184/3252/3252 3381/3438/3438 3380/3251/3251 +f 3185/3255/3255 3382/3439/3439 3381/3438/3438 +f 3185/3255/3255 3381/3438/3438 3184/3252/3252 +f 3192/3256/3256 3389/3443/3443 3191/3257/3257 +f 3389/3443/3443 3388/3444/3444 3190/3258/3258 +f 3389/3443/3443 3190/3258/3258 3191/3257/3257 +f 3390/3445/3445 3389/3443/3443 3192/3256/3256 +f 3189/3065/3065 3387/3446/3446 3188/3259/3259 +f 3387/3446/3446 3386/3447/3447 3188/3259/3259 +f 3190/3258/3258 3388/3444/3444 3387/3446/3446 +f 3190/3258/3258 3387/3446/3446 3189/3065/3065 +f 3392/3448/3448 3196/3262/3262 3393/3449/3449 +f 3394/3450/3450 3198/3264/3264 3395/3451/3451 +f 3393/3449/3449 3196/3262/3262 3394/3450/3450 +f 3198/3264/3264 3394/3450/3450 3197/3263/3263 +f 3394/3450/3450 3196/3262/3262 3197/3263/3263 +f 3195/3261/3261 3392/3448/3448 3391/3452/3452 +f 3195/3261/3261 3391/3452/3452 3193/3260/3260 +f 3196/3262/3262 3392/3448/3448 3195/3261/3261 +f 3198/3264/3264 3396/3453/3453 3395/3451/3451 +f 3398/3454/3454 3204/3268/3268 3399/3455/3455 +f 3204/3268/3268 3398/3454/3454 3202/3267/3267 +f 3398/3454/3454 3397/3456/3456 3201/3266/3266 +f 3398/3454/3454 3201/3266/3266 3202/3267/3267 +f 3200/3265/3265 3396/3453/3453 3198/3264/3264 +f 3201/3266/3266 3397/3456/3456 3396/3453/3453 +f 3201/3266/3266 3396/3453/3453 3200/3265/3265 +f 3204/3268/3268 3400/3457/3457 3399/3455/3455 +f 3403/3458/3458 3402/3459/3459 3207/3270/3270 +f 3403/3458/3458 3207/3270/3270 3209/3271/3271 +f 3402/3459/3459 3401/3460/3460 3206/3272/3272 +f 3402/3459/3459 3206/3272/3272 3207/3270/3270 +f 3205/3269/3269 3400/3457/3457 3204/3268/3268 +f 3206/3272/3272 3400/3457/3457 3205/3269/3269 +f 3206/3272/3272 3401/3460/3460 3400/3457/3457 +f 3214/3275/3275 3407/3461/3461 3213/3273/3273 +f 3407/3461/3461 3406/3462/3462 3212/3274/3274 +f 3407/3461/3461 3212/3274/3274 3213/3273/3273 +f 3408/3463/3463 3407/3461/3461 3214/3275/3275 +f 3211/3276/3276 3405/3464/3464 3404/3465/3465 +f 3211/3276/3276 3404/3465/3465 3210/3277/3277 +f 3212/3274/3274 3406/3462/3462 3211/3276/3276 +f 3406/3462/3462 3405/3464/3464 3211/3276/3276 +f 3409/3279/3279 3410/3466/3466 3621/3467/3467 +f 3412/3468/3468 3413/3282/3282 3626/3469/3469 +f 3413/3282/3282 3412/3468/3468 3219/3281/3281 +f 3412/3468/3468 3218/3280/3280 3219/3281/3281 +f 3412/3468/3468 3411/3470/3470 3218/3280/3280 +f 3217/3278/3278 3410/3466/3466 3409/3279/3279 +f 3218/3280/3280 3411/3470/3470 3410/3466/3466 +f 3218/3280/3280 3410/3466/3466 3217/3278/3278 +f 3413/3282/3282 3414/3471/3471 3626/3469/3469 +f 3417/3472/3472 3416/3473/3473 3222/3284/3284 +f 3417/3472/3472 3222/3284/3284 3224/3285/3285 +f 3416/3473/3473 3415/3474/3474 3221/3286/3286 +f 3416/3473/3473 3221/3286/3286 3222/3284/3284 +f 3220/3283/3283 3414/3471/3471 3413/3282/3282 +f 3221/3286/3286 3414/3471/3471 3220/3283/3283 +f 3221/3286/3286 3415/3474/3474 3414/3471/3471 +f 3229/3289/3289 3420/3475/3475 3228/3287/3287 +f 3422/3476/3476 3421/3477/3477 3229/3289/3289 +f 3422/3476/3476 3229/3289/3289 3230/3288/3288 +f 3421/3477/3477 3420/3475/3475 3229/3289/3289 +f 3227/3290/3290 3419/3478/3478 3418/3479/3479 +f 3227/3290/3290 3418/3479/3479 3226/3291/3291 +f 3228/3287/3287 3420/3475/3475 3227/3290/3290 +f 3420/3475/3475 3419/3478/3478 3227/3290/3290 +f 3423/3480/3480 3424/3481/3481 3425/3482/3482 +f 3424/3481/3481 3426/3483/3483 3425/3482/3482 +f 3427/3484/3484 3235/3294/3294 3428/3485/3485 +f 3426/3483/3483 3424/3481/3481 3427/3484/3484 +f 3234/3292/3292 3424/3481/3481 3233/3293/3293 +f 3235/3294/3294 3427/3484/3484 3234/3292/3292 +f 3427/3484/3484 3424/3481/3481 3234/3292/3292 +f 3232/3295/3295 3424/3481/3481 3423/3480/3480 +f 3232/3295/3295 3423/3480/3480 3231/3296/3296 +f 3233/3293/3293 3424/3481/3481 3232/3295/3295 +f 3235/3294/3294 3429/3486/3486 3428/3485/3485 +f 3238/3297/3297 3430/3487/3487 3237/3103/3103 +f 3432/3488/3488 3431/3489/3489 3238/3297/3297 +f 3432/3488/3488 3238/3297/3297 3239/3298/3298 +f 3431/3489/3489 3430/3487/3487 3238/3297/3297 +f 3236/3299/3299 3429/3486/3486 3235/3294/3294 +f 3237/3103/3103 3430/3487/3487 3236/3299/3299 +f 3430/3487/3487 3429/3486/3486 3236/3299/3299 +f 3435/3490/3490 3436/3302/3302 3437/3491/3491 +f 3242/3301/3301 3434/3492/3492 3241/3303/3303 +f 3436/3302/3302 3435/3490/3490 3242/3301/3301 +f 3435/3490/3490 3434/3492/3492 3242/3301/3301 +f 3240/3300/3300 3433/3493/3493 3432/3488/3488 +f 3240/3300/3300 3432/3488/3488 3239/3298/3298 +f 3241/3303/3303 3434/3492/3492 3240/3300/3300 +f 3434/3492/3492 3433/3493/3493 3240/3300/3300 +f 3439/3494/3494 3245/3305/3305 3440/3495/3495 +f 3440/3495/3495 3245/3305/3305 3441/3496/3496 +f 3442/3497/3497 3441/3496/3496 3246/3306/3306 +f 3442/3497/3497 3246/3306/3306 3247/3307/3307 +f 3441/3496/3496 3245/3305/3305 3246/3306/3306 +f 3244/3304/3304 3439/3494/3494 3438/3498/3498 +f 3245/3305/3305 3439/3494/3494 3244/3304/3304 +f 3446/3499/3499 3447/3312/3312 3448/3500/3500 +f 3251/3311/3311 3445/3501/3501 3250/3310/3310 +f 3447/3312/3312 3446/3499/3499 3251/3311/3311 +f 3446/3499/3499 3445/3501/3501 3251/3311/3311 +f 3249/3309/3309 3444/3502/3502 3443/3503/3503 +f 3249/3309/3309 3443/3503/3503 3248/3308/3308 +f 3250/3310/3310 3445/3501/3501 3249/3309/3309 +f 3445/3501/3501 3444/3502/3502 3249/3309/3309 +f 3447/3312/3312 3449/3504/3504 3448/3500/3500 +f 3451/3505/3505 3256/3314/3314 3452/3506/3506 +f 3255/3315/3315 3450/3507/3507 3254/3316/3316 +f 3256/3314/3314 3451/3505/3505 3255/3315/3315 +f 3451/3505/3505 3450/3507/3507 3255/3315/3315 +f 3253/3313/3313 3449/3504/3504 3447/3312/3312 +f 3254/3316/3316 3450/3507/3507 3253/3313/3313 +f 3450/3507/3507 3449/3504/3504 3253/3313/3313 +f 3453/3318/3318 3454/3508/3508 3455/3509/3509 +f 3260/3319/3319 3456/3510/3510 3259/3320/3320 +f 3458/3511/3511 3457/3512/3512 3260/3319/3319 +f 3458/3511/3511 3260/3319/3319 3261/3321/3321 +f 3457/3512/3512 3456/3510/3510 3260/3319/3319 +f 3258/3317/3317 3454/3508/3508 3453/3318/3318 +f 3259/3320/3320 3456/3510/3510 3258/3317/3317 +f 3456/3510/3510 3454/3508/3508 3258/3317/3317 +f 3459/3323/3323 3263/3322/3322 3460/3513/3513 +f 3459/3323/3323 3460/3513/3513 3461/3514/3514 +f 3263/3322/3322 3462/3515/3515 3460/3513/3513 +f 3464/3516/3516 3463/3517/3517 3266/3325/3325 +f 3464/3516/3516 3266/3325/3325 3267/3326/3326 +f 3463/3517/3517 3462/3515/3515 3264/3324/3324 +f 3463/3517/3517 3264/3324/3324 3266/3325/3325 +f 3264/3324/3324 3462/3515/3515 3263/3322/3322 +f 3465/3518/3518 3269/3327/3327 3466/3519/3519 +f 3466/3519/3519 3269/3327/3327 3467/3520/3520 +f 3468/3521/3521 3467/3520/3520 3269/3327/3327 +f 3468/3521/3521 3269/3327/3327 3270/3329/3329 +f 3267/3326/3326 3465/3518/3518 3464/3516/3516 +f 3268/3328/3328 3269/3327/3327 3267/3326/3326 +f 3269/3327/3327 3465/3518/3518 3267/3326/3326 +f 3469/3522/3522 3272/3330/3330 3470/3523/3523 +f 3471/3524/3524 3472/3332/3332 3473/3525/3525 +f 3470/3523/3523 3272/3330/3330 3471/3524/3524 +f 3472/3332/3332 3471/3524/3524 3272/3330/3330 +f 3270/3329/3329 3469/3522/3522 3468/3521/3521 +f 3271/3138/3138 3272/3330/3330 3270/3329/3329 +f 3272/3330/3330 3469/3522/3522 3270/3329/3329 +f 3472/3332/3332 3474/3526/3526 3473/3525/3525 +f 3474/3526/3526 3275/3333/3333 3475/3527/3527 +f 3476/3528/3528 3477/3335/3335 3478/3529/3529 +f 3475/3527/3527 3275/3333/3333 3476/3528/3528 +f 3477/3335/3335 3476/3528/3528 3276/3334/3334 +f 3476/3528/3528 3275/3333/3333 3276/3334/3334 +f 3275/3333/3333 3474/3526/3526 3472/3332/3332 +f 3480/3530/3530 3280/3337/3337 3481/3531/3531 +f 3482/3532/3532 3483/3339/3339 3484/3533/3533 +f 3481/3531/3531 3280/3337/3337 3482/3532/3532 +f 3483/3339/3339 3482/3532/3532 3281/3338/3338 +f 3482/3532/3532 3280/3337/3337 3281/3338/3338 +f 3279/3336/3336 3480/3530/3530 3479/3534/3534 +f 3279/3336/3336 3479/3534/3534 3278/3340/3340 +f 3280/3337/3337 3480/3530/3530 3279/3336/3336 +f 3486/3535/3535 3285/3343/3343 3487/3536/3536 +f 3283/3342/3342 3486/3535/3535 3485/3537/3537 +f 3487/3536/3536 3285/3343/3343 3488/3538/3538 +f 3489/3539/3539 3488/3538/3538 3285/3343/3343 +f 3489/3539/3539 3285/3343/3343 3286/3344/3344 +f 3284/3341/3341 3486/3535/3535 3283/3342/3342 +f 3285/3343/3343 3486/3535/3535 3284/3341/3341 +f 3490/3540/3540 3287/3345/3345 3491/3541/3541 +f 3492/3542/3542 3493/3347/3347 3494/3543/3543 +f 3491/3541/3541 3287/3345/3345 3492/3542/3542 +f 3289/3346/3346 3287/3345/3345 3288/3348/3348 +f 3493/3347/3347 3492/3542/3542 3289/3346/3346 +f 3492/3542/3542 3287/3345/3345 3289/3346/3346 +f 3287/3345/3345 3490/3540/3540 3489/3539/3539 +f 3287/3345/3345 3489/3539/3539 3286/3344/3344 +f 3495/3350/3350 3496/3544/3544 3497/3545/3545 +f 3293/3351/3351 3498/3546/3546 3292/3349/3349 +f 3500/3547/3547 3499/3548/3548 3293/3351/3351 +f 3499/3548/3548 3498/3546/3546 3293/3351/3351 +f 3292/3349/3349 3496/3544/3544 3495/3350/3350 +f 3498/3546/3546 3496/3544/3544 3292/3349/3349 +f 3294/3356/3356 3501/3549/3549 3502/3550/3550 +f 3504/3551/3551 3298/3353/3353 3505/3552/3552 +f 3297/3352/3352 3503/3553/3553 3296/3354/3354 +f 3298/3353/3353 3504/3551/3551 3297/3352/3352 +f 3504/3551/3551 3503/3553/3553 3297/3352/3352 +f 3295/3355/3355 3501/3549/3549 3294/3356/3356 +f 3296/3354/3354 3503/3553/3553 3295/3355/3355 +f 3503/3553/3553 3501/3549/3549 3295/3355/3355 +f 3298/3353/3353 3506/3554/3554 3505/3552/3552 +f 3508/3555/3555 3509/3358/3358 3510/3556/3556 +f 3301/3357/3357 3507/3557/3557 3300/3359/3359 +f 3509/3358/3358 3508/3555/3555 3301/3357/3357 +f 3508/3555/3555 3507/3557/3557 3301/3357/3357 +f 3299/3360/3360 3506/3554/3554 3298/3353/3353 +f 3300/3359/3359 3507/3557/3557 3299/3360/3360 +f 3507/3557/3557 3506/3554/3554 3299/3360/3360 +f 3509/3358/3358 3511/3558/3558 3510/3556/3556 +f 3511/3558/3558 3304/3364/3364 3512/3559/3559 +f 3513/3560/3560 3304/3364/3364 3514/3561/3561 +f 3512/3559/3559 3304/3364/3364 3513/3560/3560 +f 3514/3561/3561 3304/3364/3364 3305/3363/3363 +f 3514/3561/3561 3305/3363/3363 3306/3362/3362 +f 3303/3361/3361 3511/3558/3558 3509/3358/3358 +f 3304/3364/3364 3511/3558/3558 3303/3361/3361 +f 3516/3562/3562 3309/3366/3366 3517/3563/3563 +f 3517/3563/3563 3309/3366/3366 3518/3564/3564 +f 3519/3565/3565 3518/3564/3564 3310/3367/3367 +f 3519/3565/3565 3310/3367/3367 3311/3368/3368 +f 3518/3564/3564 3309/3366/3366 3310/3367/3367 +f 3308/3365/3365 3516/3562/3562 3515/3566/3566 +f 3308/3365/3365 3515/3566/3566 3307/3369/3369 +f 3309/3366/3366 3516/3562/3562 3308/3365/3365 +f 3312/3374/3374 3520/3567/3567 3521/3568/3568 +f 3523/3569/3569 3524/3371/3371 3525/3570/3570 +f 3315/3370/3370 3522/3571/3571 3314/3372/3372 +f 3524/3371/3371 3523/3569/3569 3315/3370/3370 +f 3523/3569/3569 3522/3571/3571 3315/3370/3370 +f 3313/3373/3373 3520/3567/3567 3312/3374/3374 +f 3314/3372/3372 3522/3571/3571 3313/3373/3373 +f 3522/3571/3571 3520/3567/3567 3313/3373/3373 +f 3524/3371/3371 3526/3572/3572 3525/3570/3570 +f 3528/3573/3573 3529/3377/3377 3530/3574/3574 +f 3319/3376/3376 3527/3575/3575 3318/3378/3378 +f 3529/3377/3377 3528/3573/3573 3319/3376/3376 +f 3528/3573/3573 3527/3575/3575 3319/3376/3376 +f 3317/3375/3375 3526/3572/3572 3524/3371/3371 +f 3318/3378/3378 3527/3575/3575 3317/3375/3375 +f 3527/3575/3575 3526/3572/3572 3317/3375/3375 +f 3324/3379/3379 3533/3576/3576 3323/3380/3380 +f 3535/3577/3577 3534/3578/3578 3324/3379/3379 +f 3535/3577/3577 3324/3379/3379 3325/3381/3381 +f 3534/3578/3578 3533/3576/3576 3324/3379/3379 +f 3322/3382/3382 3532/3579/3579 3531/3580/3580 +f 3322/3382/3382 3531/3580/3580 3321/3383/3383 +f 3323/3380/3380 3533/3576/3576 3322/3382/3382 +f 3533/3576/3576 3532/3579/3579 3322/3382/3382 +f 3536/3581/3581 3328/3386/3386 3537/3582/3582 +f 3328/3386/3386 3538/3583/3583 3537/3582/3582 +f 3326/3388/3388 3328/3386/3386 3536/3581/3581 +f 3539/3584/3584 3540/3385/3385 3541/3585/3585 +f 3538/3583/3583 3328/3386/3386 3539/3584/3584 +f 3540/3385/3385 3539/3584/3584 3329/3384/3384 +f 3539/3584/3584 3328/3386/3386 3329/3384/3384 +f 3327/3387/3387 3328/3386/3386 3326/3388/3388 +f 3540/3385/3385 3542/3586/3586 3541/3585/3585 +f 3333/3390/3390 3543/3587/3587 3332/3392/3392 +f 3545/3588/3588 3544/3589/3589 3333/3390/3390 +f 3545/3588/3588 3333/3390/3390 3334/3391/3391 +f 3544/3589/3589 3543/3587/3587 3333/3390/3390 +f 3331/3389/3389 3542/3586/3586 3540/3385/3385 +f 3332/3392/3392 3543/3587/3587 3331/3389/3389 +f 3543/3587/3587 3542/3586/3586 3331/3389/3389 +f 3546/3590/3590 3336/3396/3396 3547/3591/3591 +f 3548/3592/3592 3336/3396/3396 3549/3593/3593 +f 3336/3396/3396 3338/3394/3394 3549/3593/3593 +f 3547/3591/3591 3336/3396/3396 3548/3592/3592 +f 3338/3394/3394 3336/3396/3396 3337/3395/3395 +f 3335/3393/3393 3546/3590/3590 3545/3588/3588 +f 3335/3393/3393 3545/3588/3588 3334/3391/3391 +f 3336/3396/3396 3546/3590/3590 3335/3393/3393 +f 3342/3399/3399 3552/3594/3594 3341/3397/3397 +f 3554/3595/3595 3553/3596/3596 3342/3399/3399 +f 3554/3595/3595 3342/3399/3399 3343/3398/3398 +f 3553/3596/3596 3552/3594/3594 3342/3399/3399 +f 3340/3400/3400 3551/3597/3597 3550/3598/3598 +f 3340/3400/3400 3550/3598/3598 3339/3401/3401 +f 3341/3397/3397 3552/3594/3594 3340/3400/3400 +f 3552/3594/3594 3551/3597/3597 3340/3400/3400 +f 3344/3406/3406 3555/3599/3599 3556/3600/3600 +f 3347/3402/3402 3557/3601/3601 3346/3404/3404 +f 3559/3602/3602 3558/3603/3603 3347/3402/3402 +f 3559/3602/3602 3347/3402/3402 3348/3403/3403 +f 3558/3603/3603 3557/3601/3601 3347/3402/3402 +f 3345/3405/3405 3555/3599/3599 3344/3406/3406 +f 3346/3404/3404 3557/3601/3601 3345/3405/3405 +f 3557/3601/3601 3555/3599/3599 3345/3405/3405 +f 3562/3604/3604 3352/3408/3408 3563/3605/3605 +f 3351/3409/3409 3561/3606/3606 3350/3410/3410 +f 3352/3408/3408 3562/3604/3604 3351/3409/3409 +f 3562/3604/3604 3561/3606/3606 3351/3409/3409 +f 3349/3407/3407 3560/3607/3607 3559/3602/3602 +f 3349/3407/3407 3559/3602/3602 3348/3403/3403 +f 3350/3410/3410 3561/3606/3606 3349/3407/3407 +f 3561/3606/3606 3560/3607/3607 3349/3407/3407 +f 3356/3413/3413 3566/3608/3608 3355/3411/3411 +f 3568/3609/3609 3567/3610/3610 3356/3413/3413 +f 3568/3609/3609 3356/3413/3413 3357/3412/3412 +f 3567/3610/3610 3566/3608/3608 3356/3413/3413 +f 3354/3414/3414 3565/3611/3611 3564/3612/3612 +f 3354/3414/3414 3564/3612/3612 3353/3415/3415 +f 3355/3411/3411 3566/3608/3608 3354/3414/3414 +f 3566/3608/3608 3565/3611/3611 3354/3414/3414 +f 3569/3613/3613 3360/3418/3418 3570/3614/3614 +f 3360/3418/3418 3571/3615/3615 3570/3614/3614 +f 3572/3616/3616 3573/3417/3417 3574/3617/3617 +f 3571/3615/3615 3360/3418/3418 3572/3616/3616 +f 3573/3417/3417 3572/3616/3616 3361/3416/3416 +f 3572/3616/3616 3360/3418/3418 3361/3416/3416 +f 3359/3419/3419 3360/3418/3418 3569/3613/3613 +f 3359/3419/3419 3569/3613/3613 3358/3420/3420 +f 3573/3417/3417 3575/3618/3618 3574/3617/3617 +f 3365/3422/3422 3576/3619/3619 3364/3234/3234 +f 3578/3620/3620 3577/3621/3621 3365/3422/3422 +f 3578/3620/3620 3365/3422/3422 3366/3423/3423 +f 3577/3621/3621 3576/3619/3619 3365/3422/3422 +f 3363/3421/3421 3575/3618/3618 3573/3417/3417 +f 3364/3234/3234 3576/3619/3619 3363/3421/3421 +f 3576/3619/3619 3575/3618/3618 3363/3421/3421 +f 3579/3622/3622 3368/3427/3427 3580/3623/3623 +f 3581/3624/3624 3368/3427/3427 3582/3625/3625 +f 3580/3623/3623 3368/3427/3427 3581/3624/3624 +f 3582/3625/3625 3368/3427/3427 3369/3426/3426 +f 3582/3625/3625 3369/3426/3426 3370/3425/3425 +f 3367/3424/3424 3579/3622/3622 3578/3620/3620 +f 3367/3424/3424 3578/3620/3620 3366/3423/3423 +f 3368/3427/3427 3579/3622/3622 3367/3424/3424 +f 3374/3428/3428 3585/3626/3626 3373/3430/3430 +f 3587/3627/3627 3586/3628/3628 3374/3428/3428 +f 3587/3627/3627 3374/3428/3428 3375/3429/3429 +f 3586/3628/3628 3585/3626/3626 3374/3428/3428 +f 3372/3431/3431 3584/3629/3629 3583/3630/3630 +f 3372/3431/3431 3583/3630/3630 3371/3432/3432 +f 3373/3430/3430 3585/3626/3626 3372/3431/3431 +f 3585/3626/3626 3584/3629/3629 3372/3431/3431 +f 3376/3437/3437 3588/3631/3631 3589/3632/3632 +f 3591/3633/3633 3592/3434/3434 3593/3634/3634 +f 3379/3433/3433 3590/3635/3635 3378/3435/3435 +f 3592/3434/3434 3591/3633/3633 3379/3433/3433 +f 3591/3633/3633 3590/3635/3635 3379/3433/3433 +f 3377/3436/3436 3588/3631/3631 3376/3437/3437 +f 3378/3435/3435 3590/3635/3635 3377/3436/3436 +f 3590/3635/3635 3588/3631/3631 3377/3436/3436 +f 3592/3434/3434 3594/3636/3636 3593/3634/3634 +f 3594/3636/3636 3383/3440/3440 3595/3637/3637 +f 3596/3638/3638 3385/3442/3442 3597/3639/3639 +f 3595/3637/3637 3383/3440/3440 3596/3638/3638 +f 3385/3442/3442 3596/3638/3638 3384/3441/3441 +f 3596/3638/3638 3383/3440/3440 3384/3441/3441 +f 3381/3438/3438 3594/3636/3636 3592/3434/3434 +f 3383/3440/3440 3594/3636/3636 3381/3438/3438 +f 3389/3443/3443 3600/3640/3640 3388/3444/3444 +f 3602/3641/3641 3601/3642/3642 3389/3443/3443 +f 3602/3641/3641 3389/3443/3443 3390/3445/3445 +f 3601/3642/3642 3600/3640/3640 3389/3443/3443 +f 3387/3446/3446 3599/3643/3643 3598/3644/3644 +f 3387/3446/3446 3598/3644/3644 3386/3447/3447 +f 3388/3444/3444 3600/3640/3640 3387/3446/3446 +f 3600/3640/3640 3599/3643/3643 3387/3446/3446 +f 3603/3645/3645 3393/3449/3449 3604/3646/3646 +f 3393/3449/3449 3605/3647/3647 3604/3646/3646 +f 3605/3647/3647 3393/3449/3449 3606/3648/3648 +f 3607/3649/3649 3606/3648/3648 3394/3450/3450 +f 3607/3649/3649 3394/3450/3450 3395/3451/3451 +f 3606/3648/3648 3393/3449/3449 3394/3450/3450 +f 3392/3448/3448 3393/3449/3449 3603/3645/3645 +f 3392/3448/3448 3603/3645/3645 3391/3452/3452 +f 3398/3454/3454 3609/3650/3650 3397/3456/3456 +f 3611/3651/3651 3610/3652/3652 3398/3454/3454 +f 3611/3651/3651 3398/3454/3454 3399/3455/3455 +f 3610/3652/3652 3609/3650/3650 3398/3454/3454 +f 3396/3453/3453 3608/3653/3653 3607/3649/3649 +f 3396/3453/3453 3607/3649/3649 3395/3451/3451 +f 3397/3456/3456 3609/3650/3650 3396/3453/3453 +f 3609/3650/3650 3608/3653/3653 3396/3453/3453 +f 3612/3654/3654 3401/3460/3460 3613/3655/3655 +f 3614/3656/3656 3401/3460/3460 3615/3657/3657 +f 3613/3655/3655 3401/3460/3460 3614/3656/3656 +f 3615/3657/3657 3401/3460/3460 3402/3459/3459 +f 3615/3657/3657 3402/3459/3459 3403/3458/3458 +f 3400/3457/3457 3612/3654/3654 3611/3651/3651 +f 3400/3457/3457 3611/3651/3651 3399/3455/3455 +f 3401/3460/3460 3612/3654/3654 3400/3457/3457 +f 3407/3461/3461 3618/3658/3658 3406/3462/3462 +f 3620/3659/3659 3619/3660/3660 3407/3461/3461 +f 3620/3659/3659 3407/3461/3461 3408/3463/3463 +f 3619/3660/3660 3618/3658/3658 3407/3461/3461 +f 3405/3464/3464 3617/3661/3661 3616/3662/3662 +f 3405/3464/3464 3616/3662/3662 3404/3465/3465 +f 3406/3462/3462 3618/3658/3658 3405/3464/3464 +f 3618/3658/3658 3617/3661/3661 3405/3464/3464 +f 3621/3467/3467 3622/3663/3663 3623/3664/3664 +f 3625/3665/3665 3626/3469/3469 3627/3666/3666 +f 3412/3468/3468 3624/3667/3667 3411/3470/3470 +f 3626/3469/3469 3625/3665/3665 3412/3468/3468 +f 3625/3665/3665 3624/3667/3667 3412/3468/3468 +f 3410/3466/3466 3622/3663/3663 3621/3467/3467 +f 3411/3470/3470 3624/3667/3667 3410/3466/3466 +f 3624/3667/3667 3622/3663/3663 3410/3466/3466 +f 3626/3469/3469 3628/3668/3668 3627/3666/3666 +f 3630/3669/3669 3417/3472/3472 3631/3670/3670 +f 3416/3473/3473 3629/3671/3671 3415/3474/3474 +f 3417/3472/3472 3630/3669/3669 3416/3473/3473 +f 3630/3669/3669 3629/3671/3671 3416/3473/3473 +f 3414/3471/3471 3628/3668/3668 3626/3469/3469 +f 3415/3474/3474 3629/3671/3671 3414/3471/3471 +f 3629/3671/3671 3628/3668/3668 3414/3471/3471 +f 3633/3672/3672 3635/3673/3673 3632/3674/3674 +f 3635/3673/3673 3636/3675/3675 3637/3676/3676 +f 3635/3673/3673 3633/3672/3672 3636/3675/3675 +f 3635/3673/3673 454/501/495 440/486/480 +f 454/501/495 448/494/488 440/486/480 +f 3638/3677/3677 454/501/495 3635/3673/3673 +f 3638/3677/3677 3635/3673/3673 3637/3676/3676 +f 453/499/493 3639/3678/3678 540/594/588 +f 542/502/496 3639/3678/3678 453/499/493 +f 3638/3677/3677 542/502/496 454/501/495 +f 3640/3679/3679 3641/3680/3680 3642/3681/3681 +f 3640/3679/3679 3643/3682/3682 3641/3680/3680 +f 3644/3683/3683 3643/3682/3682 3640/3679/3679 +f 3645/3684/3684 3646/3685/3685 3647/3686/3686 +f 3641/3680/3680 3643/3682/3682 3645/3684/3684 +f 3641/3680/3680 3645/3684/3684 503/555/549 +f 3643/3682/3682 3646/3685/3685 3645/3684/3684 +f 3646/3685/3685 3643/3682/3682 3644/3683/3683 +f 3646/3685/3685 3644/3683/3683 3648/3687/3687 +f 3647/3686/3686 3646/3685/3685 3648/3687/3687 +f 3645/3684/3684 501/530/524 500/553/547 +f 3645/3684/3684 500/553/547 503/555/549 +f 3647/3686/3686 3650/3688/3688 501/530/524 +f 3647/3686/3686 501/530/524 3645/3684/3684 +f 3650/3688/3688 3649/528/522 501/530/524 +f 3649/528/522 3650/3688/3688 479/529/523 +f 479/529/523 3650/3688/3688 480/531/525 +f 445/491/485 480/531/525 3648/3687/3687 +f 445/491/485 3648/3687/3687 3634/3689/3689 +f 480/531/525 3650/3688/3688 3647/3686/3686 +f 480/531/525 3647/3686/3686 3648/3687/3687 +f 444/490/484 3635/3673/3673 439/485/479 +f 439/485/479 3635/3673/3673 440/486/480 +f 3632/3674/3674 445/491/485 3634/3689/3689 +f 3632/3674/3674 444/490/484 445/491/485 +f 3635/3673/3673 444/490/484 3632/3674/3674 +f 3651/3690/3690 527/581/575 528/582/576 +f 3651/3690/3690 3653/3691/3691 527/581/575 +f 527/581/575 3653/3691/3691 3654/580/574 +f 3655/579/573 3654/580/574 3642/3681/3681 +f 504/556/550 526/577/571 3641/3680/3680 +f 504/556/550 3641/3680/3680 503/555/549 +f 3641/3680/3680 3655/579/573 3642/3681/3681 +f 3641/3680/3680 526/577/571 3655/579/573 +f 3658/3692/3692 3659/3693/3693 3656/3694/3694 +f 528/582/576 3659/3693/3693 3651/3690/3690 +f 3656/3694/3694 3659/3693/3693 528/582/576 +f 3651/3690/3690 3659/3693/3693 3652/3695/3695 +f 3656/3694/3694 3660/3696/3696 3661/3697/3697 +f 3656/3694/3694 3661/3697/3697 3657/3698/3698 +f 3662/3699/3699 3663/576/570 3675/3700/3700 +f 3662/3699/3699 3675/3700/3700 3664/3701/3701 +f 3660/3696/3696 3663/576/570 3662/3699/3699 +f 3663/576/570 3660/3696/3696 528/582/576 +f 528/582/576 3660/3696/3696 3656/3694/3694 +f 3666/3702/3702 3668/3703/3703 3665/3704/3704 +f 3669/3705/3705 3666/3702/3702 3665/3704/3704 +f 3670/3706/3706 469/518/512 427/475/469 +f 3670/3706/3706 3671/3707/3707 3672/517/511 +f 3670/3706/3706 3672/517/511 469/518/512 +f 3668/3703/3703 3666/3702/3702 3670/3706/3706 +f 3666/3702/3702 3669/3705/3705 3671/3707/3707 +f 3666/3702/3702 3671/3707/3707 3670/3706/3706 +f 3671/3707/3707 3669/3705/3705 3673/3708/3708 +f 3672/517/511 3671/3707/3707 3673/3708/3708 +f 3673/3708/3708 470/519/513 3672/517/511 +f 3674/545/539 470/519/513 3673/3708/3708 +f 3674/545/539 3673/3708/3708 3664/3701/3701 +f 470/519/513 3674/545/539 467/515/509 +f 3674/545/539 3675/3700/3700 524/546/540 +f 3675/3700/3700 3674/545/539 3664/3701/3701 +f 524/546/540 3675/3700/3700 3663/576/570 +f 3676/419/413 3677/3709/3709 3667/3710/3710 +f 3676/419/413 3678/3711/3711 3677/3709/3709 +f 3679/3712/3712 328/367/361 3680/3713/3713 +f 328/367/361 3679/3712/3712 349/390/384 +f 3679/3712/3712 3678/3711/3711 349/390/384 +f 349/390/384 3676/419/413 350/392/386 +f 349/390/384 3678/3711/3711 3676/419/413 +f 373/417/411 3676/419/413 376/420/414 +f 376/420/414 3676/419/413 3667/3710/3710 +f 3665/3704/3704 398/444/438 376/420/414 +f 3665/3704/3704 376/420/414 3667/3710/3710 +f 3668/3703/3703 398/444/438 3665/3704/3704 +f 376/420/414 398/444/438 394/441/435 +f 398/444/438 429/476/470 395/442/436 +f 425/473/467 429/476/470 3670/3706/3706 +f 425/473/467 3670/3706/3706 427/475/469 +f 429/476/470 3668/3703/3703 3670/3706/3706 +f 429/476/470 398/444/438 3668/3703/3703 +f 328/367/361 3684/3714/3714 3680/3713/3713 +f 328/367/361 3682/3715/3715 3684/3714/3714 +f 3685/343/337 3686/3716/3716 3683/3717/3717 +f 3688/3718/3718 3698/3719/3719 3689/3720/3720 +f 3688/3718/3718 3690/3721/3721 3691/3722/3722 +f 3688/3718/3718 3691/3722/3722 3698/3719/3719 +f 3687/3723/3723 3690/3721/3721 3688/3718/3718 +f 3691/3722/3722 3690/3721/3721 259/300/293 +f 3691/3722/3722 259/300/293 196/236/229 +f 3690/3721/3721 3687/3723/3723 259/300/293 +f 258/301/294 3686/3716/3716 3685/343/337 +f 259/300/293 3687/3723/3723 3686/3716/3716 +f 259/300/293 3686/3716/3716 258/301/294 +f 3685/343/337 304/346/340 302/342/336 +f 304/346/340 3692/3724/3724 303/345/339 +f 3692/3724/3724 3685/343/337 3683/3717/3717 +f 3692/3724/3724 304/346/340 3685/343/337 +f 3681/3725/3725 3682/3715/3715 317/358/352 +f 3681/3725/3725 317/358/352 303/345/339 +f 317/358/352 3682/3715/3715 329/369/363 +f 329/369/363 3682/3715/3715 328/367/361 +f 3681/3725/3725 3692/3724/3724 3683/3717/3717 +f 3681/3725/3725 303/345/339 3692/3724/3724 +f 3695/3726/3726 136/174/167 3693/3727/3727 +f 3696/3728/3728 136/174/167 3697/3729/3729 +f 3696/3728/3728 3697/3729/3729 3689/3720/3720 +f 136/174/167 3695/3726/3726 3697/3729/3729 +f 3696/3728/3728 134/171/164 136/174/167 +f 134/171/164 195/233/226 132/169/162 +f 195/233/226 134/171/164 3696/3728/3728 +f 195/233/226 3698/3719/3719 197/235/228 +f 3696/3728/3728 3698/3719/3719 195/233/226 +f 196/236/229 197/235/228 3691/3722/3722 +f 197/235/228 3698/3719/3719 3691/3722/3722 +f 3698/3719/3719 3696/3728/3728 3689/3720/3720 +f 3699/3730/3730 3700/3731/3731 3701/3732/3732 +f 3699/3730/3730 3701/3732/3732 3702/3733/3733 +f 3703/3734/3734 3700/3731/3731 3699/3730/3730 +f 135/173/166 3693/3727/3727 136/174/167 +f 135/173/166 3700/3731/3731 3693/3727/3727 +f 3693/3727/3727 3704/3735/3735 3694/3736/3736 +f 3693/3727/3727 3700/3731/3731 3703/3734/3734 +f 3693/3727/3727 3703/3734/3734 3704/3735/3735 +f 3701/3732/3732 3706/3737/3737 3705/3738/3738 +f 3700/3731/3731 3706/3737/3737 3701/3732/3732 +f 3706/3737/3737 3707/3739/3739 3708/3740/3740 +f 3707/3739/3739 3706/3737/3737 133/170/163 +f 3706/3737/3737 135/173/166 133/170/163 +f 135/173/166 3706/3737/3737 3700/3731/3731 +f 3709/3741/3741 3710/16/3742 3711/15/3743 +f 3709/3741/3741 3712/3742/3744 87/124/116 +f 3709/3741/3741 87/124/116 3710/16/3742 +f 3713/3743/3745 3714/3744/3746 3709/3741/3741 +f 3714/3744/3746 3712/3742/3744 3709/3741/3741 +f 83/120/112 3712/3742/3744 3715/3745/3747 +f 3712/3742/3744 3714/3744/3746 3716/3746/3748 +f 3712/3742/3744 3716/3746/3748 3715/3745/3747 +f 87/124/116 3712/3742/3744 83/120/112 +f 3716/3746/3748 3714/3744/3746 3713/3743/3745 +f 3716/3746/3748 3713/3743/3745 3717/3747/3749 +f 3718/3748/3750 3719/178/171 3717/3747/3749 +f 3718/3748/3750 3717/3747/3749 3708/3740/3740 +f 3719/178/171 3716/3746/3748 3717/3747/3749 +f 3715/3745/3747 80/118/110 83/120/112 +f 3715/3745/3747 143/182/175 80/118/110 +f 3716/3746/3748 143/182/175 3715/3745/3747 +f 143/182/175 3716/3746/3748 144/181/174 +f 144/181/174 3716/3746/3748 3719/178/171 +f 3718/3748/3750 138/172/165 3719/178/171 +f 138/172/165 3707/3739/3739 133/170/163 +f 138/172/165 3718/3748/3750 3707/3739/3739 +f 3707/3739/3739 3718/3748/3750 3708/3740/3740 +f 94/130/123 3711/15/3743 90/14/119 +f 90/14/119 3710/16/3742 88/125/117 +f 3710/16/3742 87/124/116 88/125/117 +f 81/117/109 87/124/116 83/120/112 +f 3722/3749/3751 91/127/120 3720/3750/3752 +f 91/127/120 3722/3749/3751 94/130/123 +f 3726/3751/3753 3723/3752/3754 3721/3753/3755 +f 3726/3751/3753 3724/105/97 3725/3754/3756 +f 3726/3751/3753 3725/3754/3756 3723/3752/3754 +f 3727/3755/3757 3728/103/95 3729/3756/3758 +f 3727/3755/3757 3724/105/97 3728/103/95 +f 3725/3754/3756 3724/105/97 3727/3755/3757 +f 3728/103/95 3724/105/97 68/100/92 +f 3724/105/97 3726/3751/3753 69/104/96 +f 69/104/96 3726/3751/3753 91/127/120 +f 3720/3750/3752 3726/3751/3753 3721/3753/3755 +f 91/127/120 3726/3751/3753 3720/3750/3752 +f 3730/3757/3759 3732/3758/3760 3740/3759/3761 +f 3733/3760/3762 3732/3758/3760 3730/3757/3759 +f 19/49/41 3734/62/54 21/50/42 +f 3734/62/54 3735/3761/3763 3736/3762/3764 +f 3740/3759/3761 3732/3758/3760 3734/62/54 +f 3740/3759/3761 3734/62/54 19/49/41 +f 3732/3758/3760 3735/3761/3763 3734/62/54 +f 3739/3763/3765 3735/3761/3763 3737/3764/3766 +f 3739/3763/3765 3737/3764/3766 3729/3756/3758 +f 3735/3761/3763 3732/3758/3760 3733/3760/3762 +f 3735/3761/3763 3733/3760/3762 3737/3764/3766 +f 3736/3762/3764 3735/3761/3763 3739/3763/3765 +f 3736/3762/3764 33/61/53 3734/62/54 +f 33/61/53 3738/3765/3767 45/74/66 +f 33/61/53 3736/3762/3764 3738/3765/3767 +f 3739/3763/3765 3738/3765/3767 3736/3762/3764 +f 3738/3765/3767 44/73/65 45/74/66 +f 3739/3763/3765 54/85/77 44/73/65 +f 3739/3763/3765 44/73/65 3738/3765/3767 +f 3728/103/95 54/85/77 3729/3756/3758 +f 54/85/77 3739/3763/3765 3729/3756/3758 +f 22/43/35 3740/3759/3761 18/48/40 +f 3741/3766/3768 3740/3759/3761 22/43/35 +f 18/48/40 3740/3759/3761 19/49/41 +f 3730/3757/3759 3741/3766/3768 3731/3767/3769 +f 3740/3759/3761 3741/3766/3768 3730/3757/3759 +f 3744/3768/3770 3743/3769/3771 3742/3770/3772 +f 3743/3769/3771 3744/3768/3770 1/30/22 +f 3746/3771/3773 3744/3768/3770 3745/3772/3774 +f 3746/3771/3773 3745/3772/3774 3731/3767/3769 +f 3744/3768/3770 6/36/28 1/30/22 +f 6/36/28 10/39/31 11/38/30 +f 6/36/28 3746/3771/3773 10/39/31 +f 3746/3771/3773 3741/3766/3768 22/43/35 +f 10/39/31 3746/3771/3773 22/43/35 +f 3741/3766/3768 3746/3771/3773 3731/3767/3769 +f 3746/3771/3773 6/36/28 3744/3768/3770 +f 3743/3769/3771 3747/3773/3775 3748/3774/3776 +f 3750/3775/3777 1/30/22 3751/3776/3778 +f 3747/3773/3775 3750/3775/3777 3749/3777/3779 +f 3747/3773/3775 1/30/22 3750/3775/3777 +f 3751/3776/3778 1/30/22 3752/32/24 +f 1/30/22 3747/3773/3775 3743/3769/3771 +f 3752/32/24 3755/3778/3780 3753/3779/3781 +f 3752/32/24 3754/3780/3782 3751/3776/3778 +f 3755/3778/3780 2/31/23 3/37/29 +f 2/31/23 3755/3778/3780 3752/32/24 +f 3760/3781/3783 3759/3782/3784 3757/3783/3785 +f 3760/3781/3783 3757/3783/3785 3756/3784/3786 +f 3761/3785/3787 30/60/52 29/57/49 +f 3761/3785/3787 3762/3786/3788 3763/3787/3789 +f 3761/3785/3787 3763/3787/3789 30/60/52 +f 3757/3783/3785 3759/3782/3784 3761/3785/3787 +f 3759/3782/3784 3762/3786/3788 3761/3785/3787 +f 3763/3787/3789 3764/3788/3790 3753/3779/3781 +f 3764/3788/3790 3759/3782/3784 3760/3781/3783 +f 3763/3787/3789 3762/3786/3788 3764/3788/3790 +f 3762/3786/3788 3759/3782/3784 3764/3788/3790 +f 3755/3778/3780 3765/3789/3791 3763/3787/3789 +f 3755/3778/3780 3763/3787/3789 3753/3779/3781 +f 16/44/36 3765/3789/3791 15/45/37 +f 30/60/52 3763/3787/3789 16/44/36 +f 3763/3787/3789 3765/3789/3791 16/44/36 +f 15/45/37 3765/3789/3791 3/37/29 +f 3765/3789/3791 3755/3778/3780 3/37/29 +f 3767/3790/3792 3768/3791/3793 3758/3792/3794 +f 3767/3790/3792 3766/98/90 3768/3791/3793 +f 3766/98/90 3769/3793/3795 3768/3791/3793 +f 3769/3793/3795 3766/98/90 114/146/139 +f 111/116/108 3766/98/90 78/114/106 +f 114/146/139 3766/98/90 111/116/108 +f 3766/98/90 3767/3790/3792 65/97/89 +f 3767/3790/3792 52/84/76 65/97/89 +f 52/84/76 3767/3790/3792 3758/3792/3794 +f 3756/3784/3786 3770/83/75 52/84/76 +f 3756/3784/3786 52/84/76 3758/3792/3794 +f 3757/3783/3785 3771/81/73 3770/83/75 +f 3757/3783/3785 3770/83/75 3756/3784/3786 +f 40/69/61 3772/3794/3796 29/57/49 +f 40/69/61 29/57/49 28/59/51 +f 3771/81/73 3772/3794/3796 40/69/61 +f 29/57/49 3772/3794/3796 3761/3785/3787 +f 3772/3794/3796 3771/81/73 3757/3783/3785 +f 3772/3794/3796 3757/3783/3785 3761/3785/3787 +f 3773/3795/3797 3774/152/145 3775/3796/3798 +f 3773/3795/3797 3769/3793/3795 3774/152/145 +f 3774/152/145 3769/3793/3795 114/146/139 +f 3774/152/145 3776/3797/3799 3775/3796/3798 +f 3774/152/145 3777/3798/3800 3776/3797/3799 +f 3778/3799/3801 117/151/144 120/155/148 +f 3778/3799/3801 120/155/148 3779/3800/3802 +f 3777/3798/3800 117/151/144 3778/3799/3801 +f 117/151/144 3777/3798/3800 3774/152/145 +f 3781/3801/3803 3783/3802/3804 3784/3803/3805 +f 3781/3801/3803 3784/3803/3805 3780/3804/3806 +f 3785/3805/3807 3783/3802/3804 3781/3801/3803 +f 3785/3805/3807 3781/3801/3803 3780/3804/3806 +f 131/167/160 3786/3806/3808 126/162/155 +f 131/167/160 126/162/155 123/157/150 +f 3786/3806/3808 125/161/154 126/162/155 +f 3784/3803/3805 3783/3802/3804 3786/3806/3808 +f 3784/3803/3805 3786/3806/3808 131/167/160 +f 125/161/154 3787/3807/3809 3779/3800/3802 +f 3787/3807/3809 3783/3802/3804 3785/3805/3807 +f 125/161/154 3786/3806/3808 3787/3807/3809 +f 3786/3806/3808 3783/3802/3804 3787/3807/3809 +f 125/161/154 3788/156/149 118/153/146 +f 3788/156/149 125/161/154 3779/3800/3802 +f 120/155/148 3788/156/149 3779/3800/3802 +f 191/231/224 3789/3808/3810 189/226/219 +f 3790/3809/3811 3789/3808/3810 191/231/224 +f 189/226/219 3789/3808/3810 3782/3810/3812 +f 189/226/219 3782/3810/3812 184/222/215 +f 3782/3810/3812 3791/3811/3813 184/222/215 +f 3791/3811/3813 185/223/216 184/222/215 +f 128/164/157 3784/3803/3805 131/167/160 +f 129/166/159 185/223/216 3784/3803/3805 +f 129/166/159 3784/3803/3805 128/164/157 +f 3780/3804/3806 3791/3811/3813 3782/3810/3812 +f 3784/3803/3805 3791/3811/3813 3780/3804/3806 +f 3784/3803/3805 185/223/216 3791/3811/3813 +f 3793/3812/3814 3792/3813/3815 192/229/222 +f 3792/3813/3815 3794/3814/3816 191/231/224 +f 3792/3813/3815 191/231/224 192/229/222 +f 3789/3808/3810 3790/3809/3811 3782/3810/3812 +f 191/231/224 3794/3814/3816 3790/3809/3811 +f 3793/3812/3814 192/229/222 3795/3815/3817 +f 192/229/222 3796/3816/3818 3795/3815/3817 +f 3796/3816/3818 3798/3817/3819 3797/3818/3820 +f 3801/230/223 3798/3817/3819 192/229/222 +f 3798/3817/3819 3796/3816/3818 192/229/222 +f 3798/3817/3819 3801/230/223 3802/3819/3821 +f 3805/3820/3822 3804/292/285 3803/3821/3823 +f 3805/3820/3822 3803/3821/3823 3800/3822/3824 +f 3807/3823/3825 3806/3824/3826 253/295/288 +f 3806/3824/3826 3804/292/285 253/295/288 +f 3805/3820/3822 249/232/225 3804/292/285 +f 249/232/225 3805/3820/3822 187/228/221 +f 3805/3820/3822 3800/3822/3824 187/228/221 +f 187/228/221 3800/3822/3824 3801/230/223 +f 3801/230/223 3800/3822/3824 3799/3825/3827 +f 297/337/331 3809/3826/3828 3808/3827/3829 +f 3809/3826/3828 297/337/331 3810/3828/3830 +f 3810/3828/3830 297/337/331 298/338/332 +f 3811/3829/3831 3810/3828/3830 298/338/332 +f 297/337/331 3808/3827/3829 255/297/290 +f 255/297/290 3807/3823/3825 254/296/289 +f 3808/3827/3829 3807/3823/3825 255/297/290 +f 254/296/289 3807/3823/3825 253/295/288 +f 3812/3830/3832 3813/3831/3833 3814/3832/3834 +f 295/335/329 3813/3831/3833 3812/3830/3832 +f 295/335/329 299/339/333 294/333/327 +f 299/339/333 3811/3829/3831 298/338/332 +f 3815/3833/3835 3816/3834/3836 3817/3835/3837 +f 3815/3833/3835 3817/3835/3837 3818/3836/3838 +f 3816/3834/3836 3819/3837/3839 3820/3838/3840 +f 3816/3834/3836 3820/3838/3840 3817/3835/3837 +f 3821/3839/3841 3822/3840/3842 3823/328/322 +f 3821/3839/3841 3823/328/322 281/320/314 +f 3820/3838/3840 3819/3837/3839 3821/3839/3841 +f 3819/3837/3839 3822/3840/3842 3821/3839/3841 +f 3824/3841/3843 3825/3842/3844 3826/3843/3845 +f 3822/3840/3842 3819/3837/3839 3816/3834/3836 +f 3823/328/322 3827/3844/3846 287/326/320 +f 287/326/320 3827/3844/3846 3828/327/321 +f 3829/3845/3847 3830/3846/3848 3824/3841/3843 +f 3829/3845/3847 3824/3841/3843 3826/3843/3845 +f 293/334/328 3813/3831/3833 295/335/329 +f 293/334/328 296/336/330 3813/3831/3833 +f 3813/3831/3833 3831/3847/3849 3832/3848/3850 +f 3813/3831/3833 3832/3848/3850 3814/3832/3834 +f 7/33/25 9/35/27 3818/3836/3838 +f 274/18/3851 3833/17/3852 8/34/26 +f 274/18/3851 273/255/248 272/313/306 +f 3835/3849/3853 274/18/3851 8/34/26 +f 273/255/248 215/257/250 213/254/247 +f 274/18/3851 3835/3849/3853 215/257/250 +f 274/18/3851 215/257/250 273/255/248 +f 215/257/250 3835/3849/3853 218/260/253 +f 220/262/255 3835/3849/3853 7/33/25 +f 218/260/253 3835/3849/3853 220/262/255 +f 219/261/254 220/262/255 221/269/262 +f 221/269/262 7/33/25 3818/3836/3838 +f 221/269/262 220/262/255 7/33/25 +f 3817/3835/3837 221/269/262 3818/3836/3838 +f 3817/3835/3837 227/268/261 221/269/262 +f 3820/3838/3840 3836/3850/3854 3817/3835/3837 +f 3836/3850/3854 227/268/261 3817/3835/3837 +f 226/267/260 3836/3850/3854 285/323/317 +f 227/268/261 3836/3850/3854 226/267/260 +f 283/321/315 285/323/317 3821/3839/3841 +f 283/321/315 3821/3839/3841 281/320/314 +f 285/323/317 3836/3850/3854 3820/3838/3840 +f 285/323/317 3820/3838/3840 3821/3839/3841 +f 3844/23/308 3837/20/3855 272/313/306 +f 272/313/306 3837/20/3855 274/18/3851 +f 3844/23/308 316/356/350 3850/26/3856 +f 316/356/350 3844/23/308 276/314/307 +f 3846/3851/3857 3847/3852/3858 3848/380/374 +f 3846/3851/3857 3848/380/374 3849/3853/3859 +f 3847/3852/3858 3850/26/3856 3851/3854/3860 +f 3847/3852/3858 3851/3854/3860 3848/380/374 +f 323/357/351 3850/26/3856 316/356/350 +f 3851/3854/3860 3850/26/3856 323/357/351 +f 3850/26/3856 3852/3855/3861 3842/27/3862 +f 3850/26/3856 3847/3852/3858 3852/3855/3861 +f 3848/380/374 3859/3856/3863 3849/3853/3859 +f 3848/380/374 3851/3854/3860 336/378/372 +f 336/378/372 3851/3854/3860 325/365/359 +f 3851/3854/3860 323/357/351 325/365/359 +f 3853/3857/3864 3854/3858/3865 3855/3859/3866 +f 3853/3857/3864 3855/3859/3866 3856/3860/3867 +f 3854/3858/3865 372/416/410 3855/3859/3866 +f 3857/3861/3868 3854/3858/3865 3853/3857/3864 +f 372/416/410 345/388/382 346/409/403 +f 372/416/410 3854/3858/3865 345/388/382 +f 3854/3858/3865 3857/3861/3868 344/387/381 +f 3854/3858/3865 344/387/381 345/388/382 +f 344/387/381 3857/3861/3868 347/389/383 +f 341/384/378 3858/3862/3869 3849/3853/3859 +f 341/384/378 347/389/383 3858/3862/3869 +f 347/389/383 3857/3861/3868 3858/3862/3869 +f 339/382/376 3859/3856/3863 338/377/371 +f 341/384/378 3859/3856/3863 339/382/376 +f 338/377/371 3859/3856/3863 3848/380/374 +f 3859/3856/3863 341/384/378 3849/3853/3859 +f 3860/3863/3870 393/437/431 424/466/460 +f 393/437/431 3860/3863/3870 3861/3864/3871 +f 393/437/431 3861/3864/3871 371/415/409 +f 3861/3864/3871 370/414/408 371/415/409 +f 369/411/405 370/414/408 372/416/410 +f 3855/3859/3866 3861/3864/3871 3856/3860/3867 +f 3855/3859/3866 370/414/408 3861/3864/3871 +f 372/416/410 370/414/408 3855/3859/3866 +f 3863/471/465 3862/3865/3872 423/470/464 +f 3862/3865/3872 3864/3866/3873 423/470/464 +f 3864/3866/3873 424/466/460 423/470/464 +f 3861/3864/3871 3865/3867/3874 3856/3860/3867 +f 3865/3867/3874 424/466/460 3864/3866/3873 +f 3860/3863/3870 424/466/460 3865/3867/3874 +f 3860/3863/3870 3865/3867/3874 3861/3864/3871 +f 3866/3868/3875 3863/471/465 421/467/461 +f 3868/3869/3876 3869/513/507 3870/3870/3877 +f 3868/3869/3876 464/511/505 3869/513/507 +f 3867/3871/3878 464/511/505 3868/3869/3876 +f 422/469/463 3866/3868/3875 421/467/461 +f 464/511/505 3867/3871/3878 3866/3868/3875 +f 464/511/505 3866/3868/3875 422/469/463 +f 490/542/536 3869/513/507 465/512/506 +f 3869/513/507 3871/3872/3879 3870/3870/3877 +f 490/542/536 519/569/563 3871/3872/3879 +f 490/542/536 3871/3872/3879 3869/513/507 +f 519/569/563 3872/3873/3880 3871/3872/3879 +f 3873/3874/3881 518/570/564 3875/586/580 +f 3873/3874/3881 3875/586/580 3874/3875/3882 +f 3872/3873/3880 518/570/564 3873/3874/3881 +f 518/570/564 3872/3873/3880 519/569/563 +f 3878/3876/3883 3879/3877/3884 3877/3878/3885 +f 3878/3876/3883 3877/3878/3885 3876/3879/3886 +f 3877/3878/3885 3879/3877/3884 536/590/584 +f 536/590/584 3879/3877/3884 532/585/579 +f 3879/3877/3884 3880/3880/3887 3874/3875/3882 +f 3879/3877/3884 3878/3876/3883 3880/3880/3887 +f 3879/3877/3884 3875/586/580 532/585/579 +f 3875/586/580 3879/3877/3884 3874/3875/3882 +f 3883/3881/3888 3882/3882/3889 3881/3883/3890 +f 536/590/584 3883/3881/3888 3877/3878/3885 +f 3882/3882/3889 3883/3881/3888 536/590/584 +f 3877/3878/3885 3883/3881/3888 3884/3884/3891 +f 3882/3882/3889 3885/3885/3892 3886/3886/3893 +f 3887/3887/3894 544/591/585 546/597/591 +f 3887/3887/3894 3885/3885/3892 536/590/584 +f 3887/3887/3894 536/590/584 544/591/585 +f 536/590/584 3885/3885/3892 3882/3882/3889 +f 3888/3888/3895 539/592/586 538/593/587 +f 3888/3888/3895 538/593/587 540/594/588 +f 3889/3889/3896 547/598/592 539/592/586 +f 3889/3889/3896 539/592/586 3888/3888/3895 +f 511/562/556 515/566/560 509/560/554 +f 511/562/556 545/596/590 515/566/560 +f 539/592/586 547/598/592 511/562/556 +f 547/598/592 545/596/590 511/562/556 +f 543/595/589 548/599/593 546/597/591 +f 548/599/593 547/598/592 3889/3889/3896 +f 2672/2745/2745 2669/2742/2742 733/795/789 +f 2672/2745/2745 733/795/789 730/791/785 +f 2669/2742/2742 2674/2739/2739 737/798/792 +f 2669/2742/2742 737/798/792 733/795/789 +f 2674/2739/2739 2665/2738/2738 724/786/780 +f 2674/2739/2739 724/786/780 737/798/792 +f 2665/2738/2738 2664/2737/2737 723/787/781 +f 2665/2738/2738 723/787/781 724/786/780 +f 2664/2737/2737 2662/2732/2732 725/784/778 +f 2664/2737/2737 725/784/778 723/787/781 +f 2662/2732/2732 2666/2734/2734 716/778/772 +f 2662/2732/2732 716/778/772 725/784/778 +f 2666/2734/2734 2658/2729/2729 716/778/772 +f 2658/2729/2729 2657/2728/2728 715/779/773 +f 2658/2729/2729 715/779/773 716/778/772 +f 2657/2728/2728 2655/2724/2724 717/776/770 +f 2657/2728/2728 717/776/770 715/779/773 +f 2655/2724/2724 2659/2726/2726 722/782/776 +f 2655/2724/2724 722/782/776 717/776/770 +f 2659/2726/2726 2652/2722/2722 708/766/760 +f 2659/2726/2726 708/766/760 722/782/776 +f 2652/2722/2722 2651/2720/2720 707/771/765 +f 2652/2722/2722 707/771/765 708/766/760 +f 2651/2720/2720 2647/2715/2715 709/769/763 +f 2651/2720/2720 709/769/763 707/771/765 +f 2647/2715/2715 2653/2717/2717 714/773/767 +f 2647/2715/2715 714/773/767 709/769/763 +f 2653/2717/2717 2644/2713/2713 699/760/754 +f 2653/2717/2717 699/760/754 714/773/767 +f 2644/2713/2713 2643/2711/2711 699/760/754 +f 2643/2711/2711 2639/2707/2707 701/758/752 +f 2643/2711/2711 701/758/752 699/760/754 +f 2639/2707/2707 2645/2709/2709 706/764/758 +f 2639/2707/2707 706/764/758 701/758/752 +f 2645/2709/2709 2635/2703/2703 692/753/747 +f 2645/2709/2709 692/753/747 706/764/758 +f 2635/2703/2703 2636/2705/2705 691/754/748 +f 2635/2703/2703 691/754/748 692/753/747 +f 2636/2705/2705 2633/2699/2699 693/751/745 +f 2636/2705/2705 693/751/745 691/754/748 +f 2633/2699/2699 2637/2701/2701 698/757/751 +f 2633/2699/2699 698/757/751 693/751/745 +f 2637/2701/2701 2630/2697/2697 685/742/736 +f 2637/2701/2701 685/742/736 698/757/751 +f 2630/2697/2697 2629/2695/2695 684/745/739 +f 2630/2697/2697 684/745/739 685/742/736 +f 2629/2695/2695 2626/2691/2691 686/746/740 +f 2629/2695/2695 686/746/740 684/745/739 +f 2626/2691/2691 2631/2693/2693 690/749/743 +f 2626/2691/2691 690/749/743 686/746/740 +f 2631/2693/2693 2624/2688/2688 678/735/729 +f 2631/2693/2693 678/735/729 690/749/743 +f 2624/2688/2688 2623/2687/2687 677/733/727 +f 2624/2688/2688 677/733/727 678/735/729 +f 2623/2687/2687 2620/2683/2683 680/737/731 +f 2623/2687/2687 680/737/731 677/733/727 +f 2620/2683/2683 2625/2685/2685 683/740/734 +f 2620/2683/2683 683/740/734 680/737/731 +f 2625/2685/2685 2619/2679/2679 669/727/721 +f 2625/2685/2685 669/727/721 683/740/734 +f 2619/2679/2679 2617/2678/2678 668/728/722 +f 2619/2679/2679 668/728/722 669/727/721 +f 2617/2678/2678 2613/2673/2673 670/725/719 +f 2617/2678/2678 670/725/719 668/728/722 +f 2613/2673/2673 2618/2676/2676 676/731/725 +f 2613/2673/2673 676/731/725 670/725/719 +f 2618/2676/2676 2608/2669/2669 661/719/713 +f 2618/2676/2676 661/719/713 676/731/725 +f 2608/2669/2669 2607/2668/2668 660/720/714 +f 2608/2669/2669 660/720/714 661/719/713 +f 2607/2668/2668 2609/2671/2671 662/717/711 +f 2607/2668/2668 662/717/711 660/720/714 +f 2609/2671/2671 2610/2664/2664 667/723/717 +f 2609/2671/2671 667/723/717 662/717/711 +f 2610/2664/2664 2601/2663/2663 653/709/703 +f 2610/2664/2664 653/709/703 667/723/717 +f 2601/2663/2663 2600/2661/2661 652/707/701 +f 2601/2663/2663 652/707/701 653/709/703 +f 2600/2661/2661 2596/2656/2656 655/711/705 +f 2600/2661/2661 655/711/705 652/707/701 +f 2596/2656/2656 2602/2658/2658 659/714/708 +f 2596/2656/2656 659/714/708 655/711/705 +f 2602/2658/2658 2595/2653/2653 645/700/694 +f 2602/2658/2658 645/700/694 659/714/708 +f 2595/2653/2653 2594/2652/2652 643/698/692 +f 2595/2653/2653 643/698/692 645/700/694 +f 2594/2652/2652 2591/2648/2648 646/702/696 +f 2594/2652/2652 646/702/696 643/698/692 +f 2591/2648/2648 651/705/699 646/702/696 +f 2591/2648/2648 2587/2645/2645 636/693/687 +f 2591/2648/2648 636/693/687 651/705/699 +f 2587/2645/2645 2586/2644/2644 635/694/688 +f 2587/2645/2645 635/694/688 636/693/687 +f 2586/2644/2644 2583/2640/2640 637/691/685 +f 2586/2644/2644 637/691/685 635/694/688 +f 2583/2640/2640 2588/2642/2642 642/697/691 +f 2583/2640/2640 642/697/691 637/691/685 +f 2588/2642/2642 2579/2636/2636 630/689/683 +f 2588/2642/2642 630/689/683 642/697/691 +f 2579/2636/2636 2580/2638/2638 630/689/683 +f 2580/2638/2638 2578/2632/2632 632/686/680 +f 2580/2638/2638 632/686/680 630/689/683 +f 2578/2632/2632 2581/2634/2634 624/679/673 +f 2578/2632/2632 624/679/673 632/686/680 +f 2581/2634/2634 2574/2628/2628 624/679/673 +f 2574/2628/2628 2575/2630/2630 623/677/671 +f 2574/2628/2628 623/677/671 624/679/673 +f 2575/2630/2630 2571/2624/2624 626/681/675 +f 2575/2630/2630 626/681/675 623/677/671 +f 2571/2624/2624 2576/2626/2626 629/684/678 +f 2571/2624/2624 629/684/678 626/681/675 +f 2576/2626/2626 2569/2622/2622 616/670/664 +f 2576/2626/2626 616/670/664 629/684/678 +f 2569/2622/2622 2568/2620/2620 615/668/662 +f 2569/2622/2622 615/668/662 616/670/664 +f 2568/2620/2620 2565/2617/2617 618/672/666 +f 2568/2620/2620 618/672/666 615/668/662 +f 2565/2617/2617 2570/2614/2614 622/675/669 +f 2565/2617/2617 622/675/669 618/672/666 +f 2570/2614/2614 2561/2612/2612 607/662/656 +f 2570/2614/2614 607/662/656 622/675/669 +f 2561/2612/2612 2560/2611/2611 606/663/657 +f 2561/2612/2612 606/663/657 607/662/656 +f 2560/2611/2611 2557/2606/2606 608/660/654 +f 2560/2611/2611 608/660/654 606/663/657 +f 2557/2606/2606 2562/2608/2608 614/666/660 +f 2557/2606/2606 614/666/660 608/660/654 +f 2562/2608/2608 2552/2604/2604 599/654/648 +f 2562/2608/2608 599/654/648 614/666/660 +f 2552/2604/2604 2551/2601/2601 598/655/649 +f 2552/2604/2604 598/655/649 599/654/648 +f 2551/2601/2601 2553/2603/2603 600/652/646 +f 2551/2601/2601 600/652/646 598/655/649 +f 2553/2603/2603 2554/2597/2597 605/658/652 +f 2553/2603/2603 605/658/652 600/652/646 +f 2554/2597/2597 2546/2596/2596 590/644/638 +f 2554/2597/2597 590/644/638 605/658/652 +f 2546/2596/2596 2545/2594/2594 589/642/636 +f 2546/2596/2596 589/642/636 590/644/638 +f 2545/2594/2594 2543/2589/2589 592/645/639 +f 2545/2594/2594 592/645/639 589/642/636 +f 2543/2589/2589 2547/2591/2591 597/649/643 +f 2543/2589/2589 597/649/643 592/645/639 +f 2547/2591/2591 2539/2587/2587 582/634/628 +f 2547/2591/2591 582/634/628 597/649/643 +f 2539/2587/2587 2538/2585/2585 581/635/629 +f 2539/2587/2587 581/635/629 582/634/628 +f 2538/2585/2585 2536/2580/2580 583/637/631 +f 2538/2585/2585 583/637/631 581/635/629 +f 2536/2580/2580 2540/2582/2582 588/640/634 +f 2536/2580/2580 588/640/634 583/637/631 +f 2540/2582/2582 2531/2576/2576 574/628/622 +f 2540/2582/2582 574/628/622 588/640/634 +f 2531/2576/2576 2530/2575/2575 573/626/620 +f 2531/2576/2576 573/626/620 574/628/622 +f 2530/2575/2575 2532/2578/2578 576/630/624 +f 2530/2575/2575 576/630/624 573/626/620 +f 2532/2578/2578 2533/2571/2571 580/633/627 +f 2532/2578/2578 580/633/627 576/630/624 +f 2533/2571/2571 2525/2569/2569 567/620/614 +f 2533/2571/2571 567/620/614 580/633/627 +f 2525/2569/2569 2524/2568/2568 566/618/612 +f 2525/2569/2569 566/618/612 567/620/614 +f 2524/2568/2568 2522/2565/2565 569/622/616 +f 2524/2568/2568 569/622/616 566/618/612 +f 2522/2565/2565 2526/2563/2563 572/625/619 +f 2522/2565/2565 572/625/619 569/622/616 +f 2526/2563/2563 2519/2561/2561 559/609/603 +f 2526/2563/2563 559/609/603 572/625/619 +f 2519/2561/2561 2518/2560/2560 558/614/608 +f 2519/2561/2561 558/614/608 559/609/603 +f 2518/2560/2560 2514/2555/2555 560/612/606 +f 2518/2560/2560 560/612/606 558/614/608 +f 2514/2555/2555 2520/2557/2557 565/616/610 +f 2514/2555/2555 565/616/610 560/612/606 +f 2520/2557/2557 2511/2552/2552 550/600/594 +f 2520/2557/2557 550/600/594 565/616/610 +f 2511/2552/2552 2510/2551/2551 549/601/595 +f 2511/2552/2552 549/601/595 550/600/594 +f 2510/2551/2551 2512/2549/2549 556/606/600 +f 2510/2551/2551 556/606/600 549/601/595 +f 2512/2549/2549 2508/2548/2548 557/607/601 +f 2512/2549/2549 557/607/601 556/606/600 +f 2508/2548/2548 2796/2903/2903 878/952/946 +f 2508/2548/2548 878/952/946 557/607/601 +f 2796/2903/2903 2795/2899/2899 879/950/944 +f 2796/2903/2903 879/950/944 878/952/946 +f 2795/2899/2899 2797/2901/2901 884/955/949 +f 2795/2899/2899 884/955/949 879/950/944 +f 2797/2901/2901 2791/2895/2895 871/942/936 +f 2797/2901/2901 871/942/936 884/955/949 +f 2791/2895/2895 2792/2897/2897 870/940/934 +f 2791/2895/2895 870/940/934 871/942/936 +f 2792/2897/2897 2788/2890/2890 873/944/938 +f 2792/2897/2897 873/944/938 870/940/934 +f 2788/2890/2890 2793/2892/2892 877/947/941 +f 2788/2890/2890 877/947/941 873/944/938 +f 2793/2892/2892 2787/2887/2887 864/933/927 +f 2793/2892/2892 864/933/927 877/947/941 +f 2787/2887/2887 2786/2886/2886 863/934/928 +f 2787/2887/2887 863/934/928 864/933/927 +f 2786/2886/2886 2783/2882/2882 863/934/928 +f 2783/2882/2882 2779/2879/2879 869/938/932 +f 2783/2882/2882 869/938/932 863/934/928 +f 2779/2879/2879 856/928/922 869/938/932 +f 2779/2879/2879 2778/2878/2878 855/929/923 +f 2779/2879/2879 855/929/923 856/928/922 +f 2778/2878/2878 2776/2874/2874 857/926/920 +f 2778/2878/2878 857/926/920 855/929/923 +f 2776/2874/2874 2780/2876/2876 862/932/926 +f 2776/2874/2874 862/932/926 857/926/920 +f 2780/2876/2876 2772/2870/2870 849/919/913 +f 2780/2876/2876 849/919/913 862/932/926 +f 2772/2870/2870 2773/2872/2872 848/917/911 +f 2772/2870/2870 848/917/911 849/919/913 +f 2773/2872/2872 2770/2866/2866 851/921/915 +f 2773/2872/2872 851/921/915 848/917/911 +f 2770/2866/2866 2774/2868/2868 854/924/918 +f 2770/2866/2866 854/924/918 851/921/915 +f 2774/2868/2868 2767/2862/2862 841/908/902 +f 2774/2868/2868 841/908/902 854/924/918 +f 2767/2862/2862 2768/2864/2864 840/911/905 +f 2767/2862/2862 840/911/905 841/908/902 +f 2768/2864/2864 2764/2857/2857 842/912/906 +f 2768/2864/2864 842/912/906 840/911/905 +f 2764/2857/2857 2769/2859/2859 847/915/909 +f 2764/2857/2857 847/915/909 842/912/906 +f 2769/2859/2859 2761/2854/2854 833/902/896 +f 2769/2859/2859 833/902/896 847/915/909 +f 2761/2854/2854 2760/2853/2853 832/903/897 +f 2761/2854/2854 832/903/897 833/902/896 +f 2760/2853/2853 2757/2848/2848 834/900/894 +f 2760/2853/2853 834/900/894 832/903/897 +f 2757/2848/2848 2762/2850/2850 839/906/900 +f 2757/2848/2848 839/906/900 834/900/894 +f 2762/2850/2850 2753/2845/2845 825/894/888 +f 2762/2850/2850 825/894/888 839/906/900 +f 2753/2845/2845 2752/2844/2844 824/895/889 +f 2753/2845/2845 824/895/889 825/894/888 +f 2752/2844/2844 2749/2840/2840 826/892/886 +f 2752/2844/2844 826/892/886 824/895/889 +f 2749/2840/2840 2754/2842/2842 831/898/892 +f 2749/2840/2840 831/898/892 826/892/886 +f 2754/2842/2842 2745/2836/2836 816/882/876 +f 2754/2842/2842 816/882/876 831/898/892 +f 2745/2836/2836 2746/2838/2838 815/887/881 +f 2745/2836/2836 815/887/881 816/882/876 +f 2746/2838/2838 2743/2833/2833 817/885/879 +f 2746/2838/2838 817/885/879 815/887/881 +f 2743/2833/2833 2747/2830/2830 823/889/883 +f 2743/2833/2833 823/889/883 817/885/879 +f 2747/2830/2830 2740/2828/2828 807/877/871 +f 2747/2830/2830 807/877/871 823/889/883 +f 2740/2828/2828 2739/2827/2827 807/877/871 +f 2739/2827/2827 2735/2823/2823 808/875/869 +f 2739/2827/2827 808/875/869 807/877/871 +f 2735/2823/2823 814/880/874 808/875/869 +f 2735/2823/2823 2731/2820/2820 800/869/863 +f 2735/2823/2823 800/869/863 814/880/874 +f 2731/2820/2820 2730/2819/2819 799/870/864 +f 2731/2820/2820 799/870/864 800/869/863 +f 2730/2819/2819 2727/2815/2815 801/867/861 +f 2730/2819/2819 801/867/861 799/870/864 +f 2727/2815/2815 2732/2817/2817 806/873/867 +f 2727/2815/2815 806/873/867 801/867/861 +f 2732/2817/2817 2724/2813/2813 793/860/854 +f 2732/2817/2817 793/860/854 806/873/867 +f 2724/2813/2813 2723/2811/2811 792/858/852 +f 2724/2813/2813 792/858/852 793/860/854 +f 2723/2811/2811 2721/2807/2807 795/862/856 +f 2723/2811/2811 795/862/856 792/858/852 +f 2721/2807/2807 2725/2809/2809 798/865/859 +f 2721/2807/2807 798/865/859 795/862/856 +f 2725/2809/2809 2719/2805/2805 785/851/845 +f 2725/2809/2809 785/851/845 798/865/859 +f 2719/2805/2805 2718/2803/2803 784/849/843 +f 2719/2805/2805 784/849/843 785/851/845 +f 2718/2803/2803 2714/2798/2798 787/853/847 +f 2718/2803/2803 787/853/847 784/849/843 +f 2714/2798/2798 2720/2800/2800 791/856/850 +f 2714/2798/2798 791/856/850 787/853/847 +f 2720/2800/2800 2711/2796/2796 777/844/838 +f 2720/2800/2800 777/844/838 791/856/850 +f 2711/2796/2796 2710/2795/2795 776/845/839 +f 2711/2796/2796 776/845/839 777/844/838 +f 2710/2795/2795 2708/2790/2790 778/842/836 +f 2710/2795/2795 778/842/836 776/845/839 +f 2708/2790/2790 2712/2792/2792 778/842/836 +f 2712/2792/2792 2704/2787/2787 769/836/830 +f 2712/2792/2792 769/836/830 778/842/836 +f 2704/2787/2787 2703/2786/2786 768/837/831 +f 2704/2787/2787 768/837/831 769/836/830 +f 2703/2786/2786 2701/2782/2782 770/834/828 +f 2703/2786/2786 770/834/828 768/837/831 +f 2701/2782/2782 2705/2784/2784 775/840/834 +f 2701/2782/2782 775/840/834 770/834/828 +f 2705/2784/2784 2698/2780/2780 762/826/820 +f 2705/2784/2784 762/826/820 775/840/834 +f 2698/2780/2780 2697/2778/2778 761/824/818 +f 2698/2780/2780 761/824/818 762/826/820 +f 2697/2778/2778 2694/2773/2773 764/828/822 +f 2697/2778/2778 764/828/822 761/824/818 +f 2694/2773/2773 2699/2775/2775 767/831/825 +f 2694/2773/2773 767/831/825 764/828/822 +f 2699/2775/2775 2691/2770/2770 753/818/812 +f 2699/2775/2775 753/818/812 767/831/825 +f 2691/2770/2770 2690/2769/2769 753/818/812 +f 2690/2769/2769 2687/2765/2765 755/816/810 +f 2690/2769/2769 755/816/810 753/818/812 +f 2687/2765/2765 2692/2767/2767 760/822/816 +f 2687/2765/2765 760/822/816 755/816/810 +f 2692/2767/2767 2684/2762/2762 746/811/805 +f 2692/2767/2767 746/811/805 760/822/816 +f 2684/2762/2762 2683/2761/2761 745/812/806 +f 2684/2762/2762 745/812/806 746/811/805 +f 2683/2761/2761 2681/2757/2757 747/809/803 +f 2683/2761/2761 747/809/803 745/812/806 +f 2681/2757/2757 2685/2759/2759 752/815/809 +f 2681/2757/2757 752/815/809 747/809/803 +f 2685/2759/2759 2677/2753/2753 739/800/794 +f 2685/2759/2759 739/800/794 752/815/809 +f 2677/2753/2753 2678/2755/2755 738/805/799 +f 2677/2753/2753 738/805/799 739/800/794 +f 2678/2755/2755 2675/2749/2749 740/803/797 +f 2678/2755/2755 740/803/797 738/805/799 +f 2675/2749/2749 2679/2751/2751 744/807/801 +f 2675/2749/2749 744/807/801 740/803/797 +f 2679/2751/2751 2673/2747/2747 731/793/787 +f 2679/2751/2751 731/793/787 744/807/801 +f 2673/2747/2747 2672/2745/2745 730/791/785 +f 2673/2747/2747 730/791/785 731/793/787 +f 4/1833/1833 1803/1837/1837 1802/1835/1835 +f 7/33/25 3835/3849/3853 8/34/26 diff --git a/Telegram/Resources/art/premium/star_texture.svg b/Telegram/Resources/art/premium/star_texture.svg new file mode 100644 index 00000000000000..6d09446fb56ac7 --- /dev/null +++ b/Telegram/Resources/art/premium/star_texture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Telegram/Resources/art/round_placeholder.jpg b/Telegram/Resources/art/round_placeholder.jpg new file mode 100644 index 00000000000000..cbdc9aee39c6ec Binary files /dev/null and b/Telegram/Resources/art/round_placeholder.jpg differ diff --git a/Telegram/Resources/art/verified_bg.webp b/Telegram/Resources/art/verified_bg.webp new file mode 100644 index 00000000000000..f08059f4550135 Binary files /dev/null and b/Telegram/Resources/art/verified_bg.webp differ diff --git a/Telegram/Resources/art/verified_fg.webp b/Telegram/Resources/art/verified_fg.webp new file mode 100644 index 00000000000000..3535123495877e Binary files /dev/null and b/Telegram/Resources/art/verified_fg.webp differ diff --git a/Telegram/Resources/bot_webview_shell_html/body.html b/Telegram/Resources/bot_webview_shell_html/body.html new file mode 100644 index 00000000000000..36e0d3eaa3c306 --- /dev/null +++ b/Telegram/Resources/bot_webview_shell_html/body.html @@ -0,0 +1,46 @@ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Telegram/Resources/bot_webview_shell_html/page.css b/Telegram/Resources/bot_webview_shell_html/page.css new file mode 100644 index 00000000000000..ba184f57333617 --- /dev/null +++ b/Telegram/Resources/bot_webview_shell_html/page.css @@ -0,0 +1,642 @@ +html, +body { + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; + background: transparent; +} +:root { + --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif; +} +body { + font: 14px/1.35 var(--font-sans); + color: var(--title-fg, #000000); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} +button, +input, +textarea, +select { + font: inherit; +} +* { + box-sizing: border-box; +} +#root { + width: 100%; + height: 100%; + position: relative; + --shell-radius: 7px; + --shell-pad-top: 12px; + --shell-pad-right: 12px; + --shell-pad-bottom: 12px; + --shell-pad-left: 12px; + --shadow-pad-top: 12px; + --shadow-pad-right: 14px; + --shadow-pad-bottom: 16px; + --shadow-pad-left: 14px; + --header-height: 60px; + --title-pad-top: 0px; + --title-pad-right: 0px; + --title-pad-bottom: 0px; + --title-pad-left: 0px; + --badge-skip: 4px; + --frame-radius: 6px; + --control-width: 36px; + --control-height: 36px; + --button-height: 40px; + --button-gap-x: 12px; + --button-gap-y: 8px; + --disclosure-skip: 12px; + --footer-button-skip: 10px; + --footer-gap: var(--disclosure-skip); + --fullscreen-control-width: 44px; + --fullscreen-control-height: 44px; + --fullscreen-control-top: 8px; + --fullscreen-control-right: 8px; + --fullscreen-control-gap: 8px; + --body-bg: #ffffff; + --title-bg: #ffffff; + --bottom-bg: #ffffff; + --body-fg: #000000; + --title-fg: #000000; + --title-control-fg: rgba(0, 0, 0, 0.64); + --title-control-ripple: rgba(0, 0, 0, 0.064); + --footer-fg: rgba(0, 0, 0, 0.55); + --button-ripple: rgba(0, 0, 0, 0.14); + --menu-bg: #ffffff; + --menu-fg: #000000; + --menu-hover-bg: #f1f1f1; + --menu-ripple: #e5e5e5; + --menu-separator: rgba(0, 0, 0, 0.08); + --menu-attention: #d05c5c; +} +.hidden { + display: none !important; +} +#scene { + position: absolute; + inset: 0; + padding: var(--shadow-pad-top) + var(--shadow-pad-right) + var(--shadow-pad-bottom) + var(--shadow-pad-left); +} +#root.no-window-alpha #scene { + background: #eeeeee; +} +#shell { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + border-radius: var(--shell-radius); + background: var(--body-bg, #ffffff); + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.08), + 0 1px 2px rgba(0, 0, 0, 0.18), + 0 3px 8px -1px rgba(0, 0, 0, 0.16), + 0 8px 14px -5px rgba(0, 0, 0, 0.20); + overflow: hidden; +} +#root.no-window-alpha #shell { + box-shadow: none; + border: 1px solid rgba(0, 0, 0, 0.10); +} +#header { + flex: 0 0 auto; + min-height: var(--header-height); + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto auto; + align-items: center; + gap: 4px; + padding: 0 var(--shell-pad-right) 0 var(--shell-pad-left); + background: var(--title-bg, #ffffff); + color: var(--title-fg, #000000); + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + -webkit-user-select: none; + user-select: none; +} +#back { + grid-column: 1; +} +#title-row { + grid-column: 2; +} +#menu-toggle { + grid-column: 3; +} +#close { + grid-column: 4; +} +.title-control { + width: var(--control-width); + height: var(--control-height); + border: 0; + border-radius: 999px; + background: transparent; + color: var(--title-control-fg, currentColor); + cursor: pointer; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + transition: opacity 120ms linear; +} +.title-control svg { + width: 20px; + height: 20px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; + position: relative; + z-index: 1; +} +.title-control .fill-icon { + fill: currentColor; + stroke: none; +} +.title-control-mask { + display: block; + width: 20px; + height: 20px; + background: currentColor; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; + position: relative; + z-index: 1; +} +.title-control:disabled { + opacity: 0.45; + cursor: default; +} +#title-row { + display: flex; + align-items: center; + min-width: 0; + padding: var(--title-pad-top) + var(--title-pad-right) + var(--title-pad-bottom) + var(--title-pad-left); + -webkit-user-select: none; + user-select: none; +} +#title { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 20px; + line-height: 24px; + font-weight: 600; + -webkit-user-select: none; + user-select: none; +} +#badge { + margin-left: var(--badge-skip); + width: 16px; + height: 16px; + flex: 0 0 auto; + background-position: center; + background-repeat: no-repeat; + background-size: contain; +} +#body { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + gap: var(--footer-gap); + padding: 0; + background: var(--body-bg, #ffffff); +} +#frame-shell { + flex: 1 1 auto; + min-height: 0; + border-radius: 0; + overflow: hidden; + background: var(--body-bg, #ffffff); + box-shadow: none; +} +#frame-wrap, +#frame-wrap iframe { + display: block; + width: 100%; + height: 100%; + border: 0; + background: var(--body-bg, #ffffff); +} +#footer { + display: none; + flex: 0 0 auto; + color: var(--footer-fg); + padding: max(0px, calc(var(--shell-pad-bottom) - var(--footer-gap))) + var(--shell-pad-right) + var(--shell-pad-bottom) + var(--shell-pad-left); + background: var(--bottom-bg, var(--body-bg)); +} +#footer.visible { + display: flex; + flex-direction: column; +} +#disclosure { + display: none; + text-align: center; + font-size: 12px; + line-height: 16px; + padding: 0 4px; + word-break: break-word; +} +#disclosure.visible { + display: block; +} +#buttons-wrap { + display: none; + background: var(--bottom-bg, var(--body-bg)); + border-radius: var(--frame-radius); + overflow: hidden; +} +#buttons-wrap.visible { + display: block; +} +#buttons { + display: grid; + grid-template-columns: minmax(0, 1fr); + column-gap: var(--button-gap-x); + row-gap: var(--button-gap-y); +} +#buttons[data-layout="horizontal"] { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); +} +.shell-button { + min-width: 0; + height: var(--button-height); + border: 0; + border-radius: calc(var(--frame-radius) - 1px); + padding: 0 14px; + font-weight: 600; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + position: relative; + overflow: hidden; + transition: opacity 120ms linear; +} +.shell-button:disabled { + opacity: 0.55; + cursor: default; +} +.button-icon { + display: none; + flex: 0 0 auto; + width: 20px; + height: 20px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + position: relative; + z-index: 1; +} +.button-icon.visible { + display: block; +} +.button-icon.pending { + border-radius: 999px; + background-color: currentColor; + opacity: 0.18; +} +.button-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + z-index: 1; +} +.button-spinner { + display: none; + flex: 0 0 auto; + width: 14px; + height: 14px; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; + position: relative; + z-index: 1; +} +.button-spinner.visible { + display: block; +} +#menu-backdrop { + position: absolute; + inset: 0; + display: none; + background: transparent; + z-index: 11; +} +#menu-backdrop.visible { + display: block; +} +#menu { + position: absolute; + top: calc(var(--header-height) - 8px); + right: calc(var(--shell-pad-right) + var(--control-width)); + display: none; + min-width: 220px; + max-width: calc(100% - var(--shadow-pad-left) - var(--shadow-pad-right)); + background: var(--menu-bg, #ffffff); + color: var(--menu-fg, #000000); + border-radius: 12px; + box-shadow: + 0 20px 38px rgba(0, 0, 0, 0.22), + 0 4px 12px rgba(0, 0, 0, 0.12); + overflow: hidden; + z-index: 12; +} +#root.fullscreen #scene { + padding: 0; +} +#root.fullscreen #shell { + border-radius: 0; + box-shadow: none; +} +#root.fullscreen #header { + position: absolute; + top: var(--fullscreen-control-top); + right: var(--fullscreen-control-right); + z-index: 13; + width: auto; + min-height: var(--fullscreen-control-height); + display: flex; + justify-content: flex-end; + gap: var(--fullscreen-control-gap); + padding: 0; + background: transparent; + border-bottom: 0; + pointer-events: none; +} +#root.fullscreen #back, +#root.fullscreen #title-row { + display: none !important; +} +#root.fullscreen .title-control { + width: var(--fullscreen-control-width); + height: var(--fullscreen-control-height); + color: #ffffff; + background: rgba(0, 0, 0, 0.35); + --title-control-ripple: rgba(0, 0, 0, 0.094); + pointer-events: auto; +} +#root.fullscreen #body { + gap: 0; +} +#root.fullscreen #menu { + top: calc( + var(--fullscreen-control-top) + + var(--fullscreen-control-height) + - 8px); + right: calc(var(--fullscreen-control-right) + var(--fullscreen-control-width)); +} +#root.fullscreen #resize-handles { + display: none; +} +#resize-handles { + position: absolute; + inset: 0; + z-index: 11; + pointer-events: none; +} +.resize-handle { + position: absolute; + pointer-events: auto; +} +.resize-handle[data-resize-edge="top"] { + top: 0; + left: var(--shadow-pad-left); + right: var(--shadow-pad-right); + height: var(--shadow-pad-top); + cursor: ns-resize; +} +.resize-handle[data-resize-edge="bottom"] { + bottom: 0; + left: var(--shadow-pad-left); + right: var(--shadow-pad-right); + height: var(--shadow-pad-bottom); + cursor: ns-resize; +} +.resize-handle[data-resize-edge="left"] { + top: var(--shadow-pad-top); + bottom: var(--shadow-pad-bottom); + left: 0; + width: var(--shadow-pad-left); + cursor: ew-resize; +} +.resize-handle[data-resize-edge="right"] { + top: var(--shadow-pad-top); + bottom: var(--shadow-pad-bottom); + right: 0; + width: var(--shadow-pad-right); + cursor: ew-resize; +} +.resize-handle[data-resize-edge="top-left"] { + top: 0; + left: 0; + width: var(--shadow-pad-left); + height: var(--shadow-pad-top); + cursor: nwse-resize; +} +.resize-handle[data-resize-edge="top-right"] { + top: 0; + right: 0; + width: var(--shadow-pad-right); + height: var(--shadow-pad-top); + cursor: nesw-resize; +} +.resize-handle[data-resize-edge="bottom-left"] { + bottom: 0; + left: 0; + width: var(--shadow-pad-left); + height: var(--shadow-pad-bottom); + cursor: nesw-resize; +} +.resize-handle[data-resize-edge="bottom-right"] { + right: 0; + bottom: 0; + width: var(--shadow-pad-right); + height: var(--shadow-pad-bottom); + cursor: nwse-resize; +} +#menu.visible { + display: block; +} +#menu-list { + padding: 6px 0; +} +.menu-separator { + height: 1px; + margin: 6px 0; + background: var(--menu-separator, rgba(0, 0, 0, 0.08)); +} +.menu-item, +.menu-download { + width: 100%; + border: 0; + background: transparent; + color: inherit; + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + text-align: left; + cursor: default; + position: relative; + overflow: hidden; + transition: opacity 120ms linear; +} +.menu-download { + padding-left: 0; + padding-right: 0; +} +button.menu-item, +button.menu-download { + cursor: pointer; +} +button.menu-item:hover, +button.menu-download:hover { + background: var(--menu-hover-bg, rgba(127, 127, 127, 0.14)); +} +.menu-item.disabled, +.menu-download.disabled { + opacity: 0.72; +} +.menu-item.attention, +.menu-download.attention { + color: var(--menu-attention, #d05c5c); +} +.menu-group { + padding: 0 14px; +} +.menu-group-title { + padding-left: 0; + padding-right: 0; + cursor: default; +} +.menu-group-children { + display: flex; + flex-direction: column; + gap: 2px; + padding: 0 0 6px 32px; +} +.menu-item-icon { + flex: 0 0 20px; + width: 20px; + height: 20px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + position: relative; + z-index: 1; +} +.ripple { + position: absolute; + inset: 0; + border-radius: inherit; + overflow: hidden; + pointer-events: none; + z-index: 0; +} +.ripple .inner { + position: absolute; + border-radius: 50%; + transform: scale(0); + opacity: 1; + animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards; +} +.ripple.hiding { + animation: fadeOut 200ms linear forwards; +} +.title-control .ripple .inner { + background-color: var(--title-control-ripple); +} +button.menu-item .ripple .inner, +button.menu-download .ripple .inner { + background-color: var(--menu-ripple); +} +.shell-button .ripple .inner { + background-color: var(--button-ripple); +} +.menu-item-copy { + min-width: 0; + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: flex-start; + position: relative; + z-index: 1; +} +.menu-item-title, +.menu-item-subtitle, +.menu-item-action { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.menu-item-title { + font-size: 14px; + line-height: 18px; +} +.menu-item-subtitle, +.menu-item-action { + font-size: 12px; + line-height: 16px; +} +.menu-item-subtitle { + opacity: 0.64; +} +.menu-item-action { + flex: 0 0 auto; + padding-left: 12px; + opacity: 0.8; + position: relative; + z-index: 1; +} +#blocker { + position: absolute; + inset: 0; + display: none; + background: rgba(0, 0, 0, 0.12); + z-index: 20; +} +#root.blocked #blocker { + display: block; +} +@keyframes spin { + to { transform: rotate(360deg); } +} +@keyframes ripple { + to { + transform: scale(2); + } +} +@keyframes fadeOut { + to { + opacity: 0; + } +} diff --git a/Telegram/Resources/bot_webview_shell_html/page.js b/Telegram/Resources/bot_webview_shell_html/page.js new file mode 100644 index 00000000000000..9e8c5b937e7433 --- /dev/null +++ b/Telegram/Resources/bot_webview_shell_html/page.js @@ -0,0 +1,1138 @@ +(function() { + 'use strict'; + + const root = document.getElementById('root'); + const header = document.getElementById('header'); + const frameShell = document.getElementById('frame-shell'); + const frameWrap = document.getElementById('frame-wrap'); + const disclosure = document.getElementById('disclosure'); + const footer = document.getElementById('footer'); + const buttonsWrap = document.getElementById('buttons-wrap'); + const buttons = document.getElementById('buttons'); + const badge = document.getElementById('badge'); + const menuBackdrop = document.getElementById('menu-backdrop'); + const menu = document.getElementById('menu'); + const menuList = document.getElementById('menu-list'); + const blocker = document.getElementById('blocker'); + const resizeHandles = Array.prototype.slice.call( + document.querySelectorAll('.resize-handle')); + const title = document.getElementById('title'); + const controls = { + back: document.getElementById('back'), + menu: document.getElementById('menu-toggle'), + menuIcon: document.getElementById('menu-toggle-icon'), + close: document.getElementById('close') + }; + const shellState = { + backVisible: false, + menuVisible: false, + badgeVisible: false, + bottomText: '', + isFullscreen: false, + blocked: false, + menuOpen: false, + menuItems: [], + buttons: { + main: null, + secondary: null + } + }; + if (window.TelegramDesktopWindowAlphaSupported === false) { + root.classList.add('no-window-alpha'); + } + const shellAssets = { + icons: Object.create(null), + titleMenuIcon: null, + verifiedBadge: null, + menuPalette: null + }; + const shellToken = TDESKTOP_SHELL_TOKEN_PLACEHOLDER; + const nativeMessageType = 'tdesktop_external_bot_webapp'; + const maxPendingEvents = 64; + let iframe = null; + let frameLoaded = false; + let frameUrl = 'about:blank'; + let frameOrigin = ''; + let sameOrigin = false; + let frameGeneration = 0; + let reloadSupported = false; + let reloadTimeout = null; + let viewportScheduled = false; + let resizeObserver = null; + const pendingEvents = []; + + function normalizeEventData(eventData) { + if (typeof eventData === 'string') { + try { + const parsed = JSON.parse(eventData); + return parsed + && typeof parsed === 'object' + && !Array.isArray(parsed) + ? parsed + : {}; + } catch (e) { + return {}; + } + } + return eventData + && typeof eventData === 'object' + && !Array.isArray(eventData) + ? eventData + : {}; + } + + function isShellOrigin() { + return window.location.protocol === 'https:' + && window.location.hostname === 'web.telegram.org' + && (!window.location.port || window.location.port === '443'); + } + + function originFromUrl(url) { + try { + const origin = new URL(url, window.location.href).origin; + return origin && origin !== 'null' ? origin : ''; + } catch (e) { + return ''; + } + } + + function invokeNative(source, eventType, eventData, origin) { + if (!window.external + || typeof window.external.invoke !== 'function' + || typeof shellToken !== 'string' + || !shellToken + || typeof eventType !== 'string') { + return; + } + if (source === 'shell' && !isShellOrigin()) { + return; + } + window.external.invoke(JSON.stringify({ + type: nativeMessageType, + source: source, + token: shellToken, + origin: origin || window.location.origin, + eventType: eventType, + eventData: normalizeEventData(eventData) + })); + } + + function invokeShell(eventType, eventData) { + invokeNative('shell', eventType, eventData); + } + + function invokeWebApp(eventType, eventData, origin) { + invokeNative('webapp', eventType, eventData, origin); + } + + function sendToFrame(eventType, eventData, generation) { + if (!iframe + || !iframe.contentWindow + || generation !== frameGeneration) { + return; + } + if (sameOrigin && !frameOrigin) { + return; + } + const targetOrigin = sameOrigin ? frameOrigin : '*'; + iframe.contentWindow.postMessage(JSON.stringify({ + eventType: eventType, + eventData: eventData || {} + }), targetOrigin); + } + + function postToFrame(eventType, eventData) { + const generation = frameGeneration; + if (!iframe || !iframe.contentWindow || !frameLoaded) { + pendingEvents.push({ + generation: generation, + eventType: eventType, + eventData: eventData || {} + }); + while (pendingEvents.length > maxPendingEvents) { + pendingEvents.shift(); + } + return; + } + sendToFrame(eventType, eventData, generation); + } + + function shellPointerPayload(event, extra) { + const payload = { + button: event.button, + x: event.clientX, + y: event.clientY, + rootX: event.screenX, + rootY: event.screenY, + timeStamp: Math.round(event.timeStamp || 0) + }; + if (extra && typeof extra === 'object') { + for (const key in extra) { + payload[key] = extra[key]; + } + } + return payload; + } + + function beginShellControl(command, event, extra) { + if (shellState.blocked + || shellState.isFullscreen + || event.defaultPrevented + || !event.isTrusted + || event.button !== 0) { + return; + } + closeMenu(); + invokeShell(command, shellPointerPayload(event, extra)); + event.preventDefault(); + } + + function beginShellMove(event) { + const target = event.target; + if (target + && target.closest + && target.closest('.title-control, #menu')) { + return; + } + beginShellControl('shell_begin_move', event); + } + + function beginShellResize(edge, event) { + beginShellControl('shell_begin_resize', event, { + edge: edge + }); + } + + function flushPendingEvents() { + const pending = pendingEvents.splice(0); + for (const event of pending) { + if (event.generation === frameGeneration) { + sendToFrame(event.eventType, event.eventData, event.generation); + } + } + } + + function sendViewportChanged() { + if (!iframe) { + return; + } + const height = Math.max( + 0, + Math.round(frameShell.getBoundingClientRect().height)); + postToFrame('viewport_changed', { + height: height, + is_state_stable: true, + is_expanded: true + }); + } + + function scheduleViewport() { + if (viewportScheduled) { + return; + } + viewportScheduled = true; + window.requestAnimationFrame(function() { + viewportScheduled = false; + sendViewportChanged(); + }); + } + + function setMetric(name, value) { + if (typeof value === 'number' && Number.isFinite(value)) { + root.style.setProperty(name, String(value) + 'px'); + } + } + + function applyMetrics(data) { + if (!data || typeof data !== 'object') { + return; + } + setMetric('--shell-radius', data.shellRadius); + setMetric('--shell-pad-top', data.shellPaddingTop); + setMetric('--shell-pad-right', data.shellPaddingRight); + setMetric('--shell-pad-bottom', data.shellPaddingBottom); + setMetric('--shell-pad-left', data.shellPaddingLeft); + setMetric('--shadow-pad-top', data.shadowPaddingTop); + setMetric('--shadow-pad-right', data.shadowPaddingRight); + setMetric('--shadow-pad-bottom', data.shadowPaddingBottom); + setMetric('--shadow-pad-left', data.shadowPaddingLeft); + setMetric('--header-height', data.headerHeight); + setMetric('--title-pad-top', data.titlePaddingTop); + setMetric('--title-pad-right', data.titlePaddingRight); + setMetric('--title-pad-bottom', data.titlePaddingBottom); + setMetric('--title-pad-left', data.titlePaddingLeft); + setMetric('--badge-skip', data.badgeSkip); + setMetric('--frame-radius', data.frameRadius); + setMetric('--control-width', data.controlWidth); + setMetric('--control-height', data.controlHeight); + setMetric('--button-height', data.buttonHeight); + setMetric('--button-gap-x', data.buttonGapX); + setMetric('--button-gap-y', data.buttonGapY); + setMetric('--disclosure-skip', data.disclosureSkip); + setMetric('--footer-button-skip', data.footerButtonSkip); + setMetric('--fullscreen-control-width', data.fullscreenControlWidth); + setMetric('--fullscreen-control-height', data.fullscreenControlHeight); + setMetric('--fullscreen-control-top', data.fullscreenControlTop); + setMetric('--fullscreen-control-right', data.fullscreenControlRight); + setMetric('--fullscreen-control-gap', data.fullscreenControlGap); + } + + function colorForBackground(value) { + if (!/^#[0-9a-f]{6}$/i.test(value || '')) { + return null; + } + const red = parseInt(value.slice(1, 3), 16) / 255; + const green = parseInt(value.slice(3, 5), 16) / 255; + const blue = parseInt(value.slice(5, 7), 16) / 255; + const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue; + return luminance > 0.5 ? '#000000' : '#ffffff'; + } + + function footerColorForBackground(value) { + if (!/^#[0-9a-f]{6}$/i.test(value || '')) { + return null; + } + const red = parseInt(value.slice(1, 3), 16) / 255; + const green = parseInt(value.slice(3, 5), 16) / 255; + const blue = parseInt(value.slice(5, 7), 16) / 255; + const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue; + const contrast = 2.5; + const textLuminance = (luminance > 0.5) ? 0 : 1; + const adaptiveOpacity = (luminance - textLuminance + contrast) / contrast; + const opacity = Math.max(0.5, Math.min(0.64, adaptiveOpacity)); + const channel = (luminance > 0.5) ? 0 : 255; + return 'rgba(' + + String(channel) + ', ' + + String(channel) + ', ' + + String(channel) + ', ' + + String(opacity) + ')'; + } + + function titleControlColorsForBackground(value) { + if (!/^#[0-9a-f]{6}$/i.test(value || '')) { + return null; + } + const red = parseInt(value.slice(1, 3), 16) / 255; + const green = parseInt(value.slice(3, 5), 16) / 255; + const blue = parseInt(value.slice(5, 7), 16) / 255; + const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue; + const contrast = 2.5; + const textLuminance = (luminance > 0.5) ? 0 : 1; + const adaptiveOpacity = (luminance - textLuminance + contrast) / contrast; + const opacity = Math.max(0.5, Math.min(0.64, adaptiveOpacity)); + const channel = (luminance > 0.5) ? 0 : 255; + return { + fg: 'rgba(' + + String(channel) + ', ' + + String(channel) + ', ' + + String(channel) + ', ' + + String(opacity) + ')', + ripple: 'rgba(' + + String(channel) + ', ' + + String(channel) + ', ' + + String(channel) + ', ' + + String(opacity * 0.1) + ')' + }; + } + + function hexByte(value) { + const text = Math.max( + 0, + Math.min(255, Math.round(value))).toString(16); + return (text.length < 2 ? '0' : '') + text; + } + + function buttonRippleForBackground(value) { + if (!/^#[0-9a-f]{6}$/i.test(value || '')) { + return null; + } + const red = parseInt(value.slice(1, 3), 16); + const green = parseInt(value.slice(3, 5), 16); + const blue = parseInt(value.slice(5, 7), 16); + const maximum = Math.max(red, green, blue); + const minimum = Math.min(red, green, blue); + const delta = maximum - minimum; + let hue = 0; + if (delta !== 0 && maximum === red) { + hue = 60 * (((green - blue) / delta) % 6); + } else if (delta !== 0 && maximum === green) { + hue = 60 * ((blue - red) / delta + 2); + } else if (delta !== 0) { + hue = 60 * ((red - green) / delta + 4); + } + if (hue < 0) { + hue += 360; + } + const saturation = maximum === 0 ? 0 : delta / maximum; + const nextValue = Math.max( + 0, + Math.min(255, maximum + (maximum > 128 ? -32 : 32))); + const chroma = nextValue * saturation; + const x = chroma * (1 - Math.abs((hue / 60) % 2 - 1)); + const m = nextValue - chroma; + let nextRed = 0; + let nextGreen = 0; + let nextBlue = 0; + if (hue < 60) { + nextRed = chroma; + nextGreen = x; + } else if (hue < 120) { + nextRed = x; + nextGreen = chroma; + } else if (hue < 180) { + nextGreen = chroma; + nextBlue = x; + } else if (hue < 240) { + nextGreen = x; + nextBlue = chroma; + } else if (hue < 300) { + nextRed = x; + nextBlue = chroma; + } else { + nextRed = chroma; + nextBlue = x; + } + return '#' + + hexByte(nextRed + m) + + hexByte(nextGreen + m) + + hexByte(nextBlue + m); + } + + function applyColors(data) { + const next = (data && data.colors) ? data.colors : data; + if (!next || typeof next !== 'object') { + return; + } + if (next.bodyBg) { + root.style.setProperty('--body-bg', next.bodyBg); + const footerFg = footerColorForBackground(next.bodyBg); + if (footerFg) { + root.style.setProperty('--footer-fg', footerFg); + } + } + if (next.titleBg) { + root.style.setProperty('--title-bg', next.titleBg); + const titleFg = colorForBackground(next.titleBg); + if (titleFg) { + root.style.setProperty('--title-fg', titleFg); + } + const titleControl = titleControlColorsForBackground(next.titleBg); + if (titleControl) { + root.style.setProperty('--title-control-fg', titleControl.fg); + root.style.setProperty( + '--title-control-ripple', + titleControl.ripple); + } + } + if (next.bottomBg) { + root.style.setProperty('--bottom-bg', next.bottomBg); + } + } + + function applyAssets(data) { + if (!data || typeof data !== 'object') { + return; + } + if (data.icons && typeof data.icons === 'object') { + shellAssets.icons = data.icons; + } + if (data.titleMenuIcon && typeof data.titleMenuIcon === 'object') { + shellAssets.titleMenuIcon = data.titleMenuIcon; + } + if (data.verifiedBadge && typeof data.verifiedBadge === 'object') { + shellAssets.verifiedBadge = data.verifiedBadge; + } + if (data.menuPalette && typeof data.menuPalette === 'object') { + shellAssets.menuPalette = data.menuPalette; + if (data.menuPalette.bg) { + root.style.setProperty('--menu-bg', data.menuPalette.bg); + } + if (data.menuPalette.fg) { + root.style.setProperty('--menu-fg', data.menuPalette.fg); + } + if (data.menuPalette.hoverBg) { + root.style.setProperty( + '--menu-hover-bg', + data.menuPalette.hoverBg); + } + if (data.menuPalette.ripple) { + root.style.setProperty('--menu-ripple', data.menuPalette.ripple); + } + if (data.menuPalette.separator) { + root.style.setProperty( + '--menu-separator', + data.menuPalette.separator); + } + if (data.menuPalette.attention) { + root.style.setProperty( + '--menu-attention', + data.menuPalette.attention); + } + } + if (shellAssets.titleMenuIcon && shellAssets.titleMenuIcon.url) { + controls.menuIcon.style.webkitMaskImage = 'url(' + + shellAssets.titleMenuIcon.url + ')'; + controls.menuIcon.style.maskImage = 'url(' + + shellAssets.titleMenuIcon.url + ')'; + if (shellAssets.titleMenuIcon.width) { + controls.menuIcon.style.width + = String(shellAssets.titleMenuIcon.width) + 'px'; + } + if (shellAssets.titleMenuIcon.height) { + controls.menuIcon.style.height + = String(shellAssets.titleMenuIcon.height) + 'px'; + } + } + if (shellAssets.verifiedBadge && shellAssets.verifiedBadge.url) { + badge.style.backgroundImage = 'url(' + + shellAssets.verifiedBadge.url + ')'; + if (shellAssets.verifiedBadge.width) { + badge.style.width = String(shellAssets.verifiedBadge.width) + 'px'; + } + if (shellAssets.verifiedBadge.height) { + badge.style.height = String(shellAssets.verifiedBadge.height) + 'px'; + } + if (shellAssets.verifiedBadge.alt) { + badge.setAttribute('aria-label', shellAssets.verifiedBadge.alt); + } + } else { + badge.style.backgroundImage = ''; + badge.removeAttribute('aria-label'); + } + applyChrome({}); + renderMenu(); + } + + function applyChrome(data) { + if (!data || typeof data !== 'object') { + return; + } + if (Object.prototype.hasOwnProperty.call(data, 'backVisible')) { + shellState.backVisible = !!data.backVisible; + } + if (Object.prototype.hasOwnProperty.call(data, 'menuVisible')) { + shellState.menuVisible = !!data.menuVisible; + } + if (Object.prototype.hasOwnProperty.call(data, 'badgeVisible')) { + shellState.badgeVisible = !!data.badgeVisible; + } + controls.back.classList.toggle('hidden', !shellState.backVisible); + controls.menu.classList.toggle('hidden', !shellState.menuVisible); + controls.menu.disabled = !shellState.menuVisible + || !shellState.menuItems.length; + badge.classList.toggle( + 'hidden', + !shellState.badgeVisible + || !(shellAssets.verifiedBadge && shellAssets.verifiedBadge.url)); + badge.setAttribute( + 'aria-hidden', + badge.classList.contains('hidden') ? 'true' : 'false'); + if (controls.menu.disabled) { + closeMenu(); + } + } + + function visibleButtons() { + const main = shellState.buttons.main && shellState.buttons.main.visible + ? shellState.buttons.main + : null; + const secondary = shellState.buttons.secondary + && shellState.buttons.secondary.visible + ? shellState.buttons.secondary + : null; + const result = { + layout: 'single', + buttons: [] + }; + if (main && secondary) { + const position = secondary.position || 'left'; + if (position === 'top') { + result.layout = 'vertical'; + result.buttons = [secondary, main]; + } else if (position === 'bottom') { + result.layout = 'vertical'; + result.buttons = [main, secondary]; + } else if (position === 'left') { + result.layout = 'horizontal'; + result.buttons = [secondary, main]; + } else { + result.layout = 'horizontal'; + result.buttons = [main, secondary]; + } + } else if (main) { + result.buttons = [main]; + } else if (secondary) { + result.buttons = [secondary]; + } + return result; + } + + function requestButtonIcon(state) { + if (!state + || !state.visible + || !state.iconCustomEmojiId + || state.iconResolvedGeneration === state.iconGeneration + || state.iconRequestGeneration === state.iconGeneration) { + return; + } + state.iconRequestGeneration = state.iconGeneration; + invokeShell('shell_request_button_icon', { + name: state.name + }); + } + + function updateFooter() { + const visible = visibleButtons(); + const hasButtons = !!visible.buttons.length; + disclosure.textContent = ''; + disclosure.classList.remove('visible'); + buttonsWrap.classList.toggle('visible', hasButtons); + footer.classList.toggle('visible', hasButtons); + root.style.setProperty( + '--footer-gap', + shellState.isFullscreen + ? '0px' + : hasButtons + ? 'var(--footer-button-skip)' + : 'var(--disclosure-skip)'); + scheduleViewport(); + } + + function renderButtons() { + const visible = visibleButtons(); + buttons.textContent = ''; + buttons.dataset.layout = visible.layout; + for (const state of visible.buttons) { + const button = document.createElement('button'); + button.type = 'button'; + button.className = 'shell-button'; + button.disabled = !state.active; + const buttonColor = state.color || '#40a7e3'; + button.style.background = buttonColor; + button.style.color = state.textColor || '#ffffff'; + const rippleColor = buttonRippleForBackground(buttonColor); + if (rippleColor) { + button.style.setProperty('--button-ripple', rippleColor); + } + + const icon = document.createElement('span'); + icon.className = 'button-icon'; + const iconReady = state.iconResolvedGeneration === state.iconGeneration; + if (state.iconCustomEmojiId && state.iconUrl) { + icon.classList.add('visible'); + icon.style.backgroundImage = 'url(' + state.iconUrl + ')'; + } else if (state.iconCustomEmojiId && !iconReady) { + icon.classList.add('visible', 'pending'); + requestButtonIcon(state); + } + button.appendChild(icon); + + const label = document.createElement('span'); + label.className = 'button-label'; + label.textContent = state.text || ''; + button.appendChild(label); + + const spinner = document.createElement('span'); + spinner.className = 'button-spinner'; + if (state.progress) { + spinner.classList.add('visible'); + } + button.appendChild(spinner); + + button.addEventListener('click', function() { + if (!button.disabled) { + postToFrame(state.name + '_button_pressed', {}); + } + }); + setupRipple(button); + buttons.appendChild(button); + } + updateFooter(); + } + + function menuIconUrl(name) { + const asset = shellAssets.icons && name ? shellAssets.icons[name] : null; + return (asset && asset.url) ? asset.url : ''; + } + + function createMenuNode(item, className) { + const clickable = !!item.id && item.enabled !== false; + const node = document.createElement(clickable ? 'button' : 'div'); + node.className = className + + (item.attention ? ' attention' : '') + + (clickable ? '' : ' disabled'); + if (clickable) { + node.type = 'button'; + setupRipple(node); + node.addEventListener('click', function(event) { + if (!shellState.blocked && event.isTrusted) { + invokeShell('shell_menu_action', { id: item.id }); + closeMenu(); + } + }); + } + return node; + } + + function createMenuCopy(item) { + const copy = document.createElement('span'); + copy.className = 'menu-item-copy'; + const titleNode = document.createElement('span'); + titleNode.className = 'menu-item-title'; + titleNode.textContent = item.text || ''; + copy.appendChild(titleNode); + if (item.subtitle) { + const subtitleNode = document.createElement('span'); + subtitleNode.className = 'menu-item-subtitle'; + subtitleNode.textContent = item.subtitle; + copy.appendChild(subtitleNode); + } + return copy; + } + + function renderMenu() { + menuList.textContent = ''; + for (const item of shellState.menuItems) { + if (item.separator) { + const separator = document.createElement('div'); + separator.className = 'menu-separator'; + menuList.appendChild(separator); + continue; + } + if (Array.isArray(item.children) && item.children.length) { + const group = document.createElement('div'); + group.className = 'menu-group'; + + const header = document.createElement('div'); + header.className = 'menu-item menu-group-title'; + const headerIcon = document.createElement('span'); + headerIcon.className = 'menu-item-icon'; + const headerIconUrl = menuIconUrl(item.icon); + if (headerIconUrl) { + headerIcon.style.backgroundImage = 'url(' + + headerIconUrl + ')'; + } + header.appendChild(headerIcon); + header.appendChild(createMenuCopy(item)); + group.appendChild(header); + + const children = document.createElement('div'); + children.className = 'menu-group-children'; + for (const child of item.children) { + if (child.separator) { + const separator = document.createElement('div'); + separator.className = 'menu-separator'; + children.appendChild(separator); + continue; + } + const row = createMenuNode(child, 'menu-download'); + row.appendChild(createMenuCopy(child)); + if (child.actionLabel) { + const action = document.createElement('span'); + action.className = 'menu-item-action'; + action.textContent = child.actionLabel; + row.appendChild(action); + } + children.appendChild(row); + } + group.appendChild(children); + menuList.appendChild(group); + continue; + } + + const row = createMenuNode(item, 'menu-item'); + const icon = document.createElement('span'); + icon.className = 'menu-item-icon'; + const iconUrl = menuIconUrl(item.icon); + if (iconUrl) { + icon.style.backgroundImage = 'url(' + iconUrl + ')'; + } + row.appendChild(icon); + row.appendChild(createMenuCopy(item)); + menuList.appendChild(row); + } + menu.classList.toggle( + 'visible', + shellState.menuOpen + && shellState.menuVisible + && !!shellState.menuItems.length); + menuBackdrop.classList.toggle( + 'visible', + menu.classList.contains('visible')); + controls.menu.classList.toggle( + 'active', + menu.classList.contains('visible')); + } + function closeMenu() { + if (!shellState.menuOpen) { + return; + } + shellState.menuOpen = false; + renderMenu(); + } + + function toggleMenu(event) { + if (event && !event.isTrusted) { + return; + } + if (shellState.blocked) { + return; + } + if (shellState.menuOpen) { + closeMenu(); + return; + } + invokeShell('shell_menu_request', {}); + shellState.menuOpen = true; + renderMenu(); + } + + function parseFrameMessage(data) { + if (typeof data === 'string') { + try { + return JSON.parse(data); + } catch (e) { + return null; + } + } + return data && typeof data === 'object' && !Array.isArray(data) + ? data + : null; + } + + function addRipple(button, x, y) { + if (!button || button.disabled) { + return; + } + const rect = button.getBoundingClientRect(); + const ripple = document.createElement('span'); + ripple.className = 'ripple'; + const inner = document.createElement('span'); + inner.className = 'inner'; + const size = 2 * Math.hypot( + Math.max(x, rect.width - x), + Math.max(y, rect.height - y)); + inner.style.width = String(size) + 'px'; + inner.style.height = String(size) + 'px'; + inner.style.left = String(x - size / 2) + 'px'; + inner.style.top = String(y - size / 2) + 'px'; + ripple.appendChild(inner); + button.appendChild(ripple); + } + + function stopRipples(button) { + const ripples = Array.prototype.slice.call( + button.querySelectorAll('.ripple:not(.hiding)')); + for (const ripple of ripples) { + ripple.classList.add('hiding'); + window.setTimeout(function() { + ripple.remove(); + }, 200); + } + } + + function setupRipple(button) { + button.addEventListener('mousedown', function(event) { + if (event.button !== 0) { + return; + } + const rect = button.getBoundingClientRect(); + addRipple(button, event.clientX - rect.left, event.clientY - rect.top); + }); + button.addEventListener('mouseup', function() { + stopRipples(button); + }); + button.addEventListener('mouseleave', function() { + stopRipples(button); + }); + } + + window.addEventListener('message', function(event) { + if (!iframe + || !iframe.contentWindow + || event.source !== iframe.contentWindow) { + return; + } + if (sameOrigin && (!frameOrigin || event.origin !== frameOrigin)) { + return; + } + const message = parseFrameMessage(event.data); + if (!message || typeof message.eventType !== 'string') { + return; + } + if (message.eventType === 'iframe_ready') { + reloadSupported = !!(message.eventData && message.eventData.reload_supported); + return; + } else if (message.eventType === 'iframe_will_reload') { + if (reloadTimeout) { + window.clearTimeout(reloadTimeout); + reloadTimeout = null; + } + frameLoaded = false; + return; + } + invokeWebApp(message.eventType, message.eventData, event.origin); + }); + + menuBackdrop.addEventListener('mousedown', closeMenu); + blocker.addEventListener('click', function(event) { + if (!event.isTrusted || !shellState.blocked) { + return; + } + invokeShell('shell_close_layer', {}); + event.preventDefault(); + event.stopPropagation(); + }); + + document.addEventListener('mousedown', function(event) { + if (!shellState.menuOpen) { + return; + } + const target = event.target; + if (menu.contains(target) + || controls.menu.contains(target) + || menuBackdrop.contains(target)) { + return; + } + closeMenu(); + }); + + window.addEventListener('keydown', function(event) { + if (event.key === 'Escape' && shellState.menuOpen) { + event.preventDefault(); + closeMenu(); + } + }); + + window.addEventListener('resize', scheduleViewport); + if (window.ResizeObserver) { + resizeObserver = new window.ResizeObserver(scheduleViewport); + resizeObserver.observe(frameShell); + resizeObserver.observe(root); + } + + controls.close.addEventListener('click', function(event) { + if (event.isTrusted) { + invokeShell('shell_close', {}); + } + }); + controls.back.addEventListener('click', function() { + postToFrame('back_button_pressed', {}); + }); + controls.menu.addEventListener('click', toggleMenu); + setupRipple(controls.close); + setupRipple(controls.back); + setupRipple(controls.menu); + header.addEventListener('selectstart', function(event) { + event.preventDefault(); + }); + header.addEventListener('mousedown', beginShellMove); + for (const handle of resizeHandles) { + handle.addEventListener('mousedown', function(event) { + beginShellResize(handle.getAttribute('data-resize-edge'), event); + }); + } + + function createIframe(url) { + closeMenu(); + if (reloadTimeout) { + window.clearTimeout(reloadTimeout); + reloadTimeout = null; + } + const generation = ++frameGeneration; + pendingEvents.splice(0); + const next = document.createElement('iframe'); + next.setAttribute('allow', 'clipboard-read; clipboard-write; fullscreen'); + next.referrerPolicy = 'no-referrer'; + next.addEventListener('load', function() { + if (iframe !== next || generation !== frameGeneration) { + return; + } + frameLoaded = true; + flushPendingEvents(); + scheduleViewport(); + }); + frameLoaded = false; + reloadSupported = false; + if (iframe) { + iframe.remove(); + } + iframe = next; + iframe.src = url || 'about:blank'; + frameWrap.appendChild(iframe); + } + + function fallbackReloadFrame() { + createIframe(frameUrl || 'about:blank'); + } + + function reloadFrame() { + if (!iframe) { + return; + } + if (reloadSupported && frameLoaded && iframe.contentWindow) { + sendToFrame('reload_iframe', {}, frameGeneration); + if (reloadTimeout) { + window.clearTimeout(reloadTimeout); + } + reloadTimeout = window.setTimeout(function() { + reloadTimeout = null; + fallbackReloadFrame(); + }, 500); + return; + } + fallbackReloadFrame(); + } + + function isNativeToken(token) { + return !!shellToken && token === shellToken; + } + + const api = { + bootstrap: function(data, token) { + if (!isNativeToken(token)) { + return; + } + applyMetrics(data && data.metrics); + applyColors(data && data.colors); + applyChrome(data || {}); + shellState.bottomText = ''; + title.textContent = (data && data.title) || ''; + document.title = (data && data.title) || 'Telegram'; + sameOrigin = !!(data && data.sameOrigin); + frameUrl = (data && data.url) || 'about:blank'; + frameOrigin = sameOrigin ? originFromUrl(frameUrl) : ''; + createIframe(frameUrl); + renderButtons(); + renderMenu(); + }, + nativeEvent: function(eventType, eventData, token) { + if (!isNativeToken(token) || typeof eventType !== 'string') { + return; + } + if (eventType === 'fullscreen_changed' && eventData) { + shellState.isFullscreen = !!eventData.is_fullscreen; + root.classList.toggle('fullscreen', shellState.isFullscreen); + updateFooter(); + } + postToFrame(eventType, eventData || {}); + }, + setTitle: function(data, token) { + if (!isNativeToken(token)) { + return; + } + title.textContent = (data && data.title) || ''; + document.title = (data && data.title) || 'Telegram'; + }, + setChrome: function(data, token) { + if (!isNativeToken(token)) { + return; + } + applyChrome(data || {}); + }, + setColors: function(data, token) { + if (!isNativeToken(token)) { + return; + } + applyColors(data || {}); + }, + setAssets: function(data, token) { + if (!isNativeToken(token)) { + return; + } + applyAssets(data || {}); + }, + setMenu: function(data, token) { + if (!isNativeToken(token)) { + return; + } + shellState.menuItems = Array.isArray(data && data.items) + ? data.items + : []; + applyChrome({}); + renderMenu(); + }, + setBottomText: function(data, token) { + if (!isNativeToken(token)) { + return; + } + shellState.bottomText = ''; + updateFooter(); + }, + setButton: function(data, token) { + if (!isNativeToken(token)) { + return; + } + if (!data || !data.name) { + return; + } + const previous = shellState.buttons[data.name] || {}; + const next = Object.assign({}, previous, data); + if (next.iconGeneration !== previous.iconGeneration + || next.iconCustomEmojiId !== previous.iconCustomEmojiId) { + next.iconRequestGeneration = ''; + next.iconResolvedGeneration = ''; + next.iconUrl = ''; + } + if (!next.iconCustomEmojiId) { + next.iconRequestGeneration = ''; + next.iconResolvedGeneration = next.iconGeneration || ''; + next.iconUrl = ''; + } + shellState.buttons[data.name] = next; + renderButtons(); + }, + setButtonIcon: function(data, token) { + if (!isNativeToken(token)) { + return; + } + if (!data || !data.name) { + return; + } + const state = shellState.buttons[data.name]; + if (!state || state.iconGeneration !== (data.generation || '')) { + return; + } + const icon = (data.icon && typeof data.icon === 'object') + ? data.icon + : null; + state.iconRequestGeneration = ''; + state.iconResolvedGeneration = state.iconGeneration; + state.iconUrl = (icon && icon.url) ? icon.url : ''; + renderButtons(); + }, + setBlocked: function(data, token) { + if (!isNativeToken(token)) { + return; + } + shellState.blocked = !!(data && data.blocked); + root.classList.toggle('blocked', shellState.blocked); + if (shellState.blocked) { + closeMenu(); + } + }, + setProgress: function(data, token) { + if (!isNativeToken(token)) { + return; + } + root.classList.toggle('loading', !!(data && data.shown)); + }, + reloadFrame: function(data, token) { + if (!isNativeToken(token)) { + return; + } + reloadFrame(); + }, + sendViewport: function(data, token) { + if (!isNativeToken(token)) { + return; + } + scheduleViewport(); + } + }; + Object.defineProperty(window, 'TelegramDesktopShell', { + value: Object.freeze(api), + configurable: false, + writable: false + }); +})(); diff --git a/Telegram/Resources/day-custom-base.tdesktop-theme b/Telegram/Resources/day-custom-base.tdesktop-theme index d10f68c7568edc..42d69a8d4f40c2 100644 Binary files a/Telegram/Resources/day-custom-base.tdesktop-theme and b/Telegram/Resources/day-custom-base.tdesktop-theme differ diff --git a/Telegram/Resources/default_shortcuts-custom.json b/Telegram/Resources/default_shortcuts-custom.json index 42d412b15b4107..054ad70d077680 100644 --- a/Telegram/Resources/default_shortcuts-custom.json +++ b/Telegram/Resources/default_shortcuts-custom.json @@ -1,6 +1,7 @@ // This is a list of your own shortcuts for Telegram Desktop // You can see full list of commands in the 'shortcuts-default.json' file // Place a null value instead of a command string to switch the shortcut off +// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts. [ // { diff --git a/Telegram/Resources/emoji/emoji_1.webp b/Telegram/Resources/emoji/emoji_1.webp index 4669e602224636..a46934a411816d 100644 Binary files a/Telegram/Resources/emoji/emoji_1.webp and b/Telegram/Resources/emoji/emoji_1.webp differ diff --git a/Telegram/Resources/emoji/emoji_2.webp b/Telegram/Resources/emoji/emoji_2.webp index 0c0d7c2956901e..1ed5db43fb7b0e 100644 Binary files a/Telegram/Resources/emoji/emoji_2.webp and b/Telegram/Resources/emoji/emoji_2.webp differ diff --git a/Telegram/Resources/emoji/emoji_3.webp b/Telegram/Resources/emoji/emoji_3.webp index 4d874b37bfdf03..f31b74cbe72ecb 100644 Binary files a/Telegram/Resources/emoji/emoji_3.webp and b/Telegram/Resources/emoji/emoji_3.webp differ diff --git a/Telegram/Resources/emoji/emoji_4.webp b/Telegram/Resources/emoji/emoji_4.webp index bc4eead449b685..53f65c27a0a2cd 100644 Binary files a/Telegram/Resources/emoji/emoji_4.webp and b/Telegram/Resources/emoji/emoji_4.webp differ diff --git a/Telegram/Resources/emoji/emoji_5.webp b/Telegram/Resources/emoji/emoji_5.webp index 9a4e9afd6de03a..dda7edca30d4bf 100644 Binary files a/Telegram/Resources/emoji/emoji_5.webp and b/Telegram/Resources/emoji/emoji_5.webp differ diff --git a/Telegram/Resources/emoji/emoji_6.webp b/Telegram/Resources/emoji/emoji_6.webp index 7d5842d8e65386..d8dbd4774c0598 100644 Binary files a/Telegram/Resources/emoji/emoji_6.webp and b/Telegram/Resources/emoji/emoji_6.webp differ diff --git a/Telegram/Resources/emoji/emoji_7.webp b/Telegram/Resources/emoji/emoji_7.webp index b324a3e8328ec9..13e3a97437ca80 100644 Binary files a/Telegram/Resources/emoji/emoji_7.webp and b/Telegram/Resources/emoji/emoji_7.webp differ diff --git a/Telegram/Resources/emoji/emoji_8.webp b/Telegram/Resources/emoji/emoji_8.webp index 5ac85a8a713504..585d8c7182279c 100644 Binary files a/Telegram/Resources/emoji/emoji_8.webp and b/Telegram/Resources/emoji/emoji_8.webp differ diff --git a/Telegram/Resources/export_html/css/style.css b/Telegram/Resources/export_html/css/style.css index f9727d68ac6f32..3189390b4ca1d4 100644 --- a/Telegram/Resources/export_html/css/style.css +++ b/Telegram/Resources/export_html/css/style.css @@ -440,6 +440,9 @@ div.toast_shown { .section.stories { background-image: url(../images/section_stories.png); } +.section.music { + background-image: url(../images/section_music.png); +} .section.web { background-image: url(../images/section_web.png); } @@ -481,6 +484,16 @@ div.toast_shown { .media_video .fill { background-image: url(../images/media_video.png) } +.audio_icon { + width: 48px; + height: 48px; + border-radius: 50%; + background-color: #4f9cd9; + background-image: url(../images/media_music.png); + background-repeat: no-repeat; + background-position: 12px 12px; + background-size: 24px 24px; +} @media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { .section.calls { @@ -504,6 +517,9 @@ div.toast_shown { .section.stories { background-image: url(../images/section_stories@2x.png); } +.section.music { + background-image: url(../images/section_music@2x.png); +} .section.web { background-image: url(../images/section_web@2x.png); } @@ -545,6 +561,9 @@ div.toast_shown { .media_video .fill { background-image: url(../images/media_video@2x.png) } +.audio_icon { + background-image: url(../images/media_music@2x.png); +} } .spoiler { @@ -633,4 +652,101 @@ div.toast_shown { .reactions .reaction .count { margin-right: 8px; line-height: 20px; +} + +@media (prefers-color-scheme: dark) { +html, body { + background-color: #1a2026; /* groupCallBg */ + margin: 0; + padding: 0; +} +.page_wrap { + background-color: #1a2026; /* groupCallBg */ + color: #ffffff; /* groupCallMembersFg */ + min-height: 100vh; +} +.page_wrap a { + color: #4db8ff; /* groupCallActiveFg */ +} +.page_header { + background-color: #1a2026; /* groupCallBg */ + border-bottom: 1px solid #2c333d; /* groupCallMembersBg */ +} +.bold { + color: #ffffff; /* groupCallMembersFg */ +} +.details { + color: #91979e; /* groupCallMemberNotJoinedStatus */ +} +.page_body { + background-color: #1a2026; /* groupCallBg */ +} +code { + color: #ff8aac; /* historyPeer6UserpicBg */ + background-color: #2c333d; /* groupCallMembersBg */ +} +pre { + color: #ffffff; /* groupCallMembersFg */ + background-color: #2c333d; /* groupCallMembersBg */ + border: 1px solid #323a45; /* groupCallMembersBgOver */ +} +.with_divider { + border-top: 1px solid #2c333d; /* groupCallMembersBg */ +} +a.block_link:hover { + background-color: #323a45; /* groupCallMembersBgOver */ +} +.list_page .entry { + color: #ffffff; /* groupCallMembersFg */ +} +.message { + color: #ffffff; /* groupCallMembersFg */ +} +div.selected { + background-color: #323a45; /* groupCallMembersBgOver */ +} +.default .from_name { + color: #4db8ff; /* groupCallActiveFg */ +} +.default .media .description { + color: #ffffff; /* groupCallMembersFg */ +} +msgInBg, +.historyComposeAreaBg { + background-color: #2c333d; /* groupCallMembersBg */ +} +msgOutBg { + background-color: #323a45; /* groupCallMembersBgOver */ +} +msgInBgSelected { + background-color: #39424f; /* groupCallMembersBgRipple */ +} +msgOutBgSelected { + background-color: #39424f; /* groupCallMembersBgRipple */ +} +.spoiler { + background: #323a45; /* groupCallMembersBgOver */ +} +.spoiler.hidden { + background: #61c0ff; /* groupCallMemberInactiveStatus */ +} +.bot_button { + background-color: #4db8ff40; /* groupCallActiveFg with opacity */ +} +.reactions .reaction { + background-color: #2c333d; /* groupCallMembersBg */ + color: #4db8ff; /* groupCallActiveFg */ +} +.reactions .reaction.active { + background-color: #4db8ff; /* groupCallActiveFg */ + color: #1a2026; /* groupCallBg */ +} +.reactions .reaction.paid { + background-color: #323a45; /* groupCallMembersBgOver */ + color: #febb5b; /* historyPeer8UserpicBg */ +} +.reactions .reaction.active.paid { + background-color: #febb5b; /* historyPeer8UserpicBg */ + color: #1a2026; /* groupCallBg */ +} } \ No newline at end of file diff --git a/Telegram/Resources/export_html/images/section_music.png b/Telegram/Resources/export_html/images/section_music.png new file mode 100644 index 00000000000000..a76a2449d54fd2 Binary files /dev/null and b/Telegram/Resources/export_html/images/section_music.png differ diff --git a/Telegram/Resources/export_html/images/section_music@2x.png b/Telegram/Resources/export_html/images/section_music@2x.png new file mode 100644 index 00000000000000..53dc7022c65857 Binary files /dev/null and b/Telegram/Resources/export_html/images/section_music@2x.png differ diff --git a/Telegram/Resources/icons/arrows_select.png b/Telegram/Resources/icons/arrows_select.png new file mode 100644 index 00000000000000..6ce5c0f6c4538f Binary files /dev/null and b/Telegram/Resources/icons/arrows_select.png differ diff --git a/Telegram/Resources/icons/arrows_select@2x.png b/Telegram/Resources/icons/arrows_select@2x.png new file mode 100644 index 00000000000000..6db5b158e95307 Binary files /dev/null and b/Telegram/Resources/icons/arrows_select@2x.png differ diff --git a/Telegram/Resources/icons/arrows_select@3x.png b/Telegram/Resources/icons/arrows_select@3x.png new file mode 100644 index 00000000000000..63bf8a6a44d13e Binary files /dev/null and b/Telegram/Resources/icons/arrows_select@3x.png differ diff --git a/Telegram/Resources/icons/calls/call_group.png b/Telegram/Resources/icons/calls/call_group.png new file mode 100644 index 00000000000000..d60c581b1f0651 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_group.png differ diff --git a/Telegram/Resources/icons/calls/call_group@2x.png b/Telegram/Resources/icons/calls/call_group@2x.png new file mode 100644 index 00000000000000..ab1dd6a1504693 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_group@2x.png differ diff --git a/Telegram/Resources/icons/calls/call_group@3x.png b/Telegram/Resources/icons/calls/call_group@3x.png new file mode 100644 index 00000000000000..67f363752b4c36 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_group@3x.png differ diff --git a/Telegram/Resources/icons/calls/call_message.png b/Telegram/Resources/icons/calls/call_message.png new file mode 100644 index 00000000000000..3fcc4ecda58f03 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_message.png differ diff --git a/Telegram/Resources/icons/calls/call_message@2x.png b/Telegram/Resources/icons/calls/call_message@2x.png new file mode 100644 index 00000000000000..e6d5f0f8b11ac3 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_message@2x.png differ diff --git a/Telegram/Resources/icons/calls/call_message@3x.png b/Telegram/Resources/icons/calls/call_message@3x.png new file mode 100644 index 00000000000000..f7e20df4fb52d5 Binary files /dev/null and b/Telegram/Resources/icons/calls/call_message@3x.png differ diff --git a/Telegram/Resources/icons/calls/calls_add_people.png b/Telegram/Resources/icons/calls/calls_add_people.png new file mode 100644 index 00000000000000..4e07a92f475276 Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_add_people.png differ diff --git a/Telegram/Resources/icons/calls/calls_add_people@2x.png b/Telegram/Resources/icons/calls/calls_add_people@2x.png new file mode 100644 index 00000000000000..bf55e663c391e5 Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_add_people@2x.png differ diff --git a/Telegram/Resources/icons/calls/calls_add_people@3x.png b/Telegram/Resources/icons/calls/calls_add_people@3x.png new file mode 100644 index 00000000000000..c138ad5c24c537 Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_add_people@3x.png differ diff --git a/Telegram/Resources/icons/calls/filled_stream_crown.svg b/Telegram/Resources/icons/calls/filled_stream_crown.svg new file mode 100644 index 00000000000000..f1b17ee2be3b0e --- /dev/null +++ b/Telegram/Resources/icons/calls/filled_stream_crown.svg @@ -0,0 +1,7 @@ + + + Filled / filled_stream_crown + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/calls/group_call_logo.png b/Telegram/Resources/icons/calls/group_call_logo.png new file mode 100644 index 00000000000000..b083149297bd7e Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo.png differ diff --git a/Telegram/Resources/icons/calls/group_call_logo@2x.png b/Telegram/Resources/icons/calls/group_call_logo@2x.png new file mode 100644 index 00000000000000..ca935f224961d2 Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo@2x.png differ diff --git a/Telegram/Resources/icons/calls/group_call_logo@3x.png b/Telegram/Resources/icons/calls/group_call_logo@3x.png new file mode 100644 index 00000000000000..890bab3cb2a8cc Binary files /dev/null and b/Telegram/Resources/icons/calls/group_call_logo@3x.png differ diff --git a/Telegram/Resources/icons/chat/ai_letters.svg b/Telegram/Resources/icons/chat/ai_letters.svg new file mode 100644 index 00000000000000..47c8ce91d6529b --- /dev/null +++ b/Telegram/Resources/icons/chat/ai_letters.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Telegram/Resources/icons/chat/ai_star1.svg b/Telegram/Resources/icons/chat/ai_star1.svg new file mode 100644 index 00000000000000..4c62840ce6e7b3 --- /dev/null +++ b/Telegram/Resources/icons/chat/ai_star1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/chat/ai_star2.svg b/Telegram/Resources/icons/chat/ai_star2.svg new file mode 100644 index 00000000000000..45d06aefd76fb6 --- /dev/null +++ b/Telegram/Resources/icons/chat/ai_star2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/chat/ai_style_tone.svg b/Telegram/Resources/icons/chat/ai_style_tone.svg new file mode 100644 index 00000000000000..778574b6790deb --- /dev/null +++ b/Telegram/Resources/icons/chat/ai_style_tone.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Telegram/Resources/icons/chat/code_tags.svg b/Telegram/Resources/icons/chat/code_tags.svg new file mode 100644 index 00000000000000..f03e9ce1b3a53b --- /dev/null +++ b/Telegram/Resources/icons/chat/code_tags.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/filled_forge.svg b/Telegram/Resources/icons/chat/filled_forge.svg new file mode 100644 index 00000000000000..02b46ac4817b3a --- /dev/null +++ b/Telegram/Resources/icons/chat/filled_forge.svg @@ -0,0 +1,7 @@ + + + General / filled_forge + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/input_comments_expand.png b/Telegram/Resources/icons/chat/input_comments_expand.png new file mode 100644 index 00000000000000..2a67d963186765 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_expand.png differ diff --git a/Telegram/Resources/icons/chat/input_comments_expand@2x.png b/Telegram/Resources/icons/chat/input_comments_expand@2x.png new file mode 100644 index 00000000000000..5fe041c4ad8ab2 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_expand@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_comments_expand@3x.png b/Telegram/Resources/icons/chat/input_comments_expand@3x.png new file mode 100644 index 00000000000000..4e675a43f0dc2b Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_expand@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_comments_hide.png b/Telegram/Resources/icons/chat/input_comments_hide.png new file mode 100644 index 00000000000000..606fb2c4979c49 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_hide.png differ diff --git a/Telegram/Resources/icons/chat/input_comments_hide@2x.png b/Telegram/Resources/icons/chat/input_comments_hide@2x.png new file mode 100644 index 00000000000000..233fd06490ecc2 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_hide@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_comments_hide@3x.png b/Telegram/Resources/icons/chat/input_comments_hide@3x.png new file mode 100644 index 00000000000000..e6f6d925c0e5b1 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_comments_hide@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_gift.png b/Telegram/Resources/icons/chat/input_gift.png new file mode 100644 index 00000000000000..eac02d4b2a04b1 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_gift.png differ diff --git a/Telegram/Resources/icons/chat/input_gift@2x.png b/Telegram/Resources/icons/chat/input_gift@2x.png new file mode 100644 index 00000000000000..51c3472a6e27ce Binary files /dev/null and b/Telegram/Resources/icons/chat/input_gift@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_gift@3x.png b/Telegram/Resources/icons/chat/input_gift@3x.png new file mode 100644 index 00000000000000..d88028bb459bbf Binary files /dev/null and b/Telegram/Resources/icons/chat/input_gift@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_paid.svg b/Telegram/Resources/icons/chat/input_paid.svg new file mode 100644 index 00000000000000..1179751c9a6d9e --- /dev/null +++ b/Telegram/Resources/icons/chat/input_paid.svg @@ -0,0 +1,8 @@ + + + Icon / Input / input_paid + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/input_send_round.png b/Telegram/Resources/icons/chat/input_send_round.png new file mode 100644 index 00000000000000..7052e9116defd8 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_send_round.png differ diff --git a/Telegram/Resources/icons/chat/input_send_round@2x.png b/Telegram/Resources/icons/chat/input_send_round@2x.png new file mode 100644 index 00000000000000..92b3b02c7f00e5 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_send_round@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_send_round@3x.png b/Telegram/Resources/icons/chat/input_send_round@3x.png new file mode 100644 index 00000000000000..f33a744a6214d0 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_send_round@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_video.png b/Telegram/Resources/icons/chat/input_video.png new file mode 100644 index 00000000000000..34a71853d435f3 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video.png differ diff --git a/Telegram/Resources/icons/chat/input_video@2x.png b/Telegram/Resources/icons/chat/input_video@2x.png new file mode 100644 index 00000000000000..0657613a8aae90 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_video@3x.png b/Telegram/Resources/icons/chat/input_video@3x.png new file mode 100644 index 00000000000000..9fb24d542a5c35 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video@3x.png differ diff --git a/Telegram/Resources/icons/chat/large_messages.png b/Telegram/Resources/icons/chat/large_messages.png new file mode 100644 index 00000000000000..f2b2fda9d0633d Binary files /dev/null and b/Telegram/Resources/icons/chat/large_messages.png differ diff --git a/Telegram/Resources/icons/chat/large_messages@2x.png b/Telegram/Resources/icons/chat/large_messages@2x.png new file mode 100644 index 00000000000000..c4a0821ec60db8 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_messages@2x.png differ diff --git a/Telegram/Resources/icons/chat/large_messages@3x.png b/Telegram/Resources/icons/chat/large_messages@3x.png new file mode 100644 index 00000000000000..1d5b682e32d250 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_messages@3x.png differ diff --git a/Telegram/Resources/icons/chat/large_user_tag.svg b/Telegram/Resources/icons/chat/large_user_tag.svg new file mode 100644 index 00000000000000..ed97783eba20fa --- /dev/null +++ b/Telegram/Resources/icons/chat/large_user_tag.svg @@ -0,0 +1,7 @@ + + + Other / Large / large_user_tag + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/markup_webview.png b/Telegram/Resources/icons/chat/markup_webview.png new file mode 100644 index 00000000000000..801937d7c6bc79 Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview.png differ diff --git a/Telegram/Resources/icons/chat/markup_webview@2x.png b/Telegram/Resources/icons/chat/markup_webview@2x.png new file mode 100644 index 00000000000000..068f4f00b5ecb3 Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview@2x.png differ diff --git a/Telegram/Resources/icons/chat/markup_webview@3x.png b/Telegram/Resources/icons/chat/markup_webview@3x.png new file mode 100644 index 00000000000000..d52aa42c98d39d Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview@3x.png differ diff --git a/Telegram/Resources/icons/chat/menu_sidebar1.svg b/Telegram/Resources/icons/chat/menu_sidebar1.svg new file mode 100644 index 00000000000000..ae5d1b0c451e50 --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar1.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar1 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/menu_sidebar2.svg b/Telegram/Resources/icons/chat/menu_sidebar2.svg new file mode 100644 index 00000000000000..177ce3e81d012d --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar2.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar2 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/menu_sidebar3.svg b/Telegram/Resources/icons/chat/menu_sidebar3.svg new file mode 100644 index 00000000000000..7d47fa76829d82 --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar3.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar3 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/mini_gift_hidden.png b/Telegram/Resources/icons/chat/mini_gift_hidden.png new file mode 100644 index 00000000000000..3c7afb68fbdb44 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_gift_hidden.png differ diff --git a/Telegram/Resources/icons/chat/mini_gift_hidden@2x.png b/Telegram/Resources/icons/chat/mini_gift_hidden@2x.png new file mode 100644 index 00000000000000..c212d24dfdfa0a Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_gift_hidden@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_gift_hidden@3x.png b/Telegram/Resources/icons/chat/mini_gift_hidden@3x.png new file mode 100644 index 00000000000000..cb21e63b36433c Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_gift_hidden@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_info_alert.png b/Telegram/Resources/icons/chat/mini_info_alert.png new file mode 100644 index 00000000000000..8c79b97d3ba509 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_info_alert.png differ diff --git a/Telegram/Resources/icons/chat/mini_info_alert@2x.png b/Telegram/Resources/icons/chat/mini_info_alert@2x.png new file mode 100644 index 00000000000000..e36a5ed4105a53 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_info_alert@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_info_alert@3x.png b/Telegram/Resources/icons/chat/mini_info_alert@3x.png new file mode 100644 index 00000000000000..1c8cfe8edec237 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_info_alert@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_roll.svg b/Telegram/Resources/icons/chat/mini_roll.svg new file mode 100644 index 00000000000000..158f3d10b88476 --- /dev/null +++ b/Telegram/Resources/icons/chat/mini_roll.svg @@ -0,0 +1,7 @@ + + + Mini / mini_roll + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/mini_shield_lock.svg b/Telegram/Resources/icons/chat/mini_shield_lock.svg new file mode 100644 index 00000000000000..cd3713bc3e7bf4 --- /dev/null +++ b/Telegram/Resources/icons/chat/mini_shield_lock.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/chat/mini_ton_bold.svg b/Telegram/Resources/icons/chat/mini_ton_bold.svg new file mode 100644 index 00000000000000..a81c8e7491856a --- /dev/null +++ b/Telegram/Resources/icons/chat/mini_ton_bold.svg @@ -0,0 +1,7 @@ + + + Icon / Mini / mini_ton_bold + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/new_thread.svg b/Telegram/Resources/icons/chat/new_thread.svg new file mode 100644 index 00000000000000..bcf13e4ce5cdd2 --- /dev/null +++ b/Telegram/Resources/icons/chat/new_thread.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/new_topic.svg b/Telegram/Resources/icons/chat/new_topic.svg new file mode 100644 index 00000000000000..51edcba31bd8e8 --- /dev/null +++ b/Telegram/Resources/icons/chat/new_topic.svg @@ -0,0 +1,9 @@ + + + Icon / Menu / new_topic + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/paid_approve.svg b/Telegram/Resources/icons/chat/paid_approve.svg new file mode 100644 index 00000000000000..c122dca22d2f74 --- /dev/null +++ b/Telegram/Resources/icons/chat/paid_approve.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / paid_approve + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/paid_decline.svg b/Telegram/Resources/icons/chat/paid_decline.svg new file mode 100644 index 00000000000000..66f52af7ea30e8 --- /dev/null +++ b/Telegram/Resources/icons/chat/paid_decline.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / paid_decline + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/paid_edit.svg b/Telegram/Resources/icons/chat/paid_edit.svg new file mode 100644 index 00000000000000..5b4ec4a4890266 --- /dev/null +++ b/Telegram/Resources/icons/chat/paid_edit.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / paid_edit + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/refresh.svg b/Telegram/Resources/icons/chat/refresh.svg new file mode 100644 index 00000000000000..10e1e400ace30d --- /dev/null +++ b/Telegram/Resources/icons/chat/refresh.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/summary_arrows.svg b/Telegram/Resources/icons/chat/summary_arrows.svg new file mode 100644 index 00000000000000..97d5de2d02de69 --- /dev/null +++ b/Telegram/Resources/icons/chat/summary_arrows.svg @@ -0,0 +1,3 @@ + + + diff --git a/Telegram/Resources/icons/chat/summary_stars.svg b/Telegram/Resources/icons/chat/summary_stars.svg new file mode 100644 index 00000000000000..f6ac6f60398621 --- /dev/null +++ b/Telegram/Resources/icons/chat/summary_stars.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Telegram/Resources/icons/chat/text_to_file.svg b/Telegram/Resources/icons/chat/text_to_file.svg new file mode 100644 index 00000000000000..d3d7ce7039599c --- /dev/null +++ b/Telegram/Resources/icons/chat/text_to_file.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/dialogs/dialogs_chatlist_mention.svg b/Telegram/Resources/icons/dialogs/dialogs_chatlist_mention.svg new file mode 100644 index 00000000000000..7eaf976ae69e7b --- /dev/null +++ b/Telegram/Resources/icons/dialogs/dialogs_chatlist_mention.svg @@ -0,0 +1,7 @@ + + + Filled / filled_chatlist_mention + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/dialogs/dialogs_chatlist_poll.svg b/Telegram/Resources/icons/dialogs/dialogs_chatlist_poll.svg new file mode 100644 index 00000000000000..95226d86f3a726 --- /dev/null +++ b/Telegram/Resources/icons/dialogs/dialogs_chatlist_poll.svg @@ -0,0 +1,7 @@ + + + Filled / filled_chatlist_poll + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/dialogs/dialogs_chatlist_reaction.svg b/Telegram/Resources/icons/dialogs/dialogs_chatlist_reaction.svg new file mode 100644 index 00000000000000..901a7a9825dd59 --- /dev/null +++ b/Telegram/Resources/icons/dialogs/dialogs_chatlist_reaction.svg @@ -0,0 +1,7 @@ + + + Filled / filled_chatlist_reaction + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/dialogs/dialogs_mute.png b/Telegram/Resources/icons/dialogs/dialogs_mute.png new file mode 100644 index 00000000000000..3b37e1172dbf7a Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_mute.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_mute@2x.png b/Telegram/Resources/icons/dialogs/dialogs_mute@2x.png new file mode 100644 index 00000000000000..96681eb66f571f Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_mute@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_mute@3x.png b/Telegram/Resources/icons/dialogs/dialogs_mute@3x.png new file mode 100644 index 00000000000000..a246b7e6a5c023 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_mute@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_unread_media_dot.svg b/Telegram/Resources/icons/dialogs/dialogs_unread_media_dot.svg new file mode 100644 index 00000000000000..d0da6ff5fc4420 --- /dev/null +++ b/Telegram/Resources/icons/dialogs/dialogs_unread_media_dot.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Telegram/Resources/icons/history_unread_poll_vote.svg b/Telegram/Resources/icons/history_unread_poll_vote.svg new file mode 100644 index 00000000000000..ccc1f67190bfb2 --- /dev/null +++ b/Telegram/Resources/icons/history_unread_poll_vote.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Telegram/Resources/icons/info/info_iv_mini.svg b/Telegram/Resources/icons/info/info_iv_mini.svg new file mode 100644 index 00000000000000..89530ebdcf8963 --- /dev/null +++ b/Telegram/Resources/icons/info/info_iv_mini.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/levels/level10_inner.svg b/Telegram/Resources/icons/levels/level10_inner.svg new file mode 100644 index 00000000000000..8ce8b2c9b2000d --- /dev/null +++ b/Telegram/Resources/icons/levels/level10_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level10_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level1_inner.svg b/Telegram/Resources/icons/levels/level1_inner.svg new file mode 100644 index 00000000000000..37959bcb5d8e07 --- /dev/null +++ b/Telegram/Resources/icons/levels/level1_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level1_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level20_inner.svg b/Telegram/Resources/icons/levels/level20_inner.svg new file mode 100644 index 00000000000000..055e99e5c3d614 --- /dev/null +++ b/Telegram/Resources/icons/levels/level20_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level20_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level2_inner.svg b/Telegram/Resources/icons/levels/level2_inner.svg new file mode 100644 index 00000000000000..2cd67761cc6bc7 --- /dev/null +++ b/Telegram/Resources/icons/levels/level2_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level2_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level30_inner.svg b/Telegram/Resources/icons/levels/level30_inner.svg new file mode 100644 index 00000000000000..b74a2e6efbaf16 --- /dev/null +++ b/Telegram/Resources/icons/levels/level30_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level30_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level3_inner.svg b/Telegram/Resources/icons/levels/level3_inner.svg new file mode 100644 index 00000000000000..3bc0ef35050d3c --- /dev/null +++ b/Telegram/Resources/icons/levels/level3_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level3_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level40_inner.svg b/Telegram/Resources/icons/levels/level40_inner.svg new file mode 100644 index 00000000000000..f7fef2c1678081 --- /dev/null +++ b/Telegram/Resources/icons/levels/level40_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level40_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level4_inner.svg b/Telegram/Resources/icons/levels/level4_inner.svg new file mode 100644 index 00000000000000..b23971ce553bd9 --- /dev/null +++ b/Telegram/Resources/icons/levels/level4_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level4_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level50_inner.svg b/Telegram/Resources/icons/levels/level50_inner.svg new file mode 100644 index 00000000000000..23aa9e3107cb30 --- /dev/null +++ b/Telegram/Resources/icons/levels/level50_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level50_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level5_inner.svg b/Telegram/Resources/icons/levels/level5_inner.svg new file mode 100644 index 00000000000000..e28cd650ab1750 --- /dev/null +++ b/Telegram/Resources/icons/levels/level5_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level5_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level60_inner.svg b/Telegram/Resources/icons/levels/level60_inner.svg new file mode 100644 index 00000000000000..b006ec32809a0c --- /dev/null +++ b/Telegram/Resources/icons/levels/level60_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level60_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level6_inner.svg b/Telegram/Resources/icons/levels/level6_inner.svg new file mode 100644 index 00000000000000..1b8a68d0dab68a --- /dev/null +++ b/Telegram/Resources/icons/levels/level6_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level6_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level70_inner.svg b/Telegram/Resources/icons/levels/level70_inner.svg new file mode 100644 index 00000000000000..d6bc1b8b4ad31f --- /dev/null +++ b/Telegram/Resources/icons/levels/level70_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level70_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level7_inner.svg b/Telegram/Resources/icons/levels/level7_inner.svg new file mode 100644 index 00000000000000..d0a0c79299108a --- /dev/null +++ b/Telegram/Resources/icons/levels/level7_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level7_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level80_inner.svg b/Telegram/Resources/icons/levels/level80_inner.svg new file mode 100644 index 00000000000000..a469b74988cee9 --- /dev/null +++ b/Telegram/Resources/icons/levels/level80_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level80_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level8_inner.svg b/Telegram/Resources/icons/levels/level8_inner.svg new file mode 100644 index 00000000000000..f4a0d3b7d11b0e --- /dev/null +++ b/Telegram/Resources/icons/levels/level8_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level8_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level90_inner.svg b/Telegram/Resources/icons/levels/level90_inner.svg new file mode 100644 index 00000000000000..d25a731d93dd11 --- /dev/null +++ b/Telegram/Resources/icons/levels/level90_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level90_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level9_inner.svg b/Telegram/Resources/icons/levels/level9_inner.svg new file mode 100644 index 00000000000000..27fe8c25a2b115 --- /dev/null +++ b/Telegram/Resources/icons/levels/level9_inner.svg @@ -0,0 +1,7 @@ + + + Badge / level9_inner + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/levels/level_warning.svg b/Telegram/Resources/icons/levels/level_warning.svg new file mode 100644 index 00000000000000..cba1db00765c86 --- /dev/null +++ b/Telegram/Resources/icons/levels/level_warning.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_warning + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/limits/filled_rating_crown.svg b/Telegram/Resources/icons/limits/filled_rating_crown.svg new file mode 100644 index 00000000000000..e7cc2e124f588f --- /dev/null +++ b/Telegram/Resources/icons/limits/filled_rating_crown.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_rating_crown + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/limits/filled_understood.svg b/Telegram/Resources/icons/limits/filled_understood.svg new file mode 100644 index 00000000000000..337b43cc71812b --- /dev/null +++ b/Telegram/Resources/icons/limits/filled_understood.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / Folder / filled_understood + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/limits/mini_gift_lock.svg b/Telegram/Resources/icons/limits/mini_gift_lock.svg new file mode 100644 index 00000000000000..2887a84d4c2a07 --- /dev/null +++ b/Telegram/Resources/icons/limits/mini_gift_lock.svg @@ -0,0 +1,7 @@ + + + Mini / mini_gift_lock + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/local_storage/documents.svg b/Telegram/Resources/icons/local_storage/documents.svg new file mode 100644 index 00000000000000..a9e90e536d6d17 --- /dev/null +++ b/Telegram/Resources/icons/local_storage/documents.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/local_storage/music.svg b/Telegram/Resources/icons/local_storage/music.svg new file mode 100644 index 00000000000000..2909f5a288b310 --- /dev/null +++ b/Telegram/Resources/icons/local_storage/music.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/local_storage/photos.svg b/Telegram/Resources/icons/local_storage/photos.svg new file mode 100644 index 00000000000000..a23f8afb3bf25c --- /dev/null +++ b/Telegram/Resources/icons/local_storage/photos.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/local_storage/stickers.svg b/Telegram/Resources/icons/local_storage/stickers.svg new file mode 100644 index 00000000000000..a72cfb16f94c30 --- /dev/null +++ b/Telegram/Resources/icons/local_storage/stickers.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/local_storage/videos.svg b/Telegram/Resources/icons/local_storage/videos.svg new file mode 100644 index 00000000000000..3ef2b7d2220c3d --- /dev/null +++ b/Telegram/Resources/icons/local_storage/videos.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/mediaview/draw.png b/Telegram/Resources/icons/mediaview/draw.png new file mode 100644 index 00000000000000..96a1d8086eac53 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/draw.png differ diff --git a/Telegram/Resources/icons/mediaview/draw@2x.png b/Telegram/Resources/icons/mediaview/draw@2x.png new file mode 100644 index 00000000000000..40da849c7e0ab1 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/draw@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/draw@3x.png b/Telegram/Resources/icons/mediaview/draw@3x.png new file mode 100644 index 00000000000000..22ee4fb7bbb928 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/draw@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/recognize.png b/Telegram/Resources/icons/mediaview/recognize.png new file mode 100644 index 00000000000000..05941e32466ba4 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/recognize.png differ diff --git a/Telegram/Resources/icons/mediaview/recognize@2x.png b/Telegram/Resources/icons/mediaview/recognize@2x.png new file mode 100644 index 00000000000000..bc86664b691b73 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/recognize@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/recognize@3x.png b/Telegram/Resources/icons/mediaview/recognize@3x.png new file mode 100644 index 00000000000000..0be9ab5e32c9d2 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/recognize@3x.png differ diff --git a/Telegram/Resources/icons/menu/2sv_off.png b/Telegram/Resources/icons/menu/2sv_off.png new file mode 100644 index 00000000000000..78a288600010b8 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_off.png differ diff --git a/Telegram/Resources/icons/menu/2sv_off@2x.png b/Telegram/Resources/icons/menu/2sv_off@2x.png new file mode 100644 index 00000000000000..98cae67216e536 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_off@2x.png differ diff --git a/Telegram/Resources/icons/menu/2sv_off@3x.png b/Telegram/Resources/icons/menu/2sv_off@3x.png new file mode 100644 index 00000000000000..cf1b017baea776 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_off@3x.png differ diff --git a/Telegram/Resources/icons/menu/2sv_on.png b/Telegram/Resources/icons/menu/2sv_on.png new file mode 100644 index 00000000000000..f7089daa1feeb4 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_on.png differ diff --git a/Telegram/Resources/icons/menu/2sv_on@2x.png b/Telegram/Resources/icons/menu/2sv_on@2x.png new file mode 100644 index 00000000000000..021ec634e684a4 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_on@2x.png differ diff --git a/Telegram/Resources/icons/menu/2sv_on@3x.png b/Telegram/Resources/icons/menu/2sv_on@3x.png new file mode 100644 index 00000000000000..5e74c1fcc39441 Binary files /dev/null and b/Telegram/Resources/icons/menu/2sv_on@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/stickers_add.png b/Telegram/Resources/icons/menu/add.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add.png rename to Telegram/Resources/icons/menu/add.png diff --git a/Telegram/Resources/icons/info/edit/stickers_add@2x.png b/Telegram/Resources/icons/menu/add@2x.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add@2x.png rename to Telegram/Resources/icons/menu/add@2x.png diff --git a/Telegram/Resources/icons/info/edit/stickers_add@3x.png b/Telegram/Resources/icons/menu/add@3x.png similarity index 100% rename from Telegram/Resources/icons/info/edit/stickers_add@3x.png rename to Telegram/Resources/icons/menu/add@3x.png diff --git a/Telegram/Resources/icons/menu/affiliate_simple.png b/Telegram/Resources/icons/menu/affiliate_simple.png new file mode 100644 index 00000000000000..c09ce88fc2fab0 Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_simple.png differ diff --git a/Telegram/Resources/icons/menu/affiliate_simple@2x.png b/Telegram/Resources/icons/menu/affiliate_simple@2x.png new file mode 100644 index 00000000000000..3e41ea4277cfef Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_simple@2x.png differ diff --git a/Telegram/Resources/icons/menu/affiliate_simple@3x.png b/Telegram/Resources/icons/menu/affiliate_simple@3x.png new file mode 100644 index 00000000000000..d075a10bd78037 Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_simple@3x.png differ diff --git a/Telegram/Resources/icons/menu/affiliate_transparent.png b/Telegram/Resources/icons/menu/affiliate_transparent.png new file mode 100644 index 00000000000000..abcd00046ed9b5 Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_transparent.png differ diff --git a/Telegram/Resources/icons/menu/affiliate_transparent@2x.png b/Telegram/Resources/icons/menu/affiliate_transparent@2x.png new file mode 100644 index 00000000000000..ffbc05cba68aee Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_transparent@2x.png differ diff --git a/Telegram/Resources/icons/menu/affiliate_transparent@3x.png b/Telegram/Resources/icons/menu/affiliate_transparent@3x.png new file mode 100644 index 00000000000000..54185c44c8d5a3 Binary files /dev/null and b/Telegram/Resources/icons/menu/affiliate_transparent@3x.png differ diff --git a/Telegram/Resources/icons/menu/article_rich.svg b/Telegram/Resources/icons/menu/article_rich.svg new file mode 100644 index 00000000000000..2d7ce183ef6aaa --- /dev/null +++ b/Telegram/Resources/icons/menu/article_rich.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Telegram/Resources/icons/menu/auction_carry.png b/Telegram/Resources/icons/menu/auction_carry.png new file mode 100644 index 00000000000000..d63bbb745f38b0 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_carry.png differ diff --git a/Telegram/Resources/icons/menu/auction_carry@2x.png b/Telegram/Resources/icons/menu/auction_carry@2x.png new file mode 100644 index 00000000000000..dd5367f9434cf5 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_carry@2x.png differ diff --git a/Telegram/Resources/icons/menu/auction_carry@3x.png b/Telegram/Resources/icons/menu/auction_carry@3x.png new file mode 100644 index 00000000000000..c8f18887378ee6 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_carry@3x.png differ diff --git a/Telegram/Resources/icons/menu/auction_drop.png b/Telegram/Resources/icons/menu/auction_drop.png new file mode 100644 index 00000000000000..d1b3af2c3366c1 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_drop.png differ diff --git a/Telegram/Resources/icons/menu/auction_drop@2x.png b/Telegram/Resources/icons/menu/auction_drop@2x.png new file mode 100644 index 00000000000000..3ced06af81e8f2 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_drop@2x.png differ diff --git a/Telegram/Resources/icons/menu/auction_drop@3x.png b/Telegram/Resources/icons/menu/auction_drop@3x.png new file mode 100644 index 00000000000000..c2f58a2377827f Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_drop@3x.png differ diff --git a/Telegram/Resources/icons/menu/auction_refund.png b/Telegram/Resources/icons/menu/auction_refund.png new file mode 100644 index 00000000000000..aebc536a0ff77c Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_refund.png differ diff --git a/Telegram/Resources/icons/menu/auction_refund@2x.png b/Telegram/Resources/icons/menu/auction_refund@2x.png new file mode 100644 index 00000000000000..5cc6db3dcbb159 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_refund@2x.png differ diff --git a/Telegram/Resources/icons/menu/auction_refund@3x.png b/Telegram/Resources/icons/menu/auction_refund@3x.png new file mode 100644 index 00000000000000..eb9f032df0a350 Binary files /dev/null and b/Telegram/Resources/icons/menu/auction_refund@3x.png differ diff --git a/Telegram/Resources/icons/menu/bot.png b/Telegram/Resources/icons/menu/bot.png new file mode 100644 index 00000000000000..5a890cf8b945d0 Binary files /dev/null and b/Telegram/Resources/icons/menu/bot.png differ diff --git a/Telegram/Resources/icons/menu/bot@2x.png b/Telegram/Resources/icons/menu/bot@2x.png new file mode 100644 index 00000000000000..aaa8dc9afd69e2 Binary files /dev/null and b/Telegram/Resources/icons/menu/bot@2x.png differ diff --git a/Telegram/Resources/icons/menu/bot@3x.png b/Telegram/Resources/icons/menu/bot@3x.png new file mode 100644 index 00000000000000..59a23832b3a875 Binary files /dev/null and b/Telegram/Resources/icons/menu/bot@3x.png differ diff --git a/Telegram/Resources/icons/menu/bot_add.png b/Telegram/Resources/icons/menu/bot_add.png new file mode 100644 index 00000000000000..aadbec32dcf2c5 Binary files /dev/null and b/Telegram/Resources/icons/menu/bot_add.png differ diff --git a/Telegram/Resources/icons/menu/bot_add@2x.png b/Telegram/Resources/icons/menu/bot_add@2x.png new file mode 100644 index 00000000000000..0eea54a256c1f4 Binary files /dev/null and b/Telegram/Resources/icons/menu/bot_add@2x.png differ diff --git a/Telegram/Resources/icons/menu/bot_add@3x.png b/Telegram/Resources/icons/menu/bot_add@3x.png new file mode 100644 index 00000000000000..e89bb32eb0ce2f Binary files /dev/null and b/Telegram/Resources/icons/menu/bot_add@3x.png differ diff --git a/Telegram/Resources/icons/menu/cancel_fee.png b/Telegram/Resources/icons/menu/cancel_fee.png new file mode 100644 index 00000000000000..467c369cec28a1 Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee.png differ diff --git a/Telegram/Resources/icons/menu/cancel_fee@2x.png b/Telegram/Resources/icons/menu/cancel_fee@2x.png new file mode 100644 index 00000000000000..75c0a6c94192a4 Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@2x.png differ diff --git a/Telegram/Resources/icons/menu/cancel_fee@3x.png b/Telegram/Resources/icons/menu/cancel_fee@3x.png new file mode 100644 index 00000000000000..73fee9f98a6b70 Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@3x.png differ diff --git a/Telegram/Resources/icons/menu/caption_hide.png b/Telegram/Resources/icons/menu/caption_hide.png new file mode 100644 index 00000000000000..0320b2e2e78397 Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_hide.png differ diff --git a/Telegram/Resources/icons/menu/caption_hide@2x.png b/Telegram/Resources/icons/menu/caption_hide@2x.png new file mode 100644 index 00000000000000..6b9169008e917b Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_hide@2x.png differ diff --git a/Telegram/Resources/icons/menu/caption_hide@3x.png b/Telegram/Resources/icons/menu/caption_hide@3x.png new file mode 100644 index 00000000000000..75d4841d1b2f59 Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_hide@3x.png differ diff --git a/Telegram/Resources/icons/menu/caption_show.png b/Telegram/Resources/icons/menu/caption_show.png new file mode 100644 index 00000000000000..9c7b05c6399168 Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_show.png differ diff --git a/Telegram/Resources/icons/menu/caption_show@2x.png b/Telegram/Resources/icons/menu/caption_show@2x.png new file mode 100644 index 00000000000000..962eeaee1b5cc7 Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_show@2x.png differ diff --git a/Telegram/Resources/icons/menu/caption_show@3x.png b/Telegram/Resources/icons/menu/caption_show@3x.png new file mode 100644 index 00000000000000..4dd44a6097c193 Binary files /dev/null and b/Telegram/Resources/icons/menu/caption_show@3x.png differ diff --git a/Telegram/Resources/icons/menu/craft_chance.svg b/Telegram/Resources/icons/menu/craft_chance.svg new file mode 100644 index 00000000000000..c3ea4c40dba6f8 --- /dev/null +++ b/Telegram/Resources/icons/menu/craft_chance.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / craft_chance + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/craft_random.svg b/Telegram/Resources/icons/menu/craft_random.svg new file mode 100644 index 00000000000000..afaf213c10569e --- /dev/null +++ b/Telegram/Resources/icons/menu/craft_random.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / craft_random + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/craft_start.svg b/Telegram/Resources/icons/menu/craft_start.svg new file mode 100644 index 00000000000000..5a8067fbbf6dc8 --- /dev/null +++ b/Telegram/Resources/icons/menu/craft_start.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / craft_forge + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/craft_tools.svg b/Telegram/Resources/icons/menu/craft_tools.svg new file mode 100644 index 00000000000000..db111821bd6c11 --- /dev/null +++ b/Telegram/Resources/icons/menu/craft_tools.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / craft_tools + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/download_off.svg b/Telegram/Resources/icons/menu/download_off.svg new file mode 100644 index 00000000000000..bac54d5525eea4 --- /dev/null +++ b/Telegram/Resources/icons/menu/download_off.svg @@ -0,0 +1,7 @@ + + + General / menu_download_off + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/edit_stars.svg b/Telegram/Resources/icons/menu/edit_stars.svg new file mode 100644 index 00000000000000..228638acb7470f --- /dev/null +++ b/Telegram/Resources/icons/menu/edit_stars.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/edit_stars_add.svg b/Telegram/Resources/icons/menu/edit_stars_add.svg new file mode 100644 index 00000000000000..04b6a8c915ece5 --- /dev/null +++ b/Telegram/Resources/icons/menu/edit_stars_add.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Telegram/Resources/icons/menu/edited_status.png b/Telegram/Resources/icons/menu/edited_status.png new file mode 100644 index 00000000000000..70fb24466a33f9 Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status.png differ diff --git a/Telegram/Resources/icons/menu/edited_status@2x.png b/Telegram/Resources/icons/menu/edited_status@2x.png new file mode 100644 index 00000000000000..004167a148f30c Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status@2x.png differ diff --git a/Telegram/Resources/icons/menu/edited_status@3x.png b/Telegram/Resources/icons/menu/edited_status@3x.png new file mode 100644 index 00000000000000..1acdf8ea936009 Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status@3x.png differ diff --git a/Telegram/Resources/icons/menu/forwarded_status.png b/Telegram/Resources/icons/menu/forwarded_status.png new file mode 100644 index 00000000000000..66d08a835cea53 Binary files /dev/null and b/Telegram/Resources/icons/menu/forwarded_status.png differ diff --git a/Telegram/Resources/icons/menu/forwarded_status@2x.png b/Telegram/Resources/icons/menu/forwarded_status@2x.png new file mode 100644 index 00000000000000..8892f4ce311519 Binary files /dev/null and b/Telegram/Resources/icons/menu/forwarded_status@2x.png differ diff --git a/Telegram/Resources/icons/menu/forwarded_status@3x.png b/Telegram/Resources/icons/menu/forwarded_status@3x.png new file mode 100644 index 00000000000000..e2940b1636c72d Binary files /dev/null and b/Telegram/Resources/icons/menu/forwarded_status@3x.png differ diff --git a/Telegram/Resources/icons/menu/hourglass.png b/Telegram/Resources/icons/menu/hourglass.png new file mode 100644 index 00000000000000..e7389fb11a82c6 Binary files /dev/null and b/Telegram/Resources/icons/menu/hourglass.png differ diff --git a/Telegram/Resources/icons/menu/hourglass@2x.png b/Telegram/Resources/icons/menu/hourglass@2x.png new file mode 100644 index 00000000000000..69a579183a2515 Binary files /dev/null and b/Telegram/Resources/icons/menu/hourglass@2x.png differ diff --git a/Telegram/Resources/icons/menu/hourglass@3x.png b/Telegram/Resources/icons/menu/hourglass@3x.png new file mode 100644 index 00000000000000..0fcfcfab9a285c Binary files /dev/null and b/Telegram/Resources/icons/menu/hourglass@3x.png differ diff --git a/Telegram/Resources/icons/menu/name_hide.png b/Telegram/Resources/icons/menu/name_hide.png new file mode 100644 index 00000000000000..0d6bb287ae7914 Binary files /dev/null and b/Telegram/Resources/icons/menu/name_hide.png differ diff --git a/Telegram/Resources/icons/menu/name_hide@2x.png b/Telegram/Resources/icons/menu/name_hide@2x.png new file mode 100644 index 00000000000000..8903e549d4fb34 Binary files /dev/null and b/Telegram/Resources/icons/menu/name_hide@2x.png differ diff --git a/Telegram/Resources/icons/menu/name_hide@3x.png b/Telegram/Resources/icons/menu/name_hide@3x.png new file mode 100644 index 00000000000000..88b94bc5fbb8f9 Binary files /dev/null and b/Telegram/Resources/icons/menu/name_hide@3x.png differ diff --git a/Telegram/Resources/icons/menu/name_show.png b/Telegram/Resources/icons/menu/name_show.png new file mode 100644 index 00000000000000..93cad5d875601f Binary files /dev/null and b/Telegram/Resources/icons/menu/name_show.png differ diff --git a/Telegram/Resources/icons/menu/name_show@2x.png b/Telegram/Resources/icons/menu/name_show@2x.png new file mode 100644 index 00000000000000..486d0a24def846 Binary files /dev/null and b/Telegram/Resources/icons/menu/name_show@2x.png differ diff --git a/Telegram/Resources/icons/menu/name_show@3x.png b/Telegram/Resources/icons/menu/name_show@3x.png new file mode 100644 index 00000000000000..20418ed26fd3d2 Binary files /dev/null and b/Telegram/Resources/icons/menu/name_show@3x.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff.png b/Telegram/Resources/icons/menu/nft_takeoff.png new file mode 100644 index 00000000000000..464d149bdca3a7 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff@2x.png b/Telegram/Resources/icons/menu/nft_takeoff@2x.png new file mode 100644 index 00000000000000..6603a81947ec0c Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff@2x.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff@3x.png b/Telegram/Resources/icons/menu/nft_takeoff@3x.png new file mode 100644 index 00000000000000..bd0d91eaf9e5ae Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff@3x.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear.png b/Telegram/Resources/icons/menu/nft_wear.png new file mode 100644 index 00000000000000..868711c1b7bd46 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear@2x.png b/Telegram/Resources/icons/menu/nft_wear@2x.png new file mode 100644 index 00000000000000..d94838dbef3d25 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear@2x.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear@3x.png b/Telegram/Resources/icons/menu/nft_wear@3x.png new file mode 100644 index 00000000000000..35545df8963d06 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear@3x.png differ diff --git a/Telegram/Resources/icons/menu/order_date.png b/Telegram/Resources/icons/menu/order_date.png new file mode 100644 index 00000000000000..c5c1391e4d85a0 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date.png differ diff --git a/Telegram/Resources/icons/menu/order_date@2x.png b/Telegram/Resources/icons/menu/order_date@2x.png new file mode 100644 index 00000000000000..60e5f56da388e6 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_date@3x.png b/Telegram/Resources/icons/menu/order_date@3x.png new file mode 100644 index 00000000000000..9efac756f4d1dd Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date@3x.png differ diff --git a/Telegram/Resources/icons/menu/order_number.png b/Telegram/Resources/icons/menu/order_number.png new file mode 100644 index 00000000000000..1771ee9f71bc5a Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number.png differ diff --git a/Telegram/Resources/icons/menu/order_number@2x.png b/Telegram/Resources/icons/menu/order_number@2x.png new file mode 100644 index 00000000000000..3cd11faad9afb1 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_number@3x.png b/Telegram/Resources/icons/menu/order_number@3x.png new file mode 100644 index 00000000000000..a779b671310ee4 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number@3x.png differ diff --git a/Telegram/Resources/icons/menu/order_price.png b/Telegram/Resources/icons/menu/order_price.png new file mode 100644 index 00000000000000..56f780814ec51a Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price.png differ diff --git a/Telegram/Resources/icons/menu/order_price@2x.png b/Telegram/Resources/icons/menu/order_price@2x.png new file mode 100644 index 00000000000000..ff726b8866196f Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_price@3x.png b/Telegram/Resources/icons/menu/order_price@3x.png new file mode 100644 index 00000000000000..f0e4c871228165 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price@3x.png differ diff --git a/Telegram/Resources/icons/menu/quality_hd.svg b/Telegram/Resources/icons/menu/quality_hd.svg new file mode 100644 index 00000000000000..1d4378221f0d44 --- /dev/null +++ b/Telegram/Resources/icons/menu/quality_hd.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rating_gifts.svg b/Telegram/Resources/icons/menu/rating_gifts.svg new file mode 100644 index 00000000000000..ed3aa9d4b1d9ff --- /dev/null +++ b/Telegram/Resources/icons/menu/rating_gifts.svg @@ -0,0 +1,11 @@ + + + Icon / Menu / rating_gifts + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/rating_refund.svg b/Telegram/Resources/icons/menu/rating_refund.svg new file mode 100644 index 00000000000000..05434da66a2cda --- /dev/null +++ b/Telegram/Resources/icons/menu/rating_refund.svg @@ -0,0 +1,9 @@ + + + Icon / Menu / rating_refund + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/reorder.svg b/Telegram/Resources/icons/menu/reorder.svg new file mode 100644 index 00000000000000..93139b367e1b7b --- /dev/null +++ b/Telegram/Resources/icons/menu/reorder.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / reorder + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/rich/blockquote.svg b/Telegram/Resources/icons/menu/rich/blockquote.svg new file mode 100644 index 00000000000000..7b99a191456aa4 --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/blockquote.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/bullet_list.svg b/Telegram/Resources/icons/menu/rich/bullet_list.svg new file mode 100644 index 00000000000000..36fb9d099c435d --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/bullet_list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/details.svg b/Telegram/Resources/icons/menu/rich/details.svg new file mode 100644 index 00000000000000..2b5bc0c80cb781 --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/details.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/divider.svg b/Telegram/Resources/icons/menu/rich/divider.svg new file mode 100644 index 00000000000000..7e392c330c270a --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/divider.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/heading.svg b/Telegram/Resources/icons/menu/rich/heading.svg new file mode 100644 index 00000000000000..a8f848b4f5381f --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/heading.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/math.svg b/Telegram/Resources/icons/menu/rich/math.svg new file mode 100644 index 00000000000000..064f32eed301be --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/math.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/ordered_list.svg b/Telegram/Resources/icons/menu/rich/ordered_list.svg new file mode 100644 index 00000000000000..5f9d9aa88051c6 --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/ordered_list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/pullquote.svg b/Telegram/Resources/icons/menu/rich/pullquote.svg new file mode 100644 index 00000000000000..e6ee26c9cc85cd --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/pullquote.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/table.svg b/Telegram/Resources/icons/menu/rich/table.svg new file mode 100644 index 00000000000000..ec34938fd87671 --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/table.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/rich/task_list.svg b/Telegram/Resources/icons/menu/rich/task_list.svg new file mode 100644 index 00000000000000..776fef8606a3f0 --- /dev/null +++ b/Telegram/Resources/icons/menu/rich/task_list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/search_check.svg b/Telegram/Resources/icons/menu/search_check.svg new file mode 100644 index 00000000000000..b5e6fa0dd2bd9d --- /dev/null +++ b/Telegram/Resources/icons/menu/search_check.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/menu/share_off.svg b/Telegram/Resources/icons/menu/share_off.svg new file mode 100644 index 00000000000000..96d32e3bda1171 --- /dev/null +++ b/Telegram/Resources/icons/menu/share_off.svg @@ -0,0 +1,9 @@ + + + General / menu_share_off + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/share_on.svg b/Telegram/Resources/icons/menu/share_on.svg new file mode 100644 index 00000000000000..bb8edfe99203e3 --- /dev/null +++ b/Telegram/Resources/icons/menu/share_on.svg @@ -0,0 +1,7 @@ + + + General / menu_share_on + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/shortcut.png b/Telegram/Resources/icons/menu/shortcut.png new file mode 100644 index 00000000000000..e5e3389e979c58 Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut.png differ diff --git a/Telegram/Resources/icons/menu/shortcut@2x.png b/Telegram/Resources/icons/menu/shortcut@2x.png new file mode 100644 index 00000000000000..f50a5dc7103931 Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut@2x.png differ diff --git a/Telegram/Resources/icons/menu/shortcut@3x.png b/Telegram/Resources/icons/menu/shortcut@3x.png new file mode 100644 index 00000000000000..5eb87725c6892b Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut@3x.png differ diff --git a/Telegram/Resources/icons/menu/spoiler_off.png b/Telegram/Resources/icons/menu/spoiler_off.png deleted file mode 100644 index c694fd15da85df..00000000000000 Binary files a/Telegram/Resources/icons/menu/spoiler_off.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu/spoiler_off@2x.png b/Telegram/Resources/icons/menu/spoiler_off@2x.png deleted file mode 100644 index 9400628b8ddbe3..00000000000000 Binary files a/Telegram/Resources/icons/menu/spoiler_off@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu/spoiler_off@3x.png b/Telegram/Resources/icons/menu/spoiler_off@3x.png deleted file mode 100644 index ce6598ad85244d..00000000000000 Binary files a/Telegram/Resources/icons/menu/spoiler_off@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu/star.png b/Telegram/Resources/icons/menu/star.png new file mode 100644 index 00000000000000..4bb1ea334b886b Binary files /dev/null and b/Telegram/Resources/icons/menu/star.png differ diff --git a/Telegram/Resources/icons/menu/star@2x.png b/Telegram/Resources/icons/menu/star@2x.png new file mode 100644 index 00000000000000..a63b6a439a1115 Binary files /dev/null and b/Telegram/Resources/icons/menu/star@2x.png differ diff --git a/Telegram/Resources/icons/menu/star@3x.png b/Telegram/Resources/icons/menu/star@3x.png new file mode 100644 index 00000000000000..d2829cba8c124a Binary files /dev/null and b/Telegram/Resources/icons/menu/star@3x.png differ diff --git a/Telegram/Resources/icons/menu/stars_share.png b/Telegram/Resources/icons/menu/stars_share.png new file mode 100644 index 00000000000000..30871b111bd470 Binary files /dev/null and b/Telegram/Resources/icons/menu/stars_share.png differ diff --git a/Telegram/Resources/icons/menu/stars_share@2x.png b/Telegram/Resources/icons/menu/stars_share@2x.png new file mode 100644 index 00000000000000..e431b7ce48b3f3 Binary files /dev/null and b/Telegram/Resources/icons/menu/stars_share@2x.png differ diff --git a/Telegram/Resources/icons/menu/stars_share@3x.png b/Telegram/Resources/icons/menu/stars_share@3x.png new file mode 100644 index 00000000000000..2e7d149736db07 Binary files /dev/null and b/Telegram/Resources/icons/menu/stars_share@3x.png differ diff --git a/Telegram/Resources/icons/menu/sticker_add.png b/Telegram/Resources/icons/menu/sticker_add.png new file mode 100644 index 00000000000000..0a4ee1dbc7bf85 Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_add.png differ diff --git a/Telegram/Resources/icons/menu/sticker_add@2x.png b/Telegram/Resources/icons/menu/sticker_add@2x.png new file mode 100644 index 00000000000000..72c830ca7419b0 Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_add@2x.png differ diff --git a/Telegram/Resources/icons/menu/sticker_add@3x.png b/Telegram/Resources/icons/menu/sticker_add@3x.png new file mode 100644 index 00000000000000..2bfe690b77290c Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_add@3x.png differ diff --git a/Telegram/Resources/icons/menu/sticker_select.png b/Telegram/Resources/icons/menu/sticker_select.png new file mode 100644 index 00000000000000..788a9a317c0a51 Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_select.png differ diff --git a/Telegram/Resources/icons/menu/sticker_select@2x.png b/Telegram/Resources/icons/menu/sticker_select@2x.png new file mode 100644 index 00000000000000..19cec30c9ba2fa Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_select@2x.png differ diff --git a/Telegram/Resources/icons/menu/sticker_select@3x.png b/Telegram/Resources/icons/menu/sticker_select@3x.png new file mode 100644 index 00000000000000..04238df9887f75 Binary files /dev/null and b/Telegram/Resources/icons/menu/sticker_select@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_add.svg b/Telegram/Resources/icons/menu/tag_add.svg new file mode 100644 index 00000000000000..8ad0a1cb90889f --- /dev/null +++ b/Telegram/Resources/icons/menu/tag_add.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / menu_tag_add + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/tag_edit.svg b/Telegram/Resources/icons/menu/tag_edit.svg new file mode 100644 index 00000000000000..fec18ee156b22b --- /dev/null +++ b/Telegram/Resources/icons/menu/tag_edit.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / menu_tag_edit + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/tag_sell.png b/Telegram/Resources/icons/menu/tag_sell.png new file mode 100644 index 00000000000000..5afc94207683ed Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell.png differ diff --git a/Telegram/Resources/icons/menu/tag_sell@2x.png b/Telegram/Resources/icons/menu/tag_sell@2x.png new file mode 100644 index 00000000000000..bd0cc198f07b63 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_sell@3x.png b/Telegram/Resources/icons/menu/tag_sell@3x.png new file mode 100644 index 00000000000000..da06c7bf1ca44a Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell@3x.png differ diff --git a/Telegram/Resources/icons/menu/text_style_framed.svg b/Telegram/Resources/icons/menu/text_style_framed.svg new file mode 100644 index 00000000000000..de5e3ff7281bf3 --- /dev/null +++ b/Telegram/Resources/icons/menu/text_style_framed.svg @@ -0,0 +1,9 @@ + + + Icon / Menu / text_framed + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/text_style_plain.svg b/Telegram/Resources/icons/menu/text_style_plain.svg new file mode 100644 index 00000000000000..72acd4859609c3 --- /dev/null +++ b/Telegram/Resources/icons/menu/text_style_plain.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / text_plain + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/text_style_semi.svg b/Telegram/Resources/icons/menu/text_style_semi.svg new file mode 100644 index 00000000000000..95cca3188ff004 --- /dev/null +++ b/Telegram/Resources/icons/menu/text_style_semi.svg @@ -0,0 +1,8 @@ + + + Icon / Menu / text_semi + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/tradable.png b/Telegram/Resources/icons/menu/tradable.png new file mode 100644 index 00000000000000..588ad29b9a7b09 Binary files /dev/null and b/Telegram/Resources/icons/menu/tradable.png differ diff --git a/Telegram/Resources/icons/menu/tradable@2x.png b/Telegram/Resources/icons/menu/tradable@2x.png new file mode 100644 index 00000000000000..096f5cf7dd1141 Binary files /dev/null and b/Telegram/Resources/icons/menu/tradable@2x.png differ diff --git a/Telegram/Resources/icons/menu/tradable@3x.png b/Telegram/Resources/icons/menu/tradable@3x.png new file mode 100644 index 00000000000000..fa9ab721330f6c Binary files /dev/null and b/Telegram/Resources/icons/menu/tradable@3x.png differ diff --git a/Telegram/Resources/icons/menu/unique.png b/Telegram/Resources/icons/menu/unique.png new file mode 100644 index 00000000000000..8b1cc60872c3c1 Binary files /dev/null and b/Telegram/Resources/icons/menu/unique.png differ diff --git a/Telegram/Resources/icons/menu/unique@2x.png b/Telegram/Resources/icons/menu/unique@2x.png new file mode 100644 index 00000000000000..774852ecf11330 Binary files /dev/null and b/Telegram/Resources/icons/menu/unique@2x.png differ diff --git a/Telegram/Resources/icons/menu/unique@3x.png b/Telegram/Resources/icons/menu/unique@3x.png new file mode 100644 index 00000000000000..e660a8262ce266 Binary files /dev/null and b/Telegram/Resources/icons/menu/unique@3x.png differ diff --git a/Telegram/Resources/icons/menu/users_stars.svg b/Telegram/Resources/icons/menu/users_stars.svg new file mode 100644 index 00000000000000..0a068f774a1ded --- /dev/null +++ b/Telegram/Resources/icons/menu/users_stars.svg @@ -0,0 +1,10 @@ + + + Icon / Menu / users_stars + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/menu/zoom_in.svg b/Telegram/Resources/icons/menu/zoom_in.svg new file mode 100644 index 00000000000000..ca234f9fba584e --- /dev/null +++ b/Telegram/Resources/icons/menu/zoom_in.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Telegram/Resources/icons/menu/zoom_out.svg b/Telegram/Resources/icons/menu/zoom_out.svg new file mode 100644 index 00000000000000..16d99d24c5d544 --- /dev/null +++ b/Telegram/Resources/icons/menu/zoom_out.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/mini_replace.svg b/Telegram/Resources/icons/mini_replace.svg new file mode 100644 index 00000000000000..86d68953e655d5 --- /dev/null +++ b/Telegram/Resources/icons/mini_replace.svg @@ -0,0 +1,7 @@ + + + Mini / mini_replace2 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/notify_toggle.lottie b/Telegram/Resources/icons/notify_toggle.lottie new file mode 100644 index 00000000000000..e6004d6823fefc --- /dev/null +++ b/Telegram/Resources/icons/notify_toggle.lottie @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":80,"w":512,"h":512,"nm":"IC 2","ddd":0,"assets":[{"id":"comp_0","nm":"Mute_24","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10.812,1.134,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[9.12,8.787],[-9.12,-8.787]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.67,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[100]},{"t":17,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.006,206.214,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.105,0],[0,1.105],[0,0]],"o":[[1.105,0],[0,0],[0,1.105]],"v":[[0,1],[2,-1],[-2,-1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Union","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-7.118,-30.841,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":3,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[13.622,-14.091],[14.062,13.645],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[14.063,13.648],[-13.622,14.086],[0,0],[0,0]],"v":[[-248.877,286.845],[262.123,286.845],[262.123,-225.155],[-195.544,-225.155],[-161.724,-189.838],[-160.913,-139.609],[-211.044,-138.817],[-248.877,-178.009]],"c":true}]},{"t":17,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[13.622,-14.091],[14.062,13.645],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[14.063,13.648],[-13.622,14.086],[0,0],[0,0]],"v":[[-248.877,286.845],[262.123,286.845],[262.123,-225.155],[-195.544,-225.155],[236.258,193.93],[237.068,244.159],[186.937,244.951],[-248.877,-178.009]],"c":true}]}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.644,0],[0,-0.644],[0,0],[-0.003,-0.033],[0,-3.522],[0,0],[0.027,-0.03],[0,0],[-1.708,0],[0,0],[1.148,1.265],[0,0],[0,0.041],[0,0],[3.091,0.677],[0,0.072]],"o":[[0,-0.644],[-0.644,0],[0,0],[0,0.034],[-3.411,0.409],[0,0],[0,0.041],[0,0],[-1.148,1.265],[0,0],[1.708,0],[0,0],[-0.027,-0.03],[0,0],[0,-3.294],[0.012,-0.068],[0,0]],"v":[[1.5,-8.055],[0.333,-9.222],[-0.833,-8.055],[-0.833,-7.722],[-0.828,-7.62],[-6.885,-0.786],[-6.885,3.651],[-6.928,3.762],[-8.897,5.93],[-7.44,9.222],[6.773,9.222],[8.896,5.93],[6.927,3.762],[6.884,3.651],[6.884,-0.786],[1.479,-7.511],[1.5,-7.722]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-2.88,0],[0,-2.88],[0,0],[-0.307,-0.337],[0,0],[0.259,0],[0,0],[-0.174,0.192],[0,0],[0,0.456]],"o":[[0,-2.88],[2.88,0],[0,0],[0,0.456],[0,0],[0.174,0.192],[0,0],[-0.259,0],[0,0],[0.307,-0.337],[0,0]],"v":[[-5.215,-0.786],[0,-6.001],[5.214,-0.786],[5.214,3.651],[5.691,4.885],[7.66,7.053],[7.439,7.552],[-7.44,7.552],[-7.66,7.053],[-5.691,4.885],[-5.215,3.651]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Top 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":2,"s":[0]},{"i":{"x":[0.302],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":8,"s":[-3]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":19,"s":[5]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":28,"s":[-3]},{"t":36,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[255.088,261.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":6,"s":[255.088,241.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":17,"s":[255.088,281.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":26,"s":[255.088,247.008,0],"to":[0,0,0],"ti":[0,0,0]},{"t":34,"s":[255.088,261.008,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.918,5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":5,"op":40,"st":0,"bm":0}]},{"id":"comp_1","nm":"Unmute_24","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Top 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":7,"s":[3]},{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":20,"s":[-5]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":29,"s":[3]},{"t":36,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[255.088,261.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":5,"s":[255.088,281.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":18,"s":[255.088,237.008,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":27,"s":[255.088,283.008,0],"to":[0,0,0],"ti":[0,0,0]},{"t":34,"s":[255.088,261.008,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.918,5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":13,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10.812,1.134,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[9.12,8.787],[-9.12,-8.787]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.67,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.6],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"t":17,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.006,206.214,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.105,0],[0,1.105],[0,0]],"o":[[1.105,0],[0,0],[0,1.105]],"v":[[0,1],[2,-1],[-2,-1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Union","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-7.118,-30.841,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":3,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[13.622,-14.091],[14.062,13.645],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[14.063,13.648],[-13.622,14.086],[0,0],[0,0]],"v":[[-248.877,286.845],[262.123,286.845],[262.123,-225.155],[-195.544,-225.155],[236.258,193.93],[237.068,244.159],[186.937,244.951],[-248.877,-178.009]],"c":true}]},{"t":17,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[13.622,-14.091],[14.062,13.645],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[14.063,13.648],[-13.622,14.086],[0,0],[0,0]],"v":[[-248.877,286.845],[262.123,286.845],[262.123,-225.155],[-195.544,-225.155],[-161.724,-189.838],[-160.913,-139.609],[-211.044,-138.817],[-248.877,-178.009]],"c":true}]}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.644,0],[0,-0.644],[0,0],[-0.003,-0.033],[0,-3.522],[0,0],[0.027,-0.03],[0,0],[-1.708,0],[0,0],[1.148,1.265],[0,0],[0,0.041],[0,0],[3.091,0.677],[0,0.072]],"o":[[0,-0.644],[-0.644,0],[0,0],[0,0.034],[-3.411,0.409],[0,0],[0,0.041],[0,0],[-1.148,1.265],[0,0],[1.708,0],[0,0],[-0.027,-0.03],[0,0],[0,-3.294],[0.012,-0.068],[0,0]],"v":[[1.5,-8.055],[0.333,-9.222],[-0.833,-8.055],[-0.833,-7.722],[-0.828,-7.62],[-6.885,-0.786],[-6.885,3.651],[-6.928,3.762],[-8.897,5.93],[-7.44,9.222],[6.773,9.222],[8.896,5.93],[6.927,3.762],[6.884,3.651],[6.884,-0.786],[1.479,-7.511],[1.5,-7.722]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-2.88,0],[0,-2.88],[0,0],[-0.307,-0.337],[0,0],[0.259,0],[0,0],[-0.174,0.192],[0,0],[0,0.456]],"o":[[0,-2.88],[2.88,0],[0,0],[0,0.456],[0,0],[0.174,0.192],[0,0],[-0.259,0],[0,0],[0.307,-0.337],[0,0]],"v":[[-5.215,-0.786],[0,-6.001],[5.214,-0.786],[5.214,3.651],[5.691,4.885],[7.66,7.053],[7.439,7.552],[-7.44,7.552],[-7.66,7.053],[-5.691,4.885],[-5.215,3.651]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[2133.333,2133.333],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":40,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Mute_24","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[256,256,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":512,"h":512,"ip":40,"op":80,"st":40,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Unmute_24","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[256,256,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":512,"h":512,"ip":0,"op":40,"st":0,"bm":0}],"markers":[],"props":{}} \ No newline at end of file diff --git a/Telegram/Resources/icons/payments/premium_emoji.png b/Telegram/Resources/icons/payments/premium_emoji.png new file mode 100644 index 00000000000000..e8b8fcf292b6f4 Binary files /dev/null and b/Telegram/Resources/icons/payments/premium_emoji.png differ diff --git a/Telegram/Resources/icons/payments/premium_emoji@2x.png b/Telegram/Resources/icons/payments/premium_emoji@2x.png new file mode 100644 index 00000000000000..8824f11b97c6f1 Binary files /dev/null and b/Telegram/Resources/icons/payments/premium_emoji@2x.png differ diff --git a/Telegram/Resources/icons/payments/premium_emoji@3x.png b/Telegram/Resources/icons/payments/premium_emoji@3x.png new file mode 100644 index 00000000000000..5bd6ad0133eb80 Binary files /dev/null and b/Telegram/Resources/icons/payments/premium_emoji@3x.png differ diff --git a/Telegram/Resources/icons/payments/ton_emoji.svg b/Telegram/Resources/icons/payments/ton_emoji.svg new file mode 100644 index 00000000000000..bc5b9f1ee5ee1a --- /dev/null +++ b/Telegram/Resources/icons/payments/ton_emoji.svg @@ -0,0 +1,7 @@ + + + General / menu_ton + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/photo_editor/corners.svg b/Telegram/Resources/icons/photo_editor/corners.svg new file mode 100644 index 00000000000000..09b04284330346 --- /dev/null +++ b/Telegram/Resources/icons/photo_editor/corners.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Telegram/Resources/icons/photo_editor/ratio.svg b/Telegram/Resources/icons/photo_editor/ratio.svg new file mode 100644 index 00000000000000..357b6dd6efc830 --- /dev/null +++ b/Telegram/Resources/icons/photo_editor/ratio.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/player/player_settings.png b/Telegram/Resources/icons/player/player_settings.png new file mode 100644 index 00000000000000..65a420de07ae22 Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings.png differ diff --git a/Telegram/Resources/icons/player/player_settings@2x.png b/Telegram/Resources/icons/player/player_settings@2x.png new file mode 100644 index 00000000000000..af78b266614306 Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings@2x.png differ diff --git a/Telegram/Resources/icons/player/player_settings@3x.png b/Telegram/Resources/icons/player/player_settings@3x.png new file mode 100644 index 00000000000000..da1ec8c72c34b0 Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings@3x.png differ diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_add.svg b/Telegram/Resources/icons/poll/filled/filled_poll_add.svg new file mode 100644 index 00000000000000..6e080bc9f866d2 --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_add.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_add + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_correct.svg b/Telegram/Resources/icons/poll/filled/filled_poll_correct.svg new file mode 100644 index 00000000000000..3ae580ee8c9032 --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_correct.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_correct + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_country.svg b/Telegram/Resources/icons/poll/filled/filled_poll_country.svg new file mode 100644 index 00000000000000..bf77eb160fcdfa --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_country.svg @@ -0,0 +1,13 @@ + + + Filled / filled_poll_country + + + + + + + + + + diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_deadline.svg b/Telegram/Resources/icons/poll/filled/filled_poll_deadline.svg new file mode 100644 index 00000000000000..b7ce751224ca5c --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_deadline.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_deadline + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_multiple.svg b/Telegram/Resources/icons/poll/filled/filled_poll_multiple.svg new file mode 100644 index 00000000000000..3b6e509f98051a --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_multiple.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_multiple + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_revote.svg b/Telegram/Resources/icons/poll/filled/filled_poll_revote.svg new file mode 100644 index 00000000000000..caeedd891c69fb --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_revote.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_revote + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_shuffle.svg b/Telegram/Resources/icons/poll/filled/filled_poll_shuffle.svg new file mode 100644 index 00000000000000..5aaf664f55c0f7 --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_shuffle.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_shuffle + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_subscribers.svg b/Telegram/Resources/icons/poll/filled/filled_poll_subscribers.svg new file mode 100644 index 00000000000000..52b73e4d19030a --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_subscribers.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_subscribers + + + + diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_view.svg b/Telegram/Resources/icons/poll/filled/filled_poll_view.svg new file mode 100644 index 00000000000000..514b04efbd41b1 --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_view.svg @@ -0,0 +1,7 @@ + + + Filled / filled_poll_view + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/general/menu_poll_order.svg b/Telegram/Resources/icons/poll/general/menu_poll_order.svg new file mode 100644 index 00000000000000..a680acd98c548b --- /dev/null +++ b/Telegram/Resources/icons/poll/general/menu_poll_order.svg @@ -0,0 +1,7 @@ + + + General / menu_poll_order + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/general/outline_poll_add.svg b/Telegram/Resources/icons/poll/general/outline_poll_add.svg new file mode 100644 index 00000000000000..afb9d1a4d92e9d --- /dev/null +++ b/Telegram/Resources/icons/poll/general/outline_poll_add.svg @@ -0,0 +1,7 @@ + + + General / outline_poll_add + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/general/outline_poll_attach.svg b/Telegram/Resources/icons/poll/general/outline_poll_attach.svg new file mode 100644 index 00000000000000..5e2de24f5ae12c --- /dev/null +++ b/Telegram/Resources/icons/poll/general/outline_poll_attach.svg @@ -0,0 +1,7 @@ + + + General / outline_poll_attach + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/general/outline_poll_emoji.svg b/Telegram/Resources/icons/poll/general/outline_poll_emoji.svg new file mode 100644 index 00000000000000..2a52749d7d4df9 --- /dev/null +++ b/Telegram/Resources/icons/poll/general/outline_poll_emoji.svg @@ -0,0 +1,7 @@ + + + General / outline_poll_emoji + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/poll/toast_hide_results.tgs b/Telegram/Resources/icons/poll/toast_hide_results.tgs new file mode 100644 index 00000000000000..7e489655e3ec60 Binary files /dev/null and b/Telegram/Resources/icons/poll/toast_hide_results.tgs differ diff --git a/Telegram/Resources/icons/poll/uploading.tgs b/Telegram/Resources/icons/poll/uploading.tgs new file mode 100644 index 00000000000000..77973c55634fae Binary files /dev/null and b/Telegram/Resources/icons/poll/uploading.tgs differ diff --git a/Telegram/Resources/icons/profile/call.svg b/Telegram/Resources/icons/profile/call.svg new file mode 100644 index 00000000000000..4882bb62286bb5 --- /dev/null +++ b/Telegram/Resources/icons/profile/call.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/gift.svg b/Telegram/Resources/icons/profile/gift.svg new file mode 100644 index 00000000000000..0d6880abee5314 --- /dev/null +++ b/Telegram/Resources/icons/profile/gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/join.svg b/Telegram/Resources/icons/profile/join.svg new file mode 100644 index 00000000000000..7c53bdae005b28 --- /dev/null +++ b/Telegram/Resources/icons/profile/join.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/leave.svg b/Telegram/Resources/icons/profile/leave.svg new file mode 100644 index 00000000000000..a91a104440aaee --- /dev/null +++ b/Telegram/Resources/icons/profile/leave.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/live_stream.svg b/Telegram/Resources/icons/profile/live_stream.svg new file mode 100644 index 00000000000000..936e77506b8957 --- /dev/null +++ b/Telegram/Resources/icons/profile/live_stream.svg @@ -0,0 +1,3 @@ + + + diff --git a/Telegram/Resources/icons/profile/message.svg b/Telegram/Resources/icons/profile/message.svg new file mode 100644 index 00000000000000..685cf25a68df6d --- /dev/null +++ b/Telegram/Resources/icons/profile/message.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/mute.svg b/Telegram/Resources/icons/profile/mute.svg new file mode 100644 index 00000000000000..d9fea015edcfda --- /dev/null +++ b/Telegram/Resources/icons/profile/mute.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/profile_manage.png b/Telegram/Resources/icons/profile/profile_manage.png new file mode 100644 index 00000000000000..38f1a731dd7b2e Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_manage.png differ diff --git a/Telegram/Resources/icons/profile/profile_manage@2x.png b/Telegram/Resources/icons/profile/profile_manage@2x.png new file mode 100644 index 00000000000000..e5ab30a5183b9f Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_manage@2x.png differ diff --git a/Telegram/Resources/icons/profile/profile_manage@3x.png b/Telegram/Resources/icons/profile/profile_manage@3x.png new file mode 100644 index 00000000000000..8bf97111c72722 Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_manage@3x.png differ diff --git a/Telegram/Resources/icons/profile/profile_more.png b/Telegram/Resources/icons/profile/profile_more.png new file mode 100644 index 00000000000000..a35e32ff9580cf Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_more.png differ diff --git a/Telegram/Resources/icons/profile/profile_more@2x.png b/Telegram/Resources/icons/profile/profile_more@2x.png new file mode 100644 index 00000000000000..c7ceb94e5875f0 Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_more@2x.png differ diff --git a/Telegram/Resources/icons/profile/profile_more@3x.png b/Telegram/Resources/icons/profile/profile_more@3x.png new file mode 100644 index 00000000000000..3851073ed9f03b Binary files /dev/null and b/Telegram/Resources/icons/profile/profile_more@3x.png differ diff --git a/Telegram/Resources/icons/profile/report.svg b/Telegram/Resources/icons/profile/report.svg new file mode 100644 index 00000000000000..2dc982d75f7e99 --- /dev/null +++ b/Telegram/Resources/icons/profile/report.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/profile/unmute.svg b/Telegram/Resources/icons/profile/unmute.svg new file mode 100644 index 00000000000000..80bd6c2fa4ad0c --- /dev/null +++ b/Telegram/Resources/icons/profile/unmute.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/send_media/send_media_cross.svg b/Telegram/Resources/icons/send_media/send_media_cross.svg new file mode 100644 index 00000000000000..50abac8af803db --- /dev/null +++ b/Telegram/Resources/icons/send_media/send_media_cross.svg @@ -0,0 +1,7 @@ + + + Icon / SendMedia / cross + + + + diff --git a/Telegram/Resources/icons/send_media/send_media_more.svg b/Telegram/Resources/icons/send_media/send_media_more.svg new file mode 100644 index 00000000000000..b7ddcad3ec7173 --- /dev/null +++ b/Telegram/Resources/icons/send_media/send_media_more.svg @@ -0,0 +1,9 @@ + + + Icon / SendMedia / more_vertical + + + + + + diff --git a/Telegram/Resources/icons/settings/birthday_add.svg b/Telegram/Resources/icons/settings/birthday_add.svg new file mode 100644 index 00000000000000..a370aba0dc1ddc --- /dev/null +++ b/Telegram/Resources/icons/settings/birthday_add.svg @@ -0,0 +1,7 @@ + + + Icon / Menu / birthday_add + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/button_auction.png b/Telegram/Resources/icons/settings/button_auction.png new file mode 100644 index 00000000000000..6fbaa5e019ad92 Binary files /dev/null and b/Telegram/Resources/icons/settings/button_auction.png differ diff --git a/Telegram/Resources/icons/settings/button_auction@2x.png b/Telegram/Resources/icons/settings/button_auction@2x.png new file mode 100644 index 00000000000000..c4c3ef1d8feaa6 Binary files /dev/null and b/Telegram/Resources/icons/settings/button_auction@2x.png differ diff --git a/Telegram/Resources/icons/settings/button_auction@3x.png b/Telegram/Resources/icons/settings/button_auction@3x.png new file mode 100644 index 00000000000000..c9c6b285615467 Binary files /dev/null and b/Telegram/Resources/icons/settings/button_auction@3x.png differ diff --git a/Telegram/Resources/icons/settings/earn.png b/Telegram/Resources/icons/settings/earn.png new file mode 100644 index 00000000000000..c2e73499e1950a Binary files /dev/null and b/Telegram/Resources/icons/settings/earn.png differ diff --git a/Telegram/Resources/icons/settings/earn@2x.png b/Telegram/Resources/icons/settings/earn@2x.png new file mode 100644 index 00000000000000..53b7eec28c1a4e Binary files /dev/null and b/Telegram/Resources/icons/settings/earn@2x.png differ diff --git a/Telegram/Resources/icons/settings/earn@3x.png b/Telegram/Resources/icons/settings/earn@3x.png new file mode 100644 index 00000000000000..33c71446904543 Binary files /dev/null and b/Telegram/Resources/icons/settings/earn@3x.png differ diff --git a/Telegram/Resources/icons/settings/filled_verify_age.svg b/Telegram/Resources/icons/settings/filled_verify_age.svg new file mode 100644 index 00000000000000..2b13be56ca34e9 --- /dev/null +++ b/Telegram/Resources/icons/settings/filled_verify_age.svg @@ -0,0 +1,12 @@ + + + Filled / filled_verify_age + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/gift.png b/Telegram/Resources/icons/settings/gift.png new file mode 100644 index 00000000000000..8dacb6c7ebfb45 Binary files /dev/null and b/Telegram/Resources/icons/settings/gift.png differ diff --git a/Telegram/Resources/icons/settings/gift@2x.png b/Telegram/Resources/icons/settings/gift@2x.png new file mode 100644 index 00000000000000..996bd85d0ba414 Binary files /dev/null and b/Telegram/Resources/icons/settings/gift@2x.png differ diff --git a/Telegram/Resources/icons/settings/gift@3x.png b/Telegram/Resources/icons/settings/gift@3x.png new file mode 100644 index 00000000000000..240680269ed145 Binary files /dev/null and b/Telegram/Resources/icons/settings/gift@3x.png differ diff --git a/Telegram/Resources/icons/settings/large_auctions.png b/Telegram/Resources/icons/settings/large_auctions.png new file mode 100644 index 00000000000000..e56787624747ed Binary files /dev/null and b/Telegram/Resources/icons/settings/large_auctions.png differ diff --git a/Telegram/Resources/icons/settings/large_auctions@2x.png b/Telegram/Resources/icons/settings/large_auctions@2x.png new file mode 100644 index 00000000000000..1a32e7584cd036 Binary files /dev/null and b/Telegram/Resources/icons/settings/large_auctions@2x.png differ diff --git a/Telegram/Resources/icons/settings/large_auctions@3x.png b/Telegram/Resources/icons/settings/large_auctions@3x.png new file mode 100644 index 00000000000000..652c413bc8d16f Binary files /dev/null and b/Telegram/Resources/icons/settings/large_auctions@3x.png differ diff --git a/Telegram/Resources/icons/settings/mini_gift.png b/Telegram/Resources/icons/settings/mini_gift.png new file mode 100644 index 00000000000000..6e1f5660657275 Binary files /dev/null and b/Telegram/Resources/icons/settings/mini_gift.png differ diff --git a/Telegram/Resources/icons/settings/mini_gift@2x.png b/Telegram/Resources/icons/settings/mini_gift@2x.png new file mode 100644 index 00000000000000..7a5e301e1ad0f8 Binary files /dev/null and b/Telegram/Resources/icons/settings/mini_gift@2x.png differ diff --git a/Telegram/Resources/icons/settings/mini_gift@3x.png b/Telegram/Resources/icons/settings/mini_gift@3x.png new file mode 100644 index 00000000000000..d34d1b6deac2b9 Binary files /dev/null and b/Telegram/Resources/icons/settings/mini_gift@3x.png differ diff --git a/Telegram/Resources/icons/settings/mini_gift_order_date.svg b/Telegram/Resources/icons/settings/mini_gift_order_date.svg new file mode 100644 index 00000000000000..130d30310ff49d --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_date.svg @@ -0,0 +1,12 @@ + + + Mini / mini_gift_sorting2 + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/mini_gift_order_number.svg b/Telegram/Resources/icons/settings/mini_gift_order_number.svg new file mode 100644 index 00000000000000..37cc05f5722c61 --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_number.svg @@ -0,0 +1,12 @@ + + + Mini / mini_gift_sorting3 + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/mini_gift_order_price.svg b/Telegram/Resources/icons/settings/mini_gift_order_price.svg new file mode 100644 index 00000000000000..36409f6b4b3018 --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_price.svg @@ -0,0 +1,10 @@ + + + Mini / mini_gift_sorting1 + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/premium/business/earn_stars.png b/Telegram/Resources/icons/settings/premium/business/earn_stars.png new file mode 100644 index 00000000000000..84419b169f9806 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/earn_stars.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/earn_stars@2x.png b/Telegram/Resources/icons/settings/premium/business/earn_stars@2x.png new file mode 100644 index 00000000000000..3f6b9e30f601af Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/earn_stars@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/earn_stars@3x.png b/Telegram/Resources/icons/settings/premium/business/earn_stars@3x.png new file mode 100644 index 00000000000000..19823b48b4e144 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/earn_stars@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/checklist.svg b/Telegram/Resources/icons/settings/premium/checklist.svg new file mode 100644 index 00000000000000..6cb5fac3f98505 --- /dev/null +++ b/Telegram/Resources/icons/settings/premium/checklist.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / checklist + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_profile.png b/Telegram/Resources/icons/settings/premium/features/feature_color_profile.png new file mode 100644 index 00000000000000..33a6d47c9f12a7 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_profile.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_profile@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_profile@2x.png new file mode 100644 index 00000000000000..dcb515f56089eb Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_profile@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_profile@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_profile@3x.png new file mode 100644 index 00000000000000..08f02966710524 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_profile@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_profile_cover.png b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover.png new file mode 100644 index 00000000000000..657fd9390ae6f6 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@2x.png new file mode 100644 index 00000000000000..250be37750786b Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@3x.png new file mode 100644 index 00000000000000..ae696a8f349dc2 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_profile_cover@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen.png b/Telegram/Resources/icons/settings/premium/large_lastseen.png deleted file mode 100644 index e47bd7935bc38b..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_lastseen.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png deleted file mode 100644 index 305b6a491966b1..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png deleted file mode 100644 index 0cd1e12ab09dc2..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime.png b/Telegram/Resources/icons/settings/premium/large_readtime.png deleted file mode 100644 index c06294a82e44e2..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_readtime.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@2x.png b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png deleted file mode 100644 index f30a355ee8d7aa..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_readtime@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@3x.png b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png deleted file mode 100644 index 1e7d022a30e991..00000000000000 Binary files a/Telegram/Resources/icons/settings/premium/large_readtime@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/premium/peer_colors.svg b/Telegram/Resources/icons/settings/premium/peer_colors.svg new file mode 100644 index 00000000000000..0a3c1b0854f7e3 --- /dev/null +++ b/Telegram/Resources/icons/settings/premium/peer_colors.svg @@ -0,0 +1,7 @@ + + + Icon / Filled / premium_themes + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/premium_dollar.svg b/Telegram/Resources/icons/settings/premium_dollar.svg new file mode 100644 index 00000000000000..96c384d7b26eb8 --- /dev/null +++ b/Telegram/Resources/icons/settings/premium_dollar.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/toast/auction.png b/Telegram/Resources/icons/toast/auction.png new file mode 100644 index 00000000000000..41b34d5fe1d143 Binary files /dev/null and b/Telegram/Resources/icons/toast/auction.png differ diff --git a/Telegram/Resources/icons/toast/auction@2x.png b/Telegram/Resources/icons/toast/auction@2x.png new file mode 100644 index 00000000000000..e03d6a987f98a9 Binary files /dev/null and b/Telegram/Resources/icons/toast/auction@2x.png differ diff --git a/Telegram/Resources/icons/toast/auction@3x.png b/Telegram/Resources/icons/toast/auction@3x.png new file mode 100644 index 00000000000000..8bf2db0ab2df9d Binary files /dev/null and b/Telegram/Resources/icons/toast/auction@3x.png differ diff --git a/Telegram/Resources/icons/toast/check.svg b/Telegram/Resources/icons/toast/check.svg new file mode 100644 index 00000000000000..fbb3381f90d3e9 --- /dev/null +++ b/Telegram/Resources/icons/toast/check.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/toast_info.png b/Telegram/Resources/icons/toast/info.png similarity index 100% rename from Telegram/Resources/icons/toast_info.png rename to Telegram/Resources/icons/toast/info.png diff --git a/Telegram/Resources/icons/toast_info@2x.png b/Telegram/Resources/icons/toast/info@2x.png similarity index 100% rename from Telegram/Resources/icons/toast_info@2x.png rename to Telegram/Resources/icons/toast/info@2x.png diff --git a/Telegram/Resources/icons/toast_info@3x.png b/Telegram/Resources/icons/toast/info@3x.png similarity index 100% rename from Telegram/Resources/icons/toast_info@3x.png rename to Telegram/Resources/icons/toast/info@3x.png diff --git a/Telegram/Resources/icons/toast/star.svg b/Telegram/Resources/icons/toast/star.svg new file mode 100644 index 00000000000000..2e8ad94b7ed8a1 --- /dev/null +++ b/Telegram/Resources/icons/toast/star.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/tray_monochrome_attention.svg b/Telegram/Resources/icons/tray_monochrome_attention.svg new file mode 100644 index 00000000000000..95b8c2cfafd378 --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome_attention.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/tray_monochrome_mute.svg b/Telegram/Resources/icons/tray_monochrome_mute.svg new file mode 100644 index 00000000000000..448f6990b2b772 --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome_mute.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Telegram/Resources/icons/voice_lock/input_round_s.png b/Telegram/Resources/icons/voice_lock/input_round_s.png new file mode 100644 index 00000000000000..549dd1ab1823cc Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_round_s@2x.png b/Telegram/Resources/icons/voice_lock/input_round_s@2x.png new file mode 100644 index 00000000000000..4ab801a465afd8 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_round_s@3x.png b/Telegram/Resources/icons/voice_lock/input_round_s@3x.png new file mode 100644 index 00000000000000..878aa7e6a1369b Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s@3x.png differ diff --git a/Telegram/Resources/iv_html/highlight.9.12.0.css b/Telegram/Resources/iv_html/highlight.9.12.0.css deleted file mode 100644 index 7d8be18d058e99..00000000000000 --- a/Telegram/Resources/iv_html/highlight.9.12.0.css +++ /dev/null @@ -1 +0,0 @@ -.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} \ No newline at end of file diff --git a/Telegram/Resources/iv_html/highlight.9.12.0.js b/Telegram/Resources/iv_html/highlight.9.12.0.js deleted file mode 100644 index f30a334c9d15d3..00000000000000 --- a/Telegram/Resources/iv_html/highlight.9.12.0.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ -!function(e){var t="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):t&&(t.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return t.hljs}))}(function(e){function t(e){return e.replace(/&/g,"&").replace(//g,">")}function r(e){return e.nodeName.toLowerCase()}function a(e,t){var r=e&&e.exec(t);return r&&0===r.index}function n(e){return E.test(e)}function i(e){var t,r,a,i,s=e.className+" ";if(s+=e.parentNode?e.parentNode.className:"",r=M.exec(s))return w(r[1])?r[1]:"no-highlight";for(s=s.split(/\s+/),t=0,a=s.length;a>t;t++)if(i=s[t],n(i)||w(i))return i}function s(e){var t,r={},a=Array.prototype.slice.call(arguments,1);for(t in e)r[t]=e[t];return a.forEach(function(e){for(t in e)r[t]=e[t]}),r}function c(e){var t=[];return function a(e,n){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?n+=i.nodeValue.length:1===i.nodeType&&(t.push({event:"start",offset:n,node:i}),n=a(i,n),r(i).match(/br|hr|img|input/)||t.push({event:"stop",offset:n,node:i}));return n}(e,0),t}function o(e,a,n){function i(){return e.length&&a.length?e[0].offset!==a[0].offset?e[0].offset"}function c(e){u+=""}function o(e){("start"===e.event?s:c)(e.node)}for(var l=0,u="",d=[];e.length||a.length;){var b=i();if(u+=t(n.substring(l,b[0].offset)),l=b[0].offset,b===e){d.reverse().forEach(c);do o(b.splice(0,1)[0]),b=i();while(b===e&&b.length&&b[0].offset===l);d.reverse().forEach(s)}else"start"===b[0].event?d.push(b[0].node):d.pop(),o(b.splice(0,1)[0])}return u+t(n.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(t){return s(e,{v:null},t)})),e.cached_variants||e.eW&&[s(e)]||[e]}function u(e){function t(e){return e&&e.source||e}function r(r,a){return new RegExp(t(r),"m"+(e.cI?"i":"")+(a?"g":""))}function a(n,i){if(!n.compiled){if(n.compiled=!0,n.k=n.k||n.bK,n.k){var s={},c=function(t,r){e.cI&&(r=r.toLowerCase()),r.split(" ").forEach(function(e){var r=e.split("|");s[r[0]]=[t,r[1]?Number(r[1]):1]})};"string"==typeof n.k?c("keyword",n.k):k(n.k).forEach(function(e){c(e,n.k[e])}),n.k=s}n.lR=r(n.l||/\w+/,!0),i&&(n.bK&&(n.b="\\b("+n.bK.split(" ").join("|")+")\\b"),n.b||(n.b=/\B|\b/),n.bR=r(n.b),n.e||n.eW||(n.e=/\B|\b/),n.e&&(n.eR=r(n.e)),n.tE=t(n.e)||"",n.eW&&i.tE&&(n.tE+=(n.e?"|":"")+i.tE)),n.i&&(n.iR=r(n.i)),null==n.r&&(n.r=1),n.c||(n.c=[]),n.c=Array.prototype.concat.apply([],n.c.map(function(e){return l("self"===e?n:e)})),n.c.forEach(function(e){a(e,n)}),n.starts&&a(n.starts,i);var o=n.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([n.tE,n.i]).map(t).filter(Boolean);n.t=o.length?r(o.join("|"),!0):{exec:function(){return null}}}}a(e)}function d(e,r,n,i){function s(e,t){var r,n;for(r=0,n=t.c.length;n>r;r++)if(a(t.c[r].bR,e))return t.c[r]}function c(e,t){if(a(e.eR,t)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?c(e.parent,t):void 0}function o(e,t){return!n&&a(t.iR,e)}function l(e,t){var r=v.cI?t[0].toLowerCase():t[0];return e.k.hasOwnProperty(r)&&e.k[r]}function p(e,t,r,a){var n=a?"":L.classPrefix,i='',i+t+s}function m(){var e,r,a,n;if(!N.k)return t(E);for(n="",r=0,N.lR.lastIndex=0,a=N.lR.exec(E);a;)n+=t(E.substring(r,a.index)),e=l(N,a),e?(M+=e[1],n+=p(e[0],t(a[0]))):n+=t(a[0]),r=N.lR.lastIndex,a=N.lR.exec(E);return n+t(E.substr(r))}function f(){var e="string"==typeof N.sL;if(e&&!x[N.sL])return t(E);var r=e?d(N.sL,E,!0,k[N.sL]):b(E,N.sL.length?N.sL:void 0);return N.r>0&&(M+=r.r),e&&(k[N.sL]=r.top),p(r.language,r.value,!1,!0)}function g(){C+=null!=N.sL?f():m(),E=""}function _(e){C+=e.cN?p(e.cN,"",!0):"",N=Object.create(e,{parent:{value:N}})}function h(e,t){if(E+=e,null==t)return g(),0;var r=s(t,N);if(r)return r.skip?E+=t:(r.eB&&(E+=t),g(),r.rB||r.eB||(E=t)),_(r,t),r.rB?0:t.length;var a=c(N,t);if(a){var n=N;n.skip?E+=t:(n.rE||n.eE||(E+=t),g(),n.eE&&(E=t));do N.cN&&(C+=R),N.skip||(M+=N.r),N=N.parent;while(N!==a.parent);return a.starts&&_(a.starts,""),n.rE?0:t.length}if(o(t,N))throw new Error('Illegal lexeme "'+t+'" for mode "'+(N.cN||"")+'"');return E+=t,t.length||1}var v=w(e);if(!v)throw new Error('Unknown language: "'+e+'"');u(v);var y,N=i||v,k={},C="";for(y=N;y!==v;y=y.parent)y.cN&&(C=p(y.cN,"",!0)+C);var E="",M=0;try{for(var B,S,$=0;;){if(N.t.lastIndex=$,B=N.t.exec(r),!B)break;S=h(r.substring($,B.index),B[0]),$=B.index+S}for(h(r.substr($)),y=N;y.parent;y=y.parent)y.cN&&(C+=R);return{r:M,value:C,language:e,top:N}}catch(A){if(A.message&&-1!==A.message.indexOf("Illegal"))return{r:0,value:t(r)};throw A}}function b(e,r){r=r||L.languages||k(x);var a={r:0,value:t(e)},n=a;return r.filter(w).forEach(function(t){var r=d(t,e,!1);r.language=t,r.r>n.r&&(n=r),r.r>a.r&&(n=a,a=r)}),n.language&&(a.second_best=n),a}function p(e){return L.tabReplace||L.useBR?e.replace(B,function(e,t){return L.useBR&&"\n"===e?"
":L.tabReplace?t.replace(/\t/g,L.tabReplace):""}):e}function m(e,t,r){var a=t?C[t]:r,n=[e.trim()];return e.match(/\bhljs\b/)||n.push("hljs"),-1===e.indexOf(a)&&n.push(a),n.join(" ").trim()}function f(e){var t,r,a,s,l,u=i(e);n(u)||(L.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e,l=t.textContent,a=u?d(u,l,!0):b(l),r=c(t),r.length&&(s=document.createElementNS("http://www.w3.org/1999/xhtml","div"),s.innerHTML=a.value,a.value=o(r,c(s),l)),a.value=p(a.value),e.innerHTML=a.value,e.className=m(e.className,u,a.language),e.result={language:a.language,re:a.r},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.r}))}function g(e){L=s(L,e)}function _(){if(!_.called){_.called=!0;var e=document.querySelectorAll("pre code");N.forEach.call(e,f)}}function h(){addEventListener("DOMContentLoaded",_,!1),addEventListener("load",_,!1)}function v(t,r){var a=x[t]=r(e);a.aliases&&a.aliases.forEach(function(e){C[e]=t})}function y(){return k(x)}function w(e){return e=(e||"").toLowerCase(),x[e]||x[C[e]]}var N=[],k=Object.keys,x={},C={},E=/^(no-?highlight|plain|text)$/i,M=/\blang(?:uage)?-([\w-]+)\b/i,B=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,R="
",L={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=d,e.highlightAuto=b,e.fixMarkup=p,e.highlightBlock=f,e.configure=g,e.initHighlighting=_,e.initHighlightingOnLoad=h,e.registerLanguage=v,e.listLanguages=y,e.getLanguage=w,e.inherit=s,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(t,r,a){var n=e.inherit({cN:"comment",b:t,e:r,c:[]},a||{});return n.c.push(e.PWM),n.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),n},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e.registerLanguage("apache",function(e){var t={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",t]},t,e.QSM]}}],i:/\S/}}),e.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,r,a,t]}}),e.registerLanguage("coffeescript",function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},r="[A-Za-z$_][0-9A-Za-z$_]*",a={cN:"subst",b:/#\{/,e:/}/,k:t},n=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,a]},{b:/"/,e:/"/,c:[e.BE,a]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[a,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+r},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];a.c=n;var i=e.inherit(e.TM,{b:r}),s="(\\(.*\\))?\\s*\\B[-=]>",c={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:t,c:["self"].concat(n)}]};return{aliases:["coffee","cson","iced"],k:t,i:/\/\*/,c:n.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+r+"\\s*=\\s*"+s,e:"[-=]>",rB:!0,c:[i,c]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:s,e:"[-=]>",rB:!0,c:[c]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{b:r+":",e:":",rB:!0,rE:!0,r:0}])}}),e.registerLanguage("cpp",function(e){var t={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[e.BE]},{b:'(u8?|U)?R"',e:'"',c:[e.BE]},{b:"'\\\\?.",e:"'",i:"."}]},a={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},n={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},e.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},i=e.IR+"\\s*\\(",s={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},c=[t,e.CLCM,e.CBCM,a,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:s,i:"",k:s,c:["self",t]},{b:e.IR+"::",k:s},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:s,c:c.concat([{b:/\(/,e:/\)/,k:s,c:c.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+e.IR+"[\\*&\\s]+)+"+i,rB:!0,e:/[{;=]/,eE:!0,k:s,i:/[^\w\s\*&]/,c:[{b:i,rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:s,r:0,c:[e.CLCM,e.CBCM,r,a,t]},e.CLCM,e.CBCM,n]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:n,strings:r,k:s}}}),e.registerLanguage("cs",function(e){var t={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},r={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},a=e.inherit(r,{i:/\n/}),n={cN:"subst",b:"{",e:"}",k:t},i=e.inherit(n,{i:/\n/}),s={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,i]},c={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},n]},o=e.inherit(c,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},i]});n.c=[c,s,r,e.ASM,e.QSM,e.CNM,e.CBCM],i.c=[o,s,a,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[c,s,r,e.ASM,e.QSM]},u=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:t,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+u+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}}),e.registerLanguage("css",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",r={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:t,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}}),e.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}}),e.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}}),e.registerLanguage("ini",function(e){var t={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},t,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}}),e.registerLanguage("java",function(e){var t="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",r=t+"(<"+t+"(\\s*,\\s*"+t+")*>)?",a="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",n="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",i={cN:"number",b:n,r:0};return{aliases:["jsp"],k:a,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+r+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:a,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:a,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},i,{cN:"meta",b:"@[A-Za-z]+"}]}}),e.registerLanguage("javascript",function(e){var t="[A-Za-z$_][0-9A-Za-z$_]*",r={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:r,c:[]},i={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,i,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:r,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:t+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:t,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+t+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:t},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:t}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}}),e.registerLanguage("json",function(e){var t={literal:"true false null"},r=[e.QSM,e.CNM],a={e:",",eW:!0,eE:!0,c:r,k:t},n={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(a,{b:/:/})],i:"\\S"},i={b:"\\[",e:"\\]",c:[e.inherit(a)],i:"\\S"};return r.splice(r.length,0,n,i),{c:r,k:t,i:"\\S"}}),e.registerLanguage("makefile",function(e){var t={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},e.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[r],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},r]}]}}),e.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}}),e.registerLanguage("nginx",function(e){var t={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},r={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,t],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[t]},{cN:"regexp",c:[e.BE,t],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},t]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:r}],r:0}],i:"[^\\s\\}]"}}),e.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},r={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},a=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:r,l:a,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:a,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}}),e.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},a={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],s=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),a,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=s,a.c=s,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s}}),e.registerLanguage("php",function(e){var t={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},r={cN:"meta",b:/<\?(php)?|\?>/},a={cN:"string",c:[e.BE,r],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[r]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},r,{cN:"keyword",b:/\$this\b/},t,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",t,e.CBCM,a,n]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},a,n]}}),e.registerLanguage("python",function(e){var t={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},r={cN:"meta",b:/^(>>>|\.\.\.) /},a={cN:"subst",b:/\{/,e:/\}/,k:t,i:/#/},n={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[r,a]},{b:/(fr|rf|f)"""/,e:/"""/,c:[r,a]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[a]},{b:/(fr|rf|f)"/,e:/"/,c:[a]},e.ASM,e.QSM]},i={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},s={cN:"params",b:/\(/,e:/\)/,c:["self",r,i,n]};return a.c=[n,i,r],{aliases:["py","gyp"],k:t,i:/(<\/|->|\?)|=>/,c:[r,i,n,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,s,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}),e.registerLanguage("ruby",function(e){ -var t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},a={cN:"doctag",b:"@[A-Za-z]+"},n={b:"#<",e:">"},i=[e.C("#","$",{c:[a]}),e.C("^\\=begin","^\\=end",{c:[a],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},c={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},o={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},l=[c,n,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(i)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:t}),o].concat(i)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[c,{b:t}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[n,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(i),r:0}].concat(i);s.c=l,o.c=l;var u="[>?]>",d="[\\w#]+\\(\\w+\\):\\d+:\\d+>",b="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",p=[{b:/^\s*=>/,starts:{e:"$",c:l}},{cN:"meta",b:"^("+u+"|"+d+"|"+b+")",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:i.concat(p).concat(l)}}),e.registerLanguage("shell",function(e){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}}),e.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}}),e}); \ No newline at end of file diff --git a/Telegram/Resources/iv_html/morphdom-umd.min.2.7.2.js b/Telegram/Resources/iv_html/morphdom-umd.min.2.7.2.js deleted file mode 100644 index 1e40c7eaee5998..00000000000000 --- a/Telegram/Resources/iv_html/morphdom-umd.min.2.7.2.js +++ /dev/null @@ -1 +0,0 @@ -(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory():typeof define==="function"&&define.amd?define(factory):(global=global||self,global.morphdom=factory())})(this,function(){"use strict";var DOCUMENT_FRAGMENT_NODE=11;function morphAttrs(fromNode,toNode){var toNodeAttrs=toNode.attributes;var attr;var attrName;var attrNamespaceURI;var attrValue;var fromValue;if(toNode.nodeType===DOCUMENT_FRAGMENT_NODE||fromNode.nodeType===DOCUMENT_FRAGMENT_NODE){return}for(var i=toNodeAttrs.length-1;i>=0;i--){attr=toNodeAttrs[i];attrName=attr.name;attrNamespaceURI=attr.namespaceURI;attrValue=attr.value;if(attrNamespaceURI){attrName=attr.localName||attrName;fromValue=fromNode.getAttributeNS(attrNamespaceURI,attrName);if(fromValue!==attrValue){if(attr.prefix==="xmlns"){attrName=attr.name}fromNode.setAttributeNS(attrNamespaceURI,attrName,attrValue)}}else{fromValue=fromNode.getAttribute(attrName);if(fromValue!==attrValue){fromNode.setAttribute(attrName,attrValue)}}}var fromNodeAttrs=fromNode.attributes;for(var d=fromNodeAttrs.length-1;d>=0;d--){attr=fromNodeAttrs[d];attrName=attr.name;attrNamespaceURI=attr.namespaceURI;if(attrNamespaceURI){attrName=attr.localName||attrName;if(!toNode.hasAttributeNS(attrNamespaceURI,attrName)){fromNode.removeAttributeNS(attrNamespaceURI,attrName)}}else{if(!toNode.hasAttribute(attrName)){fromNode.removeAttribute(attrName)}}}}var range;var NS_XHTML="http://www.w3.org/1999/xhtml";var doc=typeof document==="undefined"?undefined:document;var HAS_TEMPLATE_SUPPORT=!!doc&&"content"in doc.createElement("template");var HAS_RANGE_SUPPORT=!!doc&&doc.createRange&&"createContextualFragment"in doc.createRange();function createFragmentFromTemplate(str){var template=doc.createElement("template");template.innerHTML=str;return template.content.childNodes[0]}function createFragmentFromRange(str){if(!range){range=doc.createRange();range.selectNode(doc.body)}var fragment=range.createContextualFragment(str);return fragment.childNodes[0]}function createFragmentFromWrap(str){var fragment=doc.createElement("body");fragment.innerHTML=str;return fragment.childNodes[0]}function toElement(str){str=str.trim();if(HAS_TEMPLATE_SUPPORT){return createFragmentFromTemplate(str)}else if(HAS_RANGE_SUPPORT){return createFragmentFromRange(str)}return createFragmentFromWrap(str)}function compareNodeNames(fromEl,toEl){var fromNodeName=fromEl.nodeName;var toNodeName=toEl.nodeName;var fromCodeStart,toCodeStart;if(fromNodeName===toNodeName){return true}fromCodeStart=fromNodeName.charCodeAt(0);toCodeStart=toNodeName.charCodeAt(0);if(fromCodeStart<=90&&toCodeStart>=97){return fromNodeName===toNodeName.toUpperCase()}else if(toCodeStart<=90&&fromCodeStart>=97){return toNodeName===fromNodeName.toUpperCase()}else{return false}}function createElementNS(name,namespaceURI){return!namespaceURI||namespaceURI===NS_XHTML?doc.createElement(name):doc.createElementNS(namespaceURI,name)}function moveChildren(fromEl,toEl){var curChild=fromEl.firstChild;while(curChild){var nextChild=curChild.nextSibling;toEl.appendChild(curChild);curChild=nextChild}return toEl}function syncBooleanAttrProp(fromEl,toEl,name){if(fromEl[name]!==toEl[name]){fromEl[name]=toEl[name];if(fromEl[name]){fromEl.setAttribute(name,"")}else{fromEl.removeAttribute(name)}}}var specialElHandlers={OPTION:function(fromEl,toEl){var parentNode=fromEl.parentNode;if(parentNode){var parentName=parentNode.nodeName.toUpperCase();if(parentName==="OPTGROUP"){parentNode=parentNode.parentNode;parentName=parentNode&&parentNode.nodeName.toUpperCase()}if(parentName==="SELECT"&&!parentNode.hasAttribute("multiple")){if(fromEl.hasAttribute("selected")&&!toEl.selected){fromEl.setAttribute("selected","selected");fromEl.removeAttribute("selected")}parentNode.selectedIndex=-1}}syncBooleanAttrProp(fromEl,toEl,"selected")},INPUT:function(fromEl,toEl){syncBooleanAttrProp(fromEl,toEl,"checked");syncBooleanAttrProp(fromEl,toEl,"disabled");if(fromEl.value!==toEl.value){fromEl.value=toEl.value}if(!toEl.hasAttribute("value")){fromEl.removeAttribute("value")}},TEXTAREA:function(fromEl,toEl){var newValue=toEl.value;if(fromEl.value!==newValue){fromEl.value=newValue}var firstChild=fromEl.firstChild;if(firstChild){var oldValue=firstChild.nodeValue;if(oldValue==newValue||!newValue&&oldValue==fromEl.placeholder){return}firstChild.nodeValue=newValue}},SELECT:function(fromEl,toEl){if(!toEl.hasAttribute("multiple")){var selectedIndex=-1;var i=0;var curChild=fromEl.firstChild;var optgroup;var nodeName;while(curChild){nodeName=curChild.nodeName&&curChild.nodeName.toUpperCase();if(nodeName==="OPTGROUP"){optgroup=curChild;curChild=optgroup.firstChild}else{if(nodeName==="OPTION"){if(curChild.hasAttribute("selected")){selectedIndex=i;break}i++}curChild=curChild.nextSibling;if(!curChild&&optgroup){curChild=optgroup.nextSibling;optgroup=null}}}fromEl.selectedIndex=selectedIndex}}};var ELEMENT_NODE=1;var DOCUMENT_FRAGMENT_NODE$1=11;var TEXT_NODE=3;var COMMENT_NODE=8;function noop(){}function defaultGetNodeKey(node){if(node){return node.getAttribute&&node.getAttribute("id")||node.id}}function morphdomFactory(morphAttrs){return function morphdom(fromNode,toNode,options){if(!options){options={}}if(typeof toNode==="string"){if(fromNode.nodeName==="#document"||fromNode.nodeName==="HTML"||fromNode.nodeName==="BODY"){var toNodeHtml=toNode;toNode=doc.createElement("html");toNode.innerHTML=toNodeHtml}else{toNode=toElement(toNode)}}else if(toNode.nodeType===DOCUMENT_FRAGMENT_NODE$1){toNode=toNode.firstElementChild}var getNodeKey=options.getNodeKey||defaultGetNodeKey;var onBeforeNodeAdded=options.onBeforeNodeAdded||noop;var onNodeAdded=options.onNodeAdded||noop;var onBeforeElUpdated=options.onBeforeElUpdated||noop;var onElUpdated=options.onElUpdated||noop;var onBeforeNodeDiscarded=options.onBeforeNodeDiscarded||noop;var onNodeDiscarded=options.onNodeDiscarded||noop;var onBeforeElChildrenUpdated=options.onBeforeElChildrenUpdated||noop;var skipFromChildren=options.skipFromChildren||noop;var addChild=options.addChild||function(parent,child){return parent.appendChild(child)};var childrenOnly=options.childrenOnly===true;var fromNodesLookup=Object.create(null);var keyedRemovalList=[];function addKeyedRemoval(key){keyedRemovalList.push(key)}function walkDiscardedChildNodes(node,skipKeyedNodes){if(node.nodeType===ELEMENT_NODE){var curChild=node.firstChild;while(curChild){var key=undefined;if(skipKeyedNodes&&(key=getNodeKey(curChild))){addKeyedRemoval(key)}else{onNodeDiscarded(curChild);if(curChild.firstChild){walkDiscardedChildNodes(curChild,skipKeyedNodes)}}curChild=curChild.nextSibling}}}function removeNode(node,parentNode,skipKeyedNodes){if(onBeforeNodeDiscarded(node)===false){return}if(parentNode){parentNode.removeChild(node)}onNodeDiscarded(node);walkDiscardedChildNodes(node,skipKeyedNodes)}function indexTree(node){if(node.nodeType===ELEMENT_NODE||node.nodeType===DOCUMENT_FRAGMENT_NODE$1){var curChild=node.firstChild;while(curChild){var key=getNodeKey(curChild);if(key){fromNodesLookup[key]=curChild}indexTree(curChild);curChild=curChild.nextSibling}}}indexTree(fromNode);function handleNodeAdded(el){onNodeAdded(el);var curChild=el.firstChild;while(curChild){var nextSibling=curChild.nextSibling;var key=getNodeKey(curChild);if(key){var unmatchedFromEl=fromNodesLookup[key];if(unmatchedFromEl&&compareNodeNames(curChild,unmatchedFromEl)){curChild.parentNode.replaceChild(unmatchedFromEl,curChild);morphEl(unmatchedFromEl,curChild)}else{handleNodeAdded(curChild)}}else{handleNodeAdded(curChild)}curChild=nextSibling}}function cleanupFromEl(fromEl,curFromNodeChild,curFromNodeKey){while(curFromNodeChild){var fromNextSibling=curFromNodeChild.nextSibling;if(curFromNodeKey=getNodeKey(curFromNodeChild)){addKeyedRemoval(curFromNodeKey)}else{removeNode(curFromNodeChild,fromEl,true)}curFromNodeChild=fromNextSibling}}function morphEl(fromEl,toEl,childrenOnly){var toElKey=getNodeKey(toEl);if(toElKey){delete fromNodesLookup[toElKey]}if(!childrenOnly){if(onBeforeElUpdated(fromEl,toEl)===false){return}morphAttrs(fromEl,toEl);onElUpdated(fromEl);if(onBeforeElChildrenUpdated(fromEl,toEl)===false){return}}if(fromEl.nodeName!=="TEXTAREA"){morphChildren(fromEl,toEl)}else{specialElHandlers.TEXTAREA(fromEl,toEl)}}function morphChildren(fromEl,toEl){var skipFrom=skipFromChildren(fromEl,toEl);var curToNodeChild=toEl.firstChild;var curFromNodeChild=fromEl.firstChild;var curToNodeKey;var curFromNodeKey;var fromNextSibling;var toNextSibling;var matchingFromEl;outer:while(curToNodeChild){toNextSibling=curToNodeChild.nextSibling;curToNodeKey=getNodeKey(curToNodeChild);while(!skipFrom&&curFromNodeChild){fromNextSibling=curFromNodeChild.nextSibling;if(curToNodeChild.isSameNode&&curToNodeChild.isSameNode(curFromNodeChild)){curToNodeChild=toNextSibling;curFromNodeChild=fromNextSibling;continue outer}curFromNodeKey=getNodeKey(curFromNodeChild);var curFromNodeType=curFromNodeChild.nodeType;var isCompatible=undefined;if(curFromNodeType===curToNodeChild.nodeType){if(curFromNodeType===ELEMENT_NODE){if(curToNodeKey){if(curToNodeKey!==curFromNodeKey){if(matchingFromEl=fromNodesLookup[curToNodeKey]){if(fromNextSibling===matchingFromEl){isCompatible=false}else{fromEl.insertBefore(matchingFromEl,curFromNodeChild);if(curFromNodeKey){addKeyedRemoval(curFromNodeKey)}else{removeNode(curFromNodeChild,fromEl,true)}curFromNodeChild=matchingFromEl;curFromNodeKey=getNodeKey(curFromNodeChild)}}else{isCompatible=false}}}else if(curFromNodeKey){isCompatible=false}isCompatible=isCompatible!==false&&compareNodeNames(curFromNodeChild,curToNodeChild);if(isCompatible){morphEl(curFromNodeChild,curToNodeChild)}}else if(curFromNodeType===TEXT_NODE||curFromNodeType==COMMENT_NODE){isCompatible=true;if(curFromNodeChild.nodeValue!==curToNodeChild.nodeValue){curFromNodeChild.nodeValue=curToNodeChild.nodeValue}}}if(isCompatible){curToNodeChild=toNextSibling;curFromNodeChild=fromNextSibling;continue outer}if(curFromNodeKey){addKeyedRemoval(curFromNodeKey)}else{removeNode(curFromNodeChild,fromEl,true)}curFromNodeChild=fromNextSibling}if(curToNodeKey&&(matchingFromEl=fromNodesLookup[curToNodeKey])&&compareNodeNames(matchingFromEl,curToNodeChild)){if(!skipFrom){addChild(fromEl,matchingFromEl)}morphEl(matchingFromEl,curToNodeChild)}else{var onBeforeNodeAddedResult=onBeforeNodeAdded(curToNodeChild);if(onBeforeNodeAddedResult!==false){if(onBeforeNodeAddedResult){curToNodeChild=onBeforeNodeAddedResult}if(curToNodeChild.actualize){curToNodeChild=curToNodeChild.actualize(fromEl.ownerDocument||doc)}addChild(fromEl,curToNodeChild);handleNodeAdded(curToNodeChild)}}curToNodeChild=toNextSibling;curFromNodeChild=fromNextSibling}cleanupFromEl(fromEl,curFromNodeChild,curFromNodeKey);var specialElHandler=specialElHandlers[fromEl.nodeName];if(specialElHandler){specialElHandler(fromEl,toEl)}}var morphedNode=fromNode;var morphedNodeType=morphedNode.nodeType;var toNodeType=toNode.nodeType;if(!childrenOnly){if(morphedNodeType===ELEMENT_NODE){if(toNodeType===ELEMENT_NODE){if(!compareNodeNames(fromNode,toNode)){onNodeDiscarded(fromNode);morphedNode=moveChildren(fromNode,createElementNS(toNode.nodeName,toNode.namespaceURI))}}else{morphedNode=toNode}}else if(morphedNodeType===TEXT_NODE||morphedNodeType===COMMENT_NODE){if(toNodeType===morphedNodeType){if(morphedNode.nodeValue!==toNode.nodeValue){morphedNode.nodeValue=toNode.nodeValue}return morphedNode}else{morphedNode=toNode}}}if(morphedNode===toNode){onNodeDiscarded(fromNode)}else{if(toNode.isSameNode&&toNode.isSameNode(morphedNode)){return}morphEl(morphedNode,toNode,childrenOnly);if(keyedRemovalList){for(var i=0,len=keyedRemovalList.length;i li, -article ol > li { - padding-left: 4px; -} -article.rtl ul > li, -article.rtl ol > li { - padding-right: 4px; - padding-left: 0; -} -/*article ul > li { - position: relative; -} -article ul > li:before { - content: '\2022'; - position: absolute; - display: block; - font-size: 163%; - left: -19px; - top: 1px; -} -article.rtl ul > li:before { - left: auto; - right: -19px; -}*/ -article ul ul, -article ul ol, -article ol ul, -article ol ol { - margin: 0 0 12px; -} - -article table { - width: 100%; - border-collapse: collapse; -} -article table.bordered, -article table.bordered td, -article table.bordered th { - border: 1px solid var(--td-history-to-down-shadow); -} -article table.striped tr:nth-child(odd) td { - background-color: var(--td-box-divider-bg); -} -article table caption { - font-size: 15px; - line-height: 18px; - margin: 4px 0 7px; - text-align: left; - color: var(--td-window-sub-text-fg); -} -article.rtl table caption { - text-align: right; -} -article td, -article th { - font-size: 15px; - line-height: 21px; - padding: 6px 5px 5px; - background-color: var(--td-window-bg); - vertical-align: middle; - font-weight: normal; - text-align: left; -} -article th { - background-color: var(--td-box-divider-bg); -} -article.rtl table td, -article.rtl table th { - text-align: right; -} -article details { - position: relative; - margin: 0 0 12px; - padding: 0 0 1px; -} -article details:before { - content: ''; - display: block; - border-bottom: 1px solid var(--td-history-to-down-shadow); - position: absolute; - left: 18px; - right: 0; - bottom: 0; -} -article.rtl details:before { - right: 18px; - left: 0; -} -article details + details { - margin-top: -12px; -} -article details > details:last-child { - margin-bottom: -1px; -} -article summary { - padding: 10px 18px 10px 42px; - line-height: 25px; - min-height: 25px; -} -article.rtl summary { - padding-left: 18px; - padding-right: 42px; -} -article summary:hover { - cursor: pointer; -} -article summary:focus { - outline: none; -} -article summary::-webkit-details-marker { - display: none; -} -article summary::marker { - content: ''; -} -article summary:before { - content: ''; - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAICAYAAADN5B7xAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAH1JREFUeNqEjUEKgCAQRSfrNi1bdZFadJjsMC46SSAIHqjB5mcFqdFfhD3eUyKZtb6ln92O2janmXdvrRu+ZTfAgasu1jAHU4qiHAwc/Ff4oCQKsxxZ0NT33XrxUTjkWvgiXFf3TWkU6Vt+XihH515yFiQRpfLnEMUw3yHAABZNTT9emBrvAAAAAElFTkSuQmCC'); - transition: all .2s ease; - display: inline-block; - position: absolute; - width: 12px; - height: 8px; - left: 18px; - top: 18px; -} -article.rtl summary:before { - right: 18px; - left: auto; -} -@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { - article summary:before { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPxJREFUeNq8lEESgiAUhgFbZ0epSW28gB2pZbrrSukBHDWto1TrwHih45AiaDOxesLP9w1PBlzXNfrLSNPqkGWV8ysHGMBqv4mAlyFC7MRPk+T51Z0Lh73AAJZgIoRFUR/bEMb4TggJPG9TTIUzxmIuWHWzOCLfQQgwRiedRMBpIsObFvn+NgSTLEE2bCiKm6eDQ0bAkS2v4AjYuPvJcqtEu9DDshaB665zFZzSV6yCfyr5JplLTOA9wZiEg/a+72Qic9nxubMOPijQSZraCK4UjEiezSVYmsBHBSrJAEIJ1wr0knG4kUAt0cONBX2JGXzGi1uG7SNmOt4CDADc4r+K4txg+wAAAABJRU5ErkJggg=='); - background-size: 12px 8px; - } -} -article details[open] > summary:before { - /*transform: rotateZ(-180deg);*/ - transform: scaleY(-1); -} -article li summary { - padding-left: 24px; -} -article li details:before, -article li summary:before { - left: 0; -} - -img, -video, -iframe { - max-width: 100%; - max-height: 480px; - vertical-align: top; -} -video { - width: 100%; -} -audio { - width: 100%; - width: calc(100% - 36px); - margin: 0 18px; - vertical-align: top; -} -img { - font-size: 12px; - line-height: 14px; - color: var(--td-window-sub-text-fg); -} -img.pic { - max-height: none; - font-size: inherit; - vertical-align: middle; - position: relative; - top: -0.1em; -} -img.pic.optional { - opacity: 0.4; -} -body:hover img.pic.optional { - opacity: 1; -} -iframe.autosize { - max-height: none; -} -.iframe-wrap { - max-width: 100%; - vertical-align: top; - display: inline-block; - position: relative; -} -.iframe-wrap iframe { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; -} -figure { - margin: 0 0 16px; - padding: 0; - text-align: center; - position: relative; -} -figure.nowide { - margin-left: 18px; - margin-right: 18px; -} -figure.nowide figcaption { - padding-left: 0; - padding-right: 0; -} -ul figure.nowide, -ol figure.nowide { - margin: 0 0 12px; -} -figure > figure { - margin: 0; -} -figcaption { - font-size: 15px; - color: var(--td-window-sub-text-fg); - padding: 6px 18px 0; - line-height: 19px; - text-align: left; -} -article.rtl figcaption { - text-align: right; -} -ul figcaption, -ol figcaption { - padding-left: 0; - padding-right: 0; -} -figcaption > cite { - font-family: var(--font-sans); - font-size: 12px; - display: block; - line-height: 15px; - padding: 2px 0 0; - font-style: normal; -} -footer { - margin: 12px 18px; - color: var(--td-window-sub-text-fg); -} - -figure.slideshow-wrap { - position: relative; -} -figure.slideshow { - position: absolute; - top: 0px; - white-space: nowrap; - width: 100%; - background: #000; - overflow: hidden; -} -figure.slideshow a { - transition: margin 200ms ease-in-out; -} -figure.slideshow .photo-wrap, -figure.slideshow .video-wrap { - position: static !important; - display: inline-block; - margin: 0; - vertical-align: middle; -} -.slideshow-buttons { - position: absolute; - width: 100%; - bottom: 10px; - white-space: nowrap; - overflow: hidden; - z-index: 5; -} -.slideshow-buttons > fieldset { - padding: 0; - margin: 0; - border: none; - line-height: 0; - overflow: hidden; - overflow-x: auto; - min-width: auto; -} -.slideshow-buttons label { - display: inline-block; - padding: 7px; - cursor: pointer; -} -.slideshow-buttons input { - position: absolute; - left: -10000px; -} -.slideshow-buttons label i { - display: inline-block; - background: #fff; - box-shadow: 0 0 3px rgba(0, 0, 0, .4); - border-radius: 3.5px; - width: 7px; - height: 7px; - opacity: .6; - transition: opacity .3s; -} -.slideshow-buttons input:checked ~ i { - opacity: 1; -} -.slideshow-next, -.slideshow-prev { - position: absolute; - z-index: 4; - top: 0; - width: 25%; - max-width: 128px; - height: 100%; - cursor: pointer; - transition: opacity 200ms ease-in-out; - user-select: none; - opacity: 0.6; -} -.slideshow-next { - right: 0; - background: linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%); -} -.slideshow-prev { - left: 0; - background: linear-gradient(to left, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%); -} -.slideshow-next:hover { - opacity: 1; -} -.slideshow-prev:hover { - opacity: 1; -} -.slideshow-prev svg, -.slideshow-next svg { - fill: none; - top: calc(50% - 12px); - position: absolute; - z-index: 5; - width: 24px; - height: 24px; - pointer-events: none; -} -.slideshow-prev svg { - left: calc(min(50% - 12px, 20px)); -} -.slideshow-next svg { - right: calc(min(50% - 12px, 20px)); -} -.slideshow-prev path, -.slideshow-next path { - stroke-width: 1.4; - stroke: #fff; -} - -figure.collage-wrap { - margin: 0px 12px; -} -figure.collage-wrap figcaption { - padding: 6px 6px 0px; -} -figure.collage { - overflow: hidden; - border-radius: 6px; -} -figure.collage .photo-wrap, -figure.collage .video-wrap { - position: absolute; -} -figure.collage .photo-wrap .photo { - background-size: cover; -} -figure.collage .video-wrap video { - object-fit: cover; - position: absolute; - top: 50%; - left: 50%; - width: auto; - height: auto; - min-width: 100%; - min-height: 100%; - transform: translate(-50%, -50%); -} -figure.collage .video-wrap .video-small, -video[autoplay] { - pointer-events: none; -} - -figure.table-wrap { - overflow: auto; - -webkit-overflow-scrolling: touch; -} -figure.table { - display: table-cell; - padding: 0 18px; -} -article ol figure.table-wrap, -article ul figure.table-wrap { - margin-top: 7px; -} -article ol figure.table, -article ul figure.table { - padding: 0; -} - -figure blockquote.embed-post { - text-align: left; - margin-bottom: 0; -} -article.rtl figure blockquote.embed-post { - text-align: right; -} -blockquote.embed-post address { - margin: 0; - padding: 5px 0 9px; - overflow: hidden; -} -blockquote.embed-post address figure { - width: 50px; - height: 50px; - float: left; - margin: 0 12px 0 0; - background: no-repeat center; - background-size: cover; - border-radius: 50%; -} -article.rtl blockquote.embed-post address figure { - float: right; - margin-left: 12px; - margin-right: 0; -} -blockquote.embed-post address a { - display: inline-block; - padding-top: 2px; - font-size: 17px; - font-weight: 600; - color: var(--td-window-fg); -} -blockquote.embed-post address time { - display: block; - line-height: 19px; -} -blockquote.embed-post p, -blockquote.embed-post blockquote { - margin: 0 0 7px; - clear: left; -} -blockquote.embed-post figcaption { - padding-left: 0; - padding-right: 0; -} -blockquote.embed-post figure.collage { - margin-left: -2px; - margin-right: -2px; -} -blockquote.embed-post footer { - margin: 12px 0 0; - font-style: normal; -} -blockquote.embed-post footer hr { - display: none; -} - -section.embed-post { - display: block; - width: auto; - height: auto; - background: var(--td-box-divider-bg); - margin: 0 18px 12px; - padding: 24px 18px; - text-align: center; -} -section.embed-post strong { - font-size: 21px; - font-weight: normal; - display: block; - color: var(--td-window-sub-text-fg); -} -section.embed-post small { - display: block; - color: var(--td-window-sub-text-fg); -} - - -section.related { - margin: 7px 0 12px; -} -section.related h4 { - font-family: var(--font-sans); - font-size: 17px; - line-height: 26px; - font-weight: 500; - display: block; - padding: 7px 18px; - background: var(--td-box-divider-bg); - margin: 0; - color: var(--td-window-fg); -} -section.related a.related-link { - display: block; - padding: 15px 18px 16px; - background: var(--td-window-bg); - position: relative; - overflow: hidden; -} -section.related a.related-link:after { - content: ''; - display: block; - border-bottom: 1px solid var(--td-history-to-down-shadow); - position: absolute; - left: 18px; - right: 0; - bottom: 0; -} -section.related a.related-link:last-child:after { - border-bottom: 0px; -} -section.related .related-link-url { - display: block; - font-size: 15px; - line-height: 18px; - padding: 7px 0; - color: var(--td-window-sub-text-fg); - word-break: break-word; -} -section.related .related-link-thumb { - display: inline-block; - float: right; - width: 87px; - height: 87px; - border-radius: 4px; - background: no-repeat center; - background-size: cover; - margin-left: 15px; -} -section.related .related-link-content { - display: block; - margin: -3px 0; -} -section.related .related-link-title { - font-size: 15px; - font-weight: 500; - line-height: 18px; - display: block; - display: -webkit-box; - margin-bottom: 4px; - max-height: 36px; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - white-space: pre-wrap; - color: var(--td-window-fg); -} -section.related .related-link-desc { - font-size: 14px; - line-height: 17px; - display: block; - display: -webkit-box; - max-height: 51px; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - white-space: pre-wrap; - color: var(--td-window-fg); -} -section.related .related-link-source { - font-size: 13px; - line-height: 17px; - display: block; - overflow: hidden; - margin-top: 4px; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--td-window-sub-text-fg); -} - -section.message { - position: absolute; - display: table; - width: 100%; - height: 100%; -} -section.message.static { - position: static; - min-height: 200px; - height: 100vh; -} -section.message > aside { - display: table-cell; - vertical-align: middle; - text-align: center; - color: var(--td-window-sub-text-fg); - font-size: 24px; - pointer-events: none; -} -section.message > aside > cite { - display: block; - font-size: 14px; - padding: 10px 0 0; - font-style: normal; - color: var(--td-window-sub-text-fg); -} - -section.channel { - margin-top: -16px; - margin-bottom: -9px; -} -section.channel:first-child { - margin-top: 0; -} -section.channel > a { - display: block; - background: var(--td-box-divider-bg); -} -section.channel > a > div.join { - color: var(--td-window-active-text-fg); - font-weight: 500; - padding: 7px 18px; - float: right; -} -section.channel.joined > a > div.join { - display: none; -} -section.channel > a > div.join:hover { - text-decoration: underline; -} -section.channel > a > div.join span:before { - content: var(--td-lng-iv-join-channel); -} -section.channel > a > h4 { - font-family: var(--font-sans); - font-size: 17px; - line-height: 26px; - font-weight: 500; - margin: 0; - color: var(--td-window-fg); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding: 7px 18px; -} - -.pullquote { - text-align: center; - max-width: 420px; - font-size: 19px; - display: block; - margin: 0 auto; -} -.media-outer { - margin-bottom: 16px; -} -.photo-wrap, -.video-wrap { - width: 100%; - margin: 0 auto; - position: relative; - overflow: hidden; -} -.photo-bg, -.video-bg { - background-size: cover; - background-position: center; - background-repeat: no-repeat; - position: absolute; - filter: blur(16px); - width: 100%; - height: 100%; -} -.video-bg, -video { - position: absolute; - top: 0px; -} -.photo { - position: relative; - background-size: contain; - background-position: center; - background-repeat: no-repeat; -} -.photo, -video { - opacity: 0; - transition: opacity 300ms ease-in-out; -} -.photo.loaded, -video.loaded { - opacity: 1; -} -.video-play-outer { - position: relative; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} -.video-play { - position: relative; - width: 48px; - height: 0; - padding-top: 48px; - max-width: 48px; - max-height: 48px; - background-color: rgba(0, 0, 0, 0.34); - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; -} -.video-play::before { - content: ''; - position: absolute; - margin: -48px -4px 0px 0px; - width: 0; - height: 0; - border-style: solid; - border-width: 10px 0 10px 16px; - border-color: transparent transparent transparent white; -} - -.toast { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--td-toast-bg); - color: var(--td-toast-fg); - padding: 10px 20px; - border-radius: 6px; - z-index: 9999; - opacity: 0; - animation: fadeIn 200ms linear forwards; -} -.toast.hiding { - opacity: 1; - animation: fadeOut 1000ms linear forwards; -} diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js deleted file mode 100644 index fda34772fe060d..00000000000000 --- a/Telegram/Resources/iv_html/page.js +++ /dev/null @@ -1,656 +0,0 @@ -var IV = { - notify: function(message) { - if (window.external && window.external.invoke) { - window.external.invoke(JSON.stringify(message)); - } - }, - frameClickHandler: function(e) { - var target = e.target; - var context = ''; - while (target) { - if (target.id == 'menu_page_blocker') { - IV.notify({ event: 'menu_page_blocker_click' }); - IV.menuShown(false); - return; - } - if (target.tagName == 'AUDIO' || target.tagName == 'VIDEO') { - return; - } - if (context === '' - && target.hasAttribute - && target.hasAttribute('data-context')) { - context = String(target.getAttribute('data-context')); - } - if (target.tagName == 'A') { - break; - } - target = target.parentNode; - } - if (!target || (context === '' && !target.hasAttribute('href'))) { - return; - } - var base = document.createElement('A'); - base.href = window.location.href; - if (base.origin != target.origin - || base.pathname != target.pathname - || base.search != target.search) { - IV.notify({ - event: 'link_click', - url: target.href, - context: context, - }); - } else if (target.hash.length < 2) { - IV.jumpToHash(''); - } else { - IV.jumpToHash(decodeURIComponent(target.hash.substr(1))); - } - e.preventDefault(); - }, - getElementTop: function (element) { - var top = 0; - while (element && !element.classList.contains('page-scroll')) { - top += element.offsetTop; - element = element.offsetParent; - } - return top; - }, - jumpToHash: function (hash, instant) { - var current = IV.computeCurrentState(); - current.hash = hash; - window.history.replaceState( - current, - '', - 'page' + IV.index + '.html'); - if (hash == '') { - IV.scrollTo(0, instant); - return; - } - - var element = document.getElementsByName(hash)[0]; - if (element) { - IV.scrollTo(IV.getElementTop(element), instant); - } - }, - frameKeyDown: function (e) { - const keyW = (e.key === 'w') - || (e.code === 'KeyW') - || (e.keyCode === 87); - const keyQ = (e.key === 'q') - || (e.code === 'KeyQ') - || (e.keyCode === 81); - const keyM = (e.key === 'm') - || (e.code === 'KeyM') - || (e.keyCode === 77); - if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) { - e.preventDefault(); - IV.notify({ - event: 'keydown', - modifier: e.ctrlKey ? 'ctrl' : 'cmd', - key: keyW ? 'w' : keyQ ? 'q' : 'm', - }); - } else if (e.key === 'Escape' || e.keyCode === 27) { - e.preventDefault(); - if (IV.position) { - window.history.back(); - } else { - IV.notify({ - event: 'keydown', - key: 'escape', - }); - } - } - }, - frameMouseEnter: function (e) { - IV.notify({ event: 'mouseenter' }); - }, - frameMouseUp: function (e) { - IV.notify({ event: 'mouseup' }); - }, - lastScrollTop: 0, - frameScrolled: function (e) { - const was = IV.lastScrollTop; - IV.lastScrollTop = IV.findPageScroll().scrollTop; - IV.updateJumpToTop(was < IV.lastScrollTop); - IV.checkVideos(); - }, - updateJumpToTop: function (scrolledDown) { - if (IV.lastScrollTop < 100) { - document.getElementById('bottom_up').classList.add('hidden'); - } else if (scrolledDown && IV.lastScrollTop > 200) { - document.getElementById('bottom_up').classList.remove('hidden'); - } - }, - updateStyles: function (styles) { - if (IV.styles !== styles) { - IV.styles = styles; - document.getElementsByTagName('html')[0].style = styles; - } - }, - toggleChannelJoined: function (id, joined) { - IV.channelsJoined['channel' + id] = joined; - IV.checkChannelButtons(); - }, - checkChannelButtons: function() { - const channels = document.getElementsByClassName('channel'); - for (var i = 0; i < channels.length; ++i) { - const channel = channels[i]; - const full = String(channel.getAttribute('data-context')); - const value = IV.channelsJoined[full]; - if (value !== undefined) { - channel.classList.toggle('joined', value); - } - } - }, - slideshowSlide: function(el, delta) { - var dir = window.getComputedStyle(el, null).direction || 'ltr'; - var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft'; - if (delta) { - var form = el.parentNode.firstChild; - var s = form.s; - const next = +s.value + delta; - s.value = (next == s.length) ? 0 : (next == -1) ? (s.length - 1) : next; - form.nextSibling.firstChild.style[marginProp] = (-100 * s.value) + '%'; - } else { - el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%'; - } - return false; - }, - initPreBlocks: function() { - if (!hljs) { - return; - } - var pres = document.getElementsByTagName('pre'); - for (var i = 0; i < pres.length; i++) { - if (pres[i].hasAttribute('data-language')) { - hljs.highlightBlock(pres[i]); - } - } - }, - initEmbedBlocks: function() { - var iframes = document.getElementsByTagName('iframe'); - for (var i = 0; i < iframes.length; i++) { - (function(iframe) { - window.addEventListener('message', function(event) { - if (event.source !== iframe.contentWindow || - event.origin != window.origin) { - return; - } - try { - var data = JSON.parse(event.data); - } catch(e) { - var data = {}; - } - if (data.eventType == 'resize_frame') { - if (data.eventData.height) { - iframe.style.height = data.eventData.height + 'px'; - } - } - }, false); - })(iframes[i]); - } - }, - addRipple: function (button, x, y) { - const ripple = document.createElement('span'); - ripple.classList.add('ripple'); - - const inner = document.createElement('span'); - inner.classList.add('inner'); - x -= button.offsetLeft; - y -= button.offsetTop; - - const mx = button.clientWidth - x; - const my = button.clientHeight - y; - const sq1 = x * x + y * y; - const sq2 = mx * mx + y * y; - const sq3 = x * x + my * my; - const sq4 = mx * mx + my * my; - const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4)); - - inner.style.width = inner.style.height = `${2 * radius}px`; - inner.style.left = `${x - radius}px`; - inner.style.top = `${y - radius}px`; - inner.classList.add('inner'); - - ripple.addEventListener('animationend', function (e) { - if (e.animationName === 'fadeOut') { - ripple.remove(); - } - }); - - ripple.appendChild(inner); - button.appendChild(ripple); - }, - stopRipples: function (button) { - const id = button.id ? button.id : button; - button = document.getElementById(id); - const ripples = button.getElementsByClassName('ripple'); - for (var i = 0; i < ripples.length; ++i) { - const ripple = ripples[i]; - if (!ripple.classList.contains('hiding')) { - ripple.classList.add('hiding'); - } - } - }, - init: function () { - var current = IV.computeCurrentState(); - window.history.replaceState(current, '', IV.pageUrl(0)); - IV.jumpToHash(current.hash, true); - - IV.lastScrollTop = window.history.state.scroll; - IV.findPageScroll().onscroll = IV.frameScrolled; - - const buttons = document.getElementsByClassName('fixed_button'); - for (let i = 0; i < buttons.length; ++i) { - const button = buttons[i]; - button.addEventListener('mousedown', function (e) { - IV.addRipple(e.currentTarget, e.clientX, e.clientY); - }); - button.addEventListener('mouseup', function (e) { - const id = e.currentTarget.id; - setTimeout(function () { - IV.stopRipples(id); - }, 0); - }); - button.addEventListener('mouseleave', function (e) { - IV.stopRipples(e.currentTarget); - }); - } - IV.initMedia(); - IV.notify({ event: 'ready' }); - - IV.forceScrollFocus(); - IV.frameScrolled(); - }, - initMedia: function () { - var scroll = IV.findPageScroll(); - const photos = scroll.getElementsByClassName('photo'); - for (let i = 0; i < photos.length; ++i) { - const photo = photos[i]; - if (photo.classList.contains('loaded')) { - continue; - } - - const url = photo.style.backgroundImage; - if (!url || url.length < 7) { - continue; - } - var img = new Image(); - img.onload = function () { - photo.classList.add('loaded'); - } - img.src = url.substr(5, url.length - 7); - if (img.complete) { - photo.classList.add('loaded'); - IV.stopAnimations(photo); - } - } - IV.videos = []; - const videos = scroll.getElementsByClassName('video'); - for (let i = 0; i < videos.length; ++i) { - const element = videos[i]; - IV.videos.push({ - element: element, - src: String(element.getAttribute('data-src')), - autoplay: (element.getAttribute('data-autoplay') == '1'), - loop: (element.getAttribute('data-loop') == '1'), - small: (element.getAttribute('data-small') == '1'), - filled: (element.firstChild - && element.firstChild.tagName == 'VIDEO'), - }); - } - }, - checkVideos: function () { - const visibleTop = IV.lastScrollTop; - const visibleBottom = visibleTop + IV.findPageScroll().offsetHeight; - const videos = IV.videos; - for (let i = 0; i < videos.length; ++i) { - const video = videos[i]; - const element = video.element; - const wrap = element.offsetParent; // video-wrap - const top = IV.getElementTop(wrap); - const bottom = top + wrap.offsetHeight; - if (top < visibleBottom && bottom > visibleTop) { - if (!video.created) { - video.created = new Date(); - video.loaded = false; - element.innerHTML = ''; - var media = element.firstChild; - media.oncontextmenu = function () { return false; }; - media.oncanplay = IV.checkVideos; - media.onloadeddata = IV.checkVideos; - } - } else if (video.created && video.autoplay) { - video.created = false; - element.innerHTML = ''; - } - if (video.created && !video.loaded) { - var media = element.firstChild; - const HAVE_CURRENT_DATA = 2; - if (media && media.readyState >= HAVE_CURRENT_DATA) { - video.loaded = true; - media.classList.add('loaded'); - if ((new Date() - video.created) < 100) { - IV.stopAnimations(media); - } - } - } - } - }, - showTooltip: function (text) { - var toast = document.createElement('div'); - toast.classList.add('toast'); - toast.textContent = text; - document.body.appendChild(toast); - setTimeout(function () { - toast.classList.add('hiding'); - }, 2000); - setTimeout(function () { - document.body.removeChild(toast); - }, 3000); - }, - scrollTo: function (y, instant) { - if (y < 200) { - document.getElementById('bottom_up').classList.add('hidden'); - } - IV.findPageScroll().scrollTo({ - top: y || 0, - behavior: instant ? 'instant' : 'smooth' - }); - }, - computeCurrentState: function () { - var now = IV.findPageScroll(); - return { - position: IV.position, - index: IV.index, - hash: ((!window.history.state - || window.history.state.hash === undefined) - ? window.location.hash.substr(1) - : window.history.state.hash), - scroll: now ? now.scrollTop : 0 - }; - }, - pageUrl: function (index, hash) { - var result = 'page' + index + '.html'; - if (hash) { - result += '#' + hash; - } - return result; - }, - navigateTo: function (index, hash) { - if (!index && !IV.index) { - IV.navigateToDOM(IV.index, hash); - return; - } - IV.pending = [index, hash]; - if (!IV.cache[index]) { - IV.loadPage(index); - } else if (IV.cache[index].dom) { - IV.navigateToDOM(index, hash); - } else if (IV.cache[index].content) { - IV.navigateToLoaded(index, hash); - } - }, - applyUpdatedContent: function (index) { - if (IV.index != index) { - IV.cache[index].contentUpdated = (IV.cache[index].dom !== undefined); - return; - } - var data = JSON.parse(IV.cache[index].content); - var article = function (el) { - return el.getElementsByTagName('article')[0]; - }; - var footer = function (el) { - return el.getElementsByClassName('page-footer')[0]; - }; - var from = IV.findPageScroll(); - var to = IV.makeScrolledContent(data.html); - morphdom(article(from), article(to), { - onBeforeElUpdated: function (fromEl, toEl) { - if (fromEl.classList.contains('video') - && toEl.classList.contains('video') - && fromEl.hasAttribute('data-src') - && toEl.hasAttribute('data-src') - && (fromEl.getAttribute('data-src') - == toEl.getAttribute('data-src'))) { - return false; - } else if (fromEl.tagName == 'SECTION' - && fromEl.classList.contains('channel') - && fromEl.hasAttribute('data-context') - && toEl.tagName == 'SECTION' - && toEl.classList.contains('channel') - && toEl.hasAttribute('data-context') - && (String(fromEl.getAttribute('data-context')) - == String(toEl.getAttribute('data-context')))) { - return false; - } else if (fromEl.classList.contains('loaded')) { - toEl.classList.add('loaded'); - } - return !fromEl.isEqualNode(toEl); - } - }); - morphdom(footer(from), footer(to)); - IV.initMedia(); - eval(data.js); - }, - loadPage: function (index) { - if (!IV.cache[index]) { - IV.cache[index] = {}; - } - IV.cache[index].loading = true; - - let xhr = new XMLHttpRequest(); - xhr.onload = function () { - IV.cache[index].loading = false; - IV.cache[index].content = xhr.responseText; - IV.applyUpdatedContent(index); - if (IV.pending && IV.pending[0] == index) { - IV.navigateToLoaded(index, IV.pending[1]); - } - if (IV.cache[index].reloadPending) { - IV.cache[index].reloadPending = false; - IV.reloadPage(index); - } - } - - xhr.open('GET', 'page' + index + '.json'); - xhr.send(); - }, - reloadPage: function (index) { - if (IV.cache[index] && IV.cache[index].loading) { - IV.cache[index].reloadPending = true; - return; - } - IV.loadPage(index); - }, - - makeScrolledContent: function (html) { - var result = document.createElement('div'); - result.className = 'page-scroll'; - result.tabIndex = '-1'; - result.innerHTML = html.trim(); - result.onscroll = IV.frameScrolled; - return result; - }, - navigateToLoaded: function (index, hash) { - if (IV.cache[index].dom) { - IV.navigateToDOM(index, hash); - } else { - var data = JSON.parse(IV.cache[index].content); - IV.cache[index].dom = IV.makeScrolledContent(data.html); - - IV.navigateToDOM(index, hash); - eval(data.js); - } - }, - navigateToDOM: function (index, hash) { - IV.pending = null; - if (IV.index == index) { - IV.jumpToHash(hash); - IV.forceScrollFocus(); - return; - } - window.history.replaceState( - IV.computeCurrentState(), - '', - IV.pageUrl(IV.index)); - - IV.position = IV.position + 1; - window.history.pushState( - { position: IV.position, index: index, hash: hash }, - '', - IV.pageUrl(index)); - IV.showDOM(index, hash); - }, - findPageScroll: function () { - var all = document.getElementsByClassName('page-scroll'); - for (i = 0; i < all.length; ++i) { - if (!all[i].classList.contains('hidden-left') - && !all[i].classList.contains('hidden-right')) { - return all[i]; - } - } - return null; - }, - showDOM: function (index, hash, scroll) { - IV.pending = null; - if (IV.index != index) { - var initial = !window.history.state - || window.history.state.position === undefined; - var back = initial - || IV.position > window.history.state.position; - IV.position = initial ? 0 : window.history.state.position; - - var now = IV.cache[index].dom; - var was = IV.findPageScroll(); - if (!IV.cache[IV.index]) { - IV.cache[IV.index] = {}; - } - IV.cache[IV.index].dom = was; - was.parentNode.appendChild(now); - if (scroll !== undefined) { - now.scrollTop = scroll; - setTimeout(function () { - // When returning by history.back to an URL with a hash - // for the first time browser forces the scroll to the - // hash instead of the saved scroll position. - // - // This workaround prevents incorrect scroll position. - now.scrollTop = scroll; - }, 0); - } - - now.classList.add(back ? 'hidden-left' : 'hidden-right'); - now.classList.remove(back ? 'hidden-right' : 'hidden-left'); - IV.stopAnimations(now.firstChild); - - if (!was.listening) { - was.listening = true; - was.firstChild.addEventListener('transitionend', function (e) { - if (was.classList.contains('hidden-left') - || was.classList.contains('hidden-right')) { - if (was.parentNode) { - was.parentNode.removeChild(was); - var videos = was.getElementsByClassName('video'); - for (var i = 0; i < videos.length; ++i) { - videos[i].innerHTML = ''; - } - } - } - }); - } - - was.classList.add(back ? 'hidden-right' : 'hidden-left'); - now.classList.remove(back ? 'hidden-left' : 'hidden-right'); - - IV.index = index; - IV.notify({ - event: 'location_change', - index: IV.index, - position: IV.position, - hash: IV.computeCurrentState().hash, - }); - if (IV.cache[index].contentUpdated) { - IV.cache[index].contentUpdated = false; - IV.applyUpdatedContent(index); - } else { - IV.initMedia(); - } - IV.checkChannelButtons(); - if (scroll === undefined) { - IV.jumpToHash(hash, true); - } else { - IV.lastScrollTop = scroll; - IV.updateJumpToTop(true); - } - } else if (scroll !== undefined) { - IV.scrollTo(scroll); - IV.lastScrollTop = scroll; - IV.updateJumpToTop(true); - } else { - IV.jumpToHash(hash); - } - - IV.forceScrollFocus(); - IV.frameScrolled(); - }, - forceScrollFocus: function () { - IV.findPageScroll().focus(); - setTimeout(function () { - // Doesn't work on #hash-ed pages in Windows WebView2 otherwise. - IV.findPageScroll().focus(); - }, 100); - }, - stopAnimations: function (element) { - element.getAnimations().forEach( - (animation) => animation.finish()); - }, - menuShown: function (shown) { - var already = document.getElementById('menu_page_blocker'); - if (already && shown) { - return; - } else if (already) { - document.body.removeChild(already); - return; - } else if (!shown) { - return; - } - var blocker = document.createElement('div'); - blocker.id = 'menu_page_blocker'; - document.body.appendChild(blocker); - }, - - videos: {}, - videosPlaying: {}, - - cache: {}, - channelsJoined: {}, - index: 0, - position: 0 -}; - -document.onclick = IV.frameClickHandler; -document.onkeydown = IV.frameKeyDown; -document.onmouseenter = IV.frameMouseEnter; -document.onmouseup = IV.frameMouseUp; -document.onresize = IV.checkVideos; -window.onmessage = IV.postMessageHandler; -window.addEventListener('popstate', function (e) { - if (e.state) { - IV.showDOM(e.state.index, e.state.hash, e.state.scroll); - } -}); -document.addEventListener("DOMContentLoaded", IV.forceScrollFocus); diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings index 16e9ea3572eabf..3776c023a4cc52 100644 --- a/Telegram/Resources/langs/cloud_lang.strings +++ b/Telegram/Resources/langs/cloud_lang.strings @@ -13,10 +13,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_topup_purpose_subs" = "Buy **Stars** to keep your channel subscriptions."; +"cloud_lng_age_verify_about_gb#one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law."; +"cloud_lng_age_verify_about_gb#other" = "To access such content, you must confirm that you are at least **{count}** years old as required by UK law."; + "cloud_lng_passport_in_ar" = "Arabic"; "cloud_lng_passport_in_az" = "Azerbaijani"; "cloud_lng_passport_in_bg" = "Bulgarian"; -"cloud_lng_passport_in_bn" = "Bangla"; +"cloud_lng_passport_in_bn" = "Bengali"; "cloud_lng_passport_in_cs" = "Czech"; "cloud_lng_passport_in_da" = "Danish"; "cloud_lng_passport_in_de" = "German"; @@ -64,7 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_translate_to_ar" = "Arabic"; "cloud_lng_translate_to_az" = "Azerbaijani"; "cloud_lng_translate_to_bg" = "Bulgarian"; -// "cloud_lng_translate_to_bn" = "Bangla"; +// "cloud_lng_translate_to_bn" = "Bengali"; "cloud_lng_translate_to_cs" = "Czech"; "cloud_lng_translate_to_da" = "Danish"; "cloud_lng_translate_to_de" = "German"; @@ -109,50 +112,116 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_translate_to_uz" = "Uzbek"; "cloud_lng_translate_to_vi" = "Vietnamese"; +"cloud_lng_language_af" = "Afrikaans"; +"cloud_lng_language_am" = "Amharic"; "cloud_lng_language_ar" = "Arabic"; "cloud_lng_language_az" = "Azerbaijani"; +"cloud_lng_language_be" = "Belarusian"; "cloud_lng_language_bg" = "Bulgarian"; -// "cloud_lng_language_bn" = "Bangla"; +"cloud_lng_language_bn" = "Bengali"; +"cloud_lng_language_bs" = "Bosnian"; +"cloud_lng_language_ca" = "Catalan"; +// "cloud_lng_language_ceb" = "Cebuano"; +"cloud_lng_language_co" = "Corsican"; "cloud_lng_language_cs" = "Czech"; +"cloud_lng_language_cy" = "Welsh"; "cloud_lng_language_da" = "Danish"; "cloud_lng_language_de" = "German"; -// "cloud_lng_language_dv" = "Divehi"; -// "cloud_lng_language_dz" = "Dzongkha"; +"cloud_lng_language_dv" = "Divehi"; +"cloud_lng_language_dz" = "Dzongkha"; "cloud_lng_language_el" = "Greek"; "cloud_lng_language_en" = "English"; +"cloud_lng_language_eo" = "Esperanto"; "cloud_lng_language_es" = "Spanish"; "cloud_lng_language_et" = "Estonian"; +"cloud_lng_language_eu" = "Basque"; "cloud_lng_language_fa" = "Persian"; +"cloud_lng_language_fi" = "Finnish"; "cloud_lng_language_fr" = "French"; +"cloud_lng_language_fy" = "Frisian"; +"cloud_lng_language_ga" = "Irish"; +"cloud_lng_language_gd" = "Scots Gaelic"; +"cloud_lng_language_gl" = "Galician"; +"cloud_lng_language_gu" = "Gujarati"; +"cloud_lng_language_ha" = "Hausa"; +"cloud_lng_language_haw" = "Hawaiian"; "cloud_lng_language_he" = "Hebrew"; +"cloud_lng_language_hi" = "Hindi"; +// "cloud_lng_language_hmn" = "Hmong"; "cloud_lng_language_hr" = "Croatian"; +"cloud_lng_language_ht" = "Haitian Creole"; "cloud_lng_language_hu" = "Hungarian"; "cloud_lng_language_hy" = "Armenian"; "cloud_lng_language_id" = "Indonesian"; +"cloud_lng_language_ig" = "Igbo"; "cloud_lng_language_is" = "Icelandic"; "cloud_lng_language_it" = "Italian"; +"cloud_lng_language_iw" = "Hebrew (Obsolete code)"; "cloud_lng_language_ja" = "Japanese"; +"cloud_lng_language_jv" = "Javanese"; "cloud_lng_language_ka" = "Georgian"; -// "cloud_lng_language_km" = "Khmer"; +"cloud_lng_language_kk" = "Kazakh"; +"cloud_lng_language_km" = "Khmer"; +"cloud_lng_language_kn" = "Kannada"; "cloud_lng_language_ko" = "Korean"; +"cloud_lng_language_ku" = "Kurdish"; +"cloud_lng_language_ky" = "Kyrgyz"; +"cloud_lng_language_la" = "Latin"; +"cloud_lng_language_lb" = "Luxembourgish"; "cloud_lng_language_lo" = "Lao"; "cloud_lng_language_lt" = "Lithuanian"; "cloud_lng_language_lv" = "Latvian"; +"cloud_lng_language_mg" = "Malagasy"; +"cloud_lng_language_mi" = "Maori"; "cloud_lng_language_mk" = "Macedonian"; +"cloud_lng_language_ml" = "Malayalam"; "cloud_lng_language_mn" = "Mongolian"; +"cloud_lng_language_mr" = "Marathi"; "cloud_lng_language_ms" = "Malay"; +"cloud_lng_language_mt" = "Maltese"; "cloud_lng_language_my" = "Burmese"; "cloud_lng_language_ne" = "Nepali"; "cloud_lng_language_nl" = "Dutch"; +"cloud_lng_language_no" = "Norwegian"; +"cloud_lng_language_ny" = "Nyanja"; +"cloud_lng_language_or" = "Odia (Oriya)"; +"cloud_lng_language_pa" = "Punjabi"; "cloud_lng_language_pl" = "Polish"; +"cloud_lng_language_ps" = "Pashto"; "cloud_lng_language_pt" = "Portuguese"; "cloud_lng_language_ro" = "Romanian"; "cloud_lng_language_ru" = "Russian"; +"cloud_lng_language_rw" = "Kinyarwanda"; +"cloud_lng_language_sd" = "Sindhi"; +"cloud_lng_language_si" = "Sinhala"; "cloud_lng_language_sk" = "Slovak"; "cloud_lng_language_sl" = "Slovenian"; +"cloud_lng_language_sm" = "Samoan"; +"cloud_lng_language_sn" = "Shona"; +"cloud_lng_language_so" = "Somali"; +"cloud_lng_language_sq" = "Albanian"; +"cloud_lng_language_sr" = "Serbian"; +"cloud_lng_language_st" = "Sesotho"; +"cloud_lng_language_su" = "Sundanese"; +"cloud_lng_language_sv" = "Swedish"; +"cloud_lng_language_sw" = "Swahili"; +"cloud_lng_language_ta" = "Tamil"; +"cloud_lng_language_te" = "Telugu"; +"cloud_lng_language_tg" = "Tajik"; "cloud_lng_language_th" = "Thai"; "cloud_lng_language_tk" = "Turkmen"; +"cloud_lng_language_tl" = "Tagalog"; "cloud_lng_language_tr" = "Turkish"; +"cloud_lng_language_tt" = "Tatar"; +"cloud_lng_language_ug" = "Uyghur"; "cloud_lng_language_uk" = "Ukrainian"; +"cloud_lng_language_ur" = "Urdu"; "cloud_lng_language_uz" = "Uzbek"; "cloud_lng_language_vi" = "Vietnamese"; +"cloud_lng_language_xh" = "Xhosa"; +"cloud_lng_language_yi" = "Yiddish"; +"cloud_lng_language_yo" = "Yoruba"; +"cloud_lng_language_zh" = "Chinese"; +// "cloud_lng_language_zh-CN" = "Chinese (Simplified)"; +// "cloud_lng_language_zh-TW" = "Chinese (Traditional)"; +"cloud_lng_language_zu" = "Zulu"; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 60c7a6413d8e7e..073cf6a978d342 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -18,9 +18,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_menu_activate" = "Use this account"; "lng_menu_set_status" = "Set Emoji Status"; "lng_menu_change_status" = "Change Emoji Status"; +"lng_menu_my_profile" = "My Profile"; "lng_menu_my_stories" = "My Stories"; "lng_menu_my_groups" = "My Groups"; "lng_menu_my_channels" = "My Channels"; +"lng_main_menu" = "Main menu"; +"lng_filter_unread_chats#one" = "{text} ({count} unread chat)"; +"lng_filter_unread_chats#other" = "{text} ({count} unread chats)"; "lng_disable_notifications_from_tray" = "Disable notifications"; "lng_enable_notifications_from_tray" = "Enable notifications"; @@ -126,6 +130,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_cancel" = "Cancel"; "lng_continue" = "Continue"; "lng_close" = "Close"; +"lng_minimize_window" = "Minimize"; +"lng_maximize_window" = "Maximize"; +"lng_restore_window" = "Restore"; +"lng_go_back" = "Go back"; "lng_connecting" = "Connecting..."; "lng_reconnecting#one" = "Reconnect in {count} s..."; "lng_reconnecting#other" = "Reconnect in {count} s..."; @@ -163,11 +171,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_chat_status_members_online" = "{members_count}, {online_count}"; "lng_chat_status_subscribers#one" = "{count} subscriber"; "lng_chat_status_subscribers#other" = "{count} subscribers"; +"lng_chat_status_direct" = "Direct messages"; "lng_channel_status" = "channel"; "lng_group_status" = "group"; "lng_scam_badge" = "SCAM"; "lng_fake_badge" = "FAKE"; +"lng_direct_badge" = "DIRECT"; "lng_remember" = "Remember this choice"; @@ -288,8 +298,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it."; "lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups."; "lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them."; +"lng_error_you_blocked_user" = "Sorry, you can't add this user or bot to groups because you've blocked them. Please unblock to proceed."; +"lng_error_add_admin_not_member" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them."; +"lng_error_user_admin_invalid" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them."; +"lng_error_channel_bots_too_much" = "Sorry, this channel has too many bots."; +"lng_error_group_bots_too_much" = "There are too many bots in this group. Please remove some of the bots you're not using first."; "lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them."; "lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them."; +"lng_error_cant_reply_other" = "This message can't be replied in another chat."; "lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group."; "lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel."; "lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted."; @@ -297,7 +313,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins."; "lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; "lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; +"lng_error_noforwards_user" = "Sorry, forwarding from this chat is restricted."; +"lng_error_nocopy_user" = "Sorry, copying from this chat is restricted."; "lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying."; +"lng_error_schedule_limit" = "Sorry, you can't schedule more than 100 messages."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -305,14 +324,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in Settings > Advanced > Connection Type."; "lng_sure_enable" = "Enable"; "lng_proxy_box_title" = "Enable proxy"; +"lng_proxy_box_table_title" = "Proxy Server"; +"lng_proxy_box_table_button" = "Connect Proxy"; +"lng_proxy_box_table_checking" = "Checking…"; +"lng_proxy_box_table_available" = "Available (ping: {ping} ms)"; +"lng_proxy_box_table_unavailable" = "Not Available"; "lng_proxy_box_server" = "Server"; "lng_proxy_box_port" = "Port"; "lng_proxy_box_secret" = "Secret"; "lng_proxy_box_status" = "Status"; +"lng_proxy_box_check_status" = "Check Status"; "lng_proxy_box_username" = "Username"; "lng_proxy_box_password" = "Password"; "lng_proxy_invalid" = "The proxy link is invalid."; "lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version."; +"lng_proxy_incorrect_secret" = "This proxy link uses invalid **secret** parameter. Please contact the proxy provider and ask him to update MTProxy source code and configure it with a correct **secret** value. Then let him provide a new link."; +"lng_proxy_check_ip_warning_title" = "Warning"; +"lng_proxy_check_ip_warning" = "This will expose your IP address to the admin of the proxy server."; +"lng_proxy_check_ip_proceed" = "Proceed"; "lng_edit_deleted" = "This message was deleted"; "lng_edit_limit_reached#one" = "You've reached the message text limit. Please make the text shorter by {count} character."; @@ -343,6 +372,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_media_album_error" = "This file cannot be saved as a part of an album."; "lng_edit_media_invalid_file" = "Sorry, no way to use this file."; +/* +"lng_iv_editor_media_invalid_file" = "Sorry, only photos, video, and audio files are supported here."; +*/ "lng_edit_photo_editor_hint" = "Left-click on the photo to edit."; "lng_edit_caption_attach" = "Sorry, you can't attach new media while editing a message."; "lng_edit_caption_voice" = "Sorry, you can't edit messages when you have an unsent voice message."; @@ -362,11 +394,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_intro_qr_step2" = "Go to Settings > Devices > Link Desktop Device"; "lng_intro_qr_step3" = "Scan this image to Log In"; "lng_intro_qr_skip" = "Or log in using your phone number"; +"lng_intro_qr_phone" = "Log in using phone number"; +"lng_intro_qr_passkey" = "Log in using passkey"; "lng_intro_fragment_title" = "Enter code"; "lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment."; "lng_intro_fragment_button" = "Open Fragment"; +"lng_intro_email_setup_title" = "Choose a login email"; +"lng_intro_email_confirm_subtitle" = "Please check your email {email} (don't forget the spam folder) and enter the code we just sent you."; + "lng_phone_title" = "Your Phone Number"; "lng_phone_desc" = "Please confirm your country code\nand enter your phone number."; "lng_phone_to_qr" = "Quick log in using QR code"; @@ -375,6 +412,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_country_ph" = "Search"; "lng_country_none" = "Country not found"; "lng_country_select" = "Select Country"; +"lng_phone_number" = "Phone number"; "lng_code_ph" = "Code"; "lng_code_desc" = "We've sent an activation code to your phone.\nPlease enter it below."; @@ -424,15 +462,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_new_channel_name" = "Channel name"; "lng_dlg_new_bot_name" = "Bot name"; "lng_no_chats" = "Your chats will be here"; +"lng_no_conversations" = "You have no\nconversations yet."; +"lng_no_conversations_button" = "New Message"; +"lng_no_conversations_subtitle" = "Your contacts on Telegram"; "lng_no_chats_filter" = "No chats currently belong to this folder."; "lng_no_saved_sublists" = "You can save messages from other chats here."; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_topics_not_found" = "No topics found."; +"lng_forum_all_messages" = "All Messages"; +"lng_forum_create_new_topic" = "Create New Thread"; "lng_dlg_search_for_messages" = "Search for messages"; "lng_update_telegram" = "Update Telegram"; "lng_dlg_search_in" = "Search messages in"; "lng_dlg_search_from" = "From: {user}"; +"lng_chat_menu" = "Chat menu"; "lng_settings_save" = "Save"; "lng_settings_apply" = "Apply"; @@ -499,11 +543,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_notify_global" = "Global settings"; "lng_settings_notify_title" = "Notifications for chats"; "lng_settings_desktop_notify" = "Desktop notifications"; -"lng_settings_native_title" = "Native notifications"; +"lng_settings_master_volume_notifications" = "Volume"; +"lng_settings_native_title" = "System integration"; "lng_settings_use_windows" = "Use Windows notifications"; +"lng_settings_skip_in_focus" = "Respect system Focus mode"; "lng_settings_use_native_notifications" = "Use native notifications"; "lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_count" = "Notifications count"; +"lng_settings_notifications_display" = "Display for notifications"; +"lng_settings_notifications_display_default" = "Default"; "lng_settings_sound_allowed" = "Allow sound"; "lng_settings_alert_windows" = "Flash the taskbar icon"; "lng_settings_alert_mac" = "Bounce the Dock icon"; @@ -529,6 +577,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_notification_private_chats" = "Private chats"; "lng_notification_groups" = "Groups"; "lng_notification_channels" = "Channels"; +"lng_notification_reactions" = "Reactions"; +"lng_notification_reactions_title" = "Notifications for reactions"; +"lng_notification_reactions_notify_about" = "Notify me about"; +"lng_notification_reactions_messages" = "Messages"; +"lng_notification_reactions_messages_full" = "Reactions to my messages"; +"lng_notification_reactions_poll_votes" = "Poll votes"; +"lng_notification_reactions_poll_votes_full" = "Votes in my polls"; +"lng_notification_reactions_from" = "Notify about reactions from"; +"lng_notification_reactions_from_nobody" = "Off"; +"lng_notification_reactions_from_contacts" = "From my contacts"; +"lng_notification_reactions_from_all" = "From everyone"; +"lng_notification_reactions_settings" = "Settings"; +"lng_notification_reactions_show_sender" = "Show sender's name"; "lng_notification_click_to_change" = "Click here to change"; "lng_notification_on" = "On, {exceptions}"; "lng_notification_off" = "Off, {exceptions}"; @@ -538,12 +599,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_notification_title_private_chats" = "Notifications for private chats"; "lng_notification_about_private_chats#one" = "Please note that **{count} chat** is listed as an exception and won't be affected by this change."; "lng_notification_about_private_chats#other" = "Please note that **{count} chats** are listed as exceptions and won't be affected by this change."; +"lng_notification_volume_private_chats" = "Notifications volume for private chats"; "lng_notification_title_groups" = "Notifications for groups"; "lng_notification_about_groups#one" = "Please note that **{count} group** is listed as an exception and won't be affected by this change."; "lng_notification_about_groups#other" = "Please note that **{count} groups** are listed as exceptions and won't be affected by this change."; +"lng_notification_volume_groups" = "Notifications volume for groups"; "lng_notification_title_channels" = "Notifications for channels"; "lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change."; "lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change."; +"lng_notification_volume_channel" = "Notifications volume for channels"; "lng_notification_exceptions_view" = "View exceptions"; "lng_notification_enable" = "Enable notifications"; "lng_notification_sound" = "Sound"; @@ -573,6 +637,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reaction_invoice" = "{reaction} to your invoice"; "lng_reaction_gif" = "{reaction} to your GIF"; +"lng_poll_vote_option" = "voted for \"{option}\" in your poll"; +"lng_poll_vote" = "voted in your poll \"{title}\""; +"lng_poll_vote_notext" = "voted in your poll"; + "lng_effect_add_title" = "Add an animated effect"; "lng_effect_stickers_title" = "Effects from stickers"; "lng_effect_send" = "Send with Effect"; @@ -595,6 +663,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_update_fail" = "Update check failed :("; "lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_window" = "Show taskbar icon"; +"lng_settings_window_close" = "When window closed"; +"lng_settings_run_in_background" = "Run in the background"; +"lng_settings_quit_on_close" = "Quit the application"; "lng_settings_close_to_taskbar" = "Close to taskbar"; "lng_settings_monochrome_icon" = "Use monochrome icon"; "lng_settings_window_system" = "Window title bar"; @@ -618,6 +689,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_section_chat_settings" = "Chat Settings"; "lng_settings_replace_emojis" = "Replace emoji automatically"; +"lng_settings_system_text_replace" = "System text replacements"; "lng_settings_suggest_emoji" = "Suggest emoji replacements"; "lng_settings_suggest_animated_emoji" = "Suggest animated emoji"; "lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji"; @@ -628,8 +700,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_send_cmdenter" = "Send with Cmd+Enter"; "lng_settings_chat_quick_action_reply" = "Reply with double click"; "lng_settings_chat_quick_action_react" = "Send reaction with double click"; +"lng_settings_chat_corner_reply" = "Reply button on messages"; "lng_settings_chat_corner_reaction" = "Reaction button on messages"; +"lng_settings_shortcuts" = "Keyboard shortcuts"; + +"lng_shortcuts_reset" = "Reset to default"; +"lng_shortcuts_recording" = "Recording..."; +"lng_shortcuts_add_another" = "Add another"; + +"lng_shortcuts_close" = "Close the window"; +"lng_shortcuts_lock" = "Lock the application"; +"lng_shortcuts_minimize" = "Minimize the window"; +"lng_shortcuts_quit" = "Quit the application"; +"lng_shortcuts_media_play" = "Play the media"; +"lng_shortcuts_media_pause" = "Pause the media"; +"lng_shortcuts_media_play_pause" = "Toggle media playback"; +"lng_shortcuts_media_stop" = "Stop media playback"; +"lng_shortcuts_media_previous" = "Previous track"; +"lng_shortcuts_media_next" = "Next track"; +"lng_shortcuts_search" = "Search messages"; +"lng_shortcuts_chat_previous" = "Previous chat"; +"lng_shortcuts_chat_next" = "Next chat"; +"lng_shortcuts_chat_first" = "First chat"; +"lng_shortcuts_chat_last" = "Last chat"; +"lng_shortcuts_chat_self" = "Saved Messages"; +"lng_shortcuts_chat_pinned_n" = "Pinned chat #{index}"; +"lng_shortcuts_show_account_n" = "Account #{index}"; +"lng_shortcuts_show_all_chats" = "All Chats folder"; +"lng_shortcuts_show_folder_n" = "Folder #{index}"; +"lng_shortcuts_show_folder_last" = "Last folder"; +"lng_shortcuts_folder_next" = "Next folder"; +"lng_shortcuts_folder_previous" = "Previous folder"; +"lng_shortcuts_scheduled" = "Scheduled messages"; +"lng_shortcuts_archive" = "Archived chats"; +"lng_shortcuts_contacts" = "Contacts list"; +"lng_shortcuts_just_send" = "Just send"; +"lng_shortcuts_silent_send" = "Silent send"; +"lng_shortcuts_schedule" = "Schedule"; +"lng_shortcuts_ai_compose_apply" = "Apply AI style to text"; +"lng_shortcuts_toggle_link_preview" = "Toggle link preview"; +"lng_shortcuts_read_chat" = "Mark chat as read"; +"lng_shortcuts_archive_chat" = "Archive chat"; +"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen"; +"lng_shortcuts_show_chat_menu" = "Show chat menu"; +"lng_shortcuts_show_chat_preview" = "Show chat preview"; +"lng_shortcuts_record_voice_message" = "Record Voice Message"; +"lng_shortcuts_record_round_message" = "Record Round Message"; +"lng_shortcuts_admin_log" = "Group/Channel Recent Actions"; + +"lng_attach" = "Add attachment"; +"lng_attach_replace" = "Replace attachment"; +"lng_emoji_sticker_gif" = "Choose emoji, sticker or gif"; +"lng_bot_keyboard_show" = "Show bot keyboard"; +"lng_bot_keyboard_hide" = "Hide bot keyboard"; +"lng_bot_commands_start" = "Bot commands start"; +"lng_broadcast_silent" = "Silent broadcast"; + "lng_settings_chat_reactions_title" = "Quick Reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; "lng_settings_chat_message_reply_from" = "Bob Harris"; @@ -667,6 +794,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_language" = "Language"; "lng_settings_default_scale" = "Default interface scale"; +"lng_settings_scale" = "Interface scale"; "lng_settings_connection_type" = "Connection type"; "lng_settings_downloading_update" = "Downloading update {progress}..."; "lng_settings_privacy_title" = "Privacy"; @@ -681,7 +809,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_messages_privacy" = "Messages"; "lng_settings_voices_privacy" = "Voice messages"; "lng_settings_bio_privacy" = "Bio"; +"lng_settings_gifts_privacy" = "Gifts"; "lng_settings_birthday_privacy" = "Date of Birth"; +"lng_settings_saved_music_privacy" = "Saved Music"; "lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages."; "lng_settings_privacy_premium_link" = "Telegram Premium"; "lng_settings_passcode_disable" = "Disable passcode"; @@ -703,16 +833,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_username_add" = "Add username"; "lng_settings_username_about" = "Username lets people contact you on Telegram without needing your phone number."; "lng_settings_birthday_label" = "Date of Birth"; +"lng_settings_birthday_title" = "Set your Birthday"; "lng_settings_birthday_add" = "Add"; "lng_settings_birthday_about" = "Choose who can see your birthday in {link}."; "lng_settings_birthday_about_link" = "Settings"; "lng_settings_birthday_contacts" = "Only your contacts can see your birthday. {link}"; "lng_settings_birthday_contacts_link" = "Change >"; "lng_settings_birthday_saved" = "Your date of birth was updated."; +"lng_settings_birthday_suggested" = "Date of birth was suggested to {user}"; "lng_settings_birthday_reset" = "Remove"; "lng_settings_channel_label" = "Personal channel"; "lng_settings_channel_add" = "Add"; "lng_settings_channel_remove" = "Remove"; +"lng_settings_channel_menu_remove" = "Remove Personal Channel"; "lng_settings_channel_no_yet" = "You don't have any public channels yet."; "lng_settings_channel_start" = "Start a Channel"; "lng_settings_channel_saved" = "Your personal channel was updated."; @@ -728,6 +861,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_theme_tinted" = "Tinted"; "lng_settings_theme_night" = "Night"; "lng_settings_theme_accent_title" = "Choose accent color"; +"lng_settings_theme_system_accent_color" = "System accent color"; "lng_settings_data_storage" = "Data and storage"; "lng_settings_information" = "Edit profile"; "lng_settings_my_account" = "My Account"; @@ -782,6 +916,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_disconnect" = "Disconnect"; "lng_settings_connected_title" = "Connected websites"; +"lng_settings_suggestion_phone_number_title" = "Is {phone} still your number?"; +"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}"; +"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do"; +"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}"; +"lng_settings_suggestion_password_title" = "Your password"; +"lng_settings_suggestion_password_about" = "Your account is protected by 2-Step Veritifaction. Do you still remember your password?"; +"lng_settings_suggestion_password_yes" = "Yes, definitely"; +"lng_settings_suggestion_password_no" = "Not sure"; +"lng_settings_suggestion_password_step_input_title" = "Enter your password"; +"lng_settings_suggestion_password_step_input_about" = "Do you still remember your password?"; +"lng_settings_suggestion_password_step_finish_title" = "Perfect!"; +"lng_settings_suggestion_password_step_finish_about" = "You still remember your password."; + "lng_settings_power_menu" = "Battery and Animations"; "lng_settings_power_title" = "Power Usage"; "lng_settings_power_subtitle" = "Power saving options"; @@ -828,6 +975,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_cloud_password_email_confirm" = "Confirm and Finish"; "lng_settings_cloud_password_reset_in" = "You can reset your password in {duration}."; +"lng_settings_cloud_login_email_section_title" = "Login Email"; +"lng_settings_cloud_login_email_box_about" = "This email address will be used every time you log in to your Telegram account from a new device."; +"lng_settings_cloud_login_email_box_ok" = "Change email"; +"lng_settings_cloud_login_email_title" = "Enter New Email"; +"lng_settings_cloud_login_email_placeholder" = "Enter Login Email"; +"lng_settings_cloud_login_email_about" = "You will receive Telegram login codes via email and not SMS. Please enter an email address to which you have access."; +"lng_settings_cloud_login_email_confirm" = "Confirm"; +"lng_settings_cloud_login_email_code_title" = "Check Your New Email"; +"lng_settings_cloud_login_email_code_about" = "Please enter the code we have sent to your new email {email}"; +"lng_settings_cloud_login_email_success" = "Your email has been changed."; +"lng_settings_cloud_login_email_set_success" = "Your login email has been set successfully."; +"lng_settings_cloud_login_email_busy" = "Please set up login email in another window."; +"lng_settings_error_email_not_alowed" = "Sorry, this email is not allowed"; + "lng_settings_ttl_title" = "Auto-Delete Messages"; "lng_settings_ttl_about" = "Automatically delete messages for everyone after a period of time in all new chats you start."; "lng_settings_ttl_after" = "After {after_duration}"; @@ -874,6 +1035,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_font_family" = "Font family"; "lng_settings_color_title" = "Color preview"; +"lng_settings_color_tab_profile" = "Profile"; +"lng_settings_color_tab_name" = "Name"; "lng_settings_color_reply" = "Reply to your message"; "lng_settings_color_reply_channel" = "Reply to your channel message"; "lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color."; @@ -888,9 +1051,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_color_emoji_off" = "Off"; "lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them."; "lng_settings_color_emoji_about_channel" = "Select an icon to create a custom pattern for replies to your messages."; -"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name."; "lng_settings_color_changed" = "Your name color has been updated!"; "lng_settings_color_changed_channel" = "Your channel color has been updated!"; +"lng_settings_color_changed_profile" = "Your profile style has been updated!"; +"lng_settings_color_changed_profile_channel" = "Your channel profile style has been updated!"; +"lng_settings_color_apply" = "Apply Style"; +"lng_settings_color_wear" = "Wear Collectible"; +"lng_settings_color_profile_emoji" = "Add icons to Profile"; +"lng_settings_color_profile_emoji_channel" = "Profile Logo"; +"lng_settings_color_reset" = "Reset Profile Color"; +"lng_settings_color_profile_about" = "You can change the color of your name and customize replies to you. {link}"; +"lng_settings_color_profile_about_link" = "Change {emoji}"; +"lng_settings_color_choose_channel" = "Choose a color and a logo for your channel's profile"; +"lng_settings_color_choose_group" = "Choose a color and a logo for the group's profile"; +"lng_settings_color_group_boost_footer#one" = "The group has **{count}** boost. {link}"; +"lng_settings_color_group_boost_footer#other" = "The group has **{count}** boosts. {link}"; +"lng_settings_color_group_boost_footer_link" = "What are boosts?"; "lng_suggest_hide_new_title" = "Hide new chats?"; "lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?"; @@ -969,15 +1145,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_local_storage_round#other" = "{count} video messages"; "lng_local_storage_animation#one" = "{count} GIF animation"; "lng_local_storage_animation#other" = "{count} GIF animations"; +"lng_local_storage_image_title" = "Images"; +"lng_local_storage_sticker_title" = "Stickers"; +"lng_local_storage_voice_title" = "Voice messages"; +"lng_local_storage_round_title" = "Video messages"; +"lng_local_storage_animation_title" = "GIFs"; +"lng_local_storage_media_title" = "Other media"; "lng_local_storage_media" = "Media cache"; "lng_local_storage_size_limit" = "Total size limit: {size}"; "lng_local_storage_media_limit" = "Media cache limit: {size}"; "lng_local_storage_time_limit" = "Clear files older than: {limit}"; +"lng_local_storage_size_limit_title" = "Total size limit"; +"lng_local_storage_media_limit_title" = "Media cache limit"; +"lng_local_storage_time_limit_title" = "Clear files older than"; "lng_local_storage_limit_never" = "Never"; "lng_local_storage_summary" = "Summary"; "lng_local_storage_clear_some" = "Clear"; "lng_local_storage_clear" = "Clear all"; +"lng_local_storage_clear_selected" = "Clear selected"; "lng_local_storage_clearing" = "Clearing..."; +"lng_local_storage_device_telegram" = "Telegram cache"; +"lng_local_storage_device_other" = "Other data"; +"lng_local_storage_device_free" = "Free"; +"lng_local_storage_device_total" = "Total"; +"lng_local_storage_device_usage" = "Telegram uses {percent} of your device storage."; +"lng_local_storage_clearing_about" = "Please keep this window open while the cache is being cleared."; +"lng_local_storage_cleared" = "{size} freed on your device!"; +"lng_local_storage_about" = "All media will stay in the Telegram cloud and can be re-downloaded if you need them again."; +"lng_local_storage_manage_title" = "Manage limits"; +"lng_local_storage_max_size_about" = "If your cache size exceeds this limit, the oldest unused media will be removed from the device."; "lng_passcode_remove_button" = "Remove"; @@ -1068,6 +1264,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_proxy_use_system_settings" = "Use system proxy settings"; "lng_proxy_use_custom" = "Use custom proxy"; "lng_proxy_use_for_calls" = "Use proxy for calls"; +"lng_proxy_auto_switch" = "Auto-switch proxies"; +"lng_proxy_auto_switch_about" = "You can choose how quickly the app should auto-connect to the nearest active proxy if the current one stops working."; +"lng_proxy_auto_switch_timeout#one" = "{count} s"; +"lng_proxy_auto_switch_timeout#other" = "{count} s"; "lng_proxy_about" = "Proxy servers may be helpful in accessing Telegram if there is no connection in a specific region."; "lng_proxy_add" = "Add proxy"; "lng_proxy_share" = "Share"; @@ -1081,6 +1281,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_proxy_menu_delete" = "Delete"; "lng_proxy_menu_restore" = "Restore"; "lng_proxy_edit_share" = "Share"; +"lng_proxy_edit_share_qr_box_title" = "Share proxy with QR code"; +"lng_proxy_edit_share_list_button" = "Share Proxy List"; +"lng_proxy_edit_share_list_toast" = "Proxy List copied to clipboard."; "lng_proxy_address_label" = "Socket address"; "lng_proxy_credentials_optional" = "Credentials (optional)"; "lng_proxy_credentials" = "Credentials"; @@ -1116,9 +1319,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_faq_button" = "Go to FAQ"; "lng_settings_ask_ok" = "Ask a Volunteer"; "lng_settings_faq" = "Telegram FAQ"; +"lng_settings_faq_subtitle" = "FAQ"; "lng_settings_faq_link" = "https://telegram.org/faq#general-questions"; "lng_settings_features" = "Telegram Features"; "lng_settings_credits" = "My Stars"; +"lng_settings_currency" = "My Grams"; "lng_settings_logout" = "Log out"; "lng_sure_logout" = "Are you sure you want to log out?"; @@ -1126,6 +1331,67 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_restart_now" = "Restart"; "lng_settings_restart_later" = "Later"; +"lng_settings_passkeys_title" = "Passkeys"; +"lng_settings_passkeys_about" = "Manage your passkey, stored safely in the cloud service you choose."; +"lng_settings_passkeys_button" = "Add Passkey"; +"lng_settings_passkeys_button_about" = "Your passkey is stored securely in your password manager. {link}"; +"lng_settings_passkeys_delete_sure_title" = "Delete Passkey"; +"lng_settings_passkeys_delete_sure_about" = "Once deleted, this passkey can't be used to log in."; +"lng_settings_passkeys_delete_sure_about2" = "Don't forget to remove it from your password manager too."; +"lng_settings_passkeys_none_title" = "Protect your account"; +"lng_settings_passkeys_none_about" = "Log in safely and keep your account secure."; +"lng_settings_passkeys_none_info1_title" = "Create a Passkey"; +"lng_settings_passkeys_none_info1_about" = "Make a passkey to sign in easily and safely."; +"lng_settings_passkeys_none_info2_title" = "Log in with face recognition"; +"lng_settings_passkeys_none_info2_about" = "Use your face, fingerprint, or screen lock to sign in."; +"lng_settings_passkeys_none_info3_title" = "Store Passkey securely"; +"lng_settings_passkeys_none_info3_about" = "Your passkey is stored safely in the cloud service you choose."; +"lng_settings_passkeys_none_button" = "Create Passkey"; +"lng_settings_passkeys_none_button_unsupported" = "Unsupported"; +"lng_settings_passkeys_created" = "Created {date}"; +"lng_settings_passkeys_last_used" = "Last used {date}"; +"lng_settings_passkeys_unsigned_error" = "Passkeys are not available in unsigned builds. Please use an official signed version of Telegram Desktop."; +"lng_settings_passkey_unknown" = "Passkey"; + +"lng_settings_quick_dialog_action_title" = "Chat list quick action"; +"lng_settings_quick_dialog_action_about" = "Choose the action you want to perform when you middle-click or swipe left in the chat list."; +"lng_settings_quick_dialog_action_both" = "Swipe left and Middle-click"; +"lng_settings_quick_dialog_action_swipe" = "Swipe left"; +"lng_settings_quick_dialog_action_mute" = "Mute"; +"lng_settings_quick_dialog_action_unmute" = "Unmute"; +"lng_settings_quick_dialog_action_pin" = "Pin"; +"lng_settings_quick_dialog_action_unpin" = "Unpin"; +"lng_settings_quick_dialog_action_read" = "Read"; +"lng_settings_quick_dialog_action_unread" = "Unread"; +"lng_settings_quick_dialog_action_archive" = "Archive"; +"lng_settings_quick_dialog_action_unarchive" = "Unarchive"; +"lng_settings_quick_dialog_action_delete" = "Delete"; +"lng_settings_quick_dialog_action_disabled" = "Change folder"; + +"lng_quick_dialog_action_toast_mute_success" = "Notifications for this chat have been muted."; +"lng_quick_dialog_action_toast_unmute_success" = "Notifications enabled for this chat."; +"lng_quick_dialog_action_toast_pin_success" = "The chat has been pinned."; +"lng_quick_dialog_action_toast_unpin_success" = "The chat has been unpinned."; +"lng_quick_dialog_action_toast_read_success" = "The chat has been marked as read."; +"lng_quick_dialog_action_toast_unread_success" = "The chat has been marked as unread."; +"lng_quick_dialog_action_toast_archive_success" = "The chat has been archived."; +"lng_quick_dialog_action_toast_unarchive_success" = "The chat has been unarchived."; + +"lng_archive_hint_title" = "This is your Archive"; +"lng_archive_hint_about" = "Archived chats will remain in the Archive when you receive a new message. {link}"; +"lng_archive_hint_about_unmuted" = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. {link}"; +"lng_archive_hint_about_link" = "Tap to change {emoji}"; +"lng_archive_hint_section_1" = "Archived Chats"; +"lng_archive_hint_section_1_info" = "Move any chat into your Archive and back by swiping on it."; +"lng_archive_hint_section_2" = "Hiding Archive"; +"lng_archive_hint_section_2_info" = "Hide the Archive from your Main screen by swiping on it."; +"lng_archive_hint_section_3" = "Stories"; +"lng_archive_hint_section_3_info" = "Archive Stories from your contacts separately from chats with them."; +"lng_archive_hint_button" = "Got it"; + +"lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting."; +"lng_settings_generic_subscribe_link" = "Telegram Premium"; + "lng_sessions_header" = "This device"; "lng_sessions_other_header" = "Active Devices"; "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; @@ -1154,6 +1420,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_blocked_list_confirm_title" = "Block {name}"; "lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?"; "lng_blocked_list_confirm_clear" = "Delete this chat"; +"lng_blocked_list_confirm_reply" = "Delete this reply"; +"lng_blocked_list_confirm_reply_all" = "Delete all replies from {user}"; "lng_blocked_list_confirm_ok" = "Block"; "lng_blocked_list_empty_title" = "No blocked users"; "lng_blocked_list_empty_description" = "You haven't blocked anyone yet."; @@ -1161,19 +1429,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_blocked_list_subtitle#other" = "{count} blocked users"; "lng_edit_privacy_everyone" = "Everybody"; +"lng_edit_privacy_no_miniapps" = "Not Mini Apps"; "lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_contacts_and_premium" = "Contacts & Premium"; +"lng_edit_privacy_paid" = "Paid"; +"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps"; "lng_edit_privacy_nobody" = "Nobody"; "lng_edit_privacy_premium" = "Premium users"; +"lng_edit_privacy_miniapps" = "Mini Apps"; "lng_edit_privacy_exceptions" = "Add exceptions"; "lng_edit_privacy_user_types" = "User types"; "lng_edit_privacy_users_and_groups" = "Users and groups"; "lng_edit_privacy_premium_status" = "all Telegram Premium subscribers"; +"lng_edit_privacy_miniapps_status" = "web mini apps that you use"; "lng_edit_privacy_exceptions_count#one" = "{count} user"; "lng_edit_privacy_exceptions_count#other" = "{count} users"; "lng_edit_privacy_exceptions_premium_and" = "Premium & {users}"; +"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}"; "lng_edit_privacy_exceptions_add" = "Add users"; "lng_edit_privacy_phone_number_title" = "Phone number privacy"; @@ -1227,6 +1501,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}"; "lng_edit_privacy_birthday_yet_link" = "Add my birthday >"; +"lng_edit_privacy_gifts_title" = "Gifts"; +"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile"; +"lng_edit_privacy_gifts_always_empty" = "Always allow"; +"lng_edit_privacy_gifts_never_empty" = "Never allow"; +"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile."; +"lng_edit_privacy_gifts_always_title" = "Always allow"; +"lng_edit_privacy_gifts_never_title" = "Never allow"; + +"lng_edit_privacy_gifts_types" = "Accepted Gift Types"; +"lng_edit_privacy_gifts_premium" = "Premium Subscriptions"; +"lng_edit_privacy_gifts_unlimited" = "Unlimited"; +"lng_edit_privacy_gifts_limited" = "Limited-Edition"; +"lng_edit_privacy_gifts_unique" = "Unique"; +"lng_edit_privacy_gifts_channels" = "From Channels"; +"lng_edit_privacy_gifts_types_about" = "Choose the types of gifts that you accept."; +"lng_edit_privacy_gifts_show_icon" = "Show Gift Icon in Chats"; +"lng_edit_privacy_gifts_show_icon_about" = "Display the {emoji}Gift icon in the message input field for both participants in all chats."; +"lng_edit_privacy_gifts_restricted" = "This user doesn't accept gifts."; + "lng_edit_privacy_calls_title" = "Calls"; "lng_edit_privacy_calls_header" = "Who can call me"; "lng_edit_privacy_calls_always_empty" = "Always allow"; @@ -1281,6 +1574,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_voices_always_title" = "Always allow"; "lng_edit_privacy_voices_never_title" = "Never Allow"; +"lng_edit_privacy_saved_music_title" = "Saved Music"; +"lng_edit_privacy_saved_music_header" = "Who can see my saved music in profile"; +"lng_edit_privacy_saved_music_always_empty" = "Always allow"; +"lng_edit_privacy_saved_music_never_empty" = "Never allow"; +"lng_edit_privacy_saved_music_exceptions" = "These users will or will not be able to see your saved music regardless of the settings above."; +"lng_edit_privacy_saved_music_always_title" = "Always allow"; +"lng_edit_privacy_saved_music_never_title" = "Never allow"; + "lng_messages_privacy_title" = "Messages"; "lng_messages_privacy_subtitle" = "Who can send me messages"; "lng_messages_privacy_everyone" = "Everybody"; @@ -1290,6 +1591,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium."; "lng_messages_privacy_premium" = "Only subscribers of {link} can select this option."; "lng_messages_privacy_premium_link" = "Telegram Premium"; +"lng_messages_privacy_charge" = "Charge for messages"; +"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first."; +"lng_messages_privacy_price" = "Set your price per message"; +"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message."; +"lng_messages_privacy_exceptions" = "Exceptions"; +"lng_messages_privacy_remove_fee" = "Remove Fee"; +"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you."; "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; @@ -1313,6 +1621,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_preview_loading" = "Getting Link Info..."; "lng_preview_cant" = "Could not generate preview for this link."; +"lng_markdown_preview_cant" = "Can't preview this Markdown file"; +"lng_markdown_preview_open_file" = "Open file"; "lng_profile_settings_section" = "Settings"; "lng_profile_bot_settings" = "Bot Settings"; @@ -1323,10 +1633,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_similar_channels#one" = "{count} similar channel"; "lng_profile_similar_channels#other" = "{count} similar channels"; +"lng_profile_similar_bots#one" = "{count} similar bot"; +"lng_profile_similar_bots#other" = "{count} similar bots"; "lng_profile_saved_messages#one" = "{count} saved message"; "lng_profile_saved_messages#other" = "{count} saved messages"; "lng_profile_peer_gifts#one" = "{count} gift"; "lng_profile_peer_gifts#other" = "{count} gifts"; +"lng_profile_unofficial_warning" = "{icon} {name} uses an unofficial Telegram client — messages to this user may be less secure."; "lng_profile_participants_section" = "Members"; "lng_profile_subscribers_section" = "Subscribers"; "lng_profile_add_contact" = "Add Contact"; @@ -1350,8 +1663,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_hide_participants_about" = "Switch this on to hide the list of members in this group. Admins will remain visible."; "lng_profile_view_channel" = "View Channel"; "lng_profile_view_discussion" = "View discussion"; +"lng_profile_direct_messages" = "Direct messages"; "lng_profile_join_channel" = "Join Channel"; "lng_profile_join_group" = "Join Group"; + +"lng_pull_next_channel" = "Pull up to go to the next unread channel"; +"lng_release_next_channel" = "Release to go to the next unread channel"; +"lng_pull_no_unread_channels" = "You have no unread channels"; + "lng_profile_apply_to_join_group" = "Apply to Join Group"; "lng_profile_kick" = "Remove"; "lng_profile_delete_removed" = "Delete"; @@ -1367,6 +1686,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_photos#other" = "{count} photos"; "lng_profile_gifs#one" = "{count} GIF"; "lng_profile_gifs#other" = "{count} GIFs"; +"lng_profile_polls#one" = "{count} poll"; +"lng_profile_polls#other" = "{count} polls"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} videos"; "lng_profile_songs#one" = "{count} audio file"; @@ -1384,6 +1705,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_administrators#one" = "{count} administrator"; "lng_profile_administrators#other" = "{count} administrators"; "lng_profile_manage" = "Channel settings"; +"lng_profile_topic_toast" = "This topic contains {name}"; "lng_invite_upgrade_title" = "Upgrade to Premium"; "lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users."; @@ -1403,6 +1725,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_invite_upgrade_via_channel_about" = "You can send an invite link to the channel in a private message instead."; "lng_invite_status_disabled" = "available only to Premium users"; +"lng_leave_next_owner_box_title" = "Leave Channel?"; +"lng_leave_next_owner_box_title_group" = "Leave Group?"; +"lng_leave_next_owner_box_about" = "If you leave, **{user}** will become the new owner of **{chat}** in **1 week**."; +"lng_leave_next_owner_box_about_admin" = "If you leave, **{user}** will become the new admin of **{chat}** in **1 week**."; +"lng_leave_next_owner_box_about_legacy" = "If you leave, **{user}** will immediately become the new owner of **{chat}**."; +"lng_leave_next_owner_box_about_admin_legacy" = "If you leave, **{user}** will immediately become the new admin of **{chat}**."; +"lng_select_next_owner_box" = "Appoint Another Owner"; +"lng_select_next_owner_box_admin" = "Appoint Another Admin"; +"lng_select_next_owner_box_title" = "Appoint New Owner"; +"lng_select_next_owner_box_title_admin" = "Appoint New Admin"; +"lng_select_next_owner_box_confirm" = "Appoint and Leave Group"; +"lng_select_next_owner_box_sub_admins" = "Channel admins"; +"lng_select_next_owner_box_sub_admins_group" = "Group admins"; +"lng_select_next_owner_box_sub_members" = "Channel members"; +"lng_select_next_owner_box_sub_members_group" = "Group members"; +"lng_select_next_owner_box_status_joined" = "joined {date}"; +"lng_select_next_owner_box_status_promoted" = "promoted {date}"; +"lng_select_next_owner_box_empty_list" = "There are no eligible participants to appoint as owner."; +"lng_select_next_owner_box_empty_list_admin" = "There are no eligible participants to appoint as admin."; + "lng_via_link_group_one" = "**{user}** restricts adding them to groups.\nYou can send them an invite link as message instead."; "lng_via_link_group_many#one" = "**{count} user** restricts adding them to groups.\nYou can send them an invite link as message instead."; "lng_via_link_group_many#other" = "**{count} users** restrict adding them to groups.\nYou can send them an invite link as message instead."; @@ -1432,8 +1774,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_birthday_today_years#other" = "{date}\n({count} years old)"; "lng_info_birthday_today_label" = "Birthday today"; "lng_info_birthday_today" = "{emoji} {date}"; +"lng_info_notes_label" = "Notes"; +"lng_info_notes_private" = "only visible to you"; +"lng_edit_note" = "Edit Note"; +"lng_delete_note" = "Delete Note"; "lng_info_bio_label" = "Bio"; "lng_info_link_label" = "Link"; +"lng_info_link_topic_label" = "This topic link will only work for group members"; "lng_info_location_label" = "Location"; "lng_info_about_label" = "Description"; "lng_info_work_open" = "Open"; @@ -1455,16 +1802,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_group_title" = "Group Info"; "lng_info_channel_title" = "Channel Info"; "lng_info_topic_title" = "Topic Info"; +"lng_info_thread_title" = "Thread Info"; "lng_profile_enable_notifications" = "Notifications"; "lng_profile_send_message" = "Send Message"; "lng_profile_open_app" = "Open App"; +"lng_profile_open_app_short" = "Open"; "lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}."; "lng_profile_open_app_terms" = "Terms of Service for Mini Apps"; +"lng_profile_open_photo" = "Open Photo"; +"lng_profile_bot_permissions_title" = "Allow access to"; +"lng_profile_bot_emoji_status_access" = "Emoji Status"; "lng_info_add_as_contact" = "Add to contacts"; "lng_profile_shared_media" = "Shared media"; "lng_profile_suggest_photo" = "Suggest Profile Photo"; +"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard"; "lng_profile_set_photo_for" = "Set Profile Photo"; +"lng_profile_set_photo_for_group" = "Set Group Photo"; +"lng_profile_set_photo_for_channel" = "Set Channel Photo"; +"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard"; +"lng_profile_set_photo_for_about" = "You can replace {user}'s photo with another photo that only you will see."; "lng_profile_photo_reset" = "Reset to Original"; +"lng_profile_photo_reset_button" = "Reset"; +"lng_profile_photo_reset_sure" = "Are you sure you want to reset {user}'s photo to the original?"; +"lng_profile_photo_from_clipboard" = "From clipboard"; "lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile."; "lng_profile_suggest_button" = "Suggest"; "lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves."; @@ -1475,6 +1835,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_changed_photo_title" = "Photo updated"; "lng_profile_changed_photo_about" = "You can change it in {link}."; "lng_profile_changed_photo_link" = "Settings"; + +"lng_profile_action_short_message" = "Message"; +"lng_profile_action_short_channel" = "Channel"; +"lng_profile_action_short_mute" = "Mute"; +"lng_profile_action_short_unmute" = "Unmute"; +"lng_profile_action_short_call" = "Call"; +"lng_profile_action_short_discuss" = "Discuss"; +"lng_profile_action_short_gift" = "Gift"; +"lng_profile_action_short_join" = "Join"; +"lng_profile_action_short_report" = "Report"; +"lng_profile_action_short_leave" = "Leave"; +"lng_profile_action_short_more" = "More"; +"lng_profile_action_short_manage" = "Manage"; +"lng_profile_action_short_live_stream" = "Live stream"; +"lng_profile_action_short_video_chat" = "Video chat"; + "lng_media_type_photos" = "Photos"; "lng_media_type_gifs" = "GIFs"; "lng_media_type_videos" = "Videos"; @@ -1482,7 +1858,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_type_files" = "Files"; "lng_media_type_audios" = "Voice messages"; "lng_media_type_links" = "Shared links"; +"lng_media_type_polls" = "Polls"; +"lng_polls_search_none" = "No polls found"; "lng_media_type_rounds" = "Video messages"; +"lng_media_zoom_in" = "Zoom In"; +"lng_media_zoom_out" = "Zoom Out"; +"lng_media_saved_music_your" = "Your playlist"; +"lng_media_saved_music_title" = "Playlist"; "lng_profile_common_groups_section" = "Groups in common"; "lng_info_edit_contact" = "Edit contact"; "lng_info_delete_contact" = "Delete contact"; @@ -1492,6 +1874,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_block_user" = "Block user"; "lng_profile_unblock_user" = "Unblock user"; "lng_profile_export_chat" = "Export chat history"; +"lng_profile_export_topic" = "Export topic history"; "lng_profile_gift_premium" = "Gift Premium"; "lng_media_selected_photo#one" = "{count} Photo"; "lng_media_selected_photo#other" = "{count} Photos"; @@ -1507,6 +1890,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_selected_audio#other" = "{count} Voice messages"; "lng_media_selected_link#one" = "{count} shared link"; "lng_media_selected_link#other" = "{count} shared links"; +"lng_media_selected_poll#one" = "{count} Poll"; +"lng_media_selected_poll#other" = "{count} Polls"; "lng_media_photo_empty" = "No photos here yet"; "lng_media_gif_empty" = "No GIFs here yet"; "lng_media_video_empty" = "No videos here yet"; @@ -1522,6 +1907,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_channel_title" = "Manage Channel"; "lng_manage_bot_title" = "Manage Bot"; "lng_manage_peer_recent_actions" = "Recent actions"; +"lng_manage_peer_star_ref" = "Affiliate programs"; "lng_manage_peer_members" = "Members"; "lng_manage_peer_subscribers" = "Subscribers"; "lng_manage_peer_administrators" = "Administrators"; @@ -1560,7 +1946,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_reactions_max_about" = "Limit the number of different reactions that can be added to a post, including already published ones."; "lng_manage_peer_reactions_paid" = "Enable Paid Reactions"; -"lng_manage_peer_reactions_paid_about" = "Switch this on to let your subscribers react to posts with Telegram Stars, which you will be able to withdraw as TON. {link}"; +"lng_manage_peer_reactions_paid_about" = "Switch this on to let your subscribers react to posts with Telegram Stars, which you will be able to withdraw as Grams. {link}"; "lng_manage_peer_reactions_paid_link" = "Learn more >"; "lng_manage_peer_antispam" = "Aggressive Anti-Spam"; @@ -1584,20 +1970,209 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_send_only_members" = "Only members"; "lng_manage_peer_send_only_members_about" = "Turn this on if you expect users to join your group before being able to send messages."; "lng_manage_peer_send_approve_members" = "Approve new members"; -"lng_manage_peer_send_approve_members_about" = "Turn this on if you want users to join the group only after they are approved by an admin."; +"lng_manage_peer_send_approve_subscribers" = "Approve new subscribers"; +"lng_manage_peer_send_approve_members_about" = "Admins must approve anyone who wants to send a message in the group."; +"lng_manage_peer_send_approve_members_about_channel" = "Admins must approve anyone who wants to subscribe to this channel."; +"lng_manage_peer_send_approve_members_about_managed" = "Admins must approve anyone who wants to send a message in the group. Managed by {bot}."; +"lng_manage_peer_send_approve_members_about_managed_channel" = "Admins must approve anyone who wants to subscribe to this channel. Managed by {bot}."; +"lng_manage_peer_request_apply_title" = "Apply to existing invite links"; +"lng_manage_peer_request_apply_enable_group#one" = "Also enable **Approve New Members** for **{count} existing invite link** in this group?"; +"lng_manage_peer_request_apply_enable_group#other" = "Also enable **Approve New Members** for **{count} existing invite links** in this group?"; +"lng_manage_peer_request_apply_enable_channel#one" = "Also enable **Approve New Subscribers** for **{count} existing invite link** in this channel?"; +"lng_manage_peer_request_apply_enable_channel#other" = "Also enable **Approve New Subscribers** for **{count} existing invite links** in this channel?"; +"lng_manage_peer_request_apply_disable_group#one" = "Also disable **Approve New Members** for **{count} existing invite link** in this group?"; +"lng_manage_peer_request_apply_disable_group#other" = "Also disable **Approve New Members** for **{count} existing invite links** in this group?"; +"lng_manage_peer_request_apply_disable_channel#one" = "Also disable **Approve New Subscribers** for **{count} existing invite link** in this channel?"; +"lng_manage_peer_request_apply_disable_channel#other" = "Also disable **Approve New Subscribers** for **{count} existing invite links** in this channel?"; +"lng_manage_peer_request_apply_skip" = "Don't Apply"; +"lng_manage_peer_request_apply_confirm" = "Apply"; "lng_manage_peer_no_forwards_title" = "Content protection"; "lng_manage_peer_no_forwards" = "Restrict saving content"; "lng_manage_peer_no_forwards_about" = "Members won't be able to copy, save or forward content from this group."; "lng_manage_peer_no_forwards_about_channel" = "Subscribers won't be able to copy, save or forward content from this channel."; +"lng_disable_sharing" = "Disable Sharing"; +"lng_enable_sharing" = "Enable Sharing"; +"lng_disable_sharing_title" = "Disable Sharing"; +"lng_disable_sharing_no_forwarding" = "No Forwarding"; +"lng_disable_sharing_no_forwarding_about" = "Disable message forwarding to other chats."; +"lng_disable_sharing_no_saving" = "No Saving"; +"lng_disable_sharing_no_saving_about" = "Disable copying texts and saving photos and videos to gallery."; +"lng_disable_sharing_unlock" = "Unlock with Telegram Premium"; +"lng_disable_sharing_button" = "Disable Sharing"; +"lng_disable_sharing_toast" = "Sharing disabled for this chat."; +"lng_enable_sharing_toast" = "Sharing enabled for this chat."; +"lng_enable_sharing_request_title" = "Enable Sharing"; +"lng_enable_sharing_request_text" = "You need **{name}'s** approval to enable sharing. Send a request?"; +"lng_enable_sharing_request_button" = "Send Request"; + +"lng_action_no_forwards_you_disabled" = "You disabled sharing in this chat"; +"lng_action_no_forwards_you_enabled" = "You enabled sharing in this chat"; +"lng_action_no_forwards_disabled" = "{from} disabled sharing in this chat"; +"lng_action_no_forwards_enabled" = "{from} enabled sharing in this chat"; +"lng_action_no_forwards_still_disabled" = "Sharing in this chat is still disabled"; +"lng_action_no_forwards_request" = "{from} would like to enable sharing in this chat, which includes:"; +"lng_action_no_forwards_request_you" = "You requested to enable sharing in this chat, which includes:"; +"lng_action_no_forwards_feature_forwarding" = "Forwarding messages"; +"lng_action_no_forwards_feature_saving" = "Saving photos and videos"; +"lng_action_no_forwards_feature_copying" = "Copying messages"; +"lng_action_no_forwards_accept" = "Accept"; +"lng_action_no_forwards_reject" = "Reject"; +"lng_action_no_forwards_request_expired" = "Sharing enable request has expired"; + "lng_manage_peer_bot_public_link" = "Public Link"; "lng_manage_peer_bot_public_links" = "Public Links"; "lng_manage_peer_bot_balance" = "Balance"; +"lng_manage_peer_bot_balance_currency" = "Toncoin"; +"lng_manage_peer_bot_balance_credits" = "Stars"; +"lng_manage_peer_bot_star_ref" = "Affiliate Program"; +"lng_manage_peer_bot_star_ref_off" = "Off"; +"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there."; +"lng_manage_peer_bot_verify" = "Verify Accounts"; "lng_manage_peer_bot_edit_intro" = "Edit Intro"; "lng_manage_peer_bot_edit_commands" = "Edit Commands"; "lng_manage_peer_bot_edit_settings" = "Change Bot Settings"; "lng_manage_peer_bot_about" = "Use {bot} to manage this bot."; +"lng_bot_verify_title" = "Choose Chat to Verify"; +"lng_bot_verify_bot_title" = "Verify Bot"; +"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_bot_about" = "You can customize your description for each bot."; +"lng_bot_verify_bot_submit" = "Verify Bot"; +"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_user_title" = "Verify User"; +"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_user_about" = "You can customize your description for each account."; +"lng_bot_verify_user_submit" = "Verify User"; +"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_channel_title" = "Verify Channel"; +"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_channel_about" = "You can customize your description for each channel."; +"lng_bot_verify_channel_submit" = "Verify Channel"; +"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_group_title" = "Verify Group"; +"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?"; +"lng_bot_verify_group_about" = "You can customize your description for each group."; +"lng_bot_verify_group_submit" = "Verify Group"; +"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting."; +"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?"; +"lng_bot_verify_description_label" = "Description"; +"lng_bot_verify_remove_title" = "Remove verification"; +"lng_bot_verify_remove_submit" = "Remove"; +"lng_bot_verify_remove_done" = "You've removed this verification."; + +"lng_star_ref_title" = "Affiliate Program"; +"lng_star_ref_about" = "Reward those who help grow your user base."; +"lng_star_ref_share_title" = "Share revenue with affiliates"; +"lng_star_ref_share_about" = "Set the commission for revenue generated by users referred to you."; +"lng_star_ref_launch_title" = "Launch your affiliate program"; +"lng_star_ref_launch_about" = "Telegram will feature your program for millions of potential affiliates."; +"lng_star_ref_let_title" = "Let affiliate promote you"; +"lng_star_ref_let_about" = "Affiliates will share your referral link with their audience."; +"lng_star_ref_commission_title" = "Commission"; +"lng_star_ref_commission_about" = "Define the percentage of star revenue your affiliates earn for referring users to your bot."; +"lng_star_ref_duration_title" = "Duration"; +"lng_star_ref_duration_about" = "Set the duration for which affiliates will earn commissions from referred users."; +"lng_star_ref_existing_title" = "View existing programs"; +"lng_star_ref_existing_about" = "Explore what other mini apps offer."; +"lng_star_ref_add_bot" = "Add {bot}"; +"lng_star_ref_end" = "End Affiliate Program"; +"lng_star_ref_start" = "Start Affiliate Program"; +"lng_star_ref_start_disabled" = "Available in {time}"; +"lng_star_ref_start_info" = "By creating an affiliate program, you agree to the {terms} of Affiliate Programs."; +"lng_star_ref_update" = "Update Affiliate Program"; +"lng_star_ref_update_info" = "By updating an affiliate program, you agree to the {terms} of Affiliate Programs."; +"lng_star_ref_button_link" = "terms and conditions"; +"lng_star_ref_tos_url" = "https://telegram.org/tos/mini-apps"; +"lng_star_ref_warning_title" = "Warning"; +"lng_star_ref_warning_text" = "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links."; +"lng_star_ref_warning_change" = "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links."; +"lng_star_ref_warning_start" = "Start"; +"lng_star_ref_warning_update" = "Update"; +"lng_star_ref_warning_if_end" = "If you end your affiliate program:"; +"lng_star_ref_warning_if_end1" = "Any referral links already shared will be disabled in **24** hours."; +"lng_star_ref_warning_if_end2" = "All participating affiliates will be notified."; +"lng_star_ref_warning_if_end3" = "You will be able to start a new affiliate program only in **24** hours."; +"lng_star_ref_warning_end" = "End Anyway"; +"lng_star_ref_created_title" = "Affiliate program started"; +"lng_star_ref_created_text" = "Any Telegram user, channel owner or mini app developer can now join your program."; +"lng_star_ref_updated_title" = "Affiliate program updated"; +"lng_star_ref_updated_text" = "Any Telegram user, channel owner or mini app developer can join your program."; +"lng_star_ref_ended_title" = "Affiliate program ended"; +"lng_star_ref_ended_text" = "Participating affiliates have been notified. All referral links will be disabled in **24** hours."; +"lng_star_ref_list_title" = "Affiliate Programs"; +"lng_star_ref_list_about_channel" = "Promote mini apps to your subscribers and earn a share of their revenue in Stars."; +"lng_star_ref_list_text" = "Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it."; +"lng_star_ref_list_my" = "My Programs"; +"lng_star_ref_list_my_open" = "Open App"; +"lng_star_ref_list_my_copy" = "Copy Link"; +"lng_star_ref_list_my_leave" = "Leave"; +"lng_star_ref_list_subtitle" = "Programs"; +"lng_star_ref_sort_text" = "Sort by {sort}"; +"lng_star_ref_sort_profitability" = "Profitability"; +"lng_star_ref_sort_date" = "Date"; +"lng_star_ref_sort_revenue" = "Revenue"; +"lng_star_ref_reliable_title" = "Reliable"; +"lng_star_ref_reliable_about" = "Receive guaranteed commissions for spending by users you refer."; +"lng_star_ref_transparent_title" = "Transparent"; +"lng_star_ref_transparent_about" = "Track your commissions from referred users in real time."; +"lng_star_ref_simple_title" = "Simple"; +"lng_star_ref_simple_about" = "Choose a mini app below, get your referral link, and start earning Stars."; +"lng_star_ref_duration_forever" = "Forever"; +"lng_star_ref_one_about" = "{app} will share {amount} of the revenue from each user you refer to it {duration}."; +"lng_star_ref_one_about_for_forever" = "for **lifetime**"; +"lng_star_ref_one_about_for_months#one" = "for **{count} month**"; +"lng_star_ref_one_about_for_months#other" = "for **{count} months**"; +"lng_star_ref_one_about_for_years#one" = "for **{count} year**"; +"lng_star_ref_one_about_for_years#other" = "for **{count} years**"; +"lng_star_ref_one_daily_revenue" = "Daily revenue per user: {amount}"; +"lng_star_ref_one_join" = "Join Program"; +"lng_star_ref_one_join_text" = "By joining this program, you agree to the {terms} of Affiliate Programs."; +"lng_star_ref_joined_title" = "Program joined"; +"lng_star_ref_joined_text" = "You can now copy the referral link."; +"lng_star_ref_link_title" = "Referral Link"; +"lng_star_ref_link_about_channel" = "Share this link with your subscribers to earn a {amount} commission on their spending in {app} {duration}."; +"lng_star_ref_link_about_user" = "Share this link with your friends to earn a {amount} commission on their spending in {app} {duration}."; +"lng_star_ref_link_about_bot" = "Share this link with your users to earn a {amount} commission on their spending in {app} {duration}."; +"lng_star_ref_link_recipient" = "Commissions will be sent to:"; +"lng_star_ref_link_copy" = "Copy Link"; +"lng_star_ref_link_copy_none" = "No one have opened {app} through this link."; +"lng_star_ref_link_copy_users#one" = "{count} user have opened {app} through this link."; +"lng_star_ref_link_copy_users#other" = "{count} users have opened {app} through this link."; +"lng_star_ref_link_copied_title" = "Link copied to clipboard"; +"lng_star_ref_link_copied_text" = "Share this link and earn {amount} of what people who use it spend in {app}!"; +"lng_star_ref_stopped" = "This affiliate link is no longer active."; +"lng_star_ref_revoke_title" = "Revoke Link"; +"lng_star_ref_revoke_text" = "Are you sure you want to revoke the link of {bot}?"; +"lng_star_ref_revoked_title" = "Link removed"; +"lng_star_ref_revoked_text" = "It will no longer work."; + +"lng_stars_rating_title" = "Rating"; +"lng_stars_rating_future" = "Future Rating"; +"lng_stars_rating_updates#one" = "in {count} day"; +"lng_stars_rating_updates#other" = "in {count} days"; +"lng_stars_rating_pending#one" = "The rating will update {when}.\n{count} point is pending. {link}"; +"lng_stars_rating_pending#other" = "The rating will update {when}.\n{count} points are pending. {link}"; +"lng_stars_rating_pending_preview" = "Preview {arrow}"; +"lng_stars_rating_pending_back" = "Back {arrow}"; +"lng_stars_rating_negative_label" = "Negative Rating"; +"lng_stars_rating_negative" = "A negative rating indicates that **{name}'s** payments are unreliable."; +"lng_stars_rating_negative_your#one" = "A negative rating indicates that your payments are unreliable. Spend **{count} Star** to fix this issue."; +"lng_stars_rating_negative_your#other" = "A negative rating indicates that your payments are unreliable. Spend **{count} Stars** to fix this issue."; +"lng_stars_rating_about" = "This rating reflects **{name}'s** activity on Telegram. What affects it:"; +"lng_stars_rating_about_your" = "This rating reflects your activity on Telegram. What affects it:"; +"lng_stars_title_gifts_telegram" = "Gifts from Telegram"; +"lng_stars_about_gifts_telegram" = "{emoji} 100% of the Stars spent on gifts purchased from Telegram."; +"lng_stars_title_gifts_users" = "Gifts and Posts from Users"; +"lng_stars_about_gifts_users" = "{emoji} 20% of the Stars spent on resold gifts, paid messages and channel posts."; +"lng_stars_title_refunds" = "Refunds and Conversions"; +"lng_stars_about_refunds" = "{emoji} 10x of refunded Stars and 85% of bought gifts converted to Stars."; +"lng_stars_rating_added" = "Added"; +"lng_stars_rating_deducted" = "Deducted"; +"lng_stars_rating_understood" = "Understood"; "lng_manage_discussion_group" = "Discussion"; "lng_manage_discussion_group_add" = "Add a group"; @@ -1619,6 +2194,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_linked_channel_posted" = "All new posts from this channel are forwarded to the group."; "lng_manage_discussion_group_warning" = "\"Chat history for new members\" will be switched to **Visible**."; +"lng_manage_monoforum" = "Direct Messages"; +"lng_manage_monoforum_off" = "Off"; +"lng_manage_monoforum_free" = "Free"; +"lng_manage_monoforum_allow" = "Allow Channel Messages"; +"lng_manage_monoforum_price" = "Price for each message"; +"lng_manage_monoforum_about" = "Allow users to send messages to your channel, with the option to charge a fee for each message."; +"lng_manage_monoforum_price_about" = "Your channel will receive {percent} of the selected fee ({amount}) for each incoming message."; +"lng_manage_monoforum_link_subtitle" = "Link to direct messages"; + "lng_manage_history_visibility_title" = "Chat history for new members"; "lng_manage_history_visibility_shown" = "Visible"; "lng_manage_history_visibility_shown_about" = "New members will see messages that were sent before they joined."; @@ -1746,6 +2330,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?"; "lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone."; "lng_sure_delete_group_history" = "Are you sure you want to delete all messages in \"{group}\"?\n\nThis action cannot be undone."; +"lng_sure_delete_channel_history" = "Are you sure you want to delete all messages in \"{channel}\"?\n\n**This action cannot be undone.**"; "lng_sure_delete_and_exit" = "Are you sure you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone."; "lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?"; "lng_sure_delete_channel" = "Are you sure you want to delete this channel? All subscribers will be removed and all messages will be lost."; @@ -1802,6 +2387,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_changed_title" = "{from} changed group name to «{title}»"; "lng_action_changed_title_channel" = "Channel name was changed to «{title}»"; "lng_action_created_chat" = "{from} created the group «{title}»"; +"lng_action_created_monoforum" = "Direct messages were enabled in this channel."; "lng_action_ttl_changed" = "{from} set messages to auto-delete in {duration}"; "lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}"; "lng_action_ttl_changed_channel" = "Messages in this channel will be automatically deleted after {duration}"; @@ -1839,6 +2425,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments"; "lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments"; "lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment"; +"lng_action_payment_bot_done" = "Bot connected to this account received {amount}"; +"lng_action_payment_bot_recurring" = "Bot connected to this account received {amount} via recurring payment"; "lng_action_took_screenshot" = "{from} took a screenshot!"; "lng_action_you_took_screenshot" = "You took a screenshot!"; "lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}."; @@ -1855,6 +2443,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_you_proximity_reached" = "You are now within {distance} from {user}"; "lng_action_you_theme_changed" = "You changed the chat theme to {emoji}"; "lng_action_theme_changed" = "{from} changed the chat theme to {emoji}"; +"lng_action_you_gift_theme_changed" = "You set {name} as a new theme for this chat."; +"lng_action_gift_theme_changed" = "{from} set {name} as a new theme for this chat."; "lng_action_you_theme_disabled" = "You disabled the chat theme"; "lng_action_theme_disabled" = "{from} disabled the chat theme"; "lng_action_proximity_distance_m#one" = "{count} meter"; @@ -1863,25 +2453,93 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_proximity_distance_km#other" = "{count} km"; "lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot."; "lng_action_gift_received" = "{user} sent you a gift for {cost}"; +"lng_action_gift_received_sold" = "{user} sold you a gift for {cost}"; +"lng_action_gift_unique_received" = "{user} sent you a unique collectible item"; "lng_action_gift_sent" = "You sent a gift for {cost}"; +"lng_action_gift_sent_sold" = "You sold a gift for {cost}"; +"lng_action_gift_unique_sent" = "You sent a unique collectible item"; +"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible"; +"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible"; +"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible"; +"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible"; +"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible"; +"lng_action_gift_sent_upgrade_other" = "{from} sent an upgrade worth {cost} for the gift you received from {user}."; +"lng_action_gift_sent_upgrade_self_other" = "You sent an upgrade worth {cost} for the gift {name} received from {user}."; +"lng_action_gift_sent_upgrade" = "{from} sent an upgrade worth {cost} for your gift."; +"lng_action_gift_sent_upgrade_self" = "You sent an upgrade worth {cost} for this gift."; +"lng_action_gift_sent_upgrade_self_channel" = "You sent an upgrade worth {cost} for your gift to {name}."; +"lng_action_gift_upgraded_helped" = "{user} unpacked the gift that you helped to upgrade."; +"lng_action_gift_upgraded_helped_self" = "You unpacked the gift that {user} helped to upgrade."; +"lng_action_gift_transferred" = "{user} transferred you a gift"; +"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}"; +"lng_action_gift_transferred_unknown" = "Someone transferred you a gift"; +"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}"; +"lng_action_gift_transferred_self" = "You transferred a unique collectible"; +"lng_action_gift_displayed_self" = "You've started displaying {name} on your Telegram profile page."; +"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}"; +"lng_action_gift_transferred_mine" = "You transferred a gift to {user}"; +"lng_action_gift_crafted" = "You crafted a new gift"; "lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; +"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}"; +"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}"; +"lng_action_gift_self_bought" = "You bought a gift for {cost}"; +"lng_action_gift_self_auction" = "You've successfully bought a gift in the auction for {cost}."; +"lng_action_gift_auction_won" = "You won the auction with a bid of {cost}."; +"lng_action_gift_self_subtitle" = "Saved Gift"; +"lng_action_gift_crafted_subtitle" = "Crafted Gift"; +"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star."; +"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars."; +"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others."; +"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star."; +"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars."; +"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible."; "lng_action_gift_for_stars#one" = "{count} Star"; "lng_action_gift_for_stars#other" = "{count} Stars"; +"lng_action_gift_for_ton#one" = "{count} Gram"; +"lng_action_gift_for_ton#other" = "{count} Grams"; "lng_action_gift_got_subtitle" = "Gift from {user}"; "lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star."; "lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars."; +"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible."; +"lng_action_gift_got_gift_text" = "You can keep this gift on your page."; +"lng_action_gift_can_remove_text" = "You can remove this gift from your page."; +"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts."; +"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts."; "lng_action_gift_sent_subtitle" = "Gift for {user}"; "lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star."; "lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars."; +"lng_action_gift_sent_upgradable" = "{user} can upgrade this gift to a unique collectible."; "lng_action_gift_premium_months#one" = "{count} Month Premium"; "lng_action_gift_premium_months#other" = "{count} Months Premium"; "lng_action_gift_premium_about" = "Subscription for exclusive Telegram features."; +"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned."; +"lng_action_gift_got_ton" = "Use Grams to suggest posts to channels."; +"lng_action_gift_offer_incoming" = "An offer to buy this gift for {amount}."; +"lng_action_gift_offer_you" = "You offered {cost} for {name}."; +"lng_action_gift_offer_state_expires" = "This offer expires in {time}."; +"lng_action_gift_offer_time_large" = "{hours} h"; +"lng_action_gift_offer_time_medium" = "{hours} h {minutes} m"; +"lng_action_gift_offer_time_small" = "{minutes} m"; +"lng_action_gift_offer_state_accepted" = "This offer was accepted."; +"lng_action_gift_offer_state_rejected" = "This offer was rejected."; +"lng_action_gift_offer_state_expired" = "This offer has expired."; +"lng_action_gift_offer_sold" = "{user} sold {name} for {cost}."; +"lng_action_gift_offer_sold_you" = "You sold {name} for {cost}."; +"lng_action_gift_offer_decline" = "Reject"; +"lng_action_gift_offer_accept" = "Accept"; +"lng_action_gift_offer_expired" = "The offer from {user} to buy your {name} for {cost} has expired."; +"lng_action_gift_offer_expired_your" = "Your offer to buy {name} for {cost} has expired."; +"lng_action_gift_offer_declined" = "{user} rejected your offer to buy {name} for {cost}."; +"lng_action_gift_offer_declined_you" = "You rejected {user}'s offer to buy your {name} for {cost}."; "lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile."; "lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile."; "lng_action_suggested_photo_button" = "View Photo"; "lng_action_suggested_video_me" = "You suggested this photo for {user}'s Telegram profile."; "lng_action_suggested_video" = "{user} suggests this photo for your Telegram profile."; "lng_action_suggested_video_button" = "View Photo"; +"lng_action_suggested_birthday_me" = "You suggest {user} add a date of birth:"; +"lng_action_suggested_birthday" = "{user} suggests you add your date of birth:"; +"lng_action_suggested_birtday_button" = "View"; "lng_action_attach_menu_bot_allowed" = "You allowed this bot to message you when you added it to your attachment menu."; "lng_action_webapp_bot_allowed" = "You allowed this bot to message you in its web-app."; "lng_action_set_wallpaper_me" = "You set a new wallpaper for this chat"; @@ -1894,14 +2552,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_topic_created_inside" = "Topic created"; "lng_action_topic_closed_inside" = "Topic closed"; "lng_action_topic_reopened_inside" = "Topic reopened"; +"lng_action_topic_closed_inside_by" = "{from} closed the topic"; +"lng_action_topic_reopened_inside_by" = "{from} reopened the topic"; "lng_action_topic_hidden_inside" = "Topic hidden"; "lng_action_topic_unhidden_inside" = "Topic unhidden"; "lng_action_topic_created" = "The topic \"{topic}\" was created"; "lng_action_topic_closed" = "\"{topic}\" was closed"; "lng_action_topic_reopened" = "\"{topic}\" was reopened"; +"lng_action_topic_closed_by" = "{from} closed \"{topic}\""; +"lng_action_topic_reopened_by" = "{from} reopened \"{topic}\""; "lng_action_topic_hidden" = "\"{topic}\" was hidden"; "lng_action_topic_unhidden" = "\"{topic}\" was unhidden"; "lng_action_topic_placeholder" = "topic"; +"lng_action_topic_bot_thread" = "thread"; "lng_action_topic_renamed" = "{from} renamed the {link} to \"{title}\""; "lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}"; "lng_action_topic_icon_removed" = "{from} removed the {link} icon"; @@ -1924,10 +2587,98 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_giveaway_results_credits#other" = "{count} winners of the giveaway were randomly selected by Telegram and received their prize."; "lng_action_giveaway_results_credits_some" = "Some winners of the giveaway were randomly selected by Telegram and received their prize."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; +"lng_action_boost_apply_me" = "You boosted the group"; "lng_action_boost_apply#one" = "{from} boosted the group"; "lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_payment_refunded" = "{peer} refunded {amount}"; +"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}"; +"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}"; +"lng_action_paid_message_one" = "send a message"; +"lng_action_paid_message_some#one" = "send {count} message"; +"lng_action_paid_message_some#other" = "send {count} messages"; +"lng_action_paid_message_got#one" = "You received {count} Star from {name}"; +"lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; +"lng_action_paid_message_refund#one" = "{from} refunded {count} Star to you"; +"lng_action_paid_message_refund#other" = "{from} refunded {count} Stars to you"; +"lng_action_paid_message_refund_self#one" = "You refunded {count} Star to {name}"; +"lng_action_paid_message_refund_self#other" = "You refunded {count} Stars to {name}"; +"lng_action_message_price_free" = "Messages are now free in this group."; +"lng_action_message_price_paid#one" = "Messages now cost {count} Star each in this group."; +"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group."; +"lng_action_direct_messages_enabled" = "Channel enabled Direct Messages."; +"lng_action_direct_messages_paid#one" = "Channel allows Direct Messages for {count} Star each."; +"lng_action_direct_messages_paid#other" = "Channel allows Direct Messages for {count} Stars each."; +"lng_action_direct_messages_disabled" = "Channel disabled Direct Messages."; +"lng_action_todo_marked_done" = "{from} marked {tasks} as done."; +"lng_action_todo_marked_done_self" = "You marked {tasks} as done."; +"lng_action_todo_marked_not_done" = "{from} marked {tasks} as not done."; +"lng_action_todo_marked_not_done_self" = "You marked {tasks} as not done."; +"lng_action_todo_added" = "{from} added {tasks} to the list."; +"lng_action_todo_added_self" = "You added {tasks} to the list."; +"lng_action_poll_added_answer" = "{from} added \"{option}\" to the poll."; +"lng_action_poll_added_answer_self" = "You added \"{option}\" to the poll."; +"lng_action_poll_deleted_answer" = "{from} removed \"{option}\" from the poll."; +"lng_action_poll_deleted_answer_self" = "You removed \"{option}\" from the poll."; +"lng_action_todo_tasks_fallback#one" = "task"; +"lng_action_todo_tasks_fallback#other" = "{count} tasks"; +"lng_action_todo_tasks_and_one" = "{tasks}, {task}"; +"lng_action_todo_tasks_and_last" = "{tasks} and {task}"; +"lng_action_suggest_success_stars#one" = "{from} has received {count} Star for publishing post."; +"lng_action_suggest_success_stars#other" = "{from} has received {count} Stars for publishing post."; +"lng_action_suggest_success_ton#one" = "{from} has received {count} Gram for publishing post."; +"lng_action_suggest_success_ton#other" = "{from} has received {count} Grams for publishing post."; +"lng_action_suggest_refund_user" = "User refunded the Stars so that post was deleted."; +"lng_action_suggest_refund_admin" = "Admin deleted the post early so that the price was refunded to the user."; +"lng_action_post_rejected" = "The post was rejected."; +"lng_action_not_enough_funds" = "Transaction failed."; +"lng_you_paid_stars#one" = "You paid {count} Star."; +"lng_you_paid_stars#other" = "You paid {count} Stars."; +"lng_action_stake_game_nothing" = "{from} didn't win anything"; +"lng_action_stake_game_nothing_you" = "You didn't win anything"; +"lng_action_stake_game_won" = "{from} won {amount}"; +"lng_action_stake_game_won_you" = "You won {amount}"; +"lng_action_stake_game_lost" = "{from} lost {amount}"; +"lng_action_stake_game_lost_you" = "You lost {amount}"; +"lng_action_change_creator" = "{from} made {user} the new main admin of the group."; +"lng_action_new_creator_pending" = "{user} will become the new main admin in 7 days if {from} does not return."; +"lng_action_managed_bot_created" = "{from} created a bot {bot}."; + +"lng_create_bot_title" = "Create Bot"; +"lng_create_bot_subtitle" = "{bot} would like to create and manage a chatbot on your behalf."; +"lng_create_bot_name_placeholder" = "Bot Name"; +"lng_create_bot_username_placeholder" = "Bot Username"; +"lng_create_bot_username_available" = "{username} is available."; +"lng_create_bot_username_link" = "Link: {link}"; +"lng_create_bot_username_taken" = "This username is already taken."; +"lng_create_bot_username_bad_symbols" = "Username can only contain a-z, 0-9, and underscores."; +"lng_create_bot_username_too_short" = "Username must be at least 5 characters."; +"lng_create_bot_button" = "Create"; +"lng_managed_bot_label" = "{icon} Created and managed by {bot}."; +"lng_managed_bot_ready" = "**{name}** is ready!\n\nClick **Start** below to test your new chatbot. Its behavior is defined by **{parent}**."; +"lng_managed_bot_created_title" = "{name} created!"; +"lng_managed_bot_created_text" = "{parent_name} will manage this bot for you."; +"lng_managed_bot_edit_photo" = "You can edit your bot's profile picture {link}"; +"lng_managed_bot_edit_photo_link" = "here {arrow}"; +"lng_managed_bot_set_photo" = "Set Profile Photo"; + +"lng_create_bot_no_manage" = "{bot} doesn't have Bot Management Mode enabled."; + +"lng_bots_create_limit#one" = "Subscribe to {link} to create up to {premium_count} bots, or delete one of your **{count}** bot via {bot}."; +"lng_bots_create_limit#other" = "Subscribe to {link} to create up to {premium_count} bots, or delete one of your **{count}** bots via {bot}."; +"lng_bots_create_limit_link" = "Premium"; +"lng_bots_create_limit_final#one" = "You can create up to **{count}** bot. Delete your current ones via {bot}."; +"lng_bots_create_limit_final#other" = "You can create up to **{count}** bots. Delete your current ones via {bot}."; + +"lng_stake_game_title" = "Emoji Stake"; +"lng_stake_game_beta" = "Beta"; +"lng_stake_game_about" = "A limited play-test of the upcoming emoji mini-game platform for a small group of users."; +"lng_stake_game_results" = "Results and Returns"; +"lng_stake_game_resets" = "A streak resets after 3 {emoji} or a stake change."; +"lng_stake_game_your" = "Your stake"; +"lng_stake_game_save_and_roll" = "Save and Roll"; + +"lng_you_joined_group" = "You joined this group"; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; @@ -1937,10 +2688,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_similar_channels_premium_all_link" = "Telegram Premium"; "lng_similar_channels_show_more" = "Show more channels"; +"lng_similar_bots_title" = "Similar bots"; +"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot."; +"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots."; +"lng_similar_bots_show_more" = "Show more bots"; + "lng_peer_gifts_title" = "Gifts"; "lng_peer_gifts_about" = "These gifts were sent to {user} by other users."; "lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings."; - +"lng_peer_gifts_empty_search" = "No matching gifts"; +"lng_peer_gifts_view_all" = "View All Gifts"; +"lng_peer_gifts_notify" = "Notify About New Gifts"; +"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift."; +"lng_peer_gifts_filter_by_value" = "Sort by Value"; +"lng_peer_gifts_filter_by_date" = "Sort by Date"; +"lng_peer_gifts_filter_unlimited" = "Unlimited"; +"lng_peer_gifts_filter_upgradable" = "Upgradeable"; +"lng_peer_gifts_filter_limited" = "Limited"; +"lng_peer_gifts_filter_unique" = "Unique"; +"lng_peer_gifts_filter_saved" = "Displayed"; +"lng_peer_gifts_filter_unsaved" = "Hidden"; + +"lng_premium_gift_duration_days#one" = "for {count} day"; +"lng_premium_gift_duration_days#other" = "for {count} days"; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; "lng_premium_gift_duration_years#one" = "for {count} year"; @@ -2055,11 +2825,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_qr_title" = "Invite by QR Code"; "lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group."; "lng_group_invite_qr_copied" = "QR Code copied to clipboard."; -"lng_group_invite_request_approve" = "Request admin approval"; -"lng_group_invite_about_approve" = "Turn this on if you want users to join only after they are approved by an admin."; -"lng_group_invite_about_no_approve" = "Turn this on if you want users to join only after they are approved by an admin."; -"lng_group_invite_about_approve_channel" = "Turn this on if you want users to join only after they are approved by an admin."; -"lng_group_invite_about_no_approve_channel" = "Turn this on if you want users to join only after they are approved by an admin."; +"lng_group_invite_request_approve" = "Approve New Members"; +"lng_group_invite_request_approve_channel" = "Approve New Subscribers"; +"lng_group_invite_about_approve" = "Require admin approval for people joining through this link."; +"lng_group_invite_about_approve_managed" = "Require admin approval for people joining through this link. Managed by {bot}."; +"lng_group_invite_about_no_approve" = "Require admin approval for people joining through this link."; +"lng_group_invite_about_approve_channel" = "Require admin approval for people joining through this link."; +"lng_group_invite_about_approve_managed_channel" = "Require admin approval for people joining through this link. Managed by {bot}."; +"lng_group_invite_about_no_approve_channel" = "Require admin approval for people joining through this link."; +"lng_group_invite_about_approve_disabled" = "This option is unavailable because anyone can join and send messages through the public group link."; +"lng_group_invite_about_approve_disabled_channel" = "This option is unavailable because anyone can join the channel through the public channel link."; +"lng_group_invite_approval_required" = "approval required"; "lng_group_invite_subscription" = "Require Monthly Fee"; "lng_group_invite_subscription_ph" = "Stars Amount per month"; "lng_group_invite_subscription_price" = "~{cost} / month"; @@ -2081,6 +2857,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_request_about_channel" = "This channel accepts new subscribers only after they are approved by its admins."; "lng_group_request_sent" = "You will be added to the group once an admin approves your request."; "lng_group_request_sent_channel" = "You will be added to the channel once its admins approve your request."; +"lng_group_request_declined" = "Your request to join the group was declined."; +"lng_group_request_declined_channel" = "Your request to join the channel was declined."; "lng_group_requests_pending#one" = "{count} join request"; "lng_group_requests_pending#other" = "{count} join requests"; "lng_group_requests_pending_user" = "{user} requested to join"; @@ -2097,11 +2875,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_context_about_private_link" = "This link will only work for members of this chat."; +"lng_public_post_private_hint_ctrl" = "Use Ctrl+Click to copy a non-public link."; +"lng_public_post_private_hint_cmd" = "Use Cmd+Click to copy a non-public link."; "lng_forwarded" = "Forwarded from {user}"; "lng_forwarded_story" = "Story from {user}"; "lng_forwarded_story_expired" = "This story has expired."; "lng_forwarded_date" = "Original: {date}"; +"lng_forwarded_forwarded_date" = "Forwarded date: {date}"; "lng_forwarded_channel" = "Forwarded from {channel}"; "lng_forwarded_psa_default" = "Forwarded from {channel}"; "lng_forwarded_via" = "Forwarded from {user} via {inline_bot}"; @@ -2115,8 +2896,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_recommended_message_title" = "Recommended"; "lng_edited" = "edited"; "lng_commented" = "commented"; +"lng_approximate" = "appx."; +"lng_repeated_daily" = "daily"; +"lng_repeated_weekly" = "weekly"; +"lng_repeated_biweekly" = "biweekly"; +"lng_repeated_monthly" = "monthly"; +"lng_repeated_every_month#one" = "{count}-monthly"; +"lng_repeated_every_month#other" = "{count}-monthly"; +"lng_repeated_yearly" = "yearly"; "lng_edited_date" = "Edited: {date}"; "lng_sent_date" = "Sent: {date}"; +"lng_edited_at" = "edited at {time}"; +"lng_edited_on" = "edited on {date} at {time}"; +"lng_sent_on" = "sent on {date} at {time}"; +"lng_approximate_about" = "Estimated date of video publishing."; "lng_views_tooltip#one" = "Views: {count}"; "lng_views_tooltip#other" = "Views: {count}"; "lng_forwards_tooltip#one" = "Shares: {count}"; @@ -2127,6 +2920,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_badge" = "channel"; "lng_topic_author_badge" = "Topic Creator"; "lng_fast_reply" = "Reply"; +"lng_fast_share_tooltip" = "Right-click to select a Recent Contact."; "lng_cancel_edit_post_sure" = "Cancel editing?"; "lng_cancel_edit_post_yes" = "Yes"; "lng_cancel_edit_post_no" = "No"; @@ -2137,6 +2931,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_allow_write_title" = "Allow messaging"; "lng_bot_allow_write" = "Do you want to allow this bot to send you messages?"; "lng_bot_allow_write_confirm" = "Allow"; +"lng_bot_new_chat" = "New Chat"; +"lng_bot_new_thread_title" = "New Thread"; +"lng_bot_new_thread_about" = "Type any message to create a new thread."; +"lng_bot_show_threads_list" = "Show Threads List"; +"lng_bot_off_thread_ph" = "Off-thread message"; "lng_attach_failed" = "Failed"; "lng_attach_file" = "File"; @@ -2151,6 +2950,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_cancel" = "Cancel"; "lng_media_video" = "Video"; "lng_media_audio" = "Voice message"; +"lng_media_round" = "Video message"; "lng_media_auto_settings" = "Automatic media download"; "lng_media_auto_in_private" = "In private chats"; @@ -2158,6 +2958,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_auto_in_channels" = "In channels"; "lng_media_auto_title" = "Automatically download"; "lng_media_auto_play" = "Autoplay"; +"lng_media_auto_exceptions" = "Exceptions"; +"lng_media_auto_always" = "Always auto-download"; +"lng_media_auto_never" = "Never auto-download"; +"lng_media_auto_exceptions_add_users" = "Add users"; +"lng_media_auto_exceptions_add_groups" = "Add groups"; +"lng_media_auto_exceptions_add_channels" = "Add channels"; +"lng_media_auto_exceptions_users#one" = "{count} user"; +"lng_media_auto_exceptions_users#other" = "{count} users"; +"lng_media_auto_exceptions_groups#one" = "{count} group"; +"lng_media_auto_exceptions_groups#other" = "{count} groups"; +"lng_media_auto_exceptions_channels#one" = "{count} channel"; +"lng_media_auto_exceptions_channels#other" = "{count} channels"; +"lng_media_auto_always_title" = "Always auto-download"; +"lng_media_auto_never_title" = "Never auto-download"; "lng_media_photo_title" = "Photos"; "lng_media_video_title" = "Videos"; "lng_media_video_messages_title" = "Round video messages"; @@ -2197,6 +3011,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers_add" = "Choose sticker set"; "lng_group_emoji" = "Select Emoji Pack"; "lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group."; +"lng_collectible_emoji" = "Collectibles"; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; @@ -2206,6 +3021,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_unlock_emoji" = "Unlock Animated Emoji"; "lng_premium_unlock_status" = "Unlock Emoji Status"; +"lng_premium_subscribe_months_24" = "2-Year"; "lng_premium_subscribe_months_12" = "Annual"; "lng_premium_subscribe_months_6" = "Semiannual"; "lng_premium_subscribe_months_1" = "Monthly"; @@ -2225,8 +3041,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_title_subscribed" = "You are all set!"; "lng_premium_summary_subtitle_gift#one" = "{user} has gifted you a {count}-month subscription to Telegram Premium."; "lng_premium_summary_subtitle_gift#other" = "{user} has gifted you a {count}-months subscription to Telegram Premium."; +"lng_premium_summary_subtitle_gift_days#one" = "{user} has gifted you a {count}-day subscription to Telegram Premium."; +"lng_premium_summary_subtitle_gift_days#other" = "{user} has gifted you a {count}-days subscription to Telegram Premium."; "lng_premium_summary_subtitle_gift_me#one" = "You gifted {user} a {count}-month subscription to Telegram Premium."; "lng_premium_summary_subtitle_gift_me#other" = "You gifted {user} a {count}-months subscription to Telegram Premium."; +"lng_premium_summary_subtitle_gift_days_me#one" = "You gifted {user} a {count}-month subscription to Telegram Premium."; +"lng_premium_summary_subtitle_gift_days_me#other" = "You gifted {user} a {count}-months subscription to Telegram Premium."; "lng_premium_summary_subtitle_wallpapers" = "Wallpapers for Both Sides"; "lng_premium_summary_about_wallpapers" = "Set custom wallpapers for you and your chat partner."; "lng_premium_summary_subtitle_stories" = "Stories"; @@ -2267,11 +3087,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies."; "lng_premium_summary_subtitle_effects" = "Message Effects"; "lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages."; +"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats"; +"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list."; +"lng_premium_summary_subtitle_todo_lists" = "Checklists"; +"lng_premium_summary_about_todo_lists" = "Plan, assign, and complete tasks - seamlessly and efficiently."; +"lng_premium_summary_subtitle_peer_colors" = "Name and Profile Colors"; +"lng_premium_summary_about_peer_colors" = "Choose a color and logo for your profile and replies to your messages."; +"lng_premium_summary_subtitle_gifts" = "Telegram Gifts"; +"lng_premium_summary_about_gifts" = "Gifts are collectible items you can trade or showcase on your profile."; +"lng_premium_summary_subtitle_no_forwards" = "Disable Sharing"; +"lng_premium_summary_about_no_forwards" = "Restrict forwarding, copying, and saving content from your private chats."; +"lng_premium_summary_subtitle_ai_compose" = "AI Tools"; +"lng_premium_summary_about_ai_compose" = "Transform your messages and entire chats in your preferred style and language."; "lng_premium_summary_bottom_subtitle" = "About Telegram Premium"; "lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone."; "lng_premium_summary_button" = "Subscribe for {cost} per month"; "lng_premium_summary_new_badge" = "NEW"; +"lng_soon_badge" = "Soon"; "lng_premium_success" = "You've successfully subscribed to Telegram Premium!"; "lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region."; @@ -2338,6 +3171,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_double_limits_subtitle_accounts" = "Connected Accounts"; "lng_premium_double_limits_about_accounts#one" = "Connect {count} account with different mobile numbers"; "lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers"; + +"lng_premium_double_limits_subtitle_similar_channels" = "Similar Channel"; +"lng_premium_double_limits_about_similar_channels#one" = "View up to {count} similar channel"; +"lng_premium_double_limits_about_similar_channels#other" = "View up to {count} similar channels"; // "lng_premium_gift_title" = "Gift Telegram Premium"; @@ -2392,6 +3229,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_title" = "Telegram Stars"; "lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram."; +"lng_credits_currency_summary_title" = "Gram Balance"; +"lng_credits_currency_summary_about" = "Offer Grams to submit post suggestions to channels on Telegram."; +"lng_credits_currency_summary_subtitle" = "You can withdraw your Grams using Fragment."; +"lng_credits_currency_summary_in_button" = "Top-up via Fragment"; +"lng_credits_currency_summary_in_subtitle" = "You can top-up your Grams balance via Fragment."; "lng_credits_summary_options_subtitle" = "Choose package"; "lng_credits_summary_options_credits#one" = "{count} Star"; "lng_credits_summary_options_credits#other" = "{count} Stars"; @@ -2399,12 +3241,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}."; "lng_credits_summary_options_about_link" = "Terms and Conditions"; "lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars"; +"lng_credits_summary_earn_title" = "Earn Stars"; +"lng_credits_summary_earn_about" = "Distribute links to mini apps and earn a share of their revenue in Stars."; "lng_credits_summary_history_tab_full" = "All Transactions"; "lng_credits_summary_history_tab_in" = "Incoming"; "lng_credits_summary_history_tab_out" = "Outgoing"; "lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; "lng_credits_summary_balance" = "Balance"; +"lng_credits_commission" = "{amount} commission"; +"lng_credits_paid_messages_fee_live_reaction" = "Fee for Live Story Reaction"; +"lng_credits_paid_messages_fee#one" = "Fee for {count} Message"; +"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages"; +"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}"; +"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}"; +"lng_credits_paid_messages_full" = "Full Price"; +"lng_credits_premium_gift_duration" = "Duration"; +"lng_credits_more_options" = "More Options"; +"lng_credits_balance_me" = "your balance"; +"lng_credits_balance_me_count" = "Your balance: {emoji} {amount}"; +"lng_credits_buy_button" = "Top Up Balance"; +"lng_credits_topup_button" = "{emoji} Top Up Balance"; +"lng_credits_buy_button_short" = "Top Up"; +"lng_credits_stats_button_short" = "Stats"; +"lng_credits_stats_button" = "View Statistics"; "lng_credits_gift_button" = "Gift Stars to Friends"; +"lng_credits_earn_button" = "Earn Stars from Mini Apps"; "lng_credits_box_out_title" = "Confirm Your Purchase"; "lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?"; "lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?"; @@ -2412,6 +3273,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?"; "lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?"; "lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?"; +"lng_credits_box_out_subscription_bot#one" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** star per month?"; +"lng_credits_box_out_subscription_bot#other" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** stars per month?"; +"lng_credits_box_out_subscription_business#one" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** star per month?"; +"lng_credits_box_out_subscription_business#other" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** stars per month?"; +"lng_credits_box_out_subscription_confirm#one" = "Subscribe for {emoji} {count} / month"; +"lng_credits_box_out_subscription_confirm#other" = "Subscribe for {emoji} {count} / month"; "lng_credits_box_out_photo" = "a photo"; "lng_credits_box_out_photos#one" = "{count} photo"; "lng_credits_box_out_photos#other" = "{count} photos"; @@ -2433,6 +3300,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; "lng_credits_box_history_entry_peer" = "Recipient"; "lng_credits_box_history_entry_peer_in" = "From"; +"lng_credits_box_history_entry_gift_from" = "Gift From"; "lng_credits_box_history_entry_via" = "Via"; "lng_credits_box_history_entry_play_market" = "Play Store"; "lng_credits_box_history_entry_app_store" = "App Store"; @@ -2442,22 +3310,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_giveaway_name" = "Received Prize"; "lng_credits_box_history_entry_gift_sent" = "Sent Gift"; "lng_credits_box_history_entry_gift_converted" = "Converted Gift"; +"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer"; +"lng_credits_box_history_entry_gift_unavailable" = "Unavailable"; +"lng_credits_box_history_entry_gift_released" = "released by {name}"; +"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out"; "lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}"; "lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}"; "lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}"; "lng_credits_box_history_entry_gift_examples" = "Examples"; "lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_premium_bot" = "Stars Top-Up"; +"lng_credits_box_history_entry_currency_in" = "Gram Top-Up"; +"lng_credits_box_history_entry_posts_search" = "Posts Search"; +"lng_credits_box_history_entry_api" = "Paid Broadcast"; +"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message"; +"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages"; +"lng_credits_box_history_entry_floodskip_row" = "Messages"; "lng_credits_box_history_entry_via_premium_bot" = "Premium Bot"; "lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; +"lng_credits_box_history_entry_reason_star_ref" = "Affiliate Program"; +"lng_credits_box_history_entry_affiliate" = "Affiliate"; +"lng_credits_box_history_entry_miniapp" = "Mini App"; +"lng_credits_box_history_entry_referred" = "Referred User"; "lng_credits_box_history_entry_success_date" = "Transaction date"; "lng_credits_box_history_entry_success_url" = "Transaction link"; "lng_credits_box_history_entry_media" = "Media"; +"lng_credits_box_history_entry_message" = "Message"; "lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; "lng_credits_box_history_entry_about_link" = "here"; "lng_credits_box_history_entry_reaction_name" = "Star Reaction"; "lng_credits_box_history_entry_subscription" = "Monthly subscription fee"; +"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade"; +"lng_credits_box_history_entry_gift_sold" = "Gift Sale"; +"lng_credits_box_history_entry_gift_bought" = "Gift Purchase"; +"lng_credits_box_history_entry_gift_sold_to" = "To"; +"lng_credits_box_history_entry_gift_full_price" = "Full Price"; +"lng_credits_box_history_entry_gift_bought_from" = "From"; +"lng_credits_box_history_entry_gift_offer" = "Gift Offer"; "lng_credits_subscription_section" = "My subscriptions"; "lng_credits_box_subscription_title" = "Subscription"; @@ -2465,6 +3355,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_subscriber_subtitle" = "appx. {total} per month"; "lng_credits_subscription_row_to" = "Subscription"; +"lng_credits_subscription_row_to_bot" = "Bot"; +"lng_credits_subscription_row_to_business" = "Business"; "lng_credits_subscription_row_from" = "Subscribed"; "lng_credits_subscription_row_next_on" = "Renews"; @@ -2475,20 +3367,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}."; "lng_credits_subscription_off_button" = "Renew Subscription"; +"lng_credits_subscription_off_rejoin_button" = "Subscribe again"; "lng_credits_subscription_off_about" = "You have canceled your subscription."; +"lng_credits_subscription_off_by_bot_about" = "{bot} has canceled your subscription."; "lng_credits_subscription_status_on" = "renews on {date}"; "lng_credits_subscription_status_off" = "expires on {date}"; "lng_credits_subscription_status_none" = "expired on {date}"; "lng_credits_subscription_status_off_right" = "canceled"; "lng_credits_subscription_status_none_right" = "expired"; +"lng_credits_subscription_status_off_by_bot_right" = "canceled\nby bot"; "lng_credits_small_balance_title#one" = "{count} Star Needed"; "lng_credits_small_balance_title#other" = "{count} Stars Needed"; "lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; "lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts."; +"lng_credits_small_balance_video_stream" = "Buy **Stars** to send them to {name} to support their stream."; "lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels."; "lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts."; +"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; +"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages."; +"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}."; +"lng_credits_small_balance_for_offer" = "Buy **Stars** to offer for this gift."; +"lng_credits_small_balance_for_search" = "Buy **Stars** to search through public posts."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_enough" = "You have enough stars at the moment. {link}"; @@ -2579,11 +3480,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_business_limit_reached#one" = "Limit of {count} message reached."; "lng_business_limit_reached#other" = "Limit of {count} messages reached."; -"lng_chatbots_title" = "Chatbots"; -"lng_chatbots_about" = "Add a bot to your account to help you automatically process and respond to the messages you receive. {link}"; -"lng_chatbots_about_link" = "Learn more..."; "lng_chatbots_placeholder" = "Enter bot URL or username"; -"lng_chatbots_add_about" = "Enter the link to the Telegram bot that you want to automatically process your chats."; "lng_chatbots_access_title" = "Chats accessible for the bot"; "lng_chatbots_all_except" = "All 1-to-1 Chats Except..."; "lng_chatbots_selected" = "Only Selected Chats"; @@ -2593,13 +3490,47 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_chatbots_include_button" = "Select Chats"; "lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to."; "lng_chatbots_permissions_title" = "Bot permissions"; +"lng_chatbots_warning_title" = "Warning"; +"lng_chatbots_warning_both_text" = "The bot {bot} will be able to **manage your gifts and stars**, including giving them away to other users."; +"lng_chatbots_warning_gifts_text" = "The bot {bot} will be able to **manage your gifts**, including giving them away to other users."; +"lng_chatbots_warning_stars_text" = "The bot {bot} will be able to **transfer your stars**."; +"lng_chatbots_warning_username_text" = "The bot {bot} will be able to **set and remove usernames** for your account, which may result in the loss of your current username."; + +"lng_chatbots_manage_messages" = "Manage Messages"; +"lng_chatbots_read" = "Read Messages"; "lng_chatbots_reply" = "Reply to Messages"; -"lng_chatbots_reply_about" = "The bot can only reply on your behalf in chats that were active during the last 24h."; -"lng_chatbots_remove" = "Remove Bot"; +"lng_chatbots_mark_as_read" = "Mark Messages as Read"; +"lng_chatbots_delete_sent" = "Delete Sent Messages"; +"lng_chatbots_delete_received" = "Delete Received Messages"; + +"lng_chatbots_manage_profile" = "Manage Profile"; +"lng_chatbots_edit_name" = "Edit Name"; +"lng_chatbots_edit_bio" = "Edit Bio"; +"lng_chatbots_edit_userpic" = "Edit Profile Picture"; +"lng_chatbots_edit_username" = "Edit Username"; + +"lng_chatbots_manage_gifts" = "Manage Gifts and Stars"; +"lng_chatbots_view_gifts" = "View Gifts"; +"lng_chatbots_sell_gifts" = "Sell Gifts"; +"lng_chatbots_gift_settings" = "Change Gift Settings"; +"lng_chatbots_transfer_gifts" = "Transfer and Upgrade Gifts"; +"lng_chatbots_transfer_stars" = "Transfer Stars"; + +"lng_chatbots_manage_stories" = "Manage Stories"; + "lng_chatbots_not_found" = "Chatbot not found."; "lng_chatbots_not_supported" = "This bot doesn't support Telegram Business yet."; "lng_chatbots_add" = "Add"; -"lng_chatbots_info_url" = "https://telegram.org/blog/telegram-business#chatbots-for-business"; +"lng_chatbots_remove_bot" = "Remove Bot"; +"lng_chatbots_leave_without_added_title" = "No Bot Added"; +"lng_chatbots_leave_without_added_text" = "You haven't added a bot to manage your account. Leave anyway?"; +"lng_chatbots_added_success" = "{bot} now manages your account."; + +"lng_chat_automation_title" = "Chat Automation"; +"lng_chat_automation_about" = "Add a bot to answer messages on your behalf."; +"lng_chat_automation_add_about" = "Choose a bot to manage your chats automatically."; +"lng_settings_chat_automation_label" = "Chat automation"; +"lng_settings_chat_automation_off" = "Off"; "lng_chatbot_status_can_reply" = "bot manages this chat"; "lng_chatbot_status_paused" = "bot paused"; "lng_chatbot_status_views" = "bot has access to this chat"; @@ -2750,11 +3681,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack."; "lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack."; +"lng_boost_channel_title_wear" = "Wear Item"; +"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles."; +"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles."; + "lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; "lng_boost_channel_ask_button" = "Copy Link"; -"lng_boost_channel_or" = "or"; -"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; -"lng_boost_channel_gifting_link" = "Get boosts >"; +//"lng_boost_channel_or" = "or"; +//"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; +//"lng_boost_channel_gifting_link" = "Get boosts >"; +"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:"; +//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}"; + +"lng_boost_channel_title_autotranslate" = "Autotranslation of Messages"; +"lng_boost_channel_needs_level_autotranslate#one" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; +"lng_boost_channel_needs_level_autotranslate#other" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; "lng_feature_stories#one" = "**{count}** Story Per Day"; "lng_feature_stories#other" = "**{count}** Stories Per Day"; @@ -2774,6 +3715,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_feature_custom_background_group" = "Custom Group Background"; "lng_feature_custom_emoji_pack" = "Custom Emoji Pack"; "lng_feature_transcribe" = "Voice-to-Text Conversion"; +"lng_feature_autotranslate" = "Autotranslation of Messages"; +"lng_feature_profile_color_channel#one" = "**{count}** Color for Channel Cover"; +"lng_feature_profile_color_channel#other" = "**{count}** Colors for Channel Cover"; +"lng_feature_profile_color_group#one" = "**{count}** Color for Group Cover"; +"lng_feature_profile_color_group#other" = "**{count}** Colors for Group Cover"; +"lng_feature_profile_icon_channel" = "Custom Logo for Channel Cover"; +"lng_feature_profile_icon_group" = "Custom Logo for Group Cover"; + +"lng_edit_topics_enable" = "Enable Topics"; +"lng_edit_topics_about" = "The group chat will be divided into topics created by admins or users."; +"lng_edit_topics_layout" = "Topics layout"; +"lng_edit_topics_layout_about" = "Choose how topics appear for all members."; +"lng_edit_topics_tabs" = "Tabs"; +"lng_edit_topics_list" = "List"; "lng_giveaway_new_title" = "Boosts via Gifts"; "lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; @@ -2988,6 +3943,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_reason_unclaimed" = "Incomplete Giveaway"; "lng_gift_link_reason_chosen" = "You were selected by the channel"; "lng_gift_link_label_date" = "Date"; +"lng_gift_link_label_first_sale" = "First Sale"; +"lng_gift_link_label_last_sale" = "Last Sale"; +"lng_gift_link_label_value" = "Value"; "lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; "lng_gift_link_also_send_link" = "send this link"; "lng_gift_link_use" = "Use Link"; @@ -3008,52 +3966,547 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram."; "lng_gift_until" = "Until"; +"lng_gift_ton_amount#one" = "{count} Gram"; +"lng_gift_ton_amount#other" = "{count} Grams"; + +"lng_gift_premium_title" = "Premium Gift"; +"lng_gift_premium_text#one" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features."; +"lng_gift_premium_text#other" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features."; "lng_gift_premium_or_stars" = "Gift Premium or Stars"; "lng_gift_premium_subtitle" = "Gift Premium"; "lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}"; "lng_gift_premium_features" = "See Features >"; "lng_gift_premium_label" = "Premium"; +"lng_gift_premium_by_stars" = "or {amount}"; "lng_gift_stars_subtitle" = "Gift Stars"; "lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}"; +"lng_gift_stars_about_collectibles" = "Collectible gifts are unique digital items you can exchange or sell. {link}"; "lng_gift_stars_link" = "What are Stars >"; "lng_gift_stars_limited" = "limited"; "lng_gift_stars_sold_out" = "sold out"; +"lng_gift_stars_resale" = "resale"; +"lng_gift_stars_on_sale" = "on sale"; +"lng_gift_on_sale_for" = "On sale for {price}"; +"lng_gift_stars_premium" = "premium"; +"lng_gift_stars_auction" = "auction"; +"lng_gift_stars_auction_join" = "Join"; +"lng_gift_stars_auction_view" = "View"; +"lng_gift_stars_auction_soon" = "soon"; +"lng_gift_stars_auction_upgraded" = "upgraded"; +"lng_gift_stars_your_left#one" = "{count} left"; +"lng_gift_stars_your_left#other" = "{count} left"; +"lng_gift_stars_your_finished" = "none left"; "lng_gift_stars_tabs_all" = "All Gifts"; -"lng_gift_stars_tabs_limited" = "Limited"; +"lng_gift_stars_tabs_my" = "My Gifts"; +"lng_gift_stars_tabs_my_empty" = "You don't have any gifts you can use as a profile cover."; +"lng_gift_stars_tabs_my_empty_next" = "Browse gifts available for purchase {emoji}"; +"lng_gift_stars_tabs_collectibles" = "Collectibles"; "lng_gift_send_title" = "Send a Gift"; "lng_gift_send_message" = "Enter Message"; "lng_gift_send_anonymous" = "Hide My Name"; +"lng_gift_send_pay_with_stars" = "Pay with {amount}"; +"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}"; +"lng_gift_send_stars_balance_link" = "Get More Stars >"; +"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message."; +"lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name."; +"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins."; +"lng_gift_send_unique" = "Make Unique for {price}"; +"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}"; +"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}"; +"lng_gift_send_unique_link" = "Learn More >"; "lng_gift_send_premium_about" = "Only {user} will see your message."; +"lng_gift_send_limited_sold#one" = "{count} sold"; +"lng_gift_send_limited_sold#other" = "{count} sold"; +"lng_gift_send_limited_left#one" = "{count} left"; +"lng_gift_send_limited_left#other" = "{count} left"; "lng_gift_send_button" = "Send a Gift for {cost}"; +"lng_gift_send_button_self" = "Buy a Gift for {cost}"; +"lng_gift_buy_resale_title" = "Buy {name}"; +"lng_gift_buy_resale_button" = "Buy for {cost}"; +"lng_gift_buy_resale_equals" = "Equals to {cost}"; +"lng_gift_buy_resale_only_ton" = "The seller only accepts Grams as payment."; +"lng_gift_buy_resale_pay_stars" = "Pay in Stars"; +"lng_gift_buy_resale_pay_ton" = "Pay in Grams"; +"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?"; +"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?"; +"lng_gift_buy_price_change_title" = "Price change!"; +"lng_gift_buy_price_change_text" = "This gift price was changed and now is {price}. Do you still want to buy?"; "lng_gift_sent_title" = "Gift Sent!"; +"lng_gift_sent_resale_done" = "{user} has been notified about your gift."; +"lng_gift_sent_resale_done_self" = "{gift} is now yours."; "lng_gift_sent_about#one" = "You spent **{count}** Star from your balance."; "lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance."; +"lng_gift_sent_finished#one" = "You've already sent **{count}** of these gifts, and it's the limit."; +"lng_gift_sent_finished#other" = "You've already sent **{count}** of these gifts, and it's the limit."; +"lng_gift_sent_remains#one" = "You can send **{count}** more."; +"lng_gift_sent_remains#other" = "You can send **{count}** more."; "lng_gift_limited_of_one" = "unique"; "lng_gift_limited_of_count" = "1 of {amount}"; +"lng_gift_collectible_tag" = "gift"; +"lng_gift_burned_tag" = "burned"; +"lng_gift_crafted_tag" = "crafted"; +"lng_gift_uncommon_tag" = "uncommon"; +"lng_gift_rare_tag" = "rare"; +"lng_gift_epic_tag" = "epic"; +"lng_gift_legendary_tag" = "legendary"; +"lng_gift_view_unpack" = "Unpack"; "lng_gift_anonymous_hint" = "Only you can see the sender's name."; +"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name."; "lng_gift_hidden_hint" = "This gift is hidden. Only you can see it."; -"lng_gift_visible_hint" = "This gift is visible to visitors of your page."; +"lng_gift_hidden_unique" = "This gift is not displayed on your page."; +"lng_gift_visible_hint" = "This gift is visible on your page."; +"lng_gift_burned_message" = "This gift was burned in crafting."; +"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel."; +"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts."; +"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}"; +"lng_gift_in_blockchain_link_arrow" = "View {arrow}"; +"lng_gift_visible_hide_arrow" = "Hide {arrow}"; +"lng_gift_visible_show_arrow" = "Show {arrow}"; +"lng_gift_show_on_page" = "Display on my Page"; +"lng_gift_show_on_channel" = "Display in channel's Gifts"; "lng_gift_availability" = "Availability"; "lng_gift_from_hidden" = "Hidden User"; +"lng_gift_subtitle_birthdays" = "Birthdays"; +"lng_gift_recipient_search" = "Search people to send a gift to..."; +"lng_gift_list_birthday_status_today" = "{emoji} Birthday today"; +"lng_gift_list_birthday_status_yesterday" = "Birthday yesterday"; +"lng_gift_list_birthday_status_tomorrow" = "Birthday tomorrow"; +"lng_gift_self_status" = "buy yourself a gift"; +"lng_gift_self_title" = "Buy a Gift"; +"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later."; +"lng_gift_channel_title" = "Send a Gift"; +"lng_gift_channel_about" = "Select a gift to show appreciation for {name}."; +"lng_gift_released_by" = "released by {name}"; +"lng_gift_unique_owner" = "Owner"; +"lng_gift_unique_address_copied" = "Address copied to clipboard."; +"lng_gift_unique_telegram" = "Telegram"; +"lng_gift_unique_status" = "Status"; +"lng_gift_unique_status_non" = "Non-Unique"; +"lng_gift_unique_upgrade" = "Upgrade"; +"lng_gift_unique_upgrade_next" = "Upgrade Next Gift"; +"lng_gift_unique_gift_upgrade" = "Gift an Upgrade"; +"lng_gift_unique_number" = "Collectible #{index}"; +"lng_gift_unique_number_by" = "Collectible #{index} by {name}"; +"lng_gift_unique_model" = "Model"; +"lng_gift_unique_backdrop" = "Backdrop"; +"lng_gift_unique_symbol" = "Symbol"; +"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute."; +"lng_gift_unique_sender" = "{from} sent you this gift on {date}"; +"lng_gift_unique_sender_you" = "You bought this gift on {date}"; +"lng_gift_unique_crafter_you" = "You crafted this gift on {date}"; +"lng_gift_unique_availability_label" = "Quantity"; +"lng_gift_unique_availability#one" = "{count} of {amount} issued"; +"lng_gift_unique_availability#other" = "{count} of {amount} issued"; +"lng_gift_unique_value" = "Value"; +"lng_gift_unique_value_learn_more" = "learn more"; +"lng_gift_unique_info" = "Gifted to {recipient} on {date}."; +"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}."; +"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\"."; +"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}."; +"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\"."; +"lng_gift_unique_info_remove_title" = "Remove Description"; +"lng_gift_unique_info_remove_text" = "Do you want to permanently remove this description from your gift?"; +"lng_gift_unique_info_remove_confirm" = "Remove for {cost}"; +"lng_gift_unique_info_removed" = "Removed {name}'s Description!"; "lng_gift_availability_left#one" = "{count} of {amount} left"; "lng_gift_availability_left#other" = "{count} of {amount} left"; "lng_gift_availability_none" = "None of {amount} left"; -"lng_gift_display_on_page" = "Display on my Page"; -"lng_gift_display_on_page_hide" = "Hide from my Page"; +"lng_gift_value_about_average" = "This is the average sale price of {gift} gifts on Telegram and Fragment over the past month."; +"lng_gift_value_about_last" = "This is the price at which {gift} was last sold on {platform}."; +"lng_gift_value_initial_sale" = "Initial Sale"; +"lng_gift_value_initial_price" = "Initial Price"; +"lng_gift_value_initial_price_value" = "{stars} ({amount})"; +"lng_gift_value_last_sale" = "Last Sale"; +"lng_gift_value_last_price" = "Last Price"; +"lng_gift_value_minimum_price" = "Minimum Price"; +"lng_gift_value_minimum_price_tooltip" = "{amount} is the floor price for {gift} gifts listed on Telegram and Fragment."; +"lng_gift_vlaue_average_price" = "Average Price"; +"lng_gift_value_average_price_tooltip" = "{amount} is the average sale price of {gift} gifts on Telegram and Fragment over the past month."; +"lng_gift_value_availability#one" = "{count} {emoji} for sale on {platform} {arrow}"; +"lng_gift_value_availability#other" = "{count} {emoji} for sale on {platform} {arrow}"; +"lng_gift_value_telegram" = "Telegram"; +"lng_gift_value_fragment" = "Fragment"; "lng_gift_convert_to_stars#one" = "Convert to {count} Star"; "lng_gift_convert_to_stars#other" = "Convert to {count} Stars"; "lng_gift_convert_sure_title" = "Convert Gift to Stars"; -"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone."; -"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone."; +"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?"; +"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?"; +"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?"; +"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?"; +"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**."; +"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**."; +"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift."; "lng_gift_convert_sure" = "Convert"; "lng_gift_display_done" = "The gift is now shown on your profile page."; +"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts."; "lng_gift_display_done_hide" = "The gift is now hidden from your profile page."; +"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts."; +"lng_gift_pinned_done_title" = "{gift} pinned"; +"lng_gift_pinned_done" = "The gift will always be shown on top."; +"lng_gift_pinned_done_replaced" = "replacing {gift}"; "lng_gift_got_stars#one" = "You got **{count} Star** for this gift."; "lng_gift_got_stars#other" = "You got **{count} Stars** for this gift."; +"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift."; +"lng_gift_channel_got#other" = "Channel got **{count} Stars** for this gift."; "lng_gift_sold_out_title" = "Sold Out!"; "lng_gift_sold_out_text#one" = "All {count} gift was already sold."; "lng_gift_sold_out_text#other" = "All {count} gifts were already sold."; +"lng_gift_send_small" = "send a gift"; +"lng_gift_sell_small#one" = "sell for {count} Star"; +"lng_gift_sell_small#other" = "sell for {count} Stars"; +"lng_gift_upgrade_title" = "Upgrade Gift"; +"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction."; +"lng_gift_upgrade_view_all" = "{emoji} View all variants {arrow}"; +"lng_gift_upgrade_preview_title" = "Make Unique"; +"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible."; +"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible."; +"lng_gift_upgrade_unique_title" = "Unique"; +"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift."; +"lng_gift_upgrade_unique_about_user" = "{name} will get a unique number, model, backdrop and symbol for your gift."; +"lng_gift_upgrade_unique_about_channel" = "Admins of {name} will get a unique number, model, backdrop and symbol for your gift."; +"lng_gift_upgrade_transferable_title" = "Transferable"; +"lng_gift_upgrade_transferable_about" = "Send your upgraded gift to any of your friends on Telegram."; +"lng_gift_upgrade_transferable_about_user" = "{name} will be able to send the gift to anyone on Telegram."; +"lng_gift_upgrade_transferable_about_channel" = "Admins of {name} will be able to send the gift to anyone on Telegram."; +"lng_gift_upgrade_tradable_title" = "Tradable"; +"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces."; +"lng_gift_upgrade_tradable_about_user" = "{name} will be able to sell the gift on Telegram and NFT marketplaces."; +"lng_gift_upgrade_tradable_about_channel" = "Admins of {name} will be able to sell the gift on Telegram and NFT marketplaces."; +"lng_gift_upgrade_wearable_title" = "Wearable"; +"lng_gift_upgrade_wearable_about" = "Display gifts on your page and set them as profile covers or statuses."; +"lng_gift_upgrade_button" = "Upgrade for {price}"; +"lng_gift_upgrade_decreases" = "Price decreases in {time}"; +"lng_gift_upgrade_see_table" = "See how this price will decrease {arrow}"; +"lng_gift_upgrade_prices_about" = "Upgrade cost drops every minute."; +"lng_gift_upgrade_prices_title" = "Upgrade Cost"; +"lng_gift_upgrade_prices_subtitle" = "Users who upgrade their gifts first get collectibles with shorter numbers."; +"lng_gift_upgrade_free" = "Upgrade for Free"; +"lng_gift_upgrade_confirm" = "Confirm"; +"lng_gift_upgrade_add_my" = "Add my name to the gift"; +"lng_gift_upgrade_add_my_comment" = "Add my name and comment"; +"lng_gift_upgrade_add_sender" = "Add sender's name to the gift"; +"lng_gift_upgrade_add_comment" = "Add sender's name and comment"; +"lng_gift_upgraded_title" = "Gift Upgraded"; +"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others"; +"lng_gift_upgrade_gifted_title" = "Upgrade Gifted"; +"lng_gift_upgrade_gifted_about" = "Now {name} can turn your gift into a unique collectible."; +"lng_gift_upgrade_gifted_about_channel" = "Now the admins of {name} can turn your gift into a unique collectible."; +"lng_gift_transferred_title" = "Gift Transferred"; +"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}."; +"lng_gift_transfer_title" = "Transfer {name}"; +"lng_gift_transfer_via_blockchain" = "Send via Blockchain"; +"lng_gift_transfer_password_title" = "Two-step verification"; +"lng_gift_transfer_password_description" = "Please enter your password to transfer."; +"lng_gift_transfer_password_about" = "You can withdraw only if you have:"; +"lng_gift_transfer_confirm_title" = "Manage with Fragment"; +"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment."; +"lng_gift_transfer_confirm_button" = "Open Fragment"; +"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day"; +"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days"; +"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour"; +"lng_gift_transfer_unlocks_hours#other" = "unlocks in {count} hours"; +"lng_gift_transfer_unlocks_title" = "Unlocking in progress"; +"lng_gift_transfer_unlocks_about" = "{when}, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction."; +"lng_gift_transfer_unlocks_when_days#one" = "In {count} day"; +"lng_gift_transfer_unlocks_when_days#other" = "In {count} days"; +"lng_gift_transfer_unlocks_when_hours#one" = "In {count} hour"; +"lng_gift_transfer_unlocks_when_hours#other" = "In {count} hours"; +"lng_gift_transfer_unlocks_update_title" = "Update required"; +"lng_gift_transfer_unlocks_update_about" = "Please update your Telegram application to the latest version."; +"lng_gift_transfer_sure" = "Do you want to transfer ownership of {name} to {recipient}?"; +"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?"; +"lng_gift_transfer_button" = "Transfer"; +"lng_gift_transfer_button_for" = "Transfer for {price}"; +"lng_gift_transfer_set_theme" = "Set as Theme in..."; +"lng_gift_transfer_choose" = "Choose Chat"; +"lng_gift_transfer_wear" = "Wear"; +"lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_transfer_sell" = "Sell"; +"lng_gift_transfer_update" = "Change Price"; +"lng_gift_transfer_unlist" = "Unlist"; +"lng_gift_transfer_locked_title" = "Action Locked"; +"lng_gift_transfer_locked_text" = "Transfer this gift to your Telegram account on Fragment to unlock this action."; +"lng_gift_offer_button" = "Offer to Buy"; +"lng_gift_offer_title" = "Offer to Buy"; +"lng_gift_offer_stars_about" = "Choose how many Stars you'd like to offer for {name}."; +"lng_gift_offer_ton_about" = "Choose how many Grams you'd like to offer for {name}."; +"lng_gift_offer_duration" = "Offer Duration"; +"lng_gift_offer_duration_about" = "Choose how long {user} can accept your offer. When the time expires, the amount will be refunded."; +"lng_gift_offer_cost_button" = "Offer {cost}"; +"lng_gift_offer_reject_title" = "Reject Offer"; +"lng_gift_offer_confirm_reject" = "Are you sure you want to reject the offer from {user}?"; +"lng_gift_offer_confirm_accept" = "Do you want to sell {name} to {user} for {cost}?"; +"lng_gift_offer_you_get" = "You will receive {cost} after fees."; +"lng_gift_offer_higher" = "The price you are offered is {percent} higher than the average price for {name}."; +"lng_gift_offer_lower" = "The price you are offered is {percent} lower than the average price for {name}."; +"lng_gift_offer_sell_for" = "Sell for {price}"; +"lng_gift_offer_confirm_title" = "Confirm Offer"; +"lng_gift_offer_confirm_text" = "Do you want to offer {cost} to {user} for {name}?"; +"lng_gift_offer_table_offer" = "Offer"; +"lng_gift_offer_table_fee" = "Fee"; +"lng_gift_offer_table_duration" = "Duration"; +"lng_gift_sell_unlist_title" = "Unlist {name}"; +"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?"; +"lng_gift_sell_title" = "Price in Stars"; +"lng_gift_sell_about" = "You will receive {percent} of the selected amount."; +"lng_gift_sell_amount#one" = "You will receive **{count}** Star."; +"lng_gift_sell_amount#other" = "You will receive **{count}** Stars."; +"lng_gift_sell_min_price#one" = "Minimum price is {count} Star."; +"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars."; +"lng_gift_sell_only_ton" = "Only Accept Grams"; +"lng_gift_sell_only_ton_about" = "If the buyer pays you in Grams, there's no risk of refunds, unlike Stars payments."; +"lng_gift_sell_amount_ton#one" = "You will receive **{count}** Gram."; +"lng_gift_sell_amount_ton#other" = "You will receive **{count}** Grams."; +"lng_gift_sell_min_price_ton#one" = "Minimum price is {count} Gram."; +"lng_gift_sell_min_price_ton#other" = "Minimum price is {count} Grams."; +"lng_gift_sell_title_ton" = "Price in Grams"; +"lng_gift_sell_put" = "Put for Sale"; +"lng_gift_sell_update" = "Update the Price"; +"lng_gift_sell_toast" = "{name} is now for sale!"; +"lng_gift_sell_updated" = "Sale price for {name} was updated."; +"lng_gift_sell_removed" = "{name} is removed from sale."; +"lng_gift_menu_show" = "Show"; +"lng_gift_menu_hide" = "Hide"; +"lng_gift_wear_title" = "Wear {name}"; +"lng_gift_wear_about" = "and get these benefits:"; +"lng_gift_wear_badge_title" = "Radiant Badge"; +"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name."; +"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name."; +"lng_gift_wear_design_title" = "Unique Profile Design"; +"lng_gift_wear_design_about" = "Your profile page will get the color and the symbol of this item."; +"lng_gift_wear_design_about_channel" = "Your channel page will get the color and the symbol of this item."; +"lng_gift_wear_proof_title" = "Proof of Ownership"; +"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner."; +"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner."; +"lng_gift_wear_start" = "Start Wearing"; +"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles."; +"lng_gift_wear_start_toast" = "You put on {name}"; +"lng_gift_wear_end_toast" = "You took off {name}"; +"lng_gift_many_pinned_title" = "Too Many Pinned Gifts"; +"lng_gift_many_pinned_choose" = "Select a gift to unpin below"; +"lng_gift_resale_price" = "Price"; +"lng_gift_resale_number" = "Number"; +"lng_gift_resale_date" = "Date"; +"lng_gift_resale_count#one" = "{count} gift in resale"; +"lng_gift_resale_count#other" = "{count} gifts in resale"; +"lng_gift_resale_count_none" = "No listings"; +"lng_gift_resale_sort_price" = "Sort by Price"; +"lng_gift_resale_sort_date" = "Sort by Date"; +"lng_gift_resale_sort_number" = "Sort by Number"; +"lng_gift_resale_all_listings" = "All Listings"; +"lng_gift_resale_stars_only" = "For Stars Only"; +"lng_gift_resale_filter_all" = "Select All"; +"lng_gift_resale_model" = "Model"; +"lng_gift_resale_models#one" = "{count} Model"; +"lng_gift_resale_models#other" = "{count} Models"; +"lng_gift_resale_backdrop" = "Backdrop"; +"lng_gift_resale_backdrops#one" = "{count} Backdrop"; +"lng_gift_resale_backdrops#other" = "{count} Backdrops"; +"lng_gift_resale_symbol" = "Symbol"; +"lng_gift_resale_symbols#one" = "{count} Symbol"; +"lng_gift_resale_symbols#other" = "{count} Symbols"; +"lng_gift_resale_search_none" = "No gifts found for your search."; +"lng_gift_resale_early" = "You will be able to resell this gift in {duration}."; +"lng_gift_transfer_early" = "You will be able to transfer this gift in {duration}."; +"lng_gift_resale_transfer_early_title" = "Try Later"; +"lng_gift_collection_add" = "Add Collection"; +"lng_gift_collection_new_title" = "Create a New Collection"; +"lng_gift_collection_new_button" = "New Collection"; +"lng_gift_collection_new_text" = "Choose a name for your collection and start adding your gifts there."; +"lng_gift_collection_new_ph" = "Title"; +"lng_gift_collection_new_create" = "Create"; +"lng_gift_collection_empty_title" = "Organize Your Gifts"; +"lng_gift_collection_empty_text" = "Add some of your gifts to this collection."; +"lng_gift_collection_all" = "All Gifts"; +"lng_gift_collection_add_title" = "Add Gifts"; +"lng_gift_collection_add_button" = "Add Gifts"; +"lng_gift_collection_share" = "Share Collection"; +"lng_gift_collection_edit" = "Edit Name"; +"lng_gift_collection_limit_title" = "Limit Reached"; +"lng_gift_collection_limit_text" = "Please remove one of the existing collections to add a new one."; +"lng_gift_collection_delete" = "Delete Collection"; +"lng_gift_collection_delete_sure" = "Are you sure you want to delete this collection?"; +"lng_gift_collection_delete_button" = "Delete"; +"lng_gift_collection_add_to" = "Add to Collection"; +"lng_gift_collection_reorder" = "Reorder"; +"lng_gift_collection_reorder_exit" = "Apply Reorder"; +"lng_gift_collection_remove_from" = "Remove from Collection"; +"lng_gift_locked_title" = "Gift Locked"; +"lng_gift_craft_menu_button" = "Craft"; +"lng_gift_craft_info_title" = "Gift Crafting"; +"lng_gift_craft_info_about" = "Turn your gifts into rare, epic\nand legendary versions."; +"lng_gift_craft_rare_title" = "Get Rare Models"; +"lng_gift_craft_rare_about" = "Select up to 4 gifts to craft a new exclusive model."; +"lng_gift_craft_chance_title" = "Maximize Chances"; +"lng_gift_craft_chance_about" = "Combine more gifts to increase your odds of success."; +"lng_gift_craft_affect_title" = "Affect the Result"; +"lng_gift_craft_affect_about" = "Use gifts with the same attribute to boost its chance."; +"lng_gift_craft_start_button" = "Start Crafting"; +"lng_gift_craft_title" = "Craft Gift"; +"lng_gift_craft_about1" = "Add up to **4** gifts to craft new\n{gift}."; +"lng_gift_craft_about2" = "If crafting fails, all used gifts\nwill be lost."; +"lng_gift_craft_view_all" = "{emoji} View all craftable models {arrow}"; +"lng_gift_craft_chance_backdrop" = "{percent} chance the crafted gift will have **{name}** background."; +"lng_gift_craft_chance_symbol" = "{percent} chance the crafted gift will have **{name}** symbol."; +"lng_gift_craft_button" = "Craft {gift}"; +"lng_gift_craft_button_chance" = "{percent} Success Chance"; +"lng_gift_craft_select_title" = "Select Gifts"; +"lng_gift_craft_select_about" = "You need to add up to **3 gifts** from the same collection for crafting."; +"lng_gift_craft_select_your" = "Your gifts"; +"lng_gift_craft_select_none" = "You don't have other gifts from this collection."; +"lng_gift_craft_search_none" = "Could not find other gifts from this collection."; +"lng_gift_craft_select_market#one" = "{count} suitable gift on sale"; +"lng_gift_craft_select_market#other" = "{count} suitable gifts on sale"; +"lng_gift_craft_progress" = "Crafting"; +"lng_gift_craft_progress_about" = "Crafting **{gift}**"; +"lng_gift_craft_progress_chance" = "{percent} success chance"; +"lng_gift_craft_progress_fails" = "If crafting fails, all used\ngifts will be lost."; +"lng_gift_craft_failed" = "Crafting failed. Gifts consumed :("; +"lng_gift_craft_failed_title" = "Crafting Failed"; +"lng_gift_craft_failed_about#one" = "This crafting attempt was unsuccessful.\n**{count}** gift was lost."; +"lng_gift_craft_failed_about#other" = "This crafting attempt was unsuccessful.\n**{count}** gifts were lost."; +"lng_gift_craft_new_button" = "Craft New Gift"; +"lng_gift_craft_another_button" = "Craft Another Gift"; +"lng_gift_craft_unavailable" = "Crafting unavailable"; +"lng_gift_craft_when" = "This gift will become available for crafting on {date} at {time}."; +"lng_gift_craft_address_error" = "This gift has been exported to the blockchain and can't be used as the primary gift for crafting."; + +"lng_auction_about_title" = "Auction"; +"lng_auction_about_subtitle" = "Join the battle for exclusive gifts."; +"lng_auction_about_top_title#one" = "Top {count} Bidder"; +"lng_auction_about_top_title#other" = "Top {count} Bidders"; +"lng_auction_about_top_rounds#one" = "{count} round"; +"lng_auction_about_top_rounds#other" = "{count} rounds"; +"lng_auction_about_top_bidders#one" = "top {count} bidder"; +"lng_auction_about_top_bidders#other" = "top {count} bidders"; +"lng_auction_about_top_about#one" = "{count} gift is dropped in {rounds} to the {bidders} by bid amount."; +"lng_auction_about_top_about#other" = "{count} gifts are dropped in {rounds} to the {bidders} by bid amount."; +"lng_auction_about_top_short#one" = "{count} gift is dropped to the {bidders} by bid amount. {link}"; +"lng_auction_about_top_short#other" = "{count} gifts are dropped to the {bidders} by bid amount. {link}"; +"lng_auction_about_bid_title" = "Bid Carryover"; +"lng_auction_about_bid_about#one" = "If your bid leaves the top {count}, it will automatically join the next round."; +"lng_auction_about_bid_about#other" = "If your bid leaves the top {count}, it will automatically join the next round."; +"lng_auction_about_missed_title" = "Missed Bidders"; +"lng_auction_about_missed_about" = "If your bid doesn't win after the final round, your Stars will be fully refunded."; +"lng_auction_about_understood" = "Understood"; +"lng_auction_text#one" = "Top **{count}** bidder will get {name} gifts this round. {link}"; +"lng_auction_text#other" = "Top **{count}** bidders will get {name} gifts this round. {link}"; +"lng_auction_text_link" = "Learn more {arrow}"; +"lng_auction_text_ended" = "Auction ended."; +"lng_auction_start_label" = "Started"; +"lng_auction_starts_label" = "Starts"; +"lng_auction_rounds_label" = "Rounds"; +"lng_auction_rounds_exact" = "Round {n}"; +"lng_auction_rounds_range" = "Rounds {n}-{last}"; +"lng_auction_rounds_seconds#one" = "{count} second each"; +"lng_auction_rounds_seconds#other" = "{count} seconds each"; +"lng_auction_rounds_minutes#one" = "{count} minute each"; +"lng_auction_rounds_minutes#other" = "{count} minutes each"; +"lng_auction_rounds_hours#one" = "{count} hour each"; +"lng_auction_rounds_hours#other" = "{count} hours each"; +"lng_auction_rounds_extended" = "{duration} + {increase} for late bids in top {n}"; +"lng_auction_end_label" = "Ends"; +"lng_auction_round_label" = "Current Round"; +"lng_auction_round_value" = "{n} of {amount}"; +"lng_auction_average_label" = "Average Price"; +"lng_auction_average_tooltip" = "{amount} is the average sale price for {gift} gifts."; +"lng_auction_availability_label" = "Availability"; +"lng_auction_availability_value" = "{n} of {amount} left"; +"lng_auction_bought#one" = "{count} {emoji} item bought {arrow}"; +"lng_auction_bought#other" = "{count} {emoji} items bought {arrow}"; +"lng_auction_join_button" = "Join Auction"; +"lng_auction_join_bid" = "Place a Bid"; +"lng_auction_join_time_left" = "{time} left"; +"lng_auction_join_time_medium" = "{hours} h {minutes} m"; +"lng_auction_join_time_small" = "{minutes} m"; +"lng_auction_join_starts_in" = "starts in {time}"; +"lng_auction_join_early_bid" = "Place an Early Bid"; +"lng_auction_menu_about" = "About"; +"lng_auction_menu_copy_link" = "Copy Link"; +"lng_auction_menu_share" = "Share"; +"lng_auction_bid_title" = "Place a Bid"; +"lng_auction_bid_title_early" = "Place an Early Bid"; +"lng_auction_bid_subtitle#one" = "Top {count} bidder will win"; +"lng_auction_bid_subtitle#other" = "Top {count} bidders will win"; +"lng_auction_bid_your" = "your bid"; +"lng_auction_bid_custom" = "click to bid more"; +"lng_auction_bid_threshold#one" = "TOP {count}"; +"lng_auction_bid_threshold#other" = "TOP {count}"; +"lng_auction_bid_minimal#one" = "minimum bid"; +"lng_auction_bid_minimal#other" = "minimum bid"; +"lng_auction_bid_until" = "until next round"; +"lng_auction_bid_left#one" = "left"; +"lng_auction_bid_left#other" = "left"; +"lng_auction_bid_before_start" = "before start"; +"lng_auction_bid_your_title" = "Your bid will be"; +"lng_auction_bid_your_outbid" = "You've been outbid"; +"lng_auction_bid_your_winning" = "You're winning"; +"lng_auction_bid_winners_title" = "Top winners"; +"lng_auction_bid_place" = "Place a {stars} Bid"; +"lng_auction_bid_increase" = "Add {stars} to Your Bid"; +"lng_auction_bid_placed_title" = "Your bid has been placed"; +"lng_auction_bid_increased_title" = "Your bid has been increased"; +"lng_auction_bid_done_text#one" = "If you fall below the **top {count}**, your bid will roll over to the next round."; +"lng_auction_bid_done_text#other" = "If you fall below the **top {count}**, your bid will roll over to the next round."; +"lng_auction_bid_custom_title" = "Custom Amount"; +"lng_auction_preview_left#one" = "{count} left"; +"lng_auction_preview_left#other" = "{count} left"; +"lng_auction_preview_join" = "Join"; +"lng_auctino_preview_finished" = "Finished"; +"lng_auction_preview_sold_out" = "Sold Out"; +"lng_auction_preview_view_results" = "View Results"; +"lng_auction_bar_active" = "Active Auction"; +"lng_auction_bar_active_many#one" = "{count} Active Auction"; +"lng_auction_bar_active_many#other" = "{count} Active Auctions"; +"lng_auction_bar_winning#one" = "You're winning ({count}st place)."; +"lng_auction_bar_winning#other" = "You're winning ({count}th place)."; +"lng_auction_bar_outbid" = "You've been outbid."; +"lng_auction_bar_winning_all" = "You're winning in all of them."; +"lng_auction_bar_outbid_some#one" = "You've been outbid in {count} of them."; +"lng_auction_bar_outbid_some#other" = "You've been outbid in {count} of them."; +"lng_auction_bar_outbid_all" = "You've been outbid in all of them."; +"lng_auction_bar_view" = "View"; +"lng_auction_bar_round" = "Round {n} of {amount}"; +"lng_auction_bar_bid_ranked" = "Your bid **{stars}** is ranked **#{n}**"; +"lng_auction_bar_bid_outbid" = "Your bid **{stars}** is outbid"; +"lng_auction_bar_raise_bid" = "Raise Bid"; +"lng_auction_bought_title#one" = "{count} Item Bought"; +"lng_auction_bought_title#other" = "{count} Items Bought"; +"lng_auction_bought_date" = "Date"; +"lng_auction_bought_bid" = "Accepted Bid"; +"lng_auction_bought_in_round" = "{name} in round {n}"; +"lng_auction_change_title" = "Change Recipient"; +"lng_auction_change_button" = "Change"; +"lng_auction_change_already" = "You've already placed a bid on this gift for {name}."; +"lng_auction_change_to" = "Do you want to raise your bid and change the recipient to {name}?"; +"lng_auction_change_already_me" = "You've already placed a bid on this gift for yourself."; +"lng_auction_change_to_me" = "Do you want to raise your bid and change the recipient to yourself?"; +"lng_auction_preview_name" = "Upcoming Auction"; +"lng_auction_preview_learn_gifts" = "Learn more about Telegram Gifts {arrow}"; +"lng_auction_preview_variants#one" = "View {emoji} {count} Variant {arrow}"; +"lng_auction_preview_variants#other" = "View {emoji} {count} Variants {arrow}"; +"lng_auction_preview_random" = "Random Traits"; +"lng_auction_preview_selected" = "Selected Traits"; +"lng_auction_preview_randomize" = "Randomize Traits"; +"lng_auction_preview_model" = "model"; +"lng_auction_preview_models#one" = "This collectible features **{count}** unique model."; +"lng_auction_preview_models#other" = "This collectible features **{count}** unique models."; +"lng_auction_preview_models_button" = "Models"; +"lng_auction_preview_crafted#one" = "This collectible features **{count}** crafted model."; +"lng_auction_preview_crafted#other" = "This collectible features **{count}** crafted models."; +"lng_auction_preview_view_craftable" = "View craftable models {arrow}"; +"lng_auction_preview_view_primary" = "View primary models {arrow}"; +"lng_auction_preview_backdrop" = "backdrop"; +"lng_auction_preview_backdrops#one" = "This collectible features **{count}** unique backdrop."; +"lng_auction_preview_backdrops#other" = "This collectible features **{count}** unique backdrops."; +"lng_auction_preview_backdrops_button" = "Backdrops"; +"lng_auction_preview_symbol" = "symbol"; +"lng_auction_preview_symbols#one" = "This collectible features **{count}** unique symbol."; +"lng_auction_preview_symbols#other" = "This collectible features **{count}** unique symbols."; +"lng_auction_preview_symbols_button" = "Symbols"; +"lng_auction_preview_wear" = "More about wearing gifts {arrow}"; +"lng_auction_preview_free_upgrade" = "You can upgrade your gift for free, sell it on the market, or set it as your profile cover."; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; @@ -3084,6 +4537,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_inline_bot_no_results" = "No results."; "lng_inline_bot_via" = "via {inline_bot}"; +"lng_guest_chat_for" = "for {user}"; "lng_box_remove" = "Remove"; @@ -3111,6 +4565,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_masks_count#other" = "{count} masks"; "lng_custom_emoji_count#one" = "{count} emoji"; "lng_custom_emoji_count#other" = "{count} emoji"; +"lng_search_back_to_results" = "Back to search"; +"lng_search_results_header" = "Search Result"; "lng_stickers_attached_sets" = "Sets of attached stickers"; "lng_custom_emoji_used_sets" = "Sets of used emoji"; "lng_custom_emoji_remove_pack_button" = "Remove Emoji"; @@ -3126,6 +4582,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_emoji_remove_group_set" = "Remove group emoji set?"; "lng_emoji_group_from_your" = "Choose from your emoji"; "lng_emoji_group_from_featured" = "Choose from trending emoji"; +"lng_emoji_group_badge" = "Group Emoji"; "lng_masks_archive_pack" = "Archive Masks"; "lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_installed" = "Mask pack has been installed."; @@ -3134,16 +4591,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_context_edit_name" = "Edit name"; "lng_stickers_context_delete" = "Delete sticker"; "lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?"; +"lng_emoji_context_delete" = "Delete emoji"; +"lng_emoji_context_delete_sure" = "Are you sure you want to delete this emoji from your set?"; +"lng_stickers_context_delete_pack" = "Delete Pack"; +"lng_stickers_context_delete_pack_everyone" = "Delete for Everyone"; +"lng_stickers_context_delete_pack_self" = "Delete for Myself"; +"lng_stickers_delete_pack_sure" = "Are you sure you want to delete this sticker set for everyone? This cannot be undone."; +"lng_stickers_bot_more_options" = "Check the {bot} bot for more options"; "lng_stickers_box_edit_name_title" = "Edit Sticker Set Name"; "lng_stickers_box_edit_name_about" = "Choose a name for your set."; "lng_stickers_creator_badge" = "edit"; +"lng_stickers_create_new" = "Create a New Sticker"; +"lng_stickers_add_existing" = "Add an Existing Sticker"; +"lng_emoji_create_new" = "Create a New Emoji"; +"lng_emoji_add_existing" = "Add an Existing Emoji"; +"lng_emoji_adapt_sticker" = "Adapt from a Sticker"; +"lng_emoji_adapt_no_video" = "Video stickers are not supported yet."; +"lng_stickers_add_to_set" = "Add to Sticker Set"; +"lng_stickers_already_in_set" = "This Sticker is already in the Set."; +"lng_stickers_set_is_full" = "This Sticker Set is full."; +"lng_emoji_add_to_set" = "Add to Emoji Set"; +"lng_emoji_already_in_set" = "This Emoji is already in the Set."; +"lng_emoji_set_is_full" = "This Emoji Set is full."; +"lng_emoji_added" = "Emoji added."; +"lng_emoji_create_image_title" = "New Emoji"; +"lng_emoji_create_emoji_about" = "Choose emojis that match your emoji"; +"lng_stickers_pack_choose_emoji_title" = "Choose Emoji"; +"lng_stickers_pack_choose_emoji_about" = "Pick an emoji that corresponds to this sticker."; +"lng_stickers_pick_existing_title" = "Choose Sticker"; +"lng_stickers_pick_existing_about" = "Pick a sticker from your library to add it to this set."; +"lng_stickers_pick_existing_empty" = "You don't have any stickers yet."; +"lng_stickers_create_image_title" = "New Sticker"; +"lng_stickers_create_image_about" = "Choose an image to add as a sticker."; +"lng_stickers_create_choose_image" = "Choose Image"; +"lng_stickers_create_image_filter" = "Images"; +"lng_stickers_create_open_failed" = "Could not load image."; +"lng_stickers_create_too_small" = "The image must be at least {size} pixels on each side."; +"lng_stickers_create_choose_emoji" = "Choose an emoji that corresponds to your sticker:"; +"lng_stickers_create_emoji_about" = "Choose emojis that match your sticker"; +"lng_stickers_create_emoji_required" = "Please choose an emoji."; +"lng_stickers_create_uploading" = "Uploading sticker…"; +"lng_stickers_create_upload_failed" = "Sticker upload failed. Please try again."; +"lng_stickers_create_added" = "Sticker added."; + "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_album" = "Album"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audio file"; "lng_in_dlg_contact" = "Contact"; "lng_in_dlg_audio" = "Voice message"; +"lng_in_dlg_audio_unread" = "{emoji} Voice message"; "lng_in_dlg_video_message" = "Video message"; "lng_in_dlg_voice_message_ttl" = "One-time Voice Message"; "lng_in_dlg_video_message_ttl" = "One-time Video Message"; @@ -3152,6 +4650,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_in_dlg_poll" = "Poll"; "lng_in_dlg_story" = "Story"; +"lng_in_dlg_todo_list" = "Checklist"; "lng_in_dlg_story_expired" = "Expired story"; "lng_in_dlg_media_count#one" = "{count} media"; "lng_in_dlg_media_count#other" = "{count} media"; @@ -3165,10 +4664,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_in_dlg_audio_count#other" = "{count} audio"; "lng_ban_user" = "Ban User"; +"lng_ban_specific_user" = "Ban {user}"; "lng_ban_users" = "Ban users"; "lng_restrict_users" = "Restrict users"; "lng_delete_all_from_user" = "Delete all from {user}"; -"lng_delete_all_from_users" = "Delete all from users"; +"lng_restrict_user#one" = "Restrict user"; +"lng_restrict_user#other" = "Restrict users"; "lng_restrict_user_part" = "Partially restrict this user {emoji}"; "lng_restrict_users_part" = "Partially restrict users {emoji}"; "lng_restrict_user_full" = "Fully ban this user {emoji}"; @@ -3176,6 +4677,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restrict_users_part_single_header" = "What can this user do?"; "lng_restrict_users_part_header#one" = "What can {count} selected user do?"; "lng_restrict_users_part_header#other" = "What can {count} selected users do?"; +"lng_restrict_users_kick_from_common_group" = "Also ban from:"; "lng_report_spam" = "Report Spam"; "lng_report_spam_and_leave" = "Report spam and leave"; "lng_report_spam_done" = "Thank you for your report"; @@ -3195,6 +4697,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join."; "lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}."; "lng_new_contact_about_status_link" = "Telegram Premium"; +"lng_new_contact_not_contact" = "Not a contact"; +"lng_new_contact_phone_number" = "Phone number"; +"lng_new_contact_registration" = "Registration"; +"lng_new_contact_common_groups" = "Common groups"; +"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}"; +"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}"; +"lng_new_contact_not_official" = "Not an official account"; +"lng_new_contact_updated_name" = "User updated name {when}"; +"lng_new_contact_updated_photo" = "User updated photo {when}"; +"lng_new_contact_updated_now" = "less than an hour ago"; +"lng_new_contact_updated_hours#one" = "{count} hour ago"; +"lng_new_contact_updated_hours#other" = "{count} hours ago"; +"lng_new_contact_updated_days#one" = "{count} day ago"; +"lng_new_contact_updated_days#other" = "{count} days ago"; +"lng_new_contact_updated_months#one" = "{count} month ago"; +"lng_new_contact_updated_months#other" = "{count} months ago"; "lng_from_request_title_channel" = "Response to your join request"; "lng_from_request_title_group" = "Response to your join request"; "lng_from_request_body" = "You received this message because you requested to join {name} on {date}."; @@ -3216,6 +4734,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reminder_message" = "Set a reminder"; "lng_schedule_title" = "Send this message on..."; "lng_remind_title" = "Remind me on..."; +"lng_schedule_notify_on" = "Recipients will be notified."; +"lng_schedule_notify_off" = "Recipients will receive a silent notification."; +"lng_schedule_repeat_label" = "Repeat:"; +"lng_schedule_repeat_never" = "Never"; +"lng_schedule_repeat_daily" = "Daily"; +"lng_schedule_repeat_weekly" = "Weekly"; +"lng_schedule_repeat_biweekly" = "Biweekly"; +"lng_schedule_repeat_monthly" = "Monthly"; +"lng_schedule_repeat_every_month#one" = "Every {count} month"; +"lng_schedule_repeat_every_month#other" = "Every {count} month"; +"lng_schedule_repeat_yearly" = "Yearly"; +"lng_schedule_repeat_promo" = "Subscribe to {link} to schedule repeating messages."; +"lng_schedule_repeat_promo_link" = "Telegram Premium"; "lng_schedule_at" = "at"; "lng_message_ph" = "Write a message..."; "lng_broadcast_ph" = "Broadcast a message..."; @@ -3223,6 +4754,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_anonymous_ph" = "Send anonymously..."; "lng_story_reply_ph" = "Reply privately..."; "lng_story_comment_ph" = "Comment story..."; +"lng_video_stream_comment_ph" = "Comment"; +"lng_video_stream_comment_paid_ph#one" = "Comment for {count} Star"; +"lng_video_stream_comment_paid_ph#other" = "Comment for {count} Stars"; +"lng_video_stream_comments_disabled" = "Comments disabled."; +"lng_video_stream_stars" = "Add Stars to highlight your comment"; +"lng_video_stream_live" = "LIVE"; +"lng_video_stream_watched#one" = "{count} watching"; +"lng_video_stream_watched#other" = "{count} watching"; +"lng_video_stream_edit_stars" = "Edit Stars"; +"lng_video_stream_remove_stars" = "Remove Stars"; +"lng_message_stars_ph#one" = "Message for {count} Star"; +"lng_message_stars_ph#other" = "Message for {count} Stars"; "lng_send_text_no" = "Text not allowed."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_type_and_last" = "{types} and {last}"; @@ -3243,11 +4786,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_record_cancel" = "Release outside this field to cancel"; "lng_record_cancel_stories" = "Release outside to cancel"; "lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?"; +"lng_record_lock_cancel_sure_round" = "Do you want to stop recording and discard your video message?"; "lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?"; +"lng_record_listen_cancel_sure_round" = "Do you want to discard your recorded video message?"; "lng_record_lock_discard" = "Discard"; +"lng_record_lock" = "Lock recording"; +"lng_record_lock_resume" = "Resume recording"; +"lng_record_lock_delete" = "Delete recording"; +"lng_record_lock_play" = "Play recording"; +"lng_record_lock_pause" = "Pause recording"; +"lng_record_cancel_recording" = "Cancel recording"; "lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message."; +"lng_record_voice_tip" = "Hold to record audio. Click to switch to video."; +"lng_record_video_tip" = "Hold to record video. Click to switch to audio."; +"lng_record_audio_problem" = "Could not start audio recording. Please check your microphone."; +"lng_record_video_problem" = "Could not start video recording. Please check your camera."; "lng_record_once_first_tooltip" = "Click to set this message to **Play Once**."; "lng_record_once_active_tooltip" = "The recipient will be able to listen only once."; +"lng_record_once_active_video" = "The recipient will be able to watch only once."; "lng_will_be_notified" = "Subscribers will be notified when you post."; "lng_wont_be_notified" = "Subscribers will receive a silent notification."; "lng_willbe_history" = "Select a chat to start messaging"; @@ -3276,6 +4832,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_scheduled_send_now" = "Send message now?"; "lng_scheduled_send_now_many#one" = "Send {count} message now?"; "lng_scheduled_send_now_many#other" = "Send {count} messages now?"; +"lng_scheduled_video_tip_title" = "Improving video..."; +"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the best viewing experience."; +"lng_scheduled_video_tip" = "Processing video may take a few minutes."; +"lng_scheduled_video_published" = "Video Published."; +"lng_scheduled_video_view" = "View"; "lng_replies_view#one" = "View {count} Reply"; "lng_replies_view#other" = "View {count} Replies"; @@ -3298,6 +4859,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_replies_no_comments" = "No comments here yet..."; "lng_verification_codes" = "Verification Codes"; +"lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number."; "lng_archived_name" = "Archived chats"; "lng_archived_add" = "Archive"; @@ -3316,8 +4878,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_skip_archive_in_search" = "Skip results from archive"; "lng_dialogs_show_archive_in_search" = "With results from archive"; +"lng_dialogs_suggestions_birthday_title" = "Add your birthday! 🎂"; +"lng_dialogs_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; +"lng_dialogs_suggestions_birthday_contact_title" = "It’s {text}'s **birthday** today! 🎂"; +"lng_dialogs_suggestions_birthday_contact_about" = "Send them a Gift."; +"lng_dialogs_suggestions_birthday_contacts_title#one" = "{count} contact have **birthdays** today! 🎂"; +"lng_dialogs_suggestions_birthday_contacts_title#other" = "{count} contacts have **birthdays** today! 🎂"; +"lng_dialogs_suggestions_birthday_contacts_about" = "Send them a Gift."; +"lng_dialogs_suggestions_birthday_contact_dismiss" = "You can send a Gift later in Settings"; +"lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; +"lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; +"lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; +"lng_dialogs_suggestions_premium_upgrade_about" = "Upgrade to the annual payment plan for Telegram Premium now to get the discount."; +"lng_dialogs_suggestions_premium_restore_title" = "Get Premium back with up to {text} off"; +"lng_dialogs_suggestions_premium_restore_about" = "Your Telegram Premium has recently expired. Tap here to extend it."; +"lng_dialogs_suggestions_premium_grace_title" = "⚠️ Your Premium subscription is expiring!"; +"lng_dialogs_suggestions_premium_grace_about" = "Don’t lose access to exclusive features."; +"lng_dialogs_suggestions_userpics_title" = "Add your photo! 📸"; +"lng_dialogs_suggestions_userpics_about" = "Help your friends spot you easily."; +"lng_dialogs_suggestions_credits_sub_low_title#one" = "{emoji} {count} Star needed for {channels}"; +"lng_dialogs_suggestions_credits_sub_low_title#other" = "{emoji} {count} Stars needed for {channels}"; +"lng_dialogs_suggestions_credits_sub_low_about" = "Insufficient funds to cover your subscription."; + +"lng_unconfirmed_auth_title" = "Someone just got access to your messages!"; +"lng_unconfirmed_auth_confirm" = "Yes, it’s me"; +"lng_unconfirmed_auth_deny" = "No, it’s not me!"; +"lng_unconfirmed_auth_single" = "We detected a new login to your account from {from}, {country}. Is it you?"; +"lng_unconfirmed_auth_multiple#one" = "We detected new {count} login to your account. Is it you?"; +"lng_unconfirmed_auth_multiple#other" = "We detected new {count} logins to your account. Is it you?"; +"lng_unconfirmed_auth_multiple_from#one" = "We detected new {count} login to your account from {country}. Is it you?"; +"lng_unconfirmed_auth_multiple_from#other" = "We detected new {count} logins to your account from {country}. Is it you?"; +"lng_unconfirmed_auth_denied_title#one" = "New Login Prevented"; +"lng_unconfirmed_auth_denied_title#other" = "New Logins Prevented"; +"lng_unconfirmed_auth_denied_single" = "We have terminated the login attempt from {country}."; +"lng_unconfirmed_auth_denied_multiple" = "We have terminated the login attempts from: {country}"; +"lng_unconfirmed_auth_denied_warning" = "Never send your login code to anyone or you can lose your Telegram account!"; +"lng_unconfirmed_auth_confirmed" = "New Login Allowed"; +"lng_unconfirmed_auth_confirmed_message" = "You can check the list of your active logins in {link}."; + "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; +"lng_about_random_stake" = "Stake: {amount}"; +"lng_about_random_stake_change" = "change"; "lng_open_this_link" = "Open this link?"; "lng_open_link" = "Open"; @@ -3328,6 +4930,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_url_auth_open_confirm" = "Do you want to open {link}?"; "lng_url_auth_login_option" = "Log in to {domain} as {user}"; "lng_url_auth_allow_messages" = "Allow {bot} to send me messages"; +"lng_url_auth_login_title" = "Log in to {domain}"; +"lng_url_auth_login_button" = "Log in"; +"lng_url_auth_site_access" = "This site will receive your **name**, **username** and **profile photo**."; +"lng_url_auth_app_access" = "This app will receive your **name**, **username** and **profile photo**."; +"lng_url_auth_unverified_app" = "Unverified App"; +"lng_url_auth_device_label" = "Device"; +"lng_url_auth_ip_label" = "IP Address"; +"lng_url_auth_login_attempt" = "This login attempt came from the device above."; +"lng_url_auth_allow_messages_label" = "Allow Messages"; +"lng_url_auth_allow_messages_about" = "This will allow {bot} to message you"; +"lng_url_auth_phone_sure_title" = "Phone Number"; +"lng_url_auth_phone_sure_text" = "{domain} wants to access your phone number **{phone}**.\n\nAllow access?"; +"lng_url_auth_phone_sure_deny" = "Deny"; +"lng_url_auth_phone_toast_good_title" = "Login successful"; +"lng_url_auth_phone_toast_good" = "You're now logged in to {domain}."; +"lng_url_auth_phone_toast_good_no_phone" = "You're now logged in to {domain}, but you didn't grant access to your phone number."; +"lng_url_auth_phone_toast_bad_title" = "Login failed"; +"lng_url_auth_phone_toast_bad" = "Please try logging in to {domain} again."; +"lng_url_auth_phone_toast_bad_expired" = "This link is expired."; +"lng_url_auth_match_code_title" = "Tap the emoji shown on your other device"; +"lng_url_auth_match_code_info" = "Login request from {domain}"; "lng_bot_start" = "Start"; "lng_bot_choose_group" = "Select a Group"; @@ -3350,6 +4973,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_sure_add_text_group" = "Are you sure you want to add this bot as an admin in the group {group}? It will have access to the list of anonymous admins."; "lng_bot_sure_add_text_channel" = "Are you sure you want to add this bot as an admin in the channel {group}? It will have access to lists of channel admins and subscribers."; "lng_bot_no_webview" = "Unfortunately, you can't open this menu with your current system configuration."; +"lng_bot_webview_failed" = "Could not initialize WebView."; +"lng_bot_webview_crashed" = "Webview crashed."; "lng_bot_remove_from_menu" = "Remove from menu"; "lng_bot_remove_from_menu_sure" = "Remove {bot} from the attachment menu?"; "lng_bot_remove_from_menu_done" = "Bot removed from the menu."; @@ -3359,6 +4984,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_settings" = "Settings"; "lng_bot_open" = "Open Bot"; "lng_bot_terms" = "Terms of Use"; +"lng_bot_privacy" = "Privacy Policy"; "lng_bot_reload_page" = "Reload Page"; "lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachment menu so you can access it from any chat."; "lng_bot_add_to_menu_done" = "Bot added to the menu."; @@ -3374,6 +5000,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; "lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps."; "lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps."; +"lng_bot_emoji_status_confirm" = "Confirm"; +"lng_bot_emoji_status_title" = "Set Emoji Status"; +"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?"; +"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}."; +"lng_bot_emoji_status_access_allow" = "Allow"; +"lng_bot_share_prepared_title" = "Share Message"; +"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select."; +"lng_bot_share_prepared_button" = "Share With..."; +"lng_bot_download_file" = "Download File"; +"lng_bot_download_file_sure" = "{bot} suggests you download the following file:"; +"lng_bot_download_file_button" = "Download"; +"lng_bot_download_starting" = "Starting..."; +"lng_bot_download_failed" = "Failed. {retry}"; +"lng_bot_download_retry" = "Retry"; "lng_bot_status_users#one" = "{count} monthly user"; "lng_bot_status_users#other" = "{count} monthly users"; @@ -3441,6 +5081,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_choose_image" = "Choose an image"; "lng_choose_file" = "Choose a file"; "lng_choose_files" = "Choose Files"; +"lng_choose_cover" = "Choose video cover"; +"lng_choose_cover_bad" = "Can't use this file as a caption."; "lng_game_tag" = "Game"; "lng_context_new_window" = "Open in new window"; @@ -3449,6 +5091,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_view_group" = "View group info"; "lng_context_view_channel" = "View channel info"; "lng_context_view_topic" = "View topic info"; +"lng_context_view_thread" = "View thread info"; "lng_context_hide_psa" = "Hide this announcement"; "lng_context_pin_to_top" = "Pin"; "lng_context_unpin_from_top" = "Unpin"; @@ -3457,14 +5100,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?"; "lng_context_mark_read_all" = "Mark all chats as read"; "lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?"; +"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**"; "lng_context_mark_read_mentions_all" = "Mark all mentions as read"; "lng_context_mark_read_reactions_all" = "Read all reactions"; +"lng_context_mark_read_poll_votes_all" = "Read all poll votes"; "lng_context_archive_expand" = "Expand"; "lng_context_archive_collapse" = "Collapse"; "lng_context_archive_to_menu" = "Move to main menu"; "lng_context_archive_to_list" = "Move to chat list"; "lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nRight click the archive button to return the Archive to your chat list."; "lng_context_archive_settings" = "Archive settings"; +"lng_context_archive_how_does_it_work" = "How does it work?"; "lng_context_mute" = "Mute notifications"; "lng_context_unmute" = "Unmute"; @@ -3473,9 +5119,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_edit_permissions" = "Edit admin rights"; "lng_context_restrict_user" = "Restrict user"; "lng_context_ban_user" = "Ban"; +"lng_context_delete_and_ban" = "Delete and ban"; "lng_context_remove_from_group" = "Remove from group"; "lng_context_add_to_group" = "Add to group"; +"lng_context_rate_transcription" = "Rate transcription"; +"lng_toast_sent_rate_transcription" = "Thank you for your feedback!"; + "lng_context_copy_link" = "Copy Link"; "lng_context_copy_message_link" = "Copy Message Link"; "lng_context_copy_post_link" = "Copy Post Link"; @@ -3495,6 +5145,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_pack_info" = "View Sticker Set"; "lng_context_pack_add" = "Add Stickers"; "lng_context_save_file" = "Save As..."; +"lng_context_save_music_to" = "Save to..."; +"lng_context_save_music_profile" = "... Profile"; +"lng_context_save_music_saved" = "... Saved Messages"; +"lng_context_save_music_folder" = "... Downloads"; +"lng_context_save_music_about" = "Choose where you want this audio to be saved."; "lng_context_copy_text" = "Copy Text"; "lng_context_open_gif" = "Open GIF"; "lng_context_save_gif" = "Add to GIFs"; @@ -3505,9 +5160,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; "lng_context_quote_and_reply" = "Quote & Reply"; +"lng_context_reply_with_timecode" = "Reply with timecode"; +"lng_context_reply_to_task" = "Reply to Task"; +"lng_context_reply_to_poll_option" = "Reply to Option"; +"lng_context_copy_poll_option" = "Copy Option"; +"lng_context_copy_poll_option_link" = "Copy Option Link"; +"lng_context_delete_poll_option" = "Delete Item"; +"lng_context_poll_message_tab" = "Poll"; +"lng_context_poll_option_tab" = "Option"; "lng_context_edit_msg" = "Edit"; +"lng_context_draw" = "Edit Image"; "lng_context_add_factcheck" = "Add Fact Check"; "lng_context_edit_factcheck" = "Edit Fact Check"; +"lng_context_add_offer" = "Add Offer"; "lng_context_forward_msg" = "Forward"; "lng_context_send_now_msg" = "Send Now"; "lng_context_reschedule" = "Reschedule"; @@ -3519,8 +5184,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_pin_msg" = "Pin"; "lng_context_unpin_msg" = "Unpin"; "lng_context_cancel_upload" = "Cancel Upload"; +"lng_context_upload_edit_caption" = "Edit Caption"; "lng_context_copy_selected" = "Copy Selected Text"; "lng_context_copy_selected_items" = "Copy Selected as Text"; +"lng_context_copy_in_reply_to" = "in reply to {name}"; "lng_context_forward_selected" = "Forward Selected"; "lng_context_send_now_selected" = "Send selected now"; "lng_context_reschedule_selected" = "Reschedule Selected"; @@ -3540,8 +5207,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_seen_watched_none" = "Nobody Listened"; "lng_context_seen_reacted#one" = "{count} Reacted"; "lng_context_seen_reacted#other" = "{count} Reacted"; +"lng_context_seen_reactions_count#one" = "{count} Reaction"; +"lng_context_seen_reactions_count#other" = "{count} Reactions"; "lng_context_seen_reacted_none" = "Nobody Reacted"; "lng_context_seen_reacted_all" = "Show All Reactions"; +"lng_context_sent_by" = "Sent by {user}"; +"lng_context_sent_today" = "Sent today at {time}"; +"lng_context_sent_yesterday" = "Sent yesterday at {time}"; +"lng_context_sent_date" = "Sent {date} at {time}"; "lng_context_set_as_quick" = "Set As Quick"; "lng_context_filter_by_tag" = "Filter by Tag"; "lng_context_tag_add_name" = "Add Name"; @@ -3556,6 +5229,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_read_show" = "show when"; "lng_context_edit_shortcut" = "Edit Shortcut"; "lng_context_delete_shortcut" = "Delete Quick Reply"; +"lng_context_gift_send" = "Send Another Gift"; +"lng_context_charge_fee" = "Charge Fee"; +"lng_context_remove_fee" = "Remove Fee"; +"lng_context_fee_now" = "{name} pays {amount} per message."; +"lng_context_fee_free" = "{name} can send messages for free."; "lng_add_tag_about" = "Tag this message with an emoji for quick search."; "lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}"; @@ -3566,24 +5244,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_add_tag_phrase" = "to messages {arrow}"; "lng_add_tag_phrase_long" = "to your Saved Messages {arrow}"; "lng_unlock_tags" = "Unlock"; +"lng_add_tag_selector#one" = "You can add a tag to the message"; +"lng_add_tag_selector#other" = "You can add a tag to the messages"; +"lng_message_tagged_with" = "Message tagged with {emoji}"; +"lng_tagged_view_saved" = "View"; + +"lng_add_channel_to_filter_selector" = "You can add a channel to your folder"; +"lng_add_group_to_filter_selector" = "You can add a group to your folder"; "lng_context_animated_emoji" = "This message contains emoji from **{name} pack**."; "lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**."; "lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**."; "lng_context_animated_reaction" = "This reaction is from the **{name}** pack."; +"lng_context_animated_emoji_preview" = "This emoji is from the **{name}** pack."; "lng_context_animated_tag" = "This tag is from **{name} pack**."; "lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**."; "lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**."; "lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**."; +"lng_context_animated_poll_option" = "This option contains emoji from **{name} pack**."; +"lng_context_animated_poll_option_many#one" = "This option contains emoji from **{count} pack**."; +"lng_context_animated_poll_option_many#other" = "This option contains emoji from **{count} packs**."; "lng_context_noforwards_info_channel" = "Copying and forwarding is not allowed in this channel."; "lng_context_noforwards_info_group" = "Copying and forwarding is not allowed in this group."; "lng_context_noforwards_info_bot" = "Copying and forwarding is not allowed from this bot."; +"lng_context_noforwards_info_his" = "{user} disabled copying and forwarding in this chat."; +"lng_context_noforwards_info_mine" = "You disabled copying and forwarding in this chat."; "lng_context_spoiler_effect" = "Hide with Spoiler"; -"lng_context_disable_spoiler" = "Remove Spoiler"; "lng_context_make_paid" = "Make This Content Paid"; "lng_context_change_price" = "Change Price"; +"lng_context_edit_cover" = "Edit Cover"; +"lng_context_clear_cover" = "Clear Cover"; + +"lng_context_mention" = "Mention"; +"lng_context_search_from" = "Search messages"; "lng_factcheck_title" = "Fact Check"; "lng_factcheck_placeholder" = "Add Facts or Context"; @@ -3611,14 +5306,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_paid_react_send" = "Send {price}"; "lng_paid_react_agree" = "By sending stars, you agree to the {link}."; "lng_paid_react_agree_link" = "Terms of Service"; +"lng_paid_react_admin_cant" = "You can't send Stars to your own Live Story."; "lng_paid_react_toast#one" = "Star Sent!"; "lng_paid_react_toast#other" = "Stars Sent!"; +"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!"; +"lng_paid_react_toast_anonymous#other" = "Stars sent anonymously!"; "lng_paid_react_toast_text#one" = "You reacted with **{count} Star**."; "lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**."; "lng_paid_react_undo" = "Undo"; "lng_paid_react_show_in_top" = "Show me in Top Senders"; "lng_paid_react_anonymous" = "Anonymous"; +"lng_paid_comment_title" = "Highlight and Pin"; +"lng_paid_comment_about" = "Highlight and pin your message by adding Stars for {name}."; +"lng_paid_comment_button" = "Add {stars}"; +"lng_paid_comment_pin_about" = "pin in chat"; +"lng_paid_comment_limit_about#one" = "character"; +"lng_paid_comment_limit_about#other" = "characters"; +"lng_paid_comment_emoji_about#one" = "emoji"; +"lng_paid_comment_emoji_about#other" = "emoji"; +"lng_paid_reaction_title" = "React with Stars"; +"lng_paid_reaction_about" = "Highlight and pin your message by sending Stars to {name}."; +"lng_paid_reaction_button" = "Send {stars}"; +"lng_paid_admin_title" = "Receive Stars from Viewers"; +"lng_paid_admin_about" = "Viewers can send you Star Reactions."; + "lng_sensitive_tag" = "18+"; "lng_sensitive_title" = "18+"; "lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?"; @@ -3627,6 +5339,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**"; "lng_translate_show_original" = "Show Original"; +"lng_translate_return_original" = "View Original ({language})"; "lng_translate_bar_to" = "Translate to {name}"; "lng_translate_bar_to_other" = "Translate to {name}"; "lng_translate_menu_to" = "Translate To"; @@ -3640,6 +5353,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_translate_settings" = "Settings"; "lng_translate_undo" = "Undo"; +"lng_translate_cocoon_menu" = "Translations are powered by\n**{emoji} Cocoon**. {link}"; +"lng_translate_cocoon_link" = "How does it work?"; +"lng_translate_cocoon_title" = "Cocoon"; +"lng_translate_cocoon_subtitle" = "**Cocoon** ({text}) handles AI tasks **safely** and **efficiently**."; +"lng_translate_cocoon_explain" = "**Co**nfidential **Co**mpute **O**pen **N**etwork"; +"lng_translate_cocoon_private_title" = "Private"; +"lng_translate_cocoon_private_text" = "No third party can access any data, such as translations, inside {mention}."; +"lng_translate_cocoon_private_mention" = "@cocoon"; +"lng_translate_cocoon_efficient_title" = "Efficient"; +"lng_translate_cocoon_efficient_text" = "Cocoon has allowed Telegram to reduce translation costs by 6x."; +"lng_translate_cocoon_everyone_title" = "For Everyone"; +"lng_translate_cocoon_everyone_text" = "Any developer can use Cocoon for AI features. Learn more at {domain}."; +"lng_translate_cocoon_everyone_domain" = "cocoon.org"; +"lng_translate_cocoon_text" = "Want to integrate Cocoon into your projects?\nReach out at {link}"; +"lng_translate_cocoon_text_link" = "t.me/cocoon?direct"; +"lng_translate_cocoon_done" = "Understood"; + "lng_downloads_section" = "Downloads"; "lng_downloads_view_in_chat" = "View in chat"; "lng_downloads_view_in_section" = "View in downloads"; @@ -3658,6 +5388,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_grouped" = "Group items"; "lng_send_compressed_one" = "Compress the image"; "lng_send_compressed" = "Compress images"; +"lng_send_high_quality" = "High Quality"; +"lng_send_as_documents_one" = "Send as a document"; +"lng_send_as_documents" = "Send as documents"; "lng_send_media_invalid_files" = "Sorry, no valid files found."; "lng_send_image" = "Send an image"; "lng_send_file" = "Send as a file"; @@ -3679,9 +5412,120 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_inline_switch_cant" = "Sorry, no way to write here :("; "lng_preview_reply_to" = "Reply to {name}"; "lng_preview_reply_to_quote" = "Reply to quote from {name}"; +"lng_preview_reply_to_task" = "Reply to task from {title}"; +"lng_preview_reply_to_poll_option" = "Reply to poll option from {title}"; + +"lng_suggest_bar_title" = "Suggest a Post Below"; +"lng_suggest_bar_text" = "Click to offer a price for publishing."; +"lng_suggest_bar_priced" = "{amount} for publishing anytime."; +"lng_suggest_bar_dated" = "Publish on {date}"; +"lng_suggest_options_title" = "Suggest a Message"; +"lng_suggest_options_change" = "Suggest Changes"; +"lng_suggest_options_stars_offer" = "Offer Stars"; +"lng_suggest_options_stars_request" = "Request Stars"; +"lng_suggest_options_stars_price" = "Enter Price in Stars"; +"lng_suggest_options_stars_price_about" = "Choose how many Stars to pay to publish this message."; +"lng_suggest_options_ton_offer" = "Offer Grams"; +"lng_suggest_options_ton_request" = "Request Grams"; +"lng_suggest_options_ton_price" = "Enter Price in Grams"; +"lng_suggest_options_ton_price_about" = "Choose how many Grams to pay to publish this message."; +"lng_suggest_options_date" = "Time"; +"lng_suggest_options_date_any" = "Anytime"; +"lng_suggest_options_date_publish" = "Publish"; +"lng_suggest_options_date_now" = "Publish Now"; +"lng_suggest_options_date_about" = "Select the date and time you want the message to be published. The post will remain available for at least 24 hours from this date."; +"lng_suggest_options_you_get_stars#one" = "You will receive {count} Star ({percent}) for publishing this post."; +"lng_suggest_options_you_get_stars#other" = "You will receive {count} Stars ({percent}) for publishing this post."; +"lng_suggest_options_you_get_ton#one" = "You will receive {count} Gram ({percent}) for publishing this post."; +"lng_suggest_options_you_get_ton#other" = "You will receive {count} Grams ({percent}) for publishing this post."; +"lng_suggest_options_stars_warning" = "Transactions in **Stars** may be reversed by the payment provider within **21** days. Only accept Stars from people you trust."; +"lng_suggest_options_offer" = "Offer {amount}"; +"lng_suggest_options_offer_free" = "Offer for Free"; +"lng_suggest_options_update" = "Update Terms"; +"lng_suggest_options_update_date" = "Update Time"; + +"lng_suggest_action_decline" = "Decline"; +"lng_suggest_action_accept" = "Accept"; +"lng_suggest_action_change" = "Suggest Changes"; +"lng_suggest_action_your" = "You suggest to post this message."; +"lng_suggest_action_his" = "{from} suggests to post this message."; +"lng_suggest_action_price_label" = "Price"; +"lng_suggest_action_price_free" = "Free"; +"lng_suggest_action_time_label" = "Time"; +"lng_suggest_action_time_any" = "Anytime"; +"lng_suggest_action_agreement" = "Agreement reached!"; +"lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}."; +"lng_suggest_action_your_charged_stars#one" = "You have been charged **{count} Star**."; +"lng_suggest_action_your_charged_stars#other" = "You have been charged **{count} Stars**."; +"lng_suggest_action_your_charged_ton#one" = "You have been charged **{count} Gram**."; +"lng_suggest_action_your_charged_ton#other" = "You have been charged **{count} Grams**."; +"lng_suggest_action_his_charged_stars#one" = "{from} has been charged **{count} Star**."; +"lng_suggest_action_his_charged_stars#other" = "{from} has been charged **{count} Stars**."; +"lng_suggest_action_his_charged_ton#one" = "{from} has been charged **{count} Gram**."; +"lng_suggest_action_his_charged_ton#other" = "{from} has been charged **{count} Grams**."; +"lng_suggest_action_agree_receive_stars" = "{channel} will receive the Stars once the post has been live for 24 hours."; +"lng_suggest_action_agree_receive_ton" = "{channel} will receive Grams once the post has been live for 24 hours."; +"lng_suggest_action_agree_removed_stars" = "If {channel} removes the post before it has been live for 24 hours, the Stars will be refunded."; +"lng_suggest_action_agree_removed_ton" = "If {channel} removes the post before it has been live for 24 hours, Grams will be refunded."; +"lng_suggest_action_your_not_enough_stars" = "**Transaction failed** because you didn't have enough Stars."; +"lng_suggest_action_your_not_enough_ton" = "**Transaction failed** because you didn't have enough Grams."; +"lng_suggest_action_his_not_enough_stars" = "**Transaction failed** because the user didn't have enough Stars."; +"lng_suggest_action_his_not_enough_ton" = "**Transaction failed** because the user didn't have enough Grams."; +"lng_suggest_action_declined" = "{from} rejected the message."; +"lng_suggest_action_declined_reason" = "{from} rejected the message with the comment."; +"lng_suggest_change_price" = "{from} suggests a new price for the message."; +"lng_suggest_change_time" = "{from} suggests a new time for the message."; +"lng_suggest_change_price_time" = "{from} suggests a new price and time for the message."; +"lng_suggest_change_content" = "{from} suggests changes for the message."; +"lng_suggest_change_price_label" = "New Price"; +"lng_suggest_change_time_label" = "New Time"; +"lng_suggest_change_text_label" = "Check the suggested message below"; +"lng_suggest_menu_edit_message" = "Edit Message"; +"lng_suggest_menu_edit_price" = "Edit Price"; +"lng_suggest_menu_edit_time" = "Edit Time"; +"lng_suggest_decline_title" = "Decline"; +"lng_suggest_decline_text" = "Do you want to decline publishing this post from {from}?"; +"lng_suggest_decline_text_to" = "Do you want to decline publishing this post to {channel}?"; +"lng_suggest_decline_reason" = "Add a reason (optional)"; +"lng_suggest_accept_title" = "Accept Terms"; +"lng_suggest_accept_text" = "Do you want to publish this post from {from}?"; +"lng_suggest_accept_text_to" = "Do you want to publish this post to {channel}?"; +"lng_suggest_accept_receive_stars#one" = "{channel} will receive **{count} Star** ({percent}) for publishing {date}."; +"lng_suggest_accept_receive_stars#other" = "{channel} will receive **{count} Stars** ({percent}) for publishing {date}."; +"lng_suggest_accept_receive_ton#one" = "{channel} will receive **{count} Gram** ({percent}) for publishing {date}."; +"lng_suggest_accept_receive_ton#other" = "{channel} will receive **{count} Grams** ({percent}) for publishing {date}."; +"lng_suggest_accept_receive_now_stars#one" = "{channel} will receive **{count} Star** ({percent}) for publishing right now."; +"lng_suggest_accept_receive_now_stars#other" = "{channel} will receive **{count} Stars** ({percent}) for publishing right now."; +"lng_suggest_accept_receive_now_ton#one" = "{channel} will receive **{count} Gram** ({percent}) for publishing right now."; +"lng_suggest_accept_receive_now_ton#other" = "{channel} will receive **{count} Grams** ({percent}) for publishing right now."; +"lng_suggest_accept_receive_if" = "It must remain visible for at least **24** hours after publication."; +"lng_suggest_accept_pay_stars#one" = "You will pay **{count} Star** for publishing {date}."; +"lng_suggest_accept_pay_stars#other" = "You will pay **{count} Stars** for publishing {date}."; +"lng_suggest_accept_pay_ton#one" = "You will pay **{count} Gram** for publishing {date}."; +"lng_suggest_accept_pay_ton#other" = "You will pay **{count} Grams** for publishing {date}."; +"lng_suggest_accept_pay_now_stars#one" = "You will pay **{count} Star** for publishing right now."; +"lng_suggest_accept_pay_now_stars#other" = "You will pay **{count} Stars** for publishing right now."; +"lng_suggest_accept_pay_now_ton#one" = "You will pay **{count} Gram** for publishing right now."; +"lng_suggest_accept_pay_now_ton#other" = "You will pay **{count} Grams** for publishing right now."; +"lng_suggest_accept_send" = "Publish"; +"lng_suggest_stars_amount#one" = "{count} Star"; +"lng_suggest_stars_amount#other" = "{count} Stars"; +"lng_suggest_ton_amount#one" = "{count} Gram"; +"lng_suggest_ton_amount#other" = "{count} Grams"; +"lng_suggest_warn_title_stars" = "Stars will be lost"; +"lng_suggest_warn_title_ton" = "Grams will be lost"; +"lng_suggest_warn_text_stars" = "You won't receive **Stars** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published."; +"lng_suggest_warn_text_ton" = "You won't receive **Grams** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published."; +"lng_suggest_warn_delete_anyway" = "Delete Anyway"; +"lng_suggest_low_ton_title" = "{amount} Grams Needed"; +"lng_suggest_low_ton_text" = "You can add funds to your balance via the third-party platform Fragment."; +"lng_suggest_low_ton_fragment" = "Add Funds via Fragment"; +"lng_suggest_low_ton_fragment_url" = "https://fragment.com/ads/topup"; "lng_reply_in_another_title" = "Reply in..."; "lng_reply_in_another_chat" = "Reply in Another Chat"; +"lng_reply_in_author" = "Message author"; +"lng_reply_in_chats_list" = "Your chats"; "lng_reply_show_in_chat" = "Show in Chat"; "lng_reply_remove" = "Do Not Reply"; "lng_reply_about_quote" = "You can select a specific part to quote."; @@ -3698,6 +5542,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_link_move_down" = "Move Down"; "lng_link_shrink_photo" = "Shrink Photo"; "lng_link_enlarge_photo" = "Enlarge Photo"; +"lng_link_shrink_video" = "Shrink Video"; +"lng_link_enlarge_video" = "Enlarge Video"; "lng_link_remove" = "Do Not Preview"; "lng_link_about_choose" = "Click on a link to generate its preview."; @@ -3706,13 +5552,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?"; "lng_share_title" = "Share to"; +"lng_share_at_time_title" = "Share at {time} to"; "lng_share_copy_link" = "Copy share link"; "lng_share_confirm" = "Send"; "lng_share_wrong_user" = "This game was opened from a different user."; "lng_share_game_link_copied" = "Game link copied to clipboard."; "lng_share_done" = "Done!"; -"lng_share_message_to_saved_messages" = "Message forwarded to **Saved Messages**."; -"lng_share_messages_to_saved_messages" = "Messages forwarded to **Saved Messages**."; +"lng_share_message_to_saved" = "Message forwarded to **{chat}**."; +"lng_share_messages_to_saved" = "Messages forwarded to **{chat}**."; "lng_share_message_to_chat" = "Message forwarded to **{chat}**."; "lng_share_messages_to_chat" = "Messages forwarded to **{chat}**."; "lng_share_message_to_two_chats" = "Message forwarded to **{user}** and **{chat}**."; @@ -3728,9 +5575,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_contact_phone_after" = "Phone number will be visible once {user} adds you as a contact."; "lng_contact_share_phone" = "Share my phone number"; "lng_contact_phone_will_be_shared" = "You can make your phone visible to {user}."; +"lng_contact_add_notes" = "Note"; +"lng_contact_add_notes_about" = "Notes are only visible to you."; +"lng_contact_notes_limit_reached#one" = "You've reached the contact note limit. Please make the note shorter by {count} character."; +"lng_contact_notes_limit_reached#other" = "You've reached the contact note limit. Please make the note shorter by {count} characters."; +"lng_suggest_photo_for" = "Suggest Photo for {user}"; +"lng_suggest_birthday" = "Suggest Date of Birth"; +"lng_suggest_birthday_box_title" = "{user}'s Date of Birth"; +"lng_suggest_birthday_box_confirm" = "Suggest"; +"lng_set_photo_for_user" = "Set Photo for {user}"; +"lng_contact_photo_replace_info" = "You can replace {user}'s photo with another photo that only you will see."; "lng_edit_contact_title" = "Edit contact"; "lng_edit_channel_title" = "Edit channel"; "lng_edit_bot_title" = "Edit bot"; +"lng_edit_autotranslate" = "Auto-translate messages"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages_about" = "Add names of admins to the messages they post."; "lng_edit_sign_profiles" = "Show authors' profiles"; @@ -3746,6 +5604,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name."; "lng_edit_channel_status_group" = "Set Group Emoji Status"; "lng_edit_channel_status_about_group" = "Choose a status that will be shown next to the group's name."; +"lng_edit_channel_personal_channel" = "Set as Personal Channel"; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; "lng_add_contact" = "Create"; @@ -3766,10 +5625,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_drag_images_here" = "Drop images here"; "lng_drag_photos_here" = "Drop photos here"; "lng_drag_files_here" = "Drop files here"; +"lng_drag_media_here" = "Drop photos and videos"; "lng_drag_to_send_quick" = "to send them in a quick way"; "lng_drag_to_send_no_compression" = "to send them without compression"; "lng_drag_to_send_files" = "to send them as documents"; +"lng_drag_to_send_media" = "to send them as media files"; + +"lng_drag_proxy_here" = "Connect to a proxy"; +"lng_drag_proxy_about" = "Drop the link here"; "lng_selected_clear" = "Cancel"; "lng_selected_delete" = "Delete"; @@ -3780,6 +5644,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_selected_delete_sure_this" = "Do you want to delete this message?"; "lng_selected_delete_sure#one" = "Do you want to delete {count} message?"; "lng_selected_delete_sure#other" = "Do you want to delete {count} messages?"; +"lng_delete_title_message_one" = "Delete this message"; +"lng_delete_title_message_many#one" = "Delete {count} message"; +"lng_delete_title_message_many#other" = "Delete {count} messages"; +"lng_delete_title_reaction_this" = "Delete this reaction"; +"lng_delete_title_reaction_all" = "Delete all reactions"; +"lng_delete_label_also_this_reaction" = "Also delete this reaction."; +"lng_delete_label_also_some_reactions" = "Also delete all reactions from some participants."; +"lng_delete_label_also_all_reactions" = "Also delete all reactions."; +"lng_delete_sub_messages" = "Delete all messages"; +"lng_delete_sub_reactions" = "Delete all reactions"; +"lng_context_delete_this_reaction" = "Delete this reaction"; +"lng_selected_remove_saved_music" = "Do you want to remove this file from your profile?"; +"lng_saved_music_added" = "Audio added to your Profile."; +"lng_saved_music_removed" = "Audio removed from your Profile."; "lng_delete_photo_sure" = "Do you want to delete this photo?"; "lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat."; "lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat."; @@ -3821,11 +5699,128 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_search_messages_from" = "Show messages from"; "lng_search_messages_n_of_amount" = "{n} of {amount}"; "lng_search_messages_none" = "No results"; +"lng_search_filter_all" = "All chats"; +"lng_search_filter_private" = "Private chats"; +"lng_search_filter_group" = "Group chats"; +"lng_search_filter_channel" = "Channels"; +"lng_search_sponsored_button" = "Ad ⋮"; +"lng_sr_chat_channel" = "Channel"; +"lng_sr_chat_group" = "Group"; +"lng_sr_chat_bot" = "Bot"; +"lng_sr_chat_topic" = "Topic"; +"lng_sr_chat_folder" = "Folder"; +"lng_sr_chat_hashtag" = "Hashtag"; +"lng_sr_chat_service" = "Service Notifications"; +"lng_sr_chat_verified" = "Verified"; +"lng_sr_chat_scam" = "Scam"; +"lng_sr_chat_fake" = "Fake"; +"lng_sr_chat_pinned" = "Pinned"; +"lng_sr_chat_unread#one" = "{count} new message"; +"lng_sr_chat_unread#other" = "{count} new messages"; +"lng_sr_chat_mention" = "Mentioned"; +"lng_sr_chat_message_reactions" = "Reactions: {reactions}"; +"lng_sr_chat_reaction_star" = "star"; +"lng_sr_chat_reaction_custom" = "custom reaction"; +"lng_sr_chat_sending" = "Sending"; +"lng_sr_chat_sent" = "Sent"; +"lng_sr_chat_failed" = "Failed"; +"lng_sr_chat_received" = "Received"; +"lng_sr_chat_online" = "Online"; +"lng_sr_chat_sponsored" = "Sponsored"; +"lng_sr_chat_stories_unread#one" = "{count} unread story"; +"lng_sr_chat_stories_unread#other" = "{count} unread stories"; +"lng_sr_chat_stories_read" = "Has stories"; +"lng_sr_chat_autodelete" = "Auto-delete enabled"; +"lng_sr_chat_subscribed" = "Subscribed"; +"lng_sr_chat_forwarded" = "Forwarded"; +"lng_sr_chat_story_reply" = "Story reply"; + +"lng_sr_chat_column_type" = "Type"; +"lng_sr_chat_column_name" = "Name"; +"lng_sr_chat_column_warning" = "Warning"; +"lng_sr_chat_column_premium" = "Premium"; +"lng_sr_chat_column_verified" = "Verified"; +"lng_sr_chat_column_activity" = "Activity"; +"lng_sr_chat_column_muted" = "Muted"; +"lng_sr_chat_column_pinned" = "Pinned"; +"lng_sr_chat_column_draft" = "Draft"; +"lng_sr_chat_column_unread" = "Unread"; +"lng_sr_chat_column_mention" = "Mention"; +"lng_sr_chat_column_sender" = "Sender"; +"lng_sr_chat_column_message" = "Message"; +"lng_sr_chat_column_delivery" = "Delivery"; +"lng_sr_chat_column_reactions" = "Reactions"; +"lng_sr_chat_column_time" = "Time"; +"lng_sr_chat_column_sponsored" = "Sponsored"; +"lng_sr_chat_column_stories" = "Stories"; +"lng_sr_chat_column_autodelete" = "Auto-delete"; +"lng_sr_chat_column_subscription" = "Subscription"; +"lng_sr_chat_column_closed" = "Closed"; +"lng_sr_chat_column_forward" = "Forward"; +"lng_sr_chat_column_folders" = "Folders"; + +"lng_sr_message_list" = "Messages"; +"lng_sr_from_me" = "Me"; +"lng_sr_message_seen" = "Seen"; +"lng_sr_message_not_seen" = "Not seen"; +"lng_sr_message_played" = "Played"; +"lng_sr_message_not_played" = "Not played"; +"lng_sr_message_not_downloaded" = "Not downloaded"; +"lng_sr_message_downloading" = "Downloading"; +"lng_sr_message_reply_to" = "In reply to {name}: {text}"; +"lng_sr_message_custom_emoji" = "Custom emoji {emoji}"; + +"lng_sr_message_column_media_type" = "Media type"; +"lng_sr_message_column_artist" = "Artist"; +"lng_sr_message_column_title" = "Title"; +"lng_sr_message_column_filename" = "Filename"; +"lng_sr_message_column_duration" = "Duration"; +"lng_sr_message_column_dimensions" = "Dimensions"; +"lng_sr_message_column_file_size" = "File size"; +"lng_sr_message_column_signature" = "Signature"; +"lng_sr_message_column_web_site" = "Website"; +"lng_sr_message_column_web_title" = "Link title"; +"lng_sr_message_column_web_description" = "Link description"; +"lng_sr_message_column_poll_question" = "Poll question"; +"lng_sr_message_column_poll_status" = "Poll status"; +"lng_sr_message_column_contact_name" = "Contact name"; +"lng_sr_message_column_contact_phone" = "Contact phone"; +"lng_sr_message_column_location" = "Location"; +"lng_sr_message_column_sticker_emoji" = "Sticker emoji"; +"lng_sr_message_column_game_title" = "Game title"; +"lng_sr_message_column_game_description" = "Game description"; +"lng_sr_message_column_invoice_title" = "Invoice title"; +"lng_sr_message_column_invoice_amount" = "Invoice amount"; +"lng_sr_message_column_spoiler" = "Spoiler"; +"lng_sr_message_column_dice" = "Dice"; +"lng_sr_message_column_giveaway" = "Giveaway"; +"lng_sr_message_column_gift" = "Gift"; +"lng_sr_message_column_todo_title" = "Todo title"; +"lng_sr_message_column_todo_items" = "Todo items"; +"lng_sr_message_column_factcheck" = "Fact check"; +"lng_sr_message_column_forward_date" = "Forward date"; +"lng_sr_message_column_forward_author" = "Forward author"; +"lng_sr_message_column_paid_reactions" = "Paid reactions"; + +"lng_sr_message_spoiler" = "Hidden media"; +"lng_sr_message_todo_completed" = "Completed"; +"lng_sr_message_todo_not_completed" = "Not completed"; +"lng_sr_message_gift_premium#one" = "Premium gift, {count} day"; +"lng_sr_message_gift_premium#other" = "Premium gift, {count} days"; +"lng_sr_message_gift_credits#one" = "Star gift, {count} star"; +"lng_sr_message_gift_credits#other" = "Star gift, {count} stars"; +"lng_sr_message_invoice_paid" = "Paid"; +"lng_sr_message_invoice_unpaid" = "Unpaid"; + +"lng_sr_folder_locked" = "{text}, Premium required"; +"lng_sr_folder_locked_about" = "Press to view subscription options."; "lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_mediaview_save_as" = "Save As..."; "lng_mediaview_copy" = "Copy"; +"lng_mediaview_copy_frame" = "Copy Frame"; "lng_mediaview_forward" = "Forward"; +"lng_mediaview_share_at_time" = "Share at {time}"; "lng_mediaview_delete" = "Delete"; "lng_mediaview_save_to_profile" = "Post to Profile"; "lng_mediaview_pin_story_done" = "Story pinned"; @@ -3869,6 +5864,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mediaview_downloads" = "Downloads"; "lng_mediaview_playback_speed" = "Playback speed: {speed}"; "lng_mediaview_rotate_video" = "Rotate video"; +"lng_mediaview_quality_auto" = "Auto"; +"lng_mediaview_quality_original" = "Original ({quality}p)"; "lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_generating" = "Generating color theme preview..."; @@ -3889,9 +5886,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_menu_formatting_blockquote" = "Quote"; "lng_menu_formatting_monospace" = "Monospace"; "lng_menu_formatting_spoiler" = "Spoiler"; +"lng_menu_formatting_date" = "Date"; "lng_menu_formatting_link_create" = "Create link"; "lng_menu_formatting_link_edit" = "Edit link"; "lng_menu_formatting_clear" = "Clear formatting"; +"lng_formatting_date_title" = "Choose date and time"; "lng_formatting_link_create_title" = "Create link"; "lng_formatting_link_edit_title" = "Edit link"; "lng_formatting_link_text" = "Text"; @@ -3903,6 +5902,40 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_text_copied" = "Text copied to clipboard."; "lng_code_copied" = "Code copied to clipboard."; +"lng_date_copied" = "Date copied to clipboard."; + +"lng_date_relative_now" = "now"; + +"lng_date_relative_seconds_ago#one" = "{count} second ago"; +"lng_date_relative_seconds_ago#other" = "{count} seconds ago"; +"lng_date_relative_minutes_ago#one" = "{count} minute ago"; +"lng_date_relative_minutes_ago#other" = "{count} minutes ago"; +"lng_date_relative_hours_ago#one" = "{count} hour ago"; +"lng_date_relative_hours_ago#other" = "{count} hours ago"; +"lng_date_relative_days_ago#one" = "{count} day ago"; +"lng_date_relative_days_ago#other" = "{count} days ago"; +"lng_date_relative_months_ago#one" = "{count} month ago"; +"lng_date_relative_months_ago#other" = "{count} months ago"; +"lng_date_relative_years_ago#one" = "{count} year ago"; +"lng_date_relative_years_ago#other" = "{count} years ago"; + +"lng_date_relative_in_seconds#one" = "in {count} second"; +"lng_date_relative_in_seconds#other" = "in {count} seconds"; +"lng_date_relative_in_minutes#one" = "in {count} minute"; +"lng_date_relative_in_minutes#other" = "in {count} minutes"; +"lng_date_relative_in_hours#one" = "in {count} hour"; +"lng_date_relative_in_hours#other" = "in {count} hours"; +"lng_date_relative_in_days#one" = "in {count} day"; +"lng_date_relative_in_days#other" = "in {count} days"; +"lng_date_relative_in_months#one" = "in {count} month"; +"lng_date_relative_in_months#other" = "in {count} months"; +"lng_date_relative_in_years#one" = "in {count} year"; +"lng_date_relative_in_years#other" = "in {count} years"; + +"lng_context_copy_date" = "Copy Date"; +"lng_context_add_to_calendar" = "Add to Calendar"; +"lng_context_set_reminder" = "Set a Reminder"; +"lng_reminder_scheduled_in" = "Reminder scheduled in {link}."; "lng_spellchecker_submenu" = "Spelling"; "lng_spellchecker_add" = "Add to Dictionary"; @@ -3950,7 +5983,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration."; "lng_payments_webview_install_edge" = "Please install {link}."; -"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkitgtk-6.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager."; +"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-4.1/webkit2gtk-4.0) using your package manager."; "lng_payments_webview_update_windows" = "Please update your system to Windows 8.1 or later."; "lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost."; "lng_payments_receipt_label" = "Receipt"; @@ -4029,6 +6062,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_status_failed" = "failed to connect"; "lng_call_status_ringing" = "ringing..."; "lng_call_status_busy" = "line busy"; +"lng_call_status_group_invite" = "Telegram Group Call"; "lng_call_status_sure" = "Click on the Camera icon if you want to start a video call."; "lng_call_fingerprint_tooltip" = "If the emoji on {user}'s screen are the same, this call is 100% secure"; @@ -4038,6 +6072,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_error_camera_not_started" = "You can switch to video call once you're connected."; "lng_call_error_camera_outdated" = "{user}'s app does not support video calls. They need to update their app before you can call them."; "lng_call_error_audio_io" = "There seems to be a problem with your sound card. Please make sure that your computer's speakers and microphone are working and try again."; +"lng_call_error_add_not_started" = "You can add more people once you're connected."; "lng_call_bar_hangup" = "End call"; "lng_call_leave_to_other_sure" = "End your active call and join this video chat?"; @@ -4056,16 +6091,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_outgoing" = "Outgoing call"; "lng_call_video_outgoing" = "Outgoing video call"; +"lng_call_group_outgoing" = "Outgoing group call"; "lng_call_incoming" = "Incoming call"; "lng_call_video_incoming" = "Incoming video call"; +"lng_call_group_incoming" = "Incoming group call"; "lng_call_missed" = "Missed call"; "lng_call_video_missed" = "Missed video call"; +"lng_call_group_missed" = "Missed group call"; "lng_call_cancelled" = "Canceled call"; "lng_call_video_cancelled" = "Canceled video call"; "lng_call_declined" = "Declined call"; "lng_call_video_declined" = "Declined video call"; +"lng_call_group_declined" = "Declined group call"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; +"lng_call_invitation" = "Group call invitation"; +"lng_call_ongoing" = "Ongoing group call"; "lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_comment" = "Comment (optional)"; @@ -4074,6 +6115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_start_video" = "Start Video"; "lng_call_stop_video" = "Stop Video"; "lng_call_screencast" = "Screencast"; +"lng_call_add_people" = "Add People"; "lng_call_end_call" = "End Call"; "lng_call_mute_audio" = "Mute"; "lng_call_unmute_audio" = "Unmute"; @@ -4092,10 +6134,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_raised_hand_status" = "wants to speak"; "lng_group_call_settings" = "Settings"; "lng_group_call_video" = "Video"; +"lng_group_call_message" = "Message"; "lng_group_call_screen_share_start" = "Share Screen"; "lng_group_call_screen_share_stop" = "Stop Sharing"; "lng_group_call_screen_title" = "Screen {index}"; "lng_group_call_screen_share_audio" = "Share System Audio"; +"lng_group_call_sharing_screen_options" = "Sharing Options"; +"lng_group_call_choose_source" = "Choose Source"; "lng_group_call_unmute" = "Unmute"; "lng_group_call_unmute_sub" = "Hold space bar to temporarily unmute."; "lng_group_call_you_are_live" = "You are Live"; @@ -4105,8 +6150,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_connecting" = "Connecting..."; "lng_group_call_leave" = "Leave"; "lng_group_call_leave_title" = "Leave video chat"; +"lng_group_call_leave_title_call" = "Leave group call"; "lng_group_call_leave_title_channel" = "Leave live stream"; "lng_group_call_leave_sure" = "Do you want to leave this video chat?"; +"lng_group_call_leave_sure_call" = "Do you want to leave this group call?"; "lng_group_call_leave_sure_channel" = "Are you sure you want to leave this live stream?"; "lng_group_call_close" = "Close"; "lng_group_call_close_sure" = "Video chat is scheduled. You can abort it or just close this panel."; @@ -4136,16 +6183,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_also_end_channel" = "End live stream"; "lng_group_call_settings_title" = "Settings"; "lng_group_call_invite" = "Invite Members"; +"lng_group_call_invite_conf" = "Add People"; "lng_group_call_invited_status" = "invited"; +"lng_group_call_calling_status" = "calling..."; +"lng_group_call_blockchain_only_status" = "listening"; "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; "lng_group_call_invite_button" = "Invite"; +"lng_group_call_confcall_add" = "Call"; "lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}». Add them to the group?"; "lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}». Add them to the group?"; "lng_group_call_add_to_group_all" = "Those users aren't members of «{group}». Add them to the group?"; "lng_group_call_invite_members" = "Group members"; "lng_group_call_invite_search_results" = "Search results"; +"lng_group_call_invite_limit" = "This is currently the maximum allowed number of participants."; "lng_group_call_new_muted" = "Mute new participants"; +"lng_group_call_enable_messages" = "Enable messages"; "lng_group_call_speakers" = "Speakers"; "lng_group_call_microphone" = "Microphone"; "lng_group_call_push_to_talk" = "Push-to-Talk Shortcut"; @@ -4183,6 +6236,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_pin_screen" = "Pin screencast"; "lng_group_call_context_unpin_screen" = "Unpin screencast"; "lng_group_call_context_remove" = "Remove"; +"lng_group_call_context_cancel_invite" = "Discard invite"; +"lng_group_call_context_stop_ringing" = "Stop calling"; +"lng_group_call_context_ban_from_call" = "Ban from call"; "lng_group_call_remove_channel" = "Remove {channel} from the video chat and ban them?"; "lng_group_call_remove_channel_from_channel" = "Remove {channel} from the live stream?"; "lng_group_call_mac_access" = "Telegram Desktop does not have access to system wide keyboard input required for Push to Talk."; @@ -4291,8 +6347,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_rtmp_viewers#one" = "{count} viewer"; "lng_group_call_rtmp_viewers#other" = "{count} viewers"; -"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; +"lng_confcall_join_title" = "Group Call"; +"lng_confcall_join_text" = "You are invited to join a Telegram Call."; +"lng_confcall_join_text_inviter" = "{user} is inviting you to join a Telegram Call."; +"lng_confcall_already_joined_one" = "{user} already joined this call."; +"lng_confcall_already_joined_two" = "{user} and {other} already joined this call."; +"lng_confcall_already_joined_three" = "{user}, {other} and {third} already joined this call."; +"lng_confcall_already_joined_many#one" = "{user}, {other} and **{count}** other person already joined this call."; +"lng_confcall_already_joined_many#other" = "{user}, {other} and **{count}** other people already joined this call."; +"lng_confcall_join_button" = "Join Group Call"; +"lng_confcall_create_call" = "Start New Call"; +"lng_confcall_create_call_description#one" = "You can add up to {count} participant to a call."; +"lng_confcall_create_call_description#other" = "You can add up to {count} participants to a call."; +"lng_confcall_create_title" = "New Call"; +"lng_confcall_create_link" = "Create Call Link"; +"lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; +"lng_confcall_link_revoke" = "Revoke link"; +"lng_confcall_link_revoked_title" = "Link Revoked"; +"lng_confcall_link_inactive" = "This link is no longer active."; +"lng_confcall_link_revoked_text" = "A new link has been generated."; +"lng_confcall_link_title" = "Call Link"; +"lng_confcall_link_about" = "Anyone on Telegram can join your call by following the link below."; +"lng_confcall_link_or" = "or"; +"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; +"lng_confcall_link_join_link" = "Open call {arrow}"; +"lng_confcall_inactive_title" = "Start Group Call"; +"lng_confcall_inactive_about" = "This call is no longer active.\nYou can start a new one."; +"lng_confcall_invite_done_user" = "You're calling {user} to join."; +"lng_confcall_invite_done_many#one" = "You're calling **{count} person** to join."; +"lng_confcall_invite_done_many#other" = "You're calling **{count} people** to join."; +"lng_confcall_invite_already_user" = "{user} is already in the call."; +"lng_confcall_invite_already_many#one" = "**{count} person** is already in the call."; +"lng_confcall_invite_already_many#other" = "**{count} people** are already in the call."; +"lng_confcall_invite_privacy_user" = "You cannot call {user} because of their privacy settings."; +"lng_confcall_invite_privacy_many#one" = "You cannot call **{count} person** because of their privacy settings."; +"lng_confcall_invite_privacy_many#other" = "You cannot call **{count} people** because of their privacy settings."; +"lng_confcall_invite_fail_user" = "Couldn't call {user} to join."; +"lng_confcall_invite_fail_many#one" = "Couldn't call **{count} person** to join."; +"lng_confcall_invite_fail_many#other" = "Couldn't call **{count} people** to join."; +"lng_confcall_invite_kicked_user" = "{user} was banned from the call."; +"lng_confcall_invite_kicked_many#one" = "**{count} person** was removed from the call."; +"lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; +"lng_confcall_not_accessible" = "This call is no longer accessible."; +"lng_confcall_participants_limit" = "This call reached the participants limit."; +"lng_confcall_sure_remove" = "Remove {user} from the call?"; +"lng_confcall_e2e_badge" = "End-to-End Encrypted"; +"lng_confcall_e2e_badge_small" = "E2E Encrypted"; +"lng_confcall_e2e_about" = "These four emoji represent the call's encryption key. They must match for all participants and change when someone joins or leaves."; +"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; "lng_player_message_today" = "today at {time}"; "lng_player_message_yesterday" = "yesterday at {time}"; "lng_player_message_date" = "{date} at {time}"; @@ -4308,9 +6411,43 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_edit_admin_header" = "What can this admin do?"; "lng_rights_edit_admin_rank_name" = "Custom title"; "lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'."; +"lng_context_add_my_tag" = "Add tag"; +"lng_context_edit_my_tag" = "Edit tag"; +"lng_context_add_member_tag" = "Add member tag"; +"lng_context_edit_member_tag" = "Edit member tag"; +"lng_rights_edit_tag_title" = "Edit tag"; +"lng_rights_tag_about" = "Add short tag next to {name}'s name."; +"lng_rights_tag_about_self" = "Share your role, title, or how you're known in this group. Your tag is visible to all members."; + +"lng_tag_info_title_user" = "Member Tags"; +"lng_tag_info_title_admin" = "Admin Tags"; +"lng_tag_info_title_owner" = "Owner Tags"; +"lng_tag_info_text_user" = "This grey tag {emoji} is {author}'s member tag in {group}."; +"lng_tag_info_text_admin" = "This green tag {emoji} is {author}'s admin tag in {group}."; +"lng_tag_info_text_owner" = "This purple tag {emoji} is {author}'s owner tag in {group}."; +"lng_tag_info_preview_member" = "Member Tag"; +"lng_tag_info_preview_admin" = "Admin Tag"; +"lng_tag_info_preview_owner" = "Owner Tag"; +"lng_tag_info_add_my_tag" = "Add My Tag"; +"lng_tag_info_edit_my_tag" = "Edit My Tag"; +"lng_tag_info_admins_only" = "Only admins can change tags in this group."; + +"lng_rights_promote_member" = "Promote to Admin"; +"lng_rights_remove_member" = "Remove from Group"; +"lng_rights_dismiss_admin" = "Dismiss Admin"; "lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights."; "lng_rights_about_add_admins_no" = "This admin will not be able to add new admins."; -"lng_rights_about_by" = "This admin promoted by {user} at {date}."; +"lng_rights_about_process_join_requests" = "Allow this bot to approve new members using its own interfaces, such as captchas."; +"lng_rights_about_by" = "This admin promoted by {user} on {date}."; + +"lng_guard_bot_approve_title" = "Approve New Members"; +"lng_guard_bot_approve_text" = "New members will now need approval from {bot} before they can send messages in this group."; +"lng_guard_bot_approve_text_channel" = "New members will now need approval from {bot} before they can join this channel."; +"lng_guard_bot_approve_confirm" = "Enable"; +"lng_guard_bot_replace_title" = "Replace Bot"; +"lng_guard_bot_replace_text" = "{from} is currently processing join requests. Switch to {to} instead?"; +"lng_guard_bot_replace_confirm" = "Use {bot}"; +"lng_guard_bot_replace_cancel" = "Keep {bot}"; "lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin."; "lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user."; @@ -4333,6 +6470,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_boosts_no_restrict" = "Do not restrict boosters"; "lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media."; "lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; +"lng_rights_charge_stars" = "Charge Stars for Messages"; +"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages."; +"lng_rights_charge_price" = "Set price per message"; +"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message."; "lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}."; "lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time."; @@ -4340,6 +6481,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_slowmode_seconds#one" = "{count} second"; "lng_slowmode_seconds#other" = "{count} seconds"; +"lng_payment_confirm_title" = "Confirm payment"; +"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message."; +"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message."; +"lng_payment_confirm_amount#one" = "**{count}** Star"; +"lng_payment_confirm_amount#other" = "**{count}** Stars"; +"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages."; +"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages."; +"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages."; +"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages."; +"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?"; +"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?"; +"lng_payment_confirm_dont_ask" = "Don't ask me again"; +"lng_payment_confirm_button#one" = "Pay for {count} Message"; +"lng_payment_confirm_button#other" = "Pay for {count} Messages"; +"lng_payment_bar_text" = "{name} must pay {cost} for each message to you."; +"lng_payment_bar_button" = "Remove Fee"; +"lng_payment_refund_title" = "Remove Fee"; +"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; +"lng_payment_refund_channel" = "Do you want to allow {name} to message the channel for free?"; +"lng_payment_refund_also#one" = "Refund already paid {count} Star"; +"lng_payment_refund_also#other" = "Refund already paid {count} Stars"; +"lng_payment_refund_confirm" = "Confirm"; + "lng_rights_gigagroup_title" = "Broadcast group"; "lng_rights_gigagroup_convert" = "Convert to Broadcast Group"; "lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them."; @@ -4365,6 +6529,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_channel_edit_stories" = "Edit stories of others"; "lng_rights_channel_delete_stories" = "Delete stories of others"; "lng_rights_channel_manage_calls" = "Manage live streams"; +"lng_rights_channel_manage_direct" = "Manage direct messages"; "lng_rights_group_info" = "Change group info"; "lng_rights_group_ban" = "Ban users"; "lng_rights_group_invite_link" = "Invite users via link"; @@ -4372,15 +6537,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_group_pin" = "Pin messages"; "lng_rights_group_topics" = "Manage topics"; "lng_rights_group_add_topics" = "Create topics"; +"lng_rights_group_edit_rank" = "Edit own tags"; +"lng_rights_group_edit_rank_single" = "Edit own tag"; "lng_rights_group_manage_calls" = "Manage video chats"; "lng_rights_group_delete" = "Delete messages"; "lng_rights_group_anonymous" = "Remain anonymous"; +"lng_rights_group_manage_ranks" = "Edit member tags"; "lng_rights_add_admins" = "Add new admins"; +"lng_rights_group_process_join_requests" = "Process join requests"; "lng_rights_chat_send_text" = "Send messages"; "lng_rights_chat_send_media" = "Send media"; "lng_rights_chat_send_stickers" = "Send stickers & GIFs"; "lng_rights_chat_send_links" = "Embed links"; -"lng_rights_chat_send_polls" = "Send polls"; +"lng_rights_chat_send_reactions" = "Reactions"; +"lng_rights_chat_send_polls" = "Polls"; "lng_rights_chat_add_members" = "Add members"; "lng_rights_chat_photos" = "Photos"; "lng_rights_chat_videos" = "Video files"; @@ -4389,8 +6559,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_chat_files" = "Files"; "lng_rights_chat_voice_messages" = "Voice messages"; "lng_rights_chat_video_messages" = "Video messages"; -"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}."; -"lng_rights_chat_banned_by" = "Banned by {user} at {date}."; +"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}."; +"lng_rights_chat_banned_by" = "Banned by {user} on {date}."; "lng_rights_chat_banned_until_header" = "Restricted until"; "lng_rights_chat_banned_forever" = "Forever"; "lng_rights_chat_banned_day#one" = "For {count} day"; @@ -4404,6 +6574,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_transfer_channel" = "Transfer channel ownership"; "lng_rights_transfer_check" = "Security check"; "lng_rights_transfer_check_about" = "You can transfer this group to {user} only if you have:"; +"lng_rights_transfer_check_about_channel" = "You can transfer this channel to {user} only if you have:"; "lng_rights_transfer_check_password" = "• Enabled **Two-Step Verification** more than **7 days** ago."; "lng_rights_transfer_check_session" = "• Logged in on this device more than **24 hours** ago."; "lng_rights_transfer_check_later" = "Please come back later."; @@ -4430,6 +6601,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_gifs" = "The admins of this group have restricted your ability to send GIFs."; "lng_restricted_send_inline" = "The admins of this group have restricted your ability to send inline content."; "lng_restricted_send_polls" = "The admins of this group have restricted your ability to send polls."; +"lng_restricted_send_reactions_click" = "You cannot send reactions in this chat."; "lng_restricted_boost_group" = "Boost this group to send messages"; "lng_restricted_send_message_until" = "The admins of this group have restricted you from sending messages until {date}, {time}."; @@ -4457,6 +6629,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_polls_all" = "Sorry, sending polls is not allowed in this group."; "lng_restricted_send_public_polls" = "Sorry, polls with visible votes can't be forwarded to channels."; +"lng_restricted_send_todo_lists" = "Sorry, Checklists can't be forwarded to channels."; "lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel."; "lng_restricted_send_voice_messages" = "{user} doesn't accept voice messages."; @@ -4471,20 +6644,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers."; "lng_send_non_premium_message_toast_link" = "Telegram Premium"; +"lng_send_charges_stars_channel" = "{channel} charges {amount} per message to its admin."; +"lng_send_free_channel" = "Send a direct message to the administrator of {channel}."; +"lng_send_charges_stars_text" = "{user} charges {amount} for each message."; +"lng_send_charges_stars_go" = "Buy Stars"; + "lng_exceptions_list_title" = "Exceptions"; "lng_removed_list_title" = "Removed users"; "lng_admin_log_title_all" = "All actions"; "lng_admin_log_title_selected" = "Selected actions"; -"lng_admin_log_filter" = "Filter"; "lng_admin_log_filter_title" = "Filter"; "lng_admin_log_filter_all_actions" = "All actions"; "lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type"; "lng_admin_log_filter_actions_member_section" = "Members And Admins"; +"lng_admin_log_filter_actions_subscriber_section" = "Subscribers And Admins"; "lng_admin_log_filter_restrictions" = "New restrictions"; "lng_admin_log_filter_admins_new" = "Admin rights"; "lng_admin_log_filter_members_new" = "New members"; +"lng_admin_log_filter_subscribers_new" = "New subscribers"; "lng_admin_log_filter_actions_settings_section" = "Group Settings"; +"lng_admin_log_filter_actions_channel_settings_section" = "Channel Settings"; "lng_admin_log_filter_info_group" = "Group info"; "lng_admin_log_filter_info_channel" = "Channel info"; "lng_admin_log_filter_actions_messages_section" = "Messages"; @@ -4495,12 +6675,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_filter_voice_chats_channel" = "Live stream"; "lng_admin_log_filter_invite_links" = "Invite links"; "lng_admin_log_filter_members_removed" = "Members leaving"; +"lng_admin_log_filter_subscribers_removed" = "Subscribers leaving"; "lng_admin_log_filter_topics" = "Topics"; "lng_admin_log_filter_sub_extend" = "Subscription Renewals"; +"lng_admin_log_filter_edit_rank" = "Tag Changes"; "lng_admin_log_filter_all_admins" = "All users and admins"; "lng_admin_log_filter_actions_admins_subtitle" = "Filter actions by admins"; "lng_admin_log_filter_actions_admins_section" = "Show Actions by All Admins"; -"lng_admin_log_about" = "What is this?"; "lng_admin_log_about_text" = "This is a list of all notable actions by group members and admins in the last 48 hours."; "lng_admin_log_about_text_channel" = "This is a list of all service actions taken by the channel's admins in the last 48 hours."; "lng_admin_log_no_results_title" = "No actions found"; @@ -4552,6 +6733,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_edited_message" = "{from} edited message:"; "lng_admin_log_previous_message" = "Original message"; "lng_admin_log_deleted_message" = "{from} deleted message:"; +"lng_admin_log_deleted_messages_collapsed#one" = "{from} deleted {count} message from {names} ({link})."; +"lng_admin_log_deleted_messages_collapsed#other" = "{from} deleted {count} messages from {names} ({link})."; +"lng_admin_log_show_all" = "Show all"; +"lng_admin_log_hide_all" = "Hide all"; +"lng_admin_log_expand_more#one" = "Show {count} More Message"; +"lng_admin_log_expand_more#other" = "Show {count} More Messages"; "lng_admin_log_sent_message" = "{from} sent this message:"; "lng_admin_log_participant_joined" = "{from} joined the group"; "lng_admin_log_participant_joined_channel" = "{from} joined the channel"; @@ -4570,6 +6757,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_stopped_poll" = "{from} stopped poll:"; "lng_admin_log_invited" = "invited {user}"; "lng_admin_log_banned" = "banned {user}"; +"lng_admin_log_banned_until" = "banned {user} {until}"; "lng_admin_log_unbanned" = "unbanned {user}"; "lng_admin_log_restricted" = "changed restrictions for {user} {until}"; "lng_admin_log_promoted" = "changed privileges for {user}"; @@ -4605,6 +6793,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; +"lng_admin_log_autotranslate_enabled" = "{from} enabled automatic translation"; +"lng_admin_log_autotranslate_disabled" = "{from} disabled automatic translation"; "lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}"; "lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}"; "lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}"; @@ -4661,6 +6851,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_banned_send_stickers" = "Send stickers & GIFs"; "lng_admin_log_banned_embed_links" = "Embed links"; "lng_admin_log_banned_send_polls" = "Send polls"; +"lng_admin_log_banned_send_reactions" = "Send reactions"; "lng_admin_log_admin_change_info" = "Change info"; "lng_admin_log_admin_post_messages" = "Post messages"; "lng_admin_log_admin_edit_messages" = "Edit messages"; @@ -4673,13 +6864,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_admin_invite_users" = "Add users"; "lng_admin_log_admin_invite_link" = "Invite users via link"; "lng_admin_log_admin_pin_messages" = "Pin messages"; +"lng_admin_log_banned_edit_rank" = "Edit own tags"; +"lng_admin_log_banned_edit_rank_single" = "Edit own tag"; "lng_admin_log_admin_manage_topics" = "Manage topics"; "lng_admin_log_admin_create_topics" = "Create topics"; "lng_admin_log_admin_manage_calls" = "Manage video chats"; "lng_admin_log_admin_manage_calls_channel" = "Manage live streams"; +"lng_admin_log_admin_manage_direct" = "Manage direct messages"; +"lng_admin_log_admin_manage_ranks" = "Edit member tags"; "lng_admin_log_admin_add_admins" = "Add new admins"; "lng_admin_log_subscription_extend" = "{name} renewed subscription until {date}"; +"lng_admin_log_changed_rank_from" = "{from} changed tag for {user} from \"{previous}\" to \"{tag}\""; +"lng_admin_log_set_rank" = "{from} set tag for {user} to \"{tag}\""; +"lng_admin_log_removed_rank" = "{from} removed tag for {user} (was \"{previous}\")"; +"lng_admin_log_changed_own_rank_from" = "{from} changed own tag from \"{previous}\" to \"{tag}\""; +"lng_admin_log_set_own_rank" = "{from} set own tag to \"{tag}\""; +"lng_admin_log_removed_own_rank" = "{from} removed own tag (was \"{previous}\")"; + "lng_admin_log_antispam_menu_report" = "Report False Positive"; "lng_admin_log_antispam_menu_report_toast" = "You can manage anti-spam settings in {link}."; "lng_admin_log_antispam_menu_report_toast_link" = "Group Info > Administrators"; @@ -4714,6 +6916,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forward_show_captions" = "Show captions"; "lng_forward_change_recipient" = "Change recipient"; "lng_forward_sender_names_removed" = "Sender names removed"; +"lng_forward_header_short" = "Forward"; +"lng_forward_action_show_sender" = "Show Sender Name"; +"lng_forward_action_show_senders" = "Show Sender Names"; +"lng_forward_action_hide_sender" = "Hide Sender Name"; +"lng_forward_action_hide_senders" = "Hide Sender Names"; +"lng_forward_action_show_caption" = "Show Caption"; +"lng_forward_action_show_captions" = "Show Captions"; +"lng_forward_action_hide_caption" = "Hide Caption"; +"lng_forward_action_hide_captions" = "Hide Captions"; +"lng_forward_action_change_recipient" = "Change Recipient"; +"lng_forward_action_remove" = "Do Not Forward"; "lng_passport_title" = "Telegram Passport"; "lng_passport_request1" = "{bot} requests access to your personal data"; @@ -4856,12 +7069,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices."; "lng_export_option_stories" = "Story archive"; "lng_export_option_stories_about" = "All stories you posted from Telegram mobile apps."; +"lng_export_option_profile_music" = "Music on Profiles"; +"lng_export_option_profile_music_about" = "All tracks you saved to your playlist."; "lng_export_option_sessions" = "Active sessions"; "lng_export_option_sessions_about" = "We may store this to display your connected devices in Settings > Privacy & Security > Show all sessions."; "lng_export_header_other" = "Other"; "lng_export_option_other" = "Miscellaneous data"; "lng_export_option_other_about" = "Other types of data not mentioned above (beta)."; "lng_export_header_chats" = "Chat export settings"; +"lng_export_header_topic" = "Topic export settings"; "lng_export_option_personal_chats" = "Personal chats"; "lng_export_option_bot_chats" = "Bot chats"; "lng_export_option_private_groups" = "Private groups"; @@ -4931,9 +7147,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_language_not_ready_link" = "translation platform"; "lng_translate_box_error" = "Translate failed."; +"lng_translate_box_error_language_pack_not_installed" = "Translation requires a local language pack. Download it in System Settings."; "lng_translate_settings_subtitle" = "Translate Messages"; "lng_translate_settings_show" = "Show Translate Button"; +"lng_translate_settings_use_platform_mac" = "Use Apple Translations"; +"lng_translate_settings_use_platform_mac_about" = "Translation on macOS won't work until you download local language packs in System Settings."; +"lng_translate_settings_use_platform_linux" = "Use KDE's Crow Translate"; "lng_translate_settings_chat" = "Translate Entire Chats"; "lng_translate_settings_choose" = "Do Not Translate"; "lng_translate_settings_about" = "The 'Translate' button will appear in the context menu of messages containing text."; @@ -4952,6 +7172,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_public" = "Poll"; "lng_polls_anonymous_quiz" = "Anonymous Quiz"; "lng_polls_public_quiz" = "Quiz"; +"lng_polls_option_open_link_title" = "Open Link"; "lng_polls_closed" = "Final results"; "lng_polls_votes_count#one" = "{count} vote"; "lng_polls_votes_count#other" = "{count} votes"; @@ -4960,21 +7181,122 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_answers_count#other" = "{count} answers"; "lng_polls_answers_none" = "No answers"; "lng_polls_submit_votes" = "Vote"; +"lng_polls_view_stats" = "View Stats"; "lng_polls_view_results" = "View results"; +"lng_polls_stats_title" = "Poll Stats"; +"lng_polls_view_votes#one" = "View Votes ({count})"; +"lng_polls_view_votes#other" = "View Votes ({count})"; +"lng_polls_admin_votes#one" = "{count} vote {arrow}"; +"lng_polls_admin_votes#other" = "{count} votes {arrow}"; +"lng_polls_admin_back_vote" = "{arrow} Vote"; +"lng_polls_ends_in_days#one" = "ends in {count} day"; +"lng_polls_ends_in_days#other" = "ends in {count} days"; +"lng_polls_results_in_days#one" = "results in {count} day"; +"lng_polls_results_in_days#other" = "results in {count} days"; +"lng_polls_ends_in_time" = "ends in {time}"; +"lng_polls_results_in_time" = "results in {time}"; +"lng_polls_results_after_close" = "Results will appear after the poll ends."; "lng_polls_retract" = "Retract vote"; "lng_polls_stop" = "Stop poll"; "lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone."; "lng_polls_stop_sure" = "Stop"; +"lng_article_menu_item" = "Article"; +/* +"lng_article_editor_title" = "Article"; +"lng_article_insert_heading" = "Heading"; +"lng_article_insert_heading1" = "Heading 1"; +"lng_article_insert_heading2" = "Heading 2"; +"lng_article_insert_heading3" = "Heading 3"; +"lng_article_insert_heading4" = "Heading 4"; +"lng_article_insert_heading5" = "Heading 5"; +"lng_article_insert_heading6" = "Heading 6"; +"lng_article_insert_emoji" = "Emoji"; +"lng_article_insert_blockquote" = "Blockquote"; +"lng_article_insert_code" = "Code Block"; +"lng_article_insert_math" = "Math"; +"lng_article_insert_divider" = "Divider"; +"lng_article_insert_ordered_list" = "Ordered List"; +"lng_article_insert_bullet_list" = "Bullet List"; +"lng_article_insert_task_list" = "Task List"; +"lng_article_insert_pullquote" = "Pullquote"; +"lng_article_insert_media" = "Attach Media"; +"lng_article_insert_details" = "Details"; +"lng_article_insert_table" = "Table"; +"lng_article_insert_map" = "Map"; +"lng_article_limit_length" = "The article is too long."; +"lng_article_limit_depth" = "The article is nested too deeply."; +"lng_article_limit_blocks" = "The article has too many blocks."; +"lng_article_limit_columns" = "The article table has too many columns."; +"lng_article_limit_media" = "The article has too many media files."; +"lng_article_premium_required" = "Subscribe to {link} to be able to send rich articles."; +"lng_article_premium_required_link" = "Telegram Premium"; +"lng_article_table_change" = "Change Table"; +"lng_article_table_add_row" = "Add Row"; +"lng_article_table_add_column" = "Add Column"; +"lng_article_table_header" = "Header"; +"lng_article_table_align_center" = "Align Center"; +"lng_article_table_align_right" = "Align Right"; +"lng_article_table_align_middle" = "Align Middle"; +"lng_article_table_align_bottom" = "Align Bottom"; +"lng_article_table_split_cell" = "Split Cell"; +"lng_article_table_unite_cells" = "Unite Cells"; +"lng_article_table_delete_row" = "Delete Row"; +"lng_article_table_delete_rows" = "Delete Rows"; +"lng_article_table_delete_column" = "Delete Column"; +"lng_article_table_delete_columns" = "Delete Columns"; +"lng_article_table_delete_table" = "Delete Table"; +"lng_article_table_bordered" = "Bordered Table"; +"lng_article_table_striped" = "Striped Table"; +*/ +"lng_polls_menu_item" = "Poll"; "lng_polls_create" = "Create poll"; "lng_polls_create_title" = "New poll"; "lng_polls_create_question" = "Question"; "lng_polls_create_question_placeholder" = "Ask a question"; +"lng_polls_create_description_placeholder" = "Add Description (optional)"; "lng_polls_create_options" = "Poll options"; "lng_polls_create_option_add" = "Add an option..."; +"lng_polls_create_option_link" = "Link"; +"lng_polls_create_option_link_title" = "Add link"; +"lng_polls_create_option_link_description" = "Attach a link to this option."; +"lng_polls_create_option_link_placeholder" = "URL"; "lng_polls_create_limit#one" = "You can add {count} more option."; "lng_polls_create_limit#other" = "You can add {count} more options."; "lng_polls_create_maximum" = "You have added the maximum number of options."; "lng_polls_create_settings" = "Settings"; +"lng_polls_create_show_who_voted" = "Show Who Voted"; +"lng_polls_create_show_who_voted_about" = "Display voter name on each option."; +"lng_polls_create_allow_multiple_answers" = "Allow Multiple Answers"; +"lng_polls_create_allow_multiple_answers_about" = "Voters can select more than one option."; +"lng_polls_create_allow_adding_options" = "Allow Adding Options"; +"lng_polls_create_allow_adding_options_about" = "Participants can suggest new options."; +"lng_polls_create_allow_revoting" = "Allow Revoting"; +"lng_polls_create_allow_revoting_about" = "Voters can change their vote."; +"lng_polls_create_shuffle_options" = "Shuffle Options"; +"lng_polls_create_shuffle_options_about" = "Answers appear in random order for each voter."; +"lng_polls_create_set_correct_answer" = "Set Correct Answer"; +"lng_polls_create_set_correct_answer_about" = "Mark one option as the right answer."; +"lng_polls_create_set_correct_answer_about_multi" = "Mark one or more options as the right answer."; +"lng_polls_create_limit_duration" = "Limit Duration"; +"lng_polls_create_limit_duration_about" = "Automatically close the poll at a set time."; +"lng_polls_create_poll_duration" = "Poll Duration"; +"lng_polls_create_poll_ends" = "Poll ends"; +"lng_polls_create_hide_results" = "Hide results"; +"lng_polls_create_hide_results_about" = "If you switch this on, results will appear only after the poll closes."; +"lng_polls_create_restrict_to_subscribers" = "Restrict to Subscribers"; +"lng_polls_create_restrict_to_subscribers_about" = "Only subscribers who joined 24+ hours ago can vote."; +"lng_polls_create_limit_by_country" = "Limit by Country"; +"lng_polls_create_limit_by_country_about" = "Only users from selected countries can vote."; +"lng_polls_create_allowed_countries" = "Allowed Countries"; +"lng_polls_create_countries_count#one" = "{count} country"; +"lng_polls_create_countries_count#other" = "{count} countries"; +"lng_polls_create_choose_country" = "Please choose at least one country."; +"lng_polls_create_countries_limit#one" = "You can choose up to {count} country."; +"lng_polls_create_countries_limit#other" = "You can choose up to {count} countries."; +"lng_polls_create_duration_custom" = "Custom"; +"lng_polls_create_deadline_title" = "Deadline"; +"lng_polls_create_deadline_button" = "Set Deadline"; +"lng_polls_create_deadline_expired" = "The poll deadline has already passed. Please choose a new time."; "lng_polls_create_anonymous" = "Anonymous Voting"; "lng_polls_create_multiple_choice" = "Multiple Answers"; "lng_polls_create_quiz_mode" = "Quiz Mode"; @@ -4986,19 +7308,75 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_solution_title" = "Explanation"; "lng_polls_solution_placeholder" = "Add a Comment (Optional)"; "lng_polls_solution_about" = "Users will see this comment after choosing a wrong answer, good for educational purposes."; +"lng_polls_media_uploading_toast_title" = "Please wait"; +"lng_polls_media_uploading_toast" = "Poll media is still uploading..."; +"lng_polls_vote_restricted_subscribers_channel" = "Only subscribers of {channel} can vote."; +"lng_polls_vote_restricted_subscribers" = "Only subscribers can vote."; +"lng_polls_vote_restricted_subscribers_recent" = "Only subscribers who joined more than 24 hours ago can vote."; +"lng_polls_vote_restricted_countries_list" = "Only users from {countries} can vote."; +"lng_polls_vote_restricted_countries" = "Only users from selected countries can vote."; +"lng_polls_ends_toast" = "Results will appear after the poll ends."; "lng_polls_poll_results_title" = "Poll results"; "lng_polls_quiz_results_title" = "Quiz results"; "lng_polls_show_more#one" = "Show more ({count})"; "lng_polls_show_more#other" = "Show more ({count})"; "lng_polls_votes_collapse" = "Collapse"; +"lng_polls_vote_yesterday" = "yesterday"; +"lng_polls_option_added_by" = "Added by {user}"; +"lng_polls_context_ends" = "Results will appear after the poll ends."; +"lng_polls_add_option" = "Add an Option"; +"lng_polls_add_option_placeholder" = "Option text..."; +"lng_polls_max_options_reached" = "Maximum number of options reached."; +"lng_polls_add_option_duplicate" = "This option already exists."; +"lng_polls_add_option_closed" = "This poll has been closed."; +"lng_polls_add_option_error" = "Could not add the option. Please try again."; +"lng_polls_add_option_save" = "Save"; + +"lng_todo_title" = "Checklist"; +"lng_todo_title_group" = "Group Checklist"; +"lng_todo_title_user" = "Checklist"; +"lng_todo_completed#one" = "{count} of {total} completed"; +"lng_todo_completed#other" = "{count} of {total} completed"; +"lng_todo_completed_none" = "None of {total} completed"; +"lng_todo_menu_item" = "Checklist"; +"lng_todo_create" = "Create Checklist"; +"lng_todo_create_title" = "New Checklist"; +"lng_todo_create_title_placeholder" = "Title"; +"lng_todo_create_list" = "Tasks List"; +"lng_todo_create_list_add" = "Add a task..."; +"lng_todo_create_limit#one" = "You can add {count} more task."; +"lng_todo_create_limit#other" = "You can add {count} more tasks."; +"lng_todo_create_maximum" = "You have added the maximum number of tasks."; +"lng_todo_create_settings" = "Settings"; +"lng_todo_create_allow_add" = "Allow Others to Add Tasks"; +"lng_todo_create_allow_mark" = "Allow Others to Mark As Done"; +"lng_todo_create_button" = "Create"; +"lng_todo_choose_title" = "Please enter a title."; +"lng_todo_choose_tasks" = "Please enter at least one task."; + +"lng_todo_add_title" = "Add Tasks"; +"lng_todo_create_premium" = "Only subscribers of {link} can create Checklists."; +"lng_todo_add_premium" = "Only subscribers of {link} can add tasks."; +"lng_todo_mark_premium" = "Only subscribers of {link} can mark tasks as done."; +"lng_todo_premium_link" = "Telegram Premium"; +"lng_todo_mark_restricted" = "{user} has restricted others from marking tasks as done."; +"lng_todo_mark_forwarded" = "You can't change forwarded checklists."; "lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM."; "lng_outdated_title_bits" = "PLEASE SWITCH TO A 64-BIT OPERATING SYSTEM."; "lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}."; "lng_outdated_now" = "So Telegram Desktop can update to newer versions."; +"lng_screen_reader_bar_text" = "Telegram is working in Screen Reader mode."; +"lng_screen_reader_bar_disable" = "Disable"; +"lng_screen_reader_confirm_text" = "Telegram detected accessibility software is being used in your system and it is working in Screen Reader friendly mode.\n\nThis may result in unexpected changes to the way Telegram user interface works.\n\nIf you do not use Screen Reader software with Telegram, please disable this mode."; +"lng_screen_reader_confirm_disable" = "Disable"; +"lng_screen_reader_settings_title" = "Screen reader"; +"lng_screen_reader_settings_disable" = "Disable screen reader mode"; + "lng_filters_all" = "All chats"; +"lng_filters_all_short" = "All"; "lng_filters_setup" = "Edit"; "lng_filters_title" = "Folders"; "lng_filters_subtitle" = "My folders"; @@ -5014,6 +7392,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_edit" = "Edit Folder"; "lng_filters_setup_menu" = "Edit Folders"; "lng_filters_new_name" = "Folder name"; +"lng_filters_enable_animations" = "Enable animations"; +"lng_filters_disable_animations" = "Disable animations"; "lng_filters_add_chats" = "Add Chats"; "lng_filters_remove_chats" = "Add Chats to Exclude"; "lng_filters_include" = "Included chats"; @@ -5053,11 +7433,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_toast_add" = "{chat} added to {folder} folder"; "lng_filters_toast_remove" = "{chat} removed from {folder} folder"; "lng_filters_shareable_status" = "shareable folder"; +"lng_filters_view_subtitle" = "Tabs view"; +"lng_filters_vertical" = "Tabs on the left"; +"lng_filters_horizontal" = "Tabs at the top"; +"lng_filters_enable_tags" = "Show Folder Tags"; +"lng_filters_enable_tags_about" = "Display folder names for each chat in the chat list."; +"lng_filters_enable_tags_about_premium" = "Subscribe to **{link}** to display folder names for each chat in the chat list."; +"lng_filters_tag_color_subtitle" = "Folder color in chat list"; +"lng_filters_tag_color_about" = "Choose a color for the tag of this folder."; +"lng_filters_tag_color_no" = "No Tag"; "lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder."; "lng_filters_link" = "Share Folder"; "lng_filters_link_has" = "Invite links"; +"lng_filters_checkbox_remove_bot" = "Remove bot from all folders"; +"lng_filters_checkbox_remove_group" = "Remove group from all folders"; +"lng_filters_checkbox_remove_channel" = "Remove channel from all folders"; + "lng_filters_link_create" = "Create an Invite Link"; "lng_filters_link_cant" = "You can’t share folders which include or exclude specific chat types like 'Groups', 'Contacts', etc."; "lng_filters_link_about" = "Share access to some of this folder's groups and channels with others."; @@ -5130,11 +7523,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_chat_theme_change_wallpaper" = "Change Wallpaper"; "lng_chat_theme_title" = "Select theme"; "lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you have an unsent voice message."; +"lng_chat_theme_gift_replace" = "This gift is already your theme in the chat with {name}. Remove it there and use it here instead?"; "lng_photo_editor_menu_delete" = "Delete"; "lng_photo_editor_menu_flip" = "Flip"; "lng_photo_editor_menu_duplicate" = "Duplicate"; +"lng_photo_editor_text_style_plain" = "Plain"; +"lng_photo_editor_text_style_framed" = "Framed"; +"lng_photo_editor_text_style_semi_transparent" = "Semi-Transparent"; + +"lng_photo_editor_crop_original" = "Original"; +"lng_photo_editor_crop_square" = "Square"; +"lng_photo_editor_crop_free" = "Free"; + +"lng_photo_editor_corners_about" = "Choose how rounded the sticker corners should look."; +"lng_photo_editor_corners_large" = "Large"; +"lng_photo_editor_corners_medium" = "Medium"; +"lng_photo_editor_corners_small" = "Small"; +"lng_photo_editor_corners_none" = "None"; + "lng_voice_speed_slow" = "Slow"; "lng_voice_speed_normal" = "Normal"; "lng_voice_speed_medium" = "Medium"; @@ -5158,12 +7566,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_boost" = "Boost"; "lng_view_button_giftcode" = "Open"; "lng_view_button_iv" = "Instant View"; +"lng_view_button_full_article" = "Show more"; "lng_view_button_stickerset" = "View stickers"; "lng_view_button_emojipack" = "View emoji"; +"lng_view_button_style" = "View Style"; +"lng_view_button_collectible" = "View collectible"; +"lng_view_button_call" = "Join call"; +"lng_view_button_storyalbum" = "View Album"; +"lng_view_button_collection" = "View Collection"; +"lng_view_button_newbot" = "Create Bot"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; -"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; +"lng_sponsored_info_description1_linked" = "Unlike other apps, Telegram never uses your private data to target ads. {link}\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; +"lng_sponsored_info_description1_link" = "Learn more in the Privacy Policy"; +"lng_sponsored_info_description1_url" = "https://telegram.org/privacy#5-6-no-ads-based-on-user-data"; "lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together."; "lng_sponsored_info_menu" = "Advertiser info"; "lng_sponsored_info_submenu" = "Advertiser: {text}"; @@ -5172,13 +7589,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sponsored_revenued_subtitle" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:"; "lng_sponsored_revenued_info1_title" = "Respect Your Privacy"; "lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them."; +"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them."; +"lng_sponsored_revenued_info1_search_description" = "Ads on Telegram do not use your personal information and are based on the search query you entered."; "lng_sponsored_revenued_info2_title" = "Help the Channel Creator"; +"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer"; "lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed."; +"lng_sponsored_revenued_info2_bot_description" = "50% of the revenue from Telegram Ads goes to the developer of the mini app where they are displayed."; "lng_sponsored_revenued_info3_title" = "Can Be Removed"; "lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers."; "lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers."; +"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}."; +"lng_sponsored_revenued_info3_search_description" = "You can turn off ads by subscribing to Telegram Premium. {link}"; +"lng_sponsored_revenued_info3_search_link" = "Subscribe {arrow}"; "lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?"; "lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}"; +"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}"; +"lng_sponsored_revenued_footer_search_description" = "Anyone can create an ad to display in search results for any query. Check out the **Telegram Ad Platform** for details. {link}"; +"lng_sponsored_top_bar_hide" = "remove"; "lng_telegram_features_url" = "https://t.me/TelegramTips"; @@ -5190,6 +7617,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ringtones_box_title" = "Notification Sound"; "lng_ringtones_box_cloud_subtitle" = "Choose your tone"; +"lng_ringtones_box_volume" = "Volume"; "lng_ringtones_box_upload_choose" = "Choose a tone"; "lng_ringtones_box_upload_button" = "Upload Sound"; "lng_ringtones_box_about" = "Right click on any short voice note or MP3 file in chat and select \"Save for Notifications\". It will appear here."; @@ -5199,6 +7627,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ringtones_error_max_size" = "Sorry, but your file is too big. The maximum size for ringtones is {size}."; "lng_ringtones_error_max_duration" = "Sorry, but your file is too long. The maximum duration for ringtones is {duration}."; +"lng_bot_thread_edit" = "Edit Thread"; +"lng_bot_thread_title" = "Thread Name"; +"lng_bot_thread_choose_title_and_icon" = "Choose a thread name and icon"; + "lng_forum_topic_new" = "New Topic"; "lng_forum_topic_edit" = "Edit Topic"; "lng_forum_topic_title" = "Topic Name"; @@ -5229,6 +7661,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forum_messages#other" = "{count} messages"; "lng_forum_show_topics_list" = "Show Topics List"; +"lng_monoforum_choose_to_reply" = "Choose a message to reply."; + "lng_request_peer_requirements" = "Requirements"; "lng_request_peer_rights" = "You must have these admin rights: {rights}."; "lng_request_peer_rights_and" = "{rights} and {last}"; @@ -5275,6 +7709,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_request_channel_delete_messages" = "delete messages"; "lng_request_channel_add_subscribers" = "add subscribers"; "lng_request_channel_manage_livestreams" = "manage live streams"; +"lng_request_channel_manage_direct" = "manage direct messages"; "lng_request_channel_add_admins" = "add new admins"; "lng_request_channel_create" = "Create a New Channel for This"; @@ -5284,6 +7719,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_my_name" = "My Story"; "lng_stories_archive" = "Hide Stories"; "lng_stories_unarchive" = "Unhide Stories"; +"lng_stories_view_anonymously" = "View Anonymously"; "lng_stories_row_count#one" = "{count} Story"; "lng_stories_row_count#other" = "{count} Stories"; "lng_stories_views#one" = "{count} view"; @@ -5311,6 +7747,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_title" = "Story Archive"; "lng_stories_archive_about" = "Only you can see archived stories unless you choose to post them to your profile."; "lng_stories_channel_archive_about" = "Only admins of the channel can see archived stories unless they are posted to the channel page."; +"lng_stories_empty" = "Your stories will be here."; +"lng_stories_empty_channel" = "Channel posts will be here."; "lng_stories_reply_sent" = "Message sent."; "lng_stories_hidden_to_contacts" = "Stories from {user} were moved to the **Archive**."; "lng_stories_shown_in_chats" = "Stories from {user} were returned from the **Archive**."; @@ -5346,6 +7784,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_save_promo" = "Subscribe to {link} to save other people's stories that are not protected."; "lng_stories_reaction_as_message" = "Send reaction as a private message"; +"lng_stories_album_add" = "Add Album"; +"lng_stories_album_new_title" = "Create a New Album"; +"lng_stories_album_new_button" = "New Album"; +"lng_stories_album_new_text" = "Choose a name for your album and start adding your stories there."; +"lng_stories_album_new_ph" = "Title"; +"lng_stories_album_new_create" = "Create"; +"lng_stories_album_empty_title" = "Organize Your Stories"; +"lng_stories_album_empty_text" = "Add some of your stories to this album."; +"lng_stories_album_all" = "All Stories"; +"lng_stories_album_add_title" = "Add Stories"; +"lng_stories_album_add_button" = "Add Stories"; +"lng_stories_album_share" = "Share Album"; +"lng_stories_album_edit" = "Edit Name"; +"lng_stories_album_limit_title" = "Limit Reached"; +"lng_stories_album_limit_text" = "Please remove one of the existing albums to add a new one."; +"lng_stories_album_delete" = "Delete Album"; +"lng_stories_album_delete_sure" = "Are you sure you want to delete this album?"; +"lng_stories_album_delete_button" = "Delete"; +"lng_stories_album_add_to" = "Add to Album"; + "lng_stealth_mode_menu_item" = "Hide My View"; "lng_stealth_mode_title" = "Stealth Mode"; "lng_stealth_mode_unlock_about" = "Subscribe to Telegram Premium to watch stories without appearing in the list of viewers."; @@ -5356,6 +7814,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stealth_mode_next_about" = "Hide my views for the next 25 minutes."; "lng_stealth_mode_unlock" = "Unlock Stealth Mode"; "lng_stealth_mode_enable" = "Enable Stealth Mode"; +"lng_stealth_mode_enable_and_open" = "Enable and open the story"; "lng_stealth_mode_cooldown_in" = "Available in {left}"; "lng_stealth_mode_cooldown_tip" = "Please wait until **Stealth Mode** is ready to use again."; "lng_stealth_mode_enabled_tip_title" = "Stealth Mode On"; @@ -5365,6 +7824,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stealth_mode_already_about" = "The creators of stories you view in the next **{left}** won't see you in the viewers' lists."; "lng_stories_link_invalid" = "This link is broken or has expired."; +"lng_stories_live_finished" = "The live story has ended."; "lng_stats_title" = "Statistics"; "lng_stats_message_title" = "Message Statistic"; @@ -5470,6 +7930,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_tab_gifts#other" = "{count} Gifts"; "lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways"; +"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up."; "lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway"; "lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium"; "lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium"; @@ -5482,6 +7943,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_title" = "Monetization"; "lng_channel_earn_about" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}"; +"lng_channel_earn_about_bot" = "Telegram shares 50% of the revenue from ads displayed in your bot. {link}"; "lng_channel_earn_about_link" = "Learn more {emoji}"; "lng_channel_earn_overview_title" = "Rewards overview"; "lng_channel_earn_available" = "Rewards available for collection"; @@ -5514,8 +7976,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_cpm#one" = "{emoji} {count} CPM"; "lng_channel_earn_cpm#other" = "{emoji} {count} CPM"; "lng_channel_earn_learn_title" = "Earn From Your Channel"; +"lng_channel_earn_bot_learn_title" = "Earn From Your Bot"; "lng_channel_earn_learn_in_subtitle" = "Telegram Ads"; "lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel."; +"lng_channel_earn_learn_bot_in_about" = "Telegram can display ads in your bot."; "lng_channel_earn_learn_split_subtitle" = "50:50 revenue split"; "lng_channel_earn_learn_split_about" = "You can receive 50% of the ad revenue as rewards in TON."; "lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals"; @@ -5526,6 +7990,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels"; "lng_channel_earn_chart_top_hours" = "Ad impressions"; "lng_channel_earn_chart_revenue" = "Ad rewards"; +"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars"; "lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON"; "lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD"; "lng_channel_earn_currency_history" = "TON Transactions"; @@ -5536,6 +8001,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_chart_revenue" = "Revenue"; "lng_bot_earn_overview_title" = "Proceeds overview"; "lng_bot_earn_available" = "Available balance"; +"lng_bot_earn_reward" = "Total balance"; "lng_bot_earn_total" = "Total lifetime proceeds"; "lng_bot_earn_balance_title" = "Available balance"; "lng_bot_earn_balance_about" = "Stars from your total balance can be used for ads or withdrawn as rewards 21 days after they are earned."; @@ -5546,22 +8012,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_balance_button_locked" = "Withdraw"; "lng_bot_earn_balance_button_buy_ads" = "Buy Ads"; "lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}"; +"lng_self_earn_learn_credits_out_about" = "You can withdraw from 10 Stars using Fragment. {link}"; "lng_bot_earn_out_ph" = "Enter amount to withdraw"; +"lng_bot_earn_out_ph_max" = "Enter amount to withdraw (max. {amount})"; "lng_bot_earn_balance_password_title" = "Two-step verification"; "lng_bot_earn_balance_password_description" = "Please enter your password to collect."; "lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}."; "lng_bot_earn_credits_out_minimal_link#one" = "{count} star"; "lng_bot_earn_credits_out_minimal_link#other" = "{count} stars"; +"lng_bot_copy_text_tooltip" = "Copy to Clipboard: {text}"; "lng_contact_add" = "Add"; "lng_contact_send_message" = "Message"; "lng_iv_open_in_browser" = "Open in Browser"; +"lng_iv_click_to_view" = "Click to View"; "lng_iv_share" = "Share"; "lng_iv_join_channel" = "Join"; "lng_iv_window_title" = "Instant View"; "lng_iv_wrong_layout" = "Wrong layout?"; "lng_iv_not_supported" = "This link appears to be invalid."; +"lng_iv_not_found_in_message" = "This link appears to be invalid."; +"lng_iv_details_state_expanded" = "Expanded"; +"lng_iv_details_state_collapsed" = "Collapsed"; +"lng_iv_zoom_tooltip_ctrl" = "Hold Ctrl to zoom by 5%.\nHold Alt to zoom by 1%."; +"lng_iv_zoom_tooltip_cmd" = "Hold Cmd to zoom by 5%.\nHold Alt to zoom by 1%."; "lng_limit_download_title" = "Download speed limited"; "lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}."; @@ -5591,6 +8066,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_recent_chats" = "Chats"; "lng_recent_channels" = "Channels"; "lng_recent_apps" = "Apps"; +"lng_recent_posts" = "Posts"; +"lng_all_photos" = "Photos"; +"lng_all_videos" = "Videos"; +"lng_all_downloads" = "Downloads"; +"lng_all_links" = "Links"; +"lng_all_files" = "Files"; +"lng_all_music" = "Music"; +"lng_all_voice" = "Voice"; "lng_channels_none_title" = "No channels yet..."; "lng_channels_none_about" = "You are not currently subscribed to any channels."; "lng_channels_your_title" = "Channels you joined"; @@ -5599,6 +8082,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channels_recommended" = "Similar channels"; "lng_bot_apps_your" = "Apps you use"; "lng_bot_apps_popular" = "Grossing apps"; +"lng_bot_apps_which" = "Which apps are included here? {link}"; +"lng_bot_apps_which_link" = "Learn >"; +"lng_posts_title" = "Global Search"; +"lng_posts_start" = "Type a keyword to search all posts from public channels."; +"lng_posts_subscribe" = "Subscribe to Premium"; +"lng_posts_need_subscribe" = "Global search is a Premium feature."; +"lng_posts_search_button" = "Search {query}"; +"lng_posts_remaining#one" = "{count} free search remaining today"; +"lng_posts_remaining#other" = "{count} free searches remaining today"; +"lng_posts_subtitle_empty" = "Telegram News"; +"lng_posts_subtitle" = "Public posts"; +"lng_posts_limit_reached" = "Limit Reached"; +"lng_posts_limit_about#one" = "You can make up to {count} search query per day."; +"lng_posts_limit_about#other" = "You can make up to {count} search queries per day."; +"lng_posts_limit_search_paid" = "Search for {cost}"; +"lng_posts_limit_unlocks" = "free search unlocks in {duration}"; +"lng_posts_paid_spent#one" = "**{count} Star** spent on extra search."; +"lng_posts_paid_spent#other" = "**{count} Stars** spent on extra search."; + +"lng_popular_apps_info_title" = "Top Mini Apps"; +"lng_popular_apps_info_text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini apps in {bot} (as described {link}), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on the weekly average."; +"lng_popular_apps_info_bot" = "@botfather"; +"lng_popular_apps_info_here" = "here"; +"lng_popular_apps_info_url" = "https://core.telegram.org/bots/webapps#launching-the-main-mini-app"; +"lng_popular_apps_info_confirm" = "Understood"; "lng_font_box_title" = "Choose font family"; "lng_font_default" = "Default"; @@ -5615,6 +8123,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_search_tab_no_results_text" = "There were no results for \"{query}\"."; "lng_search_tab_no_results_retry" = "Try another hashtag."; "lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it."; +"lng_search_tab_try_in_all" = "Search in All Messages"; "lng_contact_details_button" = "View Contact"; "lng_contact_details_title" = "Contact details"; @@ -5638,6 +8147,125 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_qr_box_transparent_background" = "Transparent Background"; "lng_qr_box_font_size" = "Font size"; +"lng_frozen_bar_title" = "Your account is frozen!"; +"lng_frozen_bar_text" = "Click to view details {arrow}"; +"lng_frozen_restrict_title" = "Your account is frozen"; +"lng_frozen_restrict_text" = "Click to view details"; +"lng_frozen_title" = "Your Account is Frozen"; +"lng_frozen_subtitle1" = "Violation of Terms"; +"lng_frozen_text1" = "Your account was frozen for breaking Telegram's Terms and Conditions."; +"lng_frozen_subtitle2" = "Read-Only Mode"; +"lng_frozen_text2" = "You can access your account but can't send messages or take actions."; +"lng_frozen_subtitle3" = "Appeal Before Deactivation"; +"lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted."; +"lng_frozen_appeal_button" = "Submit an Appeal"; + +"lng_age_verify_title" = "Age Verification"; +"lng_age_verify_mobile" = "Please open this media in the official Telegram app for Android or iOS to verify your age."; +"lng_age_verify_here" = "This is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"lng_age_verify_button" = "Verify My Age"; +"lng_age_verify_sorry_title" = "Age Check Failed"; +"lng_age_verify_sorry_text" = "Sorry, you can't view 18+ content."; + +"lng_context_bank_card_copy" = "Copy Card Number"; +"lng_context_bank_card_copied" = "Card number copied to clipboard."; + +"lng_summarize_header_title" = "AI summary"; +"lng_summarize_header_about" = "Click to see original text"; + +"lng_calendar" = "Calendar"; + +"lng_new_window_tooltip_ctrl" = "Use Ctrl+Click to open in New Window."; +"lng_new_window_tooltip_cmd" = "Use Cmd+Click to open in New Window."; + +"lng_rename_file" = "Rename file"; + +"lng_sr_playback_order" = "Playback order"; +"lng_sr_player_close" = "Close media player"; +"lng_sr_group_call_menu" = "Video chat menu"; +"lng_sr_search_date" = "Search by date"; +"lng_sr_cancel_search" = "Cancel search"; +"lng_sr_clear_search" = "Clear search"; +"lng_sr_scroll_to_top" = "Scroll to top"; +"lng_sr_verified_badge" = "Verified"; +"lng_sr_bot_verified_badge" = "Verified Bot"; +"lng_sr_profile_menu" = "Profile menu"; +"lng_sr_close_panel" = "Close panel"; + +"lng_ai_compose_title" = "AI Editor"; +"lng_ai_compose_apply" = "Apply"; +"lng_ai_compose_tab_translate" = "Translate"; +"lng_ai_compose_tab_style" = "Style"; +"lng_ai_compose_tab_fix" = "Fix"; +"lng_ai_compose_original" = "Original"; +"lng_ai_compose_result" = "Result"; +"lng_ai_compose_before" = "Before"; +"lng_ai_compose_after" = "After"; +"lng_ai_compose_to_language" = "To {language}"; +"lng_ai_compose_name_style" = "{name} ({style})"; +"lng_ai_compose_style_neutral" = "Neutral"; +"lng_ai_compose_emojify" = "emojify"; +"lng_ai_compose_error" = "AI request failed."; +"lng_ai_compose_error_too_long" = "Sorry, this text is too long."; +"lng_ai_compose_tone_invalid" = "This style was deleted or the link is invalid."; +"lng_ai_compose_apply_unbound" = "No style bound to this shortcut. Open a style and toggle 'Apply with hotkey'."; +"lng_ai_compose_apply_empty" = "Type something first."; +"lng_ai_compose_bind_set_hotkey_short" = "Set apply hotkey…"; +"lng_ai_compose_bind_use_hotkey" = "Apply with {keys}"; +"lng_ai_compose_tooltip" = "Rewrite, translate, or correct your text using AI."; +"lng_ai_compose_flood_title" = "Daily limit reached"; +"lng_ai_compose_flood_text" = "Get {link} for **50x** more AI text transformations per day."; +"lng_ai_compose_flood_link" = "Telegram Premium"; +"lng_ai_compose_increase_limit" = "Increase Limit"; +"lng_ai_compose_select_style" = "Select Style"; +"lng_ai_compose_apply_style" = "Apply Style"; +"lng_ai_compose_style_tooltip" = "Choose Style"; +"lng_ai_compose_create_tone_title" = "New Style"; +"lng_ai_compose_edit_tone_title" = "Edit Style"; +"lng_ai_compose_tone_icon_title" = "Style Icon"; +"lng_ai_compose_tone_name" = "Name"; +"lng_ai_compose_tone_prompt" = "Prompt"; +"lng_ai_compose_tone_save" = "Save"; +"lng_ai_compose_tone_create" = "Create"; +"lng_ai_compose_tone_author" = "Add a link to my account"; +"lng_ai_compose_tone_name_placeholder" = "Style Name (for example, \"Pirate\")"; +"lng_ai_compose_tone_prompt_placeholder" = "Instructions (for example \"write in bold, nautical tone, light slang (aye, matey), vivid sea imagery, playful swagger, rhythmic phrasing, and adventurous mood\")"; +"lng_ai_compose_tone_edit" = "Edit Style"; +"lng_ai_compose_tone_share" = "Share Style"; +"lng_ai_compose_tone_remove" = "Remove Style"; +"lng_ai_compose_tone_delete" = "Delete Style"; +"lng_ai_compose_tone_remove_sure" = "Are you sure you want to remove this style?"; +"lng_ai_compose_tone_delete_sure" = "Are you sure you want to delete this style? It will be removed for everyone who installed it."; +"lng_ai_compose_tone_link_copied" = "Style link copied."; +"lng_ai_compose_author" = "Style by {user}"; +"lng_ai_compose_tone_warn_icon" = "Please choose an icon."; +"lng_ai_compose_tone_warn_name" = "Please choose a name."; +"lng_ai_compose_tone_warn_prompt" = "Please enter instructions."; +"lng_ai_compose_tone_created" = "{title} Style Created!"; +"lng_ai_compose_tone_updated" = "{title} Style Updated!"; +"lng_ai_compose_tone_created_description" = "Right click the style to edit or share the link."; +"lng_ai_compose_tone_preview_about" = "Add this style to instantly rewrite your messages."; +"lng_ai_compose_tone_preview_add" = "Add Style"; +"lng_ai_compose_tone_preview_add_example" = "Another example"; +"lng_ai_compose_tone_preview_used_by#one" = "Used by {count} person."; +"lng_ai_compose_tone_preview_used_by#other" = "Used by {count} people."; +"lng_ai_compose_tone_preview_created_by" = "Created by {user}"; +"lng_ai_compose_tone_added" = "Style Added"; +"lng_ai_compose_tone_removed" = "Style removed."; +"lng_ai_compose_tone_deleted" = "Style deleted."; +"lng_ai_compose_tone_added_description" = "Tap \"AI\" → \"{name}\" when typing your next long message."; +"lng_ai_compose_tone_saved_limit#one" = "Subscribe to {link} to save up to {premium_count} styles, or delete one of your **{count}** style to add another."; +"lng_ai_compose_tone_saved_limit#other" = "Subscribe to {link} to save up to {premium_count} styles, or delete one of your **{count}** styles to add another."; +"lng_ai_compose_tone_saved_limit_link" = "Premium"; +"lng_ai_compose_tone_saved_limit_final#one" = "You can save up to **{count}** style. Delete one to add another."; +"lng_ai_compose_tone_saved_limit_final#other" = "You can save up to **{count}** styles. Delete one to add another."; +"lng_sr_ai_compose_info" = "About AI Editor"; +"lng_sr_ai_compose_copy_result" = "Copy result"; +"lng_sr_ai_compose_expand_original" = "Expand original"; +"lng_sr_ai_compose_collapse_original" = "Collapse original"; + +"lng_send_as_file_tooltip" = "Send text as a file."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; @@ -5692,14 +8320,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mac_menu_new_channel" = "New Channel"; "lng_mac_menu_show" = "Show Telegram"; "lng_mac_menu_emoji_and_symbols" = "Emoji & Symbols"; +"lng_mac_menu_fullscreen" = "Toggle Full Screen"; "lng_mac_menu_player_pause" = "Pause"; "lng_mac_menu_player_resume" = "Resume"; "lng_mac_menu_player_next" = "Next"; "lng_mac_menu_player_previous" = "Previous"; +"lng_mac_menu_profiles" = "Profiles"; "lng_mac_touchbar_favorite_stickers" = "Favorite stickers"; "lng_mac_hold_to_quit" = "Hold {text} to Quit"; +"lng_sr_country_column_name" = "Country name"; +"lng_sr_languages_column_native" = "Native name"; +"lng_sr_languages_column_name" = "Language name"; + // Keys finished diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js index e44fd51a95ef47..628a22463760fe 100644 --- a/Telegram/Resources/picker_html/picker.js +++ b/Telegram/Resources/picker_html/picker.js @@ -51,8 +51,8 @@ var LocationPicker = { }, init: function (params) { mapboxgl.accessToken = params.token; - if (params.protocol) { - mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com'; + if (location.hostname != 'desktop-app-resource') { + mapboxgl.config.API_URL = location.protocol + '//' + location.host + '/api.mapbox.com'; } var options = { container: 'map', config: { diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index ac6ff1dbf4660e..ffadf0ee799b89 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -1,6 +1,7 @@ ../../animations/blocked_peers_empty.tgs + ../../animations/change_number.tgs ../../animations/filters.tgs ../../animations/cloud_filters.tgs ../../animations/local_passcode_enter.tgs @@ -8,6 +9,7 @@ ../../animations/cloud_password/password_input.tgs ../../animations/cloud_password/hint.tgs ../../animations/cloud_password/email.tgs + ../../animations/cloud_password/validate.tgs ../../animations/ttl.tgs ../../animations/discussion.tgs ../../animations/stats.tgs @@ -15,19 +17,65 @@ ../../animations/stats_earn.tgs ../../animations/voice_ttl_idle.tgs ../../animations/voice_ttl_start.tgs + ../../animations/chat/voice_to_video.tgs + ../../animations/chat/video_to_voice.tgs + ../../animations/chat/sparkles_emoji.tgs + ../../animations/chat/white_flag_emoji.tgs ../../animations/palette.tgs ../../animations/sleep.tgs ../../animations/greeting.tgs ../../animations/location.tgs - ../../animations/robot.tgs + ../../animations/settings/chat_automation.tgs ../../animations/writing.tgs ../../animations/hours.tgs ../../animations/phone.tgs ../../animations/chat_link.tgs + ../../animations/diamond.tgs ../../animations/collectible_username.tgs ../../animations/collectible_phone.tgs ../../animations/search.tgs ../../animations/noresults.tgs + ../../animations/hello_status.tgs + ../../animations/starref_link.tgs + ../../animations/media_forbidden.tgs + ../../animations/edit_peers/topics.tgs + ../../animations/edit_peers/topics_tabs.tgs + ../../animations/edit_peers/topics_list.tgs + ../../animations/edit_peers/direct_messages.tgs + ../../animations/no_chats.tgs + ../../animations/transcribe_loading.tgs + ../../animations/cake.tgs + ../../animations/camera_outline.tgs + ../../animations/photo_suggest_icon.tgs + ../../animations/toast/saved_messages.tgs + ../../animations/toast/tagged.tgs + ../../animations/my_gifts_empty.tgs + ../../animations/toast/chats_filter_in.tgs + ../../animations/toast/save_to_gallery.tgs + ../../animations/toast/save_to_music.tgs + ../../animations/toast/delete.tgs + ../../animations/toast/voip_invite.tgs + ../../animations/toast/copy.tgs + ../../animations/toast/mute.tgs + ../../animations/toast/unmute.tgs + ../../animations/toast/pin.tgs + ../../animations/toast/unpin.tgs + ../../animations/toast/chats_archived.tgs + ../../animations/toast/star_premium_2.tgs + ../../animations/toast/contact_check.tgs + ../../animations/rtmp.tgs + ../../animations/show_or_premium_lastseen.tgs + ../../animations/show_or_premium_readtime.tgs + ../../animations/passkeys.tgs + ../../animations/ban.tgs + ../../animations/cocoon.tgs + ../../animations/craft_failed.tgs + ../../animations/stop.tgs + ../../icons/poll/toast_hide_results.tgs + ../../icons/poll/uploading.tgs + + ../../animations/profile/profile_muting.tgs + ../../animations/profile/profile_unmuting.tgs ../../animations/dice/dice_idle.tgs ../../animations/dice/dart_idle.tgs @@ -39,6 +87,7 @@ ../../animations/dice/slot_back.tgs ../../animations/dice/slot_pull.tgs ../../animations/dice/winners.tgs + ../../animations/dice/dice_6.tgs ../../animations/star_reaction/appear.tgs ../../animations/star_reaction/center.tgs @@ -47,5 +96,24 @@ ../../animations/star_reaction/effect1.tgs ../../animations/star_reaction/effect2.tgs ../../animations/star_reaction/effect3.tgs + + ../../animations/photo_editor/pen.tgs + ../../animations/photo_editor/arrow.tgs + ../../animations/photo_editor/marker.tgs + ../../animations/photo_editor/eraser.tgs + ../../animations/photo_editor/blur.tgs + + ../../animations/swipe_action/archive.tgs + ../../animations/swipe_action/unarchive.tgs + ../../animations/swipe_action/delete.tgs + ../../animations/swipe_action/disabled.tgs + ../../animations/swipe_action/mute.tgs + ../../animations/swipe_action/unmute.tgs + ../../animations/swipe_action/pin.tgs + ../../animations/swipe_action/unpin.tgs + ../../animations/swipe_action/read.tgs + ../../animations/swipe_action/unread.tgs + ../../animations/craft_progress.tgs + ../../animations/cleaning_cache.tgs diff --git a/Telegram/Resources/qrc/telegram/bot_webview_shell.qrc b/Telegram/Resources/qrc/telegram/bot_webview_shell.qrc new file mode 100644 index 00000000000000..c19383ac049052 --- /dev/null +++ b/Telegram/Resources/qrc/telegram/bot_webview_shell.qrc @@ -0,0 +1,7 @@ + + + ../../bot_webview_shell_html/page.css + ../../bot_webview_shell_html/page.js + ../../bot_webview_shell_html/body.html + + diff --git a/Telegram/Resources/qrc/telegram/export.qrc b/Telegram/Resources/qrc/telegram/export.qrc index 290d24a30bf842..e3656ae94415ba 100644 --- a/Telegram/Resources/qrc/telegram/export.qrc +++ b/Telegram/Resources/qrc/telegram/export.qrc @@ -31,6 +31,8 @@ ../../export_html/images/section_contacts@2x.png ../../export_html/images/section_frequent.png ../../export_html/images/section_frequent@2x.png + ../../export_html/images/section_music.png + ../../export_html/images/section_music@2x.png ../../export_html/images/section_other.png ../../export_html/images/section_other@2x.png ../../export_html/images/section_photos.png diff --git a/Telegram/Resources/qrc/telegram/iv.qrc b/Telegram/Resources/qrc/telegram/iv.qrc deleted file mode 100644 index 1c9e9eb6ab9d72..00000000000000 --- a/Telegram/Resources/qrc/telegram/iv.qrc +++ /dev/null @@ -1,9 +0,0 @@ - - - ../../iv_html/page.css - ../../iv_html/page.js - ../../iv_html/highlight.9.12.0.css - ../../iv_html/highlight.9.12.0.js - ../../iv_html/morphdom-umd.min.2.7.2.js - - diff --git a/Telegram/Resources/qrc/telegram/sounds.qrc b/Telegram/Resources/qrc/telegram/sounds.qrc index d9f7cf8b5a1de0..1ffc48b05210e6 100644 --- a/Telegram/Resources/qrc/telegram/sounds.qrc +++ b/Telegram/Resources/qrc/telegram/sounds.qrc @@ -10,5 +10,6 @@ ../../sounds/group_call_connect.mp3 ../../sounds/group_call_end.mp3 ../../sounds/group_call_allowed.mp3 + ../../sounds/group_call_recording_start.mp3 diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 09f8b0fac79f1a..fda0ed1ef4bc1c 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -4,9 +4,15 @@ ../../art/bg_thumbnail.png ../../art/bg_initial.jpg ../../art/business_logo.png + ../../art/affiliate_logo.png ../../art/logo_256.png ../../art/logo_256_no_margin.png ../../art/themeimage.jpg + ../../art/round_placeholder.jpg + ../../art/cocoon.webp + ../../art/premium/flecks.png + ../../art/premium/star_texture.svg + ../../art/premium/coin_border.png ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme @@ -21,7 +27,10 @@ ../../icons/settings/dino.svg ../../icons/settings/star.svg ../../icons/settings/starmini.svg + ../../icons/settings/premium_dollar.svg ../../icons/tray_monochrome.svg + ../../icons/tray_monochrome_attention.svg + ../../icons/tray_monochrome_mute.svg ../../art/topic_icons/blue.svg ../../art/topic_icons/yellow.svg ../../art/topic_icons/violet.svg @@ -32,10 +41,17 @@ ../../art/topic_icons/general.svg ../../icons/info/edit/links_subscription.svg ../../icons/plane_white.svg + ../../art/dice/dice1.svg + ../../art/dice/dice2.svg + ../../art/dice/dice3.svg + ../../art/dice/dice4.svg + ../../art/dice/dice5.svg + ../../art/dice/dice6.svg ../../icons/calls/hands.lottie ../../icons/calls/voice.lottie + ../../icons/notify_toggle.lottie ../../icons/settings/devices/device_desktop_mac.lottie ../../icons/settings/devices/device_desktop_win.lottie ../../icons/settings/devices/device_linux.lottie diff --git a/Telegram/Resources/sounds/group_call_recording_start.mp3 b/Telegram/Resources/sounds/group_call_recording_start.mp3 new file mode 100644 index 00000000000000..0060e82c3737b9 Binary files /dev/null and b/Telegram/Resources/sounds/group_call_recording_start.mp3 differ diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 798a9e2207c171..94c6a129e4edf2 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="6.9.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index bddb3423bfaecd..b73eba45653842 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,6,3,0 - PRODUCTVERSION 5,6,3,0 + FILEVERSION 6,9,3,0 + PRODUCTVERSION 6,9,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.6.3.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2024" + VALUE "FileVersion", "6.9.3.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.6.3.0" + VALUE "ProductVersion", "6.9.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 84d811fbc8c1df..0703894f6a525d 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,6,3,0 - PRODUCTVERSION 5,6,3,0 + FILEVERSION 6,9,3,0 + PRODUCTVERSION 6,9,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.6.3.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2024" + VALUE "FileVersion", "6.9.3.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2026" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.6.3.0" + VALUE "ProductVersion", "6.9.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index e563e167534a53..4df8b238000087 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -283,7 +283,7 @@ int main(int argc, char *argv[]) cout << "Compression start, size: " << resultSize << "\n"; QByteArray compressed, resultCheck; -#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size diff --git a/Telegram/SourceFiles/_other/packer.h b/Telegram/SourceFiles/_other/packer.h index 4e5fbfc7ac042e..2c200eefd748f8 100644 --- a/Telegram/SourceFiles/_other/packer.h +++ b/Telegram/SourceFiles/_other/packer.h @@ -27,7 +27,7 @@ extern "C" { #include } // extern "C" -#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win #include #else #include diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 58bc10efadf615..67b74453e16a46 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -5,6 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ +#define _GLIBCXX_USE_CXX11_ABI 0 #include #include #include diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index 78e463c116fa64..9652e7feddee20 100644 --- a/Telegram/SourceFiles/api/api_authorizations.cpp +++ b/Telegram/SourceFiles/api/api_authorizations.cpp @@ -9,10 +9,13 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "base/unixtime.h" -#include "core/changelogs.h" #include "core/application.h" +#include "core/changelogs.h" #include "core/core_settings.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" +#include "main/main_session_settings.h" +#include "main/main_session.h" namespace Api { namespace { @@ -83,9 +86,21 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { } // namespace Authorizations::Authorizations(not_null api) -: _api(&api->instance()) { +: _api(&api->instance()) +, _autoconfirmPeriod([=] { + constexpr auto kFallbackCount = 604800; + return api->session().appConfig().get( + u"authorization_autoconfirm_period"_q, + kFallbackCount); +}) +, _saveUnreviewed([=] { + api->session().settings().setUnreviewed(_unreviewed); + api->session().saveSettingsDelayed(); +}) { + _unreviewed = api->session().settings().unreviewed(); + crl::on_main(&api->session(), [=] { removeExpiredUnreviewed(); }); Core::App().settings().deviceModelChanges( - ) | rpl::start_with_next([=](const QString &model) { + ) | rpl::on_next([=](const QString &model) { auto changed = false; for (auto &entry : _list) { if (!entry.hash) { @@ -119,6 +134,7 @@ void Authorizations::reload() { ) | ranges::views::transform([](const MTPAuthorization &auth) { return ParseEntry(auth.data()); }) | ranges::to; + removeExpiredUnreviewed(); refreshCallsDisabledHereFromCloud(); _listChanges.fire({}); }).fail([=] { @@ -261,4 +277,129 @@ crl::time Authorizations::lastReceivedTime() { return _lastReceived; } +const std::vector &Authorizations::unreviewed() { + removeExpiredUnreviewed(); + return _unreviewed; +} + +void Authorizations::removeExpiredUnreviewed() { + const auto now = base::unixtime::now(); + const auto period = _autoconfirmPeriod(); + + const auto oldSize = _unreviewed.size(); + _unreviewed.erase( + std::remove_if(_unreviewed.begin(), _unreviewed.end(), + [=](const auto &auth) { + return (now - auth.date) >= period; + }), + _unreviewed.end()); + + if (_unreviewed.size() != oldSize) { + _saveUnreviewed(); + } +} + +void Authorizations::review(const std::vector &hashes, bool confirm) { + for (const auto hash : hashes) { + if (const auto sent = _reviewRequests.take(hash)) { + _api.request(*sent).cancel(); + } + } + + const auto checkComplete = [=] { + if (_reviewRequests.empty()) { + _saveUnreviewed(); + _unreviewedChanges.fire({}); + } + }; + + for (const auto hash : hashes) { + const auto removeFromUnreviewed = [=] { + _unreviewed.erase( + std::remove_if(_unreviewed.begin(), _unreviewed.end(), + [hash](const auto &auth) { return auth.hash == hash; }), + _unreviewed.end()); + _reviewRequests.remove(hash); + checkComplete(); + }; + + if (confirm) { + using Flag = MTPaccount_ChangeAuthorizationSettings::Flag; + const auto id = _api.request(MTPaccount_ChangeAuthorizationSettings( + MTP_flags(Flag::f_confirmed), + MTP_long(hash), + MTPBool(), // encrypted_requests_disabled + MTPBool() // call_requests_disabled + )).done([=] { + removeFromUnreviewed(); + }).fail([=] { + removeFromUnreviewed(); + }).send(); + _reviewRequests.emplace(hash, id); + } else { + const auto id = _api.request(MTPaccount_ResetAuthorization( + MTP_long(hash) + )).done([=](const MTPBool &result) { + if (mtpIsTrue(result)) { + _list.erase( + ranges::remove(_list, hash, &Entry::hash), + end(_list)); + _listChanges.fire({}); + } + removeFromUnreviewed(); + }).fail([=] { + removeFromUnreviewed(); + }).send(); + _reviewRequests.emplace(hash, id); + } + } +} + +rpl::producer<> Authorizations::unreviewedChanges() const { + return _unreviewedChanges.events(); +} + +void Authorizations::apply(const MTPUpdate &update) { + removeExpiredUnreviewed(); + update.match([&](const MTPDupdateNewAuthorization &data) { + auto unreviewed = Data::UnreviewedAuth{ + .hash = data.vhash().v, + .unconfirmed = data.is_unconfirmed(), + .date = data.vdate().value_or_empty(), + .device = qs(data.vdevice().value_or_empty()), + .location = qs(data.vlocation().value_or_empty()) + }; + if (!unreviewed.unconfirmed) { + const auto hash = unreviewed.hash; + const auto was = _unreviewed.size(); + _unreviewed.erase( + std::remove_if( + _unreviewed.begin(), + _unreviewed.end(), + [hash](const auto &auth) { return auth.hash == hash; }), + _unreviewed.end()); + if (was != _unreviewed.size()) { + _saveUnreviewed(); + _unreviewedChanges.fire({}); + } + return; + } + + for (auto &auth : _unreviewed) { + if (auth.hash == unreviewed.hash) { + auth = std::move(unreviewed); + _saveUnreviewed(); + _unreviewedChanges.fire({}); + return; + } + } + + _unreviewed.push_back(std::move(unreviewed)); + _saveUnreviewed(); + _unreviewedChanges.fire({}); + }, [](auto&&) { + Unexpected("Update in Authorizations::apply."); + }); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h index 5e2a41c9f90193..25c97f39457677 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #pragma once +#include "data/data_authorization.h" #include "mtproto/sender.h" class ApiWrap; @@ -35,6 +36,8 @@ class Authorizations final { Fn &&fail, std::optional hash = std::nullopt); + void apply(const MTPUpdate &update); + [[nodiscard]] crl::time lastReceivedTime(); [[nodiscard]] List list() const; @@ -42,6 +45,11 @@ class Authorizations final { [[nodiscard]] int total() const; [[nodiscard]] rpl::producer totalValue() const; + [[nodiscard]] const std::vector &unreviewed(); + [[nodiscard]] rpl::producer<> unreviewedChanges() const; + + void review(const std::vector &hashes, bool confirm); + void updateTTL(int days); [[nodiscard]] rpl::producer ttlDays() const; @@ -57,6 +65,7 @@ class Authorizations final { private: void refreshCallsDisabledHereFromCloud(); + void removeExpiredUnreviewed(); MTP::Sender _api; mtpRequestId _requestId = 0; @@ -64,10 +73,16 @@ class Authorizations final { List _list; rpl::event_stream<> _listChanges; + Fn _autoconfirmPeriod; + std::vector _unreviewed; + rpl::event_stream<> _unreviewedChanges; + Fn _saveUnreviewed; + mtpRequestId _ttlRequestId = 0; rpl::variable _ttlDays = 0; base::flat_map _toggleCallsDisabledRequests; + base::flat_map _reviewRequests; rpl::variable _callsDisabledHere; crl::time _lastReceived = 0; diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index 0a79eb733ba5d4..1e7915e65e6486 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.cpp +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -83,7 +83,7 @@ void BlockedPeers::block(not_null peer) { } const auto requestId = _api.request(MTPcontacts_Block( MTP_flags(0), - peer->input + peer->input() )).done([=] { const auto data = _blockRequests.take(peer); peer->setIsBlocked(true); @@ -127,7 +127,7 @@ void BlockedPeers::unblock( } const auto requestId = _api.request(MTPcontacts_Unblock( MTP_flags(0), - peer->input + peer->input() )).done([=] { const auto data = _blockRequests.take(peer); peer->setIsBlocked(false); @@ -204,7 +204,7 @@ auto BlockedPeers::slice() -> rpl::producer { } return _slice ? _changes.events_starting_with_copy(*_slice) - : (_changes.events() | rpl::type_erased()); + : (_changes.events() | rpl::type_erased); } void BlockedPeers::request(int offset, Fn done) { diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 5342cbd2e00450..8898ff605b2281 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -10,10 +10,12 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "api/api_cloud_password.h" #include "api/api_send_progress.h" -#include "boxes/share_box.h" +#include "api/api_suggest_post.h" +#include "boxes/peers/choose_peer_box.h" +#include "boxes/peers/create_managed_bot_box.h" #include "boxes/passcode_box.h" +#include "boxes/share_box.h" #include "boxes/url_auth_box.h" -#include "boxes/peers/choose_peer_box.h" #include "lang/lang_keys.h" #include "chat_helpers/bot_command.h" #include "core/core_cloud_password.h" @@ -38,7 +40,10 @@ For license and copyright information please follow this link: #include "ui/toast/toast.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include #include #include @@ -90,7 +95,7 @@ void SendBotCallbackData( const auto show = controller->uiShow(); button->requestId = api->request(MTPmessages_GetBotCallbackAnswer( MTP_flags(flags), - history->peer->input, + history->peer->input(), MTP_int(item->id), MTP_bytes(sendData), password ? password->result : MTP_inputCheckPasswordEmpty() @@ -217,7 +222,7 @@ void SendBotCallbackDataWithPassword( session, tr::lng_bots_password_confirm_check_about( tr::now, - Ui::Text::WithEntities)); + tr::marked)); if (box) { show->showBox(std::move(box), Ui::LayerOption::CloseOther); } else { @@ -226,7 +231,7 @@ void SendBotCallbackDataWithPassword( api->cloudPassword().state( ) | rpl::take( 1 - ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) mutable { + ) | rpl::on_next([=](const Core::CloudPasswordState &state) mutable { if (lifetime) { base::take(lifetime)->destroy(); } @@ -244,7 +249,7 @@ void SendBotCallbackDataWithPassword( fields.customSubmitButton = tr::lng_passcode_submit(); fields.customCheckCallback = [=]( const Core::CloudPasswordResult &result, - QPointer box) { + base::weak_qptr box) { if (const auto button = getButton()) { if (button->requestId) { return; @@ -390,7 +395,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::RequestPoll: { HideSingleUseKeyboard(controller, item); - auto chosen = PollData::Flags(); + auto chosen = kDefaultPollCreateFlags; auto disabled = PollData::Flags(); if (!button->data.isEmpty()) { disabled |= PollData::Flag::Quiz; @@ -399,10 +404,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { } } const auto replyTo = FullReplyTo(); + const auto suggest = SuggestOptions(); Window::PeerMenuCreatePoll( controller, item->history()->peer, replyTo, + suggest, chosen, disabled); } break; @@ -417,14 +424,17 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto itemId = item->id; const auto id = int32(button->buttonId); const auto chosen = [=](std::vector> result) { + using Flag = MTPmessages_SendBotRequestedPeer::Flag; peer->session().api().request(MTPmessages_SendBotRequestedPeer( - peer->input, + MTP_flags(Flag::f_msg_id), + peer->input(), MTP_int(itemId), + MTPstring(), // request_id MTP_int(id), MTP_vector_from_range( result | ranges::views::transform([]( not_null peer) { - return MTPInputPeer(peer->input); + return MTPInputPeer(peer->input()); })) )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); @@ -479,7 +489,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { } break; case ButtonType::Auth: - UrlAuthBox::Activate(item, row, column); + UrlAuthBox::ActivateButton(controller->uiShow(), item, row, column); break; case ButtonType::UserProfile: { @@ -516,9 +526,86 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto text = QString::fromUtf8(button->data); if (!text.isEmpty()) { QGuiApplication::clipboard()->setText(text); - controller->showToast(tr::lng_text_copied(tr::now)); + controller->showToast({ + .text = { tr::lng_text_copied(tr::now) }, + .iconLottie = u"toast/copy"_q, + .iconLottieSize = st::toastLottieIconSize, + }); } } break; + + case ButtonType::SuggestAccept: { + Api::AcceptClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; + + case ButtonType::SuggestDecline: { + Api::DeclineClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; + + case ButtonType::SuggestChange: { + Api::SuggestChangesClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; + + case ButtonType::CreateBot: { + HideSingleUseKeyboard(controller, item); + + auto suggestedName = QString(); + auto suggestedUsername = QString(); + { + auto stream = QDataStream(button->data); + stream >> suggestedName >> suggestedUsername; + } + const auto peer = item->history()->peer; + const auto itemId = item->id; + const auto id = int32(button->buttonId); + const auto bot = item->getMessageBot(); + if (!bot) { + break; + } + ShowCreateManagedBotBox({ + .show = controller->uiShow(), + .manager = bot, + .suggestedName = suggestedName, + .suggestedUsername = suggestedUsername, + .done = [=](not_null createdBot) { + using Flag = MTPmessages_SendBotRequestedPeer::Flag; + peer->session().api().request( + MTPmessages_SendBotRequestedPeer( + MTP_flags(Flag::f_msg_id), + peer->input(), + MTP_int(itemId), + MTPstring(), + MTP_int(id), + MTP_vector( + 1, + createdBot->input())) + ).done([=](const MTPUpdates &result) { + peer->session().api().applyUpdates(result); + }).send(); + controller->showPeerHistory(createdBot); + controller->showToast({ + .title = tr::lng_managed_bot_created_title( + tr::now, + lt_name, + createdBot->name()), + .text = { tr::lng_managed_bot_created_text( + tr::now, + lt_parent_name, + bot->name()) }, + .icon = &st::toastCheckIcon, + }); + }, + }); + } break; } } diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index ee6b97b5849273..cbfacbc0a0108f 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -7,11 +7,16 @@ For license and copyright information please follow this link: */ #include "api/api_chat_filters.h" +#include "api/api_text_entities.h" #include "apiwrap.h" +#include "base/event_filter.h" +#include "base/weak_ptr.h" #include "boxes/peer_list_box.h" #include "boxes/premium_limits_box.h" #include "boxes/filters/edit_filter_links.h" // FilterChatStatusText #include "core/application.h" +#include "core/core_settings.h" +#include "core/ui_integration.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_chat_filters.h" @@ -22,14 +27,22 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/filter_link_header.h" +#include "ui/effects/ripple_animation.h" +#include "ui/layers/show.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" #include "ui/filter_icons.h" +#include "ui/painter.h" #include "ui/vertical_list.h" #include "ui/ui_utility.h" #include "window/window_session_controller.h" +#include "window/window_separate_id.h" #include "styles/style_filter_icons.h" +#include "styles/style_info.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" #include "styles/style_settings.h" namespace Api { @@ -40,6 +53,112 @@ enum class ToggleAction { Removing, }; +class PreviewableRow final + : public PeerListRow + , public base::has_weak_ptr { +public: + using PeerListRow::PeerListRow; + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + void rightActionAddRipple( + QPoint point, + Fn updateCallback) override; + void rightActionStopLastRipple() override; + + void setMenuShown(bool shown); + + [[nodiscard]] QPoint buttonGlobalTopLeft() const { + return _buttonGlobalTopLeft; + } + +private: + std::unique_ptr _actionRipple; + QPoint _buttonGlobalTopLeft; + bool _menuShown = false; + +}; + +QSize PreviewableRow::rightActionSize() const { + const auto side = st::inviteLinkThreeDotsIcon.height(); + return QSize(side, side); +} + +QMargins PreviewableRow::rightActionMargins() const { + return QMargins( + 0, + (st::filterLinkChatsList.item.height + - rightActionSize().height()) / 2, + st::inviteLinkThreeDotsSkip, + 0); +} + +void PreviewableRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (_actionRipple) { + _actionRipple->paint(p, x, y, outerWidth); + if (_actionRipple->empty()) { + _actionRipple.reset(); + } + } + const auto &icon = actionSelected + ? st::inviteLinkThreeDotsIconOver + : st::inviteLinkThreeDotsIcon; + const auto size = rightActionSize(); + icon.paint( + p, + x + (size.width() - icon.width()) / 2, + y + (size.height() - icon.height()) / 2, + outerWidth); +} + +void PreviewableRow::rightActionAddRipple( + QPoint point, + Fn updateCallback) { + if (!_actionRipple) { + auto mask = Ui::RippleAnimation::EllipseMask(rightActionSize()); + _actionRipple = std::make_unique( + st::defaultRippleAnimation, + std::move(mask), + std::move(updateCallback)); + } + _actionRipple->add(point); + _buttonGlobalTopLeft = QCursor::pos() - point; +} + +void PreviewableRow::rightActionStopLastRipple() { + if (_menuShown) { + return; + } + crl::on_main(base::make_weak(this), [this] { + if (!_menuShown && _actionRipple) { + _actionRipple->lastStop(); + } + }); +} + +void PreviewableRow::setMenuShown(bool shown) { + if (_menuShown == shown) { + return; + } + _menuShown = shown; + if (!shown && _actionRipple) { + _actionRipple->lastStop(); + } +} + class ToggleChatsController final : public PeerListController , public base::has_weak_ptr { @@ -47,12 +166,13 @@ class ToggleChatsController final ToggleChatsController( not_null window, ToggleAction action, - const QString &title, + Data::ChatFilterTitle title, std::vector> chats, std::vector> additional); void prepare() override; void rowClicked(not_null row) override; + void rowRightActionClicked(not_null row) override; Main::Session &session() const override; [[nodiscard]] auto selectedValue() const @@ -73,7 +193,6 @@ class ToggleChatsController final Ui::RpWidget *_addedBottomWidget = nullptr; ToggleAction _action = ToggleAction::Adding; - QString _filterTitle; base::flat_set> _checkable; std::vector> _chats; std::vector> _additional; @@ -104,54 +223,61 @@ class ToggleChatsController final [[nodiscard]] TextWithEntities AboutText( Ui::FilterLinkHeaderType type, - const QString &title) { + TextWithEntities title) { using Type = Ui::FilterLinkHeaderType; - auto boldTitle = Ui::Text::Bold(title); + auto boldTitle = Ui::Text::Wrapped(title, EntityType::Bold); return (type == Type::AddingFilter) ? tr::lng_filters_by_link_sure( tr::now, lt_folder, std::move(boldTitle), - Ui::Text::WithEntities) + tr::marked) : (type == Type::AddingChats) ? tr::lng_filters_by_link_more_sure( tr::now, lt_folder, std::move(boldTitle), - Ui::Text::WithEntities) + tr::marked) : (type == Type::AllAdded) ? tr::lng_filters_by_link_already_about( tr::now, lt_folder, std::move(boldTitle), - Ui::Text::WithEntities) + tr::marked) : tr::lng_filters_by_link_remove_sure( tr::now, lt_folder, std::move(boldTitle), - Ui::Text::WithEntities); + tr::marked); } void InitFilterLinkHeader( not_null box, Fn adjust, Ui::FilterLinkHeaderType type, - const QString &title, - const QString &iconEmoji, - rpl::producer count) { + Data::ChatFilterTitle title, + QString iconEmoji, + rpl::producer count, + bool horizontalFilters) { const auto icon = Ui::LookupFilterIcon( Ui::LookupFilterIconByEmoji( iconEmoji ).value_or(Ui::FilterIcon::Custom)).active; + const auto isStatic = title.isStatic; auto header = Ui::MakeFilterLinkHeader(box, { .type = type, .title = TitleText(type)(tr::now), - .about = AboutText(type, title), - .folderTitle = title, + .about = AboutText(type, title.text), + .aboutContext = Core::TextContext({ + .session = &box->peerListUiShow()->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), + .folderTitle = title.text, .folderIcon = icon, .badge = (type == Ui::FilterLinkHeaderType::AddingChats ? std::move(count) : rpl::single(0)), + .horizontalFilters = horizontalFilters, }); const auto widget = header.widget; widget->resizeToWidth(st::boxWideWidth); @@ -163,13 +289,13 @@ void InitFilterLinkHeader( box->setAddedTopScrollSkip(max); std::move( header.wheelEvents - ) | rpl::start_with_next([=](not_null e) { + ) | rpl::on_next([=](not_null e) { box->sendScrollViewportEvent(e); }, widget->lifetime()); std::move( header.closeRequests - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { box->closeBox(); }, widget->lifetime()); @@ -182,7 +308,7 @@ void InitFilterLinkHeader( box->scrolls( ) | rpl::filter([=] { return !state->processing; - }) | rpl::start_with_next([=] { + }) | rpl::on_next([=] { state->processing = true; const auto guard = gsl::finally([&] { state->processing = false; }); @@ -226,7 +352,7 @@ void ImportInvite( fail(error.type()); }; auto inputs = peers | ranges::views::transform([](auto peer) { - return MTPInputPeer(peer->input); + return MTPInputPeer(peer->input()); }) | ranges::to>(); if (!slug.isEmpty()) { api->request(MTPchatlists_JoinChatlistInvite( @@ -244,12 +370,11 @@ void ImportInvite( ToggleChatsController::ToggleChatsController( not_null window, ToggleAction action, - const QString &title, + Data::ChatFilterTitle title, std::vector> chats, std::vector> additional) : _window(window) , _action(action) -, _filterTitle(title) , _chats(std::move(chats)) , _additional(std::move(additional)) { setStyleOverrides(&st::filterLinkChatsList); @@ -266,12 +391,23 @@ void ToggleChatsController::prepare() { }; const auto add = [&](not_null peer, bool additional = false) { const auto disable = disabled(peer); - auto row = (additional || !disable) - ? std::make_unique(peer) - : MakeFilterChatRow( - peer, - tr::lng_filters_link_inaccessible(tr::now), - true); + const auto channel = peer->asChannel(); + const auto willDisable = disable + || (additional && _action == ToggleAction::Adding); + const auto previewable = channel + && channel->hasUsername() + && !willDisable; + auto row = [&]() -> std::unique_ptr { + if (!additional && disable) { + return MakeFilterChatRow( + peer, + tr::lng_filters_link_inaccessible(tr::now), + true); + } else if (previewable) { + return std::make_unique(peer); + } + return std::make_unique(peer); + }(); if (delegate()->peerListFindRow(peer->id.value)) { return; } @@ -333,6 +469,31 @@ void ToggleChatsController::rowClicked(not_null row) { _selected = std::move(selected); } +void ToggleChatsController::rowRightActionClicked( + not_null row) { + const auto peer = row->peer(); + const auto previewRow = static_cast(row.get()); + const auto parent = delegate()->peerListUiShow()->toastParent(); + _menu = base::make_unique_q( + parent, + st::popupMenuWithIcons); + _menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); + const auto window = _window; + _menu->addAction(tr::lng_context_new_window(tr::now), [=] { + window->showInNewWindow(peer); + }, &st::menuIconNewWindow); + previewRow->setMenuShown(true); + _menu->setDestroyedCallback([weak = base::make_weak(previewRow)] { + if (weak) { + weak->setMenuShown(false); + } + }); + const auto size = row->rightActionSize(); + const auto bottomRight = previewRow->buttonGlobalTopLeft() + + QPoint(size.width(), size.height()); + _menu->popup(bottomRight); +} + void ToggleChatsController::setupAboveWidget() { using namespace Settings; @@ -460,7 +621,7 @@ void ToggleChatsController::adjust( void ToggleChatsController::setRealContentHeight(rpl::producer value) { std::move( value - ) | rpl::start_with_next([=](int height) { + ) | rpl::on_next([=](int height) { const auto desired = _desiredHeight.current(); if (height <= computeListSt().item.height) { return; @@ -525,7 +686,7 @@ void ShowImportError( void ShowImportToast( base::weak_ptr weak, - const QString &title, + Data::ChatFilterTitle title, Ui::FilterLinkHeaderType type, int added) { const auto strong = weak.get(); @@ -536,22 +697,51 @@ void ShowImportToast( const auto phrase = created ? tr::lng_filters_added_title : tr::lng_filters_updated_title; - auto text = Ui::Text::Bold(phrase(tr::now, lt_folder, title)); + auto text = Ui::Text::Wrapped( + phrase(tr::now, lt_folder, title.text, tr::marked), + EntityType::Bold); if (added > 0) { const auto phrase = created ? tr::lng_filters_added_also : tr::lng_filters_updated_also; text.append('\n').append(phrase(tr::now, lt_count, added)); } - strong->showToast(std::move(text)); + const auto isStatic = title.isStatic; + strong->showToast({ + .text = std::move(text), + .textContext = Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }) + }); +} + +void HandleEnterInBox(not_null box) { + const auto isEnter = [=](not_null event) { + if (event->type() == QEvent::KeyPress) { + if (const auto k = static_cast(event.get())) { + return (k->key() == Qt::Key_Enter) + || (k->key() == Qt::Key_Return); + } + } + return false; + }; + + base::install_event_filter(box, [=](not_null event) { + if (isEnter(event)) { + box->triggerButton(0); + return base::EventFilterResult::Cancel; + } + return base::EventFilterResult::Continue; + }); } void ProcessFilterInvite( base::weak_ptr weak, const QString &slug, FilterId filterId, - const QString &title, - const QString &iconEmoji, + Data::ChatFilterTitle title, + QString iconEmoji, std::vector> peers, std::vector> already) { const auto strong = weak.get(); @@ -570,6 +760,8 @@ void ProcessFilterInvite( title, std::move(peers), std::move(already)); + const auto horizontalFilters = !strong->enoughSpaceForFilters() + || Core::App().settings().chatFiltersHorizontal(); const auto raw = controller.get(); auto initBox = [=](not_null box) { box->setStyle(st::filterInviteBox); @@ -586,19 +778,24 @@ void ProcessFilterInvite( }); InitFilterLinkHeader(box, [=](int min, int max, int addedTop) { raw->adjust(min, max, addedTop); - }, type, title, iconEmoji, rpl::duplicate(badge)); + }, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters); raw->setRealContentHeight(box->heightValue()); + const auto isStatic = title.isStatic; auto owned = Ui::FilterLinkProcessButton( box, type, - title, + title.text, + Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), std::move(badge)); const auto button = owned.data(); box->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { const auto &padding = st::filterInviteBox.buttonPadding; button->resizeToWidth(width - padding.left() @@ -608,13 +805,15 @@ void ProcessFilterInvite( box->addButton(std::move(owned)); + HandleEnterInBox(box); + struct State { bool importing = false; }; const auto state = box->lifetime().make_state(); raw->selectedValue( - ) | rpl::start_with_next([=]( + ) | rpl::on_next([=]( base::flat_set> &&peers) { button->setClickedCallback([=] { if (peers.empty()) { @@ -692,7 +891,7 @@ void CheckFilterInvite( if (!strong) { return; } - auto title = QString(); + auto title = Data::ChatFilterTitle(); auto iconEmoji = QString(); auto filterId = FilterId(); auto peers = std::vector>(); @@ -711,7 +910,8 @@ void CheckFilterInvite( return result; }; result.match([&](const MTPDchatlists_chatlistInvite &data) { - title = qs(data.vtitle()); + title.text = ParseTextWithEntities(session, data.vtitle()); + title.isStatic = data.is_title_noanimate(); iconEmoji = data.vemoticon().value_or_empty(); peers = parseList(data.vpeers()); }, [&](const MTPDchatlists_chatlistInviteAlready &data) { @@ -728,7 +928,7 @@ void CheckFilterInvite( if (notLoaded) { const auto lifetime = std::make_shared(); owner.chatsFilters().changed( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { lifetime->destroy(); ProcessFilterInvite( weak, @@ -776,8 +976,8 @@ void ProcessFilterUpdate( void ProcessFilterRemove( base::weak_ptr weak, - const QString &title, - const QString &iconEmoji, + Data::ChatFilterTitle title, + QString iconEmoji, std::vector> all, std::vector> suggest, Fn>)> done) { @@ -796,6 +996,8 @@ void ProcessFilterRemove( title, std::move(suggest), std::move(all)); + const auto horizontalFilters = !strong->enoughSpaceForFilters() + || Core::App().settings().chatFiltersHorizontal(); const auto raw = controller.get(); auto initBox = [=](not_null box) { box->setStyle(st::filterInviteBox); @@ -807,17 +1009,22 @@ void ProcessFilterRemove( }); InitFilterLinkHeader(box, [=](int min, int max, int addedTop) { raw->adjust(min, max, addedTop); - }, type, title, iconEmoji, rpl::single(0)); + }, type, title, iconEmoji, rpl::single(0), horizontalFilters); + const auto isStatic = title.isStatic; auto owned = Ui::FilterLinkProcessButton( box, type, - title, + title.text, + Core::TextContext({ + .session = &strong->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), std::move(badge)); const auto button = owned.data(); box->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { const auto &padding = st::filterInviteBox.buttonPadding; button->resizeToWidth(width - padding.left() @@ -827,8 +1034,10 @@ void ProcessFilterRemove( box->addButton(std::move(owned)); + HandleEnterInBox(box); + raw->selectedValue( - ) | rpl::start_with_next([=]( + ) | rpl::on_next([=]( base::flat_set> &&peers) { button->setClickedCallback([=] { done(peers | ranges::to_vector); diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h index 9167a41db8c34b..34933a7db92d8d 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.h +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -17,6 +17,7 @@ class SessionController; namespace Data { class ChatFilter; +struct ChatFilterTitle; } // namespace Data namespace Api { @@ -36,8 +37,8 @@ void ProcessFilterUpdate( void ProcessFilterRemove( base::weak_ptr weak, - const QString &title, - const QString &iconEmoji, + Data::ChatFilterTitle title, + QString iconEmoji, std::vector> all, std::vector> suggest, Fn>)> done); diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp new file mode 100644 index 00000000000000..7839b59028f882 --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp @@ -0,0 +1,128 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_chat_filters_remove_manager.h" + +#include "api/api_chat_filters.h" +#include "apiwrap.h" +#include "data/data_chat_filters.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/boxes/confirm_box.h" +#include "ui/ui_utility.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_layers.h" + +namespace Api { +namespace { + +void RemoveChatFilter( + not_null session, + FilterId filterId, + std::vector> leave) { + const auto api = &session->api(); + session->data().chatsFilters().apply(MTP_updateDialogFilter( + MTP_flags(MTPDupdateDialogFilter::Flag(0)), + MTP_int(filterId), + MTPDialogFilter())); + if (leave.empty()) { + api->request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)), + MTP_int(filterId), + MTPDialogFilter() + )).send(); + } else { + api->request(MTPchatlists_LeaveChatlist( + MTP_inputChatlistDialogFilter(MTP_int(filterId)), + MTP_vector(ranges::views::all( + leave + ) | ranges::views::transform([](not_null peer) { + return MTPInputPeer(peer->input()); + }) | ranges::to>()) + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); + }).send(); + } +} + +} // namespace + +RemoveComplexChatFilter::RemoveComplexChatFilter() = default; + +void RemoveComplexChatFilter::request( + base::weak_qptr widget, + base::weak_ptr weak, + FilterId id) { + const auto session = &weak->session(); + const auto &list = session->data().chatsFilters().list(); + const auto i = ranges::find(list, id, &Data::ChatFilter::id); + const auto filter = (i != end(list)) ? *i : Data::ChatFilter(); + const auto has = filter.hasMyLinks(); + const auto confirm = [=](Fn action, bool onlyWhenHas = false) { + if (!has && onlyWhenHas) { + action(); + return; + } + weak->window().show(Ui::MakeConfirmBox({ + .text = (has + ? tr::lng_filters_delete_sure() + : tr::lng_filters_remove_sure()), + .confirmed = [=](Fn &&close) { close(); action(); }, + .confirmText = (has + ? tr::lng_box_delete() + : tr::lng_filters_remove_yes()), + .confirmStyle = &st::attentionBoxButton, + })); + }; + const auto simple = [=] { + confirm([=] { RemoveChatFilter(session, id, {}); }); + }; + const auto suggestRemoving = Api::ExtractSuggestRemoving(filter); + if (suggestRemoving.empty()) { + simple(); + return; + } else if (_removingRequestId) { + if (_removingId == id) { + return; + } + session->api().request(_removingRequestId).cancel(); + } + _removingId = id; + _removingRequestId = session->api().request( + MTPchatlists_GetLeaveChatlistSuggestions( + MTP_inputChatlistDialogFilter( + MTP_int(id))) + ).done(crl::guard(widget, [=, this](const MTPVector &result) { + _removingRequestId = 0; + const auto suggestRemovePeers = ranges::views::all( + result.v + ) | ranges::views::transform([=](const MTPPeer &peer) { + return session->data().peer(peerFromMTP(peer)); + }) | ranges::to_vector; + const auto chosen = crl::guard(widget, [=]( + std::vector> peers) { + RemoveChatFilter(session, id, std::move(peers)); + }); + confirm(crl::guard(widget, [=] { + Api::ProcessFilterRemove( + weak, + filter.title(), + filter.iconEmoji(), + suggestRemoving, + suggestRemovePeers, + chosen); + }), true); + })).fail(crl::guard(widget, [=, this] { + _removingRequestId = 0; + simple(); + })).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h new file mode 100644 index 00000000000000..6bd6ce7fa3e8c7 --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h @@ -0,0 +1,35 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Api { + +class RemoveComplexChatFilter final { +public: + RemoveComplexChatFilter(); + + void request( + base::weak_qptr widget, + base::weak_ptr weak, + FilterId id); + +private: + FilterId _removingId = 0; + mtpRequestId _removingRequestId = 0; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index be07e72ac1d4f0..e1a855fa96a6a8 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #include "api/api_chat_invite.h" #include "apiwrap.h" +#include "api/api_credits.h" #include "boxes/premium_limits_box.h" #include "core/application.h" #include "data/components/credits.h" @@ -20,11 +21,13 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" #include "info/profile/info_profile_badge.h" +#include "inline_bots/bot_attach_web_view.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_credits_graphics.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_stars_colored.h" #include "ui/empty_userpic.h" @@ -37,6 +40,7 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "styles/style_color_indices.h" #include "styles/style_credits.h" #include "styles/style_info.h" #include "styles/style_layers.h" @@ -46,6 +50,69 @@ namespace Api { namespace { +struct InviteParticipant { + not_null user; + Ui::PeerUserpicView userpic; +}; + +struct ChatInvite { + QString title; + QString about; + PhotoData *photo = nullptr; + int participantsCount = 0; + std::vector participants; + bool isPublic = false; + bool isChannel = false; + bool isMegagroup = false; + bool isBroadcast = false; + bool isRequestNeeded = false; + bool isFake = false; + bool isScam = false; + bool isVerified = false; +}; + +[[nodiscard]] ChatInvite ParseInvite( + not_null session, + const MTPDchatInvite &data) { + auto participants = std::vector(); + if (const auto list = data.vparticipants()) { + participants.reserve(list->v.size()); + for (const auto &participant : list->v) { + if (const auto user = session->data().processUser(participant)) { + participants.push_back(InviteParticipant{ user }); + } + } + } + const auto photo = session->data().processPhoto(data.vphoto()); + return { + .title = qs(data.vtitle()), + .about = data.vabout().value_or_empty(), + .photo = (photo->isNull() ? nullptr : photo.get()), + .participantsCount = data.vparticipants_count().v, + .participants = std::move(participants), + .isPublic = data.is_public(), + .isChannel = data.is_channel(), + .isMegagroup = data.is_megagroup(), + .isBroadcast = data.is_broadcast(), + .isRequestNeeded = data.is_request_needed(), + .isFake = data.is_fake(), + .isScam = data.is_scam(), + .isVerified = data.is_verified(), + }; +} + +[[nodiscard]] Info::Profile::BadgeType BadgeForInvite( + const ChatInvite &invite) { + using Type = Info::Profile::BadgeType; + return invite.isVerified + ? Type::Verified + : invite.isScam + ? Type::Scam + : invite.isFake + ? Type::Fake + : Type::None; +} + void SubmitChatInvite( base::weak_ptr weak, not_null session, @@ -53,40 +120,52 @@ void SubmitChatInvite( bool isGroup) { session->api().request(MTPmessages_ImportChatInvite( MTP_string(hash) - )).done([=](const MTPUpdates &result) { - session->api().applyUpdates(result); + )).done([=](const MTPmessages_ChatInviteJoinResult &result) { const auto strongController = weak.get(); - if (!strongController) { - return; + if (strongController) { + strongController->hideLayer(); } - strongController->hideLayer(); - const auto handleChats = [&](const MTPVector &chats) { - if (chats.v.isEmpty()) { - return; - } - const auto peerId = chats.v[0].match([](const MTPDchat &data) { - return peerFromChat(data.vid().v); - }, [](const MTPDchannel &data) { - return peerFromChannel(data.vid().v); - }, [](auto&&) { - return PeerId(0); - }); - if (const auto peer = session->data().peerLoaded(peerId)) { - // Shows in the primary window anyway. - strongController->showPeerHistory( - peer, - Window::SectionShow::Way::Forward); - } - }; - result.match([&](const MTPDupdates &data) { - handleChats(data.vchats()); - }, [&](const MTPDupdatesCombined &data) { - handleChats(data.vchats()); - }, [&](auto &&) { - LOG(("API Error: unexpected update cons %1 " - "(ApiWrap::importChatInvite)").arg(result.type())); - }); + ProcessChatInviteJoinResult( + session, + strongController ? strongController->uiShow() : nullptr, + result, + [=](const MTPUpdates &updates) { + session->api().applyUpdates(updates); + if (!strongController) { + return; + } + const auto handleChats = [&]( + const MTPVector &chats) { + if (chats.v.isEmpty()) { + return; + } + const auto peerId = chats.v[0].match( + [](const MTPDchat &data) { + return peerFromChat(data.vid().v); + }, + [](const MTPDchannel &data) { + return peerFromChannel(data.vid().v); + }, + [](auto&&) { + return PeerId(0); + }); + if (const auto peer = session->data().peerLoaded(peerId)) { + strongController->showPeerHistory( + peer, + Window::SectionShow::Way::Forward); + } + }; + updates.match([&](const MTPDupdates &data) { + handleChats(data.vchats()); + }, [&](const MTPDupdatesCombined &data) { + handleChats(data.vchats()); + }, [&](auto &&) { + LOG(("API Error: unexpected update cons %1 " + "(ApiWrap::importChatInvite)").arg(updates.type())); + }); + }, + weak); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); @@ -129,6 +208,7 @@ void ConfirmSubscriptionBox( struct State final { std::shared_ptr photoMedia; std::unique_ptr photoEmpty; + QImage frame; std::optional api; Ui::RpWidget* saveButton = nullptr; @@ -139,120 +219,117 @@ void ConfirmSubscriptionBox( const auto content = box->verticalLayout(); Ui::AddSkip(content, st::confirmInvitePhotoTop); - const auto userpicWrap = content->add( - object_ptr>( - content, - object_ptr(content))); - const auto userpic = userpicWrap->entity(); + const auto userpic = content->add( + object_ptr(content), + style::al_top); const auto photoSize = st::confirmInvitePhotoSize; userpic->resize(Size(photoSize)); + userpic->setNaturalWidth(photoSize); + const auto creditsIconSize = photoSize / 3; + const auto creditsIconCallback = + Ui::PaintOutlinedColoredCreditsIconCallback( + creditsIconSize, + 1.5); + state->frame = QImage( + Size(photoSize * style::DevicePixelRatio()), + QImage::Format_ARGB32_Premultiplied); + state->frame.setDevicePixelRatio(style::DevicePixelRatio()); const auto options = Images::Option::RoundCircle; userpic->paintRequest( - ) | rpl::start_with_next([=, small = Data::PhotoSize::Small] { - auto p = QPainter(userpic); - if (state->photoMedia) { - if (const auto image = state->photoMedia->image(small)) { - p.drawPixmap( + ) | rpl::on_next([=, small = Data::PhotoSize::Small] { + state->frame.fill(Qt::transparent); + { + auto p = QPainter(&state->frame); + if (state->photoMedia) { + if (const auto image = state->photoMedia->image(small)) { + p.drawPixmap( + 0, + 0, + image->pix(Size(photoSize), { .options = options })); + } + } else if (state->photoEmpty) { + state->photoEmpty->paintCircle( + p, 0, 0, - image->pix(Size(photoSize), { .options = options })); + userpic->width(), + photoSize); + } + if (creditsIconCallback) { + p.translate( + photoSize - creditsIconSize, + photoSize - creditsIconSize); + creditsIconCallback(p); } - } else if (state->photoEmpty) { - state->photoEmpty->paintCircle( - p, - 0, - 0, - userpic->width(), - photoSize); } - }, userpicWrap->lifetime()); - userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents); + auto p = QPainter(userpic); + p.drawImage(0, 0, state->frame); + }, userpic->lifetime()); + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); if (photo) { state->photoMedia = photo->createMediaView(); state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin()); if (!state->photoMedia->image(Data::PhotoSize::Small)) { session->downloaderTaskFinished( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { userpic->update(); - }, userpicWrap->entity()->lifetime()); + }, userpic->lifetime()); } } else { state->photoEmpty = std::make_unique( - Ui::EmptyUserpic::UserpicColor(0), + Ui::EmptyUserpic::UserpicColor(st::colorIndexRed), name); } Ui::AddSkip(content); Ui::AddSkip(content); - { - const auto widget = Ui::CreateChild(content); - using ColoredMiniStars = Ui::Premium::ColoredMiniStars; - const auto stars = widget->lifetime().make_state( - widget, - false, - Ui::Premium::MiniStars::Type::BiStars); - stars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); - widget->resize( - st::boxWideWidth - photoSize, - photoSize * 2); - content->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - widget->moveToLeft(photoSize / 2, 0); - const auto starsRect = Rect(widget->size()); - stars->setPosition(starsRect.topLeft()); - stars->setSize(starsRect.size()); - widget->lower(); - }, widget->lifetime()); - widget->paintRequest( - ) | rpl::start_with_next([=](const QRect &r) { - auto p = QPainter(widget); - p.fillRect(r, Qt::transparent); - stars->paint(p); - }, widget->lifetime()); - } + Settings::AddMiniStars( + content, + Ui::CreateChild(content), + photoSize, + box->width(), + 2.); box->addRow( - object_ptr>( + object_ptr( box, - object_ptr( - box, - tr::lng_channel_invite_subscription_title(), - st::inviteLinkSubscribeBoxTitle))); + tr::lng_channel_invite_subscription_title(), + st::inviteLinkSubscribeBoxTitle), + style::al_top); box->addRow( - object_ptr>( + object_ptr( box, - object_ptr( - box, - tr::lng_channel_invite_subscription_about( - lt_channel, - rpl::single(Ui::Text::Bold(name)), - lt_price, - tr::lng_credits_summary_options_credits( - lt_count, - rpl::single(amount) | tr::to_count(), - Ui::Text::Bold), - Ui::Text::WithEntities), - st::inviteLinkSubscribeBoxAbout))); + tr::lng_channel_invite_subscription_about( + lt_channel, + rpl::single(tr::bold(name)), + lt_price, + tr::lng_credits_summary_options_credits( + lt_count, + rpl::single(amount) | tr::to_count(), + tr::bold), + tr::marked), + st::inviteLinkSubscribeBoxAbout), + style::al_top); Ui::AddSkip(content); box->addRow( - object_ptr>( + object_ptr( box, - object_ptr( - box, - tr::lng_channel_invite_subscription_terms( - lt_link, - rpl::combine( - tr::lng_paid_react_agree_link(), - tr::lng_group_invite_subscription_about_url() - ) | rpl::map([](const QString &text, const QString &url) { - return Ui::Text::Link(text, url); - }), - Ui::Text::RichLangValue), - st::inviteLinkSubscribeBoxTerms))); + tr::lng_channel_invite_subscription_terms( + lt_link, + rpl::combine( + tr::lng_paid_react_agree_link(), + tr::lng_group_invite_subscription_about_url() + ) | rpl::map([](const QString &text, const QString &url) { + return tr::link(text, url); + }), + tr::rich), + st::inviteLinkSubscribeBoxTerms), + style::al_top); { const auto balance = Settings::AddBalanceWidget( content, + session, session->credits().balanceValue(), true); session->credits().load(true); @@ -260,7 +337,7 @@ void ConfirmSubscriptionBox( rpl::combine( balance->sizeValue(), content->sizeValue() - ) | rpl::start_with_next([=](const QSize &, const QSize &) { + ) | rpl::on_next([=](const QSize &, const QSize &) { balance->moveToRight( st::creditsHistoryRightSkip * 2, st::creditsHistoryRightSkip); @@ -268,25 +345,44 @@ void ConfirmSubscriptionBox( }, balance->lifetime()); } - const auto sendCredits = [=, weak = Ui::MakeWeak(box)] { + const auto sendCredits = [=, weak = base::make_weak(box)] { const auto show = box->uiShow(); const auto buttonWidth = state->saveButton ? state->saveButton->width() : 0; + const auto finish = [=] { + state->api = std::nullopt; + state->loading.force_assign(false); + if (const auto strong = weak.get()) { + strong->closeBox(); + } + }; state->api->request( MTPpayments_SendStarsForm( MTP_long(formId), MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) ).done([=](const MTPpayments_PaymentResult &result) { - state->api = std::nullopt; - state->loading.force_assign(false); result.match([&](const MTPDpayments_paymentResult &data) { session->api().applyUpdates(data.vupdates()); }, [](const MTPDpayments_paymentVerificationNeeded &data) { }); - if (weak) { - box->closeBox(); + const auto refill = session->data().activeCreditsSubsRebuilder(); + const auto strong = weak.get(); + if (!strong) { + return; } + if (!refill) { + return finish(); + } + const auto api + = strong->lifetime().make_state( + session->user(), + true, + true); + api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) { + refill->fire(std::move(d)); + finish(); + }); }).fail([=](const MTP::Error &error) { const auto id = error.type(); if (weak) { @@ -337,8 +433,239 @@ void ConfirmSubscriptionBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void ConfirmInviteBox( + not_null box, + not_null session, + const MTPDchatInvite *invitePtr, + ChannelData *invitePeekChannel, + Fn submit) { + auto invite = ParseInvite(session, *invitePtr); + const auto isChannel = invite.isChannel && !invite.isMegagroup; + const auto requestApprove = invite.isRequestNeeded; + const auto count = invite.participantsCount; + + struct State { + std::shared_ptr photoMedia; + std::unique_ptr photoEmpty; + std::vector participants; + }; + const auto state = box->lifetime().make_state(); + state->participants = std::move(invite.participants); + + const auto status = [&] { + return invitePeekChannel + ? tr::lng_channel_invite_private(tr::now) + : (!state->participants.empty() + && int(state->participants.size()) < count) + ? tr::lng_group_invite_members(tr::now, lt_count, count) + : (count > 0 && isChannel) + ? tr::lng_chat_status_subscribers( + tr::now, + lt_count_decimal, + count) + : (count > 0) + ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count) + : isChannel + ? tr::lng_channel_status(tr::now) + : tr::lng_group_status(tr::now); + }(); + + box->setNoContentMargin(true); + box->setWidth(st::boxWideWidth); + const auto content = box->verticalLayout(); + + Ui::AddSkip(content, st::confirmInvitePhotoTop); + const auto userpic = content->add( + object_ptr(content), + style::al_top); + const auto photoSize = st::confirmInvitePhotoSize; + userpic->resize(Size(photoSize)); + userpic->setNaturalWidth(photoSize); + userpic->paintRequest( + ) | rpl::on_next([=, small = Data::PhotoSize::Small] { + auto p = QPainter(userpic); + if (state->photoMedia) { + if (const auto image = state->photoMedia->image(small)) { + p.drawPixmap( + 0, + 0, + image->pix( + Size(photoSize), + { .options = Images::Option::RoundCircle })); + } + } else if (state->photoEmpty) { + state->photoEmpty->paintCircle( + p, + 0, + 0, + userpic->width(), + photoSize); + } + }, userpic->lifetime()); + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + if (const auto photo = invite.photo) { + state->photoMedia = photo->createMediaView(); + state->photoMedia->wanted( + Data::PhotoSize::Small, + Data::FileOrigin()); + if (!state->photoMedia->image(Data::PhotoSize::Small)) { + session->downloaderTaskFinished( + ) | rpl::on_next([=] { + userpic->update(); + }, userpic->lifetime()); + } + } else { + state->photoEmpty = std::make_unique( + Ui::EmptyUserpic::UserpicColor(st::colorIndexRed), + invite.title); + } + + Ui::AddSkip(content); + const auto title = box->addRow( + object_ptr( + box, + invite.title, + st::confirmInviteTitle), + style::al_top); + + const auto badgeType = BadgeForInvite(invite); + if (badgeType != Info::Profile::BadgeType::None) { + const auto badgeParent = title->parentWidget(); + const auto badge = box->lifetime().make_state( + badgeParent, + st::infoPeerBadge, + session, + rpl::single(Info::Profile::Badge::Content{ badgeType }), + nullptr, + [] { return false; }); + title->geometryValue( + ) | rpl::on_next([=](const QRect &r) { + badge->move(r.x() + r.width(), r.y(), r.y() + r.height()); + }, title->lifetime()); + } + + box->addRow( + object_ptr( + box, + status, + st::confirmInviteStatus), + style::al_top); + + if (!invite.about.isEmpty()) { + box->addRow( + object_ptr( + box, + invite.about, + st::confirmInviteAbout), + st::confirmInviteAboutPadding, + style::al_top); + } + + if (requestApprove) { + box->addRow( + object_ptr( + box, + (isChannel + ? tr::lng_group_request_about_channel(tr::now) + : tr::lng_group_request_about(tr::now)), + st::confirmInviteStatus), + st::confirmInviteAboutRequestsPadding, + style::al_top); + } + + if (!state->participants.empty()) { + while (state->participants.size() > 4) { + state->participants.pop_back(); + } + const auto padding = (st::confirmInviteUsersWidth + - 4 * st::confirmInviteUserPhotoSize) / 10; + const auto userWidth = st::confirmInviteUserPhotoSize + 2 * padding; + + auto strip = object_ptr(content); + const auto rawStrip = strip.data(); + rawStrip->resize(st::boxWideWidth, st::confirmInviteUserHeight); + rawStrip->setNaturalWidth(st::boxWideWidth); + + const auto shown = int(state->participants.size()); + const auto sumWidth = shown * userWidth; + const auto baseLeft = (st::boxWideWidth - sumWidth) / 2; + for (auto i = 0; i != shown; ++i) { + const auto &participant = state->participants[i]; + const auto name = Ui::CreateChild( + rawStrip, + st::confirmInviteUserName); + name->resizeToWidth( + st::confirmInviteUserPhotoSize + padding); + name->setText(participant.user->firstName.isEmpty() + ? participant.user->name() + : participant.user->firstName); + name->moveToLeft( + baseLeft + i * userWidth + (padding / 2), + st::confirmInviteUserNameTop - st::confirmInviteUserPhotoTop); + } + + rawStrip->paintRequest( + ) | rpl::on_next([=] { + auto p = Painter(rawStrip); + const auto total = int(state->participants.size()); + const auto totalWidth = total * userWidth; + auto left = (rawStrip->width() - totalWidth) / 2; + for (auto &participant : state->participants) { + participant.user->paintUserpicLeft( + p, + participant.userpic, + left + (userWidth - st::confirmInviteUserPhotoSize) / 2, + 0, + rawStrip->width(), + st::confirmInviteUserPhotoSize); + left += userWidth; + } + }, rawStrip->lifetime()); + + Ui::AddSkip(content, st::boxPadding.bottom()); + content->add(std::move(strip), style::margins()); + } + + box->addButton((requestApprove + ? tr::lng_group_request_to_join() + : isChannel + ? tr::lng_profile_join_channel() + : tr::lng_profile_join_group()), submit); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace +void ProcessChatInviteJoinResult( + not_null session, + std::shared_ptr show, + const MTPmessages_ChatInviteJoinResult &result, + Fn done, + base::weak_ptr controller) { + result.match([&](const MTPDmessages_chatInviteJoinResultOk &data) { + done(data.vupdates()); + }, [&](const MTPDmessages_chatInviteJoinResultWebView &data) { + session->data().processUsers(data.vusers()); + const auto bot = session->data().userLoaded(UserId(data.vbot_id().v)); + if (!bot || !show) { + LOG(("API Error: guard bot %1 not loaded " + "(Api::ProcessChatInviteJoinResult)").arg(data.vbot_id().v)); + return; + } + session->attachWebView().open({ + .bot = not_null{ bot }, + .parentShow = std::move(show), + .context = { + .controller = controller, + .maySkipConfirmation = false, + }, + .source = InlineBots::WebViewSourceJoinChat{ + .result = InlineBots::ParseWebViewResult(data.vwebview()), + }, + }); + }); +} + void CheckChatInvite( not_null controller, const QString &hash, @@ -380,16 +707,17 @@ void CheckChatInvite( session, hash, &data)) - : strong->show(Box( + : strong->show(Box( + ConfirmInviteBox, session, - data, + &data, invitePeekChannel, [=] { SubmitChatInvite(weak, session, hash, isGroup); })); if (invitePeekChannel) { box->boxClosing( ) | rpl::filter([=] { return !invitePeekChannel->amIn(); - }) | rpl::start_with_next([=] { + }) | rpl::on_next([=] { if (const auto strong = weak.get()) { strong->clearSectionStack(Window::SectionShow( Window::SectionShow::Way::ClearStack, @@ -414,6 +742,12 @@ void CheckChatInvite( } }); }, [=](const MTP::Error &error) { + if (MTP::IsFloodError(error)) { + if (const auto strong = weak.get()) { + strong->show(Ui::MakeInformBox(tr::lng_flood_error())); + } + return; + } if (error.code() != 400) { return; } @@ -425,255 +759,3 @@ void CheckChatInvite( } } // namespace Api - -struct ConfirmInviteBox::Participant { - not_null user; - Ui::PeerUserpicView userpic; -}; - -ConfirmInviteBox::ConfirmInviteBox( - QWidget*, - not_null session, - const MTPDchatInvite &data, - ChannelData *invitePeekChannel, - Fn submit) -: ConfirmInviteBox( - session, - Parse(session, data), - invitePeekChannel, - std::move(submit)) { -} - -ConfirmInviteBox::ConfirmInviteBox( - not_null session, - ChatInvite &&invite, - ChannelData *invitePeekChannel, - Fn submit) -: _session(session) -, _submit(std::move(submit)) -, _title(this, st::confirmInviteTitle) -, _badge(std::make_unique( - this, - st::infoPeerBadge, - _session, - rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }), - nullptr, - [=] { return false; })) -, _status(this, st::confirmInviteStatus) -, _about(this, st::confirmInviteAbout) -, _aboutRequests(this, st::confirmInviteStatus) -, _participants(std::move(invite.participants)) -, _isChannel(invite.isChannel && !invite.isMegagroup) -, _requestApprove(invite.isRequestNeeded) { - const auto count = invite.participantsCount; - const auto status = [&] { - return invitePeekChannel - ? tr::lng_channel_invite_private(tr::now) - : (!_participants.empty() && _participants.size() < count) - ? tr::lng_group_invite_members(tr::now, lt_count, count) - : (count > 0 && _isChannel) - ? tr::lng_chat_status_subscribers( - tr::now, - lt_count_decimal, - count) - : (count > 0) - ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count) - : _isChannel - ? tr::lng_channel_status(tr::now) - : tr::lng_group_status(tr::now); - }(); - _title->setText(invite.title); - _status->setText(status); - if (!invite.about.isEmpty()) { - _about->setText(invite.about); - } else { - _about.destroy(); - } - if (_requestApprove) { - _aboutRequests->setText(_isChannel - ? tr::lng_group_request_about_channel(tr::now) - : tr::lng_group_request_about(tr::now)); - } else { - _aboutRequests.destroy(); - } - - if (invite.photo) { - _photo = invite.photo->createMediaView(); - _photo->wanted(Data::PhotoSize::Small, Data::FileOrigin()); - if (!_photo->image(Data::PhotoSize::Small)) { - _session->downloaderTaskFinished( - ) | rpl::start_with_next([=] { - update(); - }, lifetime()); - } - } else { - _photoEmpty = std::make_unique( - Ui::EmptyUserpic::UserpicColor(0), - invite.title); - } -} - -ConfirmInviteBox::~ConfirmInviteBox() = default; - -ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( - not_null session, - const MTPDchatInvite &data) { - auto participants = std::vector(); - if (const auto list = data.vparticipants()) { - participants.reserve(list->v.size()); - for (const auto &participant : list->v) { - if (const auto user = session->data().processUser(participant)) { - participants.push_back(Participant{ user }); - } - } - } - const auto photo = session->data().processPhoto(data.vphoto()); - return { - .title = qs(data.vtitle()), - .about = data.vabout().value_or_empty(), - .photo = (photo->isNull() ? nullptr : photo.get()), - .participantsCount = data.vparticipants_count().v, - .participants = std::move(participants), - .isPublic = data.is_public(), - .isChannel = data.is_channel(), - .isMegagroup = data.is_megagroup(), - .isBroadcast = data.is_broadcast(), - .isRequestNeeded = data.is_request_needed(), - .isFake = data.is_fake(), - .isScam = data.is_scam(), - .isVerified = data.is_verified(), - }; -} - -[[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( - const ChatInvite &invite) { - using Type = Info::Profile::BadgeType; - return invite.isVerified - ? Type::Verified - : invite.isScam - ? Type::Scam - : invite.isFake - ? Type::Fake - : Type::None; -} - -void ConfirmInviteBox::prepare() { - addButton( - (_requestApprove - ? tr::lng_group_request_to_join() - : _isChannel - ? tr::lng_profile_join_channel() - : tr::lng_profile_join_group()), - _submit); - addButton(tr::lng_cancel(), [=] { closeBox(); }); - - while (_participants.size() > 4) { - _participants.pop_back(); - } - - auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom(); - if (!_participants.empty()) { - int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5; - int padding = skip / 2; - _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); - int sumWidth = _participants.size() * _userWidth; - int left = (st::boxWideWidth - sumWidth) / 2; - for (const auto &participant : _participants) { - auto name = new Ui::FlatLabel(this, st::confirmInviteUserName); - name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); - name->setText(participant.user->firstName.isEmpty() - ? participant.user->name() - : participant.user->firstName); - name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); - left += _userWidth; - } - - newHeight += st::confirmInviteUserHeight; - } - if (_about) { - const auto padding = st::confirmInviteAboutPadding; - _about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); - newHeight += padding.top() + _about->height() + padding.bottom(); - } - if (_aboutRequests) { - const auto padding = st::confirmInviteAboutRequestsPadding; - _aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); - newHeight += padding.top() + _aboutRequests->height() + padding.bottom(); - } - setDimensions(st::boxWideWidth, newHeight); -} - -void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { - BoxContent::resizeEvent(e); - - const auto padding = st::boxRowPadding; - auto nameWidth = width() - padding.left() - padding.right(); - auto badgeWidth = 0; - if (const auto widget = _badge->widget()) { - badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width(); - nameWidth -= badgeWidth; - } - _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth())); - _title->moveToLeft( - (width() - _title->width() - badgeWidth) / 2, - st::confirmInviteTitleTop); - const auto badgeLeft = _title->x() + _title->width(); - const auto badgeTop = _title->y(); - const auto badgeBottom = _title->y() + _title->height(); - _badge->move(badgeLeft, badgeTop, badgeBottom); - - _status->move( - (width() - _status->width()) / 2, - st::confirmInviteStatusTop); - auto bottom = _status->y() - + _status->height() - + st::boxPadding.bottom() - + (_participants.empty() ? 0 : st::confirmInviteUserHeight); - if (_about) { - const auto padding = st::confirmInviteAboutPadding; - _about->move((width() - _about->width()) / 2, bottom + padding.top()); - bottom += padding.top() + _about->height() + padding.bottom(); - } - if (_aboutRequests) { - const auto padding = st::confirmInviteAboutRequestsPadding; - _aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top()); - } -} - -void ConfirmInviteBox::paintEvent(QPaintEvent *e) { - BoxContent::paintEvent(e); - - Painter p(this); - - if (_photo) { - if (const auto image = _photo->image(Data::PhotoSize::Small)) { - const auto size = st::confirmInvitePhotoSize; - p.drawPixmap( - (width() - size) / 2, - st::confirmInvitePhotoTop, - image->pix( - { size, size }, - { .options = Images::Option::RoundCircle })); - } - } else if (_photoEmpty) { - _photoEmpty->paintCircle( - p, - (width() - st::confirmInvitePhotoSize) / 2, - st::confirmInvitePhotoTop, - width(), - st::confirmInvitePhotoSize); - } - - int sumWidth = _participants.size() * _userWidth; - int left = (width() - sumWidth) / 2; - for (auto &participant : _participants) { - participant.user->paintUserpicLeft( - p, - participant.userpic, - left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, - st::confirmInviteUserPhotoTop, - width(), - st::confirmInviteUserPhotoSize); - left += _userWidth; - } -} diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index 94eeab5e92220d..ae3a580f20d0ce 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -7,32 +7,22 @@ For license and copyright information please follow this link: */ #pragma once -#include "ui/layers/box_content.h" +#include "base/weak_ptr.h" -class UserData; class ChannelData; -namespace Info::Profile { -class Badge; -enum class BadgeType; -} // namespace Info::Profile - namespace Main { class Session; } // namespace Main +namespace Ui { +class Show; +} // namespace Ui + namespace Window { class SessionController; } // namespace Window -namespace Data { -class PhotoMedia; -} // namespace Data - -namespace Ui { -class EmptyUserpic; -} // namespace Ui - namespace Api { void CheckChatInvite( @@ -41,68 +31,11 @@ void CheckChatInvite( ChannelData *invitePeekChannel = nullptr, Fn loaded = nullptr); -} // namespace Api - -class ConfirmInviteBox final : public Ui::BoxContent { -public: - ConfirmInviteBox( - QWidget*, - not_null session, - const MTPDchatInvite &data, - ChannelData *invitePeekChannel, - Fn submit); - ~ConfirmInviteBox(); +void ProcessChatInviteJoinResult( + not_null session, + std::shared_ptr show, + const MTPmessages_ChatInviteJoinResult &result, + Fn done, + base::weak_ptr controller = {}); -protected: - void prepare() override; - - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - -private: - struct Participant; - struct ChatInvite { - QString title; - QString about; - PhotoData *photo = nullptr; - int participantsCount = 0; - std::vector participants; - bool isPublic = false; - bool isChannel = false; - bool isMegagroup = false; - bool isBroadcast = false; - bool isRequestNeeded = false; - bool isFake = false; - bool isScam = false; - bool isVerified = false; - }; - [[nodiscard]] static ChatInvite Parse( - not_null session, - const MTPDchatInvite &data); - [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( - const ChatInvite &invite); - - ConfirmInviteBox( - not_null session, - ChatInvite &&invite, - ChannelData *invitePeekChannel, - Fn submit); - - const not_null _session; - - Fn _submit; - object_ptr _title; - std::unique_ptr _badge; - object_ptr _status; - object_ptr _about; - object_ptr _aboutRequests; - std::shared_ptr _photo; - std::unique_ptr _photoEmpty; - std::vector _participants; - - bool _isChannel = false; - bool _requestApprove = false; - - int _userWidth = 0; - -}; +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index 10663d1de5e404..9e09257bbee7a4 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.cpp +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -51,40 +51,47 @@ std::vector ParseList( void ApplyMegagroupAdmins(not_null channel, Members list) { Expects(channel->isMegagroup()); - const auto i = ranges::find_if(list, &Api::ChatParticipant::isCreator); - if (i != list.end()) { - i->tryApplyCreatorTo(channel); - } else { - channel->mgInfo->creator = nullptr; - channel->mgInfo->creatorRank = QString(); - } + const auto creatorIt = ranges::find_if( + list, + &Api::ChatParticipant::isCreator); - auto adding = base::flat_map(); + auto adding = base::flat_set(); + auto addingRanks = base::flat_map(); for (const auto &p : list) { if (p.isUser()) { - adding.emplace(p.userId(), p.rank()); + adding.emplace(p.userId()); + if (!p.rank().isEmpty()) { + addingRanks.emplace(p.userId(), p.rank()); + } } } - if (channel->mgInfo->creator) { - adding.emplace( - peerToUser(channel->mgInfo->creator->id), - channel->mgInfo->creatorRank); + if (creatorIt != list.end() && creatorIt->isUser()) { + adding.emplace(creatorIt->userId()); + if (!creatorIt->rank().isEmpty()) { + addingRanks.emplace(creatorIt->userId(), creatorIt->rank()); + } } auto removing = channel->mgInfo->admins; if (removing.empty() && adding.empty()) { - // Add some admin-placeholder so we don't DDOS - // server with admins list requests. LOG(("API Error: Got empty admins list from server.")); - adding.emplace(0, QString()); + adding.emplace(UserId(0)); } Data::ChannelAdminChanges changes(channel); - for (const auto &[addingId, rank] : adding) { - if (!removing.remove(addingId)) { - changes.add(addingId, rank); - } + if (creatorIt != list.end()) { + creatorIt->tryApplyCreatorTo(channel); + } else { + channel->mgInfo->creator = nullptr; + } + for (const auto &addingId : adding) { + const auto r = addingRanks.find(addingId); + const auto rank = (r != end(addingRanks)) + ? r->second + : QString(); + removing.remove(addingId); + changes.add(addingId, rank); } - for (const auto &[removingId, rank] : removing) { + for (const auto &removingId : removing) { changes.remove(removingId); } } @@ -112,6 +119,7 @@ void ApplyLastList( channel->mgInfo->lastAdmins.clear(); channel->mgInfo->lastRestricted.clear(); channel->mgInfo->lastParticipants.clear(); + channel->mgInfo->memberRanks.clear(); channel->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate | MegagroupInfo::LastParticipantsOnceReceived; @@ -143,11 +151,13 @@ void ApplyLastList( } if (user->isBot()) { channel->mgInfo->bots.insert(user); - if ((channel->mgInfo->botStatus != 0) - && (channel->mgInfo->botStatus < 2)) { - channel->mgInfo->botStatus = 2; + if (channel->mgInfo->botStatus == Data::BotStatus::NoBots) { + channel->mgInfo->botStatus = Data::BotStatus::HasBots; } } + if (!p.rank().isEmpty()) { + channel->mgInfo->memberRanks[p.userId()] = p.rank(); + } } } // @@ -178,7 +188,7 @@ void ApplyBotsList( Members list) { const auto history = channel->owner().historyLoaded(channel); channel->mgInfo->bots.clear(); - channel->mgInfo->botStatus = -1; + channel->mgInfo->botStatus = Data::BotStatus::NoBots; auto needBotsInfos = false; auto botStatus = channel->mgInfo->botStatus; @@ -188,7 +198,7 @@ void ApplyBotsList( const auto user = participant->asUser(); if (user && user->isBot()) { channel->mgInfo->bots.insert(user); - botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1; + botStatus = Data::BotStatus::HasBots; if (!user->botInfo->inited) { needBotsInfos = true; } @@ -211,11 +221,10 @@ void ApplyBotsList( Data::PeerUpdate::Flag::FullInfo); } -[[nodiscard]] ChatParticipants::Channels ParseSimilar( +[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels( not_null session, const MTPmessages_Chats &chats) { - auto result = ChatParticipants::Channels(); - std::vector>(); + auto result = ChatParticipants::Peers(); chats.match([&](const auto &data) { const auto &list = data.vchats().v; result.list.reserve(list.size()); @@ -234,10 +243,29 @@ void ApplyBotsList( return result; } -[[nodiscard]] ChatParticipants::Channels ParseSimilar( +[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels( not_null channel, const MTPmessages_Chats &chats) { - return ParseSimilar(&channel->session(), chats); + return ParseSimilarChannels(&channel->session(), chats); +} + +[[nodiscard]] ChatParticipants::Peers ParseSimilarBots( + not_null session, + const MTPusers_Users &users) { + auto result = ChatParticipants::Peers(); + users.match([&](const auto &data) { + const auto &list = data.vusers().v; + result.list.reserve(list.size()); + for (const auto &user : list) { + result.list.push_back(session->data().processUser(user)); + } + if constexpr (MTPDusers_usersSlice::Is()) { + if (session->premiumPossible()) { + result.more = data.vcount().v - data.vusers().v.size(); + } + } + }); + return result; } } // namespace @@ -269,12 +297,14 @@ ChatParticipant::ChatParticipant( _type = Type::Member; _date = data.vdate().v; _by = peerToUser(peerFromUser(data.vinviter_id())); + _rank = qs(data.vrank().value_or_empty()); if (data.vsubscription_until_date()) { _subscriptionDate = data.vsubscription_until_date()->v; } }, [&](const MTPDchannelParticipant &data) { _type = Type::Member; _date = data.vdate().v; + _rank = qs(data.vrank().value_or_empty()); if (data.vsubscription_until_date()) { _subscriptionDate = data.vsubscription_until_date()->v; } @@ -282,6 +312,7 @@ ChatParticipant::ChatParticipant( _restrictions = ChatRestrictionsInfo(data.vbanned_rights()); _by = peerToUser(peerFromUser(data.vkicked_by())); _date = data.vdate().v; + _rank = qs(data.vrank().value_or_empty()); _type = (_restrictions.flags & ChatRestriction::ViewMessages) ? Type::Banned @@ -313,7 +344,11 @@ void ChatParticipant::tryApplyCreatorTo( if (isCreator() && isUser()) { if (const auto info = channel->mgInfo.get()) { info->creator = channel->owner().userLoaded(userId()); - info->creatorRank = rank(); + if (!rank().isEmpty()) { + info->memberRanks[userId()] = rank(); + } else { + info->memberRanks.remove(userId()); + } } } } @@ -404,7 +439,7 @@ void ChatParticipants::requestForAdd( _forAdd.channel = channel; _forAdd.requestId = _api.request(MTPchannels_GetParticipants( - channel->inputChannel, + channel->inputChannel(), MTP_channelParticipantsRecent(), MTP_int(offset), MTP_int(channel->session().serverConfig().chatSizeMax), @@ -432,7 +467,7 @@ void ChatParticipants::requestLast(not_null channel) { const auto offset = 0; const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( - channel->inputChannel, + channel->inputChannel(), MTP_channelParticipantsRecent(), MTP_int(offset), MTP_int(channel->session().serverConfig().chatSizeMax), @@ -462,7 +497,7 @@ void ChatParticipants::requestBots(not_null channel) { const auto offset = 0; const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( - channel->inputChannel, + channel->inputChannel(), MTP_channelParticipantsBots(), MTP_int(offset), MTP_int(channel->session().serverConfig().chatSizeMax), @@ -476,8 +511,15 @@ void ChatParticipants::requestBots(not_null channel) { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); - }).fail([=] { + }).fail([=](const MTP::Error &error) { _botsRequests.remove(channel); + if (error.type() == u"CHANNEL_MONOFORUM_UNSUPPORTED"_q) { + channel->mgInfo->bots.clear(); + channel->mgInfo->botStatus = Data::BotStatus::NoBots; + channel->session().changes().peerUpdated( + channel, + Data::PeerUpdate::Flag::FullInfo); + } }).send(); _botsRequests[channel] = requestId; @@ -491,7 +533,7 @@ void ChatParticipants::requestAdmins(not_null channel) { const auto offset = 0; const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( - channel->inputChannel, + channel->inputChannel(), MTP_channelParticipantsAdmins(), MTP_int(offset), MTP_int(channel->session().serverConfig().chatSizeMax), @@ -530,8 +572,8 @@ void ChatParticipants::add( if (const auto chat = peer->asChat()) { for (const auto &user : users) { _api.request(MTPmessages_AddChatUser( - chat->inputChat, - user->inputUser, + chat->inputChat(), + user->inputUser(), MTP_int(passGroupHistory ? kForwardMessagesOnAdd : 0) )).done([=](const MTPmessages_InvitedUsers &result) { const auto &data = result.data(); @@ -562,7 +604,7 @@ void ChatParticipants::add( const auto send = [&] { const auto callback = base::take(done); _api.request(MTPchannels_InviteToChannel( - channel->inputChannel, + channel->inputChannel(), MTP_vector(list) )).done([=](const MTPmessages_InvitedUsers &result) { const auto &data = result.data(); @@ -581,7 +623,7 @@ void ChatParticipants::add( }).afterDelay(kSmallDelayMs).send(); }; for (const auto &user : users) { - list.push_back(user->inputUser); + list.push_back(user->inputUser()); if (list.size() == kMaxUsersPerInvite) { send(); list.clear(); @@ -626,23 +668,20 @@ void ChatParticipants::Restrict( ChatRestrictionsInfo oldRights, ChatRestrictionsInfo newRights, Fn onDone, - Fn onFail) { + Fn onFail) { channel->session().api().request(MTPchannels_EditBanned( - channel->inputChannel, - participant->input, - MTP_chatBannedRights( - MTP_flags(MTPDchatBannedRights::Flags::from_raw( - uint32(newRights.flags))), - MTP_int(newRights.until)) + channel->inputChannel(), + participant->input(), + RestrictionsToMTP(newRights) )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); channel->applyEditBanned(participant, oldRights, newRights); if (onDone) { onDone(); } - }).fail([=] { + }).fail([=](const MTP::Error &error) { if (onFail) { - onFail(); + onFail(error.type()); } }).send(); } @@ -670,7 +709,7 @@ void ChatParticipants::requestSelf(not_null channel) { }; _selfParticipantRequests.emplace(channel); _api.request(MTPchannels_GetParticipant( - channel->inputChannel, + channel->inputChannel(), MTP_inputPeerSelf() )).done([=](const MTPchannels_ChannelParticipant &result) { _selfParticipantRequests.erase(channel); @@ -720,8 +759,8 @@ void ChatParticipants::kick( _api.request(MTPmessages_DeleteChatUser( MTP_flags(0), - chat->inputChat, - participant->asUser()->inputUser + chat->inputChat(), + participant->asUser()->inputUser() )).done([=](const MTPUpdates &result) { chat->session().api().applyUpdates(result); }).send(); @@ -736,12 +775,9 @@ void ChatParticipants::kick( const auto rights = ChannelData::KickedRestrictedRights(participant); const auto requestId = _api.request(MTPchannels_EditBanned( - channel->inputChannel, - participant->input, - MTP_chatBannedRights( - MTP_flags( - MTPDchatBannedRights::Flags::from_raw(uint32(rights.flags))), - MTP_int(rights.until)) + channel->inputChannel(), + participant->input(), + RestrictionsToMTP(rights) )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); @@ -763,8 +799,8 @@ void ChatParticipants::unblock( } const auto requestId = _api.request(MTPchannels_EditBanned( - channel->inputChannel, - participant->input, + channel->inputChannel(), + participant->input(), MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); @@ -782,52 +818,65 @@ void ChatParticipants::unblock( _kickRequests.emplace(kick, requestId); } -void ChatParticipants::loadSimilarChannels(not_null channel) { - if (!channel->isBroadcast()) { - return; - } else if (const auto i = _similar.find(channel); i != end(_similar)) { +void ChatParticipants::loadSimilarPeers(not_null peer) { + if (const auto i = _similar.find(peer); i != end(_similar)) { if (i->second.requestId - || !i->second.channels.more - || !channel->session().premium()) { + || !i->second.peers.more + || !peer->session().premium()) { return; } } - using Flag = MTPchannels_GetChannelRecommendations::Flag; - _similar[channel].requestId = _api.request( - MTPchannels_GetChannelRecommendations( - MTP_flags(Flag::f_channel), - channel->inputChannel) - ).done([=](const MTPmessages_Chats &result) { - auto &similar = _similar[channel]; - similar.requestId = 0; - auto parsed = ParseSimilar(channel, result); - if (similar.channels == parsed) { - return; - } - similar.channels = std::move(parsed); - if (const auto history = channel->owner().historyLoaded(channel)) { - if (const auto item = history->joinedMessageInstance()) { - history->owner().requestItemResize(item); + if (const auto channel = peer->asBroadcast()) { + using Flag = MTPchannels_GetChannelRecommendations::Flag; + _similar[peer].requestId = _api.request( + MTPchannels_GetChannelRecommendations( + MTP_flags(Flag::f_channel), + channel->inputChannel()) + ).done([=](const MTPmessages_Chats &result) { + auto &similar = _similar[channel]; + similar.requestId = 0; + auto parsed = ParseSimilarChannels(channel, result); + if (similar.peers == parsed) { + return; } - } - _similarLoaded.fire_copy(channel); - }).send(); + similar.peers = std::move(parsed); + if (const auto history = channel->owner().historyLoaded(channel)) { + if (const auto item = history->joinedMessageInstance()) { + history->owner().requestItemResize(item); + } + } + _similarLoaded.fire_copy(channel); + }).send(); + } else if (const auto bot = peer->asBot()) { + _similar[peer].requestId = _api.request( + MTPbots_GetBotRecommendations(bot->inputUser()) + ).done([=](const MTPusers_Users &result) { + auto &similar = _similar[peer]; + similar.requestId = 0; + auto parsed = ParseSimilarBots(&peer->session(), result); + if (similar.peers == parsed) { + return; + } + similar.peers = std::move(parsed); + _similarLoaded.fire_copy(peer); + }).send(); + } } -auto ChatParticipants::similar(not_null channel) --> const Channels & { - const auto i = channel->isBroadcast() - ? _similar.find(channel) +auto ChatParticipants::similar(not_null peer) +-> const Peers & { + const auto i = (peer->isBroadcast() || peer->isBot()) + ? _similar.find(peer) : end(_similar); if (i != end(_similar)) { - return i->second.channels; + return i->second.peers; } - static const auto empty = Channels(); + static const auto empty = Peers(); return empty; } auto ChatParticipants::similarLoaded() const --> rpl::producer> { +-> rpl::producer> { return _similarLoaded.events(); } @@ -841,15 +890,15 @@ void ChatParticipants::loadRecommendations() { MTP_inputChannelEmpty()) ).done([=](const MTPmessages_Chats &result) { _recommendations.requestId = 0; - auto parsed = ParseSimilar(_session, result); - _recommendations.channels = std::move(parsed); - _recommendations.channels.more = 0; + auto parsed = ParseSimilarChannels(_session, result); + _recommendations.peers = std::move(parsed); + _recommendations.peers.more = 0; _recommendationsLoaded = true; }).send(); } -const ChatParticipants::Channels &ChatParticipants::recommendations() const { - return _recommendations.channels; +const ChatParticipants::Peers &ChatParticipants::recommendations() const { + return _recommendations.peers; } rpl::producer<> ChatParticipants::recommendationsLoaded() const { diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h index df332522d806e1..4fedab7a758a8e 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.h +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -113,7 +113,7 @@ class ChatParticipants final { ChatRestrictionsInfo oldRights, ChatRestrictionsInfo newRights, Fn onDone, - Fn onFail); + Fn onFail); void add( std::shared_ptr show, not_null peer, @@ -138,27 +138,27 @@ class ChatParticipants final { not_null channel, not_null participant); - void loadSimilarChannels(not_null channel); + void loadSimilarPeers(not_null peer); - struct Channels { - std::vector> list; + struct Peers { + std::vector> list; int more = 0; friend inline bool operator==( - const Channels &, - const Channels &) = default; + const Peers &, + const Peers &) = default; }; - [[nodiscard]] const Channels &similar(not_null channel); + [[nodiscard]] const Peers &similar(not_null peer); [[nodiscard]] auto similarLoaded() const - -> rpl::producer>; + -> rpl::producer>; void loadRecommendations(); - [[nodiscard]] const Channels &recommendations() const; + [[nodiscard]] const Peers &recommendations() const; [[nodiscard]] rpl::producer<> recommendationsLoaded() const; private: - struct SimilarChannels { - Channels channels; + struct SimilarPeers { + Peers peers; mtpRequestId requestId = 0; }; @@ -186,10 +186,10 @@ class ChatParticipants final { not_null>; base::flat_map _kickRequests; - base::flat_map, SimilarChannels> _similar; - rpl::event_stream> _similarLoaded; + base::flat_map, SimilarPeers> _similar; + rpl::event_stream> _similarLoaded; - SimilarChannels _recommendations; + SimilarPeers _recommendations; rpl::variable _recommendationsLoaded = false; }; diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp index e974154b989cb3..359e9ad08f419c 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.cpp +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -68,7 +68,7 @@ void CloudPassword::clearUnconfirmedPassword() { rpl::producer CloudPassword::state() const { return _state ? _stateChanges.events_starting_with_copy(*_state) - : (_stateChanges.events() | rpl::type_erased()); + : (_stateChanges.events() | rpl::type_erased); } auto CloudPassword::stateCurrent() const @@ -544,4 +544,38 @@ auto CloudPassword::checkRecoveryEmailAddressCode(const QString &code) }; } +void RequestLoginEmailCode( + MTP::Sender &api, + const QString &sendToEmail, + Fn done, + Fn fail) { + api.request(MTPaccount_SendVerifyEmailCode( + MTP_emailVerifyPurposeLoginChange(), + MTP_string(sendToEmail) + )).done([=](const MTPaccount_SentEmailCode &result) { + done(result.data().vlength().v, qs(result.data().vemail_pattern())); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); +} + +void VerifyLoginEmail( + MTP::Sender &api, + const QString &code, + Fn done, + Fn fail) { + api.request(MTPaccount_VerifyEmail( + MTP_emailVerifyPurposeLoginChange(), + MTP_emailVerificationCode(MTP_string(code)) + )).done([=](const MTPaccount_EmailVerified &result) { + result.match([=](const MTPDaccount_emailVerified &data) { + done(); + }, [=](const MTPDaccount_emailVerifiedLogin &data) { + fail(QString()); + }); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_cloud_password.h b/Telegram/SourceFiles/api/api_cloud_password.h index 30bc37938268eb..64b0d546174633 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.h +++ b/Telegram/SourceFiles/api/api_cloud_password.h @@ -70,4 +70,15 @@ class CloudPassword final { }; +void RequestLoginEmailCode( + MTP::Sender &api, + const QString &sendToEmail, + Fn done, + Fn fail); +void VerifyLoginEmail( + MTP::Sender &api, + const QString &code, + Fn done, + Fn fail); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index cfb1e72207fd19..9e86c94abff19a 100644 --- a/Telegram/SourceFiles/api/api_common.cpp +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -14,6 +14,17 @@ For license and copyright information please follow this link: namespace Api { +MTPSuggestedPost SuggestToMTP(SuggestOptions suggest) { + using Flag = MTPDsuggestedPost::Flag; + return suggest.exists + ? MTP_suggestedPost( + MTP_flags((suggest.date ? Flag::f_schedule_date : Flag()) + | (suggest.price().empty() ? Flag() : Flag::f_price)), + StarsAmountToTL(suggest.price()), + MTP_int(suggest.date)) + : MTPSuggestedPost(); +} + SendAction::SendAction( not_null thread, SendOptions options) diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 81f098d670c0e8..3b6884dcd93b1a 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -19,17 +19,25 @@ namespace Api { inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE); +[[nodiscard]] MTPSuggestedPost SuggestToMTP(SuggestOptions suggest); + struct SendOptions { uint64 price = 0; PeerData *sendAs = nullptr; TimeId scheduled = 0; + TimeId scheduleRepeatPeriod = 0; BusinessShortcutId shortcutId = 0; EffectId effectId = 0; + QByteArray stakeSeedHash; + int64 stakeNanoTon = 0; + int starsApproved = 0; bool silent = false; bool handleSupportSwitch = false; bool invertCaption = false; bool hideViaBot = false; + bool mediaSpoiler = false; crl::time ttlSeconds = 0; + SuggestOptions suggest; friend inline bool operator==( const SendOptions &, @@ -74,7 +82,10 @@ struct MessageToSend { struct RemoteFileInfo { MTPInputFile file; std::optional thumb; + std::optional videoCover; std::vector attachedStickers; + bool forceFile = false; + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_compose_with_ai.cpp b/Telegram/SourceFiles/api/api_compose_with_ai.cpp new file mode 100644 index 00000000000000..721a4e67e4357b --- /dev/null +++ b/Telegram/SourceFiles/api/api_compose_with_ai.cpp @@ -0,0 +1,307 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_compose_with_ai.h" + +#include "api/api_text_entities.h" +#include "apiwrap.h" +#include "base/options.h" +#include "core/shortcuts.h" +#include "data/data_ai_compose_tones.h" +#include "data/data_session.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/layers/show.h" +#include "ui/widgets/fields/input_field.h" + +namespace Api { +namespace { + +base::options::option OptionAiApplyToneSlug({ + .id = "ai-apply-tone-slug", + .name = "AI apply tone slug", + .description = "Slug of the AI compose tone bound to the in-place" + " apply hotkey. Empty means no tone is bound.", +}); + +[[nodiscard]] MTPTextWithEntities Serialize( + not_null session, + const TextWithEntities &text) { + return MTP_textWithEntities( + MTP_string(text.text), + EntitiesToMTP(session, text.entities, ConvertOption::SkipLocal)); +} + +} // namespace + +ComposeWithAi::ComposeWithAi(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +mtpRequestId ComposeWithAi::request( + Request request, + Fn done, + Fn fail) { + using Flag = MTPmessages_composeMessageWithAI::Flag; + auto flags = MTPmessages_composeMessageWithAI::Flags(0); + if (request.proofread) { + flags |= Flag::f_proofread; + } + if (!request.translateToLang.isEmpty()) { + flags |= Flag::f_translate_to_lang; + } + if (request.tone) { + flags |= Flag::f_tone; + } + if (request.emojify) { + flags |= Flag::f_emojify; + } + const auto session = _session; + return _api.request(MTPmessages_ComposeMessageWithAI( + MTP_flags(flags), + Serialize(session, request.text), + request.translateToLang.isEmpty() + ? MTPstring() + : MTP_string(request.translateToLang), + request.tone + ? (request.tone->id + ? MTP_inputAiComposeToneID( + MTP_long(request.tone->id), + MTP_long(request.tone->accessHash)) + : MTP_inputAiComposeToneDefault( + MTP_string(request.tone->defaultTone))) + : MTPInputAiComposeTone() + )).done([=, done = std::move(done)]( + const MTPmessages_ComposedMessageWithAI &result) mutable { + const auto &data = result.data(); + auto parsed = Result{ + .resultText = ParseTextWithEntities(session, data.vresult_text()), + }; + if (const auto diff = data.vdiff_text()) { + parsed.diffText = ParseDiff(session, *diff); + } + done(std::move(parsed)); + }).fail([=, fail = std::move(fail)](const MTP::Error &error) mutable { + if (fail) { + fail(error); + } + }).send(); +} + +void ComposeWithAi::cancel(mtpRequestId requestId) { + if (requestId) { + _api.request(requestId).cancel(); + } +} + +ComposeWithAi::Diff ComposeWithAi::ParseDiff( + not_null session, + const MTPTextWithEntities &text) { + const auto &data = text.data(); + auto result = Diff{ + .text = ParseTextWithEntities(session, text), + }; + const auto &entities = data.ventities().v; + result.entities.reserve(entities.size()); + for (const auto &entity : entities) { + entity.match([&](const MTPDmessageEntityDiffInsert &data) { + result.entities.push_back({ + .type = DiffEntity::Type::Insert, + .offset = data.voffset().v, + .length = data.vlength().v, + }); + }, [&](const MTPDmessageEntityDiffReplace &data) { + result.entities.push_back({ + .type = DiffEntity::Type::Replace, + .offset = data.voffset().v, + .length = data.vlength().v, + .oldText = qs(data.vold_text()), + }); + }, [&](const MTPDmessageEntityDiffDelete &data) { + result.entities.push_back({ + .type = DiffEntity::Type::Delete, + .offset = data.voffset().v, + .length = data.vlength().v, + }); + }, [](const auto &) { + }); + } + return result; +} + +void ApplyAiInPlaceBySlug( + not_null session, + TextWithEntities text, + QString slug, + Fn done, + Fn fail) { + auto apply = [=, text = std::move(text), done = std::move(done)]( + ComposeWithAi::ToneRef tone) mutable { + (void)session->api().composeWithAi().request({ + .text = std::move(text), + .tone = std::move(tone), + }, [done = std::move(done)](ComposeWithAi::Result &&result) { + if (done) { + done(std::move(result.resultText)); + } + }, fail); + }; + auto &tones = session->data().aiComposeTones(); + for (const auto &cached : tones.list()) { + if (!cached.isDefault && cached.slug == slug) { + apply({ .id = cached.id, .accessHash = cached.accessHash }); + return; + } + } + tones.resolve(slug, [apply = std::move(apply)]( + Data::AiComposeTone tone) mutable { + if (tone.isDefault) { + apply({ .defaultTone = tone.defaultType }); + } else { + apply({ .id = tone.id, .accessHash = tone.accessHash }); + } + }, std::move(fail)); +} + +QString AiApplyBoundSlug() { + return OptionAiApplyToneSlug.value(); +} + +void SetAiApplyBoundSlug(const QString &slug) { + OptionAiApplyToneSlug.set(slug); +} + +void ClearAiApplyBoundSlug() { + OptionAiApplyToneSlug.set(QString()); +} + +QString AiApplyShortcutText() { + for (const auto &[keys, commands] : Shortcuts::KeysCurrents()) { + if (commands.contains(Shortcuts::Command::ComposeAiApplyInPlace)) { + auto result = keys.toString(); +#ifdef Q_OS_MAC + result = result.replace(u"Ctrl+"_q, QString() + QChar(0x2318)); + result = result.replace(u"Meta+"_q, QString() + QChar(0x2303)); + result = result.replace(u"Alt+"_q, QString() + QChar(0x2325)); + result = result.replace(u"Shift+"_q, QString() + QChar(0x21E7)); +#endif // Q_OS_MAC + return result; + } + } + return QString(); +} + +void TriggerAiApplyInPlace( + not_null session, + std::shared_ptr show, + not_null guard, + not_null field, + TextWithEntities fullFieldText, + Fn applyToField) { + const auto slug = AiApplyBoundSlug(); + if (slug.isEmpty()) { + show->showToast(tr::lng_ai_compose_apply_unbound(tr::now)); + return; + } + const auto cursor = field->textCursor(); + const auto hasSelection = cursor.hasSelection(); + const auto selectionStart = cursor.selectionStart(); + const auto selectionEnd = cursor.selectionEnd(); + const auto savedCursorPosition = cursor.position(); + auto text = TextWithEntities(); + if (hasSelection) { + const auto part = field->getTextWithTagsPart( + selectionStart, + selectionEnd); + text = { + part.text, + TextUtilities::ConvertTextTagsToEntities(part.tags), + }; + } else { + text = std::move(fullFieldText); + } + if (text.text.isEmpty()) { + show->showToast(tr::lng_ai_compose_apply_empty(tr::now)); + return; + } + const auto fieldRaw = field.get(); + ApplyAiInPlaceBySlug( + session, + std::move(text), + slug, + crl::guard(guard.get(), [=](TextWithEntities result) { + auto replacement = TextWithTags{ + result.text, + TextUtilities::ConvertEntitiesToTextTags(result.entities), + }; + auto full = TextWithTags(); + auto restoreCursor = 0; + if (hasSelection) { + const auto fullLength = int( + fieldRaw->getTextWithTags().text.size()); + const auto from = std::clamp( + selectionStart, + 0, + fullLength); + const auto till = std::clamp( + selectionEnd, + from, + fullLength); + auto before = fieldRaw->getTextWithTagsPart(0, from); + auto after = fieldRaw->getTextWithTagsPart(till); + const auto shiftMiddle = int(before.text.size()); + const auto shiftAfter = shiftMiddle + + int(replacement.text.size()); + full.text = before.text + + replacement.text + + after.text; + full.tags = std::move(before.tags); + full.tags.reserve(full.tags.size() + + replacement.tags.size() + + after.tags.size()); + for (const auto &tag : replacement.tags) { + full.tags.push_back({ + tag.offset + shiftMiddle, + tag.length, + tag.id, + }); + } + for (const auto &tag : after.tags) { + full.tags.push_back({ + tag.offset + shiftAfter, + tag.length, + tag.id, + }); + } + restoreCursor = shiftAfter; + } else { + full = std::move(replacement); + restoreCursor = std::clamp( + savedCursorPosition, + 0, + int(full.text.size())); + } + applyToField(std::move(full), restoreCursor); + }), + crl::guard(guard.get(), [=](const MTP::Error &error) { + if (MTP::IgnoreError(error)) { + return; + } + const auto type = error.type(); + if (type == u"AICOMPOSE_TONE_SLUG_INVALID"_q + || type == u"AICOMPOSE_TONE_INVALID"_q + || type == u"TONE_NOT_FOUND"_q) { + ClearAiApplyBoundSlug(); + show->showToast(tr::lng_ai_compose_tone_invalid(tr::now)); + } else { + show->showToast(type); + } + })); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_compose_with_ai.h b/Telegram/SourceFiles/api/api_compose_with_ai.h new file mode 100644 index 00000000000000..acec75cf64b263 --- /dev/null +++ b/Telegram/SourceFiles/api/api_compose_with_ai.h @@ -0,0 +1,114 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/sender.h" +#include "ui/text/text_entity.h" + +#include +#include + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class InputField; +class Show; +} // namespace Ui + +namespace Api { + +class ComposeWithAi final { +public: + struct ToneRef { + QString defaultTone; + uint64 id = 0; + uint64 accessHash = 0; + }; + + struct Request { + TextWithEntities text; + QString translateToLang; + std::optional tone; + bool proofread = false; + bool emojify = false; + + void setDefaultTone(const QString &type) { + tone = ToneRef{ .defaultTone = type }; + } + void setCustomTone(uint64 id, uint64 accessHash) { + tone = ToneRef{ .id = id, .accessHash = accessHash }; + } + }; + + struct DiffEntity { + enum class Type { + Insert, + Replace, + Delete, + }; + + Type type = Type::Insert; + int offset = 0; + int length = 0; + QString oldText; + }; + + struct Diff { + TextWithEntities text; + std::vector entities; + }; + + struct Result { + TextWithEntities resultText; + std::optional diffText; + }; + + explicit ComposeWithAi(not_null api); + + [[nodiscard]] mtpRequestId request( + Request request, + Fn done, + Fn fail = nullptr); + void cancel(mtpRequestId requestId); + +private: + [[nodiscard]] static Diff ParseDiff( + not_null session, + const MTPTextWithEntities &text); + + const not_null _session; + MTP::Sender _api; + +}; + +void ApplyAiInPlaceBySlug( + not_null session, + TextWithEntities text, + QString slug, + Fn done, + Fn fail = nullptr); + +[[nodiscard]] QString AiApplyBoundSlug(); +void SetAiApplyBoundSlug(const QString &slug); +void ClearAiApplyBoundSlug(); + +[[nodiscard]] QString AiApplyShortcutText(); + +void TriggerAiApplyInPlace( + not_null session, + std::shared_ptr show, + not_null guard, + not_null field, + TextWithEntities fullFieldText, + Fn applyToField); + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index 3a42a793df602a..a080571a0ed8d8 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -87,7 +87,7 @@ void ConfirmPhone::resolve( sentCodeLength, fragmentUrl, timeout); - const auto boxWeak = Ui::MakeWeak(box.data()); + const auto boxWeak = base::make_weak(box.data()); using LoginCode = rpl::event_stream; const auto codeHandles = box->lifetime().make_state(); controller->session().account().setHandleLoginCode([=]( @@ -95,7 +95,7 @@ void ConfirmPhone::resolve( codeHandles->fire_copy(code); }); box->resendRequests( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _api.request(MTPauth_ResendCode( MTP_flags(0), MTP_string(phone), @@ -110,7 +110,7 @@ void ConfirmPhone::resolve( rpl::merge( codeHandles->events(), box->checkRequests() - ) | rpl::start_with_next([=](const QString &code) { + ) | rpl::on_next([=](const QString &code) { if (_checkRequestId) { return; } @@ -142,7 +142,7 @@ void ConfirmPhone::resolve( }).handleFloodErrors().send(); }, box->lifetime()); box->boxClosing( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { controller->session().account().setHandleLoginCode(nullptr); }, box->lifetime()); @@ -150,6 +150,9 @@ void ConfirmPhone::resolve( }, [](const MTPDauth_sentCodeSuccess &) { LOG(("API Error: Unexpected auth.sentCodeSuccess " "(Api::ConfirmPhone).")); + }, [](const MTPDauth_sentCodePaymentRequired &) { + LOG(("API Error: Unexpected auth.sentCodePaymentRequired " + "(Api::ConfirmPhone).")); }); }).fail([=](const MTP::Error &error) { _sendRequestId = 0; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4ef4b09c85da9e..8711ab458e4226 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "api/api_credits.h" +#include "api/api_credits_history_entry.h" +#include "api/api_premium.h" #include "api/api_statistics_data_deserialize.h" #include "api/api_updates.h" #include "apiwrap.h" @@ -26,115 +28,27 @@ namespace { constexpr auto kTransactionsLimit = 100; -[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( - const MTPStarsTransaction &tl, - not_null peer) { - using HistoryPeerTL = MTPDstarsTransactionPeer; - using namespace Data; - const auto owner = &peer->owner(); - const auto photo = tl.data().vphoto() - ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) - : nullptr; - auto extended = std::vector(); - if (const auto list = tl.data().vextended_media()) { - extended.reserve(list->v.size()); - for (const auto &media : list->v) { - media.match([&](const MTPDmessageMediaPhoto &photo) { - if (const auto inner = photo.vphoto()) { - const auto photo = owner->processPhoto(*inner); - if (!photo->isNull()) { - extended.push_back(CreditsHistoryMedia{ - .type = CreditsHistoryMediaType::Photo, - .id = photo->id, - }); - } - } - }, [&](const MTPDmessageMediaDocument &document) { - if (const auto inner = document.vdocument()) { - const auto document = owner->processDocument(*inner); - if (document->isAnimation() - || document->isVideoFile() - || document->isGifv()) { - extended.push_back(CreditsHistoryMedia{ - .type = CreditsHistoryMediaType::Video, - .id = document->id, - }); - } - } - }, [&](const auto &) {}); - } - } - const auto barePeerId = tl.data().vpeer().match([]( - const HistoryPeerTL &p) { - return peerFromMTP(p.vpeer()); - }, [](const auto &) { - return PeerId(0); - }).value; - const auto stargift = tl.data().vstargift(); - const auto incoming = (int64(tl.data().vstars().v) >= 0); - return Data::CreditsHistoryEntry{ - .id = qs(tl.data().vid()), - .title = qs(tl.data().vtitle().value_or_empty()), - .description = { qs(tl.data().vdescription().value_or_empty()) }, - .date = base::unixtime::parse(tl.data().vdate().v), - .photoId = photo ? photo->id : 0, - .extended = std::move(extended), - .credits = tl.data().vstars().v, - .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), - .barePeerId = barePeerId, - .bareGiveawayMsgId = uint64( - tl.data().vgiveaway_post_id().value_or_empty()), - .bareGiftStickerId = (stargift - ? owner->processDocument(stargift->data().vsticker())->id - : 0), - .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { - return Data::CreditsHistoryEntry::PeerType::Peer; - }, [](const MTPDstarsTransactionPeerPlayMarket &) { - return Data::CreditsHistoryEntry::PeerType::PlayMarket; - }, [](const MTPDstarsTransactionPeerFragment &) { - return Data::CreditsHistoryEntry::PeerType::Fragment; - }, [](const MTPDstarsTransactionPeerAppStore &) { - return Data::CreditsHistoryEntry::PeerType::AppStore; - }, [](const MTPDstarsTransactionPeerUnsupported &) { - return Data::CreditsHistoryEntry::PeerType::Unsupported; - }, [](const MTPDstarsTransactionPeerPremiumBot &) { - return Data::CreditsHistoryEntry::PeerType::PremiumBot; - }, [](const MTPDstarsTransactionPeerAds &) { - return Data::CreditsHistoryEntry::PeerType::Ads; - }), - .subscriptionUntil = tl.data().vsubscription_period() - ? base::unixtime::parse(base::unixtime::now() - + tl.data().vsubscription_period()->v) - : QDateTime(), - .successDate = tl.data().vtransaction_date() - ? base::unixtime::parse(tl.data().vtransaction_date()->v) - : QDateTime(), - .successLink = qs(tl.data().vtransaction_url().value_or_empty()), - .convertStars = int(stargift - ? stargift->data().vconvert_stars().v - : 0), - .converted = stargift && incoming, - .reaction = tl.data().is_reaction(), - .refunded = tl.data().is_refund(), - .pending = tl.data().is_pending(), - .failed = tl.data().is_failed(), - .in = incoming, - .gift = tl.data().is_gift() || stargift.has_value(), - }; -} - [[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL( - const MTPStarsSubscription &tl) { + const MTPStarsSubscription &tl, + not_null peer) { return Data::SubscriptionEntry{ .id = qs(tl.data().vid()), .inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()), + .title = qs(tl.data().vtitle().value_or_empty()), + .slug = qs(tl.data().vinvoice_slug().value_or_empty()), .until = base::unixtime::parse(tl.data().vuntil_date().v), .subscription = Data::PeerSubscription{ .credits = tl.data().vpricing().data().vamount().v, .period = tl.data().vpricing().data().vperiod().v, }, .barePeerId = peerFromMTP(tl.data().vpeer()).value, + .photoId = (tl.data().vphoto() + ? peer->owner().photoFromWeb( + *tl.data().vphoto(), + ImageLocation())->id + : 0), .cancelled = tl.data().is_canceled(), + .cancelledByBot = tl.data().is_bot_canceled(), .expired = (base::unixtime::now() > tl.data().vuntil_date().v), .canRefulfill = tl.data().is_can_refulfill(), }; @@ -150,23 +64,24 @@ constexpr auto kTransactionsLimit = 100; if (const auto history = data.vhistory()) { entries.reserve(history->v.size()); for (const auto &tl : history->v) { - entries.push_back(HistoryFromTL(tl, peer)); + entries.push_back(CreditsHistoryEntryFromTL(tl, peer)); } } auto subscriptions = std::vector(); if (const auto history = data.vsubscriptions()) { subscriptions.reserve(history->v.size()); for (const auto &tl : history->v) { - subscriptions.push_back(SubscriptionFromTL(tl)); + subscriptions.push_back(SubscriptionFromTL(tl, peer)); } } return Data::CreditsStatusSlice{ .list = std::move(entries), .subscriptions = std::move(subscriptions), - .balance = status.data().vbalance().v, + .balance = CreditsAmountFromTL(status.data().vbalance()), .subscriptionsMissingBalance = status.data().vsubscriptions_missing_balance().value_or_empty(), - .allLoaded = !status.data().vnext_offset().has_value(), + .allLoaded = !status.data().vnext_offset().has_value() + && !status.data().vsubscriptions_next_offset().has_value(), .token = qs(status.data().vnext_offset().value_or_empty()), .tokenSubscriptions = qs( status.data().vsubscriptions_next_offset().value_or_empty()), @@ -216,7 +131,7 @@ rpl::producer CreditsTopupOptions::request() { using TLOption = MTPStarsGiftOption; _api.request(MTPpayments_GetStarsGiftOptions( MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id), - user->inputUser + user->inputUser() )).done([=](const MTPVector &result) { _options = optionsFromTL(result.v); consumer.put_done(); @@ -246,11 +161,14 @@ void CreditsStatus::request( using TLResult = MTPpayments_StarsStatus; _requestId = _api.request(MTPpayments_GetStarsStatus( - _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input + MTP_flags(0), + _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input() )).done([=](const TLResult &result) { _requestId = 0; - const auto balance = result.data().vbalance().v; - _peer->session().credits().apply(_peer->id, balance); + const auto &balance = result.data().vbalance(); + _peer->session().credits().apply( + _peer->id, + CreditsAmountFromTL(balance)); if (const auto onstack = done) { onstack(StatusFromTL(result, _peer)); } @@ -262,28 +180,34 @@ void CreditsStatus::request( }).send(); } -CreditsHistory::CreditsHistory(not_null peer, bool in, bool out) +CreditsHistory::CreditsHistory( + not_null peer, + bool in, + bool out, + bool currency) : _peer(peer) -, _flags((in == out) +, _flags(((in == out) ? HistoryTL::Flags(0) : HistoryTL::Flags(0) | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0)) | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0))) + | (currency ? HistoryTL::Flag::f_ton : HistoryTL::Flags(0))) , _api(&peer->session().api().instance()) { } void CreditsHistory::request( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done) { + Fn done, + int limit) { if (_requestId) { return; } _requestId = _api.request(MTPpayments_GetStarsTransactions( MTP_flags(_flags), MTPstring(), // subscription_id - _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, + _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input(), MTP_string(token), - MTP_int(kTransactionsLimit) + MTP_int((limit > 0) ? limit : kTransactionsLimit) )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; done(StatusFromTL(result, _peer)); @@ -295,13 +219,16 @@ void CreditsHistory::request( void CreditsHistory::requestSubscriptions( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done) { + Fn done, + bool missingBalance) { if (_requestId) { return; } _requestId = _api.request(MTPpayments_GetStarsSubscriptions( - MTP_flags(0), - _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, + MTP_flags(missingBalance + ? MTPpayments_getStarsSubscriptions::Flag::f_missing_balance + : MTPpayments_getStarsSubscriptions::Flags(0)), + _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input(), MTP_string(token) )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; @@ -329,7 +256,9 @@ rpl::producer> PremiumPeerBot( const auto api = lifetime.make_state(&session->mtp()); api->request(MTPcontacts_ResolveUsername( - MTP_string(username) + MTP_flags(0), + MTP_string(username), + MTP_string() )).done([=](const MTPcontacts_ResolvedPeer &result) { session->data().processUsers(result.data().vusers()); session->data().processChats(result.data().vchats()); @@ -355,18 +284,21 @@ rpl::producer CreditsEarnStatistics::request() { auto lifetime = rpl::lifetime(); const auto finish = [=](const QString &url) { - makeRequest(MTPpayments_GetStarsRevenueStats( + api().request(MTPpayments_GetStarsRevenueStats( MTP_flags(0), - (_isUser ? user()->input : channel()->input) + (_isUser ? user()->input() : channel()->input()) )).done([=](const MTPpayments_StarsRevenueStats &result) { const auto &data = result.data(); const auto &status = data.vstatus().data(); _data = Data::CreditsEarnStatistics{ .revenueGraph = StatisticalGraphFromTL( data.vrevenue_graph()), - .currentBalance = status.vcurrent_balance().v, - .availableBalance = status.vavailable_balance().v, - .overallRevenue = status.voverall_revenue().v, + .currentBalance = CreditsAmountFromTL( + status.vcurrent_balance()), + .availableBalance = CreditsAmountFromTL( + status.vavailable_balance()), + .overallRevenue = CreditsAmountFromTL( + status.voverall_revenue()), .usdRate = data.vusd_rate().v, .isWithdrawalEnabled = status.is_withdrawal_enabled(), .nextWithdrawalAt = status.vnext_withdrawal_at() @@ -382,9 +314,9 @@ rpl::producer CreditsEarnStatistics::request() { }).send(); }; - makeRequest( + api().request( MTPpayments_GetStarsRevenueAdsAccountUrl( - (_isUser ? user()->input : channel()->input)) + (_isUser ? user()->input() : channel()->input())) ).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) { finish(qs(result.data().vurl())); }).fail([=](const MTP::Error &error) { @@ -453,4 +385,32 @@ Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const { return _options; } +void EditCreditsSubscription( + not_null session, + const QString &id, + bool cancel, + Fn done, + Fn fail) { + using Flag = MTPpayments_ChangeStarsSubscription::Flag; + session->api().request( + MTPpayments_ChangeStarsSubscription( + MTP_flags(Flag::f_canceled), + MTP_inputPeerSelf(), + MTP_string(id), + MTP_bool(cancel) + )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); +} + +MTPInputSavedStarGift InputSavedStarGiftId( + const Data::SavedStarGiftId &id, + const std::shared_ptr &unique) { + return (!id && unique) + ? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug)) + : id.isUser() + ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare)) + : MTP_inputSavedStarGiftChat( + id.chat()->input(), + MTP_long(id.chatSavedId())); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index d2e819e08327fc..9c676963bfd707 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -12,6 +12,10 @@ For license and copyright information please follow this link: #include "data/data_credits_earn.h" #include "mtproto/sender.h" +namespace Data { +class SavedStarGiftId; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -71,14 +75,20 @@ class CreditsStatus final { class CreditsHistory final { public: - CreditsHistory(not_null peer, bool in, bool out); + CreditsHistory( + not_null peer, + bool in, + bool out, + bool currency = false); void request( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done); + Fn done, + int limit = 0); void requestSubscriptions( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done); + Fn done, + bool missingBalance = false); private: using HistoryTL = MTPpayments_GetStarsTransactions; @@ -99,14 +109,23 @@ class CreditsEarnStatistics final : public StatisticsRequestSender { [[nodiscard]] Data::CreditsEarnStatistics data() const; private: + const bool _isUser = false; Data::CreditsEarnStatistics _data; - bool _isUser = false; - - mtpRequestId _requestId = 0; }; [[nodiscard]] rpl::producer> PremiumPeerBot( not_null session); +void EditCreditsSubscription( + not_null session, + const QString &id, + bool cancel, + Fn done, + Fn fail); + +[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId( + const Data::SavedStarGiftId &id, + const std::shared_ptr &unique = nullptr); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits_history_entry.cpp b/Telegram/SourceFiles/api/api_credits_history_entry.cpp new file mode 100644 index 00000000000000..e54537df0c0c33 --- /dev/null +++ b/Telegram/SourceFiles/api/api_credits_history_entry.cpp @@ -0,0 +1,171 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_credits_history_entry.h" + +#include "api/api_premium.h" +#include "base/unixtime.h" +#include "data/data_channel.h" +#include "data/data_credits.h" +#include "data/data_document.h" +#include "data/data_peer.h" +#include "data/data_photo.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "main/main_session.h" + +namespace Api { + +Data::CreditsHistoryEntry CreditsHistoryEntryFromTL( + const MTPStarsTransaction &tl, + not_null peer) { + using HistoryPeerTL = MTPDstarsTransactionPeer; + using namespace Data; + const auto owner = &peer->owner(); + const auto photo = tl.data().vphoto() + ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) + : nullptr; + auto extended = std::vector(); + if (const auto list = tl.data().vextended_media()) { + extended.reserve(list->v.size()); + for (const auto &media : list->v) { + media.match([&](const MTPDmessageMediaPhoto &data) { + if (const auto inner = data.vphoto()) { + const auto photo = owner->processPhoto(*inner); + if (!photo->isNull()) { + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Photo, + .id = photo->id, + }); + } + } + }, [&](const MTPDmessageMediaDocument &data) { + if (const auto inner = data.vdocument()) { + const auto document = owner->processDocument( + *inner, + data.valt_documents()); + if (document->isAnimation() + || document->isVideoFile() + || document->isGifv()) { + extended.push_back(CreditsHistoryMedia{ + .type = CreditsHistoryMediaType::Video, + .id = document->id, + }); + } + } + }, [&](const auto &) {}); + } + } + const auto barePeerId = tl.data().vpeer().match([]( + const HistoryPeerTL &p) { + return peerFromMTP(p.vpeer()); + }, [](const auto &) { + return PeerId(0); + }).value; + const auto stargift = tl.data().vstargift(); + const auto nonUniqueGift = stargift + ? stargift->match([&](const MTPDstarGift &data) { + return &data; + }, [](const auto &) { return (const MTPDstarGift*)nullptr; }) + : nullptr; + const auto reaction = tl.data().is_reaction(); + const auto amount = CreditsAmountFromTL(tl.data().vamount()); + const auto starrefAmount = CreditsAmountFromTL( + tl.data().vstarref_amount()); + const auto starrefCommission + = tl.data().vstarref_commission_permille().value_or_empty(); + const auto starrefBarePeerId = tl.data().vstarref_peer() + ? peerFromMTP(*tl.data().vstarref_peer()).value + : 0; + const auto incoming = (amount >= CreditsAmount()); + const auto paidMessagesCount + = tl.data().vpaid_messages().value_or_empty(); + const auto premiumMonthsForStars + = tl.data().vpremium_gift_months().value_or_empty(); + const auto saveActorId = (reaction + || !extended.empty() + || paidMessagesCount) && incoming; + const auto parsedGift = stargift + ? FromTL(&peer->session(), *stargift) + : std::optional(); + const auto giftStickerId = parsedGift ? parsedGift->document->id : 0; + return Data::CreditsHistoryEntry{ + .id = qs(tl.data().vid()), + .title = qs(tl.data().vtitle().value_or_empty()), + .description = { qs(tl.data().vdescription().value_or_empty()) }, + .date = base::unixtime::parse( + tl.data().vads_proceeds_from_date().value_or( + tl.data().vdate().v)), + .photoId = photo ? photo->id : 0, + .extended = std::move(extended), + .credits = CreditsAmountFromTL(tl.data().vamount()), + .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), + .barePeerId = saveActorId ? peer->id.value : barePeerId, + .bareGiveawayMsgId = uint64( + tl.data().vgiveaway_post_id().value_or_empty()), + .bareGiftStickerId = giftStickerId, + .bareActorId = saveActorId ? barePeerId : uint64(0), + .uniqueGift = parsedGift ? parsedGift->unique : nullptr, + .starrefAmount = paidMessagesCount ? CreditsAmount() : starrefAmount, + .starrefCommission = paidMessagesCount ? 0 : starrefCommission, + .starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId, + .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { + return Data::CreditsHistoryEntry::PeerType::Peer; + }, [](const MTPDstarsTransactionPeerPlayMarket &) { + return Data::CreditsHistoryEntry::PeerType::PlayMarket; + }, [](const MTPDstarsTransactionPeerFragment &) { + return Data::CreditsHistoryEntry::PeerType::Fragment; + }, [](const MTPDstarsTransactionPeerAppStore &) { + return Data::CreditsHistoryEntry::PeerType::AppStore; + }, [](const MTPDstarsTransactionPeerUnsupported &) { + return Data::CreditsHistoryEntry::PeerType::Unsupported; + }, [](const MTPDstarsTransactionPeerPremiumBot &) { + return Data::CreditsHistoryEntry::PeerType::PremiumBot; + }, [](const MTPDstarsTransactionPeerAds &) { + return Data::CreditsHistoryEntry::PeerType::Ads; + }, [](const MTPDstarsTransactionPeerAPI &) { + return Data::CreditsHistoryEntry::PeerType::API; + }), + .subscriptionUntil = tl.data().vsubscription_period() + ? base::unixtime::parse(base::unixtime::now() + + tl.data().vsubscription_period()->v) + : QDateTime(), + .adsProceedsToDate = tl.data().vads_proceeds_to_date() + ? base::unixtime::parse(tl.data().vads_proceeds_to_date()->v) + : QDateTime(), + .successDate = tl.data().vtransaction_date() + ? base::unixtime::parse(tl.data().vtransaction_date()->v) + : QDateTime(), + .successLink = qs(tl.data().vtransaction_url().value_or_empty()), + .paidMessagesCount = paidMessagesCount, + .paidMessagesAmount = (paidMessagesCount + ? starrefAmount + : CreditsAmount()), + .paidMessagesCommission = paidMessagesCount ? starrefCommission : 0, + .limitedCount = parsedGift ? parsedGift->limitedCount : 0, + .limitedLeft = parsedGift ? parsedGift->limitedLeft : 0, + .starsConverted = int(nonUniqueGift + ? nonUniqueGift->vconvert_stars().v + : 0), + .premiumMonthsForStars = premiumMonthsForStars, + .floodSkip = int(tl.data().vfloodskip_number().value_or(0)), + .converted = stargift && incoming, + .stargift = stargift.has_value(), + .postsSearch = tl.data().is_posts_search(), + .giftUpgraded = tl.data().is_stargift_upgrade(), + .giftResale = tl.data().is_stargift_resale(), + .giftOffer = tl.data().is_offer(), + .reaction = tl.data().is_reaction(), + .refunded = tl.data().is_refund(), + .pending = tl.data().is_pending(), + .failed = tl.data().is_failed(), + .in = incoming, + .gift = tl.data().is_gift() || stargift.has_value(), + }; +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits_history_entry.h b/Telegram/SourceFiles/api/api_credits_history_entry.h new file mode 100644 index 00000000000000..5b3762a851f657 --- /dev/null +++ b/Telegram/SourceFiles/api/api_credits_history_entry.h @@ -0,0 +1,22 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class PeerData; + +namespace Data { +struct CreditsHistoryEntry; +} // namespace Data + +namespace Api { + +[[nodiscard]] Data::CreditsHistoryEntry CreditsHistoryEntryFromTL( + const MTPStarsTransaction &tl, + not_null peer); + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index f05e5f61881930..a5c2a98a2f7567 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -25,7 +25,7 @@ void RestrictSponsored( bool restricted, Fn failed) { channel->session().api().request(MTPchannels_RestrictSponsoredMessages( - channel->inputChannel, + channel->inputChannel(), MTP_bool(restricted)) ).done([=](const MTPUpdates &updates) { channel->session().api().applyUpdates(updates); @@ -40,46 +40,49 @@ void HandleWithdrawalButton( std::shared_ptr show) { Expects(receiver.currencyReceiver || (receiver.creditsReceiver && receiver.creditsAmount)); + struct State { rpl::lifetime lifetime; bool loading = false; }; - const auto channel = receiver.currencyReceiver; - const auto peer = receiver.creditsReceiver; + const auto currencyReceiver = receiver.currencyReceiver; + const auto creditsReceiver = receiver.creditsReceiver; + const auto isChannel = receiver.currencyReceiver + && receiver.currencyReceiver->isChannel(); const auto state = button->lifetime().make_state(); - const auto session = (channel ? &channel->session() : &peer->session()); + const auto session = (currencyReceiver + ? ¤cyReceiver->session() + : &creditsReceiver->session()); - using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl; using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl; session->api().cloudPassword().reload(); const auto processOut = [=] { if (state->loading) { return; - } - if (peer && !receiver.creditsAmount()) { + } else if (creditsReceiver && !receiver.creditsAmount()) { return; } state->loading = true; state->lifetime = session->api().cloudPassword().state( ) | rpl::take( 1 - ) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) { + ) | rpl::on_next([=](const Core::CloudPasswordState &pass) { state->loading = false; auto fields = PasscodeBox::CloudFields::From(pass); - fields.customTitle = channel + fields.customTitle = isChannel ? tr::lng_channel_earn_balance_password_title() : tr::lng_bot_earn_balance_password_title(); - fields.customDescription = channel + fields.customDescription = isChannel ? tr::lng_channel_earn_balance_password_description(tr::now) : tr::lng_bot_earn_balance_password_description(tr::now); fields.customSubmitButton = tr::lng_passcode_submit(); fields.customCheckCallback = crl::guard(button, [=]( const Core::CloudPasswordResult &result, - QPointer box) { + base::weak_qptr box) { const auto done = [=](const QString &result) { if (!result.isEmpty()) { UrlClickHandler::Open(result); @@ -89,21 +92,24 @@ void HandleWithdrawalButton( } }; const auto fail = [=](const MTP::Error &error) { - show->showToast(error.type()); + const auto message = error.type(); + if (box && !box->handleCustomCheckError(message)) { + show->showToast(message); + } }; - if (channel) { - session->api().request( - MTPstats_GetBroadcastRevenueWithdrawalUrl( - channel->inputChannel, - result.result - )).done([=](const ChannelOutUrl &r) { - done(qs(r.data().vurl())); - }).fail(fail).send(); - } else if (peer) { + if (currencyReceiver || creditsReceiver) { + using F = MTPpayments_getStarsRevenueWithdrawalUrl::Flag; session->api().request( MTPpayments_GetStarsRevenueWithdrawalUrl( - peer->input, - MTP_long(receiver.creditsAmount()), + MTP_flags(currencyReceiver + ? F::f_ton + : F::f_amount), + currencyReceiver + ? currencyReceiver->input() + : creditsReceiver->input(), + MTP_long(creditsReceiver + ? receiver.creditsAmount() + : 0), result.result )).done([=](const CreditsOutUrl &r) { done(qs(r.data().vurl())); @@ -131,17 +137,19 @@ void HandleWithdrawalButton( processOut(); } }; - if (channel) { - session->api().request( - MTPstats_GetBroadcastRevenueWithdrawalUrl( - channel->inputChannel, - MTP_inputCheckPasswordEmpty() - )).fail(fail).send(); - } else if (peer) { + if (currencyReceiver || creditsReceiver) { + using F = MTPpayments_getStarsRevenueWithdrawalUrl::Flag; session->api().request( MTPpayments_GetStarsRevenueWithdrawalUrl( - peer->input, - MTP_long(std::numeric_limits::max()), + MTP_flags(currencyReceiver + ? F::f_ton + : F::f_amount), + currencyReceiver + ? currencyReceiver->input() + : creditsReceiver->input(), + MTP_long(creditsReceiver + ? receiver.creditsAmount() + : 0), MTP_inputCheckPasswordEmpty() )).fail(fail).send(); } diff --git a/Telegram/SourceFiles/api/api_earn.h b/Telegram/SourceFiles/api/api_earn.h index cbee5d25ab03a3..4142c9453c8770 100644 --- a/Telegram/SourceFiles/api/api_earn.h +++ b/Telegram/SourceFiles/api/api_earn.h @@ -22,7 +22,7 @@ void RestrictSponsored( Fn failed); struct RewardReceiver final { - ChannelData *currencyReceiver = nullptr; + PeerData *currencyReceiver = nullptr; PeerData *creditsReceiver = nullptr; Fn creditsAmount; }; diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 662bfc98013be9..25090a4be7d8c5 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -10,15 +10,19 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "api/api_media.h" #include "api/api_text_entities.h" +#include "base/random.h" #include "ui/boxes/confirm_box.h" #include "data/business/data_shortcut_messages.h" #include "data/components/scheduled_messages.h" #include "data/data_file_origin.h" #include "data/data_histories.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" +#include "data/data_todo_list.h" #include "data/data_web_page.h" #include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/history.h" +#include "history/history_item_components.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "mtproto/mtproto_response.h" @@ -45,6 +49,244 @@ template constexpr auto ErrorWithoutId = is_callable_plain_v; +[[nodiscard]] auto ComputeEditMessageFlags( + not_null item, + const MTPVector &sentEntities, + Data::WebPageDraft webpage, + SendOptions options, + bool withMessage, + bool withMedia, + bool withRichMessage) +-> MTPmessages_EditMessage::Flags { + const auto emptyFlag = MTPmessages_EditMessage::Flag(0); + return emptyFlag + | (withMessage + ? MTPmessages_EditMessage::Flag::f_message + : emptyFlag) + | (withMedia + ? MTPmessages_EditMessage::Flag::f_media + : emptyFlag) + | (webpage.removed + ? MTPmessages_EditMessage::Flag::f_no_webpage + : emptyFlag) + | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + || options.invertCaption) + ? MTPmessages_EditMessage::Flag::f_invert_media + : emptyFlag) + | (!sentEntities.v.isEmpty() + ? MTPmessages_EditMessage::Flag::f_entities + : emptyFlag) + | (options.scheduled + ? MTPmessages_EditMessage::Flag::f_schedule_date + : emptyFlag) + | ((options.scheduled && options.scheduleRepeatPeriod) + ? MTPmessages_EditMessage::Flag::f_schedule_repeat_period + : emptyFlag) + | (item->isBusinessShortcut() + ? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id + : emptyFlag) + | (withRichMessage + ? MTPmessages_EditMessage::Flag::f_rich_message + : emptyFlag); +} + +[[nodiscard]] MsgId EditMessageRequestId(not_null item) { + return item->isScheduled() + ? item->history()->session().scheduledMessages().lookupId(item) + : item->isBusinessShortcut() + ? item->history()->session().data().shortcutMessages().lookupId(item) + : item->id; +} + +template +mtpRequestId SuggestMessage( + not_null item, + const TextWithEntities &textWithEntities, + Data::WebPageDraft webpage, + SendOptions options, + DoneCallback &&done, + FailCallback &&fail) { + Expects(options.suggest.exists); + Expects(!options.scheduled); + + const auto session = &item->history()->session(); + const auto api = &session->api(); + + const auto thread = item->history()->amMonoforumAdmin() + ? item->savedSublist() + : (Data::Thread*)item->history(); + auto action = SendAction(thread, options); + action.replyTo = FullReplyTo{ + .messageId = item->fullId(), + .monoforumPeerId = (item->history()->amMonoforumAdmin() + ? item->sublistPeerId() + : PeerId()), + }; + + auto message = MessageToSend(std::move(action)); + message.textWithTags = TextWithTags{ + textWithEntities.text, + TextUtilities::ConvertEntitiesToTextTags(textWithEntities.entities) + }; + message.webPage = webpage; + api->sendMessage(std::move(message)); + + const auto requestId = -1; + crl::on_main(session, [=] { + const auto type = u"MESSAGE_NOT_MODIFIED"_q; + if constexpr (ErrorWithId) { + fail(type, requestId); + } else if constexpr (ErrorWithoutId) { + fail(type); + } else if constexpr (WithoutCallback) { + fail(); + } else { + t_bad_callback(fail); + } + }); + return requestId; +} + +template +mtpRequestId SuggestMedia( + not_null item, + const TextWithEntities &textWithEntities, + Data::WebPageDraft webpage, + SendOptions options, + DoneCallback &&done, + FailCallback &&fail, + std::optional inputMedia) { + Expects(options.suggest.exists); + Expects(!options.scheduled); + + const auto session = &item->history()->session(); + const auto api = &session->api(); + + const auto text = textWithEntities.text; + const auto sentEntities = EntitiesToMTP( + session, + textWithEntities.entities, + ConvertOption::SkipLocal); + + const auto updateRecentStickers = inputMedia + ? Api::HasAttachedStickers(*inputMedia) + : false; + + const auto emptyFlag = MTPmessages_SendMedia::Flag(0); + auto replyTo = FullReplyTo{ + .messageId = item->fullId(), + .monoforumPeerId = (item->history()->amMonoforumAdmin() + ? item->sublistPeerId() + : PeerId()), + }; + const auto flags = emptyFlag + | MTPmessages_SendMedia::Flag::f_reply_to + | MTPmessages_SendMedia::Flag::f_suggested_post + | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + || options.invertCaption) + ? MTPmessages_SendMedia::Flag::f_invert_media + : emptyFlag) + | (!sentEntities.v.isEmpty() + ? MTPmessages_SendMedia::Flag::f_entities + : emptyFlag) + | (options.starsApproved + ? MTPmessages_SendMedia::Flag::f_allow_paid_stars + : emptyFlag); + const auto randomId = base::RandomValue(); + return api->request(MTPmessages_SendMedia( + MTP_flags(flags), + item->history()->peer->input(), + ReplyToForMTP(item->history(), replyTo), + inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())), + MTP_string(text), + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTPint(), // schedule_date + MTPint(), // schedule_repeat_period + MTPInputPeer(), // send_as + MTPInputQuickReplyShortcut(), // quick_reply_shortcut + MTPlong(), // effect + MTP_long(options.starsApproved), + Api::SuggestToMTP(options.suggest) + )).done([=]( + const MTPUpdates &result, + [[maybe_unused]] mtpRequestId requestId) { + const auto apply = [=] { api->applyUpdates(result); }; + + if constexpr (WithId) { + done(apply, requestId); + } else if constexpr (WithoutId) { + done(apply); + } else if constexpr (WithoutCallback) { + done(); + apply(); + } else { + t_bad_callback(done); + } + + if (updateRecentStickers) { + api->requestSpecialStickersForce(false, false, true); + } + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if constexpr (ErrorWithId) { + fail(error.type(), requestId); + } else if constexpr (ErrorWithoutId) { + fail(error.type()); + } else if constexpr (WithoutCallback) { + fail(); + } else { + t_bad_callback(fail); + } + }).send(); +} + +template +mtpRequestId SuggestMessageOrMedia( + not_null item, + const TextWithEntities &textWithEntities, + Data::WebPageDraft webpage, + SendOptions options, + DoneCallback &&done, + FailCallback &&fail, + std::optional inputMedia) { + const auto wasMedia = item->media(); + if (!inputMedia && wasMedia && wasMedia->allowsEditCaption()) { + if (const auto photo = wasMedia->photo()) { + inputMedia = MTP_inputMediaPhoto( + MTP_flags(0), + photo->mtpInput(), + MTPint(), // ttl_seconds + MTPInputDocument()); // video + } else if (const auto document = wasMedia->document()) { + inputMedia = MTP_inputMediaDocument( + MTP_flags(0), + document->mtpInput(), + MTPInputPhoto(), // video_cover + MTPint(), // video_timestamp + MTPint(), // ttl_seconds + MTPstring()); // query + } + } + if (inputMedia) { + return SuggestMedia( + item, + textWithEntities, + webpage, + options, + std::move(done), + std::move(fail), + inputMedia); + } + return SuggestMessage( + item, + textWithEntities, + webpage, + options, + std::move(done), + std::move(fail)); +} + template mtpRequestId EditMessage( not_null item, @@ -54,6 +296,18 @@ mtpRequestId EditMessage( DoneCallback &&done, FailCallback &&fail, std::optional inputMedia = std::nullopt) { + if (item->computeSuggestionActions() + == SuggestionActions::AcceptAndDecline) { + return SuggestMessageOrMedia( + item, + textWithEntities, + webpage, + options, + std::move(done), + std::move(fail), + inputMedia); + } + const auto session = &item->history()->session(); const auto api = &session->api(); @@ -68,49 +322,29 @@ mtpRequestId EditMessage( ? Api::HasAttachedStickers(*inputMedia) : false; - const auto emptyFlag = MTPmessages_EditMessage::Flag(0); - const auto flags = emptyFlag - | ((!text.isEmpty() || media) - ? MTPmessages_EditMessage::Flag::f_message - : emptyFlag) - | ((media && inputMedia.has_value()) - ? MTPmessages_EditMessage::Flag::f_media - : emptyFlag) - | (webpage.removed - ? MTPmessages_EditMessage::Flag::f_no_webpage - : emptyFlag) - | ((!webpage.removed && !webpage.url.isEmpty()) - ? MTPmessages_EditMessage::Flag::f_media - : emptyFlag) - | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) - || options.invertCaption) - ? MTPmessages_EditMessage::Flag::f_invert_media - : emptyFlag) - | (!sentEntities.v.isEmpty() - ? MTPmessages_EditMessage::Flag::f_entities - : emptyFlag) - | (options.scheduled - ? MTPmessages_EditMessage::Flag::f_schedule_date - : emptyFlag) - | (item->isBusinessShortcut() - ? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id - : emptyFlag); - - const auto id = item->isScheduled() - ? session->scheduledMessages().lookupId(item) - : item->isBusinessShortcut() - ? session->data().shortcutMessages().lookupId(item) - : item->id; + const auto flags = ComputeEditMessageFlags( + item, + sentEntities, + webpage, + options, + (!text.isEmpty() || media), + ((media && inputMedia.has_value()) + || (!webpage.removed && !webpage.url.isEmpty())), + false); + + const auto id = EditMessageRequestId(item); return api->request(MTPmessages_EditMessage( MTP_flags(flags), - item->history()->peer->input, + item->history()->peer->input(), MTP_int(id), MTP_string(text), inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())), MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled), - MTP_int(item->shortcutId()) + MTP_int(options.scheduleRepeatPeriod), + MTP_int(item->shortcutId()), + MTPInputRichMessage() )).done([=]( const MTPUpdates &result, [[maybe_unused]] mtpRequestId requestId) { @@ -271,19 +505,28 @@ mtpRequestId EditTextMessage( return MTP_inputMediaPhoto( MTP_flags(flags), photo->mtpInput(), - MTP_int(media->ttlSeconds())); + MTP_int(media->ttlSeconds()), + MTPInputDocument()); // video }; takeFileReference = [=] { return photo->fileReference(); }; } else if (const auto document = media->document()) { using Flag = MTPDinputMediaDocument::Flag; + const auto videoCover = media->videoCover(); + const auto videoTimestamp = media->videoTimestamp(); const auto flags = Flag() | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) - | (spoilered ? Flag::f_spoiler : Flag()); + | (spoilered ? Flag::f_spoiler : Flag()) + | (videoTimestamp ? Flag::f_video_timestamp : Flag()) + | (videoCover ? Flag::f_video_cover : Flag()); takeInputMedia = [=] { return MTP_inputMediaDocument( MTP_flags(flags), document->mtpInput(), + (videoCover + ? videoCover->mtpInput() + : MTPInputPhoto()), MTP_int(media->ttlSeconds()), + MTP_int(videoTimestamp), MTPstring()); // query }; takeFileReference = [=] { return document->fileReference(); }; @@ -350,4 +593,86 @@ mtpRequestId EditTextMessage( std::nullopt); } +mtpRequestId EditRichMessage( + not_null item, + Fn()> richMessage, + SendOptions options, + Fn done, + Fn fail) { + const auto session = &item->history()->session(); + const auto api = &session->api(); + const auto sentEntities = MTPVector(); + const auto flags = ComputeEditMessageFlags( + item, + sentEntities, + Data::WebPageDraft(), + options, + false, + false, + true); + const auto id = EditMessageRequestId(item); + const auto origin = item->fullId(); + const auto performRequest = [=]( + const auto &repeatRequest, + mtpRequestId originalRequestId, + bool refreshed) -> mtpRequestId { + const auto current = richMessage ? richMessage() : std::nullopt; + const auto requestId = originalRequestId ? originalRequestId : 0; + if (!current) { + if (fail) { + fail(QString(), requestId); + } + return requestId; + } + return api->request(MTPmessages_EditMessage( + MTP_flags(flags), + item->history()->peer->input(), + MTP_int(id), + MTPstring(), + MTPInputMedia(), + MTPReplyMarkup(), + sentEntities, + MTP_int(options.scheduled), + MTP_int(options.scheduleRepeatPeriod), + MTP_int(item->shortcutId()), + *current + )).done([=](const MTPUpdates &result, mtpRequestId requestId) { + api->applyUpdates(result); + if (done) { + done(originalRequestId ? originalRequestId : requestId); + } + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if (!refreshed && error.type().startsWith(u"FILE_REFERENCE_"_q)) { + api->refreshFileReference(origin, [=](const auto &) { + repeatRequest( + repeatRequest, + originalRequestId ? originalRequestId : requestId, + true); + }); + } else if (fail) { + fail(error.type(), originalRequestId ? originalRequestId : requestId); + } + }).send(); + }; + return performRequest(performRequest, 0, false); +} + +void EditTodoList( + not_null item, + const TodoListData &data, + SendOptions options, + Fn done, + Fn fail) { + const auto callback = [=](Fn applyUpdates, mtpRequestId id) { + applyUpdates(); + done(id); + }; + EditMessage( + item, + options, + callback, + fail, + MTP_inputMediaTodo(TodoListDataToMTP(&data))); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 630e1cd8d5a408..b551acab41bd20 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #pragma once +#include + class HistoryItem; namespace Data { @@ -57,5 +59,18 @@ mtpRequestId EditTextMessage( Fn done, Fn fail, bool spoilered); +mtpRequestId EditRichMessage( + not_null item, + Fn()> richMessage, + SendOptions options, + Fn done, + Fn fail); + +void EditTodoList( + not_null item, + const TodoListData &data, + SendOptions options, + Fn done, + Fn fail); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 83ad8d15657d25..82c35e5a7383d7 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -8,11 +8,39 @@ For license and copyright information please follow this link: #include "api/api_global_privacy.h" #include "apiwrap.h" +#include "data/components/promo_suggestions.h" +#include "data/data_user.h" #include "main/main_session.h" #include "main/main_app_config.h" namespace Api { +PeerId ParsePaidReactionShownPeer( + not_null session, + const MTPPaidReactionPrivacy &value) { + return value.match([&](const MTPDpaidReactionPrivacyDefault &) { + return session->userPeerId(); + }, [](const MTPDpaidReactionPrivacyAnonymous &) { + return PeerId(); + }, [&](const MTPDpaidReactionPrivacyPeer &data) { + return data.vpeer().match([&](const MTPDinputPeerSelf &) { + return session->userPeerId(); + }, [](const MTPDinputPeerUser &data) { + return peerFromUser(data.vuser_id()); + }, [](const MTPDinputPeerChat &data) { + return peerFromChat(data.vchat_id()); + }, [](const MTPDinputPeerChannel &data) { + return peerFromChannel(data.vchannel_id()); + }, [](const MTPDinputPeerUserFromMessage &data) -> PeerId { + Unexpected("From message peer in ParsePaidReactionShownPeer."); + }, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId { + Unexpected("From message peer in ParsePaidReactionShownPeer."); + }, [](const MTPDinputPeerEmpty &) -> PeerId { + Unexpected("Empty peer in ParsePaidReactionShownPeer."); + }); + }); +} + GlobalPrivacy::GlobalPrivacy(not_null api) : _session(&api->session()) , _api(&api->instance()) { @@ -40,7 +68,7 @@ void GlobalPrivacy::reload(Fn callback) { }).send(); _session->appConfig().value( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _showArchiveAndMute = _session->appConfig().get( u"autoarchive_setting_available"_q, false); @@ -74,13 +102,11 @@ rpl::producer GlobalPrivacy::showArchiveAndMute() const { } rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const { - return _session->appConfig().suggestionRequested( - u"AUTOARCHIVE_POPULAR"_q); + return _session->promoSuggestions().requested(u"AUTOARCHIVE_POPULAR"_q); } void GlobalPrivacy::dismissArchiveAndMuteSuggestion() { - _session->appConfig().dismissSuggestion( - u"AUTOARCHIVE_POPULAR"_q); + _session->promoSuggestions().dismiss(u"AUTOARCHIVE_POPULAR"_q); } void GlobalPrivacy::updateHideReadTime(bool hide) { @@ -88,7 +114,9 @@ void GlobalPrivacy::updateHideReadTime(bool hide) { archiveAndMuteCurrent(), unarchiveOnNewMessageCurrent(), hide, - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent(), + disallowedGiftTypesCurrent()); } bool GlobalPrivacy::hideReadTimeCurrent() const { @@ -99,43 +127,74 @@ rpl::producer GlobalPrivacy::hideReadTime() const { return _hideReadTime.value(); } -void GlobalPrivacy::updateNewRequirePremium(bool value) { +bool GlobalPrivacy::newRequirePremiumCurrent() const { + return _newRequirePremium.current(); +} + +rpl::producer GlobalPrivacy::newRequirePremium() const { + return _newRequirePremium.value(); +} + +int GlobalPrivacy::newChargeStarsCurrent() const { + return _newChargeStars.current(); +} + +rpl::producer GlobalPrivacy::newChargeStars() const { + return _newChargeStars.value(); +} + +void GlobalPrivacy::updateMessagesPrivacy( + bool requirePremium, + int chargeStars) { update( archiveAndMuteCurrent(), unarchiveOnNewMessageCurrent(), hideReadTimeCurrent(), - value); + requirePremium, + chargeStars, + disallowedGiftTypesCurrent()); } -bool GlobalPrivacy::newRequirePremiumCurrent() const { - return _newRequirePremium.current(); +DisallowedGiftTypes GlobalPrivacy::disallowedGiftTypesCurrent() const { + return _disallowedGiftTypes.current(); } -rpl::producer GlobalPrivacy::newRequirePremium() const { - return _newRequirePremium.value(); +auto GlobalPrivacy::disallowedGiftTypes() const + -> rpl::producer { + return _disallowedGiftTypes.value(); +} + +void GlobalPrivacy::updateDisallowedGiftTypes(DisallowedGiftTypes types) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + newRequirePremiumCurrent(), + newChargeStarsCurrent(), + types); } -void GlobalPrivacy::loadPaidReactionAnonymous() { - if (_paidReactionAnonymousLoaded) { +void GlobalPrivacy::loadPaidReactionShownPeer() { + if (_paidReactionShownPeerLoaded) { return; } - _paidReactionAnonymousLoaded = true; + _paidReactionShownPeerLoaded = true; _api.request(MTPmessages_GetPaidReactionPrivacy( )).done([=](const MTPUpdates &result) { _session->api().applyUpdates(result); }).send(); } -void GlobalPrivacy::updatePaidReactionAnonymous(bool value) { - _paidReactionAnonymous = value; +void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) { + _paidReactionShownPeer = shownPeer; } -bool GlobalPrivacy::paidReactionAnonymousCurrent() const { - return _paidReactionAnonymous.current(); +PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const { + return _paidReactionShownPeer.current(); } -rpl::producer GlobalPrivacy::paidReactionAnonymous() const { - return _paidReactionAnonymous.value(); +rpl::producer GlobalPrivacy::paidReactionShownPeer() const { + return _paidReactionShownPeer.value(); } void GlobalPrivacy::updateArchiveAndMute(bool value) { @@ -143,7 +202,9 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) { value, unarchiveOnNewMessageCurrent(), hideReadTimeCurrent(), - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent(), + disallowedGiftTypesCurrent()); } void GlobalPrivacy::updateUnarchiveOnNewMessage( @@ -152,19 +213,26 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage( archiveAndMuteCurrent(), value, hideReadTimeCurrent(), - newRequirePremiumCurrent()); + newRequirePremiumCurrent(), + newChargeStarsCurrent(), + disallowedGiftTypesCurrent()); } void GlobalPrivacy::update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, - bool newRequirePremium) { + bool newRequirePremium, + int newChargeStars, + DisallowedGiftTypes disallowedGiftTypes) { using Flag = MTPDglobalPrivacySettings::Flag; + using DisallowedFlag = MTPDdisallowedGiftsSettings::Flag; _api.request(_requestId).cancel(); const auto newRequirePremiumAllowed = _session->premium() || _session->appConfig().newRequirePremiumFree(); + const auto showGiftIcon + = (disallowedGiftTypes & DisallowedGiftType::SendHide); const auto flags = Flag() | (archiveAndMute ? Flag::f_archive_and_mute_new_noncontact_peers @@ -178,35 +246,96 @@ void GlobalPrivacy::update( | (hideReadTime ? Flag::f_hide_read_marks : Flag()) | ((newRequirePremium && newRequirePremiumAllowed) ? Flag::f_new_noncontact_peers_require_premium - : Flag()); + : Flag()) + | Flag::f_noncontact_peers_paid_stars + | (showGiftIcon ? Flag::f_display_gifts_button : Flag()) + | Flag::f_disallowed_gifts; + const auto disallowedFlags = DisallowedFlag() + | ((disallowedGiftTypes & DisallowedGiftType::Premium) + ? DisallowedFlag::f_disallow_premium_gifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Unlimited) + ? DisallowedFlag::f_disallow_unlimited_stargifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Limited) + ? DisallowedFlag::f_disallow_limited_stargifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::Unique) + ? DisallowedFlag::f_disallow_unique_stargifts + : DisallowedFlag()) + | ((disallowedGiftTypes & DisallowedGiftType::FromChannels) + ? DisallowedFlag::f_disallow_stargifts_from_channels + : DisallowedFlag()); + const auto typesWas = _disallowedGiftTypes.current(); + const auto typesChanged = (typesWas != disallowedGiftTypes); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( - MTP_globalPrivacySettings(MTP_flags(flags)) + MTP_globalPrivacySettings( + MTP_flags(flags), + MTP_long(newChargeStars), + MTP_disallowedGiftsSettings(MTP_flags(disallowedFlags))) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); + if (typesChanged) { + _session->user()->updateFullForced(); + } }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { - update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {}); + update( + archiveAndMute, + unarchiveOnNewMessage, + hideReadTime, + false, + 0, + DisallowedGiftTypes()); } }).send(); _archiveAndMute = archiveAndMute; _unarchiveOnNewMessage = unarchiveOnNewMessage; _hideReadTime = hideReadTime; _newRequirePremium = newRequirePremium; + _newChargeStars = newChargeStars; + _disallowedGiftTypes = disallowedGiftTypes; } -void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { - data.match([&](const MTPDglobalPrivacySettings &data) { - _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); - _unarchiveOnNewMessage = data.is_keep_archived_unmuted() - ? UnarchiveOnNewMessage::None - : data.is_keep_archived_folders() - ? UnarchiveOnNewMessage::NotInFoldersUnmuted - : UnarchiveOnNewMessage::AnyUnmuted; - _hideReadTime = data.is_hide_read_marks(); - _newRequirePremium = data.is_new_noncontact_peers_require_premium(); - }); +void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) { + const auto &data = settings.data(); + _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); + _unarchiveOnNewMessage = data.is_keep_archived_unmuted() + ? UnarchiveOnNewMessage::None + : data.is_keep_archived_folders() + ? UnarchiveOnNewMessage::NotInFoldersUnmuted + : UnarchiveOnNewMessage::AnyUnmuted; + _hideReadTime = data.is_hide_read_marks(); + _newRequirePremium = data.is_new_noncontact_peers_require_premium(); + _newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty(); + if (const auto gifts = data.vdisallowed_gifts()) { + const auto &disallow = gifts->data(); + _disallowedGiftTypes = DisallowedGiftType() + | (disallow.is_disallow_unlimited_stargifts() + ? DisallowedGiftType::Unlimited + : DisallowedGiftType()) + | (disallow.is_disallow_limited_stargifts() + ? DisallowedGiftType::Limited + : DisallowedGiftType()) + | (disallow.is_disallow_unique_stargifts() + ? DisallowedGiftType::Unique + : DisallowedGiftType()) + | (disallow.is_disallow_premium_gifts() + ? DisallowedGiftType::Premium + : DisallowedGiftType()) + | (disallow.is_disallow_stargifts_from_channels() + ? DisallowedGiftType::FromChannels + : DisallowedGiftType()) + | (data.is_display_gifts_button() + ? DisallowedGiftType::SendHide + : DisallowedGiftType()); + } else { + _disallowedGiftTypes = data.is_display_gifts_button() + ? DisallowedGiftType::SendHide + : DisallowedGiftType(); + } } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index aa7bb28fe3050a..dcd86edddc618e 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #pragma once +#include "base/flags.h" #include "mtproto/sender.h" class ApiWrap; @@ -23,6 +24,22 @@ enum class UnarchiveOnNewMessage { AnyUnmuted, }; +enum class DisallowedGiftType : uchar { + Limited = 0x01, + Unlimited = 0x02, + Unique = 0x04, + FromChannels = 0x08, + Premium = 0x10, + SendHide = 0x20, +}; +inline constexpr bool is_flag_type(DisallowedGiftType) { return true; } + +using DisallowedGiftTypes = base::flags; + +[[nodiscard]] PeerId ParsePaidReactionShownPeer( + not_null session, + const MTPPaidReactionPrivacy &value); + class GlobalPrivacy final { public: explicit GlobalPrivacy(not_null api); @@ -45,23 +62,34 @@ class GlobalPrivacy final { [[nodiscard]] bool hideReadTimeCurrent() const; [[nodiscard]] rpl::producer hideReadTime() const; - void updateNewRequirePremium(bool value); [[nodiscard]] bool newRequirePremiumCurrent() const; [[nodiscard]] rpl::producer newRequirePremium() const; - void loadPaidReactionAnonymous(); - void updatePaidReactionAnonymous(bool value); - [[nodiscard]] bool paidReactionAnonymousCurrent() const; - [[nodiscard]] rpl::producer paidReactionAnonymous() const; + [[nodiscard]] int newChargeStarsCurrent() const; + [[nodiscard]] rpl::producer newChargeStars() const; + + void updateMessagesPrivacy(bool requirePremium, int chargeStars); + + [[nodiscard]] DisallowedGiftTypes disallowedGiftTypesCurrent() const; + [[nodiscard]] auto disallowedGiftTypes() const + -> rpl::producer; + void updateDisallowedGiftTypes(DisallowedGiftTypes types); + + void loadPaidReactionShownPeer(); + void updatePaidReactionShownPeer(PeerId shownPeer); + [[nodiscard]] PeerId paidReactionShownPeerCurrent() const; + [[nodiscard]] rpl::producer paidReactionShownPeer() const; private: - void apply(const MTPGlobalPrivacySettings &data); + void apply(const MTPGlobalPrivacySettings &settings); void update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage, bool hideReadTime, - bool newRequirePremium); + bool newRequirePremium, + int newChargeStars, + DisallowedGiftTypes disallowedGiftTypes); const not_null _session; MTP::Sender _api; @@ -72,9 +100,11 @@ class GlobalPrivacy final { rpl::variable _showArchiveAndMute = false; rpl::variable _hideReadTime = false; rpl::variable _newRequirePremium = false; - rpl::variable _paidReactionAnonymous = false; + rpl::variable _newChargeStars = 0; + rpl::variable _disallowedGiftTypes; + rpl::variable _paidReactionShownPeer = false; std::vector> _callbacks; - bool _paidReactionAnonymousLoaded = false; + bool _paidReactionShownPeerLoaded = false; }; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index c896561032b885..7aceb74395bffb 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -101,7 +101,7 @@ void InviteLinks::performCreate( : Flag(0)) | (requestApproval ? Flag::f_request_needed : Flag(0)) | (args.subscription ? Flag::f_subscription_pricing : Flag(0))), - args.peer->input, + args.peer->input(), MTP_int(args.expireDate), MTP_int(args.usageLimit), MTP_string(args.label), @@ -270,7 +270,7 @@ void InviteLinks::performEdit( : Flag(0)); _api->request(MTPmessages_EditExportedChatInvite( MTP_flags(editOnlyTitle ? Flag::f_title : flags), - peer->input, + peer->input(), MTP_string(link), MTP_int(expireDate), MTP_int(usageLimit), @@ -365,7 +365,7 @@ void InviteLinks::destroy( callbacks.push_back(std::move(done)); } _api->request(MTPmessages_DeleteExportedChatInvite( - peer->input, + peer->input(), MTP_string(link) )).done([=] { const auto callbacks = _deleteCallbacks.take(key); @@ -400,8 +400,8 @@ void InviteLinks::destroyAllRevoked( callbacks.push_back(std::move(done)); } _api->request(MTPmessages_DeleteRevokedExportedChatInvites( - peer->input, - admin->inputUser + peer->input(), + admin->inputUser() )).done([=] { if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) { for (const auto &callback : *callbacks) { @@ -418,7 +418,7 @@ void InviteLinks::requestMyLinks(not_null peer) { } const auto requestId = _api->request(MTPmessages_GetExportedChatInvites( MTP_flags(0), - peer->input, + peer->input(), MTP_inputUserSelf(), MTPint(), // offset_date MTPstring(), // offset_link @@ -471,8 +471,8 @@ void InviteLinks::processRequest( using Flag = MTPmessages_HideChatJoinRequest::Flag; _api->request(MTPmessages_HideChatJoinRequest( MTP_flags(approved ? Flag::f_approved : Flag(0)), - peer->input, - user->inputUser + peer->input(), + user->inputUser() )).done([=](const MTPUpdates &result) { if (const auto chat = peer->asChat()) { if (chat->count > 0) { @@ -601,7 +601,7 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { } const auto requestId = _api->request(MTPmessages_GetChatInviteImporters( MTP_flags(MTPmessages_GetChatInviteImporters::Flag::f_link), - key.peer->input, + key.peer->input(), MTP_string(key.link), MTPstring(), // q MTP_int(0), // offset_date @@ -780,8 +780,8 @@ void InviteLinks::requestMoreLinks( _api->request(MTPmessages_GetExportedChatInvites( MTP_flags(Flag::f_offset_link | (revoked ? Flag::f_revoked : Flag(0))), - peer->input, - admin->inputUser, + peer->input(), + admin->inputUser(), MTP_int(lastDate), MTP_string(lastLink), MTP_int(kPerPage) diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index 22fc376312a9f8..582dc3e9b8e358 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -93,7 +93,8 @@ MTPInputMedia PrepareUploadedPhoto( info.file, MTP_vector( ranges::to>(info.attachedStickers)), - MTP_int(ttlSeconds)); + MTP_int(ttlSeconds), + MTPInputDocument()); // video } MTPInputMedia PrepareUploadedDocument( @@ -110,8 +111,10 @@ MTPInputMedia PrepareUploadedDocument( const auto flags = (spoiler ? Flag::f_spoiler : Flag()) | (info.thumb ? Flag::f_thumb : Flag()) | (item->groupId() ? Flag::f_nosound_video : Flag()) + | (info.forceFile ? Flag::f_force_file : Flag()) | (info.attachedStickers.empty() ? Flag::f_stickers : Flag()) - | (ttlSeconds ? Flag::f_ttl_seconds : Flag()); + | (ttlSeconds ? Flag::f_ttl_seconds : Flag()) + | (info.videoCover ? Flag::f_video_cover : Flag()); const auto document = item->media()->document(); return MTP_inputMediaUploadedDocument( MTP_flags(flags), @@ -121,6 +124,8 @@ MTPInputMedia PrepareUploadedDocument( ComposeSendingDocumentAttributes(document), MTP_vector( ranges::to>(info.attachedStickers)), + info.videoCover.value_or(MTPInputPhoto()), + MTP_int(0), // video_timestamp MTP_int(ttlSeconds)); } diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 4fbcaebf018901..ac348199bc358e 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -28,8 +28,8 @@ constexpr auto kSearchPerPage = 50; auto result = MessageIdsList(); for (const auto &message : messages) { const auto peerId = PeerFromMessage(message); - if (const auto peer = data->peerLoaded(peerId)) { - if (const auto lastDate = DateFromMessage(message)) { + if (data->peerLoaded(peerId)) { + if (DateFromMessage(message)) { const auto item = data->addNewMessage( message, MessageFlags(), @@ -58,9 +58,23 @@ constexpr auto kSearchPerPage = 50; result += u"emoji"_q + tag.emoji(); } } + switch (request.filter) { + case SearchFilter::NoFilter: break; + case SearchFilter::Pinned: result += u"\npinned"_q; break; + } return result; } +[[nodiscard]] MTPMessagesFilter PrepareFilter(SearchFilter filter) { + switch (filter) { + case SearchFilter::Pinned: + return MTP_inputMessagesFilterPinned(); + case SearchFilter::NoFilter: + return MTP_inputMessagesFilterEmpty(); + } + return MTP_inputMessagesFilterEmpty(); +} + } // namespace MessagesSearch::MessagesSearch(not_null history) @@ -103,16 +117,17 @@ void MessagesSearch::searchRequest() { _requestId = _history->session().api().request(MTPmessages_Search( MTP_flags((fromPeer ? Flag::f_from_id : Flag()) | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_request.topMsgId ? Flag::f_top_msg_id : Flag()) | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)), - _history->peer->input, + _history->peer->input(), MTP_string(_request.query), - (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), - (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), + (fromPeer ? fromPeer->input() : MTP_inputPeerEmpty()), + (savedPeer ? savedPeer->input() : MTP_inputPeerEmpty()), MTP_vector_from_range(_request.tags | ranges::views::transform( Data::ReactionToMTP )), - MTPint(), // top_msg_id - MTP_inputMessagesFilterEmpty(), + MTP_int(_request.topMsgId), // top_msg_id + PrepareFilter(_request.filter), MTP_int(0), // min_date MTP_int(0), // max_date MTP_int(_offsetId), // offset_id @@ -158,6 +173,7 @@ void MessagesSearch::searchReceived( // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); + _history->peer->processTopics(data.vtopics()); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); const auto total = int(data.vmessages().v.size()); @@ -167,6 +183,7 @@ void MessagesSearch::searchReceived( // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); + _history->peer->processTopics(data.vtopics()); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); // data.vnext_rate() is used only in global search. @@ -177,17 +194,14 @@ void MessagesSearch::searchReceived( // Don't apply cached data! owner.processUsers(data.vusers()); owner.processChats(data.vchats()); - } - if (const auto channel = _history->peer->asChannel()) { - channel->ptsReceived(data.vpts().v); - if (_requestId != 0) { - // Don't apply cached data! - channel->processTopics(data.vtopics()); + if (const auto channel = _history->peer->asChannel()) { + channel->ptsReceived(data.vpts().v); + } else { + LOG(("API Error: " + "received messages.channelMessages when no channel " + "was passed!")); } - } else { - LOG(("API Error: " - "received messages.channelMessages when no channel " - "was passed!")); + _history->peer->processTopics(data.vtopics()); } auto items = HistoryItemsFromTL(&owner, data.vmessages().v); const auto total = int(data.vcount().v); diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index 76046aaa91aacd..99b222b703a6d0 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -20,6 +20,11 @@ struct ReactionId; namespace Api { +enum class SearchFilter { + NoFilter, + Pinned, +}; + struct FoundMessages { int total = -1; MessageIdsList messages; @@ -32,6 +37,8 @@ class MessagesSearch final { QString query; PeerData *from = nullptr; std::vector tags; + MsgId topMsgId; + SearchFilter filter = SearchFilter::NoFilter; friend inline bool operator==( const Request &, diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index 8451232f6089d8..49bf50ce073130 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -36,7 +36,7 @@ MessagesSearchMerged::MessagesSearchMerged(not_null history) }; _apiSearch.messagesFounds( - ) | rpl::start_with_next([=](const FoundMessages &data) { + ) | rpl::on_next([=](const FoundMessages &data) { if (data.nextToken == _concatedFound.nextToken) { addFound(data); checkFull(data); @@ -50,7 +50,7 @@ MessagesSearchMerged::MessagesSearchMerged(not_null history) if (_migratedSearch) { _migratedSearch->messagesFounds( - ) | rpl::start_with_next([=](const FoundMessages &data) { + ) | rpl::on_next([=](const FoundMessages &data) { if (_isFull) { addFound(data); } @@ -64,6 +64,12 @@ MessagesSearchMerged::MessagesSearchMerged(not_null history) } } +void MessagesSearchMerged::disableMigrated() { + _migratedSearch = std::nullopt; + _waitingForTotal = false; + _isFull = false; +} + void MessagesSearchMerged::addFound(const FoundMessages &data) { for (const auto &message : data.messages) { _concatedFound.messages.push_back(message); @@ -74,14 +80,22 @@ const FoundMessages &MessagesSearchMerged::messages() const { return _concatedFound; } +const MessagesSearch::Request &MessagesSearchMerged::request() const { + return _request; +} + void MessagesSearchMerged::clear() { _concatedFound = {}; _migratedFirstFound = {}; + _waitingForTotal = false; + _isFull = false; } void MessagesSearchMerged::search(const Request &search) { + _request = search; + _isFull = false; + _waitingForTotal = (_migratedSearch != std::nullopt); if (_migratedSearch) { - _waitingForTotal = true; _migratedSearch->searchMessages(search); } _apiSearch.searchMessages(search); diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.h b/Telegram/SourceFiles/api/api_messages_search_merged.h index d4c6fbc1c83cf6..1a949c6ed4d0b6 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.h +++ b/Telegram/SourceFiles/api/api_messages_search_merged.h @@ -29,8 +29,10 @@ class MessagesSearchMerged final { void clear(); void search(const Request &search); void searchMore(); + void disableMigrated(); [[nodiscard]] const FoundMessages &messages() const; + [[nodiscard]] const Request &request() const; [[nodiscard]] rpl::producer<> newFounds() const; [[nodiscard]] rpl::producer<> nextFounds() const; @@ -39,6 +41,7 @@ class MessagesSearchMerged final { void addFound(const FoundMessages &data); MessagesSearch _apiSearch; + Request _request; std::optional _migratedSearch; FoundMessages _migratedFirstFound; diff --git a/Telegram/SourceFiles/api/api_peer_colors.cpp b/Telegram/SourceFiles/api/api_peer_colors.cpp index 5f3fe12d202bef..330863d90d9acd 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.cpp +++ b/Telegram/SourceFiles/api/api_peer_colors.cpp @@ -9,7 +9,9 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "data/data_peer.h" +#include "window/themes/window_theme.h" #include "ui/chat/chat_style.h" +#include "ui/color_int_conversion.h" namespace Api { namespace { @@ -20,8 +22,9 @@ constexpr auto kRequestEach = 3600 * crl::time(1000); PeerColors::PeerColors(not_null api) : _api(&api->instance()) -, _timer([=] { request(); }) { +, _timer([=] { request(); requestProfile(); }) { request(); + requestProfile(); _timer.callEach(kRequestEach); } @@ -45,6 +48,24 @@ void PeerColors::request() { }).send(); } +void PeerColors::requestProfile() { + if (_profileRequestId) { + return; + } + _profileRequestId = _api.request(MTPhelp_GetPeerProfileColors( + MTP_int(_profileHash) + )).done([=](const MTPhelp_PeerColors &result) { + _profileRequestId = 0; + result.match([&](const MTPDhelp_peerColors &data) { + _profileHash = data.vhash().v; + applyProfile(data); + }, [](const MTPDhelp_peerColorsNotModified &) { + }); + }).fail([=] { + _profileRequestId = 0; + }).send(); +} + std::vector PeerColors::suggested() const { return _suggested.current(); } @@ -76,21 +97,27 @@ const base::flat_map &PeerColors::requiredLevelsChannel() const { return _requiredLevelsChannel; } -int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const { +int PeerColors::requiredLevelFor( + PeerId channel, + uint8 index, + bool isMegagroup, + bool profile) const { if (Data::DecideColorIndex(channel) == index) { return 0; - } else if (const auto i = _requiredLevelsGroup.find(index) - ; i != end(_requiredLevelsGroup)) { - return i->second; } - return 1; -} - -int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const { - if (Data::DecideColorIndex(channel) == index) { - return 0; - } else if (const auto i = _requiredLevelsChannel.find(index) - ; i != end(_requiredLevelsChannel)) { + if (profile) { + const auto it = _profileColors.find(index); + if (it != end(_profileColors)) { + return isMegagroup + ? it->second.requiredLevelsGroup + : it->second.requiredLevelsChannel; + } + return 1; + } + const auto &levels = isMegagroup + ? _requiredLevelsGroup + : _requiredLevelsChannel; + if (const auto i = levels.find(index); i != end(levels)) { return i->second; } return 1; @@ -165,4 +192,87 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) { _suggested = std::move(suggested); } +void PeerColors::applyProfile(const MTPDhelp_peerColors &data) { + const auto parseColors = [](const MTPhelp_PeerColorSet &set) { + const auto toUint = [](const MTPint &c) { + return (uint32(1) << 24) | uint32(c.v); + }; + return set.match([&](const MTPDhelp_peerColorSet &) { + LOG(("API Error: peerColorSet in profile colors result!")); + return Data::ColorProfileSet(); + }, [&](const MTPDhelp_peerColorProfileSet &data) { + auto set = Data::ColorProfileSet(); + set.palette.reserve(data.vpalette_colors().v.size()); + set.bg.reserve(data.vbg_colors().v.size()); + set.story.reserve(data.vstory_colors().v.size()); + for (const auto &c : data.vpalette_colors().v) { + set.palette.push_back(Ui::ColorFromSerialized(toUint(c))); + } + for (const auto &c : data.vbg_colors().v) { + set.bg.push_back(Ui::ColorFromSerialized(toUint(c))); + } + for (const auto &c : data.vstory_colors().v) { + set.story.push_back(Ui::ColorFromSerialized(toUint(c))); + } + return set; + }); + }; + + auto suggested = std::vector(); + const auto &list = data.vcolors().v; + suggested.reserve(list.size()); + for (const auto &color : list) { + const auto &data = color.data(); + const auto colorIndexBare = data.vcolor_id().v; + if (colorIndexBare < 0 || colorIndexBare >= Ui::kColorIndexCount) { + LOG(("API Error: Bad color index: %1").arg(colorIndexBare)); + continue; + } + const auto colorIndex = uint8(colorIndexBare); + auto result = ProfileColorOption(); + result.isHidden = data.is_hidden(); + if (const auto min = data.vgroup_min_level()) { + result.requiredLevelsGroup = min->v; + } + if (const auto min = data.vchannel_min_level()) { + result.requiredLevelsChannel = min->v; + } + if (const auto light = data.vcolors()) { + result.data.light = parseColors(*light); + } + if (const auto dark = data.vdark_colors()) { + result.data.dark = parseColors(*dark); + } + _profileColors[colorIndex] = std::move(result); + } +} + +std::optional PeerColors::colorProfileFor( + not_null peer) const { + if (const auto colorProfileIndex = peer->colorProfileIndex()) { + return colorProfileFor(*colorProfileIndex); + } + return std::nullopt; +} + +std::optional PeerColors::colorProfileFor( + uint8 index) const { + const auto i = _profileColors.find(index); + if (i != end(_profileColors)) { + return Window::Theme::IsNightMode() + ? i->second.data.dark + : i->second.data.light; + } + return std::nullopt; +} + +std::vector PeerColors::profileColorIndices() const { + auto result = std::vector(); + result.reserve(_profileColors.size()); + for (const auto &[index, option] : _profileColors) { + result.push_back(index); + } + return result; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h index f8d379020bfa1d..690073d856d0b2 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.h +++ b/Telegram/SourceFiles/api/api_peer_colors.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "base/timer.h" +#include "data/data_peer_colors.h" #include "mtproto/sender.h" class ApiWrap; @@ -34,27 +35,45 @@ class PeerColors final { [[nodiscard]] auto requiredLevelsChannel() const -> const base::flat_map &; - [[nodiscard]] int requiredGroupLevelFor( - PeerId channel, - uint8 index) const; - [[nodiscard]] int requiredChannelLevelFor( + [[nodiscard]] int requiredLevelFor( PeerId channel, + uint8 index, + bool isMegagroup, + bool profile) const; + + [[nodiscard]] std::optional colorProfileFor( + not_null peer) const; + [[nodiscard]] std::optional colorProfileFor( uint8 index) const; + [[nodiscard]] std::vector profileColorIndices() const; + private: + struct ProfileColorOption { + Data::ColorProfileData data; + int requiredLevelsChannel = 0; + int requiredLevelsGroup = 0; + bool isHidden = false; + }; + void request(); + void requestProfile(); void apply(const MTPDhelp_peerColors &data); + void applyProfile(const MTPDhelp_peerColors &data); MTP::Sender _api; int32 _hash = 0; + int32 _profileHash = 0; mtpRequestId _requestId = 0; + mtpRequestId _profileRequestId = 0; base::Timer _timer; rpl::variable> _suggested; base::flat_map _requiredLevelsGroup; base::flat_map _requiredLevelsChannel; rpl::event_stream<> _colorIndicesChanged; std::unique_ptr _colorIndicesCurrent; + base::flat_map _profileColors; }; diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 9c8a470fa9e79b..ebfe6df9e4e7a8 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -150,12 +150,37 @@ PeerPhoto::PeerPhoto(not_null api) : _session(&api->session()) , _api(&api->instance()) { crl::on_main(_session, [=] { + auto &uploader = _session->uploader(); + // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. - _session->uploader().photoReady( - ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { + uploader.photoReady( + ) | rpl::on_next([=](const Storage::UploadedMedia &data) { ready(data.fullId, data.info.file, std::nullopt); }, _session->lifetime()); + + uploader.photoProgress( + ) | rpl::on_next([=](const FullMsgId &id) { + const auto i = _uploads.find(id); + if (i == end(_uploads) || !i->second.photoId) { + return; + } + const auto peer = i->second.peer; + const auto photo = _session->data().photo( + i->second.photoId); + _uploadProgress.fire({ peer, photo->progress() }); + }, _session->lifetime()); + + uploader.photoFailed( + ) | rpl::on_next([=](const FullMsgId &id) { + const auto i = _uploads.find(id); + if (i == end(_uploads)) { + return; + } + const auto peer = i->second.peer; + _uploads.erase(i); + _uploadFailed.fire_copy(peer); + }, _session->lifetime()); }); } @@ -222,17 +247,18 @@ void PeerPhoto::upload( _session->uploader().cancel(already->first); _uploads.erase(already); } - _uploads.emplace( + const auto &[it, ok] = _uploads.emplace( fakeId, - UploadValue{ peer, type, std::move(done) }); + UploadValue{ peer, type, std::move(done), PhotoId(0) }); if (mtpMarkup) { ready(fakeId, std::nullopt, mtpMarkup); } else { - const auto ready = PreparePeerPhoto( + const auto prepared = PreparePeerPhoto( _api.instance().mainDcId(), peer->id, base::take(photo.image)); - _session->uploader().upload(fakeId, ready); + it->second.photoId = prepared->thumbId; + _session->uploader().upload(fakeId, prepared); } } @@ -240,15 +266,81 @@ void PeerPhoto::suggest(not_null peer, UserPhoto &&photo) { upload(peer, std::move(photo), UploadType::Suggestion, nullptr); } +void PeerPhoto::subscribeToUpload( + not_null peer, + rpl::lifetime &lifetime, + UploadCallbacks callbacks) { + uploadProgress( + ) | rpl::filter([=](const UploadProgress &data) { + return (data.peer == peer); + }) | rpl::on_next([cb = callbacks.progress](const UploadProgress &data) { + if (cb) { + cb(data.progress); + } + }, lifetime); + + uploadDone( + ) | rpl::filter([=](not_null p) { + return (p == peer); + }) | rpl::on_next([cb = callbacks.done](not_null) { + if (cb) { + cb(); + } + }, lifetime); + + uploadFailed( + ) | rpl::filter([=](not_null p) { + return (p == peer); + }) | rpl::on_next([cb = callbacks.failed](not_null) { + if (cb) { + cb(); + } + }, lifetime); +} + +auto PeerPhoto::uploadProgress() const +-> rpl::producer { + return _uploadProgress.events(); +} + +auto PeerPhoto::uploadDone() const +-> rpl::producer> { + return _uploadDone.events(); +} + +auto PeerPhoto::uploadFailed() const +-> rpl::producer> { + return _uploadFailed.events(); +} + +void PeerPhoto::cancelUpload(not_null peer) { + peer = peer->migrateToOrMe(); + const auto i = ranges::find( + _uploads, + peer, + [](const auto &pair) { return pair.second.peer; }); + if (i == end(_uploads)) { + return; + } + const auto fakeId = i->first; + _uploads.erase(i); + _session->uploader().cancel(fakeId); + _uploadFailed.fire_copy(peer); +} + void PeerPhoto::clear(not_null photo) { const auto self = _session->user(); if (self->userpicPhotoId() == photo->id) { + const auto photoId = photo->id; + const auto peerId = self->id; _api.request(MTPphotos_UpdateProfilePhoto( MTP_flags(0), MTPInputUser(), // bot MTP_inputPhotoEmpty() )).done([=](const MTPphotos_Photo &result) { self->setPhoto(MTP_userProfilePhotoEmpty()); + _session->storage().remove( + Storage::UserPhotosRemoveOne(peerToUser(peerId), photoId)); }).send(); } else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) { const auto applier = [=](const MTPUpdates &result) { @@ -256,12 +348,12 @@ void PeerPhoto::clear(not_null photo) { }; if (const auto chat = photo->peer->asChat()) { _api.request(MTPmessages_EditChatPhoto( - chat->inputChat, + chat->inputChat(), MTP_inputChatPhotoEmpty() )).done(applier).send(); } else if (const auto channel = photo->peer->asChannel()) { _api.request(MTPchannels_EditPhoto( - channel->inputChannel, + channel->inputChannel(), MTP_inputChatPhotoEmpty() )).done(applier).send(); } @@ -290,7 +382,7 @@ void PeerPhoto::clear(not_null photo) { void PeerPhoto::clearPersonal(not_null user) { _api.request(MTPphotos_UploadContactProfilePhoto( MTP_flags(MTPphotos_UploadContactProfilePhoto::Flag::f_save), - user->inputUser, + user->inputUser(), MTPInputFile(), MTPInputFile(), // video MTPdouble(), // video_start_ts @@ -314,15 +406,20 @@ void PeerPhoto::set(not_null peer, not_null photo) { return; } if (peer == _session->user()) { + const auto photoId = photo->id; + const auto peerId = peer->id; _api.request(MTPphotos_UpdateProfilePhoto( MTP_flags(0), MTPInputUser(), // bot photo->mtpInput() )).done([=](const MTPphotos_Photo &result) { - result.match([&](const MTPDphotos_photo &data) { - _session->data().processPhoto(data.vphoto()); - _session->data().processUsers(data.vusers()); - }); + const auto newPhoto = _session->data().processPhoto( + result.data().vphoto()); + _session->data().processUsers(result.data().vusers()); + _session->storage().replace(Storage::UserPhotosReplace( + peerToUser(peerId), + photoId, + newPhoto->id)); }).send(); } else { const auto applier = [=](const MTPUpdates &result) { @@ -330,12 +427,12 @@ void PeerPhoto::set(not_null peer, not_null photo) { }; if (const auto chat = peer->asChat()) { _api.request(MTPmessages_EditChatPhoto( - chat->inputChat, + chat->inputChat(), MTP_inputChatPhoto(photo->mtpInput()) )).done(applier).send(); } else if (const auto channel = peer->asChannel()) { _api.request(MTPchannels_EditPhoto( - channel->inputChannel, + channel->inputChannel(), MTP_inputChatPhoto(photo->mtpInput()) )).done(applier).send(); } @@ -353,16 +450,23 @@ void PeerPhoto::ready( const auto peer = maybeUploadValue->peer; const auto type = maybeUploadValue->type; const auto done = maybeUploadValue->done; - const auto applier = [=](const MTPUpdates &result) { - _session->updates().applyUpdates(result); + const auto finish = [=] { + _uploadDone.fire_copy(peer); if (done) { done(); } }; + const auto fail = [=](const MTP::Error &error) { + _uploadFailed.fire_copy(peer); + }; + const auto applier = [=](const MTPUpdates &result) { + _session->updates().applyUpdates(result); + finish(); + }; const auto botUserInput = [&] { const auto user = peer->asUser(); return (user && user->botInfo && user->botInfo->canEditInformation) - ? std::make_optional(user->inputUser) + ? std::make_optional(user->inputUser()) : std::nullopt; }(); if (peer->isSelf() || botUserInput) { @@ -386,17 +490,19 @@ void PeerPhoto::ready( _session->storage().add(Storage::UserPhotosSetBack( peerToUser(peer->id), photoId)); + } else { + _session->storage().add(Storage::UserPhotosAddNew( + peerToUser(peer->id), + photoId)); } - if (done) { - done(); - } - }).send(); + finish(); + }).fail(fail).send(); } else if (const auto chat = peer->asChat()) { const auto history = _session->data().history(chat); using Flag = MTPDinputChatUploadedPhoto::Flag; const auto none = MTPDinputChatUploadedPhoto::Flags(0); history->sendRequestId = _api.request(MTPmessages_EditChatPhoto( - chat->inputChat, + chat->inputChat(), MTP_inputChatUploadedPhoto( MTP_flags((file ? Flag::f_file : none) | (videoSize ? Flag::f_video_emoji_markup : none)), @@ -404,13 +510,13 @@ void PeerPhoto::ready( MTPInputFile(), // video MTPdouble(), // video_start_ts videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup - )).done(applier).afterRequest(history->sendRequestId).send(); + )).done(applier).fail(fail).afterRequest(history->sendRequestId).send(); } else if (const auto channel = peer->asChannel()) { using Flag = MTPDinputChatUploadedPhoto::Flag; const auto none = MTPDinputChatUploadedPhoto::Flags(0); const auto history = _session->data().history(channel); history->sendRequestId = _api.request(MTPchannels_EditPhoto( - channel->inputChannel, + channel->inputChannel(), MTP_inputChatUploadedPhoto( MTP_flags((file ? Flag::f_file : none) | (videoSize ? Flag::f_video_emoji_markup : none)), @@ -418,7 +524,7 @@ void PeerPhoto::ready( MTPInputFile(), // video MTPdouble(), // video_start_ts videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup - )).done(applier).afterRequest(history->sendRequestId).send(); + )).done(applier).fail(fail).afterRequest(history->sendRequestId).send(); } else if (const auto user = peer->asUser()) { using Flag = MTPphotos_UploadContactProfilePhoto::Flag; const auto none = MTPphotos_UploadContactProfilePhoto::Flags(0); @@ -428,7 +534,7 @@ void PeerPhoto::ready( | ((type == UploadType::Suggestion) ? Flag::f_suggest : Flag::f_save)), - user->inputUser, + user->inputUser(), file ? (*file) : MTPInputFile(), MTPInputFile(), // video MTPdouble(), // video_start_ts @@ -441,10 +547,8 @@ void PeerPhoto::ready( if (type != UploadType::Suggestion) { user->updateFullForced(); } - if (done) { - done(); - } - }).send(); + finish(); + }).fail(fail).send(); } } @@ -456,7 +560,7 @@ void PeerPhoto::requestUserPhotos( } const auto requestId = _api.request(MTPphotos_GetUserPhotos( - user->inputUser, + user->inputUser(), MTP_int(0), MTP_long(afterId), MTP_int(kSharedMediaLimit) diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 71d340a031f6b1..4f417f930e786f 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -41,6 +41,11 @@ class PeerPhoto final { std::vector markupColors; }; + struct UploadProgress { + not_null peer; + float64 progress = 0.; + }; + void upload( not_null peer, UserPhoto &&photo, @@ -55,6 +60,24 @@ class PeerPhoto final { void clearPersonal(not_null user); void set(not_null peer, not_null photo); + struct UploadCallbacks { + Fn progress; + Fn done; + Fn failed; + }; + void subscribeToUpload( + not_null peer, + rpl::lifetime &lifetime, + UploadCallbacks callbacks); + + [[nodiscard]] auto uploadProgress() const + -> rpl::producer; + [[nodiscard]] auto uploadDone() const + -> rpl::producer>; + [[nodiscard]] auto uploadFailed() const + -> rpl::producer>; + void cancelUpload(not_null peer); + void requestUserPhotos(not_null user, UserPhotoId afterId); void requestEmojiList(EmojiListType type); @@ -100,9 +123,13 @@ class PeerPhoto final { not_null peer; UploadType type = UploadType::Default; Fn done; + PhotoId photoId = 0; }; base::flat_map _uploads; + rpl::event_stream _uploadProgress; + rpl::event_stream> _uploadDone; + rpl::event_stream> _uploadFailed; base::flat_map, mtpRequestId> _userPhotosRequests; diff --git a/Telegram/SourceFiles/api/api_peer_search.cpp b/Telegram/SourceFiles/api/api_peer_search.cpp new file mode 100644 index 00000000000000..73a252ab12ce8f --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_search.cpp @@ -0,0 +1,171 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_peer_search.h" + +#include "api/api_single_message_search.h" +#include "apiwrap.h" +#include "data/data_session.h" +#include "dialogs/ui/chat_search_in.h" // IsHashOrCashtagSearchQuery +#include "main/main_session.h" + +namespace Api { +namespace { + +constexpr auto kMinSponsoredQueryLength = 4; + +} // namespace + +PeerSearch::PeerSearch(not_null session, Type type) +: _session(session) +, _type(type) { +} + +PeerSearch::~PeerSearch() { + clear(); +} + +void PeerSearch::request( + const QString &query, + Fn callback, + RequestType type) { + using namespace Dialogs; + _query = Api::ConvertPeerSearchQuery(query); + _callback = callback; + if (_query.isEmpty() + || IsHashOrCashtagSearchQuery(_query) != HashOrCashtag::None) { + finish(PeerSearchResult{}); + return; + } + auto &cache = _cache[_query]; + if (cache.peersReady && cache.sponsoredReady) { + finish(cache.result); + return; + } else if (type == RequestType::CacheOnly) { + _callback = nullptr; + return; + } else if (cache.requested) { + return; + } + cache.requested = true; + cache.result.query = _query; + if (_query.size() < kMinSponsoredQueryLength) { + cache.sponsoredReady = true; + } else if (_type == Type::WithSponsored) { + requestSponsored(); + } + requestPeers(); +} + +void PeerSearch::requestPeers() { + const auto requestId = _session->api().request(MTPcontacts_Search( + MTP_flags(0), + MTP_string(_query), + MTP_int(SearchPeopleLimit) + )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + auto parsed = PeerSearchResult(); + parsed.my.reserve(data.vmy_results().v.size()); + for (const auto &id : data.vmy_results().v) { + const auto peerId = peerFromMTP(id); + parsed.my.push_back(_session->data().peer(peerId)); + } + parsed.peers.reserve(data.vresults().v.size()); + for (const auto &id : data.vresults().v) { + const auto peerId = peerFromMTP(id); + parsed.peers.push_back(_session->data().peer(peerId)); + } + finishPeers(requestId, std::move(parsed)); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + finishPeers(requestId, PeerSearchResult{}); + }).send(); + _peerRequests.emplace(requestId, _query); +} + +void PeerSearch::requestSponsored() { + const auto requestId = _session->api().request( + MTPcontacts_GetSponsoredPeers(MTP_string(_query)) + ).done([=]( + const MTPcontacts_SponsoredPeers &result, + mtpRequestId requestId) { + result.match([&](const MTPDcontacts_sponsoredPeersEmpty &) { + finishSponsored(requestId, PeerSearchResult{}); + }, [&](const MTPDcontacts_sponsoredPeers &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + auto parsed = PeerSearchResult(); + parsed.sponsored.reserve(data.vpeers().v.size()); + for (const auto &peer : data.vpeers().v) { + const auto &data = peer.data(); + const auto peerId = peerFromMTP(data.vpeer()); + parsed.sponsored.push_back({ + .peer = _session->data().peer(peerId), + .randomId = data.vrandom_id().v, + .sponsorInfo = TextWithEntities::Simple( + qs(data.vsponsor_info().value_or_empty())), + .additionalInfo = TextWithEntities::Simple( + qs(data.vadditional_info().value_or_empty())), + }); + } + finishSponsored(requestId, std::move(parsed)); + }); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + finishSponsored(requestId, PeerSearchResult{}); + }).send(); + _sponsoredRequests.emplace(requestId, _query); +} + +void PeerSearch::finishPeers( + mtpRequestId requestId, + PeerSearchResult result) { + const auto query = _peerRequests.take(requestId); + Assert(query.has_value()); + + auto &cache = _cache[*query]; + cache.peersReady = true; + cache.result.my = std::move(result.my); + cache.result.peers = std::move(result.peers); + if (cache.sponsoredReady && _query == *query) { + finish(cache.result); + } +} + +void PeerSearch::finishSponsored( + mtpRequestId requestId, + PeerSearchResult result) { + const auto query = _sponsoredRequests.take(requestId); + Assert(query.has_value()); + + auto &cache = _cache[*query]; + cache.sponsoredReady = true; + cache.result.sponsored = std::move(result.sponsored); + if (cache.peersReady && _query == *query) { + finish(cache.result); + } +} + +void PeerSearch::finish(PeerSearchResult result) { + if (const auto onstack = base::take(_callback)) { + onstack(std::move(result)); + } +} + +void PeerSearch::clear() { + _query = QString(); + _callback = nullptr; + _cache.clear(); + for (const auto &[requestId, query] : base::take(_peerRequests)) { + _session->api().request(requestId).cancel(); + } + for (const auto &[requestId, query] : base::take(_sponsoredRequests)) { + _session->api().request(requestId).cancel(); + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_search.h b/Telegram/SourceFiles/api/api_peer_search.h new file mode 100644 index 00000000000000..c706429521efa3 --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_search.h @@ -0,0 +1,76 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +struct SponsoredSearchResult { + not_null peer; + QByteArray randomId; + TextWithEntities sponsorInfo; + TextWithEntities additionalInfo; +}; + +struct PeerSearchResult { + QString query; + std::vector> my; + std::vector> peers; + std::vector sponsored; +}; + +class PeerSearch final { +public: + enum class Type { + WithSponsored, + JustPeers, + }; + PeerSearch(not_null session, Type type); + ~PeerSearch(); + + enum class RequestType { + CacheOnly, + CacheOrRemote, + }; + void request( + const QString &query, + Fn callback, + RequestType type = RequestType::CacheOrRemote); + void clear(); + +private: + struct CacheEntry { + PeerSearchResult result; + bool requested = false; + bool peersReady = false; + bool sponsoredReady = false; + }; + + void requestPeers(); + void requestSponsored(); + + void finish(PeerSearchResult result); + void finishPeers(mtpRequestId requestId, PeerSearchResult result); + void finishSponsored(mtpRequestId requestId, PeerSearchResult result); + + const not_null _session; + const Type _type; + + QString _query; + Fn _callback; + + base::flat_map _cache; + base::flat_map _peerRequests; + base::flat_map _sponsoredRequests; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 52a5f6d6fd0414..6612bb3748cd24 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -8,28 +8,210 @@ For license and copyright information please follow this link: #include "api/api_polls.h" #include "api/api_common.h" +#include "api/api_statistics_data_deserialize.h" +#include "api/api_text_entities.h" #include "api/api_updates.h" #include "apiwrap.h" +#include "base/call_delayed.h" +#include "base/qt/qt_key_modifiers.h" #include "base/random.h" #include "data/business/data_shortcut_messages.h" #include "data/data_changes.h" #include "data/data_histories.h" #include "data/data_poll.h" #include "data/data_session.h" +#include "data/data_statistics_chart.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" // ShouldSendSilent +#include "lang/lang_keys.h" #include "main/main_session.h" +#include "styles/style_polls.h" +#include "ui/toast/toast.h" +#include "window/window_session_controller.h" -namespace Api { namespace { -[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) { - return TimeId(msgId >> 32); +constexpr auto kVoteRestrictionToastDuration = 5 * crl::time(1000); + +const auto kSubscribersOnlyVoteErrorPatterns = std::array{ + u"POLL_SUBSCRIBERS_ONLY"_q, + u"POLL_MEMBER_RESTRICTED"_q, + u"VOTE_SUBSCRIBERS_ONLY"_q, + u"SUBSCRIBERS_ONLY"_q, + u"SUBSCRIBER_REQUIRED"_q, + u"SUBSCRIBER_ONLY"_q, +}; + +const auto kSubscribersJoinedTooRecentlyVoteErrorPatterns = std::array{ + u"POLL_SUBSCRIBERS_TOO_RECENT"_q, + u"VOTE_SUBSCRIBERS_TOO_RECENT"_q, + u"SUBSCRIBERS_TOO_RECENT"_q, + u"SUBSCRIBER_TOO_RECENT"_q, + u"JOINED_TOO_RECENTLY"_q, + u"24_HOURS"_q, +}; + +const auto kCountriesVoteErrorPatterns = std::array{ + u"POLL_COUNTRIES_ISO2"_q, + u"VOTE_COUNTRIES_ISO2"_q, + u"COUNTRIES_ISO2"_q, + u"COUNTRY_RESTRICTED"_q, + u"COUNTRY_ISO2"_q, +}; + +template +[[nodiscard]] bool MatchesErrorPattern( + const QString &type, + const std::array &patterns) { + for (const auto &pattern : patterns) { + if (!pattern.isEmpty() + && type.contains(pattern, Qt::CaseInsensitive)) { + return true; + } + } + return false; +} + +[[nodiscard]] PollData::VoteRestriction ParseVoteRestrictionError( + const QString &type) { + if (MatchesErrorPattern( + type, + kSubscribersJoinedTooRecentlyVoteErrorPatterns)) { + return PollData::VoteRestriction::SubscribersJoinedTooRecently; + } else if (MatchesErrorPattern( + type, + kSubscribersOnlyVoteErrorPatterns)) { + return PollData::VoteRestriction::SubscribersOnly; + } else if (MatchesErrorPattern( + type, + kCountriesVoteErrorPatterns)) { + return PollData::VoteRestriction::Countries; + } + return PollData::VoteRestriction::None; +} + +void ShowVoteRestrictionToast( + not_null peer, + not_null poll, + PollData::VoteRestriction restriction) { + if (restriction == PollData::VoteRestriction::None) { + return; + } + auto text = PollVoteRestrictionText(restriction, peer, poll); + if (text.text.isEmpty()) { + return; + } + if (const auto window = peer->session().tryResolveWindow(peer)) { + window->showToast({ + .text = std::move(text), + .iconLottie = u"ban"_q, + .iconLottieSize = st::pollToastIconSize, + .duration = kVoteRestrictionToastDuration, + }); + } +} + +#ifdef _DEBUG +[[nodiscard]] Data::StatisticalGraph GenerateMockupPollStats( + const PollData &poll) { + auto chart = Data::StatisticalChart(); + const auto colorKeys = std::array{ + u"BLUE"_q, + u"GREEN"_q, + u"RED"_q, + u"GOLDEN"_q, + u"LIGHTBLUE"_q, + u"LIGHTGREEN"_q, + u"ORANGE"_q, + u"INDIGO"_q, + u"PURPLE"_q, + u"CYAN"_q, + }; + + constexpr auto kPoints = 14; + constexpr auto kOneDay = float64(24 * 60 * 60 * 1000); + constexpr auto kStart = float64(1704067200000); + chart.x.reserve(kPoints); + for (auto i = 0; i != kPoints; ++i) { + chart.x.push_back(kStart + i * kOneDay); + } + chart.timeStep = kOneDay; + + auto lineId = 0; + chart.lines.reserve(poll.answers.size()); + for (const auto &answer : poll.answers) { + auto line = Data::StatisticalChart::Line(); + line.id = ++lineId; + line.idString = u"answer_%1"_q.arg(line.id); + line.name = answer.text.text.trimmed(); + if (line.name.isEmpty()) { + line.name = QString("#%1").arg(line.id); + } + line.colorKey = colorKeys[(line.id - 1) % int(colorKeys.size())]; + line.y.reserve(kPoints); + + auto seed = int64(13 * line.id + 17); + for (const auto byte : answer.option) { + seed += uchar(byte); + } + const auto base = std::max(int64(answer.votes), int64(1)); + for (auto i = 0; i != kPoints; ++i) { + const auto wave = int64( + ((i + line.id) % 5) * ((i + 2 * line.id) % 4)); + const auto trend = int64((i * (line.id + 1)) / 3); + const auto noise = int64((seed + i * 7 + line.id * 11) % 6); + const auto value = std::max( + base + wave + trend + noise - 2, + int64(1)); + line.y.push_back(value); + line.maxValue = std::max(line.maxValue, value); + line.minValue = std::min(line.minValue, value); + } + chart.lines.push_back(std::move(line)); + } + if (chart.lines.empty()) { + auto line = Data::StatisticalChart::Line(); + line.id = 1; + line.idString = u"votes"_q; + line.name = tr::lng_notification_reactions_poll_votes(tr::now); + line.colorKey = u"BLUE"_q; + line.y.reserve(kPoints); + + const auto base = std::max(int64(poll.totalVoters), int64(1)); + for (auto i = 0; i != kPoints; ++i) { + const auto value = std::max( + base + i * 2 + ((i * 5) % 7), + int64(1)); + line.y.push_back(value); + line.maxValue = std::max(line.maxValue, value); + line.minValue = std::min(line.minValue, value); + } + chart.lines.push_back(std::move(line)); + } + + chart.defaultZoomXIndex = { + .min = std::max(0, kPoints - 8), + .max = kPoints - 1, + }; + chart.measure(); + if (chart.maxValue == chart.minValue) { + if (chart.minValue) { + chart.minValue = 0; + } else { + chart.maxValue = 1; + } + } + return { + .chart = std::move(chart), + }; } +#endif } // namespace +namespace Api { + Polls::Polls(not_null api) : _session(&api->session()) , _api(&api->instance()) { @@ -37,9 +219,10 @@ Polls::Polls(not_null api) void Polls::create( const PollData &data, - const SendAction &action, + const TextWithEntities &text, + SendAction action, Fn done, - Fn fail) { + Fn fail) { _session->api().sendAction(action); const auto history = action.history; @@ -47,6 +230,7 @@ void Polls::create( const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; auto sendFlags = MTPmessages_SendMedia::Flags(0); if (action.replyTo) { sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; @@ -54,16 +238,22 @@ void Polls::create( const auto clearCloudDraft = action.clearDraft; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; - history->clearLocalDraft(topicRootId); - history->clearCloudDraft(topicRootId); - history->startSavingCloudDraft(topicRootId); + history->clearLocalDraft(topicRootId, monoforumPeerId); + history->clearCloudDraft(topicRootId, monoforumPeerId); + history->startSavingCloudDraft(topicRootId, monoforumPeerId); } const auto silentPost = ShouldSendSilent(peer, action.options); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } if (action.options.scheduled) { sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; @@ -71,10 +261,24 @@ void Polls::create( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } const auto sendAs = action.options.sendAs; if (sendAs) { sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; } + auto sentEntities = Api::EntitiesToMTP( + _session, + text.entities, + Api::ConvertOption::SkipLocal); + if (!sentEntities.v.isEmpty()) { + sendFlags |= MTPmessages_SendMedia::Flag::f_entities; + } auto &histories = history->owner().histories(); const auto randomId = base::RandomValue(); histories.sendPreparedMessage( @@ -83,21 +287,25 @@ void Polls::create( randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), PollDataToInputMedia(&data), - MTP_string(), + MTP_string(text.text), MTP_long(randomId), MTPReplyMarkup(), - MTPVector(), + sentEntities, MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + SuggestToMTP(action.options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( topicRootId, + monoforumPeerId, UnixtimeFromMsgId(response.outerMsgId)); } _session->changes().historyUpdated( @@ -110,9 +318,12 @@ void Polls::create( if (clearCloudDraft) { history->finishSavingCloudDraft( topicRootId, + monoforumPeerId, UnixtimeFromMsgId(response.outerMsgId)); } - fail(); + const auto expired = (error.code() == 400) + && error.type().startsWith(u"FILE_REFERENCE_"_q); + fail(expired); }); } @@ -128,6 +339,7 @@ void Polls::sendVotes( if (!item) { return; } + const auto peer = item->history()->peer; const auto showSending = poll && !options.empty(); const auto hideSending = [=] { @@ -141,6 +353,12 @@ void Polls::sendVotes( if (showSending) { poll->sendingVotes = options; _session->data().requestItemRepaint(item); + } else if (poll && options.empty() && poll->voted()) { + for (auto &answer : poll->answers) { + answer.chosen = false; + } + ++poll->version; + _session->data().notifyPollUpdateDelayed(poll); } auto prepared = QVector(); @@ -150,16 +368,100 @@ void Polls::sendVotes( ranges::back_inserter(prepared), [](const QByteArray &option) { return MTP_bytes(option); }); const auto requestId = _api.request(MTPmessages_SendVote( - item->history()->peer->input, + peer->input(), MTP_int(item->id), MTP_vector(prepared) )).done([=](const MTPUpdates &result) { _pollVotesRequestIds.erase(itemId); hideSending(); + if (poll) { + if (poll->voteRestriction() != PollData::VoteRestriction::None) { + poll->setVoteRestriction(PollData::VoteRestriction::None); + _session->data().notifyPollUpdateDelayed(poll); + } + } _session->updates().applyUpdates(result); - }).fail([=] { + }).fail([=](const MTP::Error &error) { _pollVotesRequestIds.erase(itemId); hideSending(); + if (poll) { + const auto restriction = ParseVoteRestrictionError(error.type()); + if (restriction != PollData::VoteRestriction::None) { + poll->setVoteRestriction(restriction); + _session->data().notifyPollUpdateDelayed(poll); + if (const auto item = _session->data().message(itemId)) { + _session->data().requestItemResize(item); + } + ShowVoteRestrictionToast(peer, poll, restriction); + } + } + }).send(); + _pollVotesRequestIds.emplace(itemId, requestId); +} + +void Polls::addAnswer( + FullMsgId itemId, + const TextWithEntities &text, + const PollMedia &media, + Fn done, + Fn fail) { + if (_pollAddAnswerRequestIds.contains(itemId)) { + return; + } + const auto item = _session->data().message(itemId); + if (!item) { + return; + } + const auto sentEntities = Api::EntitiesToMTP( + _session, + text.entities, + Api::ConvertOption::SkipLocal); + using Flag = MTPDinputPollAnswer::Flag; + const auto flags = media + ? Flag::f_media + : Flag(); + const auto answer = MTP_inputPollAnswer( + MTP_flags(flags), + MTP_textWithEntities( + MTP_string(text.text), + sentEntities), + media ? PollMediaToMTP(media) : MTPInputMedia()); + const auto requestId = _api.request(MTPmessages_AddPollAnswer( + item->history()->peer->input(), + MTP_int(item->id), + answer + )).done([=](const MTPUpdates &result) { + _pollAddAnswerRequestIds.erase(itemId); + _session->updates().applyUpdates(result); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + _pollAddAnswerRequestIds.erase(itemId); + if (fail) { + fail(error.type()); + } + }).send(); + _pollAddAnswerRequestIds.emplace(itemId, requestId); +} + +void Polls::deleteAnswer(FullMsgId itemId, const QByteArray &option) { + if (_pollVotesRequestIds.contains(itemId)) { + return; + } + const auto item = _session->data().message(itemId); + if (!item) { + return; + } + const auto requestId = _api.request(MTPmessages_DeletePollAnswer( + item->history()->peer->input(), + MTP_int(item->id), + MTP_bytes(option) + )).done([=](const MTPUpdates &result) { + _pollVotesRequestIds.erase(itemId); + _session->updates().applyUpdates(result); + }).fail([=] { + _pollVotesRequestIds.erase(itemId); }).send(); _pollVotesRequestIds.emplace(itemId, requestId); } @@ -176,14 +478,16 @@ void Polls::close(not_null item) { } const auto requestId = _api.request(MTPmessages_EditMessage( MTP_flags(MTPmessages_EditMessage::Flag::f_media), - item->history()->peer->input, + item->history()->peer->input(), MTP_int(item->id), MTPstring(), PollDataToInputMedia(poll, true), MTPReplyMarkup(), MTPVector(), MTP_int(0), // schedule_date - MTPint() // quick_reply_shortcut_id + MTP_int(0), // schedule_repeat_period + MTPint(), // quick_reply_shortcut_id + MTPInputRichMessage() )).done([=](const MTPUpdates &result) { _pollCloseRequestIds.erase(itemId); _session->updates().applyUpdates(result); @@ -198,9 +502,13 @@ void Polls::reloadResults(not_null item) { if (!item->isRegular() || _pollReloadRequestIds.contains(itemId)) { return; } + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + const auto pollHash = poll ? poll->hash : uint64(0); const auto requestId = _api.request(MTPmessages_GetPollResults( - item->history()->peer->input, - MTP_int(item->id) + item->history()->peer->input(), + MTP_int(item->id), + MTP_long(pollHash) )).done([=](const MTPUpdates &result) { _pollReloadRequestIds.erase(itemId); _session->updates().applyUpdates(result); @@ -210,4 +518,65 @@ void Polls::reloadResults(not_null item) { _pollReloadRequestIds.emplace(itemId, requestId); } +void Polls::requestStats( + FullMsgId itemId, + Fn done, + Fn fail) { + const auto item = _session->data().message(itemId); + const auto media = item ? item->media() : nullptr; + const auto poll = media ? media->poll() : nullptr; + if (!item || !item->isRegular() || !poll) { + if (fail) { + fail(QString()); + } + return; + } +#ifdef _DEBUG + if (base::IsCtrlPressed()) { + auto callback = std::move(done); + if (callback) { + constexpr auto kMockupStatsDelay = 2 * crl::time(1000); + auto graph = GenerateMockupPollStats(*poll); + base::call_delayed(kMockupStatsDelay, _session, [=]() mutable { + callback(std::move(graph)); + }); + } + return; + } +#endif + const auto requestGraph = [=](const QString &token) { + _api.request(MTPstats_LoadAsyncGraph( + MTP_flags(MTPstats_LoadAsyncGraph::Flag(0)), + MTP_string(token), + MTP_long(0) + )).done([=](const MTPStatsGraph &result) { + if (done) { + done(Api::StatisticalGraphFromTL(result)); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); + }; + _api.request(MTPstats_GetPollStats( + MTP_flags(MTPstats_GetPollStats::Flags(0)), + item->history()->peer->input(), + MTP_int(item->id) + )).done([=](const MTPstats_PollStats &result) { + auto graph = Api::StatisticalGraphFromTL(result.data().vvotes_graph()); + if (graph.chart || !graph.error.isEmpty() || graph.zoomToken.isEmpty()) { + if (done) { + done(std::move(graph)); + } + } else { + requestGraph(graph.zoomToken); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_polls.h b/Telegram/SourceFiles/api/api_polls.h index 2ff08a1ac35b1c..a7b1491d5f6cc6 100644 --- a/Telegram/SourceFiles/api/api_polls.h +++ b/Telegram/SourceFiles/api/api_polls.h @@ -8,10 +8,15 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "ui/text/text_entity.h" class ApiWrap; class HistoryItem; struct PollData; +struct PollMedia; +namespace Data { +struct StatisticalGraph; +} // namespace Data namespace Main { class Session; @@ -27,20 +32,33 @@ class Polls final { void create( const PollData &data, - const SendAction &action, + const TextWithEntities &text, + SendAction action, Fn done, - Fn fail); + Fn fail); void sendVotes( FullMsgId itemId, const std::vector &options); + void addAnswer( + FullMsgId itemId, + const TextWithEntities &text, + const PollMedia &media, + Fn done, + Fn fail); + void deleteAnswer(FullMsgId itemId, const QByteArray &option); void close(not_null item); void reloadResults(not_null item); + void requestStats( + FullMsgId itemId, + Fn done, + Fn fail); private: const not_null _session; MTP::Sender _api; base::flat_map _pollVotesRequestIds; + base::flat_map _pollAddAnswerRequestIds; base::flat_map _pollCloseRequestIds; base::flat_map _pollReloadRequestIds; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 84ce45783b9e48..215c9640f5e5b9 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -11,6 +11,7 @@ For license and copyright information please follow this link: #include "api/api_text_entities.h" #include "apiwrap.h" #include "base/random.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_peer_values.h" @@ -22,6 +23,7 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "main/main_session.h" #include "payments/payments_form.h" +#include "ui/chat/chat_style.h" // ColorCollectible #include "ui/text/format_values.h" namespace Api { @@ -34,7 +36,7 @@ namespace { .giveawayId = data.vgiveaway_msg_id().value_or_empty(), .date = data.vdate().v, .used = data.vused_date().value_or_empty(), - .months = data.vmonths().v, + .days = data.vdays().v, .giveaway = data.is_via_giveaway(), }; } @@ -44,19 +46,48 @@ namespace { auto options = PremiumSubscriptionOptionsFromTL(tlOptions); for (auto i = 0; i < options.size(); i++) { const auto &tlOption = tlOptions[i].data(); + const auto currency = qs(tlOption.vcurrency()); const auto perUserText = Ui::FillAmountAndCurrency( tlOption.vamount().v / float64(tlOption.vusers().v), - qs(tlOption.vcurrency()), + currency, false); options[i].costPerMonth = perUserText + ' ' + QChar(0x00D7) + ' ' + QString::number(tlOption.vusers().v); + options[i].total = Ui::FillAmountAndCurrency( + tlOption.vamount().v, + currency); + options[i].currency = currency; } return options; } +[[nodiscard]] int FindStarsForResale(const MTPVector *list) { + if (!list) { + return 0; + } + for (const auto &amount : list->v) { + if (amount.type() == mtpc_starsAmount) { + return int(amount.c_starsAmount().vamount().v); + } + } + return 0; +} + +[[nodiscard]] int64 FindTonForResale(const MTPVector *list) { + if (!list) { + return 0; + } + for (const auto &amount : list->v) { + if (amount.type() == mtpc_starsTonAmount) { + return int64(amount.c_starsTonAmount().vamount().v); + } + } + return 0; +} + } // namespace Premium::Premium(not_null api) @@ -67,7 +98,7 @@ Premium::Premium(not_null api) // only queued, because it is not constructed yet. Data::AmPremiumValue( _session - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { reload(); if (_session->premium()) { reloadCloudSet(); @@ -337,7 +368,7 @@ void Premium::resolveGiveawayInfo( _giveawayInfoPeer = peer; _giveawayInfoMessageId = messageId; _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( - _giveawayInfoPeer->input, + _giveawayInfoPeer->input(), MTP_int(_giveawayInfoMessageId.bare) )).done([=](const MTPpayments_GiveawayInfo &result) { _giveawayInfoRequestId = 0; @@ -377,15 +408,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } -rpl::producer<> Premium::somePremiumRequiredResolved() const { - return _somePremiumRequiredResolved.events(); +rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const { + return _someMessageMoneyRestrictionsResolved.events(); } -void Premium::resolvePremiumRequired(not_null user) { - _resolvePremiumRequiredUsers.emplace(user); - if (!_premiumRequiredRequestScheduled - && _resolvePremiumRequestedUsers.empty()) { - _premiumRequiredRequestScheduled = true; +void Premium::resolveMessageMoneyRestrictions(not_null user) { + _resolveMessageMoneyRequiredUsers.emplace(user); + if (!_messageMoneyRequestScheduled + && _resolveMessageMoneyRequestedUsers.empty()) { + _messageMoneyRequestScheduled = true; crl::on_main(_session, [=] { requestPremiumRequiredSlice(); }); @@ -393,50 +424,65 @@ void Premium::resolvePremiumRequired(not_null user) { } void Premium::requestPremiumRequiredSlice() { - _premiumRequiredRequestScheduled = false; - if (!_resolvePremiumRequestedUsers.empty() - || _resolvePremiumRequiredUsers.empty()) { + _messageMoneyRequestScheduled = false; + if (!_resolveMessageMoneyRequestedUsers.empty() + || _resolveMessageMoneyRequiredUsers.empty()) { return; } constexpr auto kPerRequest = 100; - auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers + auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers | ranges::views::transform(&UserData::inputUser)); if (users.v.size() > kPerRequest) { auto shortened = users.v; shortened.resize(kPerRequest); users = MTP_vector(std::move(shortened)); - const auto from = begin(_resolvePremiumRequiredUsers); - _resolvePremiumRequestedUsers = { from, from + kPerRequest }; - _resolvePremiumRequiredUsers.erase(from, from + kPerRequest); + const auto from = begin(_resolveMessageMoneyRequiredUsers); + _resolveMessageMoneyRequestedUsers = { from, from + kPerRequest }; + _resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest); } else { - _resolvePremiumRequestedUsers - = base::take(_resolvePremiumRequiredUsers); + _resolveMessageMoneyRequestedUsers + = base::take(_resolveMessageMoneyRequiredUsers); } - const auto finish = [=](const QVector &list) { - constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite; - constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown; - constexpr auto mask = me | known; + const auto finish = [=](const QVector &list) { auto index = 0; - for (const auto &user : base::take(_resolvePremiumRequestedUsers)) { - const auto require = (index < list.size()) - && mtpIsTrue(list[index++]); - user->setFlags((user->flags() & ~mask) - | known - | (require ? me : UserDataFlag())); + for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) { + const auto set = [&](bool requirePremium, int stars) { + using Flag = UserDataFlag; + constexpr auto me = Flag::RequiresPremiumToWrite; + constexpr auto known = Flag::MessageMoneyRestrictionsKnown; + constexpr auto hasPrem = Flag::HasRequirePremiumToWrite; + constexpr auto hasStars = Flag::HasStarsPerMessage; + user->setStarsPerMessage(stars); + user->setFlags((user->flags() & ~me) + | known + | (requirePremium ? (me | hasPrem) : Flag()) + | (stars ? hasStars : Flag())); + }; + if (index >= list.size()) { + set(false, 0); + continue; + } + list[index++].match([&](const MTPDrequirementToContactEmpty &) { + set(false, 0); + }, [&](const MTPDrequirementToContactPremium &) { + set(true, 0); + }, [&](const MTPDrequirementToContactPaidMessages &data) { + set(false, data.vstars_amount().v); + }); } - if (!_premiumRequiredRequestScheduled - && !_resolvePremiumRequiredUsers.empty()) { - _premiumRequiredRequestScheduled = true; + if (!_messageMoneyRequestScheduled + && !_resolveMessageMoneyRequiredUsers.empty()) { + _messageMoneyRequestScheduled = true; crl::on_main(_session, [=] { requestPremiumRequiredSlice(); }); } - _somePremiumRequiredResolved.fire({}); + _someMessageMoneyRestrictionsResolved.fire({}); }; _session->api().request( - MTPusers_GetIsPremiumRequiredToContact(std::move(users)) - ).done([=](const MTPVector &result) { + MTPusers_GetRequirementsToContact(std::move(users)) + ).done([=](const MTPVector &result) { finish(result.v); }).fail([=] { finish({}); @@ -457,16 +503,20 @@ rpl::producer PremiumGiftCodeOptions::request() { MTP_flags(_peer->isChannel() ? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer : MTPpayments_GetPremiumGiftCodeOptions::Flag(0)), - _peer->input + _peer->input() )).done([=](const MTPVector &result) { auto tlMapOptions = base::flat_map>(); for (const auto &tlOption : result.v) { const auto &data = tlOption.data(); tlMapOptions[data.vusers().v].push_back(tlOption); + if (qs(data.vcurrency()) == Ui::kCreditsCurrency) { + continue; + } const auto token = Token{ data.vusers().v, data.vmonths().v }; _stores[token] = Store{ .amount = data.vamount().v, + .currency = qs(data.vcurrency()), .product = qs(data.vstore_product().value_or_empty()), .quantity = data.vstore_quantity().value_or_empty(), }; @@ -475,14 +525,14 @@ rpl::producer PremiumGiftCodeOptions::request() { } } for (const auto &[amount, tlOptions] : tlMapOptions) { - if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { - _optionsForOnePerson.currency = qs( - tlOptions.front().data().vcurrency()); + if (amount == 1 && _optionsForOnePerson.currencies.empty()) { for (const auto &option : tlOptions) { _optionsForOnePerson.months.push_back( option.data().vmonths().v); _optionsForOnePerson.totalCosts.push_back( option.data().vamount().v); + _optionsForOnePerson.currencies.push_back( + qs(option.data().vcurrency())); } } _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); @@ -507,9 +557,9 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( } _api.request(MTPpayments_LaunchPrepaidGiveaway( - _peer->input, + _peer->input(), MTP_long(prepaidId), - invoice.creditsAmount + invoice.giveawayCredits ? Payments::InvoiceCreditsGiveawayToTL(invoice) : Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice) )).done([=](const MTPUpdates &result) { @@ -540,7 +590,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( const auto token = Token{ users, months }; const auto &store = _stores[token]; return Payments::InvoicePremiumGiftCode{ - .currency = _optionsForOnePerson.currency, + .currency = store.currency, .storeProduct = store.product, .randomId = randomId, .amount = store.amount, @@ -553,14 +603,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( std::vector PremiumGiftCodeOptions::optionsForPeer() const { auto result = std::vector(); - if (!_optionsForOnePerson.currency.isEmpty()) { + if (!_optionsForOnePerson.currencies.empty()) { const auto count = int(_optionsForOnePerson.months.size()); result.reserve(count); for (auto i = 0; i != count; ++i) { Assert(i < _optionsForOnePerson.totalCosts.size()); + Assert(i < _optionsForOnePerson.currencies.size()); result.push_back({ .cost = _optionsForOnePerson.totalCosts[i], - .currency = _optionsForOnePerson.currency, + .currency = _optionsForOnePerson.currencies[i], .months = _optionsForOnePerson.months[i], }); } @@ -568,24 +619,32 @@ std::vector PremiumGiftCodeOptions::optionsForPeer() const { return result; } -Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) { - const auto it = _subscriptionOptions.find(amount); +Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::optionsForGiveaway( + int usersCount) { + const auto skipForStars = [&](Data::PremiumSubscriptionOptions options) { + const auto proj = &Data::PremiumSubscriptionOption::currency; + options.erase( + ranges::remove(options, Ui::kCreditsCurrency, proj), + end(options)); + return options; + }; + const auto it = _subscriptionOptions.find(usersCount); if (it != end(_subscriptionOptions)) { - return it->second; + return skipForStars(it->second); } else { auto tlOptions = QVector(); for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) { tlOptions.push_back(MTP_premiumGiftCodeOption( MTP_flags(MTPDpremiumGiftCodeOption::Flags(0)), - MTP_int(amount), + MTP_int(usersCount), MTP_int(_optionsForOnePerson.months[i]), MTPstring(), MTPint(), - MTP_string(_optionsForOnePerson.currency), - MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); + MTP_string(_optionsForOnePerson.currencies[i]), + MTP_long(_optionsForOnePerson.totalCosts[i] * usersCount))); } - _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); - return _subscriptionOptions[amount]; + _subscriptionOptions[usersCount] = GiftCodesFromTL(tlOptions); + return skipForStars(_subscriptionOptions[usersCount]); } } @@ -598,10 +657,12 @@ auto PremiumGiftCodeOptions::requestStarGifts() MTP_int(0) )).done([=](const MTPpayments_StarGifts &result) { result.match([&](const MTPDpayments_starGifts &data) { + _peer->owner().processUsers(data.vusers()); + _peer->owner().processChats(data.vchats()); _giftsHash = data.vhash().v; const auto &list = data.vgifts().v; const auto session = &_peer->session(); - auto gifts = std::vector(); + auto gifts = std::vector(); gifts.reserve(list.size()); for (const auto &gift : list) { if (auto parsed = FromTL(session, gift)) { @@ -620,7 +681,8 @@ auto PremiumGiftCodeOptions::requestStarGifts() }; } -const std::vector &PremiumGiftCodeOptions::starGifts() const { +auto PremiumGiftCodeOptions::starGifts() const +-> const std::vector & { return _gifts; } @@ -693,28 +755,38 @@ rpl::producer SponsoredToggle::setToggled(bool v) { }; } -RequirePremiumState ResolveRequiresPremiumToWrite( +MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, History *maybeHistory) { + if (const auto channel = peer->asChannel()) { + return { + .starsPerMessage = channel->starsPerMessageChecked(), + .known = true, + }; + } const auto user = peer->asUser(); - if (!user - || !user->someRequirePremiumToWrite() - || user->session().premium()) { - return RequirePremiumState::No; - } else if (user->requirePremiumToWriteKnown()) { - return user->meRequiresPremiumToWrite() - ? RequirePremiumState::Yes - : RequirePremiumState::No; + if (!user) { + return { .known = true }; + } else if (user->messageMoneyRestrictionsKnown()) { + return { + .starsPerMessage = user->starsPerMessageChecked(), + .premiumRequired = (user->requiresPremiumToWrite() + && !user->session().premium()), + .known = true, + }; + } else if (user->hasStarsPerMessage()) { + return {}; + } else if (!user->hasRequirePremiumToWrite()) { + return { .known = true }; } else if (user->flags() & UserDataFlag::MutualContact) { - return RequirePremiumState::No; + return { .known = true }; } else if (!maybeHistory) { - return RequirePremiumState::Unknown; + return {}; } - const auto update = [&](bool require) { using Flag = UserDataFlag; - constexpr auto known = Flag::RequirePremiumToWriteKnown; - constexpr auto me = Flag::MeRequiresPremiumToWrite; + constexpr auto known = Flag::MessageMoneyRestrictionsKnown; + constexpr auto me = Flag::RequiresPremiumToWrite; user->setFlags((user->flags() & ~me) | known | (require ? me : Flag())); @@ -726,16 +798,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite( const auto item = view->data(); if (!item->out() && !item->isService()) { update(false); - return RequirePremiumState::No; + return { .known = true }; } } } if (user->isContact() // Here we know, that we're not in his contacts. && maybeHistory->loadedAtTop() // And no incoming messages. && maybeHistory->loadedAtBottom()) { - update(true); + return { + .premiumRequired = !user->session().premium(), + .known = true, + }; } - return RequirePremiumState::Unknown; + return {}; } rpl::producer RandomHelloStickerValue( @@ -758,56 +833,284 @@ rpl::producer RandomHelloStickerValue( }) | rpl::take(1) | rpl::map(random)); } -std::optional FromTL( +std::optional FromTL( not_null session, const MTPstarGift &gift) { - const auto &data = gift.data(); - const auto document = session->data().processDocument( - data.vsticker()); - const auto remaining = data.vavailability_remains(); - const auto total = data.vavailability_total(); - if (!document->sticker()) { - return {}; - } - return StarGift{ - .id = uint64(data.vid().v), - .stars = int64(data.vstars().v), - .convertStars = int64(data.vconvert_stars().v), - .document = document, - .limitedLeft = remaining.value_or_empty(), - .limitedCount = total.value_or_empty(), - }; + return gift.match([&](const MTPDstarGift &data) { + const auto document = session->data().processDocument( + data.vsticker()); + const auto resellPrice = data.vresell_min_stars().value_or_empty(); + const auto remaining = data.vavailability_remains(); + const auto total = data.vavailability_total(); + if (!document->sticker()) { + return std::optional(); + } + const auto releasedById = data.vreleased_by() + ? peerFromMTP(*data.vreleased_by()) + : PeerId(); + const auto releasedBy = releasedById + ? session->data().peer(releasedById).get() + : nullptr; + const auto background = [&] { + if (!data.vbackground()) { + return std::shared_ptr(); + } + const auto &fields = data.vbackground()->data(); + using namespace Ui; + return std::make_shared( + Data::StarGiftBackground{ + .center = ColorFromSerialized(fields.vcenter_color()), + .edge = ColorFromSerialized(fields.vedge_color()), + .text = ColorFromSerialized(fields.vtext_color()), + }); + }; + return std::optional(Data::StarGift{ + .id = uint64(data.vid().v), + .background = background(), + .stars = int64(data.vstars().v), + .starsConverted = int64(data.vconvert_stars().v), + .starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()), + .starsResellMin = int64(resellPrice), + .document = document, + .releasedBy = releasedBy, + .resellTitle = qs(data.vtitle().value_or_empty()), + .resellCount = int(data.vavailability_resale().value_or_empty()), + .auctionSlug = qs(data.vauction_slug().value_or_empty()), + .auctionGiftsPerRound = data.vgifts_per_round().value_or_empty(), + .auctionStartDate = data.vauction_start_date().value_or_empty(), + .limitedLeft = remaining.value_or_empty(), + .limitedCount = total.value_or_empty(), + .perUserTotal = data.vper_user_total().value_or_empty(), + .perUserRemains = data.vper_user_remains().value_or_empty(), + .upgradeVariants = data.vupgrade_variants().value_or_empty(), + .firstSaleDate = data.vfirst_sale_date().value_or_empty(), + .lastSaleDate = data.vlast_sale_date().value_or_empty(), + .lockedUntilDate = data.vlocked_until_date().value_or_empty(), + .requirePremium = data.is_require_premium(), + .peerColorAvailable = data.is_peer_color_available(), + .upgradable = data.vupgrade_stars().has_value(), + .birthday = data.is_birthday(), + .soldOut = data.is_sold_out(), + }); + }, [&](const MTPDstarGiftUnique &data) { + const auto total = data.vavailability_total().v; + auto model = std::optional(); + auto pattern = std::optional(); + for (const auto &attribute : data.vattributes().v) { + attribute.match([&](const MTPDstarGiftAttributeModel &data) { + model = FromTL(session, data); + }, [&](const MTPDstarGiftAttributePattern &data) { + pattern = FromTL(session, data); + }, [&](const MTPDstarGiftAttributeBackdrop &data) { + }, [&](const MTPDstarGiftAttributeOriginalDetails &data) { + }); + } + if (!model + || !model->document->sticker() + || !pattern + || !pattern->document->sticker()) { + return std::optional(); + } + const auto releasedById = data.vreleased_by() + ? peerFromMTP(*data.vreleased_by()) + : PeerId(); + const auto themeUserId = data.vtheme_peer() + ? peerFromMTP(*data.vtheme_peer()) + : PeerId(); + const auto releasedBy = releasedById + ? session->data().peer(releasedById).get() + : nullptr; + const auto themeUser = themeUserId + ? session->data().peer(themeUserId).get() + : nullptr; + const auto colorCollectible = (data.vpeer_color() + && data.vpeer_color()->type() == mtpc_peerColorCollectible) + ? std::make_shared( + Data::ParseColorCollectible( + data.vpeer_color()->c_peerColorCollectible())) + : nullptr; + auto result = Data::StarGift{ + .id = data.vid().v, + .unique = std::make_shared(Data::UniqueGift{ + .id = data.vid().v, + .initialGiftId = data.vgift_id().v, + .slug = qs(data.vslug()), + .title = qs(data.vtitle()), + .giftAddress = qs(data.vgift_address().value_or_empty()), + .ownerAddress = qs(data.vowner_address().value_or_empty()), + .ownerName = qs(data.vowner_name().value_or_empty()), + .ownerId = (data.vowner_id() + ? peerFromMTP(*data.vowner_id()) + : PeerId()), + .hostId = (data.vhost_id() + ? peerFromMTP(*data.vhost_id()) + : PeerId()), + .releasedBy = releasedBy, + .themeUser = themeUser, + .nanoTonForResale = FindTonForResale(data.vresell_amount()), + .craftChancePermille + = data.vcraft_chance_permille().value_or_empty(), + .starsForResale = FindStarsForResale(data.vresell_amount()), + .starsMinOffer = data.voffer_min_stars().value_or(-1), + .number = data.vnum().v, + .onlyAcceptTon = data.is_resale_ton_only(), + .canBeTheme = data.is_theme_available(), + .crafted = data.is_crafted(), + .burned = data.is_burned(), + .model = *model, + .pattern = *pattern, + .value = (data.vvalue_amount() + ? std::make_shared( + Data::UniqueGiftValue{ + .currency = qs( + data.vvalue_currency().value_or_empty()), + .valuePrice = int64( + data.vvalue_amount().value_or_empty()), + .valuePriceUsd = int64( + data.vvalue_usd_amount().value_or_empty()), + }) + : nullptr), + .peerColor = colorCollectible, + }), + .document = model->document, + .releasedBy = releasedBy, + .limitedLeft = (total - data.vavailability_issued().v), + .limitedCount = total, + .resellTonOnly = data.is_resale_ton_only(), + .requirePremium = data.is_require_premium(), + }; + const auto unique = result.unique.get(); + for (const auto &attribute : data.vattributes().v) { + attribute.match([&](const MTPDstarGiftAttributeModel &data) { + }, [&](const MTPDstarGiftAttributePattern &data) { + }, [&](const MTPDstarGiftAttributeBackdrop &data) { + unique->backdrop = FromTL(data); + }, [&](const MTPDstarGiftAttributeOriginalDetails &data) { + unique->originalDetails = FromTL(session, data); + }); + } + return std::make_optional(std::move(result)); + }); } -std::optional FromTL( - not_null to, - const MTPuserStarGift &gift) { +std::optional FromTL( + not_null to, + const MTPsavedStarGift &gift) { const auto session = &to->session(); const auto &data = gift.data(); auto parsed = FromTL(session, data.vgift()); if (!parsed) { return {}; + } else if (const auto unique = parsed->unique.get()) { + unique->starsForTransfer = data.vtransfer_stars().value_or(-1); + unique->exportAt = data.vcan_export_at().value_or_empty(); + unique->canTransferAt = data.vcan_transfer_at().value_or_empty(); + unique->canResellAt = data.vcan_resell_at().value_or_empty(); + unique->canCraftAt = data.vcan_craft_at().value_or_empty(); } - return UserStarGift{ - .gift = std::move(*parsed), + using Id = Data::SavedStarGiftId; + const auto hasUnique = parsed->unique != nullptr; + return Data::SavedStarGift{ + .info = std::move(*parsed), + .manageId = (to->isUser() + ? Id::User(data.vmsg_id().value_or_empty()) + : Id::Chat(to, data.vsaved_id().value_or_empty())), + .collectionIds = (data.vcollection_id() + ? (data.vcollection_id()->v + | ranges::views::transform(&MTPint::v) + | ranges::to_vector) + : std::vector()), .message = (data.vmessage() - ? TextWithEntities{ - .text = qs(data.vmessage()->data().vtext()), - .entities = Api::EntitiesFromMTP( - session, - data.vmessage()->data().ventities().v), - } + ? Api::ParseTextWithEntities( + session, + *data.vmessage()) : TextWithEntities()), - .convertStars = int64(data.vconvert_stars().value_or_empty()), + .starsConverted = int64(data.vconvert_stars().value_or_empty()), + .starsUpgradedBySender = int64( + data.vupgrade_stars().value_or_empty()), + .starsForDetailsRemove = int64( + data.vdrop_original_details_stars().value_or_empty()), + .giftPrepayUpgradeHash = qs( + data.vprepaid_upgrade_hash().value_or_empty()), .fromId = (data.vfrom_id() - ? peerFromUser(data.vfrom_id()->v) + ? peerFromMTP(*data.vfrom_id()) : PeerId()), - .messageId = data.vmsg_id().value_or_empty(), .date = data.vdate().v, + .giftNum = data.vgift_num().value_or_empty(), + .upgradeSeparate = data.is_upgrade_separate(), + .upgradable = data.is_can_upgrade(), .anonymous = data.is_name_hidden(), + .pinned = data.is_pinned_to_top() && hasUnique, .hidden = data.is_unsaved(), .mine = to->isSelf(), }; } +int ParseRarity(const MTPStarGiftAttributeRarity &rarity) { + return rarity.match([&](const MTPDstarGiftAttributeRarity &data) { + return std::max(data.vpermille().v, 0); + }, [&](const MTPDstarGiftAttributeRarityUncommon &) { + return int(Data::UniqueGiftRarity::Uncommon); + }, [&](const MTPDstarGiftAttributeRarityRare &) { + return int(Data::UniqueGiftRarity::Rare); + }, [&](const MTPDstarGiftAttributeRarityEpic &) { + return int(Data::UniqueGiftRarity::Epic); + }, [&](const MTPDstarGiftAttributeRarityLegendary &) { + return int(Data::UniqueGiftRarity::Legendary); + }); +} + +Data::UniqueGiftModel FromTL( + not_null session, + const MTPDstarGiftAttributeModel &data) { + auto result = Data::UniqueGiftModel{ + .document = session->data().processDocument(data.vdocument()), + }; + result.name = qs(data.vname()); + result.rarityValue = ParseRarity(data.vrarity()); + return result; +} + +Data::UniqueGiftPattern FromTL( + not_null session, + const MTPDstarGiftAttributePattern &data) { + auto result = Data::UniqueGiftPattern{ + .document = session->data().processDocument(data.vdocument()), + }; + result.document->overrideEmojiUsesTextColor(true); + result.name = qs(data.vname()); + result.rarityValue = ParseRarity(data.vrarity()); + return result; +} + +Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) { + auto result = Data::UniqueGiftBackdrop{ .id = data.vbackdrop_id().v }; + result.name = qs(data.vname()); + result.rarityValue = ParseRarity(data.vrarity()); + result.centerColor = Ui::ColorFromSerialized( + data.vcenter_color()); + result.edgeColor = Ui::ColorFromSerialized( + data.vedge_color()); + result.patternColor = Ui::ColorFromSerialized( + data.vpattern_color()); + result.textColor = Ui::ColorFromSerialized( + data.vtext_color()); + return result; +} + +Data::UniqueGiftOriginalDetails FromTL( + not_null session, + const MTPDstarGiftAttributeOriginalDetails &data) { + auto result = Data::UniqueGiftOriginalDetails(); + result.date = data.vdate().v; + result.senderId = data.vsender_id() + ? peerFromMTP(*data.vsender_id()) + : PeerId(); + result.recipientId = peerFromMTP(data.vrecipient_id()); + result.message = data.vmessage() + ? ParseTextWithEntities(session, *data.vmessage()) + : TextWithEntities(); + return result; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 805b68e03880ec..08b0f28b1bfbe1 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "data/data_premium_subscription_option.h" +#include "data/data_star_gift.h" #include "mtproto/sender.h" class History; @@ -29,11 +30,11 @@ struct GiftCode { MsgId giveawayId = 0; TimeId date = 0; TimeId used = 0; // 0 if not used. - int months = 0; + int days = 0; bool giveaway = false; explicit operator bool() const { - return months != 0; + return days != 0; } friend inline bool operator==( @@ -73,27 +74,6 @@ struct GiftOptionData { int months = 0; }; -struct StarGift { - uint64 id = 0; - int64 stars = 0; - int64 convertStars = 0; - not_null document; - int limitedLeft = 0; - int limitedCount = 0; -}; - -struct UserStarGift { - StarGift gift; - TextWithEntities message; - int64 convertStars = 0; - PeerId fromId = 0; - MsgId messageId = 0; - TimeId date = 0; - bool anonymous = false; - bool hidden = false; - bool mine = false; -}; - class Premium final { public: explicit Premium(not_null api); @@ -136,8 +116,9 @@ class Premium final { [[nodiscard]] auto subscriptionOptions() const -> const Data::PremiumSubscriptionOptions &; - [[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const; - void resolvePremiumRequired(not_null user); + [[nodiscard]] auto someMessageMoneyRestrictionsResolved() const + -> rpl::producer<>; + void resolveMessageMoneyRestrictions(not_null user); private: void reloadPromo(); @@ -186,10 +167,10 @@ class Premium final { Data::PremiumSubscriptionOptions _subscriptionOptions; - rpl::event_stream<> _somePremiumRequiredResolved; - base::flat_set> _resolvePremiumRequiredUsers; - base::flat_set> _resolvePremiumRequestedUsers; - bool _premiumRequiredRequestScheduled = false; + rpl::event_stream<> _someMessageMoneyRestrictionsResolved; + base::flat_set> _resolveMessageMoneyRequiredUsers; + base::flat_set> _resolveMessageMoneyRequestedUsers; + bool _messageMoneyRequestScheduled = false; }; @@ -199,7 +180,8 @@ class PremiumGiftCodeOptions final { [[nodiscard]] rpl::producer request(); [[nodiscard]] std::vector optionsForPeer() const; - [[nodiscard]] Data::PremiumSubscriptionOptions options(int amount); + [[nodiscard]] Data::PremiumSubscriptionOptions optionsForGiveaway( + int usersCount); [[nodiscard]] const std::vector &availablePresets() const; [[nodiscard]] int monthsFromPreset(int monthsIndex); [[nodiscard]] Payments::InvoicePremiumGiftCode invoice( @@ -216,7 +198,7 @@ class PremiumGiftCodeOptions final { [[nodiscard]] bool giveawayGiftsPurchaseAvailable() const; [[nodiscard]] rpl::producer requestStarGifts(); - [[nodiscard]] const std::vector &starGifts() const; + [[nodiscard]] const std::vector &starGifts() const; private: struct Token final { @@ -228,6 +210,7 @@ class PremiumGiftCodeOptions final { }; struct Store final { uint64 amount = 0; + QString currency; QString product; int quantity = 0; }; @@ -238,7 +221,7 @@ class PremiumGiftCodeOptions final { struct { std::vector months; std::vector totalCosts; - QString currency; + std::vector currencies; } _optionsForOnePerson; std::vector _availablePresets; @@ -246,7 +229,7 @@ class PremiumGiftCodeOptions final { base::flat_map _stores; int32 _giftsHash = 0; - std::vector _gifts; + std::vector _gifts; MTP::Sender _api; @@ -264,23 +247,43 @@ class SponsoredToggle final { }; -enum class RequirePremiumState { - Unknown, - Yes, - No, +struct MessageMoneyRestriction { + int starsPerMessage = 0; + bool premiumRequired = false; + bool known = false; + + explicit operator bool() const { + return starsPerMessage != 0 || premiumRequired; + } + + friend inline bool operator==( + const MessageMoneyRestriction &, + const MessageMoneyRestriction &) = default; }; -[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( +[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions( not_null peer, History *maybeHistory); [[nodiscard]] rpl::producer RandomHelloStickerValue( not_null session); -[[nodiscard]] std::optional FromTL( +[[nodiscard]] std::optional FromTL( not_null session, const MTPstarGift &gift); -[[nodiscard]] std::optional FromTL( - not_null to, - const MTPuserStarGift &gift); +[[nodiscard]] std::optional FromTL( + not_null to, + const MTPsavedStarGift &gift); + +[[nodiscard]] Data::UniqueGiftModel FromTL( + not_null session, + const MTPDstarGiftAttributeModel &data); +[[nodiscard]] Data::UniqueGiftPattern FromTL( + not_null session, + const MTPDstarGiftAttributePattern &data); +[[nodiscard]] Data::UniqueGiftBackdrop FromTL( + const MTPDstarGiftAttributeBackdrop &data); +[[nodiscard]] Data::UniqueGiftOriginalDetails FromTL( + not_null session, + const MTPDstarGiftAttributeOriginalDetails &data); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium_option.cpp b/Telegram/SourceFiles/api/api_premium_option.cpp index bd3056a756b3a6..3acd9aca2b2213 100644 --- a/Telegram/SourceFiles/api/api_premium_option.cpp +++ b/Telegram/SourceFiles/api/api_premium_option.cpp @@ -15,24 +15,34 @@ constexpr auto kDiscountDivider = 1.; Data::PremiumSubscriptionOption CreateSubscriptionOption( int months, - int monthlyAmount, + float64 monthlyAmount, int64 amount, const QString ¤cy, const QString &botUrl) { + const auto baselineAmount = monthlyAmount * months; const auto discount = [&] { - const auto percent = 1. - float64(amount) / (monthlyAmount * months); - return std::round(percent * 100. / kDiscountDivider) + const auto percent = 1. - float64(amount) / baselineAmount; + return base::SafeRound(percent * 100. / kDiscountDivider) * kDiscountDivider; }(); + const auto hasDiscount = (discount > 0); return { + .months = months, .duration = Ui::FormatTTL(months * 86400 * 31), - .discount = discount + .discount = hasDiscount ? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount) : QString(), .costPerMonth = Ui::FillAmountAndCurrency( - amount / float64(months), + int64(base::SafeRound(amount / float64(months))), + currency), + .costNoDiscount = hasDiscount + ? Ui::FillAmountAndCurrency( + int64(base::SafeRound(baselineAmount)), + currency) + : QString(), + .costPerYear = Ui::FillAmountAndCurrency( + int64(base::SafeRound(amount / float64(months / 12.))), currency), - .costTotal = Ui::FillAmountAndCurrency(amount, currency), .botUrl = botUrl, }; } diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index afe66ac4acbf72..4b265f1fb375e7 100644 --- a/Telegram/SourceFiles/api/api_premium_option.h +++ b/Telegram/SourceFiles/api/api_premium_option.h @@ -13,7 +13,7 @@ namespace Api { [[nodiscard]] Data::PremiumSubscriptionOption CreateSubscriptionOption( int months, - int monthlyAmount, + float64 monthlyAmount, int64 amount, const QString ¤cy, const QString &botUrl); @@ -24,15 +24,26 @@ template if (tlOpts.isEmpty()) { return {}; } + auto monthlyAmountPerCurrency = base::flat_map(); auto result = Data::PremiumSubscriptionOptions(); - const auto monthlyAmount = [&] { + const auto monthlyAmount = [&](const QString ¤cy) -> float64 { + const auto it = monthlyAmountPerCurrency.find(currency); + if (it != end(monthlyAmountPerCurrency)) { + return it->second; + } const auto &min = ranges::min_element( tlOpts, ranges::less(), - [](const Option &o) { return o.data().vamount().v; } + [&](const Option &o) { + return currency == qs(o.data().vcurrency()) + ? o.data().vamount().v + : std::numeric_limits::max(); + } )->data(); - return min.vamount().v / float64(min.vmonths().v); - }(); + const auto monthly = min.vamount().v / float64(min.vmonths().v); + monthlyAmountPerCurrency.emplace(currency, monthly); + return monthly; + }; result.reserve(tlOpts.size()); for (const auto &tlOption : tlOpts) { const auto &option = tlOption.data(); @@ -45,7 +56,7 @@ template const auto currency = qs(option.vcurrency()); result.push_back(CreateSubscriptionOption( months, - monthlyAmount, + monthlyAmount(currency), amount, currency, botUrl)); diff --git a/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp b/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp new file mode 100644 index 00000000000000..e28d52ff026039 --- /dev/null +++ b/Telegram/SourceFiles/api/api_reactions_notify_settings.cpp @@ -0,0 +1,185 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_reactions_notify_settings.h" + +#include "apiwrap.h" +#include "main/main_session.h" + +namespace Api { +namespace { + +[[nodiscard]] ReactionsNotifyFrom ParseFrom( + const MTPReactionNotificationsFrom &from) { + return from.match([](const MTPDreactionNotificationsFromContacts &) { + return ReactionsNotifyFrom::Contacts; + }, [](const MTPDreactionNotificationsFromAll &) { + return ReactionsNotifyFrom::All; + }); +} + +[[nodiscard]] MTPReactionNotificationsFrom SerializeFrom( + ReactionsNotifyFrom from) { + switch (from) { + case ReactionsNotifyFrom::Contacts: + return MTP_reactionNotificationsFromContacts(); + case ReactionsNotifyFrom::All: + return MTP_reactionNotificationsFromAll(); + } + Unexpected("Value in SerializeFrom."); +} + +} // namespace + +ReactionsNotifySettings::ReactionsNotifySettings(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +void ReactionsNotifySettings::reload(Fn callback) { + if (callback) { + _callbacks.push_back(std::move(callback)); + } + if (_requestId) { + return; + } + _requestId = _api.request(MTPaccount_GetReactionsNotifySettings( + )).done([=](const MTPReactionsNotifySettings &result) { + _requestId = 0; + apply(result); + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).fail([=] { + _requestId = 0; + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).send(); +} + +void ReactionsNotifySettings::updateMessagesFrom(ReactionsNotifyFrom value) { + _messagesFrom = value; + save(); +} + +void ReactionsNotifySettings::updatePollVotesFrom( + ReactionsNotifyFrom value) { + _pollVotesFrom = value; + save(); +} + +void ReactionsNotifySettings::setAllFrom(ReactionsNotifyFrom value) { + _messagesFrom = value; + _pollVotesFrom = value; + save(); +} + +void ReactionsNotifySettings::updateShowPreviews(bool value) { + _showPreviews = value; + save(); +} + +ReactionsNotifyFrom ReactionsNotifySettings::messagesFromCurrent() const { + return _messagesFrom.current(); +} + +rpl::producer ReactionsNotifySettings::messagesFrom() const { + return _messagesFrom.value(); +} + +ReactionsNotifyFrom ReactionsNotifySettings::pollVotesFromCurrent() const { + return _pollVotesFrom.current(); +} + +rpl::producer ReactionsNotifySettings::pollVotesFrom() const { + return _pollVotesFrom.value(); +} + +bool ReactionsNotifySettings::showPreviewsCurrent() const { + return _showPreviews.current(); +} + +rpl::producer ReactionsNotifySettings::showPreviews() const { + return _showPreviews.value(); +} + +bool ReactionsNotifySettings::enabledCurrent() const { + return (_messagesFrom.current() != ReactionsNotifyFrom::None) + || (_pollVotesFrom.current() != ReactionsNotifyFrom::None); +} + +rpl::producer ReactionsNotifySettings::enabled() const { + return rpl::combine( + _messagesFrom.value(), + _pollVotesFrom.value() + ) | rpl::map([]( + ReactionsNotifyFrom messages, + ReactionsNotifyFrom pollVotes) { + return (messages != ReactionsNotifyFrom::None) + || (pollVotes != ReactionsNotifyFrom::None); + }) | rpl::distinct_until_changed(); +} + +void ReactionsNotifySettings::apply( + const MTPReactionsNotifySettings &settings) { + const auto &data = settings.data(); + const auto messages = data.vmessages_notify_from(); + const auto stories = data.vstories_notify_from(); + const auto pollVotes = data.vpoll_votes_notify_from(); + _messagesFrom = messages + ? ParseFrom(*messages) + : ReactionsNotifyFrom::None; + _storiesFrom = stories + ? ParseFrom(*stories) + : ReactionsNotifyFrom::None; + _pollVotesFrom = pollVotes + ? ParseFrom(*pollVotes) + : ReactionsNotifyFrom::None; + _showPreviews = mtpIsTrue(data.vshow_previews()); +} + +void ReactionsNotifySettings::save() { + using Flag = MTPDreactionsNotifySettings::Flag; + const auto messages = _messagesFrom.current(); + const auto stories = _storiesFrom.current(); + const auto pollVotes = _pollVotesFrom.current(); + const auto previews = _showPreviews.current(); + const auto flags = Flag() + | ((messages != ReactionsNotifyFrom::None) + ? Flag::f_messages_notify_from + : Flag()) + | ((stories != ReactionsNotifyFrom::None) + ? Flag::f_stories_notify_from + : Flag()) + | ((pollVotes != ReactionsNotifyFrom::None) + ? Flag::f_poll_votes_notify_from + : Flag()); + _api.request(base::take(_requestId)).cancel(); + _requestId = _api.request(MTPaccount_SetReactionsNotifySettings( + MTP_reactionsNotifySettings( + MTP_flags(flags), + ((messages != ReactionsNotifyFrom::None) + ? SerializeFrom(messages) + : MTPReactionNotificationsFrom()), + ((stories != ReactionsNotifyFrom::None) + ? SerializeFrom(stories) + : MTPReactionNotificationsFrom()), + ((pollVotes != ReactionsNotifyFrom::None) + ? SerializeFrom(pollVotes) + : MTPReactionNotificationsFrom()), + MTP_notificationSoundDefault(), + MTP_bool(previews)) + )).done([=](const MTPReactionsNotifySettings &result) { + _requestId = 0; + apply(result); + }).fail([=] { + _requestId = 0; + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_reactions_notify_settings.h b/Telegram/SourceFiles/api/api_reactions_notify_settings.h new file mode 100644 index 00000000000000..d29a1a67870001 --- /dev/null +++ b/Telegram/SourceFiles/api/api_reactions_notify_settings.h @@ -0,0 +1,65 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/sender.h" + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +enum class ReactionsNotifyFrom : uchar { + None, + Contacts, + All, +}; + +class ReactionsNotifySettings final { +public: + explicit ReactionsNotifySettings(not_null api); + + void reload(Fn callback = nullptr); + + void updateMessagesFrom(ReactionsNotifyFrom value); + void updatePollVotesFrom(ReactionsNotifyFrom value); + void setAllFrom(ReactionsNotifyFrom value); + void updateShowPreviews(bool value); + + [[nodiscard]] ReactionsNotifyFrom messagesFromCurrent() const; + [[nodiscard]] rpl::producer messagesFrom() const; + [[nodiscard]] ReactionsNotifyFrom pollVotesFromCurrent() const; + [[nodiscard]] rpl::producer pollVotesFrom() const; + [[nodiscard]] bool showPreviewsCurrent() const; + [[nodiscard]] rpl::producer showPreviews() const; + + [[nodiscard]] bool enabledCurrent() const; + [[nodiscard]] rpl::producer enabled() const; + +private: + void apply(const MTPReactionsNotifySettings &settings); + void save(); + + const not_null _session; + MTP::Sender _api; + mtpRequestId _requestId = 0; + rpl::variable _messagesFrom + = ReactionsNotifyFrom::All; + rpl::variable _storiesFrom + = ReactionsNotifyFrom::All; + rpl::variable _pollVotesFrom + = ReactionsNotifyFrom::All; + rpl::variable _showPreviews = true; + std::vector> _callbacks; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_read_metrics.cpp b/Telegram/SourceFiles/api/api_read_metrics.cpp new file mode 100644 index 00000000000000..8d2f03c3eff3f7 --- /dev/null +++ b/Telegram/SourceFiles/api/api_read_metrics.cpp @@ -0,0 +1,73 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_read_metrics.h" + +#include "apiwrap.h" +#include "data/data_peer.h" + +namespace Api { +namespace { + +constexpr auto kSendTimeout = crl::time(5000); + +} // namespace + +ReadMetrics::ReadMetrics(not_null api) +: _api(&api->instance()) +, _timer([=] { send(); }) { +} + +void ReadMetrics::add( + not_null peer, + FinalizedReadMetric metric) { + _pending[peer].push_back(metric); + if (!_timer.isActive()) { + _timer.callOnce(kSendTimeout); + } +} + +void ReadMetrics::send() { + for (auto i = _pending.begin(); i != _pending.end();) { + if (_requests.contains(i->first)) { + ++i; + continue; + } + + auto metrics = QVector(); + metrics.reserve(i->second.size()); + for (const auto &m : i->second) { + metrics.push_back(MTP_inputMessageReadMetric( + MTP_int(m.msgId.bare), + MTP_long(m.viewId), + MTP_int(m.timeInViewMs), + MTP_int(m.activeTimeInViewMs), + MTP_int(m.heightToViewportRatioPermille), + MTP_int(m.seenRangeRatioPermille))); + } + const auto peer = i->first; + const auto finish = [=] { + _requests.erase(peer); + if (!_pending.empty() && !_timer.isActive()) { + _timer.callOnce(kSendTimeout); + } + }; + const auto requestId = _api.request(MTPmessages_ReportReadMetrics( + peer->input(), + MTP_vector(std::move(metrics)) + )).done([=](const MTPBool &) { + finish(); + }).fail([=](const MTP::Error &) { + finish(); + }).send(); + + _requests.emplace(peer, requestId); + i = _pending.erase(i); + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_read_metrics.h b/Telegram/SourceFiles/api/api_read_metrics.h new file mode 100644 index 00000000000000..dcac63db29e1bd --- /dev/null +++ b/Telegram/SourceFiles/api/api_read_metrics.h @@ -0,0 +1,45 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/sender.h" +#include "base/timer.h" + +class ApiWrap; +class PeerData; + +namespace Api { + +struct FinalizedReadMetric { + MsgId msgId = 0; + uint64 viewId = 0; + int timeInViewMs = 0; + int activeTimeInViewMs = 0; + int heightToViewportRatioPermille = 0; + int seenRangeRatioPermille = 0; +}; + +class ReadMetrics final { +public: + explicit ReadMetrics(not_null api); + + void add(not_null peer, FinalizedReadMetric metric); + +private: + void send(); + + MTP::Sender _api; + base::flat_map< + not_null, + std::vector> _pending; + base::flat_map, mtpRequestId> _requests; + base::Timer _timer; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index 309e2d240cbf04..62f2458d6c1467 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -8,7 +8,9 @@ For license and copyright information please follow this link: #include "api/api_report.h" #include "apiwrap.h" +#include "data/data_session.h" #include "data/data_peer.h" +#include "data/data_channel.h" #include "data/data_photo.h" #include "data/data_report.h" #include "data/data_user.h" @@ -40,29 +42,20 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) { } // namespace -void SendReport( +void SendPhotoReport( std::shared_ptr show, not_null peer, Ui::ReportReason reason, const QString &comment, - std::variant> data) { - auto done = [=] { + not_null photo) { + peer->session().api().request(MTPaccount_ReportProfilePhoto( + peer->input(), + photo->mtpInput(), + ReasonToTL(reason), + MTP_string(comment) + )).done([=] { show->showToast(tr::lng_report_thanks(tr::now)); - }; - v::match(data, [&](v::null_t) { - peer->session().api().request(MTPaccount_ReportPeer( - peer->input, - ReasonToTL(reason), - MTP_string(comment) - )).done(std::move(done)).send(); - }, [&](not_null photo) { - peer->session().api().request(MTPaccount_ReportProfilePhoto( - peer->input, - photo->mtpInput(), - ReasonToTL(reason), - MTP_string(comment) - )).done(std::move(done)).send(); - }); + }).send(); } auto CreateReportMessagesOrStoriesCallback( @@ -134,7 +127,7 @@ auto CreateReportMessagesOrStoriesCallback( if (!reportInput.stories.empty()) { state->requestId = peer->session().api().request( MTPstories_Report( - peer->input, + peer->input(), MTP_vector(apiIds), MTP_bytes(reportInput.optionId), MTP_string(reportInput.comment)) @@ -142,7 +135,7 @@ auto CreateReportMessagesOrStoriesCallback( } else { state->requestId = peer->session().api().request( MTPmessages_Report( - peer->input, + peer->input(), MTP_vector(apiIds), MTP_bytes(reportInput.optionId), MTP_string(reportInput.comment)) @@ -151,4 +144,57 @@ auto CreateReportMessagesOrStoriesCallback( }; } +ReactionReportCapabilities GetReactionReportCapabilities( + not_null group, + not_null participant) { + const auto channel = group->asMegagroup(); + return channel + ? ReactionReportCapabilities{ + .canReport = channel->isPublic() && !participant->isSelf(), + .canBan = channel->canRestrictParticipant(participant), + } + : ReactionReportCapabilities(); +} + +void ReportReaction( + std::shared_ptr show, + not_null group, + MsgId messageId, + not_null participant) { + group->session().api().request(MTPmessages_ReportReaction( + group->input(), + MTP_int(messageId.bare), + participant->input() + )).done([=] { + if (show) { + show->showToast(tr::lng_report_thanks(tr::now)); + } + }).send(); +} + +void ReportSpam( + not_null sender, + const MessageIdsList &ids) { + if (ids.empty()) { + return; + } + const auto peer = sender->owner().peer(ids.front().peer); + const auto channel = peer->asChannel(); + if (!channel) { + return; + } + + auto msgIds = QVector(); + msgIds.reserve(ids.size()); + for (const auto &fullId : ids) { + msgIds.push_back(MTP_int(fullId.msg)); + } + + sender->session().api().request(MTPchannels_ReportSpam( + channel->inputChannel(), + sender->input(), + MTP_vector(msgIds) + )).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_report.h b/Telegram/SourceFiles/api/api_report.h index f0c0320f3f9605..0b35c408dec63b 100644 --- a/Telegram/SourceFiles/api/api_report.h +++ b/Telegram/SourceFiles/api/api_report.h @@ -41,16 +41,35 @@ struct ReportResult final { bool successful = false; }; -void SendReport( +void SendPhotoReport( std::shared_ptr show, not_null peer, Ui::ReportReason reason, const QString &comment, - std::variant> data); + not_null photo); [[nodiscard]] auto CreateReportMessagesOrStoriesCallback( std::shared_ptr show, not_null peer) -> Fn)>; +struct ReactionReportCapabilities final { + bool canReport = false; + bool canBan = false; +}; + +[[nodiscard]] ReactionReportCapabilities GetReactionReportCapabilities( + not_null group, + not_null participant); + +void ReportReaction( + std::shared_ptr show, + not_null group, + MsgId messageId, + not_null participant); + +void ReportSpam( + not_null sender, + const MessageIdsList &ids); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_ringtones.cpp b/Telegram/SourceFiles/api/api_ringtones.cpp index 307ba580a775a9..203984ab69042f 100644 --- a/Telegram/SourceFiles/api/api_ringtones.cpp +++ b/Telegram/SourceFiles/api/api_ringtones.cpp @@ -66,7 +66,7 @@ Ringtones::Ringtones(not_null api) // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. _session->uploader().documentReady( - ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { + ) | rpl::on_next([=](const Storage::UploadedMedia &data) { ready(data.fullId, data.info.file); }, _session->lifetime()); }); diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index 93d28792652ae3..206520e201e3b3 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -137,7 +137,7 @@ void SendProgressManager::send(const Key &key, int progress) { MTP_flags(key.topMsgId ? MTPmessages_SetTyping::Flag::f_top_msg_id : MTPmessages_SetTyping::Flag(0)), - key.history->peer->input, + key.history->peer->input(), MTP_int(key.topMsgId), action )).done([=](const MTPBool &result, mtpRequestId requestId) { diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index a3003bab43a298..92166187b0018c 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -95,10 +95,15 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name() : QString(); - + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { flags |= MessageFlag::ShortcutMessage; @@ -107,10 +112,17 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } if (action.options.invertCaption) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } auto &histories = history->owner().histories(); histories.sendPreparedMessage( @@ -119,7 +131,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), std::move(inputMedia), MTPstring(), @@ -127,9 +139,12 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { MTPReplyMarkup(), MTPvector(), MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + SuggestToMTP(action.options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId); @@ -160,7 +175,7 @@ void SendExistingMedia( ? (*localMessageId) : session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); - const auto &action = message.action; + auto &action = message.action; auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMedia::Flags(0); @@ -190,10 +205,15 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_entities; } const auto captionText = caption.text; - + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { flags |= MessageFlag::ShortcutMessage; @@ -202,10 +222,17 @@ void SendExistingMedia( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } if (action.options.invertCaption) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } session->data().registerMessageRandomId(randomId, newId); @@ -215,9 +242,13 @@ void SendExistingMedia( .from = NewMessageFromId(action), .replyTo = action.replyTo, .date = NewMessageDate(action.options), + .scheduleRepeatPeriod = action.options.scheduleRepeatPeriod, .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, + .suggest = HistoryMessageSuggestInfo(action.options), + .mediaSpoiler = action.options.mediaSpoiler, }, media, caption); const auto performRequest = [=](const auto &repeatRequest) -> void { @@ -230,7 +261,7 @@ void SendExistingMedia( randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), inputMedia(), MTP_string(captionText), @@ -238,9 +269,12 @@ void SendExistingMedia( MTPReplyMarkup(), sentEntities, MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + SuggestToMTP(action.options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { if (error.code() == 400 @@ -270,9 +304,13 @@ void SendExistingDocument( std::optional localMessageId) { const auto inputMedia = [=] { return MTP_inputMediaDocument( - MTP_flags(0), + MTP_flags(message.action.options.mediaSpoiler + ? MTPDinputMediaDocument::Flag::f_spoiler + : MTPDinputMediaDocument::Flags(0)), document->mtpInput(), + MTPInputPhoto(), // video_cover MTPint(), // ttl_seconds + MTPint(), // video_timestamp MTPstring()); // query }; SendExistingMedia( @@ -295,7 +333,8 @@ void SendExistingPhoto( return MTP_inputMediaPhoto( MTP_flags(0), photo->mtpInput(), - MTPint()); + MTPint(), // ttl_seconds + MTPInputDocument()); // video }; SendExistingMedia( std::move(message), @@ -338,8 +377,7 @@ bool SendDice(MessageToSend &message) { message.action.clearDraft = false; message.action.generateLocal = true; - - const auto &action = message.action; + auto &action = message.action; api->sendAction(action); const auto newId = FullMsgId( @@ -366,6 +404,9 @@ bool SendDice(MessageToSend &message) { if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { flags |= MessageFlag::ShortcutMessage; @@ -374,42 +415,75 @@ bool SendDice(MessageToSend &message) { if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } if (action.options.invertCaption) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; } + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } session->data().registerMessageRandomId(randomId, newId); + auto seed = QByteArray(32, Qt::Uninitialized); + base::RandomFill(bytes::make_detached_span(seed)); + const auto stake = action.options.stakeSeedHash.isEmpty() + ? 0 + : action.options.stakeNanoTon; history->addNewLocalMessage({ .id = newId.msg, .flags = flags, .from = NewMessageFromId(action), .replyTo = action.replyTo, .date = NewMessageDate(action.options), + .scheduleRepeatPeriod = action.options.scheduleRepeatPeriod, .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, + .suggest = HistoryMessageSuggestInfo(action.options), }, TextWithEntities(), MTP_messageMediaDice( + MTP_flags(stake + ? MTPDmessageMediaDice::Flag::f_game_outcome + : MTPDmessageMediaDice::Flag()), MTP_int(0), - MTP_string(emoji))); + MTP_string(emoji), + MTP_messages_emojiGameOutcome( + MTP_bytes(seed), + MTP_long(stake), + MTP_long(0)))); histories.sendPreparedMessage( history, action.replyTo, randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), - MTP_inputMediaDice(MTP_string(emoji)), + (stake + ? MTP_inputMediaStakeDice( + MTP_bytes(action.options.stakeSeedHash), + MTP_long(stake), + MTP_bytes(seed)) + : MTP_inputMediaDice(MTP_string(emoji))), MTP_string(), MTP_long(randomId), MTPReplyMarkup(), MTP_vector(), MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(session, action.options.shortcutId), - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + SuggestToMTP(action.options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId, newId); @@ -456,6 +530,7 @@ void SendConfirmedFile( not_null session, const std::shared_ptr &file) { const auto isEditing = (file->type != SendMediaType::Audio) + && (file->type != SendMediaType::Round) && (file->to.replaceMediaOf != 0); const auto newId = FullMsgId( file->to.peer, @@ -525,7 +600,8 @@ void SendConfirmedFile( // Shortcut messages have no 'edited' badge. flags |= MessageFlag::HideEdited; } - if (file->type == SendMediaType::Audio) { + if (file->type == SendMediaType::Audio + || file->type == SendMediaType::Round) { if (!peer->isChannel() || peer->isMegagroup()) { flags |= MessageFlag::MediaIsUnread; } @@ -540,39 +616,44 @@ void SendConfirmedFile( MTP_flags(Flag::f_photo | (file->spoiler ? Flag::f_spoiler : Flag())), file->photo, - MTPint()); + MTPint(), // ttl_seconds + MTPDocument()); // video } else if (file->type == SendMediaType::File) { using Flag = MTPDmessageMediaDocument::Flag; return MTP_messageMediaDocument( MTP_flags(Flag::f_document - | (file->spoiler ? Flag::f_spoiler : Flag())), + | (file->spoiler ? Flag::f_spoiler : Flag()) + | (file->videoCover ? Flag::f_video_cover : Flag())), file->document, MTPVector(), // alt_documents + file->videoCover ? file->videoCover->photo : MTPPhoto(), + MTPint(), // video_timestamp MTPint()); } else if (file->type == SendMediaType::Audio) { const auto ttlSeconds = file->to.options.ttlSeconds; - const auto isVoice = [&] { - return file->document.match([](const MTPDdocumentEmpty &d) { - return false; - }, [](const MTPDdocument &d) { - return ranges::any_of(d.vattributes().v, [&]( - const MTPDocumentAttribute &attribute) { - using Att = MTPDdocumentAttributeAudio; - return attribute.match([](const Att &data) -> bool { - return data.vflags().v & Att::Flag::f_voice; - }, [](const auto &) { - return false; - }); - }); - }); - }(); using Flag = MTPDmessageMediaDocument::Flag; return MTP_messageMediaDocument( MTP_flags(Flag::f_document - | (isVoice ? Flag::f_voice : Flag()) - | (ttlSeconds ? Flag::f_ttl_seconds : Flag())), + | Flag::f_voice + | (ttlSeconds ? Flag::f_ttl_seconds : Flag()) + | (file->videoCover ? Flag::f_video_cover : Flag())), + file->document, + MTPVector(), // alt_documents + file->videoCover ? file->videoCover->photo : MTPPhoto(), + MTPint(), // video_timestamp + MTP_int(ttlSeconds)); + } else if (file->type == SendMediaType::Round) { + using Flag = MTPDmessageMediaDocument::Flag; + const auto ttlSeconds = file->to.options.ttlSeconds; + return MTP_messageMediaDocument( + MTP_flags(Flag::f_document + | Flag::f_round + | (ttlSeconds ? Flag::f_ttl_seconds : Flag()) + | (file->spoiler ? Flag::f_spoiler : Flag())), file->document, MTPVector(), // alt_documents + MTPPhoto(), // video_cover + MTPint(), // video_timestamp MTP_int(ttlSeconds)); } else { Unexpected("Type in sendFilesConfirmed."); @@ -592,6 +673,7 @@ void SendConfirmedFile( edition.useSameMarkup = true; edition.useSameReplies = true; edition.useSameReactions = true; + edition.useSameSuggest = true; edition.savePreviousMedia = true; itemToEdit->applyEdition(std::move(edition)); } else { @@ -601,10 +683,15 @@ void SendConfirmedFile( .from = NewMessageFromId(action), .replyTo = file->to.replyTo, .date = NewMessageDate(file->to.options), + .scheduleRepeatPeriod = file->to.options.scheduleRepeatPeriod, .shortcutId = file->to.options.shortcutId, + .starsPaid = std::min( + history->peer->starsPerMessageChecked(), + file->to.options.starsApproved), .postAuthor = NewMessagePostAuthor(action), .groupedId = groupId, .effectId = file->to.options.effectId, + .suggest = HistoryMessageSuggestInfo(file->to.options), }, caption, media); } diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp index 31f83f5ebae907..e1e25950e8e441 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -25,7 +25,7 @@ SensitiveContent::SensitiveContent(not_null api) } void SensitiveContent::preload() { - if (!_loaded) { + if (!_loaded && !_loadRequestId) { reload(); } } @@ -37,7 +37,6 @@ void SensitiveContent::reload(bool force) { } return; } - _loaded = true; _loadRequestId = _api.request(MTPaccount_GetContentSettings( )).done([=](const MTPaccount_ContentSettings &result) { _loadRequestId = 0; @@ -50,6 +49,10 @@ void SensitiveContent::reload(bool force) { _enabled = enabled; _canChange = canChange; } + if (!_loaded) { + _loaded = true; + _loadedChanged.fire({}); + } if (base::take(_appConfigReloadForce) || changed) { _appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout); } @@ -61,6 +64,19 @@ void SensitiveContent::reload(bool force) { }).send(); } +bool SensitiveContent::loaded() const { + return _loaded; +} + +rpl::producer SensitiveContent::loadedValue() const { + if (_loaded) { + return rpl::single(true); + } + return rpl::single(false) | rpl::then( + _loadedChanged.events() | rpl::map_to(true) + ); +} + bool SensitiveContent::enabledCurrent() const { return _enabled.current(); } @@ -69,6 +85,10 @@ rpl::producer SensitiveContent::enabled() const { return _enabled.value(); } +bool SensitiveContent::canChangeCurrent() const { + return _canChange.current(); +} + rpl::producer SensitiveContent::canChange() const { return _canChange.value(); } diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h index 576bf275f3daef..e35f8cbf463882 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.h +++ b/Telegram/SourceFiles/api/api_sensitive_content.h @@ -26,12 +26,16 @@ class SensitiveContent final { void reload(bool force = false); void update(bool enabled); + [[nodiscard]] bool loaded() const; + [[nodiscard]] rpl::producer loadedValue() const; [[nodiscard]] bool enabledCurrent() const; [[nodiscard]] rpl::producer enabled() const; + [[nodiscard]] bool canChangeCurrent() const; [[nodiscard]] rpl::producer canChange() const; private: const not_null _session; + rpl::event_stream<> _loadedChanged; MTP::Sender _api; mtpRequestId _loadRequestId = 0; mtpRequestId _saveRequestId = 0; diff --git a/Telegram/SourceFiles/api/api_single_message_search.cpp b/Telegram/SourceFiles/api/api_single_message_search.cpp index 0875091bc7cfd4..d95ff3227a04eb 100644 --- a/Telegram/SourceFiles/api/api_single_message_search.cpp +++ b/Telegram/SourceFiles/api/api_single_message_search.cpp @@ -99,7 +99,7 @@ std::optional SingleMessageSearch::performLookupByChannel( ready(); }; _requestId = _session->api().request(MTPchannels_GetMessages( - channel->inputChannel, + channel->inputChannel(), MTP_vector(1, MTP_inputMessageID(MTP_int(postId))) )).done([=](const MTPmessages_Messages &result) { const auto received = Api::ParseSearchResult( @@ -181,7 +181,9 @@ std::optional SingleMessageSearch::performLookupByUsername( ready(); }; _requestId = _session->api().request(MTPcontacts_ResolveUsername( - MTP_string(username) + MTP_flags(0), + MTP_string(username), + MTP_string() )).done([=](const MTPcontacts_ResolvedPeer &result) { result.match([&](const MTPDcontacts_resolvedPeer &data) { _session->data().processUsers(data.vusers()); diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index c960aca3335aba..ad0404dbea07fc 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "api/api_statistics.h" +#include "api/api_credits_history_entry.h" #include "api/api_statistics_data_deserialize.h" #include "apiwrap.h" #include "base/unixtime.h" @@ -14,6 +15,7 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_story.h" +#include "data/data_user.h" #include "history/history.h" #include "main/main_session.h" @@ -208,7 +210,7 @@ rpl::producer Statistics::request() { if (!channel()->isMegagroup()) { makeRequest(MTPstats_GetBroadcastStats( MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), - channel()->inputChannel + channel()->inputChannel() )).done([=](const MTPstats_BroadcastStats &result) { _channelStats = ChannelStatisticsFromTL(result.data()); consumer.put_done(); @@ -218,7 +220,7 @@ rpl::producer Statistics::request() { } else { makeRequest(MTPstats_GetMegagroupStats( MTP_flags(MTPstats_GetMegagroupStats::Flags(0)), - channel()->inputChannel + channel()->inputChannel() )).done([=](const MTPstats_MegagroupStats &result) { const auto &data = result.data(); _supergroupStats = SupergroupStatisticsFromTL(data); @@ -312,7 +314,7 @@ void PublicForwards::request( const auto msgId = IdFromMessage(message); const auto peerId = PeerFromMessage(message); const auto lastDate = DateFromMessage(message); - if (const auto peer = owner.peerLoaded(peerId)) { + if (owner.peerLoaded(peerId)) { if (!lastDate) { return; } @@ -341,22 +343,26 @@ void PublicForwards::request( .token = nextToken, }); }; + const auto processFail = [=] { + _requestId = 0; + done({}); + }; constexpr auto kLimit = tl::make_int(100); if (_fullId.messageId) { _requestId = makeRequest(MTPstats_GetMessagePublicForwards( - channel->inputChannel, + channel->inputChannel(), MTP_int(_fullId.messageId.msg), MTP_string(token), kLimit - )).done(processResult).fail([=] { _requestId = 0; }).send(); + )).done(processResult).fail(processFail).send(); } else if (_fullId.storyId) { _requestId = makeRequest(MTPstats_GetStoryPublicForwards( - channel->input, + channel->input(), MTP_int(_fullId.storyId.story), MTP_string(token), kLimit - )).done(processResult).fail([=] { _requestId = 0; }).send(); + )).done(processResult).fail(processFail).send(); } } @@ -381,7 +387,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const { } void MessageStatistics::request(Fn done) { - if (channel()->isMegagroup()) { + if (channel()->isMegagroup() && !_storyId) { return; } const auto requestFirstPublicForwards = [=]( @@ -407,7 +413,7 @@ void MessageStatistics::request(Fn done) { const Data::StatisticalGraph &messageGraph, const Data::StatisticalGraph &reactionsGraph) { api().request(MTPchannels_GetMessages( - channel()->inputChannel, + channel()->inputChannel(), MTP_vector( 1, MTP_inputMessageID(MTP_int(_fullId.msg)))) @@ -462,7 +468,7 @@ void MessageStatistics::request(Fn done) { const Data::StatisticalGraph &messageGraph, const Data::StatisticalGraph &reactionsGraph) { api().request(MTPstories_GetStoriesByID( - channel()->input, + channel()->input(), MTP_vector(1, MTP_int(_storyId.story))) ).done([=](const MTPstories_Stories &result) { const auto &storyItem = result.data().vstories().v.front(); @@ -493,7 +499,7 @@ void MessageStatistics::request(Fn done) { if (_storyId) { makeRequest(MTPstats_GetStoryStats( MTP_flags(MTPstats_GetStoryStats::Flags(0)), - channel()->input, + channel()->input(), MTP_int(_storyId.story) )).done([=](const MTPstats_StoryStats &result) { const auto &data = result.data(); @@ -506,7 +512,7 @@ void MessageStatistics::request(Fn done) { } else { makeRequest(MTPstats_GetMessageStats( MTP_flags(MTPstats_GetMessageStats::Flags(0)), - channel()->inputChannel, + channel()->inputChannel(), MTP_int(_fullId.msg.bare) )).done([=](const MTPstats_MessageStats &result) { const auto &data = result.data(); @@ -533,7 +539,7 @@ rpl::producer Boosts::request() { } _api.request(MTPpremium_GetBoostsStatus( - _peer->input + _peer->input() )).done([=](const MTPpremium_BoostsStatus &result) { const auto &data = result.data(); channel->updateLevelHint(data.vlevel().v); @@ -619,7 +625,7 @@ void Boosts::requestBoosts( gifts ? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts) : MTP_flags(0), - _peer->input, + _peer->input(), MTP_string(token.next), token.next.isEmpty() ? kTlFirstSlice : kTlLimit )).done([=](const MTPpremium_BoostsList &result) { @@ -681,45 +687,70 @@ Data::BoostStatus Boosts::boostStatus() const { return _boostStatus; } -ChannelEarnStatistics::ChannelEarnStatistics(not_null channel) -: StatisticsRequestSender(channel) { +EarnStatistics::EarnStatistics(not_null peer) +: StatisticsRequestSender(peer) +, _isUser(peer->isUser()) { } -rpl::producer ChannelEarnStatistics::request() { +rpl::producer EarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); - makeRequest(MTPstats_GetBroadcastRevenueStats( - MTP_flags(0), - channel()->inputChannel - )).done([=](const MTPstats_BroadcastRevenueStats &result) { + api().request(MTPpayments_GetStarsRevenueStats( + MTP_flags(MTPpayments_getStarsRevenueStats::Flag::f_ton), + (_isUser ? user()->input() : channel()->input()) + )).done([=](const MTPpayments_StarsRevenueStats &result) { const auto &data = result.data(); - const auto &balances = data.vbalances().data(); + const auto &balances = data.vstatus().data(); + const auto amount = [](const auto &a) { + return CreditsAmountFromTL(a); + }; _data = Data::EarnStatistics{ - .topHoursGraph = StatisticalGraphFromTL( - data.vtop_hours_graph()), + .topHoursGraph = data.vtop_hours_graph() + ? StatisticalGraphFromTL(*data.vtop_hours_graph()) + : Data::StatisticalGraph(), .revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()), - .currentBalance = balances.vcurrent_balance().v, - .availableBalance = balances.vavailable_balance().v, - .overallRevenue = balances.voverall_revenue().v, + .currentBalance = amount(balances.vcurrent_balance()), + .availableBalance = amount(balances.vavailable_balance()), + .overallRevenue = amount(balances.voverall_revenue()), .usdRate = data.vusd_rate().v, }; requestHistory({}, [=](Data::EarnHistorySlice &&slice) { _data.firstHistorySlice = std::move(slice); - api().request( - MTPchannels_GetFullChannel(channel()->inputChannel) - ).done([=](const MTPmessages_ChatFull &result) { - result.data().vfull_chat().match([&]( - const MTPDchannelFull &d) { - _data.switchedOff = d.is_restricted_sponsored(); - }, [](const auto &) { - }); + if (!_isUser) { + api().request( + MTPchannels_GetFullChannel(channel()->inputChannel()) + ).done([=](const MTPmessages_ChatFull &result) { + result.data().vfull_chat().match([&]( + const MTPDchannelFull &d) { + // Not a full ApplyChannelUpdate() here: that would + // re-trigger an EarnStatistics request and loop. We + // only refresh the revenue-visibility flags that + // gate this screen, which may be stale if the + // program was enabled after the last full load. + using Flag = ChannelDataFlag; + const auto channel = this->channel(); + channel->setFlags((channel->flags() + & ~(Flag::CanViewRevenue + | Flag::CanViewCreditsRevenue)) + | (d.is_can_view_revenue() + ? Flag::CanViewRevenue + : Flag()) + | (d.is_can_view_stars_revenue() + ? Flag::CanViewCreditsRevenue + : Flag())); + _data.switchedOff = d.is_restricted_sponsored(); + }, [](const auto &) { + }); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + } else { consumer.put_done(); - }).fail([=](const MTP::Error &error) { - consumer.put_error_copy(error.type()); - }).send(); + } }); }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); @@ -729,68 +760,41 @@ rpl::producer ChannelEarnStatistics::request() { }; } -void ChannelEarnStatistics::requestHistory( +void EarnStatistics::requestHistory( const Data::EarnHistorySlice::OffsetToken &token, Fn done) { if (_requestId) { return; } + constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); - _requestId = api().request(MTPstats_GetBroadcastRevenueTransactions( - channel()->inputChannel, - MTP_int(token), - (!token) ? kTlFirstSlice : kTlLimit - )).done([=](const MTPstats_BroadcastRevenueTransactions &result) { + + _requestId = api().request(MTPpayments_GetStarsTransactions( + MTP_flags(MTPpayments_getStarsTransactions::Flag::f_ton), + MTP_string(), // Subscription ID. + (_isUser ? user()->input() : channel()->input()), + MTP_string(token), + token.isEmpty() ? kTlFirstSlice : kTlLimit + )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; - const auto &tlTransactions = result.data().vtransactions().v; - - auto list = std::vector(); - list.reserve(tlTransactions.size()); - for (const auto &tlTransaction : tlTransactions) { - list.push_back(tlTransaction.match([&]( - const MTPDbroadcastRevenueTransactionProceeds &d) { - return Data::EarnHistoryEntry{ - .type = Data::EarnHistoryEntry::Type::In, - .amount = d.vamount().v, - .date = base::unixtime::parse(d.vfrom_date().v), - .dateTo = base::unixtime::parse(d.vto_date().v), - }; - }, [&](const MTPDbroadcastRevenueTransactionWithdrawal &d) { - return Data::EarnHistoryEntry{ - .type = Data::EarnHistoryEntry::Type::Out, - .status = d.is_pending() - ? Data::EarnHistoryEntry::Status::Pending - : d.is_failed() - ? Data::EarnHistoryEntry::Status::Failed - : Data::EarnHistoryEntry::Status::Success, - .amount = (std::numeric_limits::max() - - d.vamount().v - + 1), - .date = base::unixtime::parse(d.vdate().v), - // .provider = qs(d.vprovider()), - .successDate = d.vtransaction_date() - ? base::unixtime::parse(d.vtransaction_date()->v) - : QDateTime(), - .successLink = d.vtransaction_url() - ? qs(*d.vtransaction_url()) - : QString(), - }; - }, [&](const MTPDbroadcastRevenueTransactionRefund &d) { - return Data::EarnHistoryEntry{ - .type = Data::EarnHistoryEntry::Type::Return, - .amount = d.vamount().v, - .date = base::unixtime::parse(d.vdate().v), - // .provider = qs(d.vprovider()), - }; - })); - } - const auto nextToken = token + tlTransactions.size(); + const auto nextToken = result.data().vnext_offset().value_or_empty(); + + const auto tlTransactions + = result.data().vhistory().value_or_empty(); + + const auto peer = _isUser ? (PeerData*)user() : (PeerData*)channel(); + auto list = ranges::views::all( + tlTransactions + ) | ranges::views::transform([=](const auto &d) { + return CreditsHistoryEntryFromTL(d, peer); + }) | ranges::to_vector; done(Data::EarnHistorySlice{ .list = std::move(list), - .total = result.data().vcount().v, - .allLoaded = (result.data().vcount().v == nextToken), + .total = int(tlTransactions.size()), + // .total = result.data().vcount().v, + .allLoaded = nextToken.isEmpty(), .token = Data::EarnHistorySlice::OffsetToken(nextToken), }); }).fail([=] { @@ -799,7 +803,7 @@ void ChannelEarnStatistics::requestHistory( }).send(); } -Data::EarnStatistics ChannelEarnStatistics::data() const { +Data::EarnStatistics EarnStatistics::data() const { return _data; } diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h index 213ab92933b6bb..b60dd3d28ff5c5 100644 --- a/Telegram/SourceFiles/api/api_statistics.h +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -75,13 +75,11 @@ class MessageStatistics final : public StatisticsRequestSender { Data::PublicForwardsSlice _firstSlice; - mtpRequestId _requestId = 0; - }; -class ChannelEarnStatistics final : public StatisticsRequestSender { +class EarnStatistics final : public StatisticsRequestSender { public: - explicit ChannelEarnStatistics(not_null channel); + explicit EarnStatistics(not_null peer); [[nodiscard]] rpl::producer request(); void requestHistory( @@ -94,6 +92,7 @@ class ChannelEarnStatistics final : public StatisticsRequestSender { static constexpr auto kLimit = int(10); private: + const bool _isUser = false; Data::EarnStatistics _data; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/api/api_stickers_creator.cpp b/Telegram/SourceFiles/api/api_stickers_creator.cpp new file mode 100644 index 00000000000000..7c387276705906 --- /dev/null +++ b/Telegram/SourceFiles/api/api_stickers_creator.cpp @@ -0,0 +1,557 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_stickers_creator.h" + +#include "apiwrap.h" +#include "base/random.h" +#include "base/unixtime.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_document.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/stickers/data_stickers.h" +#include "data/stickers/data_stickers_set.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "menu/menu_action_with_thumbnail.h" +#include "storage/file_upload.h" +#include "storage/localimageloader.h" +#include "styles/style_menu_icons.h" +#include "styles/style_widgets.h" +#include "ui/dynamic_thumbnails.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_common.h" +#include "ui/widgets/popup_menu.h" + +namespace Api { +namespace { + +constexpr auto kStickerSide = 512; + +[[nodiscard]] int SideForType(Data::StickersType type) { + return (type == Data::StickersType::Emoji) + ? kEmojiStickerSideMax + : kStickerSide; +} + +[[nodiscard]] MTPInputStickerSetItem InputItem( + const MTPInputDocument &document, + const QString &emoji) { + return MTP_inputStickerSetItem( + MTP_flags(0), + document, + MTP_string(emoji), + MTPMaskCoords(), + MTPstring()); +} + +[[nodiscard]] std::shared_ptr PrepareStickerWebp( + MTP::DcId dcId, + DocumentId id, + const QByteArray &bytes, + Data::StickersType type) { + const auto side = SideForType(type); + const auto filename = (type == Data::StickersType::Emoji) + ? u"emoji.webp"_q + : u"sticker.webp"_q; + auto attributes = QVector( + 1, + MTP_documentAttributeFilename(MTP_string(filename))); + attributes.push_back(MTP_documentAttributeImageSize( + MTP_int(side), + MTP_int(side))); + + auto result = MakePreparedFile({ + .id = id, + .type = SendMediaType::File, + }); + result->filename = filename; + result->filemime = u"image/webp"_q; + result->content = bytes; + result->filesize = bytes.size(); + result->setFileData(bytes); + result->document = MTP_document( + MTP_flags(0), + MTP_long(id), + MTP_long(0), + MTP_bytes(), + MTP_int(base::unixtime::now()), + MTP_string("image/webp"), + MTP_long(bytes.size()), + MTP_vector(), + MTPVector(), + MTP_int(dcId), + MTP_vector(std::move(attributes))); + return result; +} + +void FeedSetIfFull( + not_null session, + const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &data) { + session->data().stickers().feedSetFull(data); + session->data().stickers().notifyUpdated( + Data::StickersType::Stickers); + }, [](const auto &) { + }); +} + +template +void EnumerateOwnedSets( + not_null session, + Data::StickersType type, + Callback &&callback) { + const auto &stickers = session->data().stickers(); + const auto &sets = stickers.sets(); + const auto &order = (type == Data::StickersType::Emoji) + ? stickers.emojiSetsOrder() + : stickers.setsOrder(); + for (const auto setId : order) { + const auto it = sets.find(setId); + if (it == sets.end()) { + continue; + } + const auto set = it->second.get(); + if (!(set->flags & Data::StickersSetFlag::AmCreator) + || (set->type() != type)) { + continue; + } + using namespace Data; + if constexpr (std::is_same_v< + bool, + std::invoke_result_t>>) { + if (!callback(set)) { + return; + } + } else { + callback(set); + } + } +} + +void FillChooseOwnedSetMenu( + not_null menu, + std::shared_ptr show, + not_null document, + Data::StickersType type) { + const auto session = &show->session(); + const auto emoji = StickerEmojiOrDefault(document); + const auto isEmoji = (type == Data::StickersType::Emoji); + const auto maxCount = isEmoji + ? kEmojiInOwnedSetMax + : kStickersInOwnedSetMax; + const auto fullMessage = isEmoji + ? tr::lng_emoji_set_is_full + : tr::lng_stickers_set_is_full; + const auto addedMessage = isEmoji + ? tr::lng_emoji_added + : tr::lng_stickers_create_added; + const auto alreadyMessage = isEmoji + ? tr::lng_emoji_already_in_set + : tr::lng_stickers_already_in_set; + const auto failToast = [=](QString err) { + show->showToast(err.isEmpty() + ? tr::lng_attach_failed(tr::now) + : err); + }; + EnumerateOwnedSets(session, type, [&](not_null set) { + const auto identifier = set->identifier(); + const auto coverDocument = set->lookupThumbnailDocument(); + auto thumbnail = coverDocument + ? Ui::MakeDocumentThumbnailFit( + coverDocument, + Data::FileOriginStickerSet(set->id, set->accessHash)) + : nullptr; + const auto targetSetId = set->id; + const auto handler = crl::guard(session, [=] { + const auto &map = session->data().stickers().sets(); + const auto i = map.find(targetSetId); + if (i != map.end() && i->second->count >= maxCount) { + show->showToast(fullMessage(tr::now)); + return; + } + const auto oldCount = (i != map.end()) + ? i->second->count + : 0; + AddExistingStickerToSet( + session, + identifier, + document, + emoji, + crl::guard(session, [=](MTPmessages_StickerSet) { + const auto &map = session->data().stickers().sets(); + const auto i = map.find(targetSetId); + const auto newCount = (i != map.end()) + ? i->second->count + : oldCount; + show->showToast(newCount > oldCount + ? addedMessage(tr::now) + : alreadyMessage(tr::now)); + }), + crl::guard(session, failToast)); + }); + const auto rawAction = Ui::Menu::CreateAction( + menu.get(), + set->title, + handler); + auto item = base::make_unique_q( + menu->menu(), + menu->menu()->st(), + rawAction, + std::move(thumbnail), + st::menuIconStickerAdd.width()); + menu->addAction(std::move(item)); + }); +} + +} // namespace + +void AddExistingStickerToSet( + not_null session, + const StickerSetIdentifier &set, + not_null document, + const QString &emoji, + Fn done, + Fn fail) { + session->api().request(MTPstickers_AddStickerToSet( + Data::InputStickerSet(set), + InputItem(document->mtpInput(), emoji)) + ).done([=](const MTPmessages_StickerSet &result) { + FeedSetIfFull(session, result); + if (done) { + done(result); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).handleFloodErrors().send(); +} + +QString StickerEmojiOrDefault(not_null document) { + if (const auto sticker = document->sticker()) { + if (!sticker->alt.isEmpty()) { + return sticker->alt; + } + } + return QString::fromUtf8("\xF0\x9F\x99\x82"); +} + +bool HasOwnedStickerSets(not_null session) { + auto found = false; + EnumerateOwnedSets( + session, + Data::StickersType::Stickers, + [&](not_null) { + found = true; + return false; + }); + return found; +} + +bool HasOwnedEmojiSets(not_null session) { + auto found = false; + EnumerateOwnedSets( + session, + Data::StickersType::Emoji, + [&](not_null) { + found = true; + return false; + }); + return found; +} + +void FillChooseStickerSetMenu( + not_null menu, + std::shared_ptr show, + not_null document) { + using namespace Data; + FillChooseOwnedSetMenu(menu, show, document, StickersType::Stickers); +} + +void FillChooseEmojiSetMenu( + not_null menu, + std::shared_ptr show, + not_null document) { + FillChooseOwnedSetMenu(menu, show, document, Data::StickersType::Emoji); +} + +void AddAddToStickerSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document) { + const auto session = &show->session(); + if (!HasOwnedStickerSets(session)) { + return; + } + addAction({ + .text = tr::lng_stickers_add_to_set(tr::now), + .icon = &st::menuIconStickerAdd, + .fillSubmenu = [show, document](not_null submenu) { + FillChooseStickerSetMenu(submenu, show, document); + }, + .submenuSt = &st::popupMenuWithIcons, + }); +} + +void AddAddToEmojiSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document) { + const auto session = &show->session(); + if (!HasOwnedEmojiSets(session)) { + return; + } + addAction({ + .text = tr::lng_emoji_add_to_set(tr::now), + .icon = &st::menuIconEmoji, + .fillSubmenu = [show, document](not_null submenu) { + FillChooseEmojiSetMenu(submenu, show, document); + }, + .submenuSt = &st::popupMenuWithIcons, + }); +} + +void AddAddToOwnedSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document) { + const auto sticker = document->sticker(); + const auto isLottie = sticker && sticker->isLottie(); + const auto size = document->dimensions; + const auto fitsEmoji = !size.isEmpty() + && (size.width() <= kEmojiStickerSideMax) + && (size.height() <= kEmojiStickerSideMax); + if (isLottie || !fitsEmoji) { + AddAddToStickerSetAction(addAction, show, document); + } + if (isLottie || fitsEmoji) { + AddAddToEmojiSetAction(addAction, std::move(show), document); + } +} + +void DeleteStickerSet( + not_null session, + const StickerSetIdentifier &set, + Fn done, + Fn fail) { + session->api().request(MTPstickers_DeleteStickerSet( + Data::InputStickerSet(set)) + ).done([=] { + session->data().stickers().notifyUpdated( + Data::StickersType::Stickers); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + +StickerUpload::StickerUpload( + not_null session, + StickerSetIdentifier set, + QByteArray webpBytes, + QString emoji, + Data::StickersType type) +: _session(session) +, _set(std::move(set)) +, _bytes(std::move(webpBytes)) +, _emoji(std::move(emoji)) +, _type(type) +, _api(&session->mtp()) { +} + +StickerUpload::~StickerUpload() { + cancel(); +} + +void StickerUpload::start( + Fn done, + Fn fail, + Fn progress) { + Expects(!_uploadId); + + _done = std::move(done); + _fail = std::move(fail); + _progress = std::move(progress); + + _documentId = base::RandomValue(); + auto ready = PrepareStickerWebp( + _session->mtp().mainDcId(), + _documentId, + _bytes, + _type); + _uploadId = FullMsgId( + _session->userPeerId(), + _session->data().nextLocalMessageId()); + + const auto document = _session->data().document(_documentId); + document->uploadingData = std::make_unique( + document->size > 0 ? document->size : int64(_bytes.size())); + + _session->uploader().documentReady( + ) | rpl::filter([=](const Storage::UploadedMedia &data) { + return data.fullId == _uploadId; + }) | rpl::on_next([=](const Storage::UploadedMedia &data) { + uploadReady(data.info.file); + }, _uploadLifetime); + + _session->uploader().documentFailed( + ) | rpl::filter([=](const FullMsgId &id) { + return id == _uploadId; + }) | rpl::on_next([=] { + uploadFailed(); + }, _uploadLifetime); + + if (_progress) { + _session->uploader().documentProgress( + ) | rpl::filter([=](const FullMsgId &id) { + return id == _uploadId; + }) | rpl::on_next([=] { + uploadProgressed(); + }, _uploadLifetime); + } + + _session->uploader().upload(_uploadId, ready); +} + +void StickerUpload::cancel() { + if (_uploadId) { + _session->uploader().cancel(_uploadId); + _uploadId = FullMsgId(); + } + if (_addRequestId) { + _api.request(_addRequestId).cancel(); + _addRequestId = 0; + } + _uploadLifetime.destroy(); + _done = nullptr; + _fail = nullptr; + _progress = nullptr; +} + +void StickerUpload::uploadProgressed() { + if (!_progress) { + return; + } + const auto document = _session->data().document(_documentId); + if (!document->uploading() || !document->uploadingData) { + return; + } + const auto size = document->uploadingData->size; + if (size <= 0) { + return; + } + const auto percent = int( + (document->uploadingData->offset * 100) / size); + if (percent != _lastReportedPercent) { + _lastReportedPercent = percent; + _progress(percent); + } +} + +void StickerUpload::uploadFailed() { + const auto fail = std::move(_fail); + cancel(); + if (fail) { + fail(QString()); + } +} + +void StickerUpload::uploadReady(const MTPInputFile &file) { + _uploadLifetime.destroy(); + _uploadId = FullMsgId(); + + const auto side = SideForType(_type); + auto attributes = QVector(); + attributes.push_back(MTP_documentAttributeSticker( + MTP_flags(0), + MTP_string(_emoji), + MTP_inputStickerSetEmpty(), + MTPMaskCoords())); + attributes.push_back(MTP_documentAttributeImageSize( + MTP_int(side), + MTP_int(side))); + + const auto media = MTP_inputMediaUploadedDocument( + MTP_flags(0), + file, + MTPInputFile(), + MTP_string("image/webp"), + MTP_vector(std::move(attributes)), + MTP_vector(), + MTPInputPhoto(), + MTP_int(0), + MTP_int(0)); + + _addRequestId = _api.request(MTPmessages_UploadMedia( + MTP_flags(0), + MTPstring(), + MTP_inputPeerSelf(), + media + )).done(crl::guard(this, [=](const MTPMessageMedia &result) { + _addRequestId = 0; + auto inputDoc = (MTPInputDocument*)(nullptr); + auto storage = MTPInputDocument(); + result.match([&](const MTPDmessageMediaDocument &data) { + if (const auto doc = data.vdocument()) { + doc->match([&](const MTPDdocument &d) { + storage = MTP_inputDocument( + d.vid(), + d.vaccess_hash(), + d.vfile_reference()); + inputDoc = &storage; + }, [](const auto &) { + }); + } + }, [](const auto &) { + }); + if (inputDoc) { + requestAddSticker(*inputDoc); + } else if (const auto fail = std::move(_fail)) { + cancel(); + fail(QString()); + } + })).fail(crl::guard(this, [=](const MTP::Error &error) { + _addRequestId = 0; + const auto fail = std::move(_fail); + const auto type = error.type(); + cancel(); + if (fail) { + fail(type); + } + })).handleFloodErrors().send(); +} + +void StickerUpload::requestAddSticker(const MTPInputDocument &document) { + _addRequestId = _api.request(MTPstickers_AddStickerToSet( + Data::InputStickerSet(_set), + InputItem(document, _emoji)) + ).done(crl::guard(this, [=](const MTPmessages_StickerSet &result) { + _addRequestId = 0; + FeedSetIfFull(_session, result); + const auto done = std::move(_done); + cancel(); + if (done) { + done(result); + } + })).fail(crl::guard(this, [=](const MTP::Error &error) { + _addRequestId = 0; + const auto fail = std::move(_fail); + const auto type = error.type(); + cancel(); + if (fail) { + fail(type); + } + })).handleFloodErrors().send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_stickers_creator.h b/Telegram/SourceFiles/api/api_stickers_creator.h new file mode 100644 index 00000000000000..6d0478f7dbd97c --- /dev/null +++ b/Telegram/SourceFiles/api/api_stickers_creator.h @@ -0,0 +1,123 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/weak_ptr.h" +#include "data/stickers/data_stickers.h" +#include "mtproto/sender.h" + +class DocumentData; + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class PopupMenu; +namespace Menu { +struct MenuCallback; +} // namespace Menu +} // namespace Ui + +namespace Api { + +inline constexpr auto kStickersInOwnedSetMax = 120; +inline constexpr auto kEmojiInOwnedSetMax = 200; +inline constexpr auto kEmojiStickerSideMax = 100; + +void AddExistingStickerToSet( + not_null session, + const StickerSetIdentifier &set, + not_null document, + const QString &emoji, + Fn done, + Fn fail); + +void DeleteStickerSet( + not_null session, + const StickerSetIdentifier &set, + Fn done, + Fn fail); + +[[nodiscard]] bool HasOwnedStickerSets(not_null session); +[[nodiscard]] bool HasOwnedEmojiSets(not_null session); + +[[nodiscard]] QString StickerEmojiOrDefault( + not_null document); + +void FillChooseStickerSetMenu( + not_null menu, + std::shared_ptr show, + not_null document); + +void FillChooseEmojiSetMenu( + not_null menu, + std::shared_ptr show, + not_null document); + +void AddAddToStickerSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document); + +void AddAddToEmojiSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document); + +void AddAddToOwnedSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document); + +class StickerUpload final : public base::has_weak_ptr { +public: + StickerUpload( + not_null session, + StickerSetIdentifier set, + QByteArray webpBytes, + QString emoji, + Data::StickersType type = Data::StickersType::Stickers); + ~StickerUpload(); + + void start( + Fn done, + Fn fail, + Fn progress = nullptr); + + void cancel(); + +private: + void uploadReady(const MTPInputFile &file); + void uploadFailed(); + void uploadProgressed(); + void requestAddSticker(const MTPInputDocument &document); + + const not_null _session; + StickerSetIdentifier _set; + QByteArray _bytes; + QString _emoji; + Data::StickersType _type = Data::StickersType::Stickers; + MTP::Sender _api; + rpl::lifetime _uploadLifetime; + FullMsgId _uploadId; + DocumentId _documentId = 0; + mtpRequestId _addRequestId = 0; + + Fn _done; + Fn _fail; + Fn _progress; + int _lastReportedPercent = -1; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_suggest_post.cpp b/Telegram/SourceFiles/api/api_suggest_post.cpp new file mode 100644 index 00000000000000..073d9dd9cccdc3 --- /dev/null +++ b/Telegram/SourceFiles/api/api_suggest_post.cpp @@ -0,0 +1,704 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_suggest_post.h" + +#include "apiwrap.h" +#include "base/unixtime.h" +#include "boxes/transfer_gift_box.h" +#include "chat_helpers/message_field.h" +#include "core/click_handler_types.h" +#include "data/components/credits.h" +#include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_saved_sublist.h" +#include "history/view/controls/history_view_suggest_options.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "mainwindow.h" +#include "settings/settings_credits_graphics.h" +#include "ui/boxes/choose_date_time.h" +#include "ui/layers/generic_box.h" +#include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" // InsufficientTonBox +#include "ui/text/text_utilities.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" + +namespace Api { +namespace { + +void SendApproval( + std::shared_ptr show, + not_null item, + TimeId scheduleDate = 0) { + using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag; + const auto suggestion = item->Get(); + if (!suggestion + || suggestion->accepted + || suggestion->rejected + || suggestion->requestId) { + return; + } + + const auto id = item->fullId(); + const auto session = &show->session(); + const auto finish = [=] { + if (const auto item = session->data().message(id)) { + const auto suggestion = item->Get(); + if (suggestion) { + suggestion->requestId = 0; + } + } + }; + suggestion->requestId = session->api().request( + MTPmessages_ToggleSuggestedPostApproval( + MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag()), + item->history()->peer->input(), + MTP_int(item->id.bare), + MTP_int(scheduleDate), + MTPstring()) // reject_comment + ).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + finish(); + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + finish(); + }).send(); +} + +void ConfirmApproval( + std::shared_ptr show, + not_null item, + TimeId scheduleDate = 0, + Fn accepted = nullptr) { + const auto suggestion = item->Get(); + if (!suggestion + || suggestion->accepted + || suggestion->rejected + || suggestion->requestId) { + return; + } + const auto id = item->fullId(); + const auto price = suggestion->price; + const auto admin = item->history()->amMonoforumAdmin(); + if (!admin && !price.empty()) { + const auto credits = &item->history()->session().credits(); + if (price.ton()) { + if (!credits->tonLoaded()) { + credits->tonLoad(); + return; + } else if (price > credits->tonBalance()) { + const auto session = &item->history()->session(); + show->show( + Box(Ui::InsufficientTonBox, session, price)); + return; + } + } else { + if (!credits->loaded()) { + credits->load(); + return; + } else if (price > credits->balance()) { + using namespace Settings; + const auto peer = item->history()->peer; + const auto broadcast = peer->monoforumBroadcast(); + const auto broadcastId = (broadcast ? broadcast : peer)->id; + const auto done = [=](SmallBalanceResult result) { + if (result == SmallBalanceResult::Success + || result == SmallBalanceResult::Already) { + const auto item = peer->owner().message(id); + if (item) { + ConfirmApproval( + show, + item, + scheduleDate, + accepted); + } + } + }; + MaybeRequestBalanceIncrease( + show, + int(base::SafeRound(price.value())), + SmallBalanceForSuggest{ broadcastId }, + done); + return; + } + } + } + const auto peer = item->history()->peer; + const auto session = &peer->session(); + const auto broadcast = peer->monoforumBroadcast(); + const auto channelName = (broadcast ? broadcast : peer)->name(); + const auto amount = admin + ? HistoryView::PriceAfterCommission(session, price) + : price; + const auto commission = HistoryView::FormatAfterCommissionPercent( + session, + price); + const auto date = langDateTime(base::unixtime::parse(scheduleDate)); + show->show(Box([=](not_null box) { + const auto callback = std::make_shared>(); + auto text = admin + ? tr::lng_suggest_accept_text( + tr::now, + lt_from, + tr::bold(item->from()->shortName()), + tr::marked) + : tr::lng_suggest_accept_text_to( + tr::now, + lt_channel, + tr::bold(channelName), + tr::marked); + if (price) { + text.append("\n\n").append(admin + ? (scheduleDate + ? (amount.stars() + ? tr::lng_suggest_accept_receive_stars + : tr::lng_suggest_accept_receive_ton)( + tr::now, + lt_count_decimal, + amount.value(), + lt_channel, + tr::bold(channelName), + lt_percent, + TextWithEntities{ commission }, + lt_date, + tr::bold(date), + tr::rich) + : (amount.stars() + ? tr::lng_suggest_accept_receive_now_stars + : tr::lng_suggest_accept_receive_now_ton)( + tr::now, + lt_count_decimal, + amount.value(), + lt_channel, + tr::bold(channelName), + lt_percent, + TextWithEntities{ commission }, + tr::rich)) + : (scheduleDate + ? (amount.stars() + ? tr::lng_suggest_accept_pay_stars + : tr::lng_suggest_accept_pay_ton)( + tr::now, + lt_count_decimal, + amount.value(), + lt_date, + tr::bold(date), + tr::rich) + : (amount.stars() + ? tr::lng_suggest_accept_pay_now_stars + : tr::lng_suggest_accept_pay_now_ton)( + tr::now, + lt_count_decimal, + amount.value(), + tr::rich))); + if (admin) { + text.append(' ').append( + tr::lng_suggest_accept_receive_if( + tr::now, + tr::rich)); + if (price.stars()) { + text.append("\n\n").append( + tr::lng_suggest_options_stars_warning( + tr::now, + tr::rich)); + } + } + } + Ui::ConfirmBox(box, { + .text = text, + .confirmed = [=](Fn close) { (*callback)(); close(); }, + .confirmText = tr::lng_suggest_accept_send(), + .title = tr::lng_suggest_accept_title(), + }); + *callback = [=, weak = base::make_weak(box)] { + if (const auto onstack = accepted) { + onstack(); + } + const auto item = show->session().data().message(id); + if (!item) { + return; + } + SendApproval(show, item, scheduleDate); + if (const auto strong = weak.get()) { + strong->closeBox(); + } + }; + })); +} + +void SendDecline( + std::shared_ptr show, + not_null item, + const QString &comment) { + using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag; + const auto suggestion = item->Get(); + if (!suggestion + || suggestion->accepted + || suggestion->rejected + || suggestion->requestId) { + return; + } + + const auto id = item->fullId(); + const auto session = &show->session(); + const auto finish = [=] { + if (const auto item = session->data().message(id)) { + const auto suggestion = item->Get(); + if (suggestion) { + suggestion->requestId = 0; + } + } + }; + suggestion->requestId = session->api().request( + MTPmessages_ToggleSuggestedPostApproval( + MTP_flags(Flag::f_reject + | (comment.isEmpty() ? Flag() : Flag::f_reject_comment)), + item->history()->peer->input(), + MTP_int(item->id.bare), + MTPint(), // schedule_date + MTP_string(comment)) + ).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + finish(); + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + finish(); + }).send(); +} + +void RequestApprovalDate( + std::shared_ptr show, + not_null item) { + const auto id = item->fullId(); + const auto weak = std::make_shared>(); + const auto close = [=] { + if (const auto strong = weak->get()) { + strong->closeBox(); + } + }; + const auto done = [=](TimeId result) { + if (const auto item = show->session().data().message(id)) { + ConfirmApproval(show, item, result, close); + } else { + close(); + } + }; + using namespace HistoryView; + auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{ + .session = &show->session(), + .done = done, + .mode = SuggestMode::Publish, + }); + *weak = dateBox.data(); + show->show(std::move(dateBox)); +} + +void RequestDeclineComment( + std::shared_ptr show, + not_null item) { + const auto id = item->fullId(); + const auto admin = item->history()->amMonoforumAdmin(); + const auto peer = item->history()->peer; + const auto broadcast = peer->monoforumBroadcast(); + const auto channelName = (broadcast ? broadcast : peer)->name(); + show->show(Box([=](not_null box) { + const auto callback = std::make_shared>(); + Ui::ConfirmBox(box, { + .text = (admin + ? tr::lng_suggest_decline_text( + lt_from, + rpl::single(tr::bold(item->from()->shortName())), + tr::marked) + : tr::lng_suggest_decline_text_to( + lt_channel, + rpl::single(tr::bold(channelName)), + tr::marked)), + .confirmed = [=](Fn close) { (*callback)(); close(); }, + .confirmText = tr::lng_suggest_action_decline(), + .confirmStyle = &st::attentionBoxButton, + .title = tr::lng_suggest_decline_title(), + }); + const auto reason = box->addRow(object_ptr( + box, + st::factcheckField, + Ui::InputField::Mode::NoNewlines, + tr::lng_suggest_decline_reason())); + box->setFocusCallback([=] { + reason->setFocusFast(); + }); + *callback = [=, weak = base::make_weak(box)] { + const auto item = show->session().data().message(id); + if (!item) { + return; + } + SendDecline(show, item, reason->getLastText().trimmed()); + if (const auto strong = weak.get()) { + strong->closeBox(); + } + }; + reason->submits( + ) | rpl::on_next([=](Qt::KeyboardModifiers modifiers) { + if (!(modifiers & Qt::ShiftModifier)) { + (*callback)(); + } + }, box->lifetime()); + })); +} + +struct SendSuggestState { + SendPaymentHelper sendPayment; +}; +void SendSuggest( + std::shared_ptr show, + not_null item, + std::shared_ptr state, + Fn modify, + Fn done = nullptr, + int starsApproved = 0) { + const auto suggestion = item->Get(); + const auto id = item->fullId(); + const auto withPaymentApproved = [=](int stars) { + if (const auto item = show->session().data().message(id)) { + SendSuggest(show, item, state, modify, done, stars); + } + }; + const auto isForward = item->Get(); + auto action = SendAction(item->history()); + action.options.suggest.exists = 1; + if (suggestion) { + action.options.suggest.date = suggestion->date; + action.options.suggest.priceWhole = suggestion->price.whole(); + action.options.suggest.priceNano = suggestion->price.nano(); + action.options.suggest.ton = suggestion->price.ton() ? 1 : 0; + } + modify(action.options.suggest); + action.options.starsApproved = starsApproved; + action.replyTo.monoforumPeerId = item->history()->amMonoforumAdmin() + ? item->sublistPeerId() + : PeerId(); + action.replyTo.messageId = item->fullId(); + + const auto checked = state->sendPayment.check( + show, + item->history()->peer, + action.options, + 1, + withPaymentApproved); + if (!checked) { + return; + } + + show->session().api().sendAction(action); + show->session().api().forwardMessages({ + .items = { item }, + .options = (isForward + ? Data::ForwardOptions::PreserveInfo + : Data::ForwardOptions::NoSenderNames), + }, action); + if (const auto onstack = done) { + onstack(); + } +} + +void SuggestApprovalDate( + std::shared_ptr show, + not_null item) { + const auto suggestion = item->Get(); + if (!suggestion) { + return; + } + const auto id = item->fullId(); + const auto state = std::make_shared(); + const auto weak = std::make_shared>(); + const auto done = [=](TimeId result) { + const auto item = show->session().data().message(id); + if (!item) { + return; + } + const auto close = [=] { + if (const auto strong = weak->get()) { + strong->closeBox(); + } + }; + SendSuggest( + show, + item, + state, + [=](SuggestOptions &options) { options.date = result; }, + close); + }; + using namespace HistoryView; + auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{ + .session = &show->session(), + .done = done, + .value = suggestion->date, + .mode = SuggestMode::Change, + }); + *weak = dateBox.data(); + show->show(std::move(dateBox)); +} + +void SuggestOfferForMessage( + std::shared_ptr show, + not_null item, + SuggestOptions values, + HistoryView::SuggestMode mode) { + const auto id = item->fullId(); + const auto state = std::make_shared(); + const auto weak = std::make_shared>(); + const auto done = [=](SuggestOptions result) { + const auto item = show->session().data().message(id); + if (!item) { + return; + } + const auto close = [=] { + if (const auto strong = weak->get()) { + strong->closeBox(); + } + }; + SendSuggest( + show, + item, + state, + [=](SuggestOptions &options) { options = result; }, + close); + }; + using namespace HistoryView; + auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{ + .peer = item->history()->peer, + .done = done, + .value = values, + .mode = mode, + }); + *weak = priceBox.data(); + show->show(std::move(priceBox)); +} + +void SuggestApprovalPrice( + std::shared_ptr show, + not_null item) { + const auto suggestion = item->Get(); + if (!suggestion) { + return; + } + using namespace HistoryView; + SuggestOfferForMessage(show, item, { + .exists = uint32(1), + .priceWhole = uint32(suggestion->price.whole()), + .priceNano = uint32(suggestion->price.nano()), + .ton = uint32(suggestion->price.ton() ? 1 : 0), + .date = suggestion->date, + }, SuggestMode::Change); +} + +void ConfirmGiftSaleAccept( + not_null window, + not_null item, + not_null suggestion) { + ShowGiftSaleAcceptBox(window, item, suggestion); +} + +void ConfirmGiftSaleDecline( + not_null window, + not_null item, + not_null suggestion) { + ShowGiftSaleRejectBox(window, item, suggestion); +} + +void RespondToNoForwardsRequest( + not_null controller, + not_null item, + not_null request, + bool accept) { + if (request->requestId) { + return; + } + const auto id = item->fullId(); + const auto session = &item->history()->session(); + const auto peer = item->history()->peer; + const auto msgId = item->id; + const auto finish = [=] { + if (const auto item = session->data().message(id)) { + if (const auto r = item->Get()) { + r->requestId = 0; + } + } + }; + using Flag = MTPmessages_ToggleNoForwards::Flag; + request->requestId = session->api().request(MTPmessages_ToggleNoForwards( + MTP_flags(Flag::f_request_msg_id), + peer->input(), + MTP_bool(!accept), + MTP_int(msgId) + )).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + finish(); + }).fail([=](const MTP::Error &error) { + controller->showToast(error.type()); + finish(); + }).send(); +} + +} // namespace + +std::shared_ptr AcceptClickHandler( + not_null item) { + const auto session = &item->history()->session(); + const auto id = item->fullId(); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + if (!controller || &controller->session() != session) { + return; + } + const auto item = session->data().message(id); + if (!item) { + return; + } + const auto show = controller->uiShow(); + const auto suggestion = item->Get(); + const auto nfRequest = item->Get(); + if (!suggestion && !nfRequest) { + return; + } else if (nfRequest) { + RespondToNoForwardsRequest(controller, item, nfRequest, true); + return; + } else if (suggestion->gift) { + ConfirmGiftSaleAccept(controller, item, suggestion); + } else if (!suggestion->date) { + RequestApprovalDate(show, item); + } else { + ConfirmApproval(show, item); + } + }); +} + +std::shared_ptr DeclineClickHandler( + not_null item) { + const auto session = &item->history()->session(); + const auto id = item->fullId(); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + if (!controller || &controller->session() != session) { + return; + } + const auto item = session->data().message(id); + if (!item) { + return; + } + const auto nfRequest = item->Get(); + if (nfRequest) { + RespondToNoForwardsRequest(controller, item, nfRequest, false); + return; + } + const auto suggestion = item->Get(); + if (suggestion && suggestion->gift) { + ConfirmGiftSaleDecline(controller, item, suggestion); + } else { + RequestDeclineComment(controller->uiShow(), item); + } + }); +} + +std::shared_ptr SuggestChangesClickHandler( + not_null item) { + const auto session = &item->history()->session(); + const auto id = item->fullId(); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto window = my.sessionWindow.get(); + if (!window || &window->session() != session) { + return; + } + const auto item = session->data().message(id); + if (!item) { + return; + } + const auto menu = Ui::CreateChild( + window->widget(), + st::popupMenuWithIcons); + if (HistoryView::CanEditSuggestedMessage(item)) { + menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] { + const auto item = session->data().message(id); + if (!item) { + return; + } + const auto suggestion = item->Get(); + if (!suggestion) { + return; + } + const auto history = item->history(); + const auto editData = PrepareEditText(item); + const auto cursor = MessageCursor{ + int(editData.text.size()), + int(editData.text.size()), + Ui::kQFixedMax + }; + const auto monoforumPeerId = history->amMonoforumAdmin() + ? item->sublistPeerId() + : PeerId(); + const auto previewDraft = Data::WebPageDraft::FromItem(item); + history->setLocalEditDraft(std::make_unique( + editData, + FullReplyTo{ + .messageId = FullMsgId(history->peer->id, item->id), + .monoforumPeerId = monoforumPeerId, + }, + SuggestOptions{ + .exists = uint32(1), + .priceWhole = uint32(suggestion->price.whole()), + .priceNano = uint32(suggestion->price.nano()), + .ton = uint32(suggestion->price.ton() ? 1 : 0), + .date = suggestion->date, + }, + cursor, + previewDraft)); + history->session().changes().entryUpdated( + (monoforumPeerId + ? item->savedSublist() + : (Data::Thread*)history.get()), + Data::EntryUpdate::Flag::LocalDraftSet); + }, &st::menuIconEdit); + } + menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] { + if (const auto item = session->data().message(id)) { + SuggestApprovalPrice(window->uiShow(), item); + } + }, &st::menuIconTagSell); + menu->addAction(tr::lng_suggest_menu_edit_time(tr::now), [=] { + if (const auto item = session->data().message(id)) { + SuggestApprovalDate(window->uiShow(), item); + } + }, &st::menuIconSchedule); + menu->popup(QCursor::pos()); + }); +} + +void AddOfferToMessage( + std::shared_ptr show, + FullMsgId itemId) { + const auto session = &show->session(); + const auto item = session->data().message(itemId); + if (!item || !HistoryView::CanAddOfferToMessage(item)) { + return; + } + SuggestOfferForMessage(show, item, {}, HistoryView::SuggestMode::New); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_suggest_post.h b/Telegram/SourceFiles/api/api_suggest_post.h new file mode 100644 index 00000000000000..582f0adca0ac15 --- /dev/null +++ b/Telegram/SourceFiles/api/api_suggest_post.h @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class ClickHandler; + +namespace Main { +class SessionShow; +} // namespace Main + +namespace Api { + +[[nodiscard]] std::shared_ptr AcceptClickHandler( + not_null item); +[[nodiscard]] std::shared_ptr DeclineClickHandler( + not_null item); +[[nodiscard]] std::shared_ptr SuggestChangesClickHandler( + not_null item); + +void AddOfferToMessage( + std::shared_ptr show, + FullMsgId itemId); + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 067cc6c0c1ad7d..87af7a77a3114e 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -12,7 +12,10 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_stickers_set.h" +#include "history/history.h" +#include "history/history_item.h" #include "main/main_session.h" +#include "ui/basic_click_handlers.h" namespace Api { namespace { @@ -47,14 +50,23 @@ using namespace TextUtilities; if (!parsed.userId || parsed.selfId != session->userId().bare) { return {}; } - return MTP_inputMessageEntityMentionName( - offset, - length, - (parsed.userId == parsed.selfId - ? MTP_inputUserSelf() - : MTP_inputUser( - MTP_long(parsed.userId), - MTP_long(parsed.accessHash)))); + const auto user = session->data().user(UserId(parsed.userId)); + const auto item = user->isLoaded() + ? nullptr + : user->owner().messageWithPeer(user->id); + const auto input = item + ? MTP_inputUserFromMessage( + item->history()->peer->input(), + MTP_int(item->id.bare), + MTP_long(parsed.userId)) + : (parsed.userId == parsed.selfId) + ? MTP_inputUserSelf() + : user->isLoaded() + ? user->inputUser() + : MTP_inputUser( + MTP_long(parsed.userId), + MTP_long(parsed.accessHash)); + return MTP_inputMessageEntityMentionName(offset, length, input); } } // namespace @@ -202,7 +214,11 @@ EntitiesInText EntitiesFromMTP( d.vlength().v, }); }, [&](const MTPDmessageEntityBankCard &d) { - // Skipping cards. // #TODO entities + result.push_back({ + EntityType::BankCard, + d.voffset().v, + d.vlength().v, + }); }, [&](const MTPDmessageEntitySpoiler &d) { result.push_back({ EntityType::Spoiler, @@ -223,13 +239,42 @@ EntitiesInText EntitiesFromMTP( d.vlength().v, d.is_collapsed() ? u"1"_q : QString(), }); + }, [&](const MTPDmessageEntityFormattedDate &d) { + auto flags = FormattedDateFlags(); + if (d.is_relative()) { + flags |= FormattedDateFlag::Relative; + } + if (d.is_short_time()) { + flags |= FormattedDateFlag::ShortTime; + } + if (d.is_long_time()) { + flags |= FormattedDateFlag::LongTime; + } + if (d.is_short_date()) { + flags |= FormattedDateFlag::ShortDate; + } + if (d.is_long_date()) { + flags |= FormattedDateFlag::LongDate; + } + if (d.is_day_of_week()) { + flags |= FormattedDateFlag::DayOfWeek; + } + result.push_back({ + EntityType::FormattedDate, + d.voffset().v, + d.vlength().v, + SerializeFormattedDateData(d.vdate().v, flags), + }); + }, [&](const MTPDmessageEntityDiffInsert &) { + }, [&](const MTPDmessageEntityDiffReplace &) { + }, [&](const MTPDmessageEntityDiffDelete &) { }); } return result; } MTPVector EntitiesToMTP( - not_null session, + Main::Session *session, const EntitiesInText &entities, ConvertOption option) { auto v = QVector(); @@ -250,7 +295,8 @@ MTPVector EntitiesToMTP( && entity.type() != EntityType::Spoiler && entity.type() != EntityType::MentionName && entity.type() != EntityType::CustomUrl - && entity.type() != EntityType::CustomEmoji) { + && entity.type() != EntityType::CustomEmoji + && entity.type() != EntityType::FormattedDate) { continue; } @@ -261,11 +307,15 @@ MTPVector EntitiesToMTP( v.push_back(MTP_messageEntityUrl(offset, length)); } break; case EntityType::CustomUrl: { + const auto external = UrlClickHandler::ExternalUrlFromInternalUrl( + entity.data()); v.push_back( MTP_messageEntityTextUrl( offset, length, - MTP_string(entity.data()))); + MTP_string(external.isEmpty() + ? entity.data() + : external))); } break; case EntityType::Email: { v.push_back(MTP_messageEntityEmail(offset, length)); @@ -273,6 +323,9 @@ MTPVector EntitiesToMTP( case EntityType::Phone: { v.push_back(MTP_messageEntityPhone(offset, length)); } break; + case EntityType::BankCard: { + v.push_back(MTP_messageEntityBankCard(offset, length)); + } break; case EntityType::Hashtag: { v.push_back(MTP_messageEntityHashtag(offset, length)); } break; @@ -283,6 +336,7 @@ MTPVector EntitiesToMTP( v.push_back(MTP_messageEntityMention(offset, length)); } break; case EntityType::MentionName: { + Assert(session != nullptr); const auto valid = MentionNameEntity( session, offset, @@ -339,9 +393,50 @@ MTPVector EntitiesToMTP( v.push_back(*valid); } } break; + case EntityType::FormattedDate: { + const auto [date, dateFlags] = DeserializeFormattedDateData( + entity.data()); + if (date) { + using Flag = MTPDmessageEntityFormattedDate::Flag; + auto mtpFlags = MTPDmessageEntityFormattedDate::Flags(); + if (dateFlags & FormattedDateFlag::Relative) { + mtpFlags |= Flag::f_relative; + } + if (dateFlags & FormattedDateFlag::ShortTime) { + mtpFlags |= Flag::f_short_time; + } + if (dateFlags & FormattedDateFlag::LongTime) { + mtpFlags |= Flag::f_long_time; + } + if (dateFlags & FormattedDateFlag::ShortDate) { + mtpFlags |= Flag::f_short_date; + } + if (dateFlags & FormattedDateFlag::LongDate) { + mtpFlags |= Flag::f_long_date; + } + if (dateFlags & FormattedDateFlag::DayOfWeek) { + mtpFlags |= Flag::f_day_of_week; + } + v.push_back(MTP_messageEntityFormattedDate( + MTP_flags(mtpFlags), + offset, + length, + MTP_int(date))); + } + } break; } } return MTP_vector(std::move(v)); } +TextWithEntities ParseTextWithEntities( + Main::Session *session, + const MTPTextWithEntities &text) { + const auto &data = text.data(); + return { + .text = qs(data.vtext()), + .entities = EntitiesFromMTP(session, data.ventities().v), + }; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_text_entities.h b/Telegram/SourceFiles/api/api_text_entities.h index 95348d61649928..a2d9adb88b9e8b 100644 --- a/Telegram/SourceFiles/api/api_text_entities.h +++ b/Telegram/SourceFiles/api/api_text_entities.h @@ -25,8 +25,12 @@ enum class ConvertOption { const QVector &entities); [[nodiscard]] MTPVector EntitiesToMTP( - not_null session, + Main::Session *session, const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal); +[[nodiscard]] TextWithEntities ParseTextWithEntities( + Main::Session *session, + const MTPTextWithEntities &text); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_todo_lists.cpp b/Telegram/SourceFiles/api/api_todo_lists.cpp new file mode 100644 index 00000000000000..ecd02a20da983b --- /dev/null +++ b/Telegram/SourceFiles/api/api_todo_lists.cpp @@ -0,0 +1,257 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_todo_lists.h" + +#include "api/api_editing.h" +#include "apiwrap.h" +#include "base/random.h" +#include "data/business/data_shortcut_messages.h" // ShortcutIdToMTP +#include "data/data_changes.h" +#include "data/data_histories.h" +#include "data/data_todo_list.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" // ShouldSendSilent +#include "main/main_session.h" + +namespace Api { +namespace { + +constexpr auto kSendTogglesDelay = 3 * crl::time(1000); + +} // namespace + +TodoLists::TodoLists(not_null api) +: _session(&api->session()) +, _api(&api->instance()) +, _sendTimer([=] { sendAccumulatedToggles(false); }) { +} + +void TodoLists::create( + const TodoListData &data, + SendAction action, + Fn done, + Fn fail) { + _session->api().sendAction(action); + + const auto history = action.history; + const auto peer = history->peer; + const auto topicRootId = action.replyTo.messageId + ? action.replyTo.topicRootId + : 0; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + auto sendFlags = MTPmessages_SendMedia::Flags(0); + if (action.replyTo) { + sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; + } + const auto clearCloudDraft = action.clearDraft; + if (clearCloudDraft) { + sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; + history->clearLocalDraft(topicRootId, monoforumPeerId); + history->clearCloudDraft(topicRootId, monoforumPeerId); + history->startSavingCloudDraft(topicRootId, monoforumPeerId); + } + const auto silentPost = ShouldSendSilent(peer, action.options); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); + if (silentPost) { + sendFlags |= MTPmessages_SendMedia::Flag::f_silent; + } + if (action.options.scheduled) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } + } + if (action.options.shortcutId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; + } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } + const auto sendAs = action.options.sendAs; + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } + auto &histories = history->owner().histories(); + const auto randomId = base::RandomValue(); + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(sendFlags), + peer->input(), + Data::Histories::ReplyToPlaceholder(), + TodoListDataToInputMedia(&data), + MTP_string(), + MTP_long(randomId), + MTPReplyMarkup(), + MTPVector(), + MTP_int(action.options.scheduled), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), + Data::ShortcutIdToMTP(_session, action.options.shortcutId), + MTP_long(action.options.effectId), + MTP_long(starsPaid), + SuggestToMTP(action.options.suggest) + ), [=](const MTPUpdates &result, const MTP::Response &response) { + if (clearCloudDraft) { + history->finishSavingCloudDraft( + topicRootId, + monoforumPeerId, + UnixtimeFromMsgId(response.outerMsgId)); + } + _session->changes().historyUpdated( + history, + (action.options.scheduled + ? Data::HistoryUpdate::Flag::ScheduledSent + : Data::HistoryUpdate::Flag::MessageSent)); + if (const auto onstack = done) { + onstack(); + } + }, [=](const MTP::Error &error, const MTP::Response &response) { + if (clearCloudDraft) { + history->finishSavingCloudDraft( + topicRootId, + monoforumPeerId, + UnixtimeFromMsgId(response.outerMsgId)); + } + if (const auto onstack = fail) { + onstack(error.type()); + } + }); +} + +void TodoLists::edit( + not_null item, + const TodoListData &data, + SendOptions options, + Fn done, + Fn fail) { + EditTodoList(item, data, options, [=](mtpRequestId) { + if (const auto onstack = done) { + onstack(); + } + }, [=](const QString &error, mtpRequestId) { + if (const auto onstack = fail) { + onstack(error); + } + }); +} + +void TodoLists::add( + not_null item, + const std::vector &items, + Fn done, + Fn fail) { + if (items.empty()) { + return; + } + const auto session = _session; + _session->api().request(MTPmessages_AppendTodoList( + item->history()->peer->input(), + MTP_int(item->id.bare), + TodoListItemsToMTP(&item->history()->session(), items) + )).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + if (const auto onstack = done) { + onstack(); + } + }).fail([=](const MTP::Error &error) { + if (const auto onstack = fail) { + onstack(error.type()); + } + }).send(); +} + +void TodoLists::toggleCompletion(FullMsgId itemId, int id, bool completed) { + auto &entry = _toggles[itemId]; + if (completed) { + const auto changed1 = entry.completed.emplace(id).second; + const auto changed2 = entry.incompleted.remove(id); + if (!changed1 && !changed2) { + return; + } + } else { + const auto changed1 = entry.incompleted.emplace(id).second; + const auto changed2 = entry.completed.remove(id); + if (!changed1 && !changed2) { + return; + } + } + entry.scheduled = crl::now(); + if (!entry.requestId && !_sendTimer.isActive()) { + _sendTimer.callOnce(kSendTogglesDelay); + } +} + +void TodoLists::sendAccumulatedToggles(bool force) { + const auto now = crl::now(); + auto nearest = crl::time(0); + for (auto &[itemId, entry] : _toggles) { + if (entry.requestId) { + continue; + } + const auto wait = entry.scheduled + kSendTogglesDelay - now; + if (wait <= 0) { + entry.scheduled = 0; + send(itemId, entry); + } else if (!nearest || nearest > wait) { + nearest = wait; + } + } + if (nearest > 0) { + _sendTimer.callOnce(nearest); + } +} + +void TodoLists::send(FullMsgId itemId, Accumulated &entry) { + const auto item = _session->data().message(itemId); + if (!item) { + return; + } + auto completed = entry.completed + | ranges::views::transform([](int id) { return MTP_int(id); }); + auto incompleted = entry.incompleted + | ranges::views::transform([](int id) { return MTP_int(id); }); + entry.requestId = _api.request(MTPmessages_ToggleTodoCompleted( + item->history()->peer->input(), + MTP_int(item->id), + MTP_vector_from_range(completed), + MTP_vector_from_range(incompleted) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + finishRequest(itemId); + }).fail([=](const MTP::Error &error) { + finishRequest(itemId); + }).send(); + entry.completed.clear(); + entry.incompleted.clear(); +} + +void TodoLists::finishRequest(FullMsgId itemId) { + auto &entry = _toggles[itemId]; + entry.requestId = 0; + if (entry.completed.empty() && entry.incompleted.empty()) { + _toggles.remove(itemId); + } else { + sendAccumulatedToggles(false); + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_todo_lists.h b/Telegram/SourceFiles/api/api_todo_lists.h new file mode 100644 index 00000000000000..92d6a634b23bfe --- /dev/null +++ b/Telegram/SourceFiles/api/api_todo_lists.h @@ -0,0 +1,69 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/timer.h" +#include "mtproto/sender.h" + +class ApiWrap; +class HistoryItem; +struct TodoListItem; +struct TodoListData; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +struct SendAction; +struct SendOptions; + +class TodoLists final { +public: + explicit TodoLists(not_null api); + + void create( + const TodoListData &data, + SendAction action, + Fn done, + Fn fail); + void edit( + not_null item, + const TodoListData &data, + SendOptions options, + Fn done, + Fn fail); + void add( + not_null item, + const std::vector &items, + Fn done, + Fn fail); + void toggleCompletion(FullMsgId itemId, int id, bool completed); + +private: + struct Accumulated { + base::flat_set completed; + base::flat_set incompleted; + crl::time scheduled = 0; + mtpRequestId requestId = 0; + }; + + void sendAccumulatedToggles(bool force); + void send(FullMsgId itemId, Accumulated &entry); + void finishRequest(FullMsgId itemId); + + const not_null _session; + MTP::Sender _api; + + base::flat_map _toggles; + base::Timer _sendTimer; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_toggling_media.cpp b/Telegram/SourceFiles/api/api_toggling_media.cpp index 65d2ce317ff92f..6c28df197d78f6 100644 --- a/Telegram/SourceFiles/api/api_toggling_media.cpp +++ b/Telegram/SourceFiles/api/api_toggling_media.cpp @@ -24,7 +24,7 @@ void ToggleExistingMedia( Data::FileOrigin origin, ToggleRequestCallback toggleRequest, DoneCallback &&done) { - const auto api = &document->owner().session().api(); + const auto api = &document->session().api(); auto performRequest = [=](const auto &repeatRequest) -> void { const auto usedFileReference = document->fileReference(); diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index dd983dc37b4cad..7c6b271ca594a4 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #include "api/api_transcribes.h" #include "apiwrap.h" +#include "api/api_text_entities.h" #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" @@ -15,8 +16,11 @@ For license and copyright information please follow this link: #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" +#include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "main/main_session_settings.h" +#include "spellcheck/spellcheck_types.h" namespace Api { @@ -25,6 +29,32 @@ Transcribes::Transcribes(not_null api) , _api(&api->instance()) { } +bool Transcribes::isRated(not_null item) const { + const auto fullId = item->fullId(); + for (const auto &[transcribeId, id] : _ids) { + if (id == fullId) { + return _session->settings().isTranscriptionRated(transcribeId); + } + } + return false; +} + +void Transcribes::rate(not_null item, bool isGood) { + const auto fullId = item->fullId(); + for (const auto &[transcribeId, id] : _ids) { + if (id == fullId) { + _api.request(MTPmessages_RateTranscribedAudio( + item->history()->peer->input(), + MTP_int(item->id), + MTP_long(transcribeId), + MTP_bool(isGood))).send(); + _session->settings().markTranscriptionAsRated(transcribeId); + _session->saveSettings(); + return; + } + } +} + bool Transcribes::freeFor(not_null item) const { if (const auto channel = item->history()->peer->asMegagroup()) { const auto owner = &channel->owner(); @@ -76,7 +106,6 @@ void Transcribes::toggle(not_null item) { auto i = _map.find(id); if (i == _map.end()) { load(item); - //_session->data().requestItemRepaint(item); _session->data().requestItemResize(item); } else if (!i->second.requestId) { i->second.shown = !i->second.shown; @@ -87,6 +116,25 @@ void Transcribes::toggle(not_null item) { } } +void Transcribes::toggleSummary(not_null item) { + const auto id = item->fullId(); + auto i = _summaries.find(id); + if (i == _summaries.end()) { + summarize(item); + } else if (!i->second.loading) { + auto &entry = i->second; + if (entry.result.empty()) { + summarize(item); + } else { + entry.shown = entry.premiumRequired ? false : !entry.shown; + _session->data().requestItemResize(item); + if (entry.shown) { + _session->data().requestItemShowHighlight(item); + } + } + } +} + const Transcribes::Entry &Transcribes::entry( not_null item) const { static auto empty = Entry(); @@ -94,6 +142,13 @@ const Transcribes::Entry &Transcribes::entry( return (i != _map.end()) ? i->second : empty; } +const SummaryEntry &Transcribes::summary( + not_null item) const { + static const auto empty = SummaryEntry(); + const auto i = _summaries.find(item->fullId()); + return (i != _summaries.end()) ? i->second : empty; +} + void Transcribes::apply(const MTPDupdateTranscribedAudio &update) { const auto id = update.vtranscription_id().v; const auto i = _ids.find(id); @@ -131,7 +186,7 @@ void Transcribes::load(not_null item) { }; const auto id = item->fullId(); const auto requestId = _api.request(MTPmessages_TranscribeAudio( - item->history()->peer->input, + item->history()->peer->input(), MTP_int(item->id) )).done([=](const MTPmessages_TranscribedAudio &result) { const auto &data = result.data(); @@ -181,4 +236,74 @@ void Transcribes::load(not_null item) { entry.pending = false; } +void Transcribes::summarize(not_null item) { + if (!item->isHistoryEntry() || item->isLocal()) { + return; + } + + const auto id = item->fullId(); + const auto translatedTo = item->history()->translatedTo(); + const auto langCode = translatedTo + ? translatedTo.twoLetterCode() + : QString(); + const auto requestId = _api.request(MTPmessages_SummarizeText( + langCode.isEmpty() + ? MTP_flags(0) + : MTP_flags(MTPmessages_summarizeText::Flag::f_to_lang), + item->history()->peer->input(), + MTP_int(item->id), + langCode.isEmpty() ? MTPstring() : MTP_string(langCode), + MTPstring() // tone + )).done([=](const MTPTextWithEntities &result) { + const auto &data = result.data(); + auto &entry = _summaries[id]; + entry.requestId = 0; + entry.loading = false; + entry.premiumRequired = false; + entry.languageId = translatedTo; + entry.result = TextWithEntities( + qs(data.vtext()), + Api::EntitiesFromMTP(_session, data.ventities().v)); + if (const auto item = _session->data().message(id)) { + _session->data().requestItemTextRefresh(item); + _session->data().requestItemShowHighlight(item); + } + }).fail([=](const MTP::Error &error) { + auto &entry = _summaries[id]; + if (error.type() == u"SUMMARY_FLOOD_PREMIUM"_q) { + entry.premiumRequired = true; + } + entry.requestId = 0; + entry.shown = false; + entry.loading = false; + if (const auto item = _session->data().message(id)) { + _session->data().requestItemTextRefresh(item); + } + }).send(); + + auto &entry = _summaries.emplace(id).first->second; + entry.requestId = requestId; + entry.shown = true; + entry.loading = true; + + item->setHasSummaryEntry(); + _session->data().requestItemResize(item); +} + +void Transcribes::checkSummaryToTranslate(FullMsgId id) { + const auto i = _summaries.find(id); + if (i == _summaries.end() || i->second.result.empty()) { + return; + } + const auto item = _session->data().message(id); + if (!item) { + return; + } + const auto translatedTo = item->history()->translatedTo(); + if (i->second.languageId != translatedTo) { + i->second.result = tr::lng_contacts_loading(tr::now, tr::italic); + summarize(item); + } +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index b074e42f13f3b5..7eef8a4fb6bc82 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "spellcheck/spellcheck_types.h" class ApiWrap; @@ -17,6 +18,15 @@ class Session; namespace Api { +struct SummaryEntry { + TextWithEntities result; + LanguageId languageId; + bool shown = false; + bool loading = false; + bool premiumRequired = false; + mtpRequestId requestId = 0; +}; + class Transcribes final { public: explicit Transcribes(not_null api); @@ -34,9 +44,16 @@ class Transcribes final { void toggle(not_null item); [[nodiscard]] const Entry &entry(not_null item) const; + void toggleSummary(not_null item); + [[nodiscard]] const SummaryEntry &summary( + not_null item) const; + void checkSummaryToTranslate(FullMsgId id); + void apply(const MTPDupdateTranscribedAudio &update); [[nodiscard]] bool freeFor(not_null item) const; + [[nodiscard]] bool isRated(not_null item) const; + void rate(not_null item, bool isGood); [[nodiscard]] bool trialsSupport(); [[nodiscard]] TimeId trialsRefreshAt(); @@ -45,6 +62,7 @@ class Transcribes final { private: void load(not_null item); + void summarize(not_null item); const not_null _session; MTP::Sender _api; @@ -56,6 +74,8 @@ class Transcribes final { base::flat_map _map; base::flat_map _ids; + base::flat_map _summaries; + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index dac998dfdce83c..e871b71faab8d9 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "data/data_channel.h" #include "data/data_forum_topic.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" @@ -31,7 +32,9 @@ UnreadThings::UnreadThings(not_null api) : _api(api) { bool UnreadThings::trackMentions(Data::Thread *thread) const { const auto peer = thread ? thread->peer().get() : nullptr; - return peer && (peer->isChat() || peer->isMegagroup()); + return peer + && (peer->isChat() || peer->isMegagroup()) + && !peer->isMonoforum(); } bool UnreadThings::trackReactions(Data::Thread *thread) const { @@ -39,6 +42,13 @@ bool UnreadThings::trackReactions(Data::Thread *thread) const { return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup()); } +bool UnreadThings::trackPollVotes(Data::Thread *thread) const { + const auto peer = thread ? thread->peer().get() : nullptr; + return peer + && (peer->isChat() || peer->isMegagroup()) + && !peer->isMonoforum(); +} + void UnreadThings::preloadEnough(Data::Thread *thread) { if (trackMentions(thread)) { preloadEnoughMentions(thread); @@ -46,6 +56,9 @@ void UnreadThings::preloadEnough(Data::Thread *thread) { if (trackReactions(thread)) { preloadEnoughReactions(thread); } + if (trackPollVotes(thread)) { + preloadEnoughPollVotes(thread); + } } void UnreadThings::mediaAndMentionsRead( @@ -81,6 +94,15 @@ void UnreadThings::preloadEnoughReactions(not_null thread) { } } +void UnreadThings::preloadEnoughPollVotes(not_null thread) { + const auto fullCount = thread->unreadPollVotes().count(); + const auto loadedCount = thread->unreadPollVotes().loadedCount(); + const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); + if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { + requestPollVotes(thread, loadedCount); + } +} + void UnreadThings::cancelRequests(not_null thread) { if (const auto requestId = _mentionsRequests.take(thread)) { _api->request(*requestId).cancel(); @@ -88,12 +110,15 @@ void UnreadThings::cancelRequests(not_null thread) { if (const auto requestId = _reactionsRequests.take(thread)) { _api->request(*requestId).cancel(); } + if (const auto requestId = _pollVotesRequests.take(thread)) { + _api->request(*requestId).cancel(); + } } void UnreadThings::requestMentions( not_null thread, int loaded) { - if (_mentionsRequests.contains(thread)) { + if (_mentionsRequests.contains(thread) || thread->asSublist()) { return; } const auto offsetId = std::max( @@ -108,7 +133,7 @@ void UnreadThings::requestMentions( using Flag = MTPmessages_GetUnreadMentions::Flag; const auto requestId = _api->request(MTPmessages_GetUnreadMentions( MTP_flags(topic ? Flag::f_top_msg_id : Flag()), - history->peer->input, + history->peer->input(), MTP_int(topic ? topic->rootId() : 0), MTP_int(offsetId), MTP_int(addOffset), @@ -138,12 +163,15 @@ void UnreadThings::requestReactions( const auto maxId = 0; const auto minId = 0; const auto history = thread->owningHistory(); + const auto sublist = thread->asSublist(); const auto topic = thread->asTopic(); using Flag = MTPmessages_GetUnreadReactions::Flag; const auto requestId = _api->request(MTPmessages_GetUnreadReactions( - MTP_flags(topic ? Flag::f_top_msg_id : Flag()), - history->peer->input, + MTP_flags((topic ? Flag::f_top_msg_id : Flag()) + | (sublist ? Flag::f_saved_peer_id : Flag())), + history->peer->input(), MTP_int(topic ? topic->rootId() : 0), + (sublist ? sublist->sublistPeer()->input() : MTPInputPeer()), MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), @@ -158,4 +186,38 @@ void UnreadThings::requestReactions( _reactionsRequests.emplace(thread, requestId); } +void UnreadThings::requestPollVotes( + not_null thread, + int loaded) { + if (_pollVotesRequests.contains(thread) || thread->asSublist()) { + return; + } + const auto offsetId = loaded + ? std::max(thread->unreadPollVotes().maxLoaded(), MsgId(1)) + : MsgId(1); + const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; + const auto addOffset = loaded ? -(limit + 1) : -limit; + const auto maxId = 0; + const auto minId = 0; + const auto history = thread->owningHistory(); + const auto topic = thread->asTopic(); + using Flag = MTPmessages_GetUnreadPollVotes::Flag; + const auto requestId = _api->request(MTPmessages_GetUnreadPollVotes( + MTP_flags(topic ? Flag::f_top_msg_id : Flag()), + history->peer->input(), + MTP_int(topic ? topic->rootId() : 0), + MTP_int(offsetId), + MTP_int(addOffset), + MTP_int(limit), + MTP_int(maxId), + MTP_int(minId) + )).done([=](const MTPmessages_Messages &result) { + _pollVotesRequests.remove(thread); + thread->unreadPollVotes().addSlice(result, loaded); + }).fail([=] { + _pollVotesRequests.remove(thread); + }).send(); + _pollVotesRequests.emplace(thread, requestId); +} + } // namespace UnreadThings diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h index f3c7e1711eb10c..dfc07459a5e4a0 100644 --- a/Telegram/SourceFiles/api/api_unread_things.h +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -23,6 +23,7 @@ class UnreadThings final { [[nodiscard]] bool trackMentions(Data::Thread *thread) const; [[nodiscard]] bool trackReactions(Data::Thread *thread) const; + [[nodiscard]] bool trackPollVotes(Data::Thread *thread) const; void preloadEnough(Data::Thread *thread); @@ -35,14 +36,17 @@ class UnreadThings final { private: void preloadEnoughMentions(not_null thread); void preloadEnoughReactions(not_null thread); + void preloadEnoughPollVotes(not_null thread); void requestMentions(not_null thread, int loaded); void requestReactions(not_null thread, int loaded); + void requestPollVotes(not_null thread, int loaded); const not_null _api; base::flat_map, mtpRequestId> _mentionsRequests; base::flat_map, mtpRequestId> _reactionsRequests; + base::flat_map, mtpRequestId> _pollVotesRequests; }; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 3bbab8cf4abe64..e557ee9da9871e 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -21,13 +21,18 @@ For license and copyright information please follow this link: #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" +#include "chat_helpers/stickers_dice_pack.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" +#include "data/components/gift_auctions.h" +#include "data/components/promo_suggestions.h" #include "data/components/scheduled_messages.h" #include "data/components/top_peers.h" #include "data/notify/data_notify_settings.h" #include "data/stickers/data_stickers.h" +#include "data/data_ai_compose_tones.h" #include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_chat.h" @@ -41,6 +46,7 @@ For license and copyright information please follow this link: #include "data/data_histories.h" #include "data/data_folder.h" #include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_send_action.h" #include "data/data_stories.h" #include "data/data_message_reactions.h" @@ -50,6 +56,7 @@ For license and copyright information please follow this link: #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" +#include "history/history_streamed_drafts.h" #include "history/history_unread_things.h" #include "core/application.h" #include "storage/storage_account.h" @@ -85,6 +92,90 @@ enum class DataIsLoadedResult { Ok = 3, }; +[[nodiscard]] bool PeerDataIsLoaded( + not_null owner, + PeerId peerId) { + return !peerId || owner->peerLoaded(peerId); +} + +[[nodiscard]] bool MentionUsersDataIsLoaded( + not_null owner, + const MTPVector &entities) { + for (const auto &entity : entities.v) { + auto loaded = true; + entity.match([&](const MTPDmessageEntityMentionName &data) { + loaded = owner->userLoaded(data.vuser_id()); + }, [&](const MTPDinputMessageEntityMentionName &data) { + data.vuser_id().match([&](const MTPDinputUser &data) { + loaded = owner->userLoaded(data.vuser_id()); + }, [](const auto &) { + }); + }, [](const auto &) { + }); + if (!loaded) { + return false; + } + } + return true; +} + +[[nodiscard]] bool ForwardedInfoDataIsLoaded( + not_null owner, + const MTPMessageFwdHeader &header) { + return header.match([&](const MTPDmessageFwdHeader &data) { + return (!data.vfrom_id() + || PeerDataIsLoaded(owner, peerFromMTP(*data.vfrom_id()))) + && (!data.vsaved_from_peer() + || PeerDataIsLoaded(owner, peerFromMTP(*data.vsaved_from_peer()))) + && (!data.vsaved_from_id() + || PeerDataIsLoaded(owner, peerFromMTP(*data.vsaved_from_id()))); + }); +} + +[[nodiscard]] bool ReplyDataIsLoaded( + not_null owner, + const MTPMessageReplyHeader &header) { + return header.match([&](const MTPDmessageReplyHeader &data) { + return (!data.vreply_to_peer_id() + || PeerDataIsLoaded(owner, peerFromMTP(*data.vreply_to_peer_id()))) + && (!data.vreply_from() + || ForwardedInfoDataIsLoaded(owner, *data.vreply_from())) + && (!data.vquote_entities() + || MentionUsersDataIsLoaded(owner, *data.vquote_entities())); + }, [&](const MTPDmessageReplyStoryHeader &data) { + return PeerDataIsLoaded(owner, peerFromMTP(data.vpeer())); + }); +} + +[[nodiscard]] bool DataIsLoaded( + not_null owner, + const MTPDupdateShortMessage &data) { + return owner->userLoaded(data.vuser_id()) + && (!data.vfwd_from() + || ForwardedInfoDataIsLoaded(owner, *data.vfwd_from())) + && (!data.vvia_bot_id() + || owner->userLoaded(*data.vvia_bot_id())) + && (!data.vreply_to() + || ReplyDataIsLoaded(owner, *data.vreply_to())) + && (!data.ventities() + || MentionUsersDataIsLoaded(owner, *data.ventities())); +} + +[[nodiscard]] bool DataIsLoaded( + not_null owner, + const MTPDupdateShortChatMessage &data) { + return owner->chatLoaded(data.vchat_id()) + && owner->userLoaded(data.vfrom_id()) + && (!data.vfwd_from() + || ForwardedInfoDataIsLoaded(owner, *data.vfwd_from())) + && (!data.vvia_bot_id() + || owner->userLoaded(*data.vvia_bot_id())) + && (!data.vreply_to() + || ReplyDataIsLoaded(owner, *data.vreply_to())) + && (!data.ventities() + || MentionUsersDataIsLoaded(owner, *data.ventities())); +} + void ProcessScheduledMessageWithElapsedTime( not_null session, bool needToAdd, @@ -132,100 +223,6 @@ bool HasForceLogoutNotification(const MTPUpdates &updates) { return false; } -bool ForwardedInfoDataLoaded( - not_null session, - const MTPMessageFwdHeader &header) { - return header.match([&](const MTPDmessageFwdHeader &data) { - if (const auto fromId = data.vfrom_id()) { - // Fully loaded is required in this case. - if (!session->data().peerLoaded(peerFromMTP(*fromId))) { - return false; - } - } - return true; - }); -} - -bool MentionUsersLoaded( - not_null session, - const MTPVector &entities) { - for (const auto &entity : entities.v) { - auto type = entity.type(); - if (type == mtpc_messageEntityMentionName) { - if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id())) { - return false; - } - } else if (type == mtpc_inputMessageEntityMentionName) { - auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id(); - if (inputUser.type() == mtpc_inputUser) { - if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id())) { - return false; - } - } - } - } - return true; -} - -DataIsLoadedResult AllDataLoadedForMessage( - not_null session, - const MTPMessage &message) { - return message.match([&](const MTPDmessage &message) { - if (const auto fromId = message.vfrom_id()) { - if (!message.is_post() - && !session->data().peerLoaded(peerFromMTP(*fromId))) { - return DataIsLoadedResult::FromNotLoaded; - } - } - if (const auto viaBotId = message.vvia_bot_id()) { - if (!session->data().userLoaded(*viaBotId)) { - return DataIsLoadedResult::NotLoaded; - } - } - if (const auto fwd = message.vfwd_from()) { - if (!ForwardedInfoDataLoaded(session, *fwd)) { - return DataIsLoadedResult::NotLoaded; - } - } - if (const auto entities = message.ventities()) { - if (!MentionUsersLoaded(session, *entities)) { - return DataIsLoadedResult::MentionNotLoaded; - } - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageService &message) { - if (const auto fromId = message.vfrom_id()) { - if (!message.is_post() - && !session->data().peerLoaded(peerFromMTP(*fromId))) { - return DataIsLoadedResult::FromNotLoaded; - } - } - return message.vaction().match( - [&](const MTPDmessageActionChatAddUser &action) { - for (const auto &userId : action.vusers().v) { - if (!session->data().userLoaded(userId)) { - return DataIsLoadedResult::NotLoaded; - } - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageActionChatJoinedByLink &action) { - if (!session->data().userLoaded(action.vinviter_id())) { - return DataIsLoadedResult::NotLoaded; - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageActionChatDeleteUser &action) { - if (!session->data().userLoaded(action.vuser_id())) { - return DataIsLoadedResult::NotLoaded; - } - return DataIsLoadedResult::Ok; - }, [](const auto &) { - return DataIsLoadedResult::Ok; - }); - }, [](const MTPDmessageEmpty &message) { - return DataIsLoadedResult::Ok; - }); -} - } // namespace Updates::Updates(not_null session) @@ -241,12 +238,12 @@ Updates::Updates(not_null session) _ptsWaiter.setRequesting(true); session->account().mtpUpdates( - ) | rpl::start_with_next([=](const MTPUpdates &updates) { + ) | rpl::on_next([=](const MTPUpdates &updates) { mtpUpdateReceived(updates); }, _lifetime); session->account().mtpNewSessionCreated( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { mtpNewSessionCreated(); }, _lifetime); @@ -260,7 +257,7 @@ Updates::Updates(not_null session) Data::PeerUpdate::Flag::FullInfo ) | rpl::filter([](const Data::PeerUpdate &update) { return update.peer->isChat() || update.peer->isMegagroup(); - }) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + }) | rpl::on_next([=](const Data::PeerUpdate &update) { const auto peer = update.peer; if (const auto list = _pendingSpeakingCallParticipants.take(peer)) { if (const auto call = peer->groupCall()) { @@ -303,25 +300,34 @@ void Updates::feedUpdateVector( auto list = updates.v; const auto hasGroupCallParticipantUpdates = ranges::contains( list, - mtpc_updateGroupCallParticipants, - &MTPUpdate::type); + true, + [](const MTPUpdate &update) { + return update.type() == mtpc_updateGroupCallParticipants + || update.type() == mtpc_updateGroupCallChainBlocks; + }); if (hasGroupCallParticipantUpdates) { ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) { - if (entry.type() == mtpc_updateGroupCallParticipants) { + if (entry.type() == mtpc_updateGroupCallChainBlocks) { return 0; - } else { + } else if (entry.type() == mtpc_updateGroupCallParticipants) { return 1; + } else { + return 2; } }); } else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) { return; } + if (policy == SkipUpdatePolicy::SkipNone) { + applyConvertToScheduledOnSend(updates); + } for (const auto &entry : std::as_const(list)) { const auto type = entry.type(); if ((policy == SkipUpdatePolicy::SkipMessageIds && type == mtpc_updateMessageID) || (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants - && type != mtpc_updateGroupCallParticipants)) { + && type != mtpc_updateGroupCallParticipants + && type != mtpc_updateGroupCallChainBlocks)) { continue; } feedUpdate(entry); @@ -329,6 +335,15 @@ void Updates::feedUpdateVector( session().data().sendHistoryChangeNotifications(); } +void Updates::checkForSentToScheduled(const MTPUpdates &updates) { + updates.match([&](const MTPDupdates &data) { + applyConvertToScheduledOnSend(data.vupdates(), true); + }, [&](const MTPDupdatesCombined &data) { + applyConvertToScheduledOnSend(data.vupdates(), true); + }, [](const auto &) { + }); +} + void Updates::feedMessageIds(const MTPVector &updates) { for (const auto &update : updates.v) { if (update.type() == mtpc_updateMessageID) { @@ -432,6 +447,7 @@ void Updates::feedChannelDifference( session().data().processChats(data.vchats()); _handlingChannelDifference = true; + applyConvertToScheduledOnSend(data.vother_updates()); feedMessageIds(data.vother_updates()); session().data().processMessages( data.vnew_messages(), @@ -596,6 +612,7 @@ void Updates::feedDifference( Core::App().checkAutoLock(); session().data().processUsers(users); session().data().processChats(chats); + applyConvertToScheduledOnSend(other); feedMessageIds(other); session().data().processMessages(msgs, NewMessageType::Unread); feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds); @@ -722,7 +739,7 @@ void Updates::getChannelDifference( } api().request(MTPupdates_GetChannelDifference( MTP_flags(flags), - channel->inputChannel, + channel->inputChannel(), filter, MTP_int(channel->pts()), MTP_int(kChannelGetDifferenceLimit) @@ -741,7 +758,7 @@ void Updates::addActiveChat(rpl::producer chat) { const auto key = _activeChats.empty() ? 0 : _activeChats.back().first + 1; std::move( chat - ) | rpl::start_with_next_done([=](PeerData *peer) { + ) | rpl::on_next_done([=](PeerData *peer) { auto &active = _activeChats[key]; const auto was = active.peer; if (was != peer) { @@ -801,7 +818,7 @@ void Updates::channelRangeDifferenceSend( MTP_int(range.till - 1)))); const auto requestId = api().request(MTPupdates_GetChannelDifference( MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force), - channel->inputChannel, + channel->inputChannel(), filter, MTP_int(pts), MTP_int(limit) @@ -881,6 +898,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) { } } +void Updates::applyConvertToScheduledOnSend( + const MTPVector &other, + bool skipScheduledCheck) { + for (const auto &update : other.v) { + update.match([&](const MTPDupdateNewScheduledMessage &data) { + const auto &message = data.vmessage(); + const auto id = IdFromMessage(message); + const auto scheduledMessages = &_session->scheduledMessages(); + const auto scheduledId = scheduledMessages->localMessageId(id); + for (const auto &updateId : other.v) { + updateId.match([&](const MTPDupdateMessageID &dataId) { + if (dataId.vid().v == id) { + auto &owner = session().data(); + if (skipScheduledCheck) { + const auto peerId = PeerFromMessage(message); + const auto history = owner.historyLoaded(peerId); + if (history) { + _session->data().sentToScheduled({ + .history = history, + .scheduledId = scheduledId, + }); + } + return; + } + const auto rand = dataId.vrandom_id().v; + const auto localId = owner.messageIdByRandomId(rand); + if (const auto local = owner.message(localId)) { + if (!local->isScheduled()) { + _session->data().sentToScheduled({ + .history = local->history(), + .scheduledId = scheduledId, + }); + + // We've sent a non-scheduled message, + // but it was converted to a scheduled. + local->destroy(); + } + } + } + }, [](const auto &) {}); + } + }, [](const auto &) {}); + } +} + void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { updates.match([&](const MTPDupdates &data) { session().data().processUsers(data.vusers()); @@ -895,7 +957,8 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { data.vupdates(), SkipUpdatePolicy::SkipExceptGroupCallParticipants); }, [&](const MTPDupdateShort &data) { - if (data.vupdate().type() == mtpc_updateGroupCallParticipants) { + if (data.vupdate().type() == mtpc_updateGroupCallParticipants + || data.vupdate().type() == mtpc_updateGroupCallChainBlocks) { feedUpdate(data.vupdate()); } }, [](const auto &) { @@ -1029,6 +1092,9 @@ void Updates::handleSendActionUpdate( const auto from = (fromId == session().userPeerId()) ? session().user().get() : session().data().peerLoaded(fromId); + const auto when = requestingDifference() + ? 0 + : base::unixtime::now(); if (action.type() == mtpc_speakingInGroupCallAction) { handleSpeakingInCall(peer, fromId, from); } @@ -1041,10 +1107,15 @@ void Updates::handleSendActionUpdate( const auto &data = action.c_sendMessageEmojiInteractionSeen(); handleEmojiInteraction(peer, qs(data.vemoticon())); return; + } else if (action.type() == mtpc_sendMessageTextDraftAction) { + const auto &data = action.c_sendMessageTextDraftAction(); + history->streamedDrafts().apply(rootId, fromId, when, data); + return; + } else if (action.type() == mtpc_sendMessageRichMessageDraftAction) { + const auto &data = action.c_sendMessageRichMessageDraftAction(); + history->streamedDrafts().apply(rootId, fromId, when, data); + return; } - const auto when = requestingDifference() - ? 0 - : base::unixtime::now(); session().data().sendActionManager().registerFor( history, rootId, @@ -1137,11 +1208,13 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { ? peerToMTP(_session->userPeerId()) : MTP_peerUser(d.vuser_id())), MTPint(), // from_boosts_applied + MTPstring(), // from_rank MTP_peerUser(d.vuser_id()), MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), MTPlong(), // via_business_bot_id + MTPPeer(), // guestchat_via_from d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), d.vdate(), d.vmessage(), @@ -1159,7 +1232,13 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTP_int(d.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id MTPlong(), // effect - MTPFactCheck()), + MTPFactCheck(), + MTPint(), // report_delivery_until_date + MTPlong(), // paid_message_stars + MTPSuggestedPost(), + MTPint(), // schedule_repeat_period + MTPstring(), // summary_from_language + MTPRichMessage()), MessageFlags(), NewMessageType::Unread); } break; @@ -1174,11 +1253,13 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { d.vid(), MTP_peerUser(d.vfrom_id()), MTPint(), // from_boosts_applied + MTPstring(), // from_rank MTP_peerChat(d.vchat_id()), MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), MTPlong(), // via_business_bot_id + MTPPeer(), // guestchat_via_from d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), d.vdate(), d.vmessage(), @@ -1196,7 +1277,13 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTP_int(d.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id MTPlong(), // effect - MTPFactCheck()), + MTPFactCheck(), + MTPint(), // report_delivery_until_date + MTPlong(), // paid_message_stars + MTPSuggestedPost(), + MTPint(), // schedule_repeat_period + MTPstring(), // summary_from_language + MTPRichMessage()), MessageFlags(), NewMessageType::Unread); } break; @@ -1246,7 +1333,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { user->madeAction(base::unixtime::now()); } } - ClearMediaAsExpired(item); + item->clearMediaAsExpired(); } } else { // Perhaps it was an unread mention! @@ -1430,18 +1517,13 @@ void Updates::applyUpdates( case mtpc_updateShortMessage: { auto &d = updates.c_updateShortMessage(); - const auto viaBotId = d.vvia_bot_id(); - const auto entities = d.ventities(); - const auto fwd = d.vfwd_from(); - if (!session().data().userLoaded(d.vuser_id()) - || (viaBotId && !session().data().userLoaded(*viaBotId)) - || (entities && !MentionUsersLoaded(&session(), *entities)) - || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { + if (!DataIsLoaded(&_session->data(), d)) { MTP_LOG(0, ("getDifference " - "{ good - getting user for updateShortMessage }%1" - ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); + "{ good - after not all data loaded in updateShortMessage }%1" + ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); return getDifference(); } + _session->data().fillMessagePeers(d); if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) { // Update date as well. setState(0, d.vdate().v, _updatesQts, _updatesSeq); @@ -1450,24 +1532,13 @@ void Updates::applyUpdates( case mtpc_updateShortChatMessage: { auto &d = updates.c_updateShortChatMessage(); - const auto noFrom = !session().data().userLoaded(d.vfrom_id()); - const auto chat = session().data().chatLoaded(d.vchat_id()); - const auto viaBotId = d.vvia_bot_id(); - const auto entities = d.ventities(); - const auto fwd = d.vfwd_from(); - if (!chat - || noFrom - || (viaBotId && !session().data().userLoaded(*viaBotId)) - || (entities && !MentionUsersLoaded(&session(), *entities)) - || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { + if (!DataIsLoaded(&_session->data(), d)) { MTP_LOG(0, ("getDifference " - "{ good - getting user for updateShortChatMessage }%1" - ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); - if (chat && noFrom) { - session().api().requestFullPeer(chat); - } + "{ good - after not all data loaded in updateShortChatMessage }%1" + ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); return getDifference(); } + _session->data().fillMessagePeers(d); if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) { // Update date as well. setState(0, d.vdate().v, _updatesQts, _updatesSeq); @@ -1497,13 +1568,7 @@ void Updates::applyUpdates( const auto wasAlready = (lookupMessage() != nullptr); feedUpdate(MTP_updateMessageID(d.vid(), MTP_long(randomId))); // ignore real date if (const auto item = lookupMessage()) { - const auto list = d.ventities(); - if (list && !MentionUsersLoaded(&session(), *list)) { - session().api().requestMessageData( - item->history()->peer, - item->id, - nullptr); - } + _session->data().fillMessagePeers(item->fullId(), d); item->applySentMessage(sent.text, d, wasAlready); } } @@ -1530,41 +1595,26 @@ void Updates::feedUpdate(const MTPUpdate &update) { // New messages. case mtpc_updateNewMessage: { auto &d = update.c_updateNewMessage(); - - const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); - if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) { - MTP_LOG(0, ("getDifference " - "{ good - after not all data loaded in updateNewMessage }%1" + if (!requestingDifference()) { + const auto peerId = PeerFromMessage(d.vmessage()); + const auto peer = session().data().peerLoaded(peerId); + if (peerId && !peer) { + MTP_LOG(0, ("getDifference " + "{ good - getting peer for updateNewMessage }%1" ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); - - // This can be if this update was created by grouping - // some short message update into an updates vector. - return getDifference(); + return getDifference(); + } } - updateAndApply(d.vpts().v, d.vpts_count().v, update); } break; case mtpc_updateNewChannelMessage: { auto &d = update.c_updateNewChannelMessage(); auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage()))); - const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); - if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { + if (!requestingDifference() && !channel) { MTP_LOG(0, ("getDifference " "{ good - after not all data loaded in updateNewChannelMessage }%1" ).arg(_session->mtp().isTestMode() ? " TESTMODE" : "")); - - // Request last active supergroup participants if the 'from' user was not loaded yet. - // This will optimize similar getDifference() calls for almost all next messages. - if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { - if (channel->canViewMembers() - && channel->mgInfo->lastParticipants.size() < _session->serverConfig().chatSizeMax - && (channel->mgInfo->lastParticipants.empty() - || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { - session().api().chatParticipants().requestLast(channel); - } - } - if (!_byMinChannelTimer.isActive()) { // getDifference after timeout _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); } @@ -1609,13 +1659,21 @@ void Updates::feedUpdate(const MTPUpdate &update) { local->history()->peer, local->date()); } - local->setRealId(d.vid().v); + local->setRealId(newId); + if (const auto topic = local->topic()) { + topic->applyMaybeLast(local); + } + if (const auto sublist = local->savedSublist()) { + sublist->applyMaybeLast(local); + } } } } else { owner.histories().checkTopicCreated(id, newId); } session().data().unregisterMessageRandomId(randomId); + } else { + Core::App().calls().handleUpdate(&session(), update); } session().data().unregisterMessageSentData(randomId); } break; @@ -1845,7 +1903,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { // Update web page anyway. session().data().processWebpage(d.vwebpage()); - session().data().sendWebPageGamePollNotifications(); + session().data().sendWebPageGamePollTodoListNotifications(); updateAndApply(d.vpts().v, d.vpts_count().v, update); } break; @@ -1855,7 +1913,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { // Update web page anyway. session().data().processWebpage(d.vwebpage()); - session().data().sendWebPageGamePollNotifications(); + session().data().sendWebPageGamePollTodoListNotifications(); auto channel = session().data().channelLoaded(d.vchannel_id()); if (channel && !_handlingChannelDifference) { @@ -1871,14 +1929,60 @@ void Updates::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateMessagePoll: { - session().data().applyUpdate(update.c_updateMessagePoll()); + const auto &d = update.c_updateMessagePoll(); + const auto wasRecentVoters = session().data().pollRecentVoters( + d.vpoll_id().v); + session().data().applyUpdate(d); + const auto notifyItem = session().data().findItemForPoll( + d.vpoll_id().v); + if (notifyItem) { + CheckPollVoteNotificationSchedule( + notifyItem, + wasRecentVoters); + } + if (const auto tlPeer = d.vpeer()) { + const auto &results = d.vresults(); + const auto hasUnread = results.match([]( + const MTPDpollResults &data) { + return data.is_has_unread_votes(); + }); + const auto isMin = results.match([]( + const MTPDpollResults &data) { + return data.is_min(); + }); + const auto peer = peerFromMTP(*tlPeer); + const auto msgId = d.vmsg_id()->v; + if (const auto history = session().data().historyLoaded(peer)) { + if (const auto item = session().data().message( + peer, + msgId)) { + if (hasUnread) { + if (!item->hasUnreadPollVote()) { + item->setHasUnreadPollVote(); + item->addToUnreadThings( + HistoryUnreadThings::AddType::New); + } + } else if (!isMin && item->hasUnreadPollVote()) { + item->markPollVotesRead(); + } + } else { + if (history->unreadPollVotes().has()) { + if (hasUnread) { + history->unreadPollVotes().checkAdd(msgId); + } + } + history->owner().histories().requestDialogEntry( + history); + } + } + } } break; case mtpc_updateUserTyping: { auto &d = update.c_updateUserTyping(); handleSendActionUpdate( peerFromUser(d.vuser_id()), - 0, + d.vtop_msg_id().value_or_empty(), peerFromUser(d.vuser_id()), d.vaction()); } break; @@ -1917,6 +2021,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().data().applyUpdate(update.c_updateChatParticipantAdmin()); } break; + case mtpc_updateChatParticipantRank: { + session().data().applyUpdate(update.c_updateChatParticipantRank()); + } break; + case mtpc_updateChatDefaultBannedRights: { session().data().applyUpdate(update.c_updateChatDefaultBannedRights()); } break; @@ -1998,6 +2106,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateConfig: { session().mtp().requestConfig(); + session().promoSuggestions().invalidate(); } break; case mtpc_updateUserPhone: { @@ -2047,8 +2156,12 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePhoneCall: case mtpc_updatePhoneCallSignalingData: case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCallChainBlocks: case mtpc_updateGroupCallConnection: - case mtpc_updateGroupCall: { + case mtpc_updateGroupCall: + case mtpc_updateGroupCallMessage: + case mtpc_updateGroupCallEncryptedMessage: + case mtpc_updateDeleteGroupCallMessages: { Core::App().calls().handleUpdate(&session(), update); } break; @@ -2114,6 +2227,15 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().data().webViewResultSent({ .queryId = d.vquery_id().v }); } break; + case mtpc_updateJoinChatWebViewDecision: { + const auto &d = update.c_updateJoinChatWebViewDecision(); + session().data().joinChatWebViewDecision({ + .peerId = peerFromMTP(d.vpeer()), + .queryId = uint64(d.vquery_id().v), + .result = d.vresult(), + }); + } break; + case mtpc_updateBotMenuButton: { const auto &d = update.c_updateBotMenuButton(); if (const auto bot = session().data().userLoaded(d.vbot_id())) { @@ -2138,6 +2260,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updateNewAuthorization: { + session().api().authorizations().apply(update); + } break; + case mtpc_updateServiceNotification: { const auto &d = update.c_updateServiceNotification(); const auto text = TextWithEntities { @@ -2369,6 +2495,32 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().data().updateRepliesReadTill({ id, readTillId, true }); } break; + case mtpc_updateReadMonoForumInbox: { + const auto &d = update.c_updateReadMonoForumInbox(); + const auto parentChatId = ChannelId(d.vchannel_id()); + const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id()); + const auto readTillId = d.vread_max_id().v; + session().data().updateSublistReadTill({ + parentChatId, + sublistPeerId, + readTillId, + false, + }); + } break; + + case mtpc_updateReadMonoForumOutbox: { + const auto &d = update.c_updateReadMonoForumOutbox(); + const auto parentChatId = ChannelId(d.vchannel_id()); + const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id()); + const auto readTillId = d.vread_max_id().v; + session().data().updateSublistReadTill({ + parentChatId, + sublistPeerId, + readTillId, + true, + }); + } break; + case mtpc_updateChannelAvailableMessages: { auto &d = update.c_updateChannelAvailableMessages(); if (const auto channel = session().data().channelLoaded(d.vchannel_id())) { @@ -2379,9 +2531,9 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; - case mtpc_updateChannelPinnedTopic: { - const auto &d = update.c_updateChannelPinnedTopic(); - const auto peerId = peerFromChannel(d.vchannel_id()); + case mtpc_updatePinnedForumTopic: { + const auto &d = update.c_updatePinnedForumTopic(); + const auto peerId = peerFromMTP(d.vpeer()); if (const auto peer = session().data().peerLoaded(peerId)) { const auto rootId = d.vtopic_id().v; if (const auto topic = peer->forumTopicFor(rootId)) { @@ -2392,9 +2544,9 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; - case mtpc_updateChannelPinnedTopics: { - const auto &d = update.c_updateChannelPinnedTopics(); - const auto peerId = peerFromChannel(d.vchannel_id()); + case mtpc_updatePinnedForumTopics: { + const auto &d = update.c_updatePinnedForumTopics(); + const auto peerId = peerFromMTP(d.vpeer()); if (const auto peer = session().data().peerLoaded(peerId)) { if (const auto forum = peer->forum()) { const auto done = [&] { @@ -2588,13 +2740,22 @@ void Updates::feedUpdate(const MTPUpdate &update) { const auto &data = update.c_updateDraftMessage(); const auto peerId = peerFromMTP(data.vpeer()); const auto topicRootId = data.vtop_msg_id().value_or_empty(); + const auto monoforumPeerId = data.vsaved_peer_id() + ? peerFromMTP(*data.vsaved_peer_id()) + : PeerId(); data.vdraft().match([&](const MTPDdraftMessage &data) { - Data::ApplyPeerCloudDraft(&session(), peerId, topicRootId, data); + Data::ApplyPeerCloudDraft( + &session(), + peerId, + topicRootId, + monoforumPeerId, + data); }, [&](const MTPDdraftMessageEmpty &data) { Data::ClearPeerCloudDraft( &session(), peerId, topicRootId, + monoforumPeerId, data.vdate().value_or_empty()); }); } break; @@ -2623,6 +2784,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().api().ringtones().applyUpdate(); } break; + case mtpc_updateAiComposeTones: { + session().data().aiComposeTones().applyUpdate(); + } break; + case mtpc_updateTranscribedAudio: { const auto &data = update.c_updateTranscribedAudio(); _session->api().transcribes().apply(data); @@ -2648,8 +2813,23 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePaidReactionPrivacy: { const auto &data = update.c_updatePaidReactionPrivacy(); - _session->api().globalPrivacy().updatePaidReactionAnonymous( - mtpIsTrue(data.vprivate())); + _session->api().globalPrivacy().updatePaidReactionShownPeer( + Api::ParsePaidReactionShownPeer(_session, data.vprivate())); + } break; + + case mtpc_updateStarGiftAuctionState: { + const auto &data = update.c_updateStarGiftAuctionState(); + _session->giftAuctions().apply(data); + } break; + + case mtpc_updateStarGiftAuctionUserState: { + const auto &data = update.c_updateStarGiftAuctionUserState(); + _session->giftAuctions().apply(data); + } break; + + case mtpc_updateEmojiGameInfo: { + const auto &data = update.c_updateEmojiGameInfo(); + _session->diceStickersPacks().apply(data); } break; } diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h index 654e36b51c0ab1..4e07ac099e2ade 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -40,6 +40,8 @@ class Updates final { void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdateNoPtsCheck(const MTPUpdate &update); + void checkForSentToScheduled(const MTPUpdates &updates); + [[nodiscard]] int32 pts() const; void updateOnline(crl::time lastNonIdleTime = 0); @@ -65,6 +67,13 @@ class Updates final { void addActiveChat(rpl::producer chat); [[nodiscard]] bool inActiveChats(not_null peer) const; + [[nodiscard]] bool requestingDifference() const { + return _ptsWaiter.requesting(); + } + [[nodiscard]] bool handlingChannelDifference() const { + return _handlingChannelDifference; + } + private: enum class ChannelDifferenceRequest { Unknown, @@ -97,9 +106,6 @@ class Updates final { void getDifferenceByPts(); void getDifferenceAfterFail(); - [[nodiscard]] bool requestingDifference() const { - return _ptsWaiter.requesting(); - } void getChannelDifference( not_null channel, ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); @@ -131,6 +137,9 @@ class Updates final { // Doesn't call sendHistoryChangeNotifications itself. void feedUpdate(const MTPUpdate &update); + void applyConvertToScheduledOnSend( + const MTPVector &other, + bool skipScheduledCheck = false); void applyGroupCallParticipantUpdates(const MTPUpdates &updates); bool whenGetDiffChanged( diff --git a/Telegram/SourceFiles/api/api_user_names.cpp b/Telegram/SourceFiles/api/api_user_names.cpp index fa040f17ea0b4a..d697de749db598 100644 --- a/Telegram/SourceFiles/api/api_user_names.cpp +++ b/Telegram/SourceFiles/api/api_user_names.cpp @@ -28,7 +28,7 @@ namespace { not_null peer) { const auto user = peer->asUser(); return (user && user->botInfo && user->botInfo->canEditInformation) - ? std::make_optional(user->inputUser) + ? std::make_optional(user->inputUser()) : std::nullopt; } @@ -98,9 +98,9 @@ rpl::producer Usernames::loadUsernames( if (peer->isSelf()) { requestUser(MTP_inputUserSelf()); } else if (const auto user = peer->asUser()) { - requestUser(user->inputUser); + requestUser(user->inputUser()); } else if (const auto channel = peer->asChannel()) { - requestChannel(channel->inputChannel); + requestChannel(channel->inputChannel()); } return lifetime; }; @@ -134,7 +134,7 @@ rpl::producer Usernames::toggle( if (list.empty()) { if (error == Error::Unknown) { it->second.done.fire_done(); - } else if (error == Error::TooMuch) { + } else { it->second.done.fire_error_copy(error); } _toggleRequests.remove(peerId); @@ -149,6 +149,8 @@ rpl::producer Usernames::toggle( const auto type = error.type(); if (type == u"USERNAMES_ACTIVE_TOO_MUCH"_q) { pop(Error::TooMuch); + } else if (type.startsWith(u"FLOOD_WAIT_"_q)) { + pop(Error::Flood); } else { pop(Error::Unknown); } @@ -158,19 +160,19 @@ rpl::producer Usernames::toggle( _api.request(MTPaccount_ToggleUsername( MTP_string(username), MTP_bool(active) - )).done(done).fail(fail).send(); + )).done(done).fail(fail).handleFloodErrors().send(); } else if (const auto channel = peer->asChannel()) { _api.request(MTPchannels_ToggleUsername( - channel->inputChannel, + channel->inputChannel(), MTP_string(username), MTP_bool(active) - )).done(done).fail(fail).send(); + )).done(done).fail(fail).handleFloodErrors().send(); } else if (const auto botUserInput = BotUserInput(peer)) { _api.request(MTPbots_ToggleUsername( *botUserInput, MTP_string(username), MTP_bool(active) - )).done(done).fail(fail).send(); + )).done(done).fail(fail).handleFloodErrors().send(); } else { return rpl::never(); } @@ -214,7 +216,7 @@ rpl::producer<> Usernames::reorder( _reorderRequests.emplace(peerId, requestId); } else if (const auto channel = peer->asChannel()) { const auto requestId = _api.request(MTPchannels_ReorderUsernames( - channel->inputChannel, + channel->inputChannel(), MTP_vector(std::move(tlUsernames)) )).done(finish).fail(finish).send(); _reorderRequests.emplace(peerId, requestId); @@ -249,7 +251,7 @@ void Usernames::requestToCache(not_null peer) { const auto lifetime = std::make_shared(); *lifetime = loadUsernames( peer - ) | rpl::start_with_next([=, id = peer->id](Data::Usernames usernames) { + ) | rpl::on_next([=, id = peer->id](Data::Usernames usernames) { _tinyCache = std::make_pair(id, std::move(usernames)); lifetime->destroy(); }); diff --git a/Telegram/SourceFiles/api/api_user_names.h b/Telegram/SourceFiles/api/api_user_names.h index 54b59ac2947edc..a25da1b375f58a 100644 --- a/Telegram/SourceFiles/api/api_user_names.h +++ b/Telegram/SourceFiles/api/api_user_names.h @@ -23,6 +23,7 @@ class Usernames final { public: enum class Error { TooMuch, + Flood, Unknown, }; diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index d0c17fa6b18469..3b9f64745bac83 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -15,7 +15,7 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "data/data_user.h" #include "main/main_session.h" -#include "settings/settings_premium.h" // Settings::ShowPremium. +#include "settings/sections/settings_premium.h" // Settings::ShowPremium. namespace Api { namespace { @@ -33,7 +33,7 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { result.reserve(peers.size()); for (const auto &peer : peers) { if (const auto user = peer->asUser()) { - result.push_back(user->inputUser); + result.push_back(user->inputUser()); } } return result; @@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { if (rule.always.premiums && (rule.option != Option::Everyone)) { result.push_back(MTP_inputPrivacyValueAllowPremium()); } + if (rule.always.miniapps && (rule.option != Option::Everyone)) { + result.push_back(MTP_inputPrivacyValueAllowBots()); + } } if (!rule.ignoreNever) { const auto users = collectInputUsers(rule.never); @@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { MTP_inputPrivacyValueDisallowChatParticipants( MTP_vector(chats))); } + if (rule.never.miniapps && (rule.option != Option::Nobody)) { + result.push_back(MTP_inputPrivacyValueDisallowBots()); + } } result.push_back([&] { switch (rule.option) { @@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { setOption(Option::CloseFriends); }, [&](const MTPDprivacyValueAllowPremium &) { result.always.premiums = true; + }, [&](const MTPDprivacyValueAllowBots &) { + result.always.miniapps = true; + }, [&](const MTPDprivacyValueDisallowBots &) { + result.never.miniapps = true; }, [&](const MTPDprivacyValueAllowUsers &data) { const auto &users = data.vusers().v; always.reserve(always.size() + users.size()); @@ -199,6 +209,9 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) { case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages(); case Key::About: return MTP_inputPrivacyKeyAbout(); case Key::Birthday: return MTP_inputPrivacyKeyBirthday(); + case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave(); + case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages(); + case Key::SavedMusic: return MTP_inputPrivacyKeySavedMusic(); } Unexpected("Key in Api::UserPrivacy::KetToTL."); } @@ -228,6 +241,12 @@ std::optional TLToKey(mtpTypeId type) { case mtpc_inputPrivacyKeyAbout: return Key::About; case mtpc_privacyKeyBirthday: case mtpc_inputPrivacyKeyBirthday: return Key::Birthday; + case mtpc_privacyKeyStarGiftsAutoSave: + case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave; + case mtpc_privacyKeyNoPaidMessages: + case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages; + case mtpc_privacyKeySavedMusic: + case mtpc_inputPrivacyKeySavedMusic: return Key::SavedMusic; } return std::nullopt; } diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h index 471f41f4809487..42a5b1e9463c03 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.h +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -31,6 +31,9 @@ class UserPrivacy final { Voices, About, Birthday, + GiftsAutoSave, + NoPaidMessages, + SavedMusic, }; enum class Option { Everyone, @@ -41,6 +44,7 @@ class UserPrivacy final { struct Exceptions { std::vector> peers; bool premiums = false; + bool miniapps = false; }; struct Rule { Option option = Option::Everyone; diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp index e80f56e213b7b3..6e6ac4dd305693 100644 --- a/Telegram/SourceFiles/api/api_views.cpp +++ b/Telegram/SourceFiles/api/api_views.cpp @@ -95,7 +95,7 @@ void ViewsManager::viewsIncrement() { ids.push_back(MTP_int(msgId)); } const auto requestId = _api.request(MTPmessages_GetMessagesViews( - i->first->input, + i->first->input(), MTP_vector(ids), MTP_bool(true) )).done([=]( @@ -183,7 +183,7 @@ void ViewsManager::sendPollRequests( } }; const auto requestId = _api.request(MTPmessages_GetExtendedMedia( - peer->input, + peer->input(), MTP_vector(list) )).done([=](const MTPUpdates &result, mtpRequestId id) { _session->api().applyUpdates(result); diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index 6c9df39b2d9503..5cc833ec4161a3 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -116,6 +116,7 @@ struct Userpic { TimeId date = 0; bool dateReacted = false; QString customEntityData; + ReactionId reaction; mutable Ui::PeerUserpicView view; mutable InMemoryKey uniqueKey; }; @@ -128,6 +129,26 @@ struct State { bool scheduled = false; }; +[[nodiscard]] bool ApplyReactionsRemovedToCachedData( + PeersWithReactions &data, + const Data::ReactionsRemoved &update) { + const auto was = data.list.size(); + data.list.erase( + ranges::remove_if(data.list, [&](const PeerWithReaction &entry) { + return !entry.reaction.empty() + && entry.peerWithDate.peer == update.participant->id; + }), + end(data.list)); + const auto removed = int(was - data.list.size()); + if (!removed) { + return false; + } + data.fullReactionsCount = (data.fullReactionsCount > removed) + ? (data.fullReactionsCount - removed) + : 0; + return true; +} + [[nodiscard]] auto Contexts() -> base::flat_map, std::unique_ptr> & { static auto result = base::flat_map< @@ -174,7 +195,7 @@ struct State { } session->changes().messageUpdates( Data::MessageUpdate::Flag::Destroyed - ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { + ) | rpl::on_next([=](const Data::MessageUpdate &update) { const auto i = context->cachedRead.find(update.item); if (i != end(context->cachedRead)) { session->api().request(i->second.requestId).cancel(); @@ -188,11 +209,27 @@ struct State { context->cachedReacted.erase(j); } }, context->subscriptions[session]); + session->data().reactionsRemoved( + ) | rpl::on_next([=](const Data::ReactionsRemoved &update) { + for (auto &[item, map] : context->cachedReacted) { + if (item->history()->peer->id != update.peer->id) { + continue; + } else if (update.msgId && item->id != update.msgId) { + continue; + } + for (auto &entry : map) { + auto data = entry.second.data.current(); + if (ApplyReactionsRemovedToCachedData(data, update)) { + entry.second.data = std::move(data); + } + } + } + }, context->subscriptions[session]); Data::AmPremiumValue( session ) | rpl::skip(1) | rpl::filter( rpl::mappers::_1 - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { for (auto &[item, cache] : context->cachedRead) { if (cache.data.current().state == Ui::WhoReadState::MyHidden) { cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; @@ -202,7 +239,7 @@ struct State { session->api().globalPrivacy().hideReadTime( ) | rpl::skip(1) | rpl::filter( !rpl::mappers::_1 - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { for (auto &[item, cache] : context->cachedRead) { if (cache.data.current().state == Ui::WhoReadState::MyHidden) { cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; @@ -240,19 +277,19 @@ struct State { [[nodiscard]] rpl::producer WhoReadIds( not_null item, not_null context) { - auto weak = QPointer(context.get()); + auto weak = base::make_weak(context); const auto session = &item->history()->session(); return [=](auto consumer) { if (!weak) { return rpl::lifetime(); } - const auto context = PreparedContextAt(weak.data(), session); + const auto context = PreparedContextAt(weak.get(), session); auto &entry = context->cacheRead(item); if (entry.requestId) { } else if (const auto user = item->history()->peer->asUser()) { entry.requestId = session->api().request( MTPmessages_GetOutboxReadDate( - user->input, + user->input(), MTP_int(item->id) ) ).done([=](const MTPOutboxReadDate &result) { @@ -282,7 +319,7 @@ struct State { } else { entry.requestId = session->api().request( MTPmessages_GetMessageReadParticipants( - item->history()->peer->input, + item->history()->peer->input(), MTP_int(item->id) ) ).done([=](const MTPVector &result) { @@ -325,13 +362,13 @@ struct State { not_null item, const ReactionId &reaction, not_null context) { - auto weak = QPointer(context.get()); + auto weak = base::make_weak(context); const auto session = &item->history()->session(); return [=](auto consumer) { if (!weak) { return rpl::lifetime(); } - const auto context = PreparedContextAt(weak.data(), session); + const auto context = PreparedContextAt(weak.get(), session); auto &entry = context->cacheReacted(item, reaction); if (!entry.requestId) { using Flag = MTPmessages_GetMessageReactionsList::Flag; @@ -340,7 +377,7 @@ struct State { MTP_flags(reaction.empty() ? Flag(0) : Flag::f_reaction), - item->history()->peer->input, + item->history()->peer->input(), MTP_int(item->id), ReactionToMTP(reaction), MTPstring(), // offset @@ -443,12 +480,22 @@ bool UpdateUserpics( return resolved.peer != nullptr; }) | ranges::to_vector; - const auto same = ranges::equal( - state->userpics, - peers, - ranges::equal_to(), - [](const Userpic &u) { return std::pair(u.peer.get(), u.date); }, - [](const ResolvedPeer &r) { return std::pair(r.peer, r.date); }); + const auto same = [&] { + if (state->userpics.size() != peers.size()) { + return false; + } + const auto count = state->userpics.size(); + for (auto i = size_t(); i != count; ++i) { + const auto &userpic = state->userpics[i]; + const auto &resolved = peers[i]; + if ((userpic.peer.get() != resolved.peer) + || (userpic.date != resolved.date) + || (userpic.reaction != resolved.reaction)) { + return false; + } + } + return true; + }(); if (same) { return false; } @@ -461,6 +508,7 @@ bool UpdateUserpics( if (i != end(was) && i->view.cloud) { i->date = resolved.date; i->dateReacted = resolved.dateReacted; + i->reaction = resolved.reaction; now.push_back(std::move(*i)); now.back().customEntityData = data; continue; @@ -470,6 +518,7 @@ bool UpdateUserpics( .date = resolved.date, .dateReacted = resolved.dateReacted, .customEntityData = data, + .reaction = resolved.reaction, }); auto &userpic = now.back(); userpic.uniqueKey = peer->userpicUniqueKey(userpic.view); @@ -512,11 +561,15 @@ void RegenerateParticipants(not_null state, int small, int large) { const auto peer = userpic.peer; const auto date = userpic.date; const auto id = peer->id.value; + const auto self = peer->isSelf(); const auto was = ranges::find(old, id, &Ui::WhoReadParticipant::id); if (was != end(old)) { was->name = peer->name(); was->date = FormatReadDate(date, currentDate); was->dateReacted = userpic.dateReacted; + was->self = self; + was->customEntityData = userpic.customEntityData; + was->reaction = userpic.reaction; now.push_back(std::move(*was)); continue; } @@ -524,7 +577,9 @@ void RegenerateParticipants(not_null state, int small, int large) { .name = peer->name(), .date = FormatReadDate(date, currentDate), .dateReacted = userpic.dateReacted, + .self = self, .customEntityData = userpic.customEntityData, + .reaction = userpic.reaction, .userpicLarge = GenerateUserpic(userpic, large), .userpicKey = userpic.uniqueKey, .id = id, @@ -590,7 +645,7 @@ rpl::producer WhoReacted( } std::move( idsWithReactions - ) | rpl::start_with_next([=](PeersWithReactions &&peers) { + ) | rpl::on_next([=](PeersWithReactions &&peers) { if (peers.state == WhoReadState::Unknown) { state->userpics.clear(); consumer.put_next(Ui::WhoReadContent{ @@ -624,7 +679,7 @@ rpl::producer WhoReacted( item->history()->session().downloaderTaskFinished( ) | rpl::filter([=] { return state->someUserpicsNotLoaded && !state->scheduled; - }) | rpl::start_with_next([=] { + }) | rpl::on_next([=] { for (const auto &userpic : state->userpics) { if (userpic.peer->userpicUniqueKey(userpic.view) != userpic.uniqueKey) { @@ -666,12 +721,7 @@ QString FormatReadDate(TimeId date, const QDateTime &now) { return tr::lng_mediaview_date_time( tr::now, lt_date, - tr::lng_month_day( - tr::now, - lt_month, - Lang::MonthDay(readDate.month())(tr::now), - lt_day, - QString::number(readDate.day())), + langDayOfMonthShort(readDate), lt_time, QLocale().toString(parsed.time(), QLocale::ShortFormat)); } @@ -712,7 +762,8 @@ bool WhoReadExists(not_null item) { const auto megagroup = peer->asMegagroup(); if ((!chat && !megagroup) || (megagroup - && (megagroup->flags() & ChannelDataFlag::ParticipantsHidden))) { + && (megagroup->flags() & ChannelDataFlag::ParticipantsHidden)) + || (megagroup && megagroup->isMonoforum())) { return false; } const auto &appConfig = peer->session().appConfig(); @@ -757,4 +808,31 @@ rpl::producer WhoReacted( return WhoReacted(item, reaction, context, st, nullptr); } +[[nodiscard]] rpl::producer WhenDate( + not_null author, + TimeId date, + Ui::WhoReadType type) { + return rpl::single(Ui::WhoReadContent{ + .participants = { Ui::WhoReadParticipant{ + .name = author->name(), + .date = FormatReadDate(date, QDateTime::currentDateTime()), + .id = author->id.value, + } }, + .type = type, + .fullReadCount = 1, + }); +} + +rpl::producer WhenEdited( + not_null author, + TimeId date) { + return WhenDate(author, date, Ui::WhoReadType::Edited); +} + +rpl::producer WhenOriginal( + not_null author, + TimeId date) { + return WhenDate(author, date, Ui::WhoReadType::Original); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h index 9a9100535c5269..8e1bc9a3bf02c2 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.h +++ b/Telegram/SourceFiles/api/api_who_reacted.h @@ -61,5 +61,11 @@ struct WhoReadList { const Data::ReactionId &reaction, not_null context, // Cache results for this lifetime. const style::WhoRead &st); +[[nodiscard]] rpl::producer WhenEdited( + not_null author, + TimeId date); +[[nodiscard]] rpl::producer WhenOriginal( + not_null author, + TimeId date); } // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5ef4af3d6c60ca..528577e93b63b7 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "api/api_authorizations.h" #include "api/api_attached_stickers.h" #include "api/api_blocked_peers.h" +#include "api/api_chat_invite.h" #include "api/api_chat_links.h" #include "api/api_chat_participants.h" #include "api/api_cloud_password.h" @@ -21,27 +22,37 @@ For license and copyright information please follow this link: #include "api/api_polls.h" #include "api/api_sending.h" #include "api/api_text_entities.h" +#include "api/api_todo_lists.h" #include "api/api_self_destruct.h" #include "api/api_sensitive_content.h" #include "api/api_global_privacy.h" +#include "api/api_reactions_notify_settings.h" #include "api/api_updates.h" #include "api/api_user_privacy.h" +#include "api/api_read_metrics.h" #include "api/api_views.h" #include "api/api_confirm_phone.h" #include "api/api_unread_things.h" #include "api/api_ringtones.h" +#include "api/api_compose_with_ai.h" #include "api/api_transcribes.h" #include "api/api_premium.h" #include "api/api_user_names.h" #include "api/api_websites.h" #include "data/business/data_shortcut_messages.h" +#include "data/components/credits.h" #include "data/components/scheduled_messages.h" #include "data/notify/data_notify_settings.h" #include "data/data_changes.h" +#include "data/data_media_types.h" #include "data/data_web_page.h" #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_forum.h" +#include "data/data_message_reaction_id.h" +#include "data/data_premium_limits.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_music.h" #include "data/data_saved_sublist.h" #include "data/data_search_controller.h" #include "data/data_session.h" @@ -63,10 +74,13 @@ For license and copyright information please follow this link: #include "history/history.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" +#include "history/view/controls/history_view_forward_panel.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_account.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/emoji_stake_box.h" +#include "ui/controls/ton_common.h" #include "boxes/sticker_set_box.h" #include "boxes/premium_limits_box.h" #include "window/notifications_manager.h" @@ -80,7 +94,7 @@ For license and copyright information please follow this link: #include "ui/chat/attach/attach_prepare.h" #include "ui/toast/toast.h" #include "support/support_helper.h" -#include "settings/settings_premium.h" +#include "settings/sections/settings_premium.h" #include "storage/localimageloader.h" #include "storage/download_manager_mtproto.h" #include "storage/file_upload.h" @@ -91,8 +105,6 @@ namespace { // Save draft to the cloud with 1 sec extra delay. constexpr auto kSaveCloudDraftTimeout = 1000; -constexpr auto kTopPromotionInterval = TimeId(60 * 60); -constexpr auto kTopPromotionMinDelay = TimeId(10); constexpr auto kSmallDelayMs = 5; constexpr auto kReadFeaturedSetsTimeout = crl::time(1000); constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000); @@ -106,10 +118,6 @@ using PhotoFileLocationId = Data::PhotoFileLocationId; using DocumentFileLocationId = Data::DocumentFileLocationId; using UpdatedFileReferences = Data::UpdatedFileReferences; -[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) { - return TimeId(msgId >> 32); -} - [[nodiscard]] std::shared_ptr ShowForPeer( not_null peer) { if (const auto window = Core::App().windowFor(peer)) { @@ -142,8 +150,26 @@ void ShowChannelsLimitBox(not_null peer) { action.replaceMediaOf); } +[[nodiscard]] QString FormatVideoTimestamp(TimeId seconds) { + const auto minutes = seconds / 60; + const auto hours = minutes / 60; + return hours + ? u"%1h%2m%3s"_q.arg(hours).arg(minutes % 60).arg(seconds % 60) + : minutes + ? u"%1m%2s"_q.arg(minutes).arg(seconds % 60) + : QString::number(seconds); +} + } // namespace +namespace Api { + +TimeId UnixtimeFromMsgId(mtpMsgId msgId) { + return TimeId(msgId >> 32); +} + +} // namespace Api + ApiWrap::ApiWrap(not_null session) : MTP::Sender(&session->account().mtp()) , _session(session) @@ -153,7 +179,6 @@ ApiWrap::ApiWrap(not_null session) , _featuredSetsReadTimer([=] { readFeaturedSets(); }) , _dialogsLoadState(std::make_unique()) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) -, _topPromotionTimer([=] { refreshTopPromotion(); }) , _updateNotifyTimer([=] { sendNotifySettingsUpdates(); }) , _statsSessionKillTimer([=] { checkStatsSessions(); }) , _authorizations(std::make_unique(this)) @@ -163,16 +188,21 @@ ApiWrap::ApiWrap(not_null session) , _selfDestruct(std::make_unique(this)) , _sensitiveContent(std::make_unique(this)) , _globalPrivacy(std::make_unique(this)) +, _reactionsNotifySettings( + std::make_unique(this)) , _userPrivacy(std::make_unique(this)) , _inviteLinks(std::make_unique(this)) , _chatLinks(std::make_unique(this)) , _views(std::make_unique(this)) +, _readMetrics(std::make_unique(this)) , _confirmPhone(std::make_unique(this)) , _peerPhoto(std::make_unique(this)) , _polls(std::make_unique(this)) +, _todoLists(std::make_unique(this)) , _chatParticipants(std::make_unique(this)) , _unreadThings(std::make_unique(this)) , _ringtones(std::make_unique(this)) +, _composeWithAi(std::make_unique(this)) , _transcribes(std::make_unique(this)) , _premium(std::make_unique(this)) , _usernames(std::make_unique(this)) @@ -184,21 +214,38 @@ ApiWrap::ApiWrap(not_null session) _session->data().chatsFilters().changed( ) | rpl::filter([=] { return _session->data().chatsFilters().archiveNeeded(); - }) | rpl::start_with_next([=] { + }) | rpl::on_next([=] { requestMoreDialogsIfNeeded(); }, _session->lifetime()); + _reactionsNotifySettings->reload(); setupSupportMode(); - - Core::App().settings().proxy().connectionTypeValue( - ) | rpl::start_with_next([=] { - refreshTopPromotion(); - }, _session->lifetime()); }); } ApiWrap::~ApiWrap() = default; +void ApiWrap::ProcessRecentSelfForwards( + not_null session, + const MTPUpdates &updates, + PeerId targetPeerId, + PeerId fromPeerId) { + auto newIds = MessageIdsList(); + updates.match([&](const MTPDupdates &data) { + for (const auto &update : data.vupdates().v) { + update.match([&](const MTPDupdateMessageID &d) { + newIds.push_back(FullMsgId(targetPeerId, d.vid().v)); + }, [](const auto &) {}); + } + }, [](const auto &) {}); + if (!newIds.empty()) { + session->data().addRecentSelfForwards({ + .fromPeerId = fromPeerId, + .ids = newIds, + }); + } +} + Main::Session &ApiWrap::session() const { return *_session; } @@ -217,7 +264,7 @@ void ApiWrap::setupSupportMode() { } _session->settings().supportChatsTimeSliceValue( - ) | rpl::start_with_next([=](int seconds) { + ) | rpl::on_next([=](int seconds) { _dialogsLoadTill = seconds ? std::max(base::unixtime::now() - seconds, 0) : 0; refreshDialogsLoadBlocked(); }, _session->lifetime()); @@ -233,73 +280,6 @@ void ApiWrap::requestChangelog( //).send(); } -void ApiWrap::refreshTopPromotion() { - const auto now = base::unixtime::now(); - const auto next = (_topPromotionNextRequestTime != 0) - ? _topPromotionNextRequestTime - : now; - if (_topPromotionRequestId) { - getTopPromotionDelayed(now, next); - return; - } - const auto key = [&]() -> std::pair { - if (!Core::App().settings().proxy().isEnabled()) { - return {}; - } - const auto &proxy = Core::App().settings().proxy().selected(); - if (proxy.type != MTP::ProxyData::Type::Mtproto) { - return {}; - } - return { proxy.host, proxy.port }; - }(); - if (_topPromotionKey == key && now < next) { - getTopPromotionDelayed(now, next); - return; - } - _topPromotionKey = key; - _topPromotionRequestId = request(MTPhelp_GetPromoData( - )).done([=](const MTPhelp_PromoData &result) { - _topPromotionRequestId = 0; - topPromotionDone(result); - }).fail([=] { - _topPromotionRequestId = 0; - const auto now = base::unixtime::now(); - const auto next = _topPromotionNextRequestTime = now - + kTopPromotionInterval; - if (!_topPromotionTimer.isActive()) { - getTopPromotionDelayed(now, next); - } - }).send(); -} - -void ApiWrap::getTopPromotionDelayed(TimeId now, TimeId next) { - _topPromotionTimer.callOnce(std::min( - std::max(next - now, kTopPromotionMinDelay), - kTopPromotionInterval) * crl::time(1000)); -}; - -void ApiWrap::topPromotionDone(const MTPhelp_PromoData &proxy) { - _topPromotionNextRequestTime = proxy.match([&](const auto &data) { - return data.vexpires().v; - }); - getTopPromotionDelayed( - base::unixtime::now(), - _topPromotionNextRequestTime); - - proxy.match([&](const MTPDhelp_promoDataEmpty &data) { - _session->data().setTopPromoted(nullptr, QString(), QString()); - }, [&](const MTPDhelp_promoData &data) { - _session->data().processChats(data.vchats()); - _session->data().processUsers(data.vusers()); - const auto peerId = peerFromMTP(data.vpeer()); - const auto history = _session->data().history(peerId); - _session->data().setTopPromoted( - history, - data.vpsa_type().value_or_empty(), - data.vpsa_message().value_or_empty()); - }); -} - void ApiWrap::requestDeepLinkInfo( const QString &path, Fn callback) { @@ -386,7 +366,7 @@ void ApiWrap::checkChatInvite( request(base::take(_checkInviteRequestId)).cancel(); _checkInviteRequestId = request(MTPmessages_CheckChatInvite( MTP_string(hash) - )).done(std::move(done)).fail(std::move(fail)).send(); + )).done(std::move(done)).fail(std::move(fail)).handleFloodErrors().send(); } void ApiWrap::checkFilterInvite( @@ -403,7 +383,7 @@ void ApiWrap::savePinnedOrder(Data::Folder *folder) { const auto &order = _session->data().pinnedChatsOrder(folder); const auto input = [](Dialogs::Key key) { if (const auto history = key.history()) { - return MTP_inputDialogPeer(history->peer->input); + return MTP_inputDialogPeer(history->peer->input()); } else if (const auto folder = key.folder()) { return MTP_inputDialogPeerFolder(MTP_int(folder->id())); } @@ -436,9 +416,9 @@ void ApiWrap::savePinnedOrder(not_null forum) { order, ranges::back_inserter(topics), input); - request(MTPchannels_ReorderPinnedForumTopics( - MTP_flags(MTPchannels_ReorderPinnedForumTopics::Flag::f_force), - forum->channel()->inputChannel, + request(MTPmessages_ReorderPinnedForumTopics( + MTP_flags(MTPmessages_ReorderPinnedForumTopics::Flag::f_force), + forum->peer()->input(), MTP_vector(topics) )).done([=](const MTPUpdates &result) { applyUpdates(result); @@ -446,10 +426,13 @@ void ApiWrap::savePinnedOrder(not_null forum) { } void ApiWrap::savePinnedOrder(not_null saved) { + if (saved->parentChat()) { + return; + } const auto &order = _session->data().pinnedChatsOrder(saved); const auto input = [](Dialogs::Key key) { if (const auto sublist = key.sublist()) { - return MTP_inputDialogPeer(sublist->peer()->input); + return MTP_inputDialogPeer(sublist->sublistPeer()->input()); } Unexpected("Key type in pinnedDialogsOrder()."); }; @@ -478,7 +461,7 @@ void ApiWrap::toggleHistoryArchived( MTP_vector( 1, MTP_inputFolderPeer( - history->peer->input, + history->peer->input(), MTP_int(archived ? archiveId : 0))) )).done([=](const MTPUpdates &result) { applyUpdates(result); @@ -513,13 +496,14 @@ void ApiWrap::sendMessageFail( uint64 randomId, FullMsgId itemId) { const auto show = ShowForPeer(peer); + const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q; if (show && error == u"PEER_FLOOD"_q) { show->showBox( Ui::MakeInformBox( PeerFloodErrorText(&session(), PeerFloodType::Send)), Ui::LayerOption::CloseOther); } else if (show && error == u"USER_BANNED_IN_CHANNEL"_q) { - const auto link = Ui::Text::Link( + const auto link = tr::link( tr::lng_cant_more_info(tr::now), session().createInternalLinkFull(u"spambot"_q)); show->showBox( @@ -528,7 +512,7 @@ void ApiWrap::sendMessageFail( tr::now, lt_more_info, link, - Ui::Text::WithEntities)), + tr::marked)), Ui::LayerOption::CloseOther); } else if (error.startsWith(u"SLOWMODE_WAIT_"_q)) { const auto chop = u"SLOWMODE_WAIT_"_q.size(); @@ -556,9 +540,58 @@ void ApiWrap::sendMessageFail( } else if (show && error == u"CHAT_FORWARDS_RESTRICTED"_q) { show->showToast(peer->isBroadcast() ? tr::lng_error_noforwards_channel(tr::now) + : peer->isUser() + ? tr::lng_error_noforwards_user(tr::now) : tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration); } else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) { Settings::ShowPremium(&session(), "premium_stickers"); + } else if (error == u"SCHEDULE_TOO_MUCH"_q) { + auto &scheduled = _session->scheduledMessages(); + if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) { + scheduled.removeSending(item); + } + if (show) { + show->showToast(tr::lng_error_schedule_limit(tr::now)); + } + } else if (error.startsWith(paidStarsPrefix)) { + if (show) { + show->showToast( + u"Payment requirements changed. Please, try again."_q); + } + if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) { + if (const auto user = peer->asUser()) { + user->setStarsPerMessage(stars); + } else if (const auto channel = peer->asChannel()) { + channel->setStarsPerMessage(stars); + } + } + peer->updateFull(); + } else if (error == u"BALANCE_TOO_LOW"_q) { + const auto item = _session->data().message(itemId); + const auto stake = (item && item->media()) + ? item->media()->diceGameOutcome().stakeNanoTon + : int64(0); + if (stake > 0) { + const auto required = CreditsAmount( + stake / Ui::kNanosInOne, + stake % Ui::kNanosInOne, + CreditsType::Ton); + if (randomId) { + _session->data().unregisterMessageRandomId(randomId); + } + item->destroy(); + if (show) { + show->show(Box( + Ui::InsufficientTonBox, + _session, + required)); + } + return; + } else if (show) { + show->showToast(error); + } + } else if (show) { + show->showToast(error); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); @@ -652,7 +685,7 @@ void ApiWrap::resolveMessageDatas() { if (!ids.isEmpty()) { const auto channel = j->first; const auto requestId = request(MTPchannels_GetMessages( - channel->inputChannel, + channel->inputChannel(), MTP_vector(ids) )).done([=]( const MTPmessages_Messages &result, @@ -708,7 +741,9 @@ void ApiWrap::finalizeMessageDataRequest( QString ApiWrap::exportDirectMessageLink( not_null item, - bool inRepliesContext) { + bool inRepliesContext, + bool forceNonPublicLink, + std::optional videoTimestamp) { Expects(item->history()->peer->isChannel()); const auto itemId = item->fullId(); @@ -731,7 +766,7 @@ QString ApiWrap::exportDirectMessageLink( const auto sender = root ? root->discussionPostOriginalSender() : nullptr; - if (sender && sender->hasUsername()) { + if (sender && sender->hasUsername() && !forceNonPublicLink) { // Comment to a public channel. const auto forwarded = root->Get(); linkItemId = forwarded->savedFromMsgId; @@ -747,7 +782,7 @@ QString ApiWrap::exportDirectMessageLink( } } } - const auto base = linkChannel->hasUsername() + const auto base = (linkChannel->hasUsername() && !forceNonPublicLink) ? linkChannel->username() : "c/" + QString::number(peerToChannel(linkChannel->id).bare); const auto post = QString::number(linkItemId.bare); @@ -760,20 +795,11 @@ QString ApiWrap::exportDirectMessageLink( : linkThreadId ? (QString::number(linkThreadId.bare) + '/' + post) : post); - if (linkChannel->hasUsername() - && !linkChannel->isMegagroup() - && !linkCommentId - && !linkThreadId) { - if (const auto media = item->media()) { - if (const auto document = media->document()) { - if (document->isVideoMessage()) { - return u"https://telesco.pe/"_q + query; - } - } - } - } return session().createInternalLinkFull(query); }; + if (forceNonPublicLink) { + return fallback(); + } const auto i = _unlikelyMessageLinks.find(itemId); const auto current = (i != end(_unlikelyMessageLinks)) ? i->second @@ -782,7 +808,7 @@ QString ApiWrap::exportDirectMessageLink( MTP_flags(inRepliesContext ? MTPchannels_ExportMessageLink::Flag::f_thread : MTPchannels_ExportMessageLink::Flag(0)), - channel->inputChannel, + channel->inputChannel(), MTP_int(item->id) )).done([=](const MTPExportedMessageLink &result) { const auto link = qs(result.data().vlink()); @@ -790,7 +816,14 @@ QString ApiWrap::exportDirectMessageLink( _unlikelyMessageLinks.emplace_or_assign(itemId, link); } }).send(); - return current; + const auto addTimestamp = channel->hasUsername() + && !inRepliesContext + && videoTimestamp.has_value(); + const auto addedSeparator = (current.indexOf('?') >= 0) ? '&' : '?'; + const auto addedTimestamp = addTimestamp + ? (addedSeparator + u"t="_q + FormatVideoTimestamp(*videoTimestamp)) + : QString(); + return current + addedTimestamp; } QString ApiWrap::exportDirectStoryLink(not_null story) { @@ -798,8 +831,10 @@ QString ApiWrap::exportDirectStoryLink(not_null story) { const auto peer = story->peer(); const auto fallback = [&] { const auto base = peer->username(); - const auto story = QString::number(storyId.story); - const auto query = base + "/s/" + story; + const auto id = story->call() + ? u"live"_q + : QString::number(storyId.story); + const auto query = base + "/s/" + id; return session().createInternalLinkFull(query); }; const auto i = _unlikelyStoryLinks.find(storyId); @@ -807,7 +842,7 @@ QString ApiWrap::exportDirectStoryLink(not_null story) { ? i->second : fallback(); request(MTPstories_ExportStoryLink( - peer->input, + peer->input(), MTP_int(story->id()) )).done([=](const MTPExportedStoryLink &result) { const auto link = qs(result.data().vlink()); @@ -874,7 +909,7 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { MTP_int(state->offsetDate), MTP_int(state->offsetId), (state->offsetPeer - ? state->offsetPeer->input + ? state->offsetPeer->input() : MTP_inputPeerEmpty()), MTP_int(loadCount), MTP_long(hash) @@ -1125,6 +1160,8 @@ void ApiWrap::requestWallPaper( void ApiWrap::requestFullPeer(not_null peer) { if (_fullPeerRequests.contains(peer)) { return; + } else if (!peer->isUser() && !peer->barSettings().has_value()) { + requestPeerSettings(peer); } const auto requestId = [&] { @@ -1137,7 +1174,7 @@ void ApiWrap::requestFullPeer(not_null peer) { _session->supportHelper().refreshInfo(user); } return request(MTPusers_GetFullUser( - user->inputUser + user->inputUser() )).done([=](const MTPusers_UserFull &result) { result.match([&](const MTPDusers_userFull &data) { _session->data().processUsers(data.vusers()); @@ -1147,13 +1184,13 @@ void ApiWrap::requestFullPeer(not_null peer) { }).fail(failHandler).send(); } else if (const auto chat = peer->asChat()) { return request(MTPmessages_GetFullChat( - chat->inputChat + chat->inputChat() )).done([=](const MTPmessages_ChatFull &result) { gotChatFull(peer, result); }).fail(failHandler).send(); } else if (const auto channel = peer->asChannel()) { return request(MTPchannels_GetFullChannel( - channel->inputChannel + channel->inputChannel() )).done([=](const MTPmessages_ChatFull &result) { gotChatFull(peer, result); migrateDone(channel, channel); @@ -1225,9 +1262,13 @@ void ApiWrap::gotUserFull( void ApiWrap::requestPeerSettings(not_null peer) { if (!_requestedPeerSettings.emplace(peer).second) { return; + } else if (peer->isMonoforum()) { + peer->setBarSettings(PeerBarSettings()); + _requestedPeerSettings.erase(peer); + return; } request(MTPmessages_GetPeerSettings( - peer->input + peer->input() )).done([=](const MTPmessages_PeerSettings &result) { result.match([&](const MTPDmessages_peerSettings &data) { _session->data().processUsers(data.vusers()); @@ -1236,6 +1277,7 @@ void ApiWrap::requestPeerSettings(not_null peer) { _requestedPeerSettings.erase(peer); }); }).fail([=] { + peer->setBarSettings(PeerBarSettings()); _requestedPeerSettings.erase(peer); }).send(); } @@ -1281,7 +1323,7 @@ void ApiWrap::migrateChat( } request(MTPmessages_MigrateChat( - chat->inputChat + chat->inputChat() )).done([=](const MTPUpdates &result) { applyUpdates(result); session().changes().sendNotifications(); @@ -1353,7 +1395,7 @@ void ApiWrap::markContentsRead( } for (const auto &channelIds : channelMarkedIds) { request(MTPchannels_ReadMessageContents( - channelIds.first->inputChannel, + channelIds.first->inputChannel(), MTP_vector(channelIds.second) )).send(); } @@ -1366,7 +1408,7 @@ void ApiWrap::markContentsRead(not_null item) { const auto ids = MTP_vector(1, MTP_int(item->id)); if (const auto channel = item->history()->peer->asChannel()) { request(MTPchannels_ReadMessageContents( - channel->inputChannel, + channel->inputChannel(), ids )).send(); } else { @@ -1400,8 +1442,8 @@ void ApiWrap::deleteAllFromParticipantSend( not_null channel, not_null from) { request(MTPchannels_DeleteParticipantHistory( - channel->inputChannel, - from->input + channel->inputChannel(), + from->input() )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = applyAffectedHistory(channel, result); if (offset > 0) { @@ -1412,6 +1454,69 @@ void ApiWrap::deleteAllFromParticipantSend( }).send(); } +void ApiWrap::deleteAllReactionsFromParticipant( + not_null peer, + not_null participant, + MsgId originMsgId, + const Data::ReactionId &originReaction) { + _session->data().removeReactionsFromParticipant( + peer, + 0, + participant, + originReaction, + originMsgId); + request(MTPmessages_DeleteParticipantReactions( + peer->input(), + participant->input() + )).send(); +} + +void ApiWrap::deleteParticipantReaction( + not_null peer, + MsgId msgId, + not_null participant, + const Data::ReactionId &reaction) { + _session->data().removeReactionsFromParticipant( + peer, + msgId, + participant, + reaction, + 0); + request(MTPmessages_DeleteParticipantReaction( + peer->input(), + MTP_int(msgId.bare), + participant->input() + )).done([=](const MTPUpdates &result) { + applyUpdates(result); + }).send(); +} + +void ApiWrap::deleteSublistHistory( + not_null channel, + not_null sublistPeer) { + deleteSublistHistorySend(channel, sublistPeer); +} + +void ApiWrap::deleteSublistHistorySend( + not_null parentChat, + not_null sublistPeer) { + request(MTPmessages_DeleteSavedHistory( + MTP_flags(MTPmessages_DeleteSavedHistory::Flag::f_parent_peer), + parentChat->input(), + sublistPeer->input(), + MTP_int(0), // max_id + MTP_int(0), // min_date + MTP_int(0) // max_date + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = applyAffectedHistory(parentChat, result); + if (offset > 0) { + deleteSublistHistorySend(parentChat, sublistPeer); + } else if (const auto monoforum = parentChat->monoforum()) { + monoforum->applySublistDeleted(sublistPeer); + } + }).send(); +} + void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { if (!_stickerSetRequests.contains(setId)) { _stickerSetRequests.emplace(setId, StickerSetRequest{ access }); @@ -1719,10 +1824,21 @@ void ApiWrap::joinChannel(not_null channel) { Data::PeerUpdate::Flag::ChannelAmIn); } else if (!_channelAmInRequests.contains(channel)) { const auto requestId = request(MTPchannels_JoinChannel( - channel->inputChannel - )).done([=](const MTPUpdates &result) { + channel->inputChannel() + )).done([=](const MTPmessages_ChatInviteJoinResult &result) { _channelAmInRequests.remove(channel); - applyUpdates(result); + + Api::ProcessChatInviteJoinResult( + _session, + ShowForPeer(channel), + result, + [=](const MTPUpdates &updates) { + applyUpdates(updates); + session().data().addRecentJoinChat({ + .fromPeerId = channel->id, + .joinedPeerId = channel->id, + }); + }); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); @@ -1759,7 +1875,7 @@ void ApiWrap::joinChannel(not_null channel) { _channelAmInRequests.emplace(channel, requestId); using Flag = ChannelDataFlag; - chatParticipants().loadSimilarChannels(channel); + chatParticipants().loadSimilarPeers(channel); channel->setFlags(channel->flags() | Flag::SimilarExpanded); } } @@ -1771,7 +1887,7 @@ void ApiWrap::leaveChannel(not_null channel) { Data::PeerUpdate::Flag::ChannelAmIn); } else if (!_channelAmInRequests.contains(channel)) { auto requestId = request(MTPchannels_LeaveChannel( - channel->inputChannel + channel->inputChannel() )).done([=](const MTPUpdates &result) { _channelAmInRequests.remove(channel); applyUpdates(result); @@ -1814,12 +1930,14 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { return PeerId(0); }, [](const MTPDinputPeerChannel &data) { return peerFromChannel(data.vchannel_id()); + }, [](const MTPDinputPeerChannelFromMessage &data) { + return peerFromChannel(data.vchannel_id()); }, [](const MTPDinputPeerChat &data) { return peerFromChat(data.vchat_id()); }, [](const MTPDinputPeerUser &data) { return peerFromUser(data.vuser_id()); - }, [](const auto &) -> PeerId { - Unexpected("Type in ApiRequest::requestNotifySettings peer."); + }, [](const MTPDinputPeerUserFromMessage &data) { + return peerFromUser(data.vuser_id()); }); }; const auto key = peer.match([](const MTPDinputNotifyUsers &) { @@ -1873,7 +1991,7 @@ void ApiWrap::updateNotifySettingsDelayed( } if (_updateNotifyTopics.emplace(topic).second) { topic->destroyed( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _updateNotifyTopics.remove(topic); }, _updateNotifyQueueLifetime); _updateNotifyTimer.callOnce(kNotifySettingSaveTimeout); @@ -1894,17 +2012,17 @@ void ApiWrap::updateNotifySettingsDelayed(Data::DefaultNotify type) { void ApiWrap::sendNotifySettingsUpdates() { _updateNotifyQueueLifetime.destroy(); - for (const auto topic : base::take(_updateNotifyTopics)) { + for (const auto &topic : base::take(_updateNotifyTopics)) { request(MTPaccount_UpdateNotifySettings( MTP_inputNotifyForumTopic( - topic->channel()->input, + topic->peer()->input(), MTP_int(topic->rootId())), topic->notify().serialize() )).afterDelay(kSmallDelayMs).send(); } - for (const auto peer : base::take(_updateNotifyPeers)) { + for (const auto &peer : base::take(_updateNotifyPeers)) { request(MTPaccount_UpdateNotifySettings( - MTP_inputNotifyPeer(peer->input), + MTP_inputNotifyPeer(peer->input()), peer->notify().serialize() )).afterDelay(kSmallDelayMs).send(); } @@ -1983,8 +2101,8 @@ void ApiWrap::deleteConversation(not_null peer, bool revoke) { if (const auto chat = peer->asChat()) { request(MTPmessages_DeleteChatUser( MTP_flags(0), - chat->inputChat, - _session->user()->inputUser + chat->inputChat(), + _session->user()->inputUser() )).done([=](const MTPUpdates &result) { applyUpdates(result); deleteHistory(peer, false, revoke); @@ -2103,8 +2221,13 @@ void ApiWrap::saveCurrentDraftToCloud() { _session->local().writeDrafts(history); const auto topicRootId = thread->topicRootId(); - const auto localDraft = history->localDraft(topicRootId); - const auto cloudDraft = history->cloudDraft(topicRootId); + const auto monoforumPeerId = thread->monoforumPeerId(); + const auto localDraft = history->localDraft( + topicRootId, + monoforumPeerId); + const auto cloudDraft = history->cloudDraft( + topicRootId, + monoforumPeerId); if (!Data::DraftsAreEqual(localDraft, cloudDraft) && !_session->supportMode()) { saveDraftToCloudDelayed(thread); @@ -2127,15 +2250,22 @@ void ApiWrap::saveDraftsToCloud() { const auto history = thread->owningHistory(); const auto topicRootId = thread->topicRootId(); - auto cloudDraft = history->cloudDraft(topicRootId); - auto localDraft = history->localDraft(topicRootId); + const auto monoforumPeerId = thread->monoforumPeerId(); + auto cloudDraft = history->cloudDraft(topicRootId, monoforumPeerId); + auto localDraft = history->localDraft(topicRootId, monoforumPeerId); if (cloudDraft && cloudDraft->saveRequestId) { request(base::take(cloudDraft->saveRequestId)).cancel(); } if (!_session->supportMode()) { - cloudDraft = history->createCloudDraft(topicRootId, localDraft); + cloudDraft = history->createCloudDraft( + topicRootId, + monoforumPeerId, + localDraft); } else if (!cloudDraft) { - cloudDraft = history->createCloudDraft(topicRootId, nullptr); + cloudDraft = history->createCloudDraft( + topicRootId, + monoforumPeerId, + nullptr); } auto flags = MTPmessages_SaveDraft::Flags(0); @@ -2145,37 +2275,48 @@ void ApiWrap::saveDraftsToCloud() { } else if (!cloudDraft->webpage.url.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_media; } - if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) { + if (cloudDraft->reply.messageId + || cloudDraft->reply.topicRootId + || cloudDraft->reply.monoforumPeerId) { flags |= MTPmessages_SaveDraft::Flag::f_reply_to; } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; } + if (cloudDraft->suggest) { + flags |= MTPmessages_SaveDraft::Flag::f_suggested_post; + } auto entities = Api::EntitiesToMTP( _session, TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), Api::ConvertOption::SkipLocal); - history->startSavingCloudDraft(topicRootId); + history->startSavingCloudDraft(topicRootId, monoforumPeerId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), ReplyToForMTP(history, cloudDraft->reply), - history->peer->input, + history->peer->input(), MTP_string(textWithTags.text), entities, Data::WebPageForMTP( cloudDraft->webpage, textWithTags.text.isEmpty()), - MTP_long(0) // effect + MTP_long(0), // effect + Api::SuggestToMTP(cloudDraft->suggest), + MTPInputRichMessage() )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( topicRootId, - UnixtimeFromMsgId(response.outerMsgId)); - if (const auto cloudDraft = history->cloudDraft(topicRootId)) { + monoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + const auto cloudDraft = history->cloudDraft( + topicRootId, + monoforumPeerId); + if (cloudDraft) { if (cloudDraft->saveRequestId == requestId) { cloudDraft->saveRequestId = 0; - history->draftSavedToCloud(topicRootId); + history->draftSavedToCloud(topicRootId, monoforumPeerId); } } const auto i = _draftsSaveRequestIds.find(weak); @@ -2188,10 +2329,14 @@ void ApiWrap::saveDraftsToCloud() { const auto requestId = response.requestId; history->finishSavingCloudDraft( topicRootId, - UnixtimeFromMsgId(response.outerMsgId)); - if (const auto cloudDraft = history->cloudDraft(topicRootId)) { + monoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + const auto cloudDraft = history->cloudDraft( + topicRootId, + monoforumPeerId); + if (cloudDraft) { if (cloudDraft->saveRequestId == requestId) { - history->clearCloudDraft(topicRootId); + history->clearCloudDraft(topicRootId, monoforumPeerId); } } const auto i = _draftsSaveRequestIds.find(weak); @@ -2327,7 +2472,7 @@ void ApiWrap::resolveWebPages() { QVector reqsByIndex(idsByChannel.size(), 0); for (auto i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) { reqsByIndex[i->second.first] = request(MTPchannels_GetMessages( - i->first->inputChannel, + i->first->inputChannel(), MTP_vector(i->second.second) )).done([=, channel = i->first]( const MTPmessages_Messages &result, @@ -2442,18 +2587,46 @@ void ApiWrap::refreshFileReference( }; v::match(origin.data, [&](Data::FileOriginMessage data) { if (const auto item = _session->data().message(data)) { + if (const auto iv = item->Get()) { + if (!iv->url.isEmpty()) { + return refreshFileReference( + Data::FileOriginWebPage{ iv->url }, + std::move(handler)); + } + } const auto media = item->media(); - const auto storyId = media ? media->storyId() : FullStoryId(); + const auto mediaStory = media ? media->storyId() : FullStoryId(); + const auto storyId = mediaStory + ? mediaStory + : FullStoryId{ + (IsStoryMsgId(item->id) + ? item->history()->peer->id + : PeerId()), + (IsStoryMsgId(item->id) + ? StoryIdFromMsgId(item->id) + : StoryId()) + }; if (storyId) { request(MTPstories_GetStoriesByID( - _session->data().peer(storyId.peer)->input, + _session->data().peer(storyId.peer)->input(), MTP_vector(1, MTP_int(storyId.story)))); } else if (item->isScheduled()) { const auto realId = _session->scheduledMessages().lookupId( item); request(MTPmessages_GetScheduledMessages( - item->history()->peer->input, + item->history()->peer->input(), MTP_vector(1, MTP_int(realId)))); + } else if (item->isSavedMusicItem()) { + const auto user = item->history()->peer->asUser(); + const auto media = item->media(); + const auto document = media ? media->document() : nullptr; + if (user && document) { + request(MTPusers_GetSavedMusicByID( + user->inputUser(), + MTP_vector(1, document->mtpInput()))); + } else { + fail(); + } } else if (item->isBusinessShortcut()) { const auto &shortcuts = _session->data().shortcutMessages(); const auto realId = shortcuts.lookupId(item); @@ -2464,7 +2637,7 @@ void ApiWrap::refreshFileReference( MTP_long(0))); } else if (const auto channel = item->history()->peer->asChannel()) { request(MTPchannels_GetMessages( - channel->inputChannel, + channel->inputChannel(), MTP_vector( 1, MTP_inputMessageID(MTP_int(item->id))))); @@ -2480,7 +2653,7 @@ void ApiWrap::refreshFileReference( }, [&](Data::FileOriginUserPhoto data) { if (const auto user = _session->data().user(data.userId)) { request(MTPphotos_GetUserPhotos( - user->inputUser, + user->inputUser(), MTP_int(-1), MTP_long(data.photoId), MTP_int(1))); @@ -2489,12 +2662,20 @@ void ApiWrap::refreshFileReference( } }, [&](Data::FileOriginFullUser data) { if (const auto user = _session->data().user(data.userId)) { - request(MTPusers_GetFullUser(user->inputUser)); + request(MTPusers_GetFullUser(user->inputUser())); } else { fail(); } }, [&](Data::FileOriginPeerPhoto data) { - fail(); + const auto peer = _session->data().peer(data.peerId); + if (const auto channel = peer->asChannel()) { + request(MTPchannels_GetFullChannel( + channel->inputChannel())); + } else if (const auto chat = peer->asChat()) { + request(MTPmessages_GetFullChat(chat->inputChat())); + } else { + fail(); + } }, [&](Data::FileOriginStickerSet data) { const auto isRecentAttached = (data.setId == Data::Stickers::CloudRecentAttachedSetId); @@ -2558,14 +2739,17 @@ void ApiWrap::refreshFileReference( MTP_int(0))); }, [&](Data::FileOriginStory data) { request(MTPstories_GetStoriesByID( - _session->data().peer(data.peer)->input, + _session->data().peer(data.peer)->input(), MTP_vector(1, MTP_int(data.story)))); }, [&](v::null_t) { fail(); }); } -void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req) { +void ApiWrap::gotWebPages( + ChannelData *channel, + const MTPmessages_Messages &result, + mtpRequestId req) { WebPageData::ApplyChanges(_session, channel, result); for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) { if (i->second == req) { @@ -2579,7 +2763,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu ++i; } } - _session->data().sendWebPageGamePollNotifications(); + _session->data().sendWebPageGamePollTodoListNotifications(); } void ApiWrap::updateStickers() { @@ -2625,7 +2809,7 @@ void ApiWrap::setGroupStickerSet( megagroup->mgInfo->stickerSet = set; request(MTPchannels_SetStickers( - megagroup->inputChannel, + megagroup->inputChannel(), Data::InputStickerSet(set) )).send(); _session->data().stickers().notifyUpdated(Data::StickersType::Stickers); @@ -2638,7 +2822,7 @@ void ApiWrap::setGroupEmojiSet( megagroup->mgInfo->emojiSet = set; request(MTPchannels_SetEmojiStickers( - megagroup->inputChannel, + megagroup->inputChannel(), Data::InputStickerSet(set) )).send(); _session->changes().peerUpdated( @@ -2969,17 +3153,27 @@ void ApiWrap::resolveJumpToDate( Fn, MsgId)> callback) { if (const auto peer = chat.peer()) { const auto topic = chat.topic(); - const auto rootId = topic ? topic->rootId() : 0; - resolveJumpToHistoryDate(peer, rootId, date, std::move(callback)); + const auto sublist = chat.sublist(); + const auto rootId = topic ? topic->rootId() : MsgId(); + const auto monoforumPeerId = sublist + ? sublist->sublistPeer()->id + : PeerId(); + resolveJumpToHistoryDate( + peer, + rootId, + monoforumPeerId, + date, + std::move(callback)); } } template void ApiWrap::requestMessageAfterDate( - not_null peer, - MsgId topicRootId, - const QDate &date, - Callback &&callback) { + not_null peer, + MsgId topicRootId, + PeerId monoforumPeerId, + const QDate &date, + Callback &&callback) { // API returns a message with date <= offset_date. // So we request a message with offset_date = desired_date - 1 and add_offset = -1. // This should give us the first message with date >= desired_date. @@ -3003,17 +3197,19 @@ void ApiWrap::requestMessageAfterDate( }; const auto list = result.match([&]( const MTPDmessages_messages &data) { + peer->processTopics(data.vtopics()); return handleMessages(data); }, [&](const MTPDmessages_messagesSlice &data) { + peer->processTopics(data.vtopics()); return handleMessages(data); }, [&](const MTPDmessages_channelMessages &data) { if (const auto channel = peer->asChannel()) { channel->ptsReceived(data.vpts().v); - channel->processTopics(data.vtopics()); } else { LOG(("API Error: received messages.channelMessages when " "no channel was passed! (ApiWrap::jumpToDate)")); } + peer->processTopics(data.vtopics()); return handleMessages(data); }, [&](const MTPDmessages_messagesNotModified &) { LOG(("API Error: received messages.messagesNotModified! " @@ -3036,7 +3232,7 @@ void ApiWrap::requestMessageAfterDate( }; if (topicRootId) { send(MTPmessages_GetReplies( - peer->input, + peer->input(), MTP_int(topicRootId), MTP_int(offsetId), MTP_int(offsetDate), @@ -3045,9 +3241,21 @@ void ApiWrap::requestMessageAfterDate( MTP_int(maxId), MTP_int(minId), MTP_long(historyHash))); + } else if (monoforumPeerId) { + send(MTPmessages_GetSavedHistory( + MTP_flags(MTPmessages_GetSavedHistory::Flag::f_parent_peer), + peer->input(), + session().data().peer(monoforumPeerId)->input(), + MTP_int(offsetId), + MTP_int(offsetDate), + MTP_int(addOffset), + MTP_int(limit), + MTP_int(maxId), + MTP_int(minId), + MTP_long(historyHash))); } else { send(MTPmessages_GetHistory( - peer->input, + peer->input(), MTP_int(offsetId), MTP_int(offsetDate), MTP_int(addOffset), @@ -3061,28 +3269,41 @@ void ApiWrap::requestMessageAfterDate( void ApiWrap::resolveJumpToHistoryDate( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, const QDate &date, Fn, MsgId)> callback) { if (const auto channel = peer->migrateTo()) { return resolveJumpToHistoryDate( channel, topicRootId, + monoforumPeerId, date, std::move(callback)); } const auto jumpToDateInPeer = [=] { - requestMessageAfterDate(peer, topicRootId, date, [=](MsgId itemId) { - callback(peer, itemId); - }); + requestMessageAfterDate( + peer, + topicRootId, + monoforumPeerId, + date, + [=](MsgId itemId) { callback(peer, itemId); }); }; - if (const auto chat = topicRootId ? nullptr : peer->migrateFrom()) { - requestMessageAfterDate(chat, 0, date, [=](MsgId itemId) { - if (itemId) { - callback(chat, itemId); - } else { - jumpToDateInPeer(); - } - }); + const auto migrated = (topicRootId || monoforumPeerId) + ? nullptr + : peer->migrateFrom(); + if (migrated) { + requestMessageAfterDate( + migrated, + MsgId(), + PeerId(), + date, + [=](MsgId itemId) { + if (itemId) { + callback(migrated, itemId); + } else { + jumpToDateInPeer(); + } + }); } else { jumpToDateInPeer(); } @@ -3131,12 +3352,14 @@ void ApiWrap::requestHistory( void ApiWrap::requestSharedMedia( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, SharedMediaType type, MsgId messageId, SliceType slice) { const auto key = SharedMediaRequest{ peer, topicRootId, + monoforumPeerId, type, messageId, slice, @@ -3148,6 +3371,7 @@ void ApiWrap::requestSharedMedia( const auto prepared = Api::PrepareSearchRequest( peer, topicRootId, + monoforumPeerId, type, QString(), messageId, @@ -3170,7 +3394,12 @@ void ApiWrap::requestSharedMedia( messageId, slice, result); - sharedMediaDone(peer, topicRootId, type, std::move(parsed)); + sharedMediaDone( + peer, + topicRootId, + monoforumPeerId, + type, + std::move(parsed)); finish(); }).fail([=] { _sharedMediaRequests.remove(key); @@ -3183,16 +3412,19 @@ void ApiWrap::requestSharedMedia( void ApiWrap::sharedMediaDone( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, SharedMediaType type, Api::SearchResult &&parsed) { const auto topic = peer->forumTopicFor(topicRootId); - if (topicRootId && !topic) { + const auto sublist = peer->monoforumSublistFor(monoforumPeerId); + if ((topicRootId && !topic) || (monoforumPeerId && !sublist)) { return; } const auto hasMessages = !parsed.messageIds.empty(); _session->storage().add(Storage::SharedMediaAddSlice( peer->id, topicRootId, + monoforumPeerId, type, std::move(parsed.messageIds), parsed.noSkipRange, @@ -3203,9 +3435,37 @@ void ApiWrap::sharedMediaDone( if (topic) { topic->setHasPinnedMessages(true); } + if (sublist) { + sublist->setHasPinnedMessages(true); + } } } +mtpRequestId ApiWrap::requestGlobalMedia( + Storage::SharedMediaType type, + const QString &query, + int32 offsetRate, + Data::MessagePosition offsetPosition, + Fn done) { + auto prepared = Api::PrepareGlobalMediaRequest( + _session, + offsetRate, + offsetPosition, + type, + query); + if (!prepared) { + done({}); + return 0; + } + return request( + std::move(*prepared) + ).done([=](const Api::SearchRequestResult &result) { + done(Api::ParseGlobalMediaResult(_session, result)); + }).fail([=] { + done({}); + }).send(); +} + void ApiWrap::sendAction(const SendAction &action) { if (!action.options.scheduled && !action.options.shortcutId @@ -3214,8 +3474,14 @@ void ApiWrap::sendAction(const SendAction &action) { const auto topic = topicRootId ? action.history->peer->forumTopicFor(topicRootId) : nullptr; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + const auto sublist = monoforumPeerId + ? action.history->peer->monoforumSublistFor(monoforumPeerId) + : nullptr; if (topic) { topic->readTillEnd(); + } else if (sublist) { + sublist->readTillEnd(); } else { _session->data().histories().readInbox(action.history); } @@ -3227,20 +3493,23 @@ void ApiWrap::sendAction(const SendAction &action) { void ApiWrap::finishForwarding(const SendAction &action) { const auto history = action.history; const auto topicRootId = action.replyTo.topicRootId; - auto toForward = history->resolveForwardDraft(topicRootId); + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + auto toForward = history->resolveForwardDraft( + topicRootId, + monoforumPeerId); if (!toForward.items.empty()) { - const auto error = GetErrorTextForSending( + const auto error = GetErrorForSending( history->peer, { .topicRootId = topicRootId, .forward = &toForward.items, }); - if (!error.isEmpty()) { + if (error) { return; } + history->setForwardDraft(topicRootId, monoforumPeerId, {}); forwardMessages(std::move(toForward), action); - history->setForwardDraft(topicRootId, {}); } _session->data().sendHistoryChangeNotifications(); @@ -3255,12 +3524,32 @@ void ApiWrap::finishForwarding(const SendAction &action) { void ApiWrap::forwardMessages( Data::ResolvedForwardDraft &&draft, - const SendAction &action, + SendAction action, FnMut &&successCallback) { Expects(!draft.items.empty()); auto &histories = _session->data().histories(); + for (auto i = begin(draft.items); i != end(draft.items);) { + const auto item = *i; + if (item->isSavedMusicItem()) { + SendExistingDocument(MessageToSend(action), item->media()->document()); + i = draft.items.erase(i); + } else { + ++i; + } + } + if (draft.items.empty()) { + if (successCallback) { + successCallback(); + } + return; + } + draft.options = HistoryView::Controls::NormalizeForwardOptions( + _session, + draft.items, + draft.options); + struct SharedCallback { int requestsLeft = 0; FnMut callback; @@ -3295,11 +3584,17 @@ void ApiWrap::forwardMessages( if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= SendFlag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= SendFlag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { flags |= MessageFlag::ShortcutMessage; sendFlags |= SendFlag::f_quick_reply_shortcut; } + if (action.options.effectId) { + sendFlags |= SendFlag::f_effect; + } if (draft.options != Data::ForwardOptions::PreserveInfo) { sendFlags |= SendFlag::f_drop_author; } @@ -3309,6 +3604,9 @@ void ApiWrap::forwardMessages( if (sendAs) { sendFlags |= SendFlag::f_send_as; } + if (action.options.suggest) { + sendFlags |= SendFlag::f_suggested_post; + } const auto kGeneralId = Data::ForumTopic::kGeneralId; const auto topicRootId = action.replyTo.topicRootId; const auto topMsgId = (topicRootId == kGeneralId) @@ -3317,6 +3615,13 @@ void ApiWrap::forwardMessages( if (topMsgId) { sendFlags |= SendFlag::f_top_msg_id; } + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + const auto monoforumPeer = monoforumPeerId + ? session().data().peer(monoforumPeerId).get() + : nullptr; + if (monoforumPeer || (action.options.suggest && action.replyTo)) { + sendFlags |= SendFlag::f_reply_to; + } auto forwardFrom = draft.items.front()->history()->peer; auto ids = QVector(); @@ -3327,39 +3632,90 @@ void ApiWrap::forwardMessages( if (shared) { ++shared->requestsLeft; } - const auto requestType = Data::Histories::RequestType::Send; const auto idsCopy = localIds; - histories.sendRequest(history, requestType, [=](Fn finish) { - history->sendRequestId = request(MTPmessages_ForwardMessages( - MTP_flags(sendFlags), - forwardFrom->input, + const auto scheduled = action.options.scheduled; + const auto starsPaid = std::min( + action.options.starsApproved, + int(ids.size() * peer->starsPerMessageChecked())); + auto oneFlags = sendFlags; + if (starsPaid) { + action.options.starsApproved -= starsPaid; + oneFlags |= SendFlag::f_allow_paid_stars; + } + auto buildMessage = [=]( + not_null history, + FullReplyTo replyTo) + -> Data::Histories::PreparedMessage { + const auto kGeneralId = Data::ForumTopic::kGeneralId; + const auto realTopMsgId = (replyTo.topicRootId == kGeneralId) + ? MsgId(0) + : replyTo.topicRootId; + auto flags = oneFlags; + if (realTopMsgId) { + flags |= SendFlag::f_top_msg_id; + } else { + flags &= ~SendFlag::f_top_msg_id; + } + return MTPmessages_ForwardMessages( + MTP_flags(flags), + forwardFrom->input(), MTP_vector(ids), MTP_vector(randomIds), - peer->input, - MTP_int(topMsgId), + history->peer->input(), + MTP_int(realTopMsgId), + (action.options.suggest + ? ReplyToForMTP(history, replyTo) + : monoforumPeer + ? MTP_inputReplyToMonoForum( + monoforumPeer->input()) + : MTPInputReplyTo()), MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, action.options.shortcutId) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs + ? sendAs->input() + : MTP_inputPeerEmpty()), + Data::ShortcutIdToMTP( + &history->session(), + action.options.shortcutId), + MTP_long(action.options.effectId), + MTPint(), + MTP_long(starsPaid), + Api::SuggestToMTP(action.options.suggest)); + }; + histories.sendPreparedMessage( + history, + FullReplyTo{ .topicRootId = topicRootId }, + uint64(0), + std::move(buildMessage), + [=](const MTPUpdates &result, const MTP::Response &) { + if (!scheduled) { + _session->api().updates().checkForSentToScheduled( + result); + } if (shared && !--shared->requestsLeft) { shared->callback(); } - finish(); - }).fail([=](const MTP::Error &error) { + if (peer->isSelf() && _session->premium()) { + ProcessRecentSelfForwards( + _session, + result, + peer->id, + forwardFrom->id); + } + }, + [=](const MTP::Error &error, const MTP::Response &) { if (idsCopy) { for (const auto &[randomId, itemId] : *idsCopy) { - sendMessageFail(error, peer, randomId, itemId); + _session->api().sendMessageFail( + error, + peer, + randomId, + itemId); } } else { - sendMessageFail(error, peer); + _session->api().sendMessageFail(error, peer); } - finish(); - }).afterRequest( - history->sendRequestId - ).send(); - return history->sendRequestId; - }); + }); ids.resize(0); randomIds.resize(0); @@ -3368,7 +3724,7 @@ void ApiWrap::forwardMessages( ids.reserve(count); randomIds.reserve(count); - for (const auto item : draft.items) { + for (const auto &item : draft.items) { const auto randomId = base::RandomValue(); if (genClientSideMessage) { const auto newId = FullMsgId( @@ -3378,11 +3734,15 @@ void ApiWrap::forwardMessages( .id = newId.msg, .flags = flags, .from = NewMessageFromId(action), - .replyTo = { .topicRootId = topMsgId }, + .replyTo = { + .topicRootId = topMsgId, + .monoforumPeerId = monoforumPeerId, + }, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = action.options.starsApproved, .postAuthor = NewMessagePostAuthor(action), - + .suggest = HistoryMessageSuggestInfo(action.options), // forwarded messages don't have effects //.effectId = action.options.effectId, }, item); @@ -3474,8 +3834,10 @@ void ApiWrap::sendSharedContact( .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = action.options.starsApproved, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, + .suggest = HistoryMessageSuggestInfo(action.options), }, TextWithEntities(), MTP_messageMediaContact( MTP_string(phone), MTP_string(firstName), @@ -3502,16 +3864,20 @@ void ApiWrap::sendVoiceMessage( QByteArray result, VoiceWaveform waveform, crl::time duration, + bool video, const SendAction &action) { const auto caption = TextWithTags(); const auto to = FileLoadTaskOptions(action); - _fileLoader->addTask(std::make_unique( - &session(), - result, - duration, - waveform, - to, - caption)); + _fileLoader->addTask( + std::make_unique(FileLoadTask::VoiceArgs{ + .session = &session(), + .voice = result, + .duration = duration, + .waveform = waveform, + .video = video, + .to = to, + .caption = caption, + })); } void ApiWrap::editMedia( @@ -3522,35 +3888,60 @@ void ApiWrap::editMedia( if (list.files.empty()) return; auto &file = list.files.front(); - const auto to = FileLoadTaskOptions(action); - _fileLoader->addTask(std::make_unique( - &session(), - file.path, - file.content, - std::move(file.information), - type, - to, - caption, - file.spoiler)); + auto to = FileLoadTaskOptions(action); + const auto existing = to.replaceMediaOf + ? session().data().message(action.history->peer, to.replaceMediaOf) + : nullptr; + if (existing && existing->computeSuggestionActions() + == SuggestionActions::AcceptAndDecline) { + to.replyTo.messageId = { + action.history->peer->id, + to.replaceMediaOf + }; + to.replyTo.monoforumPeerId = existing->sublistPeerId(); + to.replaceMediaOf = MsgId(); + } + const auto forceFile = (type == SendMediaType::File) + && (file.type == Ui::PreparedFile::Type::Video); + _fileLoader->addTask(std::make_unique(FileLoadTask::Args{ + .session = &session(), + .filepath = file.path, + .content = file.content, + .information = std::move(file.information), + .videoCover = (file.videoCover + ? std::make_unique(FileLoadTask::Args{ + .session = &session(), + .filepath = file.videoCover->path, + .content = file.videoCover->content, + .information = std::move(file.videoCover->information), + .videoCover = nullptr, + .type = SendMediaType::Photo, + .to = to, + .caption = TextWithTags(), + .spoiler = false, + .album = nullptr, + .forceFile = false, + .sendLargePhotos = false, + .idOverride = 0, + }) + : nullptr), + .type = type, + .to = to, + .caption = caption, + .spoiler = file.spoiler, + .album = nullptr, + .forceFile = forceFile, + .sendLargePhotos = file.sendLargePhotos, + .idOverride = 0, + .displayName = file.displayName, + })); } void ApiWrap::sendFiles( Ui::PreparedList &&list, SendMediaType type, - TextWithTags &&caption, std::shared_ptr album, const SendAction &action) { - const auto haveCaption = !caption.text.isEmpty(); - if (haveCaption - && !list.canAddCaption( - album != nullptr, - type == SendMediaType::Photo)) { - auto message = MessageToSend(action); - message.textWithTags = base::take(caption); - message.action.clearDraft = false; - sendMessage(std::move(message)); - } - const auto to = FileLoadTaskOptions(action); if (album) { album->options = to.options; @@ -3564,17 +3955,40 @@ void ApiWrap::sendFiles( && type != SendMediaType::File) ? SendMediaType::Photo : SendMediaType::File; - tasks.push_back(std::make_unique( - &session(), - file.path, - file.content, - std::move(file.information), - uploadWithType, - to, - caption, - file.spoiler, - album)); - caption = TextWithTags(); + const auto forceFile = (type == SendMediaType::File) + && (file.type == Ui::PreparedFile::Type::Video); + tasks.push_back(std::make_unique(FileLoadTask::Args{ + .session = &session(), + .filepath = file.path, + .content = file.content, + .information = std::move(file.information), + .videoCover = (file.videoCover + ? std::make_unique(FileLoadTask::Args{ + .session = &session(), + .filepath = file.videoCover->path, + .content = file.videoCover->content, + .information = std::move(file.videoCover->information), + .videoCover = nullptr, + .type = SendMediaType::Photo, + .to = to, + .caption = TextWithTags(), + .spoiler = false, + .album = nullptr, + .forceFile = false, + .sendLargePhotos = false, + .idOverride = 0, + }) + : nullptr), + .type = uploadWithType, + .to = to, + .caption = std::move(file.caption), + .spoiler = file.spoiler, + .album = album, + .forceFile = forceFile, + .sendLargePhotos = file.sendLargePhotos, + .idOverride = 0, + .displayName = file.displayName, + })); } if (album) { _sendingAlbums.emplace(album->groupId, album); @@ -3593,16 +4007,20 @@ void ApiWrap::sendFile( const auto to = FileLoadTaskOptions(action); auto caption = TextWithTags(); const auto spoiler = false; - const auto information = nullptr; - _fileLoader->addTask(std::make_unique( - &session(), - QString(), - fileContent, - information, - type, - to, - caption, - spoiler)); + _fileLoader->addTask(std::make_unique(FileLoadTask::Args{ + .session = &session(), + .filepath = QString(), + .content = fileContent, + .information = nullptr, + .videoCover = nullptr, + .type = type, + .to = to, + .caption = caption, + .spoiler = spoiler, + .album = nullptr, + .forceFile = false, + .idOverride = 0 + })); } void ApiWrap::sendUploadedPhoto( @@ -3653,7 +4071,7 @@ void ApiWrap::sendShortcutMessages( auto ids = QVector(); auto randomIds = QVector(); request(MTPmessages_SendQuickReplyMessages( - peer->input, + peer->input(), MTP_int(id), MTP_vector(ids), MTP_vector(randomIds) @@ -3663,7 +4081,124 @@ void ApiWrap::sendShortcutMessages( }).send(); } -void ApiWrap::sendMessage(MessageToSend &&message) { +void ApiWrap::sendRichMessage( + not_null item, + const MTPInputRichMessage &richMessage, + SendAction action) { + Expects(item->history() == action.history); + + const auto history = item->history(); + const auto peer = history->peer; + action.generateLocal = true; + sendAction(action); + + const auto clearCloudDraft = action.clearDraft; + const auto draftTopicRootId = action.replyTo.topicRootId; + const auto draftMonoforumPeerId = action.replyTo.monoforumPeerId; + const auto randomId = base::RandomValue(); + auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); + if (starsPaid) { + action.options.starsApproved -= starsPaid; + } + _session->data().registerMessageRandomId(randomId, item->fullId()); + _session->data().registerMessageSentData( + randomId, + peer->id, + item->originalText().text); + + using Flag = MTPmessages_SendMessage::Flag; + auto sendFlags = MTPmessages_SendMessage::Flags(0) + | Flag::f_rich_message; + if (action.replyTo) { + sendFlags |= Flag::f_reply_to; + } + if (ShouldSendSilent(peer, action.options)) { + sendFlags |= Flag::f_silent; + } + if (clearCloudDraft) { + sendFlags |= Flag::f_clear_draft; + history->clearCloudDraft(draftTopicRootId, draftMonoforumPeerId); + history->startSavingCloudDraft( + draftTopicRootId, + draftMonoforumPeerId); + } + if (action.options.sendAs) { + sendFlags |= Flag::f_send_as; + } + if (action.options.scheduled) { + sendFlags |= Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= Flag::f_schedule_repeat_period; + } + } + if (action.options.shortcutId) { + sendFlags |= Flag::f_quick_reply_shortcut; + } + if (action.options.effectId) { + sendFlags |= Flag::f_effect; + } + if (action.options.suggest) { + sendFlags |= Flag::f_suggested_post; + } + if (starsPaid) { + sendFlags |= Flag::f_allow_paid_stars; + } + const auto done = [=]( + const MTPUpdates &result, + const MTP::Response &response) { + if (clearCloudDraft) { + history->finishSavingCloudDraft( + draftTopicRootId, + draftMonoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + } + }; + const auto fail = [=]( + const MTP::Error &error, + const MTP::Response &response) { + sendMessageFail(error, peer, randomId, item->fullId()); + if (clearCloudDraft) { + history->finishSavingCloudDraft( + draftTopicRootId, + draftMonoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + } + }; + const auto mtpShortcut = Data::ShortcutIdToMTP( + _session, + action.options.shortcutId); + history->owner().histories().sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(sendFlags), + peer->input(), + Data::Histories::ReplyToPlaceholder(), + MTP_string(QString()), + MTP_long(randomId), + MTPReplyMarkup(), + MTPVector(), + MTP_int(action.options.scheduled), + MTP_int(action.options.scheduleRepeatPeriod), + (action.options.sendAs + ? action.options.sendAs->input() + : MTP_inputPeerEmpty()), + mtpShortcut, + MTP_long(action.options.effectId), + MTP_long(starsPaid), + Api::SuggestToMTP(action.options.suggest), + richMessage), + done, + fail); + finishForwarding(action); +} + +void ApiWrap::sendMessage( + MessageToSend &&message, + std::optional localMessageId) { const auto history = message.action.history; const auto peer = history->peer; auto &textWithTags = message.textWithTags; @@ -3674,6 +4209,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { const auto clearCloudDraft = action.clearDraft; const auto draftTopicRootId = action.replyTo.topicRootId; + const auto draftMonoforumPeerId = action.replyTo.monoforumPeerId; const auto replyTo = action.replyTo.messageId ? peer->owner().message(action.replyTo.messageId) : nullptr; @@ -3702,17 +4238,22 @@ void ApiWrap::sendMessage(MessageToSend &&message) { HistoryItem *lastMessage = nullptr; auto &histories = history->owner().histories(); + const auto messageLengthLimit = Data::PremiumLimits( + &history->session() + ).messageLengthCurrent(); const auto exactWebPage = !message.webPage.url.isEmpty(); auto isFirst = true; - while (TextUtilities::CutPart(sending, left, MaxMessageSize) + while (TextUtilities::CutPart(sending, left, messageLengthLimit) || (isFirst && exactWebPage)) { TextUtilities::Trim(left); const auto isLast = left.empty(); auto newId = FullMsgId( peer->id, - _session->data().nextLocalMessageId()); + localMessageId + ? std::exchange(localMessageId, std::nullopt).value() + : _session->data().nextLocalMessageId()); auto randomId = base::RandomValue(); TextUtilities::Trim(sending); @@ -3783,8 +4324,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (clearCloudDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; - history->clearCloudDraft(draftTopicRootId); - history->startSavingCloudDraft(draftTopicRootId); + history->clearCloudDraft(draftTopicRootId, draftMonoforumPeerId); + history->startSavingCloudDraft( + draftTopicRootId, + draftMonoforumPeerId); } const auto sendAs = action.options.sendAs; if (sendAs) { @@ -3795,6 +4338,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + if (action.options.scheduleRepeatPeriod) { + sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_repeat_period; + mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period; + } } if (action.options.shortcutId) { flags |= MessageFlag::ShortcutMessage; @@ -3805,15 +4352,30 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_effect; mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.suggest) { + sendFlags |= MTPmessages_SendMessage::Flag::f_suggested_post; + mediaFlags |= MTPmessages_SendMedia::Flag::f_suggested_post; + } + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars; + mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars; + } lastMessage = history->addNewLocalMessage({ .id = newId.msg, .flags = flags, .from = NewMessageFromId(action), .replyTo = action.replyTo, .date = NewMessageDate(action.options), + .scheduleRepeatPeriod = action.options.scheduleRepeatPeriod, .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, + .suggest = HistoryMessageSuggestInfo(action.options), }, sending, media); const auto done = [=]( const MTPUpdates &result, @@ -3821,7 +4383,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (clearCloudDraft) { history->finishSavingCloudDraft( draftTopicRootId, - UnixtimeFromMsgId(response.outerMsgId)); + draftMonoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); } }; const auto fail = [=]( @@ -3835,7 +4398,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (clearCloudDraft) { history->finishSavingCloudDraft( draftTopicRootId, - UnixtimeFromMsgId(response.outerMsgId)); + draftMonoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); } }; const auto mtpShortcut = Data::ShortcutIdToMTP( @@ -3850,7 +4414,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { randomId, Data::Histories::PrepareMessage( MTP_flags(mediaFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), Data::WebPageForMTP(message.webPage, true), msgText, @@ -3858,9 +4422,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTPReplyMarkup(), sentEntities, MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), mtpShortcut, - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + Api::SuggestToMTP(action.options.suggest) ), done, fail); } else { histories.sendPreparedMessage( @@ -3869,16 +4436,20 @@ void ApiWrap::sendMessage(MessageToSend &&message) { randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), msgText, MTP_long(randomId), MTPReplyMarkup(), sentEntities, MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + MTP_int(action.options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), mtpShortcut, - MTP_long(action.options.effectId) + MTP_long(action.options.effectId), + MTP_long(starsPaid), + Api::SuggestToMTP(action.options.suggest), + MTPInputRichMessage() ), done, fail); } isFirst = false; @@ -3918,8 +4489,8 @@ void ApiWrap::sendBotStart( info->startToken = QString(); } request(MTPmessages_StartBot( - bot->inputUser, - chat ? chat->input : MTP_inputPeerEmpty(), + bot->inputUser(), + chat ? chat->input() : MTP_inputPeerEmpty(), MTP_long(randomId), MTP_string(token) )).done([=](const MTPUpdates &result) { @@ -3935,8 +4506,9 @@ void ApiWrap::sendBotStart( void ApiWrap::sendInlineResult( not_null bot, not_null data, - const SendAction &action, - std::optional localMessageId) { + SendAction action, + std::optional localMessageId, + Fn done) { sendAction(action); const auto history = action.history; @@ -3950,6 +4522,7 @@ void ApiWrap::sendInlineResult( const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; using SendFlag = MTPmessages_SendInlineBotResult::Flag; auto flags = NewMessageFlags(peer); @@ -3974,6 +4547,13 @@ void ApiWrap::sendInlineResult( if (action.options.hideViaBot) { sendFlags |= SendFlag::f_hide_via; } + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + action.options.starsApproved); + if (starsPaid) { + action.options.starsApproved -= starsPaid; + sendFlags |= SendFlag::f_allow_paid_stars; + } const auto sendAs = action.options.sendAs; if (sendAs) { @@ -3988,14 +4568,15 @@ void ApiWrap::sendInlineResult( .replyTo = action.replyTo, .date = NewMessageDate(action.options), .shortcutId = action.options.shortcutId, + .starsPaid = starsPaid, .viaBotId = ((bot && !action.options.hideViaBot) ? peerToUser(bot->id) : UserId()), .postAuthor = NewMessagePostAuthor(action), }); - history->clearCloudDraft(topicRootId); - history->startSavingCloudDraft(topicRootId); + history->clearCloudDraft(topicRootId, monoforumPeerId); + history->startSavingCloudDraft(topicRootId, monoforumPeerId); auto &histories = history->owner().histories(); histories.sendPreparedMessage( @@ -4004,23 +4585,32 @@ void ApiWrap::sendInlineResult( randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), MTP_long(randomId), MTP_long(data->getQueryId()), MTP_string(data->getId()), MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, action.options.shortcutId) + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), + Data::ShortcutIdToMTP(_session, action.options.shortcutId), + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { history->finishSavingCloudDraft( topicRootId, - UnixtimeFromMsgId(response.outerMsgId)); + monoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + if (done) { + done(true); + } }, [=](const MTP::Error &error, const MTP::Response &response) { sendMessageFail(error, peer, randomId, newId); history->finishSavingCloudDraft( topicRootId, - UnixtimeFromMsgId(response.outerMsgId)); + monoforumPeerId, + Api::UnixtimeFromMsgId(response.outerMsgId)); + if (done) { + done(false); + } }); finishForwarding(action); } @@ -4036,7 +4626,7 @@ void ApiWrap::uploadAlbumMedia( request(MTPmessages_UploadMedia( MTP_flags(0), MTPstring(), // business_connection_id - item->history()->peer->input, + item->history()->peer->input(), media )).done([=](const MTPMessageMedia &result) { const auto item = _session->data().message(localId); @@ -4073,7 +4663,8 @@ void ApiWrap::uploadAlbumMedia( fields.vid(), fields.vaccess_hash(), fields.vfile_reference()), - MTP_int(data.vttl_seconds().value_or_empty())); + MTP_int(data.vttl_seconds().value_or_empty()), + MTPInputDocument()); // video sendAlbumWithUploaded(item, groupId, media); } break; @@ -4085,16 +4676,29 @@ void ApiWrap::uploadAlbumMedia( return; } const auto &fields = document->c_document(); + const auto mtpCover = data.vvideo_cover(); + const auto cover = (mtpCover && mtpCover->type() == mtpc_photo) + ? &(mtpCover->c_photo()) + : (const MTPDphoto*)nullptr; using Flag = MTPDinputMediaDocument::Flag; const auto flags = Flag() | (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag()) - | (spoiler ? Flag::f_spoiler : Flag()); + | (spoiler ? Flag::f_spoiler : Flag()) + | (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag()) + | (cover ? Flag::f_video_cover : Flag()); const auto media = MTP_inputMediaDocument( MTP_flags(flags), MTP_inputDocument( fields.vid(), fields.vaccess_hash(), fields.vfile_reference()), + (cover + ? MTP_inputPhoto( + cover->vid(), + cover->vaccess_hash(), + cover->vfile_reference()) + : MTPInputPhoto()), + MTP_int(data.vvideo_timestamp().value_or_empty()), MTP_int(data.vttl_seconds().value_or_empty()), MTPstring()); // query sendAlbumWithUploaded(item, groupId, media); @@ -4124,6 +4728,7 @@ void ApiWrap::sendMediaWithRandomId( Fn done) { const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4133,6 +4738,12 @@ void ApiWrap::sendMediaWithRandomId( Api::ConvertOption::SkipLocal); const auto updateRecentStickers = Api::HasAttachedStickers(media); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); + if (starsPaid) { + options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4142,13 +4753,17 @@ void ApiWrap::sendMediaWithRandomId( : Flag(0)) | (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0)) | (options.scheduled ? Flag::f_schedule_date : Flag(0)) + | ((options.scheduled && options.scheduleRepeatPeriod) + ? Flag::f_schedule_repeat_period + : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) - | (options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (options.suggest ? Flag::f_suggested_post : Flag(0)) + | (options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); histories.sendPreparedMessage( history, @@ -4156,7 +4771,7 @@ void ApiWrap::sendMediaWithRandomId( randomId, Data::Histories::PrepareMessage( MTP_flags(flags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), (options.price ? MTPInputMedia(MTP_inputMediaPaidMedia( @@ -4170,9 +4785,12 @@ void ApiWrap::sendMediaWithRandomId( MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled), - (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), + MTP_int(options.scheduleRepeatPeriod), + (options.sendAs ? options.sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), - MTP_long(options.effectId) + MTP_long(options.effectId), + MTP_long(starsPaid), + Api::SuggestToMTP(options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { @@ -4191,7 +4809,7 @@ void ApiWrap::sendMultiPaidMedia( Expects(album->options.price > 0); const auto groupId = album->groupId; - const auto &options = album->options; + auto &options = album->options; const auto randomId = album->items.front().randomId; auto medias = album->items | ranges::view::transform([]( const SendingAlbum::Item &part) { @@ -4201,6 +4819,7 @@ void ApiWrap::sendMultiPaidMedia( const auto history = item->history(); const auto replyTo = item->replyTo(); + const auto peer = history->peer; auto caption = item->originalText(); TextUtilities::Trim(caption); @@ -4208,6 +4827,12 @@ void ApiWrap::sendMultiPaidMedia( _session, caption.entities, Api::ConvertOption::SkipLocal); + const auto starsPaid = std::min( + peer->starsPerMessageChecked(), + options.starsApproved); + if (starsPaid) { + options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMedia::Flag; const auto flags = Flag(0) @@ -4217,21 +4842,26 @@ void ApiWrap::sendMultiPaidMedia( : Flag(0)) | (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0)) | (options.scheduled ? Flag::f_schedule_date : Flag(0)) + | (options.scheduleRepeatPeriod + ? Flag::f_schedule_repeat_period + : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (options.effectId ? Flag::f_effect : Flag(0)) - | (options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (options.suggest ? Flag::f_suggested_post : Flag(0)) + | (options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); - const auto peer = history->peer; const auto itemId = item->fullId(); + album->sent = true; histories.sendPreparedMessage( history, replyTo, randomId, Data::Histories::PrepareMessage( MTP_flags(flags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), MTP_inputMediaPaidMedia( MTP_flags(0), @@ -4243,9 +4873,12 @@ void ApiWrap::sendMultiPaidMedia( MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled), - (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), + MTP_int(options.scheduleRepeatPeriod), + (options.sendAs ? options.sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, options.shortcutId), - MTP_long(options.effectId) + MTP_long(options.effectId), + MTP_long(starsPaid), + Api::SuggestToMTP(options.suggest) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (const auto album = _sendingAlbums.take(groupId)) { const auto copy = (*album)->items; @@ -4296,6 +4929,9 @@ void ApiWrap::sendAlbumWithCancelled( } void ApiWrap::sendAlbumIfReady(not_null album) { + if (album->sent) { + return; + } const auto groupId = album->groupId; if (album->items.empty()) { _sendingAlbums.remove(groupId); @@ -4320,6 +4956,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { return; } else if (medias.size() < 2) { const auto &single = medias.front().data(); + album->sent = true; sendMediaWithRandomId( sample, single.vmedia(), @@ -4331,6 +4968,12 @@ void ApiWrap::sendAlbumIfReady(not_null album) { const auto history = sample->history(); const auto replyTo = sample->replyTo(); const auto sendAs = album->options.sendAs; + const auto starsPaid = std::min( + history->peer->starsPerMessageChecked() * int(medias.size()), + album->options.starsApproved); + if (starsPaid) { + album->options.starsApproved -= starsPaid; + } using Flag = MTPmessages_SendMultiMedia::Flag; const auto flags = Flag(0) | (replyTo ? Flag::f_reply_to : Flag(0)) @@ -4338,27 +4981,34 @@ void ApiWrap::sendAlbumIfReady(not_null album) { ? Flag::f_silent : Flag(0)) | (album->options.scheduled ? Flag::f_schedule_date : Flag(0)) + //| (album->options.scheduleRepeatPeriod + // ? Flag::f_schedule_repeat_period + // : Flag(0)) | (sendAs ? Flag::f_send_as : Flag(0)) | (album->options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) | (album->options.effectId ? Flag::f_effect : Flag(0)) - | (album->options.invertCaption ? Flag::f_invert_media : Flag(0)); + | (album->options.invertCaption ? Flag::f_invert_media : Flag(0)) + | (starsPaid ? Flag::f_allow_paid_stars : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; + album->sent = true; histories.sendPreparedMessage( history, replyTo, uint64(0), // randomId Data::Histories::PrepareMessage( MTP_flags(flags), - peer->input, + peer->input(), Data::Histories::ReplyToPlaceholder(), MTP_vector(medias), MTP_int(album->options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()), + //MTP_int(album->options.scheduleRepeatPeriod), + (sendAs ? sendAs->input() : MTP_inputPeerEmpty()), Data::ShortcutIdToMTP(_session, album->options.shortcutId), - MTP_long(album->options.effectId) + MTP_long(album->options.effectId), + MTP_long(starsPaid) ), [=](const MTPUpdates &result, const MTP::Response &response) { _sendingAlbums.remove(groupId); }, [=](const MTP::Error &error, const MTP::Response &response) { @@ -4392,7 +5042,7 @@ rpl::producer ApiWrap::contactSignupSilent() const { return _contactSignupSilent ? _contactSignupSilentChanges.events_starting_with_copy( *_contactSignupSilent) - : (_contactSignupSilentChanges.events() | rpl::type_erased()); + : (_contactSignupSilentChanges.events() | rpl::type_erased); } std::optional ApiWrap::contactSignupSilentCurrent() const { @@ -4437,7 +5087,7 @@ void ApiWrap::requestBotCommonGroups( }; const auto limit = 100; request(MTPmessages_GetCommonChats( - bot->inputUser, + bot->inputUser(), MTP_long(0), // max_id MTP_int(limit) )).done([=](const MTPmessages_Chats &result) { @@ -4536,6 +5186,10 @@ Api::GlobalPrivacy &ApiWrap::globalPrivacy() { return *_globalPrivacy; } +Api::ReactionsNotifySettings &ApiWrap::reactionsNotifySettings() { + return *_reactionsNotifySettings; +} + Api::UserPrivacy &ApiWrap::userPrivacy() { return *_userPrivacy; } @@ -4552,6 +5206,10 @@ Api::ViewsManager &ApiWrap::views() { return *_views; } +Api::ReadMetrics &ApiWrap::readMetrics() { + return *_readMetrics; +} + Api::ConfirmPhone &ApiWrap::confirmPhone() { return *_confirmPhone; } @@ -4564,6 +5222,10 @@ Api::Polls &ApiWrap::polls() { return *_polls; } +Api::TodoLists &ApiWrap::todoLists() { + return *_todoLists; +} + Api::ChatParticipants &ApiWrap::chatParticipants() { return *_chatParticipants; } @@ -4576,6 +5238,10 @@ Api::Ringtones &ApiWrap::ringtones() { return *_ringtones; } +Api::ComposeWithAi &ApiWrap::composeWithAi() { + return *_composeWithAi; +} + Api::Transcribes &ApiWrap::transcribes() { return *_transcribes; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7259c410d34e84..a1609c7a625cff 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -14,6 +14,7 @@ For license and copyright information please follow this link: #include "data/data_messages.h" class TaskQueue; +class HistoryItem; struct MessageGroupId; struct SendingAlbum; enum class SendMediaType; @@ -25,10 +26,11 @@ class Session; } // namespace Main namespace Data { +struct ReactionId; struct UpdatedFileReferences; class WallPaper; struct ResolvedForwardDraft; -enum class DefaultNotify; +enum class DefaultNotify : uint8_t; enum class StickersType : uchar; class Forum; class ForumTopic; @@ -59,6 +61,7 @@ class Show; namespace Api { struct SearchResult; +struct GlobalMediaResult; class Updates; class Authorizations; @@ -68,6 +71,7 @@ class CloudPassword; class SelfDestruct; class SensitiveContent; class GlobalPrivacy; +class ReactionsNotifySettings; class UserPrivacy; class InviteLinks; class ChatLinks; @@ -76,11 +80,14 @@ class ConfirmPhone; class PeerPhoto; class PeerColors; class Polls; +class TodoLists; class ChatParticipants; class UnreadThings; class Ringtones; +class ComposeWithAi; class Transcribes; class Premium; +class ReadMetrics; class Usernames; class Websites; @@ -127,6 +134,8 @@ QString RequestKey(Types &&...values) { return result; } +[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId); + } // namespace Api class ApiWrap final : public MTP::Sender { @@ -164,7 +173,9 @@ class ApiWrap final : public MTP::Sender { void requestMessageData(PeerData *peer, MsgId msgId, Fn done); QString exportDirectMessageLink( not_null item, - bool inRepliesContext); + bool inRepliesContext, + bool forceNonPublicLink = false, + std::optional videoTimestamp = {}); QString exportDirectStoryLink(not_null item); void requestContacts(); @@ -197,7 +208,6 @@ class ApiWrap final : public MTP::Sender { void requestChangelog( const QString &sinceVersion, Fn callback); - void refreshTopPromotion(); void requestDeepLinkInfo( const QString &path, Fn callback); @@ -229,6 +239,19 @@ class ApiWrap final : public MTP::Sender { void deleteAllFromParticipant( not_null channel, not_null from); + void deleteAllReactionsFromParticipant( + not_null peer, + not_null participant, + MsgId originMsgId, + const Data::ReactionId &originReaction); + void deleteParticipantReaction( + not_null peer, + MsgId msgId, + not_null participant, + const Data::ReactionId &reaction); + void deleteSublistHistory( + not_null parentChat, + not_null sublistPeer); void requestWebPageDelayed(not_null page); void clearWebPageRequest(not_null page); @@ -284,9 +307,16 @@ class ApiWrap final : public MTP::Sender { void requestSharedMedia( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, Storage::SharedMediaType type, MsgId messageId, SliceType slice); + mtpRequestId requestGlobalMedia( + Storage::SharedMediaType type, + const QString &query, + int32 offsetRate, + Data::MessagePosition offsetPosition, + Fn done); void readFeaturedSetDelayed(uint64 setId); @@ -297,7 +327,7 @@ class ApiWrap final : public MTP::Sender { void finishForwarding(const SendAction &action); void forwardMessages( Data::ResolvedForwardDraft &&draft, - const SendAction &action, + SendAction action, FnMut &&successCallback = nullptr); void shareContact( const QString &phone, @@ -317,11 +347,11 @@ class ApiWrap final : public MTP::Sender { QByteArray result, VoiceWaveform waveform, crl::time duration, + bool video, const SendAction &action); void sendFiles( Ui::PreparedList &&list, SendMediaType type, - TextWithTags &&caption, std::shared_ptr album, const SendAction &action); void sendFile( @@ -349,7 +379,13 @@ class ApiWrap final : public MTP::Sender { void sendShortcutMessages( not_null peer, BusinessShortcutId id); - void sendMessage(MessageToSend &&message); + void sendRichMessage( + not_null item, + const MTPInputRichMessage &richMessage, + SendAction action); + void sendMessage( + MessageToSend &&message, + std::optional localMessageId = std::nullopt); void sendBotStart( std::shared_ptr show, not_null bot, @@ -358,8 +394,9 @@ class ApiWrap final : public MTP::Sender { void sendInlineResult( not_null bot, not_null data, - const SendAction &action, - std::optional localMessageId); + SendAction action, + std::optional localMessageId, + Fn done = nullptr); void sendMessageFail( const MTP::Error &error, not_null peer, @@ -392,16 +429,20 @@ class ApiWrap final : public MTP::Sender { [[nodiscard]] Api::SelfDestruct &selfDestruct(); [[nodiscard]] Api::SensitiveContent &sensitiveContent(); [[nodiscard]] Api::GlobalPrivacy &globalPrivacy(); + [[nodiscard]] Api::ReactionsNotifySettings &reactionsNotifySettings(); [[nodiscard]] Api::UserPrivacy &userPrivacy(); [[nodiscard]] Api::InviteLinks &inviteLinks(); [[nodiscard]] Api::ChatLinks &chatLinks(); [[nodiscard]] Api::ViewsManager &views(); + [[nodiscard]] Api::ReadMetrics &readMetrics(); [[nodiscard]] Api::ConfirmPhone &confirmPhone(); [[nodiscard]] Api::PeerPhoto &peerPhoto(); [[nodiscard]] Api::Polls &polls(); + [[nodiscard]] Api::TodoLists &todoLists(); [[nodiscard]] Api::ChatParticipants &chatParticipants(); [[nodiscard]] Api::UnreadThings &unreadThings(); [[nodiscard]] Api::Ringtones &ringtones(); + [[nodiscard]] Api::ComposeWithAi &composeWithAi(); [[nodiscard]] Api::Transcribes &transcribes(); [[nodiscard]] Api::Premium &premium(); [[nodiscard]] Api::Usernames &usernames(); @@ -412,6 +453,12 @@ class ApiWrap final : public MTP::Sender { static constexpr auto kJoinErrorDuration = 5 * crl::time(1000); + static void ProcessRecentSelfForwards( + not_null session, + const MTPUpdates &updates, + PeerId targetPeerId, + PeerId fromPeerId); + private: struct MessageDataRequest { using Callbacks = std::vector>; @@ -492,20 +539,27 @@ class ApiWrap final : public MTP::Sender { void resolveJumpToHistoryDate( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, const QDate &date, Fn, MsgId)> callback); template void requestMessageAfterDate( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, const QDate &date, Callback &&callback); void sharedMediaDone( not_null peer, MsgId topicRootId, + PeerId monoforumPeerId, SharedMediaType type, Api::SearchResult &&parsed); + void globalMediaDone( + SharedMediaType type, + FullMsgId messageId, + Api::GlobalMediaResult &&parsed); void sendSharedContact( const QString &phone, @@ -525,6 +579,9 @@ class ApiWrap final : public MTP::Sender { void deleteAllFromParticipantSend( not_null channel, not_null from); + void deleteSublistHistorySend( + not_null parentChat, + not_null sublistPeer); void uploadAlbumMedia( not_null item, @@ -554,9 +611,6 @@ class ApiWrap final : public MTP::Sender { not_null album, Fn done = nullptr); - void getTopPromotionDelayed(TimeId now, TimeId next); - void topPromotionDone(const MTPhelp_PromoData &proxy); - void sendNotifySettingsUpdates(); template @@ -648,6 +702,7 @@ class ApiWrap final : public MTP::Sender { struct SharedMediaRequest { not_null peer; MsgId topicRootId = 0; + PeerId monoforumPeerId = 0; SharedMediaType mediaType = {}; MsgId aroundId = 0; SliceType sliceType = {}; @@ -669,6 +724,17 @@ class ApiWrap final : public MTP::Sender { }; base::flat_set _historyRequests; + struct GlobalMediaRequest { + SharedMediaType mediaType = {}; + FullMsgId aroundId; + SliceType sliceType = {}; + + friend inline auto operator<=>( + const GlobalMediaRequest&, + const GlobalMediaRequest&) = default; + }; + base::flat_set _globalMediaRequests; + std::unique_ptr _dialogsLoadState; TimeId _dialogsLoadTill = 0; rpl::variable _dialogsLoadMayBlockByDate = false; @@ -683,11 +749,6 @@ class ApiWrap final : public MTP::Sender { std::unique_ptr _fileLoader; base::flat_map> _sendingAlbums; - mtpRequestId _topPromotionRequestId = 0; - std::pair _topPromotionKey; - TimeId _topPromotionNextRequestTime = TimeId(0); - base::Timer _topPromotionTimer; - base::flat_set> _updateNotifyTopics; base::flat_set> _updateNotifyPeers; base::flat_set _updateNotifyDefaults; @@ -729,16 +790,20 @@ class ApiWrap final : public MTP::Sender { const std::unique_ptr _selfDestruct; const std::unique_ptr _sensitiveContent; const std::unique_ptr _globalPrivacy; + const std::unique_ptr _reactionsNotifySettings; const std::unique_ptr _userPrivacy; const std::unique_ptr _inviteLinks; const std::unique_ptr _chatLinks; const std::unique_ptr _views; + const std::unique_ptr _readMetrics; const std::unique_ptr _confirmPhone; const std::unique_ptr _peerPhoto; const std::unique_ptr _polls; + const std::unique_ptr _todoLists; const std::unique_ptr _chatParticipants; const std::unique_ptr _unreadThings; const std::unique_ptr _ringtones; + const std::unique_ptr _composeWithAi; const std::unique_ptr _transcribes; const std::unique_ptr _premium; const std::unique_ptr _usernames; diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index 75674299f8d81c..9bf8912af4fe2b 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -7,20 +7,27 @@ For license and copyright information please follow this link: */ #include "boxes/about_box.h" -#include "lang/lang_keys.h" -#include "mainwidget.h" -#include "mainwindow.h" -#include "ui/boxes/confirm_box.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/labels.h" -#include "ui/text/text_utilities.h" #include "base/platform/base_platform_info.h" +#include "core/application.h" #include "core/file_utilities.h" -#include "core/click_handler_types.h" #include "core/update_checker.h" -#include "core/application.h" +#include "core/version.h" +#include "lang/lang_keys.h" +#include "ui/boxes/confirm_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "styles/style_channel_earn.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" +#include "styles/style_menu_icons.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" #include #include @@ -30,105 +37,99 @@ namespace { rpl::producer Text1() { return tr::lng_about_text1( lt_api_link, - tr::lng_about_text1_api( - ) | Ui::Text::ToLink("https://core.telegram.org/api"), - Ui::Text::WithEntities); + tr::lng_about_text1_api(tr::url(u"https://core.telegram.org/api"_q)), + tr::marked); } rpl::producer Text2() { return tr::lng_about_text2( lt_gpl_link, - rpl::single(Ui::Text::Link( + rpl::single(tr::link( "GNU GPL", "https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE")), lt_github_link, - rpl::single(Ui::Text::Link( + rpl::single(tr::link( "GitHub", "https://github.com/telegramdesktop/tdesktop")), - Ui::Text::WithEntities); + tr::marked); } rpl::producer Text3() { return tr::lng_about_text3( lt_faq_link, - tr::lng_about_text3_faq() | Ui::Text::ToLink(telegramFaqLink()), - Ui::Text::WithEntities); + tr::lng_about_text3_faq(tr::url(telegramFaqLink())), + tr::marked); } } // namespace -AboutBox::AboutBox(QWidget *parent) -: _version(this, tr::lng_about_version(tr::now, lt_version, currentVersionText()), st::aboutVersionLink) -, _text1(this, Text1(), st::aboutLabel) -, _text2(this, Text2(), st::aboutLabel) -, _text3(this, Text3(), st::aboutLabel) { -} - -void AboutBox::prepare() { - setTitle(rpl::single(u"Telegram Desktop"_q)); +void AboutBox(not_null box) { + box->setTitle(u"Telegram Desktop"_q); - addButton(tr::lng_close(), [this] { closeBox(); }); + auto layout = box->verticalLayout(); - _text1->setLinksTrusted(); - _text2->setLinksTrusted(); - _text3->setLinksTrusted(); + const auto version = layout->add( + object_ptr( + box, + tr::lng_about_version( + tr::now, + lt_version, + currentVersionText()), + st::aboutVersionLink), + QMargins( + st::boxRowPadding.left(), + -st::lineWidth * 3, + st::boxRowPadding.right(), + st::boxRowPadding.bottom())); + version->setClickedCallback([=] { + if (cRealAlphaVersion()) { + auto url = u"https://tdesktop.com/"_q; + if (Platform::IsWindows32Bit()) { + url += u"win/%1.zip"_q; + } else if (Platform::IsWindows64Bit()) { + url += u"win64/%1.zip"_q; + } else if (Platform::IsWindowsARM64()) { + url += u"winarm/%1.zip"_q; + } else if (Platform::IsMac()) { + url += u"mac/%1.zip"_q; + } else if (Platform::IsLinux()) { + url += u"linux/%1.tar.xz"_q; + } else { + Unexpected("Platform value."); + } + url = url.arg(u"talpha%1_%2"_q + .arg(cRealAlphaVersion()) + .arg(Core::countAlphaVersionSignature(cRealAlphaVersion()))); - _version->setClickedCallback([this] { showVersionHistory(); }); - - setDimensions(st::aboutWidth, st::aboutTextTop + _text1->height() + st::aboutSkip + _text2->height() + st::aboutSkip + _text3->height()); -} - -void AboutBox::resizeEvent(QResizeEvent *e) { - BoxContent::resizeEvent(e); - - const auto available = width() - - st::boxPadding.left() - - st::boxPadding.right(); - _version->moveToLeft(st::boxPadding.left(), st::aboutVersionTop); - _text1->resizeToWidth(available); - _text1->moveToLeft(st::boxPadding.left(), st::aboutTextTop); - _text2->resizeToWidth(available); - _text2->moveToLeft(st::boxPadding.left(), _text1->y() + _text1->height() + st::aboutSkip); - _text3->resizeToWidth(available); - _text3->moveToLeft(st::boxPadding.left(), _text2->y() + _text2->height() + st::aboutSkip); -} + QGuiApplication::clipboard()->setText(url); -void AboutBox::showVersionHistory() { - if (cRealAlphaVersion()) { - auto url = u"https://tdesktop.com/"_q; - if (Platform::IsWindows32Bit()) { - url += u"win/%1.zip"_q; - } else if (Platform::IsWindows64Bit()) { - url += u"win64/%1.zip"_q; - } else if (Platform::IsWindowsARM64()) { - url += u"winarm/%1.zip"_q; - } else if (Platform::IsMac()) { - url += u"mac/%1.zip"_q; - } else if (Platform::IsLinux()) { - url += u"linux/%1.tar.xz"_q; + box->getDelegate()->show( + Ui::MakeInformBox( + "The link to the current private alpha " + "version of Telegram Desktop was copied " + "to the clipboard.")); } else { - Unexpected("Platform value."); + File::OpenUrl(Core::App().changelogLink()); } - url = url.arg(u"talpha%1_%2"_q.arg(cRealAlphaVersion()).arg(Core::countAlphaVersionSignature(cRealAlphaVersion()))); + }); - QGuiApplication::clipboard()->setText(url); + Ui::AddSkip(layout, st::aboutTopSkip); - getDelegate()->show( - Ui::MakeInformBox( - "The link to the current private alpha " - "version of Telegram Desktop was copied to the clipboard."), - Ui::LayerOption::CloseOther); - } else { - File::OpenUrl(Core::App().changelogLink()); - } -} + const auto addText = [&](rpl::producer text) { + const auto label = layout->add( + object_ptr(box, std::move(text), st::aboutLabel), + st::boxRowPadding); + label->setLinksTrusted(); + Ui::AddSkip(layout, st::aboutSkip); + }; -void AboutBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - closeBox(); - } else { - BoxContent::keyPressEvent(e); - } + addText(Text1()); + addText(Text2()); + addText(Text3()); + + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + + box->setWidth(st::aboutWidth); } QString telegramFaqLink() { @@ -160,5 +161,154 @@ QString currentVersionText() { } else if (Platform::IsWindowsARM64()) { result += " arm64"; } +#ifdef _DEBUG + result += " DEBUG"; +#endif return result; } + +void ArchiveHintBox( + not_null box, + bool unarchiveOnNewMessage, + Fn onUnarchive) { + box->setNoContentMargin(true); + + const auto content = box->verticalLayout().get(); + + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + { + const auto &icon = st::dialogsArchiveUserpic; + const auto rect = Rect(icon.size() * 2); + auto owned = object_ptr(content); + owned->resize(rect.size()); + owned->setNaturalWidth(rect.width()); + const auto widget = box->addRow(std::move(owned), style::al_top); + widget->paintRequest( + ) | rpl::on_next([=] { + auto p = Painter(widget); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::activeButtonBg); + p.drawEllipse(rect); + icon.paintInCenter(p, rect); + }, widget->lifetime()); + } + Ui::AddSkip(content); + Ui::AddSkip(content); + box->addRow( + object_ptr( + content, + tr::lng_archive_hint_title(), + st::boxTitle), + style::al_top); + Ui::AddSkip(content); + Ui::AddSkip(content); + { + const auto label = box->addRow( + object_ptr( + content, + (unarchiveOnNewMessage + ? tr::lng_archive_hint_about_unmuted + : tr::lng_archive_hint_about)( + lt_link, + tr::lng_archive_hint_about_link( + lt_emoji, + rpl::single( + Ui::Text::IconEmoji(&st::textMoreIconEmoji)), + tr::rich + ) | rpl::map([](TextWithEntities text) { + return tr::link(std::move(text), 1); + }), + tr::rich), + st::channelEarnHistoryRecipientLabel)); + label->resizeToWidth(box->width() + - rect::m::sum::h(st::boxRowPadding)); + label->setLink( + 1, + std::make_shared([=](ClickContext context) { + if (context.button == Qt::LeftButton) { + onUnarchive(); + } + })); + } + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + { + const auto padding = QMargins( + st::settingsButton.padding.left(), + st::boxRowPadding.top(), + st::boxRowPadding.right(), + st::boxRowPadding.bottom()); + const auto addEntry = [&]( + rpl::producer title, + rpl::producer about, + const style::icon &icon) { + const auto top = content->add( + object_ptr( + content, + std::move(title), + st::channelEarnSemiboldLabel), + padding); + Ui::AddSkip(content, st::channelEarnHistoryThreeSkip); + content->add( + object_ptr( + content, + std::move(about), + st::channelEarnHistoryRecipientLabel), + padding); + const auto left = Ui::CreateChild( + box->verticalLayout().get()); + left->paintRequest( + ) | rpl::on_next([=] { + auto p = Painter(left); + icon.paint(p, 0, 0, left->width()); + }, left->lifetime()); + left->resize(icon.size()); + top->geometryValue( + ) | rpl::on_next([=](const QRect &g) { + left->moveToLeft( + (g.left() - left->width()) / 2, + g.top() + st::channelEarnHistoryThreeSkip); + }, left->lifetime()); + }; + addEntry( + tr::lng_archive_hint_section_1(), + tr::lng_archive_hint_section_1_info(), + st::menuIconArchive); + Ui::AddSkip(content); + Ui::AddSkip(content); + addEntry( + tr::lng_archive_hint_section_2(), + tr::lng_archive_hint_section_2_info(), + st::menuIconStealth); + Ui::AddSkip(content); + Ui::AddSkip(content); + addEntry( + tr::lng_archive_hint_section_3(), + tr::lng_archive_hint_section_3_info(), + st::menuIconStoriesSavedSection); + Ui::AddSkip(content); + Ui::AddSkip(content); + } + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + { + const auto &st = st::premiumPreviewDoubledLimitsBox; + box->setStyle(st); + auto button = object_ptr( + box, + tr::lng_archive_hint_button(), + st::defaultActiveButton); + button->resizeToWidth(box->width() + - st.buttonPadding.left() + - st.buttonPadding.left()); + button->setClickedCallback([=] { box->closeBox(); }); + box->addButton(std::move(button)); + } +} + diff --git a/Telegram/SourceFiles/boxes/about_box.h b/Telegram/SourceFiles/boxes/about_box.h index e03b9970eca45f..60953aafd0f237 100644 --- a/Telegram/SourceFiles/boxes/about_box.h +++ b/Telegram/SourceFiles/boxes/about_box.h @@ -7,32 +7,13 @@ For license and copyright information please follow this link: */ #pragma once -#include "ui/layers/box_content.h" +#include "ui/layers/generic_box.h" -namespace Ui { -class LinkButton; -class FlatLabel; -} // namespace Ui - -class AboutBox : public Ui::BoxContent { -public: - AboutBox(QWidget*); - -protected: - void prepare() override; - - void resizeEvent(QResizeEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - -private: - void showVersionHistory(); - - object_ptr _version; - object_ptr _text1; - object_ptr _text2; - object_ptr _text3; - -}; +void AboutBox(not_null box); +void ArchiveHintBox( + not_null box, + bool unarchiveOnNewMessage, + Fn onUnarchive); QString telegramFaqLink(); QString currentVersionText(); diff --git a/Telegram/SourceFiles/boxes/about_sponsored_box.cpp b/Telegram/SourceFiles/boxes/about_sponsored_box.cpp index 8c932df6af7ef2..e4af2b8a1acef9 100644 --- a/Telegram/SourceFiles/boxes/about_sponsored_box.cpp +++ b/Telegram/SourceFiles/boxes/about_sponsored_box.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "core/file_utilities.h" #include "lang/lang_keys.h" #include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "styles/style_boxes.h" @@ -18,7 +19,7 @@ For license and copyright information please follow this link: namespace Ui { namespace { -constexpr auto kUrl = "https://promote.telegram.org"_cs; +constexpr auto kUrl = "https://ads.telegram.org"_cs; } // namespace @@ -37,11 +38,10 @@ void AboutSponsoredBox(not_null box) { st); button->setBrushOverride(Qt::NoBrush); button->setPenOverride(QPen(st::historyLinkInFg)); - button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); rpl::combine( row->sizeValue(), button->sizeValue() - ) | rpl::start_with_next([=]( + ) | rpl::on_next([=]( const QSize &rowSize, const QSize &buttonSize) { button->moveToLeft( @@ -54,8 +54,16 @@ void AboutSponsoredBox(not_null box) { }; const auto &stLabel = st::aboutLabel; - const auto info1 = box->addRow(object_ptr(box, stLabel)); - info1->setText(tr::lng_sponsored_info_description1(tr::now)); + auto text1 = tr::lng_sponsored_info_description1_linked( + lt_link, + rpl::combine( + tr::lng_sponsored_info_description1_link(), + tr::lng_sponsored_info_description1_url() + ) | rpl::map([](const QString &text, const QString &url) { + return tr::link(text, url); + }), + tr::rich); + box->addRow(object_ptr(box, std::move(text1), stLabel)); box->addSkip(st::sponsoredUrlButtonSkip); addUrl(); diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index d48e93108f39a6..119875a2935159 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -37,11 +37,11 @@ void showBox( } // namespace internal template -QPointer show( +base::weak_qptr show( object_ptr content, Ui::LayerOptions options = Ui::LayerOption::CloseOther, anim::type animated = anim::type::normal) { - auto result = QPointer(content.data()); + auto result = base::weak_qptr(content.data()); internal::showBox(std::move(content), options, animated); return result; } diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 0aba9b6a5ee8df..815e2bbea923c7 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -42,6 +42,7 @@ For license and copyright information please follow this link: #include "api/api_peer_photo.h" #include "api/api_self_destruct.h" #include "main/main_session.h" +#include "styles/style_chat_helpers.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" @@ -111,6 +112,10 @@ void ChatCreateDone( show, chat, CollectForbiddenUsers(&chat->session(), result)); + chat->owner().addRecentJoinChat({ + .fromPeerId = chat->id, + .joinedPeerId = chat->id, + }); } }; if (!success) { @@ -122,22 +127,12 @@ void ChatCreateDone( void MustBePublicDestroy(not_null channel) { const auto session = &channel->session(); session->api().request(MTPchannels_DeleteChannel( - channel->inputChannel + channel->inputChannel() )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); }).send(); } -void MustBePublicFailed( - not_null navigation, - not_null channel) { - const auto text = channel->isMegagroup() - ? "Can't create a public group :(" - : "Can't create a public channel :("; - navigation->showToast(text); - MustBePublicDestroy(channel); -} - [[nodiscard]] Fn)> WrapPeerDoneFromChannelDone( Fn)> channelDone) { if (!channelDone) { @@ -156,7 +151,7 @@ void MustBePublicFailed( TextWithEntities PeerFloodErrorText( not_null session, PeerFloodType type) { - const auto link = Ui::Text::Link( + const auto link = tr::link( tr::lng_cant_more_info(tr::now), session->createInternalLinkFull(u"spambot"_q)); return ((type == PeerFloodType::InviteGroup) @@ -165,7 +160,7 @@ TextWithEntities PeerFloodErrorText( tr::now, lt_more_info, link, - Ui::Text::WithEntities); + tr::marked); } void ShowAddParticipantsError( @@ -194,16 +189,16 @@ void ShowAddParticipantsError( && channel->canAddAdmins()) { const auto makeAdmin = [=](Fn close) { const auto user = forbidden.users.front(); - const auto weak = std::make_shared>(); + const auto weak = std::make_shared>(); const auto done = [=](auto&&...) { - if (const auto strong = weak->data()) { + if (const auto strong = weak->get()) { strong->uiShow()->showToast( tr::lng_box_done(tr::now)); strong->closeBox(); } }; const auto fail = [=] { - if (const auto strong = weak->data()) { + if (const auto strong = weak->get()) { strong->closeBox(); } }; @@ -241,7 +236,7 @@ void ShowAddParticipantsError( ? PeerFloodType::InviteGroup : PeerFloodType::InviteChannel; const auto text = PeerFloodErrorText(&chat->session(), type); - Ui::show(Ui::MakeInformBox(text), Ui::LayerOption::KeepOther); + show->showBox(Ui::MakeInformBox(text), Ui::LayerOption::KeepOther); return; } else if (error == u"USER_PRIVACY_RESTRICTED"_q) { ChatInviteForbidden(show, chat, forbidden); @@ -262,10 +257,20 @@ void ShowAddParticipantsError( return tr::lng_bot_already_in_group(tr::now); } else if (error == u"BOT_GROUPS_BLOCKED"_q) { return tr::lng_error_cant_add_bot(tr::now); + } else if (error == u"YOU_BLOCKED_USER"_q) { + return tr::lng_error_you_blocked_user(tr::now); + } else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) { + return tr::lng_error_add_admin_not_member(tr::now); + } else if (error == u"USER_ADMIN_INVALID"_q) { + return tr::lng_error_user_admin_invalid(tr::now); + } else if (error == u"BOTS_TOO_MUCH"_q) { + return (chat->isChannel() + ? tr::lng_error_channel_bots_too_much + : tr::lng_error_group_bots_too_much)(tr::now); } else if (error == u"ADMINS_TOO_MUCH"_q) { - return ((chat->isChat() || chat->isMegagroup()) - ? tr::lng_error_admin_limit - : tr::lng_error_admin_limit_channel)(tr::now); + return (chat->isBroadcast() + ? tr::lng_error_admin_limit_channel + : tr::lng_error_admin_limit)(tr::now); } return tr::lng_failed_add_participant(tr::now); }(); @@ -314,9 +319,9 @@ void AddContactBox::prepare() { const auto submitted = [=] { submit(); }; _first->submits( - ) | rpl::start_with_next(submitted, _first->lifetime()); + ) | rpl::on_next(submitted, _first->lifetime()); _last->submits( - ) | rpl::start_with_next(submitted, _last->lifetime()); + ) | rpl::on_next(submitted, _last->lifetime()); connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); }); setDimensions( @@ -440,7 +445,7 @@ void AddContactBox::save() { firstName = lastName; lastName = QString(); } - const auto weak = Ui::MakeWeak(this); + const auto weak = base::make_weak(this); const auto session = _session; _sentName = firstName; _contactId = base::RandomValue(); @@ -448,10 +453,12 @@ void AddContactBox::save() { MTP_vector( 1, MTP_inputPhoneContact( + MTP_flags(0), MTP_long(_contactId), MTP_string(phone), MTP_string(firstName), - MTP_string(lastName))) + MTP_string(lastName), + MTPTextWithEntities())) // note )).done(crl::guard(weak, [=]( const MTPcontacts_ImportedContacts &result) { const auto &data = result.data(); @@ -553,7 +560,7 @@ void GroupInfoBox::prepare() { &_navigation->parentController()->window(), Ui::UserpicButton::Role::ChoosePhoto, st::defaultUserpicButton, - (_type == Type::Forum)); + (_type == Type::Forum) ? Ui::PeerUserpicShape::Forum : Ui::PeerUserpicShape::Auto); _photo->showCustomOnChosen(); _title.create( this, @@ -565,7 +572,8 @@ void GroupInfoBox::prepare() { _title->setMaxLength(Ui::EditPeer::kMaxGroupChannelTitle); _title->setInstantReplaces(Ui::InstantReplaces::Default()); _title->setInstantReplacesEnabled( - Core::App().settings().replaceEmojiValue()); + Core::App().settings().replaceEmojiValue(), + Core::App().settings().systemTextReplaceValue()); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _title, @@ -581,18 +589,19 @@ void GroupInfoBox::prepare() { _description->setMaxLength(Ui::EditPeer::kMaxChannelDescription); _description->setInstantReplaces(Ui::InstantReplaces::Default()); _description->setInstantReplacesEnabled( - Core::App().settings().replaceEmojiValue()); + Core::App().settings().replaceEmojiValue(), + Core::App().settings().systemTextReplaceValue()); _description->setSubmitSettings( Core::App().settings().sendSubmitWay()); _description->heightChanges( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { descriptionResized(); }, _description->lifetime()); _description->submits( - ) | rpl::start_with_next([=] { submit(); }, _description->lifetime()); + ) | rpl::on_next([=] { submit(); }, _description->lifetime()); _description->cancelled( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { closeBox(); }, _description->lifetime()); @@ -602,7 +611,7 @@ void GroupInfoBox::prepare() { &_navigation->session()); } _title->submits( - ) | rpl::start_with_next([=] { submitName(); }, _title->lifetime()); + ) | rpl::on_next([=] { submitName(); }, _title->lifetime()); addButton( ((_type != Type::Group || _canAddBot) @@ -710,7 +719,7 @@ TimeId GroupInfoBox::ttlPeriod() const { } void GroupInfoBox::createGroup( - QPointer selectUsersBox, + base::weak_qptr selectUsersBox, const QString &title, const std::vector> &users) { if (_creationRequestId) { @@ -723,7 +732,7 @@ void GroupInfoBox::createGroup( auto user = peer->asUser(); Assert(user != nullptr); if (!user->isSelf()) { - inputs.push_back(user->inputUser); + inputs.push_back(user->inputUser()); } } _creationRequestId = _api.request(MTPmessages_CreateChat( @@ -742,25 +751,25 @@ void GroupInfoBox::createGroup( }).fail([=](const MTP::Error &error) { const auto &type = error.type(); _creationRequestId = 0; - const auto controller = _navigation->parentController(); + const auto show = uiShow(); if (type == u"NO_CHAT_TITLE"_q) { - const auto weak = Ui::MakeWeak(this); - if (const auto strong = selectUsersBox.data()) { + const auto weak = base::make_weak(this); + if (const auto strong = selectUsersBox.get()) { strong->closeBox(); } if (weak) { _title->showError(); } } else if (type == u"USERS_TOO_FEW"_q) { - controller->show( + show->showBox( Ui::MakeInformBox(tr::lng_cant_invite_privacy())); } else if (type == u"PEER_FLOOD"_q) { - controller->show(Ui::MakeInformBox( + show->showBox(Ui::MakeInformBox( PeerFloodErrorText( &_navigation->session(), PeerFloodType::InviteGroup))); } else if (type == u"USER_RESTRICTED"_q) { - controller->show(Ui::MakeInformBox(tr::lng_cant_do_this())); + show->showBox(Ui::MakeInformBox(tr::lng_cant_do_this())); } }).send(); } @@ -786,10 +795,10 @@ void GroupInfoBox::submit() { } else if (_canAddBot) { createGroup(nullptr, title, { not_null(_canAddBot) }); } else { - auto initBox = [title, weak = Ui::MakeWeak(this)]( + auto initBox = [title, weak = base::make_weak(this)]( not_null box) { auto create = [box, title, weak] { - if (const auto strong = weak.data()) { + if (const auto strong = weak.get()) { strong->createGroup( box.get(), title, @@ -799,7 +808,7 @@ void GroupInfoBox::submit() { box->addButton(tr::lng_create_group_create(), std::move(create)); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }; - Ui::show( + uiShow()->showBox( Box( std::make_unique( &_navigation->session()), @@ -877,18 +886,14 @@ void GroupInfoBox::createChannel( }).fail([this](const MTP::Error &error) { const auto &type = error.type(); _creationRequestId = 0; - const auto controller = _navigation->parentController(); + const auto show = uiShow(); if (type == u"NO_CHAT_TITLE"_q) { _title->setFocus(); _title->showError(); } else if (type == u"USER_RESTRICTED"_q) { - controller->show( - Ui::MakeInformBox(tr::lng_cant_do_this()), - Ui::LayerOption::CloseOther); + show->showBox(Ui::MakeInformBox(tr::lng_cant_do_this())); } else if (type == u"CHANNELS_TOO_MUCH"_q) { - controller->show( - Box(ChannelsLimitBox, &controller->session()), - Ui::LayerOption::CloseOther); // TODO + show->showBox(Box(ChannelsLimitBox, &_navigation->session())); } }).send(); } @@ -908,7 +913,7 @@ void GroupInfoBox::checkInviteLink() { _createdChannel->session().changes().peerUpdates( _createdChannel, Data::PeerUpdate::Flag::FullInfo - ) | rpl::take(1) | rpl::start_with_next([=] { + ) | rpl::take(1) | rpl::on_next([=] { checkInviteLink(); }, lifetime()); } @@ -921,13 +926,14 @@ void GroupInfoBox::channelReady() { closeBox(); callback(argument); } else { - _navigation->parentController()->show( + uiShow()->showBox( Box( _navigation, _createdChannel, _mustBePublic, _done), - Ui::LayerOption::CloseOther); + Ui::LayerOption::KeepOther); + closeBox(); } } @@ -1022,7 +1028,7 @@ void SetupChannelBox::prepare() { setMouseTracking(true); _checkRequestId = _api.request(MTPchannels_CheckUsername( - _channel->inputChannel, + _channel->inputChannel(), MTP_string("preston") )).fail([=](const MTP::Error &error) { _checkRequestId = 0; @@ -1051,11 +1057,11 @@ void SetupChannelBox::prepare() { _channel->session().changes().peerUpdates( _channel, Data::PeerUpdate::Flag::InviteLinks - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { rtlupdate(_invitationLink); }, lifetime()); - boxClosing() | rpl::start_with_next([=] { + boxClosing() | rpl::on_next([=] { if (!_mustBePublic) { AddParticipantsBoxController::Start(_navigation, _channel); } @@ -1243,7 +1249,11 @@ void SetupChannelBox::mousePressEvent(QMouseEvent *e) { return; } else if (!_channel->inviteLink().isEmpty()) { QGuiApplication::clipboard()->setText(_channel->inviteLink()); - showToast(tr::lng_create_channel_link_copied(tr::now)); + showToast({ + .text = { tr::lng_create_channel_link_copied(tr::now) }, + .iconLottie = u"toast/voip_invite"_q, + .iconLottieSize = st::toastLottieIconSize, + }); } else if (_channel->isFullLoaded() && !_creatingInviteLink) { _creatingInviteLink = true; _channel->session().api().inviteLinks().create({ _channel }); @@ -1269,7 +1279,7 @@ void SetupChannelBox::save() { const auto saveUsername = [&](const QString &link) { _sentUsername = link; _saveRequestId = _api.request(MTPchannels_UpdateUsername( - _channel->inputChannel, + _channel->inputChannel(), MTP_string(_sentUsername) )).done([=] { const auto done = _done; @@ -1353,7 +1363,7 @@ void SetupChannelBox::check() { if (link.size() >= Ui::EditPeer::kMinUsernameLength) { _checkUsername = link; _checkRequestId = _api.request(MTPchannels_CheckUsername( - _channel->inputChannel, + _channel->inputChannel(), MTP_string(link) )).done([=](const MTPBool &result) { _checkRequestId = 0; @@ -1381,7 +1391,7 @@ void SetupChannelBox::privacyChanged(Privacy value) { _privacyGroup->setValue(Privacy::Public); check(); }); - Ui::show( + uiShow()->showBox( Box(PublicLinksLimitBox, _navigation, callback), Ui::LayerOption::KeepOther); return; @@ -1473,17 +1483,18 @@ void SetupChannelBox::showRevokePublicLinkBoxForEdit() { const auto mustBePublic = _mustBePublic; const auto done = _done; const auto navigation = _navigation; + const auto show = uiShow(); const auto revoked = std::make_shared(false); const auto callback = [=] { *revoked = true; - navigation->parentController()->show( + show->showBox( Box(navigation, channel, mustBePublic, done)); }; - const auto revoker = navigation->parentController()->show( + const auto revoker = show->show( Box(PublicLinksLimitBox, navigation, callback)); const auto session = &navigation->session(); revoker->boxClosing( - ) | rpl::start_with_next(crl::guard(session, [=] { + ) | rpl::on_next(crl::guard(session, [=] { base::call_delayed(200, session, [=] { if (*revoked) { return; @@ -1495,7 +1506,10 @@ void SetupChannelBox::showRevokePublicLinkBoxForEdit() { } void SetupChannelBox::mustBePublicFailed() { - MustBePublicFailed(_navigation, _channel); + showToast(_channel->isMegagroup() + ? "Can't create a public group :(" + : "Can't create a public channel :("); + MustBePublicDestroy(_channel); } void SetupChannelBox::firstCheckFail(UsernameResult result) { @@ -1517,7 +1531,10 @@ void SetupChannelBox::firstCheckFail(UsernameResult result) { } } -EditNameBox::EditNameBox(QWidget*, not_null user) +EditNameBox::EditNameBox( + QWidget*, + not_null user, + Focus focus) : _user(user) , _api(&_user->session().mtp()) , _first( @@ -1530,7 +1547,8 @@ EditNameBox::EditNameBox(QWidget*, not_null user) st::defaultInputField, tr::lng_signup_lastname(), _user->lastName) -, _invertOrder(langFirstNameGoesSecond()) { +, _invertOrder(langFirstNameGoesSecond()) +, _focus(focus) { } void EditNameBox::prepare() { @@ -1551,25 +1569,26 @@ void EditNameBox::prepare() { _last->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName); _first->submits( - ) | rpl::start_with_next([=] { submit(); }, _first->lifetime()); + ) | rpl::on_next([=] { submit(); }, _first->lifetime()); _last->submits( - ) | rpl::start_with_next([=] { submit(); }, _last->lifetime()); - - _first->customTab(true); - _last->customTab(true); + ) | rpl::on_next([=] { submit(); }, _last->lifetime()); _first->tabbed( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=](not_null handled) { _last->setFocus(); + *handled = true; }, _first->lifetime()); _last->tabbed( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=](not_null handled) { _first->setFocus(); + *handled = true; }, _last->lifetime()); } void EditNameBox::setInnerFocus() { - (_invertOrder ? _last : _first)->setFocusFast(); + const auto focusLast = (_focus == Focus::LastName) + || (_focus == Focus::FirstName && _invertOrder); + (focusLast ? _last : _first)->setFocusFast(); } void EditNameBox::submit() { diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index 9bc7de65209c72..cb61dbdfd5390c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -129,7 +129,7 @@ class GroupInfoBox : public Ui::BoxContent { private: void createChannel(const QString &title, const QString &description); void createGroup( - QPointer selectUsersBox, + base::weak_qptr selectUsersBox, const QString &title, const std::vector> &users); void submitName(); @@ -246,7 +246,14 @@ class SetupChannelBox final : public Ui::BoxContent { class EditNameBox : public Ui::BoxContent { public: - EditNameBox(QWidget*, not_null user); + enum class Focus { + FirstName, + LastName, + }; + EditNameBox( + QWidget*, + not_null user, + Focus focus = Focus::FirstName); protected: void setInnerFocus() override; @@ -266,6 +273,7 @@ class EditNameBox : public Ui::BoxContent { object_ptr _last; bool _invertOrder = false; + Focus _focus = Focus::FirstName; mtpRequestId _requestId = 0; QString _sentName; diff --git a/Telegram/SourceFiles/boxes/auto_download_box.cpp b/Telegram/SourceFiles/boxes/auto_download_box.cpp index 3617a9eb65eb58..db507d366eb7c1 100644 --- a/Telegram/SourceFiles/boxes/auto_download_box.cpp +++ b/Telegram/SourceFiles/boxes/auto_download_box.cpp @@ -7,9 +7,13 @@ For license and copyright information please follow this link: */ #include "boxes/auto_download_box.h" +#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" +#include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "data/data_peer.h" #include "data/data_session.h" #include "data/data_auto_download.h" #include "ui/vertical_list.h" @@ -31,6 +35,103 @@ constexpr auto kDefaultDownloadLimit = 10 * kMegabyte; constexpr auto kDefaultAutoPlayLimit = 50 * kMegabyte; using Type = Data::AutoDownload::Type; +using Source = Data::AutoDownload::Source; +using Override = Data::AutoDownload::Override; + +[[nodiscard]] bool PeerMatchesSource( + not_null peer, + Source source) { + if (peer->isSelf() || peer->isServiceUser() || peer->isRepliesChat()) { + return false; + } + switch (source) { + case Source::User: return peer->isUser(); + case Source::Group: return peer->isChat() || peer->isMegagroup(); + case Source::Channel: + return peer->isChannel() && !peer->isMegagroup(); + } + Unexpected("Source in PeerMatchesSource."); +} + +struct PeerBuckets { + base::flat_set always; + base::flat_set never; +}; + +[[nodiscard]] PeerBuckets BucketsForSource( + const Data::AutoDownload::Full &settings, + not_null owner, + Source source) { + auto result = PeerBuckets(); + settings.enumeratePeerOverrides([&](PeerId peerId, Override value) { + const auto peer = owner->peer(peerId); + if (!PeerMatchesSource(peer, source)) { + return; + } else if (value == Override::ForceAllow) { + result.always.emplace(peerId); + } else if (value == Override::ForceDeny) { + result.never.emplace(peerId); + } + }); + return result; +} + +class ExceptionsBoxController final : public ChatsListBoxController { +public: + ExceptionsBoxController( + not_null session, + Source source, + base::flat_set selected); + + Main::Session &session() const override; + void rowClicked(not_null row) override; + +protected: + void prepareViewHook() override; + std::unique_ptr createRow(not_null history) override; + +private: + const not_null _session; + const Source _source; + base::flat_set _selected; + +}; + +ExceptionsBoxController::ExceptionsBoxController( + not_null session, + Source source, + base::flat_set selected) +: ChatsListBoxController(session) +, _session(session) +, _source(source) +, _selected(std::move(selected)) { +} + +Main::Session &ExceptionsBoxController::session() const { + return *_session; +} + +void ExceptionsBoxController::rowClicked(not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); +} + +void ExceptionsBoxController::prepareViewHook() { + auto &owner = _session->data(); + auto peers = std::vector>(); + peers.reserve(_selected.size()); + for (const auto &peerId : _selected) { + peers.push_back(owner.peer(peerId)); + } + delegate()->peerListAddSelectedPeers(peers); +} + +auto ExceptionsBoxController::createRow(not_null history) +-> std::unique_ptr { + const auto peer = history->peer; + return PeerMatchesSource(peer, _source) + ? std::make_unique(history) + : nullptr; +} not_null AddSizeLimitSlider( not_null container, @@ -116,7 +217,7 @@ void AutoDownloadBox::setupContent() { ))->toggleOn( rpl::single(value > 0) )->toggledChanges( - ) | rpl::start_with_next([=](bool enabled) { + ) | rpl::on_next([=](bool enabled) { (*values)[type] = enabled ? 1 : 0; }, content->lifetime()); values->emplace(type, value); @@ -157,6 +258,106 @@ void AutoDownloadBox::setupContent() { : *downloadLimit; }; + const auto staged = content->lifetime().make_state( + BucketsForSource(*settings, &_session->data(), _source)); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, tr::lng_media_auto_exceptions()); + + const auto countText = [source = _source](int count) { + switch (source) { + case Source::User: + return tr::lng_media_auto_exceptions_users( + tr::now, + lt_count, + count); + case Source::Group: + return tr::lng_media_auto_exceptions_groups( + tr::now, + lt_count, + count); + case Source::Channel: + return tr::lng_media_auto_exceptions_channels( + tr::now, + lt_count, + count); + } + Unexpected("Source in countText."); + }; + const auto addText = [source = _source] { + switch (source) { + case Source::User: + return tr::lng_media_auto_exceptions_add_users(tr::now); + case Source::Group: + return tr::lng_media_auto_exceptions_add_groups(tr::now); + case Source::Channel: + return tr::lng_media_auto_exceptions_add_channels(tr::now); + } + Unexpected("Source in addText."); + }; + + const auto updates = content->lifetime().make_state>(); + const auto addExceptionButton = [&]( + Override kind, + rpl::producer text, + rpl::producer title) { + auto label = updates->events_starting_with( + {} + ) | rpl::map([=] { + const auto count = int((kind == Override::ForceAllow) + ? staged->always.size() + : staged->never.size()); + return count ? countText(count) : addText(); + }); + const auto button = AddButtonWithLabel( + content, + std::move(text), + std::move(label), + st::settingsButtonNoIcon); + button->setClickedCallback([=, title = std::move(title)]() mutable { + const auto &selected = (kind == Override::ForceAllow) + ? staged->always + : staged->never; + auto controller = std::make_unique( + _session, + _source, + selected); + auto initBox = [=, this, title = std::move(title)]( + not_null box) mutable { + box->setTitle(std::move(title)); + box->addButton(tr::lng_settings_save(), crl::guard(this, [=] { + auto &dest = (kind == Override::ForceAllow) + ? staged->always + : staged->never; + auto &other = (kind == Override::ForceAllow) + ? staged->never + : staged->always; + dest.clear(); + for (const auto &peer : box->collectSelectedRows()) { + dest.emplace(peer->id); + other.remove(peer->id); + } + updates->fire({}); + box->closeBox(); + })); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + getDelegate()->show(Box( + std::move(controller), + std::move(initBox))); + }); + }; + addExceptionButton( + Override::ForceAllow, + tr::lng_media_auto_always(), + tr::lng_media_auto_always_title()); + addExceptionButton( + Override::ForceDeny, + tr::lng_media_auto_never(), + tr::lng_media_auto_never_title()); + addButton(tr::lng_connection_save(), [=] { auto &&values = ranges::views::concat( *downloadValues, @@ -190,6 +391,25 @@ void AutoDownloadBox::setupContent() { return (now > 0) && (now != limitByType(type)); }); + const auto current = BucketsForSource( + *settings, + &_session->data(), + _source); + const auto exceptionsChanged = (current.always != staged->always) + || (current.never != staged->never); + const auto exceptionsAllowMore = ranges::any_of( + staged->always, + [&](PeerId id) { return !current.always.contains(id); } + ) || ranges::any_of( + current.never, + [&](PeerId id) { return !staged->never.contains(id); }); + const auto exceptionsAllowLess = ranges::any_of( + staged->never, + [&](PeerId id) { return !current.never.contains(id); } + ) || ranges::any_of( + current.always, + [&](PeerId id) { return !staged->always.contains(id); }); + if (changed) { for (const auto &[type, enabled] : values) { const auto value = enabled ? limitByType(type) : 0; @@ -207,16 +427,35 @@ void AutoDownloadBox::setupContent() { } } } - if (changed || hiddenChanged) { + if (exceptionsChanged) { + for (const auto &peerId : current.always) { + if (!staged->always.contains(peerId)) { + settings->setPeerOverride(peerId, Override::Default); + } + } + for (const auto &peerId : current.never) { + if (!staged->never.contains(peerId)) { + settings->setPeerOverride(peerId, Override::Default); + } + } + for (const auto &peerId : staged->always) { + settings->setPeerOverride(peerId, Override::ForceAllow); + } + for (const auto &peerId : staged->never) { + settings->setPeerOverride(peerId, Override::ForceDeny); + } + } + if (changed || hiddenChanged || exceptionsChanged) { _session->saveSettingsDelayed(); } - if (allowMoreTypes.contains(Type::Photo)) { + if (allowMoreTypes.contains(Type::Photo) || exceptionsAllowMore) { _session->data().photoLoadSettingsChanged(); } - if (ranges::any_of(allowMoreTypes, _1 != Type::Photo)) { + if (ranges::any_of(allowMoreTypes, _1 != Type::Photo) + || exceptionsAllowMore) { _session->data().documentLoadSettingsChanged(); } - if (less) { + if (less || exceptionsAllowLess) { _session->data().checkPlayingAnimations(); } closeBox(); diff --git a/Telegram/SourceFiles/boxes/auto_lock_box.cpp b/Telegram/SourceFiles/boxes/auto_lock_box.cpp index cc7cdb427151cc..eb3d44bb85f3ce 100644 --- a/Telegram/SourceFiles/boxes/auto_lock_box.cpp +++ b/Telegram/SourceFiles/boxes/auto_lock_box.cpp @@ -102,7 +102,7 @@ void AutoLockBox::prepare() { }; timeInput->focuses( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { group->setValue(kCustom); }, lifetime()); @@ -119,7 +119,7 @@ void AutoLockBox::prepare() { [=] { return group->current() == kCustom; } ), timeInput->submitRequests() - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { if (const auto result = collect()) { durationChanged(result); } else { diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 59729b7feddd2e..40cb7c2f858d3e 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -211,12 +211,12 @@ void BackgroundBox::prepare() { setInnerTopSkip(st::lineWidth); _inner->chooseEvents( - ) | rpl::start_with_next([=](const Data::WallPaper &paper) { + ) | rpl::on_next([=](const Data::WallPaper &paper) { chosen(paper); }, _inner->lifetime()); _inner->removeRequests( - ) | rpl::start_with_next([=](const Data::WallPaper &paper) { + ) | rpl::on_next([=](const Data::WallPaper &paper) { removePaper(paper); }, _inner->lifetime()); } @@ -337,7 +337,7 @@ void BackgroundBox::resetForPeer() { const auto api = &_controller->session().api(); api->request(MTPmessages_SetChatWallPaper( MTP_flags(0), - _forPeer->input, + _forPeer->input(), MTPInputWallPaper(), MTPWallPaperSettings(), MTPint() @@ -345,7 +345,7 @@ void BackgroundBox::resetForPeer() { api->applyUpdates(result); }).send(); - const auto weak = Ui::MakeWeak(this); + const auto weak = base::make_weak(this); _forPeer->setWallPaper({}); if (weak) { _controller->finishChatThemeEdit(_forPeer); @@ -358,7 +358,7 @@ bool BackgroundBox::forChannel() const { void BackgroundBox::removePaper(const Data::WallPaper &paper) { const auto session = &_controller->session(); - const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) { + const auto remove = [=, weak = base::make_weak(this)](Fn &&close) { close(); if (weak) { weak->_inner->removePaper(paper); @@ -396,30 +396,30 @@ BackgroundBox::Inner::Inner( + st::backgroundPadding)); Window::Theme::IsNightModeValue( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { updatePapers(); }, lifetime()); requestPapers(); _session->downloaderTaskFinished( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { update(); }, lifetime()); style::PaletteChanged( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _check->invalidateCache(); }, lifetime()); if (forChannel()) { _session->data().cloudThemes().chatThemesUpdated( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { updatePapers(); }, lifetime()); } else { using Update = Window::Theme::BackgroundUpdate; Window::Theme::Background()->updates( - ) | rpl::start_with_next([=](const Update &update) { + ) | rpl::on_next([=](const Update &update) { if (update.type == Update::Type::New) { sortPapers(); requestPapers(); @@ -451,12 +451,12 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const return {}; } const auto nonCustom = Window::Theme::Background()->paper(); - const auto themeEmoji = _forPeer->themeEmoji(); - if (forChannel() || themeEmoji.isEmpty()) { + const auto themeToken = _forPeer->themeToken(); + if (forChannel() || themeToken.isEmpty()) { return nonCustom; } const auto &themes = _forPeer->owner().cloudThemes(); - const auto theme = themes.themeForEmoji(themeEmoji); + const auto theme = themes.themeForToken(themeToken); if (!theme) { return nonCustom; } diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 828b2d095090ed..f6646b3ebf3711 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -41,12 +41,13 @@ For license and copyright information please follow this link: #include "data/data_file_origin.h" #include "data/data_peer_values.h" #include "data/data_premium_limits.h" -#include "settings/settings_premium.h" +#include "settings/sections/settings_premium.h" #include "storage/file_upload.h" #include "storage/localimageloader.h" #include "window/window_session_controller.h" #include "window/themes/window_themes_embedded.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -161,7 +162,7 @@ constexpr auto kMaxWallPaperSlugLength = 255; return paper; } const auto &themes = session->data().cloudThemes(); - if (const auto theme = themes.themeForEmoji(paper.emojiId())) { + if (const auto theme = themes.themeForToken(paper.emojiId())) { using Type = Data::CloudThemeType; const auto type = dark ? Type::Dark : Type::Light; const auto i = theme->settings.find(type); @@ -225,28 +226,28 @@ BackgroundPreviewBox::BackgroundPreviewBox( } generateBackground(); _controller->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { update(); }, lifetime()); _appNightMode.changes( - ) | rpl::start_with_next([=](bool night) { + ) | rpl::on_next([=](bool night) { _boxDarkMode = night; update(); }, lifetime()); _boxDarkMode.changes( - ) | rpl::start_with_next([=](bool dark) { + ) | rpl::on_next([=](bool dark) { applyDarkMode(dark); }, lifetime()); const auto prepare = [=](bool dark, auto pointer) { - const auto weak = Ui::MakeWeak(this); + const auto weak = base::make_weak(this); crl::async([=] { auto result = std::make_unique(); Window::Theme::PreparePaletteCallback(dark, {})(*result); crl::on_main([=, result = std::move(result)]() mutable { - if (const auto strong = weak.data()) { + if (const auto strong = weak.get()) { strong->*pointer = std::move(result); strong->paletteReady(); } @@ -373,7 +374,7 @@ void BackgroundPreviewBox::createDimmingSlider(bool dark) { _dimmingContent->resize(inner->size()); _dimmingContent->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { + ) | rpl::on_next([=](QRect clip) { auto p = QPainter(_dimmingContent); const auto palette = (dark ? _darkPalette : _lightPalette).get(); p.fillRect(clip, equals ? st::boxBg : palette->boxBg()); @@ -386,13 +387,13 @@ void BackgroundPreviewBox::createDimmingSlider(bool dark) { heightValue(), _dimmingWrap->heightValue(), rpl::mappers::_1 - rpl::mappers::_2 - ) | rpl::start_with_next([=](int top) { + ) | rpl::on_next([=](int top) { _dimmingWrap->move(0, top); }, _dimmingWrap->lifetime()); _dimmingWrap->toggle(dark, anim::type::instant); _dimmingHeight = _dimmingWrap->heightValue(); - _dimmingHeight.changes() | rpl::start_with_next([=] { + _dimmingHeight.changes() | rpl::on_next([=] { update(); }, _dimmingWrap->lifetime()); } @@ -554,7 +555,7 @@ void BackgroundPreviewBox::recreateBlurCheckbox() { sizeValue(), _blur->sizeValue(), _dimmingHeight.value() - ) | rpl::start_with_next([=](QSize outer, QSize inner, int dimming) { + ) | rpl::on_next([=](QSize outer, QSize inner, int dimming) { const auto bottom = st::historyPaddingBottom; _blur->move( (outer.width() - inner.width()) / 2, @@ -562,7 +563,7 @@ void BackgroundPreviewBox::recreateBlurCheckbox() { }, _blur->lifetime()); _blur->checkedChanges( - ) | rpl::start_with_next([=](bool checked) { + ) | rpl::on_next([=](bool checked) { checkBlurAnimationStart(); update(); }, _blur->lifetime()); @@ -607,7 +608,7 @@ void BackgroundPreviewBox::uploadForPeer(bool both) { document->size); session->uploader().documentProgress( - ) | rpl::start_with_next([=](const FullMsgId &fullId) { + ) | rpl::on_next([=](const FullMsgId &fullId) { if (fullId != _uploadId) { return; } @@ -619,7 +620,7 @@ void BackgroundPreviewBox::uploadForPeer(bool both) { }, _uploadLifetime); session->uploader().documentReady( - ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { + ) | rpl::on_next([=](const Storage::UploadedMedia &data) { if (data.fullId != _uploadId) { return; } @@ -668,7 +669,7 @@ void BackgroundPreviewBox::setExistingForPeer( | (_fromMessageId ? Flag() : Flag::f_wallpaper) | (both ? Flag::f_for_both : Flag()) | Flag::f_settings), - _forPeer->input, + _forPeer->input(), paper.mtpInput(&_controller->session()), paper.mtpSettings(), MTP_int(_fromMessageId.msg) @@ -685,7 +686,7 @@ void BackgroundPreviewBox::checkLevelForChannel() { const auto show = _controller->uiShow(); _forPeerLevelCheck = true; - const auto weak = Ui::MakeWeak(this); + const auto weak = base::make_weak(this); CheckBoostLevel(show, _forPeer, [=](int level) { if (!weak) { return std::optional(); @@ -742,13 +743,13 @@ void BackgroundPreviewBox::applyForPeer() { object_ptr(this)); const auto overlay = _forBothOverlay->entity(); - sizeValue() | rpl::start_with_next([=](QSize size) { + sizeValue() | rpl::on_next([=](QSize size) { _forBothOverlay->setGeometry({ QPoint(), size }); overlay->setGeometry({ QPoint(), size }); }, _forBothOverlay->lifetime()); overlay->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { + ) | rpl::on_next([=](QRect clip) { auto p = QPainter(overlay); p.drawImage(0, 0, bg); p.fillRect(clip, QColor(0, 0, 0, 64)); @@ -787,17 +788,13 @@ void BackgroundPreviewBox::applyForPeer() { const auto raw = _forBothOverlay.release(); raw->shownValue() | rpl::filter( !rpl::mappers::_1 - ) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] { + ) | rpl::take(1) | rpl::on_next(crl::guard(raw, [=] { delete raw; }), raw->lifetime()); raw->toggle(false, anim::type::normal); }); - forMe->setTextTransform(RoundButton::TextTransform::NoTransform); - forBoth->setTextTransform(RoundButton::TextTransform::NoTransform); - cancel->setTextTransform(RoundButton::TextTransform::NoTransform); - overlay->sizeValue( - ) | rpl::start_with_next([=](QSize size) { + ) | rpl::on_next([=](QSize size) { const auto padding = st::backgroundConfirmPadding; const auto width = size.width() - padding.left() @@ -842,7 +839,11 @@ void BackgroundPreviewBox::applyForEveryone() { void BackgroundPreviewBox::share() { QGuiApplication::clipboard()->setText( _paper.shareUrl(&_controller->session())); - showToast(tr::lng_background_link_copied(tr::now)); + showToast({ + .text = { tr::lng_background_link_copied(tr::now) }, + .iconLottie = u"toast/voip_invite"_q, + .iconLottieSize = st::toastLottieIconSize, + }); } void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { @@ -960,6 +961,7 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { _chatStyle.get(), rect(), rect(), + rect(), _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer)); p.translate(0, textsTop()); if (_service) { @@ -1063,7 +1065,7 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { } _serviceBgLifetime = _paletteServiceBg.value( - ) | rpl::start_with_next([=](QColor color) { + ) | rpl::on_next([=](QColor color) { _serviceBg = Ui::ThemeAdjustedColor( color, QColor(red / count, green / count, blue / count)); @@ -1078,7 +1080,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { ? tr::lng_background_other_group(tr::now) : forChannel() ? tr::lng_background_other_channel(tr::now) - : (_forPeer && !_fromMessageId) + : (_forPeer + && !_fromMessageId + && !_forPeer->starsPerMessageChecked()) ? tr::lng_background_other_info( tr::now, lt_user, diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 22e7da4a60ce30..dd52d99bbb3bc1 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -23,6 +23,16 @@ UserpicButton { uploadBg: color; uploadIcon: icon; uploadIconPosition: point; + uploadProgressLine: pixels; + uploadProgressMargin: pixels; +} +UserpicsRow { + button: UserpicButton; + bg: color; + shift: pixels; + stroke: pixels; + complex: bool; + invert: bool; } ShortInfoBox { label: FlatLabel; @@ -64,6 +74,8 @@ defaultUserpicButton: UserpicButton { uploadBg: msgDateImgBgOver; uploadIcon: defaultUploadUserpicIcon; uploadIconPosition: point(-1px, 1px); + uploadProgressLine: 3px; + uploadProgressMargin: 8px; } uploadUserpicSize: 32px; uploadUserpicButton: UserpicButton(defaultUserpicButton) { @@ -73,13 +85,14 @@ uploadUserpicButton: UserpicButton(defaultUserpicButton) { changeIconPosition: point(4px, 4px); } uploadUserpicButtonBorder: 2px; +userpicUploadCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }}; restoreUserpicIcon: UserpicButton(defaultUserpicButton) { size: size(22px, 22px); photoSize: 22px; } confirmInviteTitle: FlatLabel(defaultFlatLabel) { - align: align(center); + align: align(top); minWidth: 320px; maxHeight: 24px; textFg: windowBoldFg; @@ -88,9 +101,8 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) { } } confirmInviteAbout: FlatLabel(boxLabel) { - align: align(center); + align: align(top); minWidth: 320px; - maxHeight: 60px; style: TextStyle(boxLabelStyle) { lineHeight: 19px; } @@ -102,10 +114,8 @@ confirmInviteStatus: FlatLabel(confirmInviteAbout) { } confirmInviteAboutPadding: margins(36px, 4px, 36px, 10px); confirmInviteAboutRequestsPadding: margins(36px, 9px, 36px, 15px); -confirmInviteTitleTop: 141px; confirmInvitePhotoSize: 96px; confirmInvitePhotoTop: 33px; -confirmInviteStatusTop: 164px; confirmInviteUserHeight: 100px; confirmInviteUserPhotoSize: 50px; confirmInviteUserPhotoTop: 210px; @@ -154,13 +164,25 @@ contactsSortButton: IconButton(defaultIconButton) { iconPosition: point(10px, -1px); rippleAreaPosition: point(1px, 6px); rippleAreaSize: 42px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } + ripple: defaultRippleAnimationBgOver; } contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }}; contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }}; +contactsSortHeaderHeight: 29px; +contactsSortHeaderFont: font(13px); +contactsSortHeaderPosition: point(16px, 7px); +contactsSortHeaderFg: windowSubTextFg; + +peerListIndexWidth: 22px; +peerListIndexApproach: 32px; +peerListIndexSlotHeight: 15px; +peerListIndexMinSlotHeight: 12px; +peerListIndexVerticalInset: 10px; +peerListIndexFont: font(bold 11px); +peerListIndexFg: windowSubTextFg; +peerListIndexActiveFg: windowActiveTextFg; + membersMarginTop: 10px; membersMarginBottom: 10px; @@ -189,7 +211,6 @@ peerListBox: PeerList(defaultPeerList) { } localStorageRowHeight: 50px; -localStorageRowPadding: margins(22px, 5px, 20px, 5px); localStorageRowTitle: FlatLabel(defaultFlatLabel) { textFg: windowBoldFg; maxHeight: 20px; @@ -208,12 +229,100 @@ localStorageClear: defaultBoxButton; localStorageLimitLabel: LabelSimple(defaultLabelSimple) { font: boxTextFont; } +localStorageLimitValue: LabelSimple(localStorageLimitLabel) { + textFg: windowActiveTextFg; +} localStorageLimitLabelMargin: margins(22px, 10px, 20px, 5px); localStorageLimitSlider: MediaSlider(defaultContinuousSlider) { seekSize: size(15px, 15px); } localStorageLimitMargin: margins(22px, 5px, 20px, 10px); +localStorageChartHeight: 200px; +localStorageChartDiameter: 172px; +localStorageChartThickness: 38px; +localStorageChartThicknessThin: 12px; +localStorageChartSelectGrow: 9px; +localStorageChartSizeFont: font(32px semibold); +localStorageChartUnitFont: font(13px); +localStorageChartLabelFont: font(13px); +localStorageChartPercentFont: font(15px semibold); +localStorageChartParticle: 15px; +localStorageCompleteStar1: 18px; +localStorageCompleteStar2: 12px; +localStorageCompleteStar3: 10px; +localStorageCompleteExclude: 80px; +localStorageCompleteField: 150px; +localStorageCompleteSpeed: 4px; +localStorageParticlePhotos: icon{{ "local_storage/photos-72x72", windowFg }}; +localStorageParticleVideos: icon{{ "local_storage/videos-72x72", windowFg }}; +localStorageParticleDocuments: icon{{ "local_storage/documents-72x72", windowFg }}; +localStorageParticleMusic: icon{{ "local_storage/music-72x72", windowFg }}; +localStorageParticleStickers: icon{{ "local_storage/stickers-72x72", windowFg }}; + +localStorageIslandMargin: margins(12px, 10px, 12px, 0px); +localStorageIslandRadius: 12px; +localStorageIslandPadding: 6px; +localStorageBottomSkip: 12px; + +localStorageCategoryHeight: 44px; +localStorageCategoryPadding: margins(22px, 0px, 20px, 0px); +localStorageCategoryCheckSkip: 14px; +localStorageCategoryPercentSkip: 8px; +localStorageCategoryCheck: Check(defaultCheck) { + diameter: 20px; +} +localStorageClearMargin: margins(12px, 6px, 12px, 9px); +localStorageClearHeight: 38px; +localStorageClearFont: font(13px semibold); +localStorageClearSkip: 7px; +localStorageCategoryName: FlatLabel(defaultFlatLabel) { + textFg: windowBoldFg; + maxHeight: 20px; + style: TextStyle(defaultTextStyle) { + font: font(13px); + } +} +localStorageCategoryPercent: FlatLabel(localStorageCategoryName) { + style: TextStyle(defaultTextStyle) { + font: font(13px semibold); + } +} +localStorageCategorySize: FlatLabel(localStorageCategoryName) { + textFg: windowActiveTextFg; +} + +localStorageUsageTopSkip: 2px; +localStorageUsageBarSkip: 9px; +localStorageUsageBarWidth: 174px; +localStorageUsageBarHeight: 4px; +localStorageUsageBottomSkip: 6px; +localStorageUsageBarMargin: margins(6px, 6px, 6px, 6px); +localStorageUsageTooltipMaxWidth: 240px; +localStorageUsageTooltipSkip: 8px; +localStorageAboutMargin: margins(28px, 9px, 28px, 12px); +localStorageAbout: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 240px; + style: TextStyle(defaultTextStyle) { + font: font(13px); + } +} +localStorageUsageSubtitle: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + align: align(top); + style: TextStyle(defaultTextStyle) { + font: font(13px); + } +} + +localStorageClearingSpinner: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: activeButtonBg; + thickness: 3px; + size: size(48px, 48px); +} +localStorageClearingSpinnerPadding: margins(0px, 20px, 0px, 14px); + shareRowsTop: 12px; shareRowHeight: 108px; sharePhotoTop: 6px; @@ -338,12 +447,11 @@ themeWarningHeight: 150px; themeWarningTextTop: 60px; aboutWidth: 390px; -aboutVersionTop: -3px; aboutVersionLink: LinkButton(defaultLinkButton) { color: windowSubTextFg; overColor: windowSubTextFg; } -aboutTextTop: 34px; +aboutTopSkip: 19px; aboutSkip: 14px; aboutLabel: FlatLabel(defaultFlatLabel) { minWidth: 300px; @@ -372,12 +480,9 @@ connectionPortInputField: InputField(defaultInputField) { width: 55px; } connectionUserInputField: InputField(defaultInputField) { - width: 95px; } connectionPasswordInputField: InputField(defaultInputField) { - width: 120px; } -connectionIPv6Skip: 11px; autolockWidth: 256px; autolockButton: Checkbox(defaultBoxCheckbox) { @@ -416,9 +521,7 @@ calendarPrevious: IconButton { rippleAreaPosition: point(2px, 2px); rippleAreaSize: 44px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } + ripple: defaultRippleAnimationBgOver; } calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }}; calendarNext: IconButton(calendarPrevious) { @@ -522,9 +625,9 @@ colorResultInput: InputField(colorValueInput) { changePhoneButton: RoundButton(defaultActiveButton) { width: 256px; } -changePhoneButtonPadding: margins(0px, 32px, 0px, 44px); changePhoneTitle: FlatLabel(boxTitle) { } +localStorageClearingLottieSize: size(150px, 150px); changePhoneTitlePadding: margins(0px, 8px, 0px, 8px); changePhoneDescription: FlatLabel(defaultFlatLabel) { minWidth: 332px; @@ -533,7 +636,6 @@ changePhoneDescription: FlatLabel(defaultFlatLabel) { } changePhoneDescriptionPadding: margins(0px, 1px, 0px, 8px); changePhoneIconPadding: margins(0px, 39px, 0px, 5px); -changePhoneIconSize: 120px; changePhoneLabel: FlatLabel(defaultFlatLabel) { minWidth: 275px; textFg: windowSubTextFg; @@ -542,18 +644,8 @@ changePhoneError: FlatLabel(changePhoneLabel) { textFg: boxTextFgError; } -adminLogFilterUserpicLeft: 15px; -adminLogFilterLittleSkip: 16px; -adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) { - style: TextStyle(boxTextStyle) { - font: font(boxFontSize semibold); - } -} -adminLogFilterSkip: 32px; -adminLogFilterUserCheckbox: Checkbox(defaultBoxCheckbox) { - margin: margins(8px, 6px, 8px, 6px); - checkPosition: point(8px, 6px); -} +normalBoxLottieSize: size(120px, 120px); + rightsCheckbox: Checkbox(defaultCheckbox) { textPosition: point(10px, 1px); rippleBg: attentionButtonBgOver; @@ -577,10 +669,10 @@ rightsButton: SettingsButton(defaultSettingsButton) { toggleSkip: 20px; } rightsButtonToggleWidth: 70px; +rightsButtonArrowSkip: 5px; rightsDividerMargin: margins(0px, 0px, 0px, 20px); rightsHeaderMargin: margins(22px, 13px, 22px, 7px); rightsToggleMargin: margins(22px, 8px, 22px, 8px); -rightsAboutMargin: margins(22px, 8px, 22px, 8px); rightsPhotoButton: UserpicButton(defaultUserpicButton) { size: size(60px, 60px); photoSize: 60px; @@ -616,9 +708,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px); proxyRowPadding: margins(22px, 8px, 8px, 8px); proxyRowIconSkip: 32px; proxyRowSkip: 2px; -proxyRowRipple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; -} +proxyRowRipple: defaultRippleAnimationBgOver; proxyRowTitleFg: windowFg; proxyRowTitlePalette: TextPalette(defaultTextPalette) { linkFg: windowSubTextFg; @@ -654,8 +744,8 @@ proxyCheckingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) thickness: 1px; size: size(8px, 8px); } -proxyDropdownDownPosition: point(-2px, 35px); -proxyDropdownUpPosition: point(-2px, 20px); +proxyDropdownDownPosition: point(2px, 35px); +proxyDropdownUpPosition: point(2px, 20px); proxyAboutPadding: margins(22px, 7px, 22px, 14px); proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px); @@ -663,6 +753,41 @@ proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px); proxyApplyBoxLabel : FlatLabel(defaultFlatLabel) { maxHeight: 30px; } +proxyApplyBoxTable: Table(defaultTable) { + labelMinWidth: 91px; +} +proxyApplyBoxTableMargin: margins(24px, 4px, 24px, 4px); +proxyApplyBoxTableLabelMargin: margins(13px, 10px, 13px, 10px); +proxyApplyBoxTableValueMargin: margins(13px, 9px, 13px, 9px); +proxyApplyBoxValueMultiline: FlatLabel(defaultTableValue) { + minWidth: 128px; + maxHeight: 100px; + style: TextStyle(defaultTextStyle) { + font: font(10px); + linkUnderline: kLinkUnderlineNever; + } +} +proxyApplyBoxSponsorLabel: FlatLabel(defaultFlatLabel) { + textFg: windowBoldFg; + minWidth: 100px; + align: align(top); + style: TextStyle(defaultTextStyle) { + font: font(11px); + } +} +proxyApplyBoxSponsorMargin: margins(13px, 8px, 13px, 8px); +proxyApplyBoxButton: RoundButton(defaultActiveButton) { + height: 38px; + textTop: 10px; + style: semiboldTextStyle; +} +proxyApplyBox: Box(defaultBox) { + buttonPadding: margins(22px, 11px, 22px, 22px); + buttonHeight: 38px; + buttonWide: true; + button: proxyApplyBoxButton; + shadowIgnoreTopSkip: true; +} markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px); @@ -683,84 +808,23 @@ themesMenuToggle: IconButton(defaultIconButton) { rippleAreaPosition: point(4px, 4px); rippleAreaSize: 36px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } + ripple: defaultRippleAnimationBgOver; } -themesMenuPosition: point(-2px, 25px); +themesMenuPosition: point(2px, 25px); -createPollField: InputField(defaultInputField) { - textMargins: margins(0px, 4px, 0px, 4px); - textAlign: align(left); - heightMin: 36px; - heightMax: 86px; - placeholderFg: placeholderFg; - placeholderFgActive: placeholderFgActive; - placeholderFgError: placeholderFgActive; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderScale: 0.; - placeholderFont: boxTextFont; - placeholderShift: -50px; - border: 0px; - borderActive: 0px; - duration: 100; -} -createPollFieldPadding: margins(22px, 5px, 22px, 5px); -createPollOptionField: InputField(createPollField) { - textMargins: margins(22px, 11px, 40px, 11px); - placeholderMargins: margins(2px, 0px, 2px, 0px); - heightMax: 68px; -} -createPollOptionFieldPremium: InputField(createPollOptionField) { - textMargins: margins(22px, 11px, 68px, 11px); -} -createPollOptionFieldPremiumEmojiPosition: point(15px, -1px); -createPollSolutionField: InputField(createPollField) { - textMargins: margins(0px, 4px, 0px, 4px); - border: 1px; - borderActive: 2px; -} -createPollLimitPadding: margins(22px, 10px, 22px, 16px); -createPollOptionRemove: CrossButton { - width: 22px; - height: 22px; - - cross: CrossAnimation { - size: 22px; - skip: 6px; - stroke: 1.5; - minScale: 0.3; - } - crossFg: boxTitleCloseFg; - crossFgOver: boxTitleCloseFgOver; - crossPosition: point(0px, 0px); +sendGifWithCaptionEmojiPosition: point(-30px, 23px); - duration: 150; - loadingPeriod: 1000; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } +notesFieldWithEmoji: InputField(defaultInputField) { + textMargins: margins(0px, 28px, 30px, 4px); +// border: 0px; +// borderActive: 0px; } -createPollOptionRemovePosition: point(11px, 9px); -createPollOptionEmojiPositionSkip: 4px; -createPollWarning: FlatLabel(defaultFlatLabel) { - textFg: windowSubTextFg; - palette: TextPalette(defaultTextPalette) { - linkFg: boxTextFgError; - } -} -createPollWarningPosition: point(16px, 6px); -createPollCheckboxMargin: margins(22px, 10px, 22px, 10px); -createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px); - -sendGifWithCaptionEmojiPosition: point(-30px, 23px); backgroundCheckbox: Checkbox(defaultCheckbox) { textFg: msgServiceFg; textFgActive: msgServiceFg; - width: -50px; + width: -10px; margin: margins(0px, 0px, 0px, 0px); textPosition: point(0px, 6px); @@ -803,12 +867,42 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) { color: shadowFg; } } +urlAuthBox: Box(defaultBox) { + buttonPadding: margins(0px, 0px, 0px, 12px); + buttonHeight: 0px; +} urlAuthCheckbox: Checkbox(defaultBoxCheckbox) { width: 240px; } +urlAuthCodesTitle: FlatLabel(defaultPeerListAbout) { + textFg: boxTitleFg; + style: TextStyle(defaultTextStyle) { + font: boxTitleFont; + } +} +urlAuthCheckboxAbout: FlatLabel(defaultPeerListAbout) { + style: TextStyle(boxTextStyle) { + font: font(12px); + } +} +urlAuthCodesButton: RoundButton(defaultLightButton) { + height: 58px; + textTop: 19px; + style: TextStyle(semiboldTextStyle) { + font: font(20px semibold); + } +} +urlAuthBoxRowTopLabel: FlatLabel(boxLabel) { + maxHeight: 30px; +} +urlAuthBoxRowBottomLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + maxHeight: 30px; +} addContactFieldMargin: margins(19px, 0px, 19px, 10px); addContactWarningMargin: margins(19px, 10px, 19px, 5px); +editContactSuggestBirthday: icon{{ "settings/birthday_add-22x22", lightButtonFg }}; blockUserConfirmation: FlatLabel(boxLabel) { minWidth: 240px; } @@ -824,7 +918,7 @@ boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) { } customBadgeField: InputField(defaultInputField) { - textMargins: margins(2px, 7px, 2px, 0px); + textMargins: margins(2px, 0px, 2px, 0px); placeholderFg: placeholderFg; placeholderFgActive: placeholderFgActive; @@ -833,33 +927,15 @@ customBadgeField: InputField(defaultInputField) { placeholderScale: 0.; placeholderFont: normalFont; - heightMin: 32px; -} + border: 0px; + borderActive: 0px; -pollResultsQuestion: FlatLabel(defaultFlatLabel) { - minWidth: 320px; - textFg: windowBoldFg; - style: TextStyle(defaultTextStyle) { - font: font(16px semibold); - } -} -pollResultsVotesCount: FlatLabel(defaultFlatLabel) { - textFg: windowSubTextFg; + heightMin: 32px; } -pollResultsHeaderPadding: margins(22px, 22px, 22px, 8px); -pollResultsShowMore: SettingsButton(defaultSettingsButton) { - textFg: lightButtonFg; - textFgOver: lightButtonFgOver; - textBg: windowBg; - textBgOver: windowBgOver; - - style: semiboldTextStyle; - height: 20px; - padding: margins(71px, 10px, 8px, 8px); - - ripple: defaultRippleAnimation; -} +tagPreviewLineHeight: 8px; +tagPreviewLineSpacing: 4px; +tagPreviewInputSkip: 16px; inviteViaLinkButton: SettingsButton(defaultSettingsButton) { textFg: lightButtonFg; @@ -888,6 +964,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) { peerListSingleRow: PeerList(peerListBox) { padding: margins(0px, 0px, 0px, 0px); } +peerListSmallSkips: PeerList(peerListBox) { + padding: margins( + 0px, + defaultVerticalListSkip, + 0px, + defaultVerticalListSkip); +} scheduleHeight: 95px; scheduleDateTop: 38px; @@ -908,6 +991,8 @@ scheduleDateWidth: 136px; scheduleTimeWidth: 72px; scheduleAtSkip: 24px; scheduleAtTop: 42px; +scheduleNotifyIconSize: 24px; +scheduleNotifyTooltipShift: 5px; scheduleAtLabel: FlatLabel(defaultFlatLabel) { } scheduleTimeSeparator: FlatLabel(defaultFlatLabel) { @@ -916,6 +1001,24 @@ scheduleTimeSeparator: FlatLabel(defaultFlatLabel) { } } scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px); +scheduleRepeatDropdownLock: IconEmoji { + icon: icon {{ "emoji/premium_lock", windowSubTextFg }}; + padding: margins(1px, 1px, 0px, 0px); +} +scheduleRepeatDropdownArrow: IconEmoji { + icon: icon {{ "arrows_select", windowSubTextFg }}; + padding: margins(6px, 5px, 0px, 0px); +} +scheduleRepeatHeight: 28px; +scheduleRepeatRadius: 14px; +scheduleRepeatTextPadding: 12px; +scheduleRepeatMargin: margins(0px, -4px, 0px, 10px); +scheduleRepeatLabel: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(13px); + } + textFg: windowBoldFg; +} muteBoxTimeField: InputField(scheduleDateField) { textMargins: margins(0px, 0px, 0px, 0px); @@ -951,9 +1054,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) { textTop: 7px; style: defaultTextStyle; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } + ripple: defaultRippleAnimationBgOver; } requestPeerRestriction: FlatLabel(defaultFlatLabel) { @@ -987,9 +1088,8 @@ contactsWithStories: PeerList(peerListBox) { statusPosition: point(70px, 27px); checkbox: RoundImageCheckbox(defaultPeerListCheckbox) { - selectExtendTwice: 1px; imageRadius: 21px; - imageSmallRadius: 19px; + imageSmallRadius: 18px; check: RoundCheckbox(defaultPeerListCheck) { size: 0px; } @@ -997,8 +1097,6 @@ contactsWithStories: PeerList(peerListBox) { nameFgChecked: contactsNameFg; } } -storiesReadLineTwice: 2px; -storiesUnreadLineTwice: 4px; requestsAcceptButton: RoundButton(defaultActiveButton) { width: -28px; height: 30px; @@ -1083,7 +1181,6 @@ collectibleHeaderPadding: margins(24px, 16px, 24px, 12px); collectibleOwnerPadding: margins(24px, 4px, 24px, 8px); collectibleInfo: inviteForbiddenInfo; collectibleInfoPadding: margins(24px, 12px, 24px, 12px); -collectibleInfoTonMargins: margins(0px, 3px, 0px, 0px); collectibleMore: RoundButton(defaultActiveButton) { height: 36px; textTop: 9px; @@ -1106,15 +1203,30 @@ moderateBoxUserpic: UserpicButton(defaultUserpicButton) { photoSize: 34px; photoPosition: point(0px, 4px); } -moderateBoxExpand: icon {{ "chat/reply_type_group", boxTextFg }}; +moderateBoxExpand: IconEmoji { + icon: icon {{ "chat/reply_type_group", boxTextFg }}; + padding: margins(1px, 3px, 1px, 0px); + useIconColor: true; +} moderateBoxExpandHeight: 20px; moderateBoxExpandRight: 10px; moderateBoxExpandInnerSkip: 2px; moderateBoxExpandFont: font(11px); +moderateBoxExpandTextStyle: TextStyle(boxTextStyle) { + font: moderateBoxExpandFont; +} moderateBoxExpandToggleSize: 4px; moderateBoxExpandToggleFourStrokes: 3px; -moderateBoxExpandIcon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }}; -moderateBoxExpandIconDown: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }}; +moderateBoxExpandIcon: IconEmoji{ + icon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }}; + padding: margins(-2px, -1px, 0px, 0px); + useIconColor: true; +} +moderateBoxExpandIconDown: IconEmoji{ + icon: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }}; + padding: margins(-2px, -1px, 0px, 0px); + useIconColor: true; +} moderateBoxDividerLabel: FlatLabel(boxDividerLabel) { palette: TextPalette(defaultTextPalette) { selectLinkFg: windowActiveTextFg; @@ -1122,8 +1234,327 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) { } profileQrFont: font(fsize bold); -profileQrCenterSize: 34px; profileQrBackgroundRadius: 12px; profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }}; profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px); profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px); + +foldersMenu: PopupMenu(popupMenuWithIcons) { + maxHeight: 320px; + menu: Menu(menuWithIcons) { + itemPadding: margins(54px, 8px, 44px, 8px); + } +} + +moderateCommonGroupsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) { + imageRadius: 12px; + imageSmallRadius: 11px; + selectWidth: 2px; + check: RoundCheckbox(defaultPeerListCheck) { + size: 16px; + sizeSmall: 0.3; + bgInactive: overviewCheckBg; + bgActive: overviewCheckBgActive; + check: icon {{ + "default_checkbox_check", + overviewCheckFgActive, + point(1px, 4px) + }}; + } +} + +futureOwnerBox: Box(defaultBox) { + buttonPadding: margins(0px, 0px, 0px, 14px); + buttonHeight: 0px; +} +futureOwnerBoxSelect: collectibleBox; +guardBotReplaceBox: Box(futureOwnerBox) { +} + +localAutomationBox: Box(defaultBox) { + buttonPadding: margins(0px, 0px, 0px, 12px); + buttonHeight: 0px; +} + +disableSharingIconPadding: margins(12px, 12px, 12px, 8px); +disableSharingButtonLock: IconEmoji { + icon: icon {{ "emoji/premium_lock", activeButtonFg }}; + padding: margins(-2px, 1px, 0px, 0px); +} + +createBotBox: Box(defaultBox) { + shadowIgnoreTopSkip: true; +} +createBotUserpicPadding: margins(0px, 16px, 0px, 10px); +createBotTitlePadding: margins(0px, 0px, 0px, 8px); +createBotSubtitlePadding: margins(0px, 0px, 0px, 4px); +createBotCenteredText: FlatLabel(defaultFlatLabel) { + align: align(top); + minWidth: 40px; +} +createBotFieldSpacing: 8px; +createBotUsernameField: InputField(defaultInputField) { + textMargins: margins(0px, 28px, 0px, 4px); +} +createBotUsernamePrefix: FlatLabel(defaultFlatLabel) { + style: boxTextStyle; +} +createBotUsernameSuffix: FlatLabel(createBotUsernamePrefix) { + textFg: windowSubTextFg; +} + +managedBotIconEmoji: IconEmoji { + icon: icon {{ "chat/code_tags", windowSubTextFg }}; + padding: margins(-2px, -2px, -2px, 0px); +} + +createBotStatusLabel: FlatLabel(aboutRevokePublicLabel) { + maxHeight: 20px; +} + +aiComposeBoxStyleTabsSkip: 8px; +aiComposeContentMargin: margins(16px, 0px, 16px, 0px); + +aiComposeButtonBgActive: windowActiveTextFg; +aiComposeButtonBgActiveOpacity: 0.1; +aiComposeButtonFg: windowBoldFg; +aiComposeButtonFgActive: windowActiveTextFg; +aiComposeButtonRippleInactive: RippleAnimation(defaultRippleAnimation) { + color: windowBoldFg; +} +aiComposeButtonRippleInactiveOpacity: 0.1; +aiComposeButtonRippleActive: RippleAnimation(defaultRippleAnimation) { + color: windowActiveTextFg; +} +aiComposeButtonRippleActiveOpacity: 0.1; + +aiComposeTabsBg: boxBg; +aiComposeTabsHeight: 58px; +aiComposeTabsRadius: 29px; +aiComposeTabsPadding: margins(6px, 6px, 6px, 6px); +aiComposeTabsSkip: 4px; +aiComposeTabButtonBgActive: aiComposeButtonBgActive; +aiComposeTabLabelFg: aiComposeButtonFg; +aiComposeTabLabelFgActive: aiComposeButtonFgActive; +aiComposeTabLabelFont: font(12px semibold); +aiComposeTabIconTop: 6px; +aiComposeTabLabelTop: 26px; +aiComposeTabTranslateIcon: icon {{ "menu/translate", aiComposeButtonFg }}; +aiComposeTabTranslateIconActive: icon {{ "menu/translate", aiComposeButtonFgActive }}; +aiComposeTabStyleIcon: icon {{ "menu/edit_stars", aiComposeButtonFg }}; +aiComposeTabStyleIconActive: icon {{ "menu/edit_stars", aiComposeButtonFgActive }}; +aiComposeTabFixIcon: icon {{ "menu/search_check", aiComposeButtonFg }}; +aiComposeTabFixIconActive: icon {{ "menu/search_check", aiComposeButtonFgActive }}; + +aiComposeStyleTabsBg: boxBg; +aiComposeStyleTabsHeight: 64px; +aiComposeStyleTabsRadius: 14px; +aiComposeStyleTabsPadding: margins(6px, 6px, 6px, 6px); +aiComposeStyleTabsSkip: 4px; +aiComposeStyleButtonBgActive: aiComposeButtonBgActive; +aiComposeStyleLabelFg: aiComposeButtonFg; +aiComposeStyleLabelFgActive: aiComposeButtonFgActive; +aiComposeStyleEmojiTop: 5px; +aiComposeStyleLabelTop: 30px; +aiComposeStyleLabelFont: font(12px semibold); +aiComposeStyleTabsScroll: ScrollArea(defaultScrollArea) { + barHidden: true; +} +aiComposeStyleButtonPadding: margins(12px, 0px, 12px, 0px); +aiComposeStyleFadeWidth: 24px; +aiComposeBadge: RoundButton(customEmojiTextBadge) { + textFg: activeButtonBg; + textBg: activeButtonFg; + width: -8px; + height: 16px; + radius: 4px; + textTop: 1px; + style: TextStyle(semiboldTextStyle) { + font: font(10px semibold); + } +} +aiComposeBadgeMargin: margins(0px, 2px, 0px, 0px); + +aiComposeAddStyleIcon: icon {{ "menu/edit_stars_add", aiComposeButtonFg }}; +aiComposeAddStyleIconOver: icon {{ "menu/edit_stars_add", aiComposeButtonFgActive }}; + +aiToneIconPreviewSize: 80px; +aiToneIconPreviewBottomSkip: 12px; +aiToneIconPreviewTopSkip: 4px; +aiToneIconPreviewBg: boxBg; +aiToneIconPreviewInnerSize: 54px; + +aiToneIconPreviewPlaceholder: icon {{ "chat/ai_style_tone", windowSubTextFg }}; + +aiToneFieldBg: boxBg; +aiToneFieldRadius: 12px; +aiToneFieldPadding: margins(16px, 12px, 16px, 12px); +aiToneFieldsMargin: margins(16px, 0px, 16px, 0px); +aiToneFieldsSkip: 8px; + +aiToneNameField: InputField(defaultInputField) { + textBg: transparent; + textBgActive: transparent; + textMargins: margins(16px, 12px, 16px, 12px); + border: 0px; + borderActive: 0px; + heightMin: 44px; +} +aiTonePromptField: InputField(newGroupDescription) { + textBg: transparent; + textBgActive: transparent; + textMargins: margins(16px, 12px, 16px, 12px); + border: 0px; + borderActive: 0px; + heightMin: 140px; + heightMax: 240px; +} +aiTonePlaceholderLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 80px; + style: TextStyle(defaultTextStyle) { + font: font(14px); + } +} +aiToneAuthorCheckboxMargin: margins(0px, 12px, 0px, 8px); +aiToneDeleteButton: RoundButton(defaultActiveButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFg; + textBg: boxBg; + textBgOver: boxBg; + height: 42px; + textTop: 12px; + style: semiboldTextStyle; + ripple: defaultRippleAnimation; +} +aiToneDeleteButtonMargin: margins(16px, 8px, 16px, 0px); +aiComposeToneToastIconSize: size(32px, 32px); +aiComposeToneToastIconPadding: margins(12px, 6px, 12px, 6px); + +aiTonePreviewTitleLabel: FlatLabel(defaultFlatLabel) { + textFg: windowFg; + minWidth: 0px; + maxHeight: 28px; + align: align(top); + style: TextStyle(semiboldTextStyle) { + font: font(17px semibold); + } +} +aiTonePreviewTitleMargin: margins(16px, 6px, 16px, 0px); + +aiTonePreviewAboutLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 20px; + align: align(top); + style: TextStyle(defaultTextStyle) { + font: font(14px); + } +} +aiTonePreviewAboutMargin: margins(28px, 6px, 28px, 6px); + +aiTonePreviewAttributionLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 0px; + align: align(top); + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkUnderline: kLinkUnderlineActive; + } +} +aiTonePreviewAttributionMargin: margins(16px, 10px, 16px, 8px); + +aiTonePreviewExampleCardBg: boxBg; +aiTonePreviewExampleCardRadius: 22px; +aiTonePreviewExampleCardPadding: margins(14px, 12px, 16px, 12px); +aiTonePreviewExampleCardSectionSkip: 24px; +aiTonePreviewExampleCardTitleSkip: 6px; +aiTonePreviewExampleCardMargin: margins(16px, 6px, 16px, 0px); +aiTonePreviewBottomSkip: 6px; + +aiTonePreviewAnotherExampleButton: RoundButton(defaultLightButton) { + width: -12px; + height: 26px; + radius: 8px; + textTop: 4px; + style: TextStyle(defaultTextStyle) { + font: font(12px semibold); + } +} +aiTonePreviewAnotherExampleIcon: IconEmoji { + icon: icon {{ "chat/refresh-18x18", lightButtonFg }}; + padding: margins(0px, 1px, 4px, 0px); +} +aiComposeToneRemovedToastIcon: icon {{ "menu/delete", toastFg }}; + +aiComposeCardBg: boxBg; +aiComposeCardRadius: 22px; +aiComposeCardPadding: margins(12px, 16px, 16px, 16px); +aiComposeCardSectionSkip: 12px; +aiComposeCardControlSkip: 8px; +aiComposeCardTitle: FlatLabel(defaultSubsectionTitle) { + textFg: windowFg; + minWidth: 0px; + maxHeight: 22px; +} +aiComposeEmojifyCheckbox: Checkbox(defaultBoxCheckbox) { + textFg: windowSubTextFg; + textFgActive: windowSubTextFg; + width: 0px; +} + +aiComposeExpandIcon: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }}; +aiComposeCollapseIcon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }}; +aiComposeExpandButton: IconButton(defaultIconButton) { + width: 32px; + height: 32px; + icon: aiComposeExpandIcon; + iconOver: aiComposeExpandIcon; + iconPosition: point(5px, 5px); + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 32px; + ripple: defaultRippleAnimationBgOver; +} + +aiComposeCopyIcon: icon{{ "menu/copy", windowActiveTextFg }}; +aiComposeCopyButton: IconButton(aiComposeExpandButton) { + icon: aiComposeCopyIcon; + iconOver: aiComposeCopyIcon; +} + +aiComposeAuthorLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + minWidth: 0px; + maxHeight: 20px; + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkUnderline: kLinkUnderlineActive; + } +} +aiComposeAuthorLabelTop: 8px; + +aiComposeBoxButton: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + style: semiboldTextStyle; +} +aiComposeBox: Box(defaultBox) { + buttonPadding: margins(16px, 12px, 16px, 12px); + buttonHeight: 42px; + buttonWide: true; + button: aiComposeBoxButton; + shadowIgnoreTopSkip: true; + bg: boxDividerBg; +} +aiComposeBoxWithSend: Box(aiComposeBox) { + buttonPadding: margins(16px, 12px, 66px, 12px); +} +aiComposeSendButtonSkip: 8px; +aiComposeBoxClose: IconButton(boxTitleClose) { + ripple: defaultRippleAnimation; +} +aiComposeBoxInfoButton: IconButton(boxTitleClose) { + icon: icon {{ "menu/info", boxTitleCloseFg }}; + iconOver: icon {{ "menu/info", boxTitleCloseFgOver }}; + ripple: defaultRippleAnimation; +} +aiComposeShadowOpacity: 0.3; diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 4e99a01ba855d5..d705ca392109f9 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -8,32 +8,123 @@ For license and copyright information please follow this link: #include "boxes/choose_filter_box.h" #include "apiwrap.h" +#include "boxes/filters/edit_filter_box.h" #include "boxes/premium_limits_box.h" #include "core/application.h" // primaryWindow +#include "core/ui_integration.h" #include "data/data_chat_filters.h" +#include "data/data_premium_limits.h" #include "data/data_session.h" +#include "data/data_channel.h" +#include "data/data_user.h" #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "ui/text/text_utilities.h" // Ui::Text::Bold +#include "ui/empty_userpic.h" +#include "ui/filter_icons.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" // tr::bold +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/menu/menu_action.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" +#include "main/main_session_settings.h" +#include "styles/style_dialogs.h" #include "styles/style_media_player.h" // mediaPlayerMenuCheck +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" namespace { +[[nodiscard]] QImage Icon(const Data::ChatFilter &f) { + constexpr auto kScale = 0.75; + const auto icon = Ui::LookupFilterIcon(Ui::ComputeFilterIcon(f)).normal; + const auto originalWidth = icon->width(); + const auto originalHeight = icon->height(); + + const auto scaledWidth = int(originalWidth * kScale); + const auto scaledHeight = int(originalHeight * kScale); + + auto image = QImage( + scaledWidth * style::DevicePixelRatio(), + scaledHeight * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(style::DevicePixelRatio()); + image.fill(Qt::transparent); + + { + auto p = QPainter(&image); + auto hq = PainterHighQualityEnabler(p); + + const auto x = int((scaledWidth - originalWidth * kScale) / 2); + const auto y = int((scaledHeight - originalHeight * kScale) / 2); + + p.scale(kScale, kScale); + icon->paint(p, x, y, scaledWidth, st::dialogsUnreadBgMuted->c); + if (const auto color = f.colorIndex()) { + p.resetTransform(); + const auto circleSize = scaledWidth / 3.; + const auto r = QRectF( + x + scaledWidth - circleSize, + y + scaledHeight - circleSize - circleSize / 3., + circleSize, + circleSize); + p.setPen(Qt::NoPen); + p.setCompositionMode(QPainter::CompositionMode_Clear); + p.setBrush(Qt::transparent); + p.drawEllipse(r + Margins(st::lineWidth * 1.5)); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setBrush(Ui::EmptyUserpic::UserpicColor(*color).color2); + p.drawEllipse(r); + } + } + + return image; +} + +class FilterAction : public Ui::Menu::Action { +public: + using Ui::Menu::Action::Action; + + void setIcon(QImage &&image) { + _icon = std::move(image); + } + +protected: + void paintEvent(QPaintEvent *event) override { + Ui::Menu::Action::paintEvent(event); + if (!_icon.isNull()) { + const auto size = _icon.size() / style::DevicePixelRatio(); + auto p = QPainter(this); + p.drawImage( + width() + - size.width() + - st::menuWithIcons.itemPadding.right(), + (height() - size.height()) / 2, + _icon); + } + } + +private: + QImage _icon; + +}; + Data::ChatFilter ChangedFilter( const Data::ChatFilter &filter, not_null history, bool add) { auto always = base::duplicate(filter.always()); + auto pinned = filter.pinned(); auto never = base::duplicate(filter.never()); if (add) { never.remove(history); } else { always.remove(history); + pinned.erase(ranges::remove(pinned, history), end(pinned)); } const auto result = Data::ChatFilter( filter.id(), @@ -42,7 +133,7 @@ Data::ChatFilter ChangedFilter( filter.colorIndex(), filter.flags(), std::move(always), - filter.pinned(), + pinned, std::move(never)); const auto in = result.contains(history); if (in == add) { @@ -62,7 +153,7 @@ Data::ChatFilter ChangedFilter( filter.colorIndex(), filter.flags(), std::move(always), - filter.pinned(), + std::move(pinned), std::move(never)); } @@ -85,17 +176,27 @@ void ChangeFilterById( )).done([=, chat = history->peer->name(), name = filter.title()] { const auto account = not_null(&history->session().account()); if (const auto controller = Core::App().windowFor(account)) { - controller->showToast((add - ? tr::lng_filters_toast_add - : tr::lng_filters_toast_remove)( - tr::now, - lt_chat, - Ui::Text::Bold(chat), - lt_folder, - Ui::Text::Bold(name), - Ui::Text::WithEntities)); + const auto isStatic = name.isStatic; + controller->showToast({ + .text = (add + ? tr::lng_filters_toast_add + : tr::lng_filters_toast_remove)( + tr::now, + lt_chat, + tr::bold(chat), + lt_folder, + Ui::Text::Wrapped(name.text, EntityType::Bold), + tr::marked), + .textContext = Core::TextContext({ + .session = &history->session(), + .customEmojiLoopLimit = isStatic ? -1 : 0, + }), + }); } }).fail([=](const MTP::Error &error) { + LOG(("API Error: failed to %1 a dialog to a folder. %2") + .arg(add ? u"add"_q : u"remove"_q) + .arg(error.type())); // Revert filter on fail. history->owner().chatsFilters().set(was); }).send(); @@ -117,15 +218,24 @@ bool ChooseFilterValidator::canAdd() const { return false; } +bool ChooseFilterValidator::canAdd(FilterId filterId) const { + Expects(filterId != 0); + + const auto list = _history->owner().chatsFilters().list(); + const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); + if (i != end(list)) { + return !i->contains(_history); + } + return false; +} + bool ChooseFilterValidator::canRemove(FilterId filterId) const { Expects(filterId != 0); const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i != end(list)) { - const auto &filter = *i; - return filter.contains(_history) - && ((filter.always().size() > 1) || filter.flags()); + return Data::CanRemoveFromChatFilter(*i, _history); } return false; } @@ -161,14 +271,15 @@ void FillChooseFilterMenu( not_null history) { const auto weak = base::make_weak(controller); const auto validator = ChooseFilterValidator(history); - for (const auto &filter : history->owner().chatsFilters().list()) { + const auto &list = history->owner().chatsFilters().list(); + const auto showColors = history->owner().chatsFilters().tagsEnabled(); + for (const auto &filter : list) { const auto id = filter.id(); if (!id) { continue; } - const auto contains = filter.contains(history); - const auto action = menu->addAction(filter.title(), [=] { + auto callback = [=] { const auto toAdd = !filter.contains(history); const auto r = validator.limitReached(id, toAdd); if (r.reached) { @@ -185,13 +296,284 @@ void FillChooseFilterMenu( validator.remove(id); } } - }, contains ? &st::mediaPlayerMenuCheck : nullptr); + }; + + const auto contains = filter.contains(history); + const auto title = filter.title(); + auto item = base::make_unique_q( + menu->menu(), + menu->st().menu, + Ui::Menu::CreateAction( + menu.get(), + Ui::Text::FixAmpersandInAction(title.text.text), + std::move(callback)), + contains ? &st::mediaPlayerMenuCheck : nullptr, + contains ? &st::mediaPlayerMenuCheck : nullptr); + item->setMarkedText(title.text, QString(), Core::TextContext({ + .session = &history->session(), + .repaint = [raw = item.get()] { raw->update(); }, + .customEmojiLoopLimit = title.isStatic ? -1 : 0, + })); + + item->setIcon(Icon(showColors ? filter : filter.withColorIndex({}))); + const auto action = menu->addAction(std::move(item)); action->setEnabled(contains ? validator.canRemove(id) : validator.canAdd()); } + + const auto limit = [session = &controller->session()] { + return Data::PremiumLimits(session).dialogFiltersCurrent(); + }; + if ((list.size() - 1) < limit()) { + menu->addAction(tr::lng_filters_create(tr::now), [=] { + const auto strong = weak.get(); + if (!strong) { + return; + } + const auto session = &strong->session(); + const auto &list = session->data().chatsFilters().list(); + if ((list.size() - 1) >= limit()) { + return; + } + const auto chooseNextId = [&] { + auto id = 2; + while (ranges::contains(list, id, &Data::ChatFilter::id)) { + ++id; + } + return id; + }; + auto filter = + Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {}); + const auto send = [=](const Data::ChatFilter &filter) { + session->api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(chooseNextId()), + filter.tl() + )).done([=] { + session->data().chatsFilters().reload(); + }).send(); + }; + strong->uiShow()->show( + Box(EditFilterBox, strong, std::move(filter), send, nullptr)); + }, &st::menuIconShowInFolder); + } + history->owner().chatsFilters().changed( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { + menu->hideMenu(); + }, menu->lifetime()); +} + +bool FillChooseFilterWithAdminedGroupsMenu( + not_null controller, + not_null menu, + not_null user, + std::shared_ptr> listUpdates, + std::vector> common, + std::shared_ptr> collectCommon) { + const auto weak = base::make_weak(controller); + const auto session = &controller->session(); + const auto &list = session->data().chatsFilters().list(); + const auto showColors = session->data().chatsFilters().tagsEnabled(); + auto added = 0; + for (const auto &filter : list) { + const auto id = filter.id(); + if (!id) { + continue; + } + auto canRestrictList = std::vector>(); + const auto maybeAppend = [&](not_null chat) { + const auto channel = chat->peer->asChannel(); + if (channel && channel->canRestrictParticipant(user)) { + if (channel->isGroupAdmin(user) && !channel->amCreator()) { + return; + } + canRestrictList.push_back(chat->peer); + } + }; + for (const auto &chat : filter.always()) { + maybeAppend(chat); + } + for (const auto &chat : filter.pinned()) { + maybeAppend(chat); + } + if (canRestrictList.empty()) { + continue; + } + + const auto checked = std::make_shared(false); + + const auto contains = false; + const auto title = filter.title(); + auto item = base::make_unique_q( + menu->menu(), + menu->st().menu, + new QAction( + Ui::Text::FixAmpersandInAction(title.text.text), + menu.get()), + contains ? &st::mediaPlayerMenuCheck : nullptr, + contains ? &st::mediaPlayerMenuCheck : nullptr); + const auto triggered = [=, raw = item.get()] { + *checked = !*checked; + if (*checked) { + for (const auto &peer : canRestrictList) { + if (ranges::contains(common, peer)) { + collectCommon->push_back(peer->id); + } + } + } else { + for (const auto &peer : canRestrictList) { + if (const auto i = ranges::find(*collectCommon, peer->id); + i != collectCommon->end()) { + collectCommon->erase(i); + } + } + } + raw->Ui::Menu::Action::setIcon( + *checked ? &st::mediaPlayerMenuCheck : nullptr, + *checked ? &st::mediaPlayerMenuCheck : nullptr); + listUpdates->fire({}); + }; + item->setActionTriggered([=] { + triggered(); + + auto groups = session->settings().moderateCommonGroups(); + if (*checked && !ranges::contains(groups, id)) { + groups.push_back(id); + } else if (!*checked) { + groups.erase(ranges::remove(groups, id), groups.end()); + } + session->settings().setModerateCommonGroups(groups); + session->saveSettingsDelayed(); + }); + if (ranges::contains( + session->settings().moderateCommonGroups(), + id)) { + triggered(); + } + item->setPreventClose(true); + item->setMarkedText(title.text, QString(), Core::TextContext({ + .session = session, + .repaint = [raw = item.get()] { raw->update(); }, + .customEmojiLoopLimit = title.isStatic ? -1 : 0, + })); + + item->setIcon(Icon(showColors ? filter : filter.withColorIndex({}))); + menu->addAction(std::move(item)); + added++; + } + + session->data().chatsFilters().changed( + ) | rpl::on_next([=] { menu->hideMenu(); }, menu->lifetime()); + + return added; +} + +History *HistoryFromMimeData( + const QMimeData *mime, + not_null session) { + const auto mimeFormat = u"application/x-telegram-dialog"_q; + if (mime->hasFormat(mimeFormat)) { + auto peerId = int64(-1); + auto isTestMode = false; + auto stream = QDataStream(mime->data(mimeFormat)); + stream >> peerId; + stream >> isTestMode; + if (isTestMode != session->isTestMode()) { + return nullptr; + } + return session->data().historyLoaded(PeerId(peerId)); + } + if (mime->hasText()) { + auto text = mime->text().trimmed(); + if (text.startsWith('@')) { + text = text.mid(1); + } else if (text.startsWith(u"https://t.me/"_q)) { + text = text.mid(13); + } else { + return nullptr; + } + if (const auto peer = session->data().peerByUsername(text)) { + return session->data().historyLoaded(peer->id); + } + } + return nullptr; +} + +void SetupFilterDragAndDrop( + not_null outer, + not_null session, + Fn(QPoint)> filterIdAtPosition, + Fn activeFilterId, + Fn selectByFilterId) { + const auto hasAction = [=](not_null drop, bool perform) { + const auto mimeData = drop->mimeData(); + const auto filterId = filterIdAtPosition( + outer->mapToGlobal(drop->pos())); + if (!filterId) { + return false; + } + const auto id = *filterId; + if (const auto h = HistoryFromMimeData(mimeData, session)) { + auto v = ChooseFilterValidator(h); + if (id) { + if (v.canAdd(id)) { + if (!v.limitReached(id, true).reached) { + if (perform) { + v.add(id); + } + selectByFilterId(perform ? FilterId(-1) : id); + return true; + } + } + } else { + if (const auto active = activeFilterId(); + active && v.canRemove(active)) { + if (perform) { + v.remove(active); + } + selectByFilterId(perform ? FilterId(-1) : active); + return true; + } + } + } + selectByFilterId(-1); + return false; + }; + outer->setAcceptDrops(true); + outer->events( + ) | rpl::filter([](not_null e) { + return e->type() == QEvent::DragEnter + || e->type() == QEvent::DragMove + || e->type() == QEvent::DragLeave + || e->type() == QEvent::Drop; + }) | rpl::on_next([=](not_null e) { + if (e->type() == QEvent::DragEnter) { + const auto de = static_cast(e.get()); + if (hasAction(de, false)) { + de->acceptProposedAction(); + } else { + de->ignore(); + } + } else if (e->type() == QEvent::DragMove) { + const auto dm = static_cast(e.get()); + if (hasAction(dm, false)) { + dm->acceptProposedAction(); + } else { + dm->ignore(); + } + } else if (e->type() == QEvent::DragLeave) { + selectByFilterId(-1); + } else if (e->type() == QEvent::Drop) { + const auto drop = static_cast(e.get()); + if (hasAction(drop, true)) { + drop->acceptProposedAction(); + } else { + drop->ignore(); + } + } + }, outer->lifetime()); } diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.h b/Telegram/SourceFiles/boxes/choose_filter_box.h index e6c5ad3353157f..59cff9633304b3 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.h +++ b/Telegram/SourceFiles/boxes/choose_filter_box.h @@ -7,7 +7,12 @@ For license and copyright information please follow this link: */ #pragma once +namespace Main { +class Session; +} // namespace Main + namespace Ui { +class RpWidget; class PopupMenu; } // namespace Ui @@ -26,6 +31,7 @@ class ChooseFilterValidator final { }; [[nodiscard]] bool canAdd() const; + [[nodiscard]] bool canAdd(FilterId filterId) const; [[nodiscard]] bool canRemove(FilterId filterId) const; [[nodiscard]] LimitData limitReached( FilterId filterId, @@ -43,3 +49,22 @@ void FillChooseFilterMenu( not_null controller, not_null menu, not_null history); + +bool FillChooseFilterWithAdminedGroupsMenu( + not_null controller, + not_null menu, + not_null user, + std::shared_ptr> listUpdates, + std::vector> common, + std::shared_ptr> collectCommon); + +void SetupFilterDragAndDrop( + not_null outer, + not_null session, + Fn(QPoint)> filterIdAtPosition, + Fn activeFilterId, + Fn selectByFilterId); + +[[nodiscard]] History *HistoryFromMimeData( + const QMimeData *mime, + not_null session); diff --git a/Telegram/SourceFiles/boxes/compose_ai_box.cpp b/Telegram/SourceFiles/boxes/compose_ai_box.cpp new file mode 100644 index 00000000000000..862e3796bf9983 --- /dev/null +++ b/Telegram/SourceFiles/boxes/compose_ai_box.cpp @@ -0,0 +1,1963 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/compose_ai_box.h" + +#include "api/api_compose_with_ai.h" +#include "apiwrap.h" +#include "boxes/create_ai_tone_box.h" +#include "core/shortcuts.h" +#include "menu/menu_check_item.h" +#include "settings/sections/settings_shortcuts.h" +#include "ui/widgets/checkbox.h" +#include "window/window_session_controller.h" +#include "boxes/premium_preview_box.h" +#include "boxes/share_box.h" +#include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/stickers_lottie.h" +#include "core/application.h" +#include "core/click_handler_types.h" +#include "core/core_settings.h" +#include "core/ui_integration.h" +#include "data/data_ai_compose_tones.h" +#include "data/data_document.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" +#include "lang/lang_keys.h" +#include "main/session/session_show.h" +#include "main/main_session.h" +#include "settings/sections/settings_premium.h" +#include "spellcheck/platform/platform_language.h" +#include "ui/boxes/about_cocoon_box.h" +#include "ui/boxes/choose_language_box.h" +#include "ui/chat/chat_style.h" +#include "ui/controls/labeled_emoji_tabs.h" +#include "ui/controls/send_button.h" +#include "ui/effects/ripple_animation.h" +#include "ui/effects/skeleton_animation.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/ui_utility.h" +#include "ui/text/custom_emoji_helper.h" +#include "ui/text/custom_emoji_text_badge.h" +#include "ui/text/text_extended_data.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/menu/menu_action.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/tooltip.h" +#include "styles/style_basic.h" +#include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_widgets.h" + +#include +#include + +namespace HistoryView::Controls { +namespace { + +constexpr auto kAiComposeStyleTooltipHiddenPref = "ai_compose_style_tooltip_hidden"_cs; + +enum class ComposeAiMode { + Translate, + Style, + Fix, +}; + +enum class CardState { + Waiting, + Loading, + Ready, + Failed, +}; + +[[nodiscard]] QColor ComposeAiColorWithAlpha( + const style::color &color, + float64 alpha) { + auto result = color->c; + result.setAlphaF(result.alphaF() * alpha); + return result; +} + +[[nodiscard]] TextWithEntities HighlightDiff(TextWithEntities text) { + return Ui::Text::Colorized( + Ui::Text::Wrapped(std::move(text), EntityType::Underline), 1); +} + +[[nodiscard]] TextWithEntities StrikeOutDiff(TextWithEntities text) { + return Ui::Text::Colorized( + Ui::Text::Wrapped(std::move(text), EntityType::StrikeOut), 2); +} + +[[nodiscard]] TextWithEntities BuildDiffDisplay( + const Api::ComposeWithAi::Diff &diff) { + auto result = TextWithEntities(); + auto entities = diff.entities; + std::stable_sort( + entities.begin(), + entities.end(), + [](const auto &a, const auto &b) { + return a.offset < b.offset; + }); + const auto size = int(diff.text.text.size()); + auto taken = 0; + for (const auto &entity : entities) { + const auto offset = std::clamp(entity.offset, 0, size); + const auto length = std::clamp(entity.length, 0, size - offset); + if (offset > taken) { + result.append(Ui::Text::Mid(diff.text, taken, offset - taken)); + } + auto part = Ui::Text::Mid(diff.text, offset, length); + switch (entity.type) { + case Api::ComposeWithAi::DiffEntity::Type::Insert: + result.append(HighlightDiff(std::move(part))); + break; + case Api::ComposeWithAi::DiffEntity::Type::Replace: + if (!entity.oldText.isEmpty()) { + result.append( + StrikeOutDiff( + TextWithEntities::Simple(entity.oldText))); + } + result.append(HighlightDiff(std::move(part))); + break; + case Api::ComposeWithAi::DiffEntity::Type::Delete: + result.append(StrikeOutDiff(std::move(part))); + break; + } + taken = std::max(taken, offset + length); + } + if (taken < size) { + result.append(Ui::Text::Mid(diff.text, taken)); + } + return result; +} + +[[nodiscard]] QString FromTitle(LanguageId id) { + return tr::lng_ai_compose_original(tr::now); +} + +[[nodiscard]] TextWithEntities ToTitle( + LanguageId id, + const QString &style) { + const auto name = style.isEmpty() + ? tr::link(Ui::LanguageName(id)) + : tr::link(tr::lng_ai_compose_name_style( + tr::now, + lt_name, + tr::marked(Ui::LanguageName(id)), + lt_style, + tr::marked(style), + tr::marked)); + return tr::lng_ai_compose_to_language( + tr::now, + lt_language, + name, + tr::marked); +} + +[[nodiscard]] LanguageId DefaultAiTranslateTo(LanguageId offeredFrom) { + const auto current = LanguageId{ + QLocale(Lang::LanguageIdOrDefault(Lang::Id())).language() + }; + if (current && (current != offeredFrom)) { + return current; + } + const auto english = LanguageId{ QLocale::English }; + if (english != offeredFrom) { + return english; + } + return LanguageId{ QLocale::Spanish }; +} + +[[nodiscard]] const style::icon &ModeIcon( + ComposeAiMode mode, + bool active) { + switch (mode) { + case ComposeAiMode::Translate: + return active + ? st::aiComposeTabTranslateIconActive + : st::aiComposeTabTranslateIcon; + case ComposeAiMode::Style: + return active + ? st::aiComposeTabStyleIconActive + : st::aiComposeTabStyleIcon; + case ComposeAiMode::Fix: + return active + ? st::aiComposeTabFixIconActive + : st::aiComposeTabFixIcon; + } + return active + ? st::aiComposeTabTranslateIconActive + : st::aiComposeTabTranslateIcon; +} + +[[nodiscard]] qreal ComposeAiPillRadius(int height) { + return height / 2.; +} + +[[nodiscard]] QColor ComposeAiActiveBackgroundColor( + const style::color &color) { + return ComposeAiColorWithAlpha( + color, + st::aiComposeButtonBgActiveOpacity); +} + +[[nodiscard]] QColor ComposeAiRippleColor( + const style::RippleAnimation &ripple, + float64 opacity) { + return ComposeAiColorWithAlpha( + ripple.color, + opacity); +} + +[[nodiscard]] Ui::LabeledEmojiTab ResolveStyleDescriptor( + const Data::AiComposeTone &tone) { + return { + .id = tone.isDefault ? tone.defaultType : QString::number(tone.id), + .label = tone.title, + .customEmojiData = tone.emojiId + ? Data::SerializeCustomEmojiId(tone.emojiId) + : QString(), + }; +} + +[[nodiscard]] std::vector ResolveStyleDescriptors( + const std::vector &tones) { + auto result = std::vector(); + result.reserve(tones.size()); + for (const auto &tone : tones) { + result.push_back(ResolveStyleDescriptor(tone)); + } + return result; +} + +[[nodiscard]] std::vector ResolveTranslateStyleDescriptors( + not_null session, + const std::vector &styles) { + const auto neutral = ChatHelpers::GenerateLocalTgsSticker( + session, + u"chat/white_flag_emoji"_q); + auto result = std::vector(); + result.reserve(styles.size() + 1); + result.push_back({ + .id = QString(), + .label = tr::lng_ai_compose_style_neutral(tr::now), + .customEmojiData = Data::SerializeCustomEmojiId(neutral->id), + }); + result.insert(end(result), begin(styles), end(styles)); + return result; +} + +[[nodiscard]] auto WithAddStyleTab(std::vector tabs) +-> std::vector { + tabs.push_back({ + .id = u"_add_style"_q, + .label = tr::lng_ai_compose_tone_create(tr::now), + .icon = &st::aiComposeAddStyleIcon, + .iconActive = &st::aiComposeAddStyleIconOver, + }); + return tabs; +} + +[[nodiscard]] TextWithEntities LoadingTitleSparkle( + not_null session) { + const auto sparkles = ChatHelpers::GenerateLocalTgsSticker( + session, + u"chat/sparkles_emoji"_q); + return tr::marked(u" "_q) + .append(Data::SingleCustomEmoji(sparkles->id)); +} + +class ComposeAiModeButton final : public Ui::RippleButton { +public: + ComposeAiModeButton( + QWidget *parent, + ComposeAiMode mode, + QString label); + + void setSelected(bool selected); + [[nodiscard]] ComposeAiMode mode() const; + +protected: + void paintEvent(QPaintEvent *e) override; + [[nodiscard]] QImage prepareRippleMask() const override; + +private: + const ComposeAiMode _mode; + const QString _label; + bool _selected = false; + +}; + +class ComposeAiModeTabs final : public Ui::RpWidget { +public: + ComposeAiModeTabs(QWidget *parent); + + void setActive(ComposeAiMode mode); + void setChangedCallback(Fn callback); + +protected: + int resizeGetHeight(int newWidth) override; + void paintEvent(QPaintEvent *e) override; + +private: + const not_null _translate; + const not_null _style; + const not_null _fix; + Fn _changed; + ComposeAiMode _active = ComposeAiMode::Style; + +}; + +class ComposeAiPreviewCard final : public Ui::RpWidget { +public: + ComposeAiPreviewCard( + QWidget *parent, + not_null session, + TextWithEntities original, + std::shared_ptr chatStyle); + + void setResizeCallback(Fn callback); + void setChooseCallback(Fn callback); + void setCopyCallback(Fn callback); + void setEmojifyChangedCallback(Fn callback); + void setOriginalTitle(const QString &title); + void setOriginalVisible(bool visible); + void setResultTitle(const TextWithEntities &title); + void setEmojifyVisible(bool visible); + void setEmojifyChecked(bool checked); + void setState(CardState state); + void setResultText(TextWithEntities text); + void setShow(std::shared_ptr show); + +protected: + int resizeGetHeight(int newWidth) override; + void paintEvent(QPaintEvent *e) override; + +private: + void refreshGeometry(); + void updateOriginalToggleIcon(); + + const Ui::Text::MarkedContext _context; + const TextWithEntities _original; + const not_null _originalTitle; + const not_null _originalBody; + const not_null _originalToggle; + const not_null _resultTitle; + const not_null _resultBody; + const not_null _copy; + const not_null _emojify; + Fn _resized; + Fn _chooseCallback; + Fn _copyCallback; + Fn _emojifyChanged; + bool _ignoreResizedCallback = false; + bool _originalExpanded = false; + bool _originalVisible = true; + bool _emojifyVisible = false; + bool _dividerVisible = false; + int _dividerTop = 0; + CardState _state = CardState::Waiting; + Ui::SkeletonAnimation _skeleton; + std::array _diffColors; + +}; + +class ComposeAiContent final : public Ui::RpWidget { +public: + ComposeAiContent( + QWidget *parent, + not_null box, + ComposeAiBoxArgs args); + ~ComposeAiContent(); + + [[nodiscard]] bool hasResult() const; + [[nodiscard]] const TextWithEntities &result() const; + [[nodiscard]] const std::vector &stylesData() const; + [[nodiscard]] const std::vector &tones() const; + void setReadyChangedCallback(Fn callback); + void setLoadingChangedCallback(Fn callback); + void setPremiumFloodCallback(Fn callback); + void setModeChangedCallback(Fn callback); + void setStyleSelectedCallback(Fn callback); + [[nodiscard]] ComposeAiMode mode() const; + [[nodiscard]] bool hasStyleSelection() const; + void setModeTabs(not_null tabs); + void setStyleTabs(not_null*> stylesWrap); + void refreshTones(); + void selectToneById(uint64 id); + void start(); + +protected: + int resizeGetHeight(int newWidth) override; + +private: + void refreshLayout(); + void chooseLanguage(); + void copyResult(); + void setMode(ComposeAiMode mode); + void updateTitles(); + void updatePinnedTabs(anim::type animated); + void cancelRequest(); + void request(); + void resetState(CardState state); + void applyResult(Api::ComposeWithAi::Result &&result); + void showError(const QString &error = {}); + void setAuthorId(UserId authorId); + void notifyLoadingChanged(); + void notifyReadyChanged(); + [[nodiscard]] QString currentTranslateStyle() const; + [[nodiscard]] QString currentTranslateStyleLabel() const; + + const not_null _box; + const not_null _session; + const TextWithEntities _original; + const LanguageId _detectedFrom; + LanguageId _to; + std::vector _tones; + std::vector _stylesData; + std::vector _translateStylesData; + QPointer _tabs; + QPointer _styles; + QPointer> _stylesWrap; + const not_null _preview; + const not_null _authorLabel; + Fn _readyChanged; + Fn _loadingChanged; + Fn _premiumFlood; + Fn _modeChanged; + Fn _styleSelected; + ComposeAiMode _mode = ComposeAiMode::Style; + int _styleIndex = -1; + int _translateStyleIndex = 0; + UserId _authorId = UserId(0); + bool _emojify = false; + CardState _state = CardState::Waiting; + mtpRequestId _requestId = 0; + int _requestToken = 0; + TextWithEntities _result; + +}; + +// ComposeAiModeButton + +ComposeAiModeButton::ComposeAiModeButton( + QWidget *parent, + ComposeAiMode mode, + QString label) +: RippleButton(parent, st::aiComposeButtonRippleInactive) +, _mode(mode) +, _label(std::move(label)) { + setCursor(style::cur_pointer); + setAccessibleName(_label); +} + +void ComposeAiModeButton::setSelected(bool selected) { + if (_selected == selected) { + return; + } + _selected = selected; + update(); +} + +ComposeAiMode ComposeAiModeButton::mode() const { + return _mode; +} + +void ComposeAiModeButton::paintEvent(QPaintEvent *e) { + Painter p(this); + PainterHighQualityEnabler hq(p); + + const auto radius = ComposeAiPillRadius(height()); + if (_selected) { + p.setPen(Qt::NoPen); + p.setBrush(ComposeAiActiveBackgroundColor( + st::aiComposeTabButtonBgActive)); + p.drawRoundedRect( + rect(), + radius, + radius); + } + const auto ripple = ComposeAiRippleColor( + _selected + ? st::aiComposeButtonRippleActive + : st::aiComposeButtonRippleInactive, + _selected + ? st::aiComposeButtonRippleActiveOpacity + : st::aiComposeButtonRippleInactiveOpacity); + paintRipple(p, 0, 0, &ripple); + + const auto &icon = ModeIcon(_mode, _selected); + const auto iconLeft = (width() - icon.width()) / 2; + icon.paint(p, iconLeft, st::aiComposeTabIconTop, width()); + + p.setPen(_selected + ? st::aiComposeTabLabelFgActive + : st::aiComposeTabLabelFg); + p.setFont(st::aiComposeTabLabelFont); + p.drawText( + QRect( + 0, + st::aiComposeTabLabelTop, + width(), + height() - st::aiComposeTabLabelTop), + Qt::AlignHCenter | Qt::AlignTop, + _label); +} + +QImage ComposeAiModeButton::prepareRippleMask() const { + return Ui::RippleAnimation::MaskByDrawer(size(), false, [&](QPainter &p) { + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + const auto radius = ComposeAiPillRadius(height()); + p.drawRoundedRect( + rect(), + radius, + radius); + }); +} + +// ComposeAiModeTabs + +ComposeAiModeTabs::ComposeAiModeTabs(QWidget *parent) +: RpWidget(parent) +, _translate(Ui::CreateChild( + this, + ComposeAiMode::Translate, + tr::lng_ai_compose_tab_translate(tr::now))) +, _style(Ui::CreateChild( + this, + ComposeAiMode::Style, + tr::lng_ai_compose_tab_style(tr::now))) +, _fix(Ui::CreateChild( + this, + ComposeAiMode::Fix, + tr::lng_ai_compose_tab_fix(tr::now))) { + const auto bind = [=](not_null button) { + button->setClickedCallback([=] { + setActive(button->mode()); + if (_changed) { + _changed(button->mode()); + } + }); + }; + bind(_translate); + bind(_style); + bind(_fix); + setActive(ComposeAiMode::Style); +} + +void ComposeAiModeTabs::setActive(ComposeAiMode mode) { + _active = mode; + _translate->setSelected(mode == ComposeAiMode::Translate); + _style->setSelected(mode == ComposeAiMode::Style); + _fix->setSelected(mode == ComposeAiMode::Fix); +} + +void ComposeAiModeTabs::setChangedCallback(Fn callback) { + _changed = std::move(callback); +} + +int ComposeAiModeTabs::resizeGetHeight(int newWidth) { + const auto padding = st::aiComposeTabsPadding; + const auto skip = st::aiComposeTabsSkip; + const auto innerWidth = newWidth - padding.left() - padding.right(); + const auto buttonWidth = (innerWidth - (2 * skip)) / 3; + const auto buttonHeight = st::aiComposeTabsHeight + - padding.top() + - padding.bottom(); + const auto top = padding.top(); + auto left = padding.left(); + for (const auto &button : { _translate, _style, _fix }) { + button->setGeometry(left, top, buttonWidth, buttonHeight); + left += buttonWidth + skip; + } + return st::aiComposeTabsHeight; +} + +void ComposeAiModeTabs::paintEvent(QPaintEvent *e) { + Painter p(this); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::aiComposeTabsBg); + const auto radius = st::aiComposeTabsRadius; + p.drawRoundedRect( + rect(), + radius, + radius); +} + +// ComposeAiPreviewCard + +ComposeAiPreviewCard::ComposeAiPreviewCard( + QWidget *parent, + not_null session, + TextWithEntities original, + std::shared_ptr chatStyle) +: RpWidget(parent) +, _context(Core::TextContext({ .session = session })) +, _original(std::move(original)) +, _originalTitle(Ui::CreateChild( + this, + st::aiComposeCardTitle)) +, _originalBody(Ui::CreateChild( + this, + st::aiComposeBodyLabel)) +, _originalToggle(Ui::CreateChild( + this, + st::aiComposeExpandButton)) +, _resultTitle(Ui::CreateChild( + this, + st::aiComposeCardTitle)) +, _resultBody(Ui::CreateChild( + this, + st::aiComposeBodyLabel)) +, _copy(Ui::CreateChild( + this, + st::aiComposeCopyButton)) +, _emojify( + Ui::CreateChild( + this, + tr::lng_ai_compose_emojify(tr::now), + st::aiComposeEmojifyCheckbox, + std::make_unique(st::defaultCheck,false))) +, _skeleton(_resultBody) { + _originalBody->setSelectable(true); + _originalBody->setMarkedText(_original, _context); + _resultTitle->setClickHandlerFilter([=](const auto &...) { + if (_chooseCallback) { + _chooseCallback(); + } + return false; + }); + _resultBody->setSelectable(true); + const auto watchHeight = [=](not_null label) { + label->heightValue( + ) | rpl::skip(1) | rpl::on_next([=] { + if (_resized && !_ignoreResizedCallback) { + _resized(); + } + }, lifetime()); + }; + watchHeight(_originalBody); + watchHeight(_resultBody); + _diffColors[0] = { &st::boxTextFgGood->p, &st::boxTextFgGood->p }; + _diffColors[1] = { &st::attentionButtonFg->p, &st::attentionButtonFg->p }; + _resultBody->setColors(_diffColors); + _originalToggle->setClickedCallback([=] { + _originalExpanded = !_originalExpanded; + updateOriginalToggleIcon(); + if (_resized) { + _resized(); + } + }); + _copy->setClickedCallback([=] { + if (_copyCallback) { + _copyCallback(); + } + }); + _copy->setAccessibleName(tr::lng_sr_ai_compose_copy_result(tr::now)); + _emojify->checkedChanges( + ) | rpl::on_next([=](bool checked) { + if (_emojifyChanged) { + _emojifyChanged(checked); + } + }, _emojify->lifetime()); + setOriginalTitle(tr::lng_ai_compose_original(tr::now)); + setResultTitle(tr::lng_ai_compose_result(tr::now, tr::marked)); + _resultBody->setMarkedText(_original, _context); + _copy->setVisible(false); + updateOriginalToggleIcon(); + if (chatStyle) { + const auto style = chatStyle; + const auto s = session.get(); + const auto setupCaches = [=](not_null label) { + label->setPreCache([=] { + return style->messageStyle(false, false).preCache.get(); + }); + label->setBlockquoteCache([=] { + return style->coloredQuoteCache( + false, + s->user()->colorIndex()); + }); + }; + setupCaches(_originalBody); + setupCaches(_resultBody); + } +} + +void ComposeAiPreviewCard::setResizeCallback(Fn callback) { + _resized = std::move(callback); +} + +void ComposeAiPreviewCard::setChooseCallback(Fn callback) { + _chooseCallback = std::move(callback); +} + +void ComposeAiPreviewCard::setCopyCallback(Fn callback) { + _copyCallback = std::move(callback); +} + +void ComposeAiPreviewCard::setEmojifyChangedCallback(Fn callback) { + _emojifyChanged = std::move(callback); +} + +void ComposeAiPreviewCard::setOriginalTitle(const QString &title) { + _originalTitle->setText(title); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setOriginalVisible(bool visible) { + if (_originalVisible == visible) { + return; + } + _originalVisible = visible; + _originalTitle->setVisible(visible); + _originalBody->setVisible(visible); + _originalToggle->setVisible(false); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setResultTitle(const TextWithEntities &title) { + _resultTitle->setMarkedText(title); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setEmojifyVisible(bool visible) { + _emojifyVisible = visible; + _emojify->setVisible(visible); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setEmojifyChecked(bool checked) { + _emojify->setChecked(checked, Ui::Checkbox::NotifyAboutChange::DontNotify); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setState(CardState state) { + if (_state == state) { + return; + } + const auto wasLoading = (_state == CardState::Loading); + _state = state; + switch (_state) { + case CardState::Waiting: + case CardState::Failed: + _resultBody->setMarkedText(_original, _context); + _copy->setVisible(false); + if (wasLoading) { + _skeleton.stop(); + } + break; + case CardState::Loading: + _resultBody->setMarkedText(_original, _context); + _copy->setVisible(false); + _skeleton.start(); + break; + case CardState::Ready: + _copy->setVisible(true); + if (wasLoading) { + _skeleton.stop(); + } + break; + } + refreshGeometry(); +} + +void ComposeAiPreviewCard::setResultText(TextWithEntities text) { + _resultBody->setMarkedText(std::move(text), _context); + refreshGeometry(); +} + +void ComposeAiPreviewCard::setShow(std::shared_ptr show) { + const auto setupFilter = [&](not_null label) { + label->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (dynamic_cast(handler.get())) { + ActivateClickHandler(label, handler, ClickContext{ + .button = button, + .other = QVariant::fromValue(ClickHandlerContext{ + .show = show, + }) + }); + return false; + } + return true; + }); + }; + setupFilter(_originalBody); + setupFilter(_resultBody); +} + +int ComposeAiPreviewCard::resizeGetHeight(int newWidth) { + const auto padding = st::aiComposeCardPadding; + const auto contentWidth = newWidth - padding.left() - padding.right(); + auto y = padding.top(); + + _dividerVisible = false; + if (_originalVisible) { + _originalTitle->show(); + _originalBody->show(); + _originalTitle->resizeToWidth(contentWidth); + _originalToggle->setVisible(false); + + const auto toggleTop = y + + (_originalTitle->height() - _originalToggle->height()) / 2; + _originalToggle->moveToRight(padding.right(), toggleTop, newWidth); + const auto originalTitleWidth = contentWidth + - _originalToggle->width() + - st::aiComposeCardControlSkip; + _originalTitle->setGeometryToLeft( + padding.left(), + y, + std::max(originalTitleWidth, 0), + _originalTitle->height(), + newWidth); + y = std::max( + y + _originalTitle->height(), + toggleTop + _originalToggle->height()); + + _ignoreResizedCallback = true; + const auto wasOriginalSize = _originalBody->size(); + _originalBody->resizeToWidth(contentWidth); + const auto fullOriginalHeight = _originalBody->height(); + _originalBody->resize(wasOriginalSize); + _ignoreResizedCallback = false; + + const auto lineHeight = _originalBody->st().style.lineHeight; + const auto originalHeight = _originalExpanded + ? fullOriginalHeight + : std::min(fullOriginalHeight, lineHeight); + _originalBody->setGeometryToLeft( + padding.left(), + y, + contentWidth, + originalHeight, + newWidth); + const auto expandable = fullOriginalHeight > lineHeight; + _originalToggle->setVisible(expandable); + y += originalHeight + st::aiComposeCardSectionSkip; + _dividerTop = y; + _dividerVisible = true; + y += st::lineWidth + st::aiComposeCardSectionSkip; + } else { + _originalTitle->hide(); + _originalBody->hide(); + _originalToggle->hide(); + } + + _resultTitle->show(); + auto controlsWidth = 0; + if (_emojifyVisible) { + _emojify->show(); + _emojify->resizeToNaturalWidth(contentWidth); + controlsWidth += _emojify->width() + + st::aiComposeCardControlSkip; + } else { + _emojify->hide(); + } + const auto resultTitleWidth = std::max( + contentWidth - controlsWidth, + 0); + _resultTitle->resizeToWidth(resultTitleWidth); + auto right = padding.right(); + if (_emojifyVisible) { + _emojify->moveToRight(right, y, newWidth); + right += _emojify->width() + st::aiComposeCardControlSkip; + } + _resultTitle->setGeometryToLeft( + padding.left(), + y, + resultTitleWidth, + _resultTitle->height(), + newWidth); + y = std::max( + y + _resultTitle->height(), + (_emojifyVisible + ? (y - _emojify->getMargins().top() + _emojify->height()) + : 0)); + + const auto lineHeight = _resultBody->st().style.lineHeight + ? _resultBody->st().style.lineHeight + : _resultBody->st().style.font->height; + if (!_copy->isHidden()) { + _resultBody->setSkipBlock(_copy->width(), lineHeight); + } else { + _resultBody->setSkipBlock(0, 0); + } + _resultBody->resizeToWidth(contentWidth); + _resultBody->setGeometryToLeft( + padding.left(), + y, + contentWidth, + _resultBody->height(), + newWidth); + if (!_copy->isHidden()) { + _copy->moveToRight( + padding.right(), + y + _resultBody->height() - lineHeight, + newWidth); + } + y += _resultBody->height(); + + return y + padding.bottom(); +} + +void ComposeAiPreviewCard::paintEvent(QPaintEvent *e) { + Painter p(this); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::aiComposeCardBg); + p.drawRoundedRect( + rect(), + st::aiComposeCardRadius, + st::aiComposeCardRadius); + if (_dividerVisible) { + p.setBrush(Qt::NoBrush); + auto color = st::windowSubTextFg->c; + color.setAlphaF(st::aiComposeShadowOpacity); + p.setPen(color); + p.drawLine( + st::aiComposeCardPadding.left(), + _dividerTop, + width() - st::aiComposeCardPadding.right(), + _dividerTop); + } +} + +void ComposeAiPreviewCard::refreshGeometry() { + if (width() > 0) { + resizeToWidth(width()); + } + if (_resized) { + _resized(); + } +} + +void ComposeAiPreviewCard::updateOriginalToggleIcon() { + _originalToggle->setIconOverride( + _originalExpanded ? &st::aiComposeCollapseIcon : nullptr, + _originalExpanded ? &st::aiComposeCollapseIcon : nullptr); + _originalToggle->setAccessibleName(_originalExpanded + ? tr::lng_sr_ai_compose_collapse_original(tr::now) + : tr::lng_sr_ai_compose_expand_original(tr::now)); +} + +// ComposeAiContent + +ComposeAiContent::ComposeAiContent( + QWidget *parent, + not_null box, + ComposeAiBoxArgs args) +: RpWidget(parent) +, _box(box) +, _session(args.session) +, _original(std::move(args.text)) +, _detectedFrom(Platform::Language::Recognize(_original.text)) +, _to(DefaultAiTranslateTo(_detectedFrom)) +, _tones(_session->data().aiComposeTones().list()) +, _stylesData(ResolveStyleDescriptors(_tones)) +, _translateStylesData(ResolveTranslateStyleDescriptors(_session, _stylesData)) +, _preview( + Ui::CreateChild( + this, + _session, + _original, + args.chatStyle)) +, _authorLabel(Ui::CreateChild( + this, + st::aiComposeAuthorLabel)) { + _preview->setResizeCallback([=] { refreshLayout(); }); + _preview->setChooseCallback([=] { chooseLanguage(); }); + _preview->setCopyCallback([=] { copyResult(); }); + _preview->setEmojifyChangedCallback([=](bool checked) { + _emojify = checked; + if (_mode != ComposeAiMode::Fix) { + request(); + } + }); + _preview->setShow(_box->uiShow()); + _authorLabel->setVisible(false); + _authorLabel->heightValue( + ) | rpl::skip(1) | rpl::on_next([=] { + refreshLayout(); + }, lifetime()); + const auto show = _box->uiShow(); + _authorLabel->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (dynamic_cast(handler.get())) { + ActivateClickHandler(_authorLabel, handler, ClickContext{ + .button = button, + .other = QVariant::fromValue(ClickHandlerContext{ + .show = show, + }) + }); + return false; + } + return true; + }); +} + +ComposeAiContent::~ComposeAiContent() { + cancelRequest(); +} + +bool ComposeAiContent::hasResult() const { + return _state == CardState::Ready; +} + +const TextWithEntities &ComposeAiContent::result() const { + return _result; +} + +const std::vector &ComposeAiContent::stylesData() const { + return _stylesData; +} + +const std::vector &ComposeAiContent::tones() const { + return _tones; +} + +void ComposeAiContent::setReadyChangedCallback(Fn callback) { + _readyChanged = std::move(callback); +} + +void ComposeAiContent::setLoadingChangedCallback(Fn callback) { + _loadingChanged = std::move(callback); + notifyLoadingChanged(); +} + +void ComposeAiContent::setModeTabs(not_null tabs) { + _tabs = tabs; + _tabs->setChangedCallback([=](ComposeAiMode mode) { + setMode(mode); + }); + _tabs->setActive(_mode); +} + +void ComposeAiContent::setStyleTabs( + not_null*> stylesWrap) { + _stylesWrap = stylesWrap; + _stylesWrap->setDuration(0); + _styles = stylesWrap->entity(); + _styles->setChangedCallback([=](int index) { + if (index >= 0 && index < int(_tones.size())) { + const auto wasNoSelection = (_styleIndex < 0); + _styleIndex = index; + updateTitles(); + if (_mode == ComposeAiMode::Style) { + request(); + if (wasNoSelection && _styleSelected) { + _styleSelected(); + } + } + } else if (index == int(_tones.size())) { + _styles->setActive(_styleIndex); + _box->uiShow()->show(Box( + CreateAiToneBox, + _session, + crl::guard(this, [=](Data::AiComposeTone tone) { + selectToneById(tone.id); + }))); + } + }); + _styles->setActive(_styleIndex); + _stylesWrap->toggle(_mode == ComposeAiMode::Style, anim::type::instant); +} + +void ComposeAiContent::refreshTones() { + auto previousKey = QString(); + auto hadSelection = false; + if (_styleIndex >= 0 && _styleIndex < int(_tones.size())) { + const auto &prev = _tones[_styleIndex]; + previousKey = prev.isDefault + ? prev.defaultType + : QString::number(prev.id); + hadSelection = true; + } + _tones = _session->data().aiComposeTones().list(); + _stylesData = ResolveStyleDescriptors(_tones); + _translateStylesData = ResolveTranslateStyleDescriptors( + _session, + _stylesData); + auto remapped = -1; + if (hadSelection) { + for (auto i = 0; i != int(_tones.size()); ++i) { + const auto &tone = _tones[i]; + const auto key = tone.isDefault + ? tone.defaultType + : QString::number(tone.id); + if (key == previousKey) { + remapped = i; + break; + } + } + } + _styleIndex = remapped; + if (_mode == ComposeAiMode::Style && hadSelection && _styleIndex < 0) { + request(); + } +} + +void ComposeAiContent::selectToneById(uint64 id) { + for (auto i = 0; i != int(_tones.size()); ++i) { + const auto &tone = _tones[i]; + if (!tone.isDefault && tone.id == id) { + const auto wasNoSelection = (_styleIndex < 0); + _styleIndex = i; + updateTitles(); + if (_styles) { + _styles->setActive(_styleIndex); + _styles->scrollToActive(); + } + if (_mode == ComposeAiMode::Style) { + request(); + if (wasNoSelection && _styleSelected) { + _styleSelected(); + } + } + return; + } + } +} + +void ComposeAiContent::start() { + updatePinnedTabs(anim::type::instant); + updateTitles(); + request(); +} + +int ComposeAiContent::resizeGetHeight(int newWidth) { + _preview->resizeToWidth(newWidth); + _preview->moveToLeft(0, 0, newWidth); + auto y = _preview->height(); + if (!_authorLabel->isHidden()) { + _authorLabel->resizeToWidth(newWidth); + _authorLabel->moveToLeft( + 0, + y + st::aiComposeAuthorLabelTop, + newWidth); + y += st::aiComposeAuthorLabelTop + _authorLabel->height(); + } + return y; +} + +void ComposeAiContent::refreshLayout() { + if (width() > 0) { + resizeToWidth(width()); + } +} + +void ComposeAiContent::chooseLanguage() { + if (_mode != ComposeAiMode::Translate) { + return; + } + const auto weak = QPointer(this); + const auto session = _session; + const auto styles = _translateStylesData; + const auto selectedStyle = std::make_shared(_translateStyleIndex); + _box->uiShow()->showBox(Box([=](not_null box) { + const auto apply = [=](LanguageId id, int styleIndex) { + if (!weak) { + return; + } + weak->_to = id; + if (const auto count = int(weak->_translateStylesData.size())) { + weak->_translateStyleIndex = std::clamp(styleIndex, 0, count - 1); + } + weak->updateTitles(); + weak->request(); + }; + Ui::ChooseLanguageBox( + box, + tr::lng_languages(), + [=](std::vector ids) { + if (ids.empty()) { + return; + } + apply(ids.front(), *selectedStyle); + }, + { _to }, + false, + nullptr); + const auto bottom = box->setPinnedToBottomContent( + object_ptr(box)); + const auto skip = st::defaultSubsectionTitlePadding.left(); + const auto tabs = bottom->add( + object_ptr( + bottom, + styles, + session->data().customEmojiManager().factory( + Data::CustomEmojiSizeTag::Large)), + QMargins( + (skip - st::aiComposeStyleTabsPadding.left()), + 0, + (skip - st::aiComposeStyleTabsPadding.right()), + 0)); + tabs->setPaintOuterCorners(false); + tabs->setChangedCallback([=](int index) { + if (index >= 0 && index < int(styles.size())) { + *selectedStyle = index; + apply(_to, index); + box->closeBox(); + } + }); + tabs->setActive(std::clamp(*selectedStyle, 0, int(styles.size()) - 1)); + tabs->scrollToActive(); + })); +} + +void ComposeAiContent::copyResult() { + if (_state != CardState::Ready) { + return; + } + TextUtilities::SetClipboardText( + TextForMimeData::WithExpandedLinks(_result)); +} + +void ComposeAiContent::setMode(ComposeAiMode mode) { + if (_mode == mode) { + return; + } + if (mode != ComposeAiMode::Style) { + _styleIndex = -1; + } + _mode = mode; + _state = CardState::Waiting; + _preview->setState(CardState::Waiting); + setAuthorId(UserId(0)); + notifyLoadingChanged(); + if (_modeChanged) { + _modeChanged(_mode); + } + updatePinnedTabs(anim::type::normal); + updateTitles(); + refreshLayout(); + request(); +} + +void ComposeAiContent::updateTitles() { + const auto hasResult = (_state == CardState::Loading) + || (_state == CardState::Ready); + _preview->setOriginalVisible(hasResult); + _preview->setOriginalTitle( + (_mode == ComposeAiMode::Translate) + ? FromTitle(_detectedFrom) + : tr::lng_ai_compose_original(tr::now)); + _preview->setResultTitle( + hasResult + ? ((_mode == ComposeAiMode::Translate) + ? ToTitle(_to, currentTranslateStyleLabel()) + : tr::lng_ai_compose_result(tr::now, tr::marked)) + : tr::lng_ai_compose_original(tr::now, tr::marked)); + const auto emojifyOnlyMode = !hasResult + && (_mode == ComposeAiMode::Style) + && (_styleIndex < 0); + _preview->setEmojifyVisible( + (hasResult && (_mode != ComposeAiMode::Fix)) + || emojifyOnlyMode); + _preview->setEmojifyChecked(_emojify); +} + +void ComposeAiContent::updatePinnedTabs(anim::type animated) { + if (_tabs) { + _tabs->setActive(_mode); + } + if (_styles) { + _styles->setActive(_styleIndex); + } + if (_stylesWrap) { + _stylesWrap->toggle(_mode == ComposeAiMode::Style, animated); + } +} + +void ComposeAiContent::cancelRequest() { + ++_requestToken; + if (_requestId) { + _session->api().composeWithAi().cancel(_requestId); + _requestId = 0; + } +} + +void ComposeAiContent::request() { + cancelRequest(); + if (_mode == ComposeAiMode::Style && _styleIndex < 0 && !_emojify) { + if (_state != CardState::Waiting) { + resetState(CardState::Waiting); + } + return; + } + resetState(CardState::Loading); + + auto request = Api::ComposeWithAi::Request{ + .text = _original, + .emojify = (_mode != ComposeAiMode::Fix) && _emojify, + }; + switch (_mode) { + case ComposeAiMode::Translate: { + request.translateToLang = _to.twoLetterCode(); + const auto style = currentTranslateStyle(); + if (!style.isEmpty()) { + request.setDefaultTone(style); + } + } break; + case ComposeAiMode::Style: + if (_styleIndex >= 0 && _styleIndex < int(_tones.size())) { + const auto &tone = _tones[_styleIndex]; + if (tone.isDefault) { + request.setDefaultTone(tone.defaultType); + } else { + request.setCustomTone(tone.id, tone.accessHash); + } + } + break; + case ComposeAiMode::Fix: + request.proofread = true; + break; + } + + const auto token = ++_requestToken; + const auto weak = QPointer(this); + _requestId = _session->api().composeWithAi().request( + std::move(request), + [=](Api::ComposeWithAi::Result &&result) { + if (!weak || weak->_requestToken != token) { + return; + } + weak->_requestId = 0; + weak->applyResult(std::move(result)); + }, + [=](const MTP::Error &error) { + if (!weak || weak->_requestToken != token) { + return; + } + weak->_requestId = 0; + if (MTP::IgnoreError(error)) { + weak->resetState(CardState::Waiting); + return; + } + weak->showError(error.type()); + }); +} + +void ComposeAiContent::setAuthorId(UserId authorId) { + if (_authorId == authorId) { + return; + } + _authorId = authorId; + if (const auto user = _session->data().userLoaded(authorId)) { + const auto name = user->shortName(); + auto mention = tr::marked(name); + mention.entities.push_back(EntityInText( + EntityType::MentionName, + 0, + name.size(), + TextUtilities::MentionNameDataFromFields({ + .selfId = _session->userId().bare, + .userId = authorId.bare, + .accessHash = user->accessHash(), + }))); + _authorLabel->setMarkedText( + tr::lng_ai_compose_author( + tr::now, + lt_user, + std::move(mention), + tr::marked), + Core::TextContext({ .session = _session })); + _authorLabel->setVisible(true); + } else { + _authorLabel->setMarkedText({}); + _authorLabel->setVisible(false); + _authorId = UserId(0); + } + refreshLayout(); +} + +void ComposeAiContent::resetState(CardState state) { + _state = state; + _result = {}; + setAuthorId(UserId(0)); + _preview->setState(state); + notifyLoadingChanged(); + updateTitles(); + notifyReadyChanged(); +} + +void ComposeAiContent::applyResult(Api::ComposeWithAi::Result &&result) { + _result = std::move(result.resultText); + if (_result.text.isEmpty()) { + showError({}); + return; + } + auto display = (_mode == ComposeAiMode::Fix && result.diffText) + ? BuildDiffDisplay(*result.diffText) + : _result; + _state = _result.text.isEmpty() ? CardState::Failed : CardState::Ready; + _preview->setState(_state); + notifyLoadingChanged(); + if (_state == CardState::Ready) { + _preview->setResultText(std::move(display)); + if (_mode == ComposeAiMode::Style + && _styleIndex >= 0 + && _styleIndex < int(_tones.size())) { + setAuthorId(_tones[_styleIndex].authorId); + } else { + setAuthorId(UserId(0)); + } + } + updateTitles(); + notifyReadyChanged(); + refreshLayout(); +} + +void ComposeAiContent::showError(const QString &error) { + _state = CardState::Failed; + setAuthorId(UserId(0)); + _preview->setState(CardState::Failed); + notifyLoadingChanged(); + updateTitles(); + notifyReadyChanged(); + refreshLayout(); + if (error == u"AICOMPOSE_FLOOD_PREMIUM"_q) { + const auto show = Main::MakeSessionShow( + _box->uiShow(), + _session); + Settings::ShowPremiumPromoToast( + show, + ChatHelpers::ResolveWindowDefault(), + tr::lng_ai_compose_flood_text( + tr::now, + lt_link, + tr::link(tr::lng_ai_compose_flood_link(tr::now, tr::bold)), + tr::rich), + u"ai_compose"_q); + if (_premiumFlood) { + _premiumFlood(); + } + return; + } else if (error == u"INPUT_TEXT_TOO_LONG"_q) { + _box->showToast(tr::lng_ai_compose_error_too_long(tr::now)); + return; + } + _box->showToast(error.isEmpty() + ? tr::lng_ai_compose_error(tr::now) + : error); +} + +void ComposeAiContent::notifyLoadingChanged() { + if (_loadingChanged) { + _loadingChanged(_state == CardState::Loading); + } +} + +void ComposeAiContent::notifyReadyChanged() { + if (_readyChanged) { + _readyChanged(_state == CardState::Ready); + } +} + +void ComposeAiContent::setPremiumFloodCallback(Fn callback) { + _premiumFlood = std::move(callback); +} + +void ComposeAiContent::setModeChangedCallback( + Fn callback) { + _modeChanged = std::move(callback); +} + +void ComposeAiContent::setStyleSelectedCallback(Fn callback) { + _styleSelected = std::move(callback); +} + +QString ComposeAiContent::currentTranslateStyle() const { + return (_translateStyleIndex >= 0 + && _translateStyleIndex < int(_translateStylesData.size())) + ? _translateStylesData[_translateStyleIndex].id + : QString(); +} + +QString ComposeAiContent::currentTranslateStyleLabel() const { + if (const auto style = currentTranslateStyle(); !style.isEmpty()) { + return (_translateStyleIndex >= 0 + && _translateStyleIndex < int(_translateStylesData.size())) + ? _translateStylesData[_translateStyleIndex].label + : QString(); + } + return QString(); +} + +ComposeAiMode ComposeAiContent::mode() const { + return _mode; +} + +bool ComposeAiContent::hasStyleSelection() const { + return _styleIndex >= 0; +} + +struct StyleTooltipHandle { + QPointer tooltip; + Fn updateVisibility; +}; + +[[nodiscard]] StyleTooltipHandle SetupStyleTooltip( + not_null box, + not_null pinnedToTop, + not_null stylesWrap, + Fn currentMode) { + const auto tooltip = Ui::CreateChild( + box, + object_ptr>( + box, + Ui::MakeNiceTooltipLabel( + box, + tr::lng_ai_compose_style_tooltip(tr::rich), + st::historyMessagesTTLLabel.minWidth, + st::ttlMediaImportantTooltipLabel), + st::historyRecordTooltip.padding), + st::historyRecordTooltip); + tooltip->toggleFast(false); + + struct State { + bool shown = false; + bool shownOnce = false; + }; + const auto state = box->lifetime().make_state(); + + const auto updateGeometry = [=] { + const auto local = stylesWrap->geometry(); + if (local.isEmpty()) { + return; + } + const auto geometry = Ui::MapFrom(box, pinnedToTop, local); + const auto countPosition = [=](QSize size) { + const auto left = geometry.x() + + (geometry.width() - size.width()) / 2; + return QPoint( + std::max(std::min(left, box->width() - size.width()), 0), + (geometry.y() + + geometry.height() + - st::historyRecordTooltip.arrow + - (st::aiComposeBoxStyleTabsSkip / 2))); + }; + tooltip->pointAt(geometry, RectPart::Bottom, countPosition); + }; + + const auto updateVisibility = [=](bool visible) { + const auto show = visible + && !Core::App().settings().readPref( + kAiComposeStyleTooltipHiddenPref); + if (state->shown != show) { + state->shown = show; + if (show) { + updateGeometry(); + tooltip->raise(); + } + if (show && !state->shownOnce) { + state->shownOnce = true; + tooltip->toggleFast(true); + } else { + tooltip->toggleAnimated(show); + } + } + }; + + stylesWrap->geometryValue( + ) | rpl::on_next([=](const QRect &geometry) { + if (!geometry.isEmpty()) { + if (state->shown) { + updateGeometry(); + } else { + updateVisibility(currentMode() == ComposeAiMode::Style); + } + } + }, tooltip->lifetime()); + + return { tooltip, updateVisibility }; +} + +} // namespace + +void ComposeAiBox(not_null box, ComposeAiBoxArgs &&args) { + const auto sendButtonHeight = st::aiComposeSendButton.inner.height; + const auto buttonHeight = st::aiComposeSendButton.inner.icon.height() + + 2 * st::aiComposeSendButton.sendIconFillPadding; + const auto boxStyle = [&](const style::Box &base) { + const auto result = box->lifetime().make_state(base); + result->button.height = buttonHeight; + result->buttonHeight = buttonHeight; + result->button.textTop = base.button.textTop + - (base.button.height - buttonHeight) / 2; + return result; + }; + const auto boxStyleNoSend = boxStyle(st::aiComposeBox); + const auto boxStyleWithSend = boxStyle(st::aiComposeBoxWithSend); + box->setStyle(*boxStyleNoSend); + box->setNoContentMargin(true); + box->setWidth(st::boxWideWidth); + const auto session = args.session; + box->addTopButton(st::aiComposeBoxClose, [=] { + box->closeBox(); + })->setAccessibleName(tr::lng_close(tr::now)); + box->addTopButton(st::aiComposeBoxInfoButton, [=] { + box->uiShow()->show(Box(Ui::AboutCocoonBox)); + })->setAccessibleName(tr::lng_sr_ai_compose_info(tr::now)); + + const auto body = box->verticalLayout(); + const auto tabsSkip = QMargins(0, 0, 0, st::aiComposeBoxStyleTabsSkip); + const auto pinnedToTop = box->setPinnedToTopContent( + object_ptr(box)); + const auto tabs = pinnedToTop->add( + object_ptr(pinnedToTop), + st::aiComposeContentMargin + tabsSkip); + const auto content = body->add( + object_ptr(box, box, args), + st::aiComposeContentMargin); + const auto contextMenu = box->lifetime().make_state< + base::unique_qptr>(); + const auto stylesWrapHolder = box->lifetime().make_state< + QPointer>>(); + const auto styleTooltipHolder = box->lifetime().make_state< + QPointer>(); + const auto styleTooltipUpdater = box->lifetime().make_state< + Fn>(); + + content->setModeTabs(tabs); + + const auto rebuildStylesWrap = [=] { + auto savedScroll = -1; + if (const auto old = stylesWrapHolder->data()) { + savedScroll = old->entity()->scrollLeft(); + delete old; + } + if (const auto old = styleTooltipHolder->data()) { + delete old; + } + auto emojiFactory = session->data().customEmojiManager().factory( + Data::CustomEmojiSizeTag::Large); + auto wrap = object_ptr>( + pinnedToTop, + object_ptr( + pinnedToTop, + WithAddStyleTab(content->stylesData()), + std::move(emojiFactory)), + tabsSkip); + const auto ptr = wrap.data(); + pinnedToTop->add(std::move(wrap), st::aiComposeContentMargin); + *stylesWrapHolder = ptr; + ptr->entity()->setContextMenuCallback([=](int index, QPoint globalPos) { + const auto &tones = content->tones(); + if (index < 0 || index >= int(tones.size())) { + return; + } + const auto &tone = tones[index]; + if (tone.isDefault) { + return; + } + *contextMenu = base::make_unique_q( + ptr->entity(), + st::popupMenuWithIcons); + const auto toneCopy = tone; + if (!toneCopy.slug.isEmpty()) { + const auto shortcutText = Api::AiApplyShortcutText(); + if (shortcutText.isEmpty()) { + const auto resolve = ChatHelpers::ResolveWindowDefault(); + (*contextMenu)->addAction( + tr::lng_ai_compose_bind_set_hotkey_short(tr::now), + [=] { + if (const auto window = resolve(session)) { + window->setHighlightControlId( + Settings::ShortcutsHighlightId( + Shortcuts::Command + ::ComposeAiApplyInPlace)); + window->showSettings( + Settings::ShortcutsId()); + } + }, + &st::menuIconShortcut); + } else { + const auto label = tr::lng_ai_compose_bind_use_hotkey( + tr::now, + lt_keys, + shortcutText); + const auto checked + = (Api::AiApplyBoundSlug() == toneCopy.slug); + auto item = base::make_unique_q( + (*contextMenu)->menu(), + st::popupMenuWithIcons.menu, + Ui::CreateChild( + (*contextMenu)->menu().get()), + nullptr, + nullptr); + item->action()->setText(label); + item->init(checked); + item->checkView()->checkedChanges( + ) | rpl::on_next([=](bool toggled) { + if (toggled) { + Api::SetAiApplyBoundSlug(toneCopy.slug); + } else if (Api::AiApplyBoundSlug() + == toneCopy.slug) { + Api::ClearAiApplyBoundSlug(); + } + }, item->lifetime()); + (*contextMenu)->addAction(std::move(item)); + } + } + if (toneCopy.creator) { + (*contextMenu)->addAction( + tr::lng_ai_compose_tone_edit(tr::now), + [=] { + box->uiShow()->show(Box( + EditAiToneBox, + session, + toneCopy, + crl::guard(content, [=](Data::AiComposeTone tone) { + content->selectToneById(tone.id); + }))); + }, + &st::menuIconEdit); + } + (*contextMenu)->addAction( + tr::lng_ai_compose_tone_share(tr::now), + [=] { + const auto url = session->createInternalLinkFull( + "addstyle/" + toneCopy.slug); + FastShareLink( + Main::MakeSessionShow(box->uiShow(), session), + url); + }, + &st::menuIconShare); + (*contextMenu)->addAction(base::make_unique_q( + (*contextMenu)->menu(), + st::menuWithIconsAttention, + Ui::Menu::CreateAction( + (*contextMenu)->menu().get(), + toneCopy.creator + ? tr::lng_ai_compose_tone_delete(tr::now) + : tr::lng_ai_compose_tone_remove(tr::now), + [=] { + ConfirmDeleteAiTone( + box->uiShow(), + session, + toneCopy); + }), + &st::menuIconDeleteAttention, + &st::menuIconDeleteAttention)); + (*contextMenu)->popup(globalPos); + }); + content->setStyleTabs(ptr); + if (savedScroll >= 0) { + ptr->entity()->setScrollLeft(savedScroll); + } + auto handle = SetupStyleTooltip( + box, + pinnedToTop, + ptr, + [=] { return content->mode(); }); + *styleTooltipHolder = handle.tooltip; + *styleTooltipUpdater = std::move(handle.updateVisibility); + }; + rebuildStylesWrap(); + + session->data().aiComposeTones().updated( + ) | rpl::on_next([=] { + content->refreshTones(); + rebuildStylesWrap(); + }, box->lifetime()); + + const auto sparkle = LoadingTitleSparkle(session); + const auto loading = box->lifetime().make_state< + rpl::variable>(); + + content->setLoadingChangedCallback([=](bool value) { + *loading = value; + }); + + box->setTitle(rpl::combine( + loading->value(), + tr::lng_ai_compose_title(tr::marked) + ) | rpl::map([=](bool loading, TextWithEntities title) { + return loading ? title.append(sparkle) : title; + }), Core::TextContext({ .session = session })); + + auto premiumFlooded = std::make_shared(false); + auto sendButton = std::make_shared>(); + + const auto applyAndClose = [=] { + if (!content->hasResult()) { + return; + } + args.apply(TextWithEntities(content->result())); + box->closeBox(); + }; + const auto sendResult = [=](Api::SendOptions options) { + if (!args.send || !content->hasResult()) { + return; + } + args.send( + TextWithEntities(content->result()), + options, + crl::guard(box, [=] { + box->closeBox(); + })); + }; + const auto addApplyButton = [=]( + const style::Box &style, + rpl::producer text, + Fn callback) { + box->setStyle(style); + const auto result = box->addButton(std::move(text), std::move(callback)); + result->setFullRadius(true); + return result; + }; + const auto disableButton = [=](not_null button) { + button->clearState(); + button->setDisabled(true); + button->setAttribute(Qt::WA_TransparentForMouseEvents); + button->setTextFgOverride( + anim::color(st::activeButtonBg, st::activeButtonFg, 0.5)); + button->setClickedCallback([] { + }); + }; + + const auto rebuildButtons = [=] { + if (*sendButton) { + delete sendButton->data(); + } + *sendButton = nullptr; + box->clearButtons(); + box->addTopButton(st::aiComposeBoxClose, [=] { + box->closeBox(); + })->setAccessibleName(tr::lng_close(tr::now)); + box->addTopButton(st::aiComposeBoxInfoButton, [=] { + box->uiShow()->show(Box(Ui::AboutCocoonBox)); + })->setAccessibleName(tr::lng_sr_ai_compose_info(tr::now)); + + if (*premiumFlooded) { + auto helper = Ui::Text::CustomEmojiHelper(); + const auto badge = helper.paletteDependent( + Ui::Text::CustomEmojiTextBadge( + u"x50"_q, + st::aiComposeBadge, + st::aiComposeBadgeMargin)); + const auto btn = addApplyButton( + *boxStyleNoSend, + tr::lng_ai_compose_increase_limit(), nullptr); + btn->setContext(helper.context()); + btn->setText(rpl::single( + tr::lng_ai_compose_increase_limit(tr::now, tr::marked) + .append(' ') + .append(badge))); + const auto resolve = ChatHelpers::ResolveWindowDefault(); + const auto close = crl::guard(box, [=] { + box->closeBox(); + }); + btn->setClickedCallback([=] { + if (const auto controller = resolve(session)) { + ShowPremiumPreviewBox( + controller, + PremiumFeature::AiCompose); + } + close(); + }); + } else if (content->mode() == ComposeAiMode::Style + && !content->hasStyleSelection() + && !content->hasResult()) { + const auto btn = addApplyButton( + *boxStyleNoSend, + tr::lng_ai_compose_select_style(), nullptr); + disableButton(btn); + } else if (content->hasResult()) { + const auto isStyle = + (content->mode() == ComposeAiMode::Style); + const auto btn = addApplyButton( + args.send ? *boxStyleWithSend : *boxStyleNoSend, + isStyle + ? tr::lng_ai_compose_apply_style() + : tr::lng_ai_compose_apply(), + applyAndClose); + if (args.send) { + const auto send = Ui::CreateChild( + btn->parentWidget(), + st::aiComposeSendButton); + send->setState({ .type = Ui::SendButton::Type::Send }); + send->setAccessibleName(tr::lng_send_button(tr::now)); + send->show(); + btn->geometryValue( + ) | rpl::on_next([=](QRect geometry) { + const auto size = sendButtonHeight; + send->resize(size, size); + send->moveToLeft( + geometry.x() + geometry.width() + + st::aiComposeSendButtonSkip, + geometry.y() + (geometry.height() - size) / 2); + }, send->lifetime()); + send->setClickedCallback([=] { + sendResult({}); + }); + if (args.setupMenu) { + args.setupMenu( + send, + [=](Api::SendOptions options) { + sendResult(options); + }); + } + *sendButton = send; + } + } else { + const auto isStyle = + (content->mode() == ComposeAiMode::Style); + const auto btn = addApplyButton( + *boxStyleNoSend, + isStyle + ? tr::lng_ai_compose_apply_style() + : tr::lng_ai_compose_apply(), + nullptr); + disableButton(btn); + } + }; + + content->setReadyChangedCallback([=](bool) { + rebuildButtons(); + }); + content->setPremiumFloodCallback([=] { + *premiumFlooded = true; + rebuildButtons(); + }); + content->setModeChangedCallback([=](ComposeAiMode mode) { + rebuildButtons(); + (*styleTooltipUpdater)(mode == ComposeAiMode::Style); + }); + content->setStyleSelectedCallback([=] { + rebuildButtons(); + if (!Core::App().settings().readPref(kAiComposeStyleTooltipHiddenPref)) { + Core::App().settings().writePref(kAiComposeStyleTooltipHiddenPref, true); + } + (*styleTooltipUpdater)(false); + }); + + rebuildButtons(); + content->start(); +} + +void ShowComposeAiBox( + std::shared_ptr show, + ComposeAiBoxArgs &&args) { + show->show(Box(ComposeAiBox, std::move(args))); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/boxes/compose_ai_box.h b/Telegram/SourceFiles/boxes/compose_ai_box.h new file mode 100644 index 00000000000000..f7e23ac0b6da7a --- /dev/null +++ b/Telegram/SourceFiles/boxes/compose_ai_box.h @@ -0,0 +1,39 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "api/api_common.h" +#include "base/object_ptr.h" +#include "ui/text/text_entity.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class ChatStyle; +class GenericBox; +class RpWidget; +class Show; +} // namespace Ui + +namespace HistoryView::Controls { + +struct ComposeAiBoxArgs { + not_null session; + TextWithEntities text; + std::shared_ptr chatStyle; + Fn apply; + Fn)> send; + Fn, Fn)> setupMenu; +}; + +void ComposeAiBox(not_null box, ComposeAiBoxArgs &&args); +void ShowComposeAiBox(std::shared_ptr show, ComposeAiBoxArgs &&args); + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 621fd2c41e25cb..6d99e31da3e112 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -8,17 +8,25 @@ For license and copyright information please follow this link: #include "boxes/connection_box.h" #include "base/call_delayed.h" +#include "base/qt/qt_key_modifiers.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" +#include "base/weak_ptr.h" #include "core/application.h" #include "core/core_settings.h" #include "core/local_url_handlers.h" #include "lang/lang_keys.h" #include "main/main_account.h" +#include "main/main_session.h" #include "mtproto/facade.h" +#include "mtproto/mtproto_config.h" +#include "mtproto/proxy_check.h" +#include "qr/qr_generate.h" +#include "settings/settings_common.h" #include "storage/localstorage.h" #include "ui/basic_click_handlers.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/peer_qr_box.h" #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" #include "ui/painter.h" @@ -28,25 +36,32 @@ For license and copyright information please follow this link: #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" +#include "ui/wrap/table_layout.h" #include "ui/wrap/vertical_layout.h" #include "ui/vertical_list.h" #include "ui/ui_utility.h" #include "boxes/abstract_box.h" // Ui::show(). #include "window/window_session_controller.h" -#include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" +#include "styles/style_intro.h" +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" +#include "styles/style_settings.h" #include #include +#include namespace { @@ -54,6 +69,254 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000); using ProxyData = MTP::ProxyData; +[[nodiscard]] int ClosestProxyRotationTimeoutSection(int value) { + auto result = 0; + auto bestDistance = 0; + for (auto i = 0; i != int(Core::SettingsProxy::kProxyRotationTimeouts.size()); ++i) { + const auto current = Core::SettingsProxy::kProxyRotationTimeouts[i]; + const auto distance = (current > value) ? (current - value) : (value - current); + if ((i == 0) || (distance < bestDistance)) { + result = i; + bestDistance = distance; + } + } + return result; +} + +[[nodiscard]] std::vector ExtractLinkCandidates(const QString &input) { + auto urls = std::vector(); + static const auto urlRegex = QRegularExpression( + R"((?:https?:\/\/[^\s]+|tg:\/\/[^\s]+|(?:www\.)?(?:t\.me|telegram\.me|telegram\.dog)\/[^\s]+))", + QRegularExpression::CaseInsensitiveOption); + + auto it = urlRegex.globalMatch(input); + while (it.hasNext()) { + urls.push_back(it.next().captured(0)); + } + + return urls; +} + +[[nodiscard]] bool ProxyDataIsShareable(const ProxyData &proxy) { + using Type = ProxyData::Type; + return (proxy.type == Type::Socks5) + || (proxy.type == Type::Mtproto); +} + +[[nodiscard]] QString ProxyDataToQueryPath(const ProxyData &proxy) { + using Type = ProxyData::Type; + const auto path = [&] { + switch (proxy.type) { + case Type::Socks5: return u"socks"_q; + case Type::Mtproto: return u"proxy"_q; + case Type::None: + case Type::Http: return QString(); + } + Unexpected("Proxy type in ProxyDataToQueryPath."); + }(); + if (path.isEmpty()) { + return QString(); + } + return path + + "?server=" + proxy.host + "&port=" + QString::number(proxy.port) + + ((proxy.type == Type::Socks5 && !proxy.user.isEmpty()) + ? "&user=" + qthelp::url_encode(proxy.user) : "") + + ((proxy.type == Type::Socks5 && !proxy.password.isEmpty()) + ? "&pass=" + qthelp::url_encode(proxy.password) : "") + + ((proxy.type == Type::Mtproto && !proxy.password.isEmpty()) + ? "&secret=" + proxy.password : ""); +} + +[[nodiscard]] QString ProxyDataToLocalLink(const ProxyData &proxy) { + const auto queryPath = ProxyDataToQueryPath(proxy); + return queryPath.isEmpty() ? QString() : (u"tg://"_q + queryPath); +} + +[[nodiscard]] QString ProxyDataToPublicLink( + not_null account, + const ProxyData &proxy) { + const auto queryPath = ProxyDataToQueryPath(proxy); + if (queryPath.isEmpty()) { + return QString(); + } + if (const auto session = account->maybeSession()) { + return session->createInternalLinkFull(queryPath); + } + auto domain = MTP::ConfigFields( + account->mtp().environment() + ).internalLinksDomain; + if (domain.endsWith('/') && queryPath.startsWith('/')) { + domain.chop(1); + } else if (!domain.endsWith('/') && !queryPath.startsWith('/')) { + domain += '/'; + } + return domain + queryPath; +} + +[[nodiscard]] QColor ProxyQrActiveColor() { + return QColor(0x40, 0xA7, 0xE3); +} + +[[nodiscard]] QImage ProxyQr(const Qr::Data &data, int pixel, int max = 0) { + Expects(data.size > 0); + + const auto available = max + ? std::max(max - 2 * st::introQrBackgroundSkip, 1) + : 0; + if (available > 0 && data.size * pixel > available) { + pixel = std::max(available / data.size, 1); + } + return Qr::Generate( + data, + pixel * style::DevicePixelRatio(), + Qt::black, + Qt::white); +} + +[[nodiscard]] QImage ProxyQrLogo() { + const auto size = QSize(st::introQrCenterSize, st::introQrCenterSize); + auto result = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + { + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(ProxyQrActiveColor()); + p.setPen(Qt::NoPen); + p.drawEllipse(QRect(QPoint(), size)); + st::introQrPlane.paintInCenter(p, QRect(QPoint(), size)); + } + return result; +} + +[[nodiscard]] QImage ProxyQrTile(const QString &link, int max = 0) { + const auto data = Qr::Encode(link, Qr::Redundancy::Quartile); + const auto qr = ProxyQr( + data, + st::introQrPixel, + max); + const auto qrSize = qr.width() / style::DevicePixelRatio(); + const auto skip = st::introQrBackgroundSkip; + const auto size = QSize(qrSize + 2 * skip, qrSize + 2 * skip); + auto result = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + { + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + p.drawRoundedRect( + QRect(QPoint(), size), + st::introQrBackgroundRadius, + st::introQrBackgroundRadius); + p.drawImage(QRect(skip, skip, qrSize, qrSize), qr); + const auto logo = ProxyQrLogo(); + p.drawImage( + QRect( + (size.width() - st::introQrCenterSize) / 2, + (size.height() - st::introQrCenterSize) / 2, + st::introQrCenterSize, + st::introQrCenterSize), + logo); + } + return result; +} + +[[nodiscard]] QImage ProxyQrForShare(const QString &link) { + return ProxyQrTile( + link, + st::boxWidth - st::boxRowPadding.left() - st::boxRowPadding.right()); +} + +void ShowProxyQrBox(std::shared_ptr show, const QString &link) { + show->showBox(Box([=](not_null box) { + box->setTitle(tr::lng_proxy_edit_share_qr_box_title()); + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); + + const auto copyCallback = [=, image = ProxyQrForShare(link)] { + QGuiApplication::clipboard()->setImage(image); + show->showToast({ + .text = { tr::lng_group_invite_qr_copied(tr::now) }, + .iconLottie = u"toast/copy"_q, + .iconLottieSize = st::toastLottieIconSize, + }); + }; + + const auto qr = ProxyQrTile( + link, + st::boxWidth + - st::boxRowPadding.left() + - st::boxRowPadding.right()); + const auto size = qr.width() / style::DevicePixelRatio(); + const auto height = st::inviteLinkQrSkip * 2 + size; + const auto container = box->addRow( + object_ptr(box, height), + st::inviteLinkQrMargin); + const auto button = Ui::CreateChild(container); + button->resize(size, size); + button->paintRequest( + ) | rpl::on_next([=] { + QPainter(button).drawImage(QRect(0, 0, size, size), qr); + }, button->lifetime()); + container->widthValue( + ) | rpl::on_next([=](int width) { + button->move((width - size) / 2, st::inviteLinkQrSkip); + }, button->lifetime()); + button->setClickedCallback(copyCallback); + + box->addLeftButton(tr::lng_group_invite_context_copy(), copyCallback); + })); +} + +void ShareProxy( + std::shared_ptr show, + not_null account, + const ProxyData &proxy, + bool qr) { + if (!ProxyDataIsShareable(proxy)) { + return; + } + const auto qrLink = ProxyDataToLocalLink(proxy); + if (qrLink.isEmpty()) { + return; + } + if (qr) { + if (account->sessionExists()) { + show->showBox(Box([=](not_null box) { + Ui::FillPeerQrBox( + box, + nullptr, + qrLink, + rpl::single(QString())); + box->setTitle(tr::lng_proxy_edit_share_qr_box_title()); + })); + } else { + ShowProxyQrBox(show, qrLink); + } + return; + } + const auto internal = base::IsCtrlPressed() + || base::IsAltPressed() + || base::IsShiftPressed(); + const auto shareLink = internal + ? qrLink + : ProxyDataToPublicLink(account, proxy); + if (shareLink.isEmpty()) { + return; + } + TextUtilities::SetClipboardText(TextForMimeData::Simple(shareLink)); + show->showToast({ + .text = { tr::lng_username_copied(tr::now) }, + .iconLottie = u"toast/voip_invite"_q, + .iconLottieSize = st::toastLottieIconSize, + }); +} + [[nodiscard]] ProxyData ProxyDataFromFields( ProxyData::Type type, const QMap &fields) { @@ -70,6 +333,131 @@ using ProxyData = MTP::ProxyData; return proxy; }; +[[nodiscard]] ProxyData ProxyDataFromLocalUrl(const QString &local) { + const auto protocol = u"tg://"_q; + const auto proxyString = u"proxy"_q; + const auto socksString = u"socks"_q; + if (!local.startsWith(protocol + proxyString, Qt::CaseInsensitive) + && !local.startsWith(protocol + socksString, Qt::CaseInsensitive)) { + return ProxyData(); + } + const auto command = base::StringViewMid(local, protocol.size(), 8192); + using namespace qthelp; + const auto options = RegExOption::CaseInsensitive; + for (const auto &[expression, _] : Core::LocalUrlHandlers()) { + const auto midExpression = base::StringViewMid(expression, 1); + const auto isSocks = midExpression.startsWith(socksString); + if (!midExpression.startsWith(proxyString) && !isSocks) { + continue; + } + const auto match = regex_match(expression, command, options); + if (!match) { + continue; + } + const auto type = isSocks + ? ProxyData::Type::Socks5 + : ProxyData::Type::Mtproto; + auto fields = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + if (type == ProxyData::Type::Mtproto) { + auto &secret = fields[u"secret"_q]; + secret.replace('+', '-').replace('/', '_'); + } + return ProxyDataFromFields(type, fields); + } + return ProxyData(); +} + +[[nodiscard]] ProxyData ProxyDataFromClipboard() { + const auto candidates = ExtractLinkCandidates( + QGuiApplication::clipboard()->text()); + if (candidates.size() != 1) { + return ProxyData(); + } + const auto trimmed = candidates.front().trimmed(); + const auto converted = Core::TryConvertUrlToLocal(trimmed); + const auto local = converted.isEmpty() ? trimmed : converted; + const auto proxy = ProxyDataFromLocalUrl(local); + return proxy.valid() ? proxy : ProxyData(); +} + +void AddProxyFromClipboard( + not_null controller, + std::shared_ptr show) { + const auto proxyString = u"proxy"_q; + const auto socksString = u"socks"_q; + const auto protocol = u"tg://"_q; + + const auto maybeUrls = ExtractLinkCandidates( + QGuiApplication::clipboard()->text()); + const auto isSingle = maybeUrls.size() == 1; + + enum class Result { + Success, + Failed, + Unsupported, + IncorrectSecret, + Invalid, + }; + + const auto proceedUrl = [=](const QString &local) { + const auto isProxyLink + = local.startsWith(protocol + proxyString, Qt::CaseInsensitive) + || local.startsWith(protocol + socksString, Qt::CaseInsensitive); + if (!isProxyLink) { + return Result::Failed; + } + const auto proxy = ProxyDataFromLocalUrl(local); + if (proxy.type == ProxyData::Type::None) { + return Result::Success; + } else if (!proxy) { + const auto status = proxy.status(); + return (status == ProxyData::Status::Unsupported) + ? Result::Unsupported + : (status == ProxyData::Status::IncorrectSecret) + ? Result::IncorrectSecret + : Result::Invalid; + } + const auto contains = controller->contains(proxy); + const auto toast = (contains + ? tr::lng_proxy_add_from_clipboard_existing_toast + : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now); + if (isSingle) { + show->showToast(toast); + } + if (!contains) { + controller->addNewItem(proxy); + } + return Result::Success; + }; + + auto success = Result::Failed; + for (const auto &maybeUrl : maybeUrls) { + const auto trimmed = maybeUrl.trimmed(); + const auto local = Core::TryConvertUrlToLocal(trimmed); + const auto check = local.isEmpty() ? trimmed : local; + const auto result = proceedUrl(check); + if (success != Result::Success) { + success = result; + } + } + + if (success != Result::Success) { + if (success == Result::Failed) { + show->showToast( + tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); + } else { + show->showBox(Ui::MakeInformBox( + ((success == Result::IncorrectSecret) + ? tr::lng_proxy_incorrect_secret(tr::now, tr::rich) + : (success == Result::Unsupported) + ? tr::lng_proxy_unsupported(tr::now, tr::rich) + : tr::lng_proxy_invalid(tr::now, tr::rich)))); + } + } +} + class HostInput : public Ui::MaskedInputField { public: HostInput( @@ -177,6 +565,7 @@ class ProxyRow : public Ui::RippleButton { rpl::producer<> restoreClicks() const; rpl::producer<> editClicks() const; rpl::producer<> shareClicks() const; + rpl::producer<> showQrClicks() const; protected: int resizeGetHeight(int newWidth) override; @@ -198,6 +587,7 @@ class ProxyRow : public Ui::RippleButton { rpl::event_stream<> _restoreClicks; rpl::event_stream<> _editClicks; rpl::event_stream<> _shareClicks; + rpl::event_stream<> _showQrClicks; base::unique_qptr _menu; bool _set = false; @@ -218,10 +608,13 @@ class ProxiesBox : public Ui::BoxContent { ProxiesBox( QWidget*, not_null controller, - Core::SettingsProxy &settings); + Core::SettingsProxy &settings, + const QString &highlightId = QString()); protected: void prepare() override; + void showFinished() override; + void keyPressEvent(QKeyEvent *e) override; private: void setupContent(); @@ -232,12 +625,16 @@ class ProxiesBox : public Ui::BoxContent { void setupButtons(int id, not_null button); int rowHeight() const; void refreshProxyForCalls(); + void refreshProxyRotation(); not_null _controller; Core::SettingsProxy &_settings; QPointer _tryIPv6; std::shared_ptr> _proxySettings; QPointer> _proxyForCalls; + QPointer> _proxyRotation; + QPointer> _proxyRotationOptions; + QPointer _proxyRotationTimeout; QPointer _about; base::unique_qptr _noRows; object_ptr _initialWrap; @@ -246,6 +643,10 @@ class ProxiesBox : public Ui::BoxContent { base::flat_map> _rows; + QPointer _addProxyButton; + QPointer _shareListButton; + QString _highlightId; + }; class ProxyBox final : public Ui::BoxContent { @@ -278,6 +679,7 @@ class ProxyBox final : public Ui::BoxContent { not_null parent, const QString &text) const; + const bool _allowShare = false; Fn _callback; Fn _shareCallback; @@ -319,6 +721,10 @@ rpl::producer<> ProxyRow::shareClicks() const { return _shareClicks.events(); } +rpl::producer<> ProxyRow::showQrClicks() const { + return _showQrClicks.events(); +} + void ProxyRow::setupControls(View &&view) { updateFields(std::move(view)); _toggled.stop(); @@ -346,7 +752,7 @@ void ProxyRow::updateFields(View &&view) { TextWithEntities() .append(_view.type) .append(' ') - .append(Ui::Text::Link(endpoint, QString())), + .append(tr::link(endpoint, QString())), Ui::ItemTextDefaultOptions()); const auto state = _view.state; @@ -563,6 +969,9 @@ void ProxyRow::showMenu() { addAction(tr::lng_proxy_edit_share(tr::now), [=] { _shareClicks.fire({}); }, &st::menuIconShare); + addAction(tr::lng_group_invite_context_qr(tr::now), [=] { + _showQrClicks.fire({}); + }, &st::menuIconQrCode); } if (_view.deleted) { addAction(tr::lng_proxy_menu_restore(tr::now), [=] { @@ -607,92 +1016,90 @@ void ProxyRow::showMenu() { ProxiesBox::ProxiesBox( QWidget*, not_null controller, - Core::SettingsProxy &settings) + Core::SettingsProxy &settings, + const QString &highlightId) : _controller(controller) , _settings(settings) -, _initialWrap(this) { +, _initialWrap(this) +, _highlightId(highlightId) { _controller->views( - ) | rpl::start_with_next([=](View &&view) { + ) | rpl::on_next([=](View &&view) { applyView(std::move(view)); }, lifetime()); } +void ProxiesBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Copy + || (e->key() == Qt::Key_C && e->modifiers() == Qt::ControlModifier)) { + _controller->shareItems(); + } else if (e->key() == Qt::Key_Paste + || (e->key() == Qt::Key_V && e->modifiers() == Qt::ControlModifier)) { + AddProxyFromClipboard(_controller, uiShow()); + } else { + BoxContent::keyPressEvent(e); + } +} + void ProxiesBox::prepare() { setTitle(tr::lng_proxy_settings()); - addButton(tr::lng_proxy_add(), [=] { addNewProxy(); }); + _addProxyButton = addButton(tr::lng_proxy_add(), [=] { addNewProxy(); }); addButton(tr::lng_close(), [=] { closeBox(); }); setupTopButton(); setupContent(); } +void ProxiesBox::showFinished() { + if (_highlightId == u"proxy/add-proxy"_q) { + if (_addProxyButton) { + _highlightId = QString(); + Settings::HighlightWidget( + _addProxyButton, + { .rippleShape = true }); + } + } else if (_highlightId == u"proxy/share-list"_q) { + if (_shareListButton) { + _highlightId = QString(); + Settings::HighlightWidget(_shareListButton); + } + } +} + void ProxiesBox::setupTopButton() { const auto top = addTopButton(st::infoTopBarMenu); const auto menu = top->lifetime().make_state>(); - const auto callback = [=] { - const auto maybeUrl = QGuiApplication::clipboard()->text(); - const auto local = Core::TryConvertUrlToLocal(maybeUrl); - - const auto proxyString = u"proxy"_q; - const auto socksString = u"socks"_q; - const auto protocol = u"tg://"_q; - const auto command = base::StringViewMid( - local, - protocol.size(), - 8192); - - if (local.startsWith(protocol + proxyString) - || local.startsWith(protocol + socksString)) { - - using namespace qthelp; - const auto options = RegExOption::CaseInsensitive; - for (const auto &[expression, _] : Core::LocalUrlHandlers()) { - const auto midExpression = base::StringViewMid( - expression, - 1); - const auto isSocks = midExpression.startsWith( - socksString); - if (!midExpression.startsWith(proxyString) - && !isSocks) { - continue; - } - const auto match = regex_match( - expression, - command, - options); - if (!match) { - continue; - } - const auto type = isSocks - ? ProxyData::Type::Socks5 - : ProxyData::Type::Mtproto; - const auto fields = url_parse_params( - match->captured(1), - qthelp::UrlParamNameTransform::ToLower); - const auto proxy = ProxyDataFromFields(type, fields); - const auto contains = _controller->contains(proxy); - const auto toast = (contains - ? tr::lng_proxy_add_from_clipboard_existing_toast - : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now); - uiShow()->showToast(toast); - if (!contains) { - _controller->addNewItem(proxy); - } - break; - } - } else { - uiShow()->showToast( - tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); - } - }; + top->setClickedCallback([=] { - *menu = base::make_unique_q(top, st::defaultPopupMenu); - (*menu)->addAction( - tr::lng_proxy_add_from_clipboard(tr::now), - callback); - (*menu)->popup(QCursor::pos()); + *menu = base::make_unique_q( + top, + st::popupMenuWithIcons); + const auto raw = menu->get(); + const auto addAction = Ui::Menu::CreateAddActionCallback(raw); + addAction({ + .text = tr::lng_proxy_add_from_clipboard(tr::now), + .handler = [=] { AddProxyFromClipboard(_controller, uiShow()); }, + .icon = &st::menuIconImportTheme, + }); + if (!_rows.empty()) { + addAction({ + .text = tr::lng_group_invite_context_delete_all(tr::now), + .handler = [=] { _controller->deleteItems(); }, + .icon = &st::menuIconDeleteAttention, + .isAttention = true, + }); + } + raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); + top->setForceRippled(true); + raw->setDestroyedCallback([=] { + if (const auto strong = top.data()) { + strong->setForceRippled(false); + } + }); + raw->popup( + top->mapToGlobal( + QPoint(top->width(), top->height() - st::lineWidth * 3))); return true; }); } @@ -747,6 +1154,47 @@ void ProxiesBox::setupContent() { 0, st::proxyTryIPv6Padding.right(), st::proxyTryIPv6Padding.top())); + _proxyRotation = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + tr::lng_proxy_auto_switch(tr::now), + _settings.proxyRotationEnabled()), + style::margins( + 0, + st::proxyUsePadding.top(), + 0, + st::proxyUsePadding.bottom())), + style::margins( + st::proxyTryIPv6Padding.left(), + 0, + st::proxyTryIPv6Padding.right(), + st::proxyTryIPv6Padding.top())); + _proxyRotationOptions = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + _proxyRotationTimeout = _proxyRotationOptions->entity()->add( + object_ptr( + _proxyRotationOptions->entity(), + st::settingsSlider), + st::settingsBigScalePadding); + for (const auto seconds : Core::SettingsProxy::kProxyRotationTimeouts) { + _proxyRotationTimeout->addSection( + tr::lng_proxy_auto_switch_timeout( + tr::now, + lt_count, + seconds)); + } + _proxyRotationTimeout->setActiveSectionFast( + ClosestProxyRotationTimeoutSection(_settings.proxyRotationTimeout())); + _proxyRotationOptions->entity()->add( + object_ptr( + _proxyRotationOptions->entity(), + tr::lng_proxy_auto_switch_about(tr::now), + st::boxDividerLabel), + st::proxyAboutPadding); _about = inner->add( object_ptr( @@ -769,27 +1217,61 @@ void ProxiesBox::setupContent() { addNewProxy(); } refreshProxyForCalls(); + refreshProxyRotation(); }); _tryIPv6->checkedChanges( - ) | rpl::start_with_next([=](bool checked) { + ) | rpl::on_next([=](bool checked) { _controller->setTryIPv6(checked); }, _tryIPv6->lifetime()); _controller->proxySettingsValue( - ) | rpl::start_with_next([=](ProxyData::Settings value) { + ) | rpl::on_next([=](ProxyData::Settings value) { _proxySettings->setValue(value); + refreshProxyForCalls(); + refreshProxyRotation(); }, inner->lifetime()); _proxyForCalls->entity()->checkedChanges( - ) | rpl::start_with_next([=](bool checked) { + ) | rpl::on_next([=](bool checked) { _controller->setProxyForCalls(checked); }, _proxyForCalls->lifetime()); + _proxyRotation->entity()->checkedChanges( + ) | rpl::on_next([=](bool checked) { + _controller->setProxyRotationEnabled(checked); + refreshProxyRotation(); + }, _proxyRotation->lifetime()); + _proxyRotationTimeout->sectionActivated( + ) | rpl::on_next([=](int section) { + _controller->setProxyRotationTimeout( + Core::SettingsProxy::kProxyRotationTimeouts[section]); + }, _proxyRotationTimeout->lifetime()); if (_rows.empty()) { createNoRowsLabel(); } refreshProxyForCalls(); + refreshProxyRotation(); _proxyForCalls->finishAnimating(); + _proxyRotation->finishAnimating(); + _proxyRotationOptions->finishAnimating(); + + { + const auto wrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto shareList = Settings::AddButtonWithIcon( + wrap->entity(), + tr::lng_proxy_edit_share_list_button(), + st::settingsButton, + { &st::menuIconCopy }); + _shareListButton = shareList; + shareList->setClickedCallback([=] { + _controller->shareItems(); + }); + wrap->toggleOn(_controller->listShareableChanges()); + wrap->finishAnimating(); + } inner->resizeToWidth(st::boxWideWidth); @@ -801,7 +1283,7 @@ void ProxiesBox::setupContent() { + 3 * rowHeight()), st::boxMaxListHeight); }) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=](int height) { + ) | rpl::on_next([=](int height) { setDimensions(st::boxWideWidth, height); }, inner->lifetime()); } @@ -816,6 +1298,20 @@ void ProxiesBox::refreshProxyForCalls() { anim::type::normal); } +void ProxiesBox::refreshProxyRotation() { + if (!_proxyRotation || !_proxyRotationOptions) { + return; + } + const auto visible = (_proxySettings->current() + == ProxyData::Settings::Enabled) + && _settings.selected() + && (_settings.list().size() > 1); + _proxyRotation->toggle(visible, anim::type::normal); + _proxyRotationOptions->toggle( + visible && _proxyRotation->entity()->checked(), + anim::type::normal); +} + int ProxiesBox::rowHeight() const { return st::proxyRowPadding.top() + st::semiboldFont->height @@ -858,6 +1354,7 @@ void ProxiesBox::applyView(View &&view) { } else { i->second->updateFields(std::move(view)); } + refreshProxyRotation(); } void ProxiesBox::createNoRowsLabel() { @@ -876,7 +1373,7 @@ void ProxiesBox::createNoRowsLabel() { tr::lng_proxy_description(tr::now), st::proxyEmptyListLabel); _noRows->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { label->resizeToWidth(width); label->moveToLeft(0, 0); }, label->lifetime()); @@ -884,27 +1381,29 @@ void ProxiesBox::createNoRowsLabel() { void ProxiesBox::setupButtons(int id, not_null button) { button->deleteClicks( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _controller->deleteItem(id); }, button->lifetime()); button->restoreClicks( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _controller->restoreItem(id); }, button->lifetime()); button->editClicks( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { getDelegate()->show(_controller->editItemBox(id)); }, button->lifetime()); - button->shareClicks( - ) | rpl::start_with_next([=] { - _controller->shareItem(id); + rpl::merge( + button->shareClicks() | rpl::map_to(false), + button->showQrClicks() | rpl::map_to(true) + ) | rpl::on_next([=](bool qr) { + _controller->shareItem(id, qr); }, button->lifetime()); button->clicks( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _controller->applyItem(id); }, button->lifetime()); } @@ -914,7 +1413,8 @@ ProxyBox::ProxyBox( const ProxyData &data, Fn callback, Fn shareCallback) -: _callback(std::move(callback)) +: _allowShare(data.type != Type::None) +, _callback(std::move(callback)) , _shareCallback(std::move(shareCallback)) , _content(this) { setupControls(data); @@ -940,7 +1440,7 @@ void ProxyBox::prepare() { }); }); _port.data()->events( - ) | rpl::start_with_next([=](not_null e) { + ) | rpl::on_next([=](not_null e) { if (e->type() == QEvent::KeyPress && (static_cast(e.get())->key() == Qt::Key_Backspace) && _port->cursorPosition() == 0) { @@ -949,6 +1449,30 @@ void ProxyBox::prepare() { } }, _port->lifetime()); + const auto submit = [=] { + if (_host->hasFocus() + && !_host->getLastText().trimmed().isEmpty()) { + _port->setFocus(); + } else if (_port->hasFocus() + && !_port->getLastText().trimmed().isEmpty()) { + if (_type->current() == Type::Mtproto) { + _secret->setFocus(); + } else { + _user->setFocus(); + } + } else if (_user->hasFocus()) { + _password->setFocus(); + } else { + save(); + } + }; + connect(_host.data(), &Ui::MaskedInputField::submitted, submit); + connect(_port.data(), &Ui::MaskedInputField::submitted, submit); + _user->submits( + ) | rpl::on_next(submit, _user->lifetime()); + connect(_password.data(), &Ui::MaskedInputField::submitted, submit); + connect(_secret.data(), &Ui::MaskedInputField::submitted, submit); + refreshButtons(); setDimensionsToContent(st::boxWideWidth, _content); } @@ -959,7 +1483,8 @@ void ProxyBox::refreshButtons() { addButton(tr::lng_cancel(), [=] { closeBox(); }); const auto type = _type->current(); - if (type == Type::Socks5 || type == Type::Mtproto) { + if (_allowShare + && (type == Type::Socks5 || type == Type::Mtproto)) { addLeftButton(tr::lng_proxy_share(), [=] { share(); }); } } @@ -1006,10 +1531,10 @@ ProxyData ProxyBox::collectData() { } void ProxyBox::setupTypes() { - const auto types = std::map{ - { Type::Http, "HTTP" }, - { Type::Socks5, "SOCKS5" }, - { Type::Mtproto, "MTPROTO" }, + const auto types = std::vector>{ + { Type::Mtproto, u"MTPROTO"_q }, + { Type::Socks5, u"SOCKS5"_q }, + { Type::Http, u"HTTP"_q }, }; for (const auto &[type, label] : types) { _content->add( @@ -1050,7 +1575,7 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) { data.port ? QString::number(data.port) : QString(), 65535); address->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { _port->moveToRight(0, 0); _host->resize( width - _port->width() - st::proxyEditSkip, @@ -1060,7 +1585,7 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) { } void ProxyBox::setupCredentials(const ProxyData &data) { - _credentials = _content->add( + _credentials = _content->add( object_ptr>( _content, object_ptr(_content))); @@ -1082,11 +1607,11 @@ void ProxyBox::setupCredentials(const ProxyData &data) { (data.type == Type::Mtproto) ? QString() : data.password); _password->move(0, 0); _password->heightValue( - ) | rpl::start_with_next([=, wrap = passwordWrap.data()](int height) { + ) | rpl::on_next([=, wrap = passwordWrap.data()](int height) { wrap->resize(wrap->width(), height); }, _password->lifetime()); passwordWrap->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { _password->resize(width, _password->height()); }, _password->lifetime()); credentials->add(std::move(passwordWrap), st::proxyEditInputPadding); @@ -1108,11 +1633,11 @@ void ProxyBox::setupMtprotoCredentials(const ProxyData &data) { (data.type == Type::Mtproto) ? data.password : QString()); _secret->move(0, 0); _secret->heightValue( - ) | rpl::start_with_next([=, wrap = secretWrap.data()](int height) { + ) | rpl::on_next([=, wrap = secretWrap.data()](int height) { wrap->resize(wrap->width(), height); }, _secret->lifetime()); secretWrap->widthValue( - ) | rpl::start_with_next([=](int width) { + ) | rpl::on_next([=](int width) { _secret->resize(width, _secret->height()); }, _secret->lifetime()); mtproto->add(std::move(secretWrap), st::proxyEditInputPadding); @@ -1121,7 +1646,7 @@ void ProxyBox::setupMtprotoCredentials(const ProxyData &data) { void ProxyBox::setupControls(const ProxyData &data) { _type = std::make_shared>( (data.type == Type::None - ? Type::Socks5 + ? Type::Mtproto : data.type)); _content.create(this); _content->resizeToWidth(st::boxWideWidth); @@ -1133,15 +1658,19 @@ void ProxyBox::setupControls(const ProxyData &data) { setupMtprotoCredentials(data); const auto handleType = [=](Type type) { - _credentials->toggle( - type == Type::Http || type == Type::Socks5, - anim::type::instant); - _mtprotoCredentials->toggle( - type == Type::Mtproto, - anim::type::instant); - _aboutSponsored->toggle( - type == Type::Mtproto, - anim::type::instant); + const auto credentialsShown + = (type == Type::Http || type == Type::Socks5); + const auto mtprotoShown = (type == Type::Mtproto); + _credentials->toggle(credentialsShown, anim::type::instant); + _mtprotoCredentials->toggle(mtprotoShown, anim::type::instant); + _aboutSponsored->toggle(mtprotoShown, anim::type::instant); + const auto credentialsPolicy = credentialsShown + ? Qt::StrongFocus + : Qt::NoFocus; + _user->rawTextEdit()->setFocusPolicy(credentialsPolicy); + _password->setFocusPolicy(credentialsPolicy); + _secret->setFocusPolicy( + mtprotoShown ? Qt::StrongFocus : Qt::NoFocus); }; _type->setChangedCallback([=](Type type) { handleType(type); @@ -1161,6 +1690,9 @@ void ProxyBox::addLabel( st::proxyEditTitlePadding); } +using Connection = MTP::details::AbstractConnection; +using Checker = MTP::ProxyCheckConnection; + } // namespace ProxiesBoxController::ProxiesBoxController(not_null account) @@ -1174,7 +1706,7 @@ ProxiesBoxController::ProxiesBoxController(not_null account) }) | ranges::to_vector; _settings.connectionTypeChanges( - ) | rpl::start_with_next([=] { + ) | rpl::on_next([=] { _proxySettingsChanges.fire_copy(_settings.settings()); const auto i = findByProxy(_settings.selected()); if (i != end(_list)) { @@ -1193,10 +1725,13 @@ void ProxiesBoxController::ShowApplyConfirmation( const QMap &fields) { const auto proxy = ProxyDataFromFields(type, fields); if (!proxy) { + const auto status = proxy.status(); auto box = Ui::MakeInformBox( - (proxy.status() == ProxyData::Status::Unsupported - ? tr::lng_proxy_unsupported(tr::now) - : tr::lng_proxy_invalid(tr::now))); + ((status == ProxyData::Status::Unsupported) + ? tr::lng_proxy_unsupported(tr::now, tr::rich) + : (status == ProxyData::Status::IncorrectSecret) + ? tr::lng_proxy_incorrect_secret(tr::now, tr::rich) + : tr::lng_proxy_invalid(tr::now, tr::rich))); if (controller) { controller->uiShow()->showBox(std::move(box)); } else { @@ -1222,45 +1757,237 @@ void ProxiesBoxController::ShowApplyConfirmation( QString() ).replace(UrlEndRegExp, QString()); const auto box = [=](not_null box) { - box->setTitle(tr::lng_proxy_box_title()); - if (type == Type::Mtproto) { - box->addRow(object_ptr( - box, - tr::lng_proxy_sponsor_warning(), - st::boxDividerLabel)); - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); + box->setTitle(tr::lng_proxy_box_table_title()); + box->setStyle(st::proxyApplyBox); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + if (ProxyDataIsShareable(proxy)) { + const auto account = controller + ? &controller->session().account() + : &Core::App().activeAccount(); + const auto top = box->addTopButton(st::boxTitleMenu); + const auto menu = top->lifetime().make_state< + base::unique_qptr>(); + top->setClickedCallback([=] { + *menu = base::make_unique_q( + top, + st::popupMenuWithIcons); + const auto raw = menu->get(); + const auto addAction = Ui::Menu::CreateAddActionCallback(raw); + addAction({ + .text = tr::lng_proxy_edit_share(tr::now), + .handler = [=] { + ShareProxy(box->uiShow(), account, proxy, false); + }, + .icon = &st::menuIconShare, + }); + addAction({ + .text = tr::lng_group_invite_context_qr(tr::now), + .handler = [=] { + ShareProxy(box->uiShow(), account, proxy, true); + }, + .icon = &st::menuIconQrCode, + }); + raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); + top->setForceRippled(true); + raw->setDestroyedCallback([=] { + if (const auto strong = top.data()) { + strong->setForceRippled(false); + } + }); + raw->popup(top->mapToGlobal(QPoint( + top->width(), + top->height() - st::lineWidth * 3))); + return true; + }); } - const auto &stL = st::proxyApplyBoxLabel; - const auto &stSubL = st::boxDividerLabel; - const auto add = [&](const QString &s, tr::phrase<> phrase) { - if (!s.isEmpty()) { - box->addRow(object_ptr(box, s, stL)); - box->addRow(object_ptr(box, phrase(), stSubL)); - Ui::AddSkip(box->verticalLayout()); - Ui::AddSkip(box->verticalLayout()); + + const auto table = box->addRow( + object_ptr( + box, + st::proxyApplyBoxTable), + st::proxyApplyBoxTableMargin); + const auto addRow = [&]( + rpl::producer label, + object_ptr value) { + table->addRow( + object_ptr( + table, + std::move(label), + table->st().defaultLabel), + std::move(value), + st::proxyApplyBoxTableLabelMargin, + st::proxyApplyBoxTableValueMargin); + }; + const auto add = [&]( + const QString &value, + rpl::producer label) { + if (!value.isEmpty()) { + constexpr auto kOneLineCount = 20; + const auto oneLine = value.length() <= kOneLineCount; + auto widget = object_ptr( + table, + rpl::single(Ui::Text::Wrapped( + { value }, + EntityType::Code, + {})), + (oneLine + ? table->st().defaultValue + : st::proxyApplyBoxValueMultiline), + st::defaultPopupMenu); + addRow(std::move(label), std::move(widget)); } }; if (!displayServer.isEmpty()) { - add(displayServer, tr::lng_proxy_box_server); + add(displayServer, tr::lng_proxy_box_server()); } - add(QString::number(proxy.port), tr::lng_proxy_box_port); + add(QString::number(proxy.port), tr::lng_proxy_box_port()); if (type == Type::Socks5) { - add(proxy.user, tr::lng_proxy_box_username); - add(proxy.password, tr::lng_proxy_box_password); + add(proxy.user, tr::lng_proxy_box_username()); + add(proxy.password, tr::lng_proxy_box_password()); } else if (type == Type::Mtproto) { - add(proxy.password, tr::lng_proxy_box_secret); + add(proxy.password, tr::lng_proxy_box_secret()); + } + + { + struct ProxyCheckStatusState { + Checker v4; + Checker v6; + rpl::variable statusValue; + bool finished = false; + }; + const auto state + = box->lifetime().make_state(); + state->statusValue = Ui::Text::Link( + tr::lng_proxy_box_check_status(tr::now)); + const auto weak = base::make_weak(box); + auto statusWidget = object_ptr( + table, + state->statusValue.value(), + table->st().defaultValue, + st::defaultPopupMenu); + const auto statusLabel = statusWidget.data(); + addRow(tr::lng_proxy_box_status(), std::move(statusWidget)); + const auto relayout = [=] { + table->resizeToWidth(table->width()); + }; + const auto setUnavailable = [=] { + state->statusValue = TextWithEntities{ + tr::lng_proxy_box_table_unavailable(tr::now), + }; + statusLabel->setTextColorOverride( + st::proxyRowStatusFgOffline->c); + relayout(); + }; + const auto runCheck = [=] { + if (!weak) { + return; + } + const auto account = controller + ? &controller->session().account() + : &Core::App().activeAccount(); + state->finished = false; + state->statusValue = TextWithEntities{ + tr::lng_proxy_box_table_checking(tr::now), + }; + statusLabel->setTextColorOverride(st::proxyRowStatusFg->c); + relayout(); + MTP::StartProxyCheck( + &account->mtp(), + proxy, + Core::App().settings().proxy().tryIPv6(), + state->v4, + state->v6, + [=](Connection *raw, int ping) { + if (!weak || state->finished) { + return; + } + MTP::DropProxyChecker(state->v4, state->v6, raw); + state->finished = true; + MTP::ResetProxyCheckers(state->v4, state->v6); + state->statusValue = TextWithEntities{ + tr::lng_proxy_box_table_available( + tr::now, + lt_ping, + QString::number(ping)), + }; + statusLabel->setTextColorOverride( + st::proxyRowStatusFgAvailable->c); + relayout(); + }, + [=](Connection *raw) { + if (!weak || state->finished) { + return; + } + MTP::DropProxyChecker(state->v4, state->v6, raw); + if (!MTP::HasProxyCheckers(state->v4, state->v6)) { + state->finished = true; + setUnavailable(); + } + }); + if (!MTP::HasProxyCheckers(state->v4, state->v6)) { + state->finished = true; + setUnavailable(); + } + }; + statusLabel->setClickHandlerFilter([=](const auto &...) { + auto &proxy = Core::App().settings().proxy(); + if (proxy.checkIpWarningShown()) { + runCheck(); + } else { + box->uiShow()->showBox(Ui::MakeConfirmBox({ + .text = tr::lng_proxy_check_ip_warning(), + .confirmed = [=](Fn close) { + auto &proxy = Core::App().settings().proxy(); + proxy.setCheckIpWarningShown(true); + Local::writeSettings(); + close(); + runCheck(); + }, + .confirmText = tr::lng_proxy_check_ip_proceed(), + .title = tr::lng_proxy_check_ip_warning_title(), + })); + } + return false; + }); } - box->addButton(tr::lng_sure_enable(), [=] { - auto &proxies = Core::App().settings().proxy().list(); - if (!ranges::contains(proxies, proxy)) { - proxies.push_back(proxy); + + if (type == Type::Mtproto) { + table->addRow( + object_ptr( + table, + tr::lng_proxy_sponsor_warning(), + st::proxyApplyBoxSponsorLabel), + object_ptr(nullptr), + st::proxyApplyBoxSponsorMargin, + st::proxyApplyBoxSponsorMargin); + } + + const auto enableButton = box->addButton( + tr::lng_proxy_box_table_button(), + [=] { + auto &settings = Core::App().settings().proxy(); + if (settings.indexInList(proxy) < 0) { + settings.addToList(proxy); + } + Core::App().setCurrentProxy( + proxy, + ProxyData::Settings::Enabled); + Local::writeSettings(); + box->closeBox(); + }); + enableButton->setFullRadius(true); + box->events() | rpl::on_next([=](not_null e) { + if ((e->type() != QEvent::KeyPress) || !enableButton) { + return; } - Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled); - Local::writeSettings(); - box->closeBox(); - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + const auto k = static_cast(e.get()); + if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) { + enableButton->clicked({}, Qt::LeftButton); + } + }, box->lifetime()); }; if (controller) { controller->uiShow()->showBox(Box(box)); @@ -1277,108 +2004,62 @@ auto ProxiesBoxController::proxySettingsValue() const } void ProxiesBoxController::refreshChecker(Item &item) { - using Variants = MTP::DcOptions::Variants; - const auto type = (item.data.type == Type::Http) - ? Variants::Http - : Variants::Tcp; - const auto mtproto = &_account->mtp(); - const auto dcId = mtproto->mainDcId(); - const auto forFiles = false; - item.state = ItemState::Checking; - const auto setup = [&](Checker &checker, const bytes::vector &secret) { - checker = MTP::details::AbstractConnection::Create( - mtproto, - type, - QThread::currentThread(), - secret, - item.data); - setupChecker(item.id, checker); - }; - if (item.data.type == Type::Mtproto) { - const auto secret = item.data.secretFromMtprotoPassword(); - setup(item.checker, secret); - item.checker->connectToServer( - item.data.host, - item.data.port, - secret, - dcId, - forFiles); - item.checkerv6 = nullptr; - } else { - const auto options = mtproto->dcOptions().lookup( - dcId, - MTP::DcType::Regular, - true); - const auto connect = [&]( - Checker &checker, - Variants::Address address) { - const auto &list = options.data[address][type]; - if (list.empty() - || ((address == Variants::IPv6) - && !Core::App().settings().proxy().tryIPv6())) { - checker = nullptr; + const auto id = item.id; + MTP::StartProxyCheck( + &_account->mtp(), + item.data, + Core::App().settings().proxy().tryIPv6(), + item.checker, + item.checkerv6, + [=](Connection *raw, int pingTime) { + const auto item = ranges::find( + _list, + id, + [](const Item &item) { return item.id; }); + if (item == end(_list)) { return; } - const auto &endpoint = list.front(); - setup(checker, endpoint.secret); - checker->connectToServer( - QString::fromStdString(endpoint.ip), - endpoint.port, - endpoint.secret, - dcId, - forFiles); - }; - connect(item.checker, Variants::IPv4); - connect(item.checkerv6, Variants::IPv6); - if (!item.checker && !item.checkerv6) { - item.state = ItemState::Unavailable; - } + MTP::DropProxyChecker(item->checker, item->checkerv6, raw); + MTP::ResetProxyCheckers(item->checker, item->checkerv6); + if (item->state == ItemState::Checking) { + item->state = ItemState::Available; + item->ping = pingTime; + updateView(*item); + } + }, + [=](Connection *raw) { + const auto item = ranges::find( + _list, + id, + [](const Item &item) { return item.id; }); + if (item == end(_list)) { + return; + } + MTP::DropProxyChecker(item->checker, item->checkerv6, raw); + if (!MTP::HasProxyCheckers(item->checker, item->checkerv6) + && item->state == ItemState::Checking) { + item->state = ItemState::Unavailable; + updateView(*item); + } + }); + if (!MTP::HasProxyCheckers(item.checker, item.checkerv6)) { + item.state = ItemState::Unavailable; } } -void ProxiesBoxController::setupChecker(int id, const Checker &checker) { - using Connection = MTP::details::AbstractConnection; - const auto pointer = checker.get(); - pointer->connect(pointer, &Connection::connected, [=] { - const auto item = findById(id); - const auto pingTime = pointer->pingTime(); - item->checker = nullptr; - item->checkerv6 = nullptr; - if (item->state == ItemState::Checking) { - item->state = ItemState::Available; - item->ping = pingTime; - updateView(*item); - } - }); - const auto failed = [=] { - const auto item = findById(id); - if (item->checker == pointer) { - item->checker = nullptr; - } else if (item->checkerv6 == pointer) { - item->checkerv6 = nullptr; - } - if (!item->checker - && !item->checkerv6 - && item->state == ItemState::Checking) { - item->state = ItemState::Unavailable; - updateView(*item); - } - }; - pointer->connect(pointer, &Connection::disconnected, failed); - pointer->connect(pointer, &Connection::error, failed); -} - object_ptr ProxiesBoxController::CreateOwningBox( - not_null account) { + not_null account, + const QString &highlightId) { auto controller = std::make_unique(account); - auto box = controller->create(); + auto box = controller->create(highlightId); Ui::AttachAsChild(box, std::move(controller)); return box; } -object_ptr ProxiesBoxController::create() { - auto result = Box(this, _settings); +object_ptr ProxiesBoxController::create( + const QString &highlightId) { + auto result = Box(this, _settings, highlightId); _show = result->uiShow(); for (const auto &item : _list) { updateView(item); @@ -1407,12 +2088,39 @@ void ProxiesBoxController::deleteItem(int id) { setDeleted(id, true); } +void ProxiesBoxController::deleteItems() { + for (const auto &item : _list) { + setDeleted(item.id, true); + } +} + void ProxiesBoxController::restoreItem(int id) { setDeleted(id, false); } -void ProxiesBoxController::shareItem(int id) { - share(findById(id)->data); +void ProxiesBoxController::shareItem(int id, bool qr) { + share(findById(id)->data, qr); +} + +void ProxiesBoxController::shareItems() { + auto result = QString(); + for (const auto &item : _list) { + if (!item.deleted && ProxyDataIsShareable(item.data)) { + const auto link = ProxyDataToPublicLink(_account, item.data); + if (!link.isEmpty()) { + result += (result.isEmpty() ? QString() : u"\n\n"_q) + link; + } + } + } + if (result.isEmpty()) { + return; + } + QGuiApplication::clipboard()->setText(result); + _show->showToast({ + .text = { tr::lng_proxy_edit_share_list_toast(tr::now) }, + .iconLottie = u"toast/copy"_q, + .iconLottieSize = st::toastLottieIconSize, + }); } void ProxiesBoxController::applyItem(int id) { @@ -1441,8 +2149,8 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) { item->deleted = deleted; if (deleted) { - auto &proxies = _settings.list(); - proxies.erase(ranges::remove(proxies, item->data), end(proxies)); + const auto removed = _settings.removeFromList(item->data); + Assert(removed); if (item->data == _settings.selected()) { _lastSelectedProxy = _settings.selected(); @@ -1458,16 +2166,19 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) { } } } else { - auto &proxies = _settings.list(); - if (ranges::find(proxies, item->data) == end(proxies)) { + if (_settings.indexInList(item->data) < 0) { + const auto &proxies = _settings.list(); auto insertBefore = item + 1; while (insertBefore != end(_list) && insertBefore->deleted) { ++insertBefore; } - auto insertBeforeIt = (insertBefore == end(_list)) - ? end(proxies) - : ranges::find(proxies, insertBefore->data); - proxies.insert(insertBeforeIt, item->data); + const auto foundIndex = (insertBefore == end(_list)) + ? int(proxies.size()) + : _settings.indexInList(insertBefore->data); + const auto insertIndex = (foundIndex >= 0) + ? foundIndex + : int(proxies.size()); + _settings.insertToList(insertIndex, item->data); } if (!_settings.selected() && _lastSelectedProxy == item->data) { @@ -1506,8 +2217,8 @@ object_ptr ProxiesBoxController::editItemBox(int id) { void ProxiesBoxController::replaceItemWith( std::vector::iterator which, std::vector::iterator with) { - auto &proxies = _settings.list(); - proxies.erase(ranges::remove(proxies, which->data), end(proxies)); + const auto removed = _settings.removeFromList(which->data); + Assert(removed); _views.fire({ which->id }); _list.erase(which); @@ -1526,10 +2237,8 @@ void ProxiesBoxController::replaceItemValue( restoreItem(which->id); } - auto &proxies = _settings.list(); - const auto i = ranges::find(proxies, which->data); - Assert(i != end(proxies)); - *i = proxy; + const auto replaced = _settings.replaceInList(which->data, proxy); + Assert(replaced); which->data = proxy; refreshChecker(*which); @@ -1538,7 +2247,8 @@ void ProxiesBoxController::replaceItemValue( } object_ptr ProxiesBoxController::addNewItemBox() { - return Box(ProxyData(), [=](const ProxyData &result) { + const auto fromClipboard = ProxyDataFromClipboard(); + return Box(fromClipboard, [=](const ProxyData &result) { auto j = ranges::find( _list, result, @@ -1565,8 +2275,7 @@ bool ProxiesBoxController::contains(const ProxyData &proxy) const { } void ProxiesBoxController::addNewItem(const ProxyData &proxy) { - auto &proxies = _settings.list(); - proxies.push_back(proxy); + _settings.addToList(proxy); _list.push_back({ ++_idCounter, proxy }); refreshChecker(_list.back()); @@ -1603,6 +2312,22 @@ void ProxiesBoxController::setProxyForCalls(bool enabled) { saveDelayed(); } +void ProxiesBoxController::setProxyRotationEnabled(bool enabled) { + if (_settings.proxyRotationEnabled() == enabled) { + return; + } + _settings.setProxyRotationEnabled(enabled); + saveDelayed(); +} + +void ProxiesBoxController::setProxyRotationTimeout(int value) { + if (_settings.proxyRotationTimeout() == value) { + return; + } + _settings.setProxyRotationTimeout(value); + saveDelayed(); +} + void ProxiesBoxController::setTryIPv6(bool enabled) { if (Core::App().settings().proxy().tryIPv6() == enabled) { return; @@ -1614,6 +2339,7 @@ void ProxiesBoxController::setTryIPv6(bool enabled) { } void ProxiesBoxController::saveDelayed() { + Core::App().proxyRotationSettingsChanged(); _saveTimer.callOnce(kSaveSettingsDelayedTimeout); } @@ -1621,6 +2347,17 @@ auto ProxiesBoxController::views() const -> rpl::producer { return _views.events(); } +rpl::producer ProxiesBoxController::listShareableChanges() const { + return _views.events_starting_with(ItemView()) | rpl::map([=] { + for (const auto &item : _list) { + if (!item.deleted && ProxyDataIsShareable(item.data)) { + return true; + } + } + return false; + }); +} + void ProxiesBoxController::updateView(const Item &item) { const auto selected = (_settings.selected() == item.data); const auto deleted = item.deleted; @@ -1640,8 +2377,7 @@ void ProxiesBoxController::updateView(const Item &item) { } return ItemState::Connecting; }(); - const auto supportsShare = (item.data.type == Type::Socks5) - || (item.data.type == Type::Mtproto); + const auto supportsShare = ProxyDataIsShareable(item.data); const auto supportsCalls = item.data.supportsCalls(); _views.fire({ item.id, @@ -1653,24 +2389,19 @@ void ProxiesBoxController::updateView(const Item &item) { deleted, !deleted && supportsShare, supportsCalls, - state }); + state, + }); } -void ProxiesBoxController::share(const ProxyData &proxy) { - if (proxy.type == Type::Http) { - return; - } - const auto link = u"https://t.me/"_q - + (proxy.type == Type::Socks5 ? "socks" : "proxy") - + "?server=" + proxy.host + "&port=" + QString::number(proxy.port) - + ((proxy.type == Type::Socks5 && !proxy.user.isEmpty()) - ? "&user=" + qthelp::url_encode(proxy.user) : "") - + ((proxy.type == Type::Socks5 && !proxy.password.isEmpty()) - ? "&pass=" + qthelp::url_encode(proxy.password) : "") - + ((proxy.type == Type::Mtproto && !proxy.password.isEmpty()) - ? "&secret=" + proxy.password : ""); - QGuiApplication::clipboard()->setText(link); - _show->showToast(tr::lng_username_copied(tr::now)); +void ProxiesBoxController::share(const ProxyData &proxy, bool qr) { + ShareProxy(_show, _account, proxy, qr); +} + +void ProxiesBoxController::Show( + not_null controller, + const QString &highlightId) { + controller->show( + CreateOwningBox(&controller->session().account(), highlightId)); } ProxiesBoxController::~ProxiesBoxController() { diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 25da434586d8cf..5de98bc663bd64 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -47,8 +47,12 @@ class ProxiesBoxController { const QMap &fields); static object_ptr CreateOwningBox( - not_null account); - object_ptr create(); + not_null account, + const QString &highlightId = QString()); + static void Show( + not_null controller, + const QString &highlightId = QString()); + object_ptr create(const QString &highlightId = QString()); enum class ItemState { Connecting, @@ -72,13 +76,17 @@ class ProxiesBoxController { }; void deleteItem(int id); + void deleteItems(); void restoreItem(int id); - void shareItem(int id); + void shareItem(int id, bool qr); + void shareItems(); void applyItem(int id); object_ptr editItemBox(int id); object_ptr addNewItemBox(); bool setProxySettings(ProxyData::Settings value); void setProxyForCalls(bool enabled); + void setProxyRotationEnabled(bool enabled); + void setProxyRotationTimeout(int value); void setTryIPv6(bool enabled); rpl::producer proxySettingsValue() const; @@ -87,6 +95,8 @@ class ProxiesBoxController { rpl::producer views() const; + rpl::producer listShareableChanges() const; + ~ProxiesBoxController(); private: @@ -106,10 +116,9 @@ class ProxiesBoxController { std::vector::iterator findByProxy(const ProxyData &proxy); void setDeleted(int id, bool deleted); void updateView(const Item &item); - void share(const ProxyData &proxy); + void share(const ProxyData &proxy, bool qr = false); void saveDelayed(); void refreshChecker(Item &item); - void setupChecker(int id, const Checker &checker); void replaceItemWith( std::vector::iterator which, diff --git a/Telegram/SourceFiles/boxes/create_ai_tone_box.cpp b/Telegram/SourceFiles/boxes/create_ai_tone_box.cpp new file mode 100644 index 00000000000000..c7ae9415e35427 --- /dev/null +++ b/Telegram/SourceFiles/boxes/create_ai_tone_box.cpp @@ -0,0 +1,718 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/create_ai_tone_box.h" + +#include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/emoji_list_widget.h" +#include "chat_helpers/stickers_lottie.h" +#include "data/data_ai_compose_tones.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_forum_icons.h" +#include "data/data_premium_limits.h" +#include "data/data_session.h" +#include "data/stickers/data_custom_emoji.h" +#include "history/view/media/history_view_sticker_player.h" +#include "lang/lang_keys.h" +#include "main/session/session_show.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "settings/sections/settings_premium.h" +#include "ui/abstract_button.h" +#include "ui/boxes/confirm_box.h" +#include "ui/controls/custom_emoji_toast_icon.h" +#include "ui/controls/warning_tooltip.h" +#include "ui/effects/animations.h" +#include "ui/layers/generic_box.h" +#include "ui/layers/show.h" +#include "ui/painter.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/shadow.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" + +#include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_dialogs.h" +#include "styles/style_layers.h" + +namespace { + +constexpr auto kAiComposeToneToastDuration = crl::time(4000); + +void ShowToneToast( + std::shared_ptr show, + not_null session, + const Data::AiComposeTone &tone, + bool created) { + const auto size = QSize( + st::aiComposeToneToastIconSize.width(), + st::aiComposeToneToastIconSize.height()); + show->showToast(Ui::Toast::Config{ + .title = (created + ? tr::lng_ai_compose_tone_created + : tr::lng_ai_compose_tone_updated)( + tr::now, + lt_title, + tone.title), + .text = tr::lng_ai_compose_tone_created_description( + tr::now, + Ui::Text::WithEntities), + .iconContent = Ui::MakeCustomEmojiToastIcon( + session, + tone.emojiId, + size), + .iconPadding = st::aiComposeToneToastIconPadding, + .duration = kAiComposeToneToastDuration, + }); +} + +void ChooseToneIconBox( + not_null box, + not_null controller, + Fn chosen) { + using namespace ChatHelpers; + + box->setTitle(tr::lng_ai_compose_tone_icon_title()); + box->setWidth(st::boxWideWidth); + box->setMaxHeight(st::editTopicMaxHeight); + box->setScrollStyle(st::reactPanelScroll); + + const auto manager = &controller->session().data().customEmojiManager(); + const auto icons = &controller->session().data().forumIcons(); + + auto factory = [=](DocumentId id, Fn repaint) + -> std::unique_ptr { + return manager->create( + id, + std::move(repaint), + Data::CustomEmojiManager::SizeTag::Large); + }; + + const auto top = box->setPinnedToTopContent( + object_ptr(box)); + + const auto body = box->verticalLayout(); + const auto selector = body->add( + object_ptr(body, EmojiListDescriptor{ + .show = controller->uiShow(), + .mode = EmojiListWidget::Mode::TopicIcon, + .paused = Window::PausedIn( + controller, + Window::GifPauseReason::Layer), + .customRecentList = DocumentListToRecent(icons->list()), + .customRecentFactory = std::move(factory), + .st = &st::reactPanelEmojiPan, + }), + st::reactPanelEmojiPan.padding); + + icons->requestDefaultIfUnknown(); + icons->defaultUpdates( + ) | rpl::on_next([=] { + selector->provideRecent(DocumentListToRecent(icons->list())); + }, selector->lifetime()); + + top->add(selector->createFooter()); + + const auto shadow = Ui::CreateChild(box.get()); + shadow->show(); + + rpl::combine( + top->heightValue(), + selector->widthValue() + ) | rpl::on_next([=](int topHeight, int width) { + shadow->setGeometry(0, topHeight, width, st::lineWidth); + }, shadow->lifetime()); + + selector->refreshEmoji(); + + selector->scrollToRequests( + ) | rpl::on_next([=](int y) { + box->scrollToY(y); + shadow->update(); + }, selector->lifetime()); + + rpl::combine( + box->heightValue(), + top->heightValue(), + rpl::mappers::_1 - rpl::mappers::_2 + ) | rpl::on_next([=](int height) { + selector->setMinimalHeight(selector->width(), height); + }, body->lifetime()); + + selector->customChosen( + ) | rpl::on_next([=](ChatHelpers::FileChosen data) { + chosen(data.document->id); + box->closeBox(); + }, selector->lifetime()); + + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +} // namespace + +not_null AddAiToneIconPreview( + not_null container, + not_null session, + rpl::producer emojiIdValue, + Fn emojiIdChosen) { + using StickerPlayer = HistoryView::StickerPlayer; + struct State { + DocumentId emojiId = 0; + std::shared_ptr player; + bool playerUsesTextColor = false; + }; + + const auto outer = st::aiToneIconPreviewSize; + const auto inner = st::aiToneIconPreviewInnerSize; + const auto top = st::aiToneIconPreviewTopSkip; + const auto bottom = st::aiToneIconPreviewBottomSkip; + const auto holder = container->add( + object_ptr( + container, + outer + top + bottom)); + const auto button = Ui::CreateChild(holder); + button->resize(outer, outer); + button->show(); + + holder->widthValue( + ) | rpl::on_next([=](int width) { + button->move((width - outer) / 2, top); + }, button->lifetime()); + + const auto state = button->lifetime().make_state(); + const auto emojiIdVar = button->lifetime().make_state< + rpl::variable>(std::move(emojiIdValue)); + + emojiIdVar->value( + ) | rpl::on_next([=](DocumentId id) { + state->emojiId = id; + }, button->lifetime()); + + emojiIdVar->value( + ) | rpl::map([=](DocumentId id) -> rpl::producer { + if (!id) { + return rpl::single((DocumentData*)nullptr); + } + return session->data().customEmojiManager().resolve( + id + ) | rpl::map([=](not_null document) { + return document.get(); + }) | rpl::map_error_to_done(); + }) | rpl::flatten_latest( + ) | rpl::map([=](DocumentData *document) + -> rpl::producer> { + if (!document) { + return rpl::single(std::shared_ptr()); + } + const auto media = document->createMediaView(); + media->checkStickerLarge(); + media->goodThumbnailWanted(); + + return rpl::single() | rpl::then( + document->session().downloaderTaskFinished() + ) | rpl::filter([=] { + return media->loaded(); + }) | rpl::take(1) | rpl::map([=] { + auto result = std::shared_ptr(); + const auto sticker = document->sticker(); + const auto size = QSize(inner, inner); + if (sticker && sticker->isLottie()) { + result = std::make_shared( + ChatHelpers::LottiePlayerFromDocument( + media.get(), + ChatHelpers::StickerLottieSize::StickerSet, + size, + Lottie::Quality::High)); + } else if (sticker && sticker->isWebm()) { + result = std::make_shared( + media->owner()->location(), + media->bytes(), + size); + } else { + result = std::make_shared( + media->owner()->location(), + media->bytes(), + size); + } + result->setRepaintCallback([=] { button->update(); }); + state->playerUsesTextColor + = media->owner()->emojiUsesTextColor(); + return result; + }); + }) | rpl::flatten_latest( + ) | rpl::on_next([=](std::shared_ptr player) { + state->player = std::move(player); + button->update(); + }, button->lifetime()); + + button->paintRequest( + ) | rpl::on_next([=] { + auto p = QPainter(button); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::aiToneIconPreviewBg); + p.drawEllipse(button->rect()); + if (state->player && state->player->ready()) { + const auto color = state->playerUsesTextColor + ? st::windowFg->c + : QColor(0, 0, 0, 0); + const auto frame = state->player->frame( + QSize(inner, inner), + color, + false, + crl::now(), + false).image; + const auto sz = frame.size() / style::DevicePixelRatio(); + p.drawImage( + QRect( + (outer - sz.width()) / 2, + (outer - sz.height()) / 2, + sz.width(), + sz.height()), + frame); + state->player->markFrameShown(); + } else if (!state->emojiId) { + st::aiToneIconPreviewPlaceholder.paintInCenter( + p, + button->rect()); + } + }, button->lifetime()); + + if (emojiIdChosen) { + button->setClickedCallback([=] { + const auto controller = ChatHelpers::ResolveWindowDefault()( + session); + if (!controller) { + return; + } + controller->uiShow()->showBox(Box( + ChooseToneIconBox, + controller, + crl::guard(button, [=](DocumentId id) { + emojiIdChosen(id); + }))); + }); + } else { + button->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + return button; +} + +namespace { + +void SetupToneBox( + not_null box, + not_null session, + DocumentId initialEmojiId, + const QString &initialName, + const QString &initialPrompt, + bool initialDisplayAuthor, + rpl::producer title, + rpl::producer submitLabel, + Fn submit, + Fn requestDelete = nullptr) { + box->setStyle(st::aiComposeBox); + box->setNoContentMargin(true); + box->setWidth(st::boxWideWidth); + box->addTopButton(st::aiComposeBoxClose, [=] { box->closeBox(); }); + box->setTitle(std::move(title)); + + const auto container = box->verticalLayout(); + const auto emojiId = container->lifetime().make_state< + rpl::variable>(initialEmojiId); + + const auto iconButton = AddAiToneIconPreview( + container, + session, + emojiId->value(), + [=](DocumentId id) { *emojiId = id; }); + + const auto name = box->addRow( + object_ptr( + box, + st::aiToneNameField, + Ui::InputField::Mode::SingleLine, + rpl::producer(), + initialName), + st::aiToneFieldsMargin); + name->setMaxLength(session->appConfig().get( + u"aicompose_tone_title_length_max"_q, + 12)); + + Ui::AddSkip(container, st::aiToneFieldsSkip); + + const auto promptSt = box->lifetime().make_state( + st::aiTonePromptField); + { + const auto &placeholderStyle = st::aiTonePlaceholderLabel.style; + const auto fieldsMargin = st::aiToneFieldsMargin; + const auto contentWidth = st::boxWideWidth + - fieldsMargin.left() - fieldsMargin.right() + - promptSt->textMargins.left() - promptSt->textMargins.right(); + auto measure = Ui::Text::String{ contentWidth / 2 }; + measure.setText( + placeholderStyle, + tr::lng_ai_compose_tone_prompt_placeholder(tr::now)); + const auto desiredMin = measure.countHeight(contentWidth) + + promptSt->textMargins.top() + + promptSt->textMargins.bottom(); + if (promptSt->heightMin < desiredMin) { + promptSt->heightMin = desiredMin; + } + if (promptSt->heightMax < promptSt->heightMin) { + promptSt->heightMax = promptSt->heightMin; + } + } + + const auto prompt = box->addRow( + object_ptr( + box, + *promptSt, + Ui::InputField::Mode::MultiLine, + rpl::producer(), + initialPrompt), + st::aiToneFieldsMargin); + prompt->setSubmitSettings(Ui::InputField::SubmitSettings::None); + prompt->setMaxLength(session->appConfig().get( + u"aicompose_tone_prompt_length_max"_q, + 1024)); + + struct FieldDecor { + not_null bg; + not_null placeholder; + Ui::Animations::Simple anim; + bool hidden = false; + }; + const auto makeDecor = [=]( + not_null field, + rpl::producer placeholderText) { + const auto parent = field->parentWidget(); + const auto decor = field->lifetime().make_state(FieldDecor{ + .bg = Ui::CreateChild(parent), + .placeholder = Ui::CreateChild( + parent, + std::move(placeholderText), + st::aiTonePlaceholderLabel), + }); + decor->bg->setAttribute(Qt::WA_TransparentForMouseEvents); + decor->placeholder->setAttribute(Qt::WA_TransparentForMouseEvents); + decor->bg->paintRequest( + ) | rpl::on_next([bg = decor->bg] { + auto p = QPainter(bg); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::aiToneFieldBg); + const auto r = st::aiToneFieldRadius; + p.drawRoundedRect(bg->rect(), r, r); + }, decor->bg->lifetime()); + decor->bg->lower(); + decor->placeholder->raise(); + + const auto applyPosition = [=] { + const auto pad = st::aiToneFieldPadding; + const auto progress = decor->anim.value(decor->hidden ? 1. : 0.); + const auto shift = int(base::SafeRound( + progress * (-st::defaultInputField.placeholderShift))); + decor->placeholder->moveToLeft( + field->x() + pad.left() + shift, + field->y() + pad.top()); + decor->placeholder->setOpacity(1. - progress); + }; + field->geometryValue( + ) | rpl::on_next([=](QRect g) { + if (g.isEmpty()) { + return; + } + const auto pad = st::aiToneFieldPadding; + decor->bg->setGeometry(g); + decor->placeholder->resizeToWidth( + g.width() - pad.left() - pad.right()); + applyPosition(); + }, field->lifetime()); + + const auto animate = [=](bool hidden) { + if (decor->hidden == hidden) { + return; + } + decor->hidden = hidden; + decor->anim.start( + applyPosition, + hidden ? 0. : 1., + hidden ? 1. : 0., + st::defaultInputField.duration); + }; + field->changes( + ) | rpl::on_next([=] { + animate(!field->getLastText().isEmpty()); + }, field->lifetime()); + decor->hidden = !field->getLastText().isEmpty(); + applyPosition(); + return decor; + }; + makeDecor(name, tr::lng_ai_compose_tone_name_placeholder()); + const auto promptDecor = makeDecor( + prompt, + tr::lng_ai_compose_tone_prompt_placeholder()); + + const auto authorCheckbox = box->addRow( + object_ptr( + box, + tr::lng_ai_compose_tone_author(tr::now), + st::aiComposeEmojifyCheckbox, + std::make_unique( + st::defaultCheck, + initialDisplayAuthor)), + st::aiToneAuthorCheckboxMargin, + style::al_top); + + const auto deleteButton = requestDelete + ? box->addRow( + object_ptr( + box, + tr::lng_ai_compose_tone_delete(), + st::aiToneDeleteButton), + st::aiToneDeleteButtonMargin) + : nullptr; + if (deleteButton) { + deleteButton->setFullRadius(true); + deleteButton->setClickedCallback(std::move(requestDelete)); + box->widthValue( + ) | rpl::on_next([=](int width) { + const auto &margin = st::aiToneDeleteButtonMargin; + deleteButton->setFullWidth( + width - margin.left() - margin.right()); + }, deleteButton->lifetime()); + } + + rpl::combine( + prompt->topValue(), + promptDecor->placeholder->heightValue(), + box->getDelegate()->contentHeightMaxValue() + ) | rpl::on_next([=](int top, int phHeight, int contentHeight) { + const auto pad = st::aiToneFieldPadding; + const auto deleteBlock = deleteButton + ? (deleteButton->heightNoMargins() + + st::aiToneDeleteButtonMargin.top() + + st::aiToneDeleteButtonMargin.bottom()) + : 0; + prompt->setMaxHeight(contentHeight + - top + - st::aiToneFieldsMargin.bottom() + - authorCheckbox->heightNoMargins() + - st::aiToneAuthorCheckboxMargin.top() + - st::aiToneAuthorCheckboxMargin.bottom() + - deleteBlock); + prompt->setMinHeight(phHeight + pad.top() + pad.bottom()); + }, prompt->lifetime()); + + box->setFocusCallback([=] { + name->setFocusFast(); + }); + + const auto warning = box->lifetime().make_state(); + const auto save = [=] { + const auto nameText = name->getLastText().trimmed(); + const auto promptText = prompt->getLastText().trimmed(); + const auto showWarning = [=]( + not_null target, + rpl::producer text) { + warning->show({ + .parent = box, + .target = target, + .text = std::move(text), + }); + }; + if (!emojiId->current()) { + showWarning( + iconButton, + tr::lng_ai_compose_tone_warn_icon(tr::marked)); + return; + } else if (nameText.isEmpty()) { + name->showError(); + showWarning( + name, + tr::lng_ai_compose_tone_warn_name(tr::marked)); + return; + } else if (promptText.isEmpty()) { + prompt->showError(); + showWarning( + prompt, + tr::lng_ai_compose_tone_warn_prompt(tr::marked)); + return; + } + warning->hide(anim::type::normal); + submit( + emojiId->current(), + nameText, + promptText, + authorCheckbox->checked()); + }; + + const auto submitBtn = box->addButton(std::move(submitLabel), save); + submitBtn->setFullRadius(true); +} + +} // namespace + +void CreateAiToneBox( + not_null box, + not_null session, + Fn saved) { + SetupToneBox( + box, + session, + DocumentId(0), + QString(), + QString(), + false, + tr::lng_ai_compose_create_tone_title(), + tr::lng_ai_compose_tone_create(), + [=](DocumentId emojiId, + const QString &name, + const QString &prompt, + bool displayAuthor) { + session->data().aiComposeTones().create( + name, + prompt, + emojiId, + displayAuthor, + crl::guard(box, [=](Data::AiComposeTone tone) { + const auto show = box->uiShow(); + box->closeBox(); + ShowToneToast(show, session, tone, true); + if (saved) { + saved(tone); + } + }), + crl::guard(box, [=](const MTP::Error &error) { + if (error.type() == u"TONES_SAVED_TOO_MANY"_q) { + ShowAiComposeToneLimitError(box->uiShow(), session); + } else if (!MTP::IgnoreError(error)) { + box->showToast(error.type()); + } + })); + }, + nullptr); +} + +void EditAiToneBox( + not_null box, + not_null session, + const Data::AiComposeTone &tone, + Fn saved) { + const auto toneCopy = tone; + SetupToneBox( + box, + session, + tone.emojiId, + tone.title, + tone.prompt, + tone.authorId != 0, + tr::lng_ai_compose_edit_tone_title(), + tr::lng_ai_compose_tone_save(), + [=](DocumentId emojiId, + const QString &name, + const QString &prompt, + bool displayAuthor) { + session->data().aiComposeTones().update( + toneCopy, + name, + prompt, + std::make_optional(emojiId), + std::make_optional(displayAuthor), + crl::guard(box, [=](Data::AiComposeTone updated) { + const auto show = box->uiShow(); + box->closeBox(); + ShowToneToast(show, session, updated, false); + if (saved) { + saved(updated); + } + })); + }, + [=] { + ConfirmDeleteAiTone( + box->uiShow(), + session, + toneCopy, + [=] { box->closeBox(); }); + }); +} + +void ConfirmDeleteAiTone( + std::shared_ptr show, + not_null session, + const Data::AiComposeTone &tone, + Fn done) { + if (!tone.creator) { + show->show(Ui::MakeConfirmBox({ + .text = tr::lng_ai_compose_tone_remove_sure(), + .confirmed = [=](Fn &&close) { + close(); + session->data().aiComposeTones().save( + tone, + true, + done); + }, + .confirmText = tr::lng_box_remove(), + })); + return; + } + show->show(Ui::MakeConfirmBox({ + .text = tr::lng_ai_compose_tone_delete_sure(), + .confirmed = [=](Fn &&close) { + close(); + session->data().aiComposeTones().remove(tone, done); + }, + .confirmText = tr::lng_box_delete(), + .confirmStyle = &st::attentionBoxButton, + .title = tr::lng_ai_compose_tone_delete(), + })); +} + +void ShowAiComposeToneLimitError( + std::shared_ptr show, + not_null session) { + const auto limits = Data::PremiumLimits(session); + const auto premium = session->premium(); + const auto premiumPossible = session->premiumPossible(); + const auto defaultLimit = limits.aiComposeSavedTonesDefault(); + const auto premiumLimit = limits.aiComposeSavedTonesPremium(); + const auto current = premium ? premiumLimit : defaultLimit; + if (premium || !premiumPossible) { + show->showToast(tr::lng_ai_compose_tone_saved_limit_final( + tr::now, + lt_count, + current, + tr::rich)); + } else { + Settings::ShowPremiumPromoToast( + Main::MakeSessionShow(show, session), + ChatHelpers::ResolveWindowDefault(), + tr::lng_ai_compose_tone_saved_limit( + tr::now, + lt_count, + defaultLimit, + lt_link, + tr::bold(tr::lng_ai_compose_tone_saved_limit_link( + tr::now, + tr::link)), + lt_premium_count, + tr::bold(QString::number(premiumLimit)), + tr::rich), + u"ai_compose_tones"_q); + } +} diff --git a/Telegram/SourceFiles/boxes/create_ai_tone_box.h b/Telegram/SourceFiles/boxes/create_ai_tone_box.h new file mode 100644 index 00000000000000..4760eeaacb1160 --- /dev/null +++ b/Telegram/SourceFiles/boxes/create_ai_tone_box.h @@ -0,0 +1,50 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Data { +struct AiComposeTone; +} // namespace Data + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class AbstractButton; +class GenericBox; +class Show; +class VerticalLayout; +} // namespace Ui + +not_null AddAiToneIconPreview( + not_null container, + not_null session, + rpl::producer emojiIdValue, + Fn emojiIdChosen = nullptr); + +void CreateAiToneBox( + not_null box, + not_null session, + Fn saved = nullptr); + +void EditAiToneBox( + not_null box, + not_null session, + const Data::AiComposeTone &tone, + Fn saved = nullptr); + +void ConfirmDeleteAiTone( + std::shared_ptr show, + not_null session, + const Data::AiComposeTone &tone, + Fn done = nullptr); + +void ShowAiComposeToneLimitError( + std::shared_ptr show, + not_null session); diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 223c5cab47041b..6ec3f0aa307246 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -7,44 +7,104 @@ For license and copyright information please follow this link: */ #include "boxes/create_poll_box.h" +#include "poll/poll_link_box.h" +#include "poll/poll_link_thumbnail.h" +#include "poll/poll_media_upload.h" #include "base/call_delayed.h" +#include "base/qt/qt_key_modifiers.h" +#include "base/unixtime.h" +#include "boxes/premium_limits_box.h" #include "base/event_filter.h" #include "base/random.h" #include "base/unique_qptr.h" +#include "countries/countries_instance.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" +#include "core/file_utilities.h" +#include "core/mime_type.h" #include "core/application.h" #include "core/core_settings.h" +#include "core/shortcuts.h" +#include "core/ui_integration.h" +#include "ui/power_saving.h" +#include "data/data_cloud_file.h" +#include "data/data_document.h" +#include "data/data_file_origin.h" +#include "data/data_location.h" #include "data/data_poll.h" +#include "data/data_peer.h" +#include "data/data_photo.h" +#include "data/data_session.h" #include "data/data_user.h" +#include "data/data_web_page.h" #include "data/stickers/data_custom_emoji.h" +#include "history/view/controls/history_view_webpage_processor.h" +#include "history/view/media/menu/history_view_poll_menu.h" #include "history/view/history_view_schedule_box.h" +#include "info/channel_statistics/boosts/giveaway/select_countries_box.h" #include "lang/lang_keys.h" +#include "layout/layout_document_generic_preview.h" +#include "main/main_app_config.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "platform/platform_file_utilities.h" #include "main/main_session.h" #include "menu/menu_send.h" +#include "settings/detailed_settings_button.h" +#include "settings/settings_common.h" +#include "storage/file_upload.h" +#include "storage/localimageloader.h" +#include "storage/storage_account.h" +#include "storage/storage_media_prepare.h" #include "ui/controls/emoji_button.h" #include "ui/controls/emoji_button_factory.h" +#include "ui/controls/location_picker.h" +#include "ui/chat/attach/attach_prepare.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" +#include "ui/effects/radial_animation.h" +#include "ui/effects/ripple_animation.h" +#include "ui/effects/ttl_icon.h" +#include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/menu/menu_action.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" +#include "ui/boxes/choose_date_time.h" +#include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/vertical_layout_reorder.h" #include "ui/ui_utility.h" +#include "window/section_widget.h" #include "window/window_session_controller.h" +#include "apiwrap.h" #include "styles/style_boxes.h" +#include "styles/style_dialogs.h" +#include "styles/style_chat.h" #include "styles/style_chat_helpers.h" // defaultComposeFiles. #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_overview.h" +#include "styles/style_polls.h" #include "styles/style_settings.h" +#include +#include + namespace { constexpr auto kQuestionLimit = 255; @@ -55,29 +115,61 @@ constexpr auto kWarnOptionLimit = 30; constexpr auto kSolutionLimit = 200; constexpr auto kWarnSolutionLimit = 60; constexpr auto kErrorLimit = 99; +constexpr auto kMediaUploadMaxAge = 45 * 60 * crl::time(1000); + +using PollMediaState = PollMediaUpload::PollMediaState; +using PollMediaButton = PollMediaUpload::PollMediaButton; +using PollMediaUploader = PollMediaUpload::PollMediaUploader; + +using PollMediaUpload::FileListFromMimeData; +using PollMediaUpload::GenerateDocumentFilePreview; +using PollMediaUpload::LocalImageThumbnail; +using PollMediaUpload::PreparePollMediaTask; +using PollMediaUpload::UploadContext; +using PollMediaUpload::ValidateFileDragData; class Options { public: + using AttachCallback = Fn, + std::shared_ptr)>; + using FieldDropCallback = Fn, + std::shared_ptr)>; + using WidgetDropCallback = Fn, + std::shared_ptr)>; + Options( not_null box, not_null container, not_null controller, ChatHelpers::TabbedPanel *emojiPanel, - bool chooseCorrectEnabled); + bool chooseCorrectEnabled, + AttachCallback attachCallback, + FieldDropCallback fieldDropCallback, + WidgetDropCallback widgetDropCallback); [[nodiscard]] bool hasOptions() const; [[nodiscard]] bool isValid() const; [[nodiscard]] bool hasCorrect() const; + [[nodiscard]] bool hasUploadingMedia() const; + bool refreshStaleMedia(crl::time threshold); [[nodiscard]] std::vector toPollAnswers() const; void focusFirst(); - void enableChooseCorrect(bool enabled); + void enableChooseCorrect(bool enabled, bool multiCorrect = false); + [[nodiscard]] not_null layoutWidget() const; [[nodiscard]] rpl::producer usedCount() const; [[nodiscard]] rpl::producer> scrollToWidget() const; [[nodiscard]] rpl::producer<> backspaceInFront() const; [[nodiscard]] rpl::producer<> tabbed() const; + void handlePaste( + not_null field, + const QStringList &list); + private: class Option { public: @@ -86,14 +178,18 @@ class Options { not_null container, not_null session, int position, - std::shared_ptr group); + std::shared_ptr group, + AttachCallback attachCallback, + FieldDropCallback fieldDropCallback, + WidgetDropCallback widgetDropCallback); Option(const Option &other) = delete; Option &operator=(const Option &other) = delete; - void toggleRemoveAlways(bool toggled); void enableChooseCorrect( - std::shared_ptr group); + std::shared_ptr group, + bool multiCorrect = false, + Fn checkboxChanged = nullptr); void show(anim::type animated); void destroy(FnMut done); @@ -106,34 +202,41 @@ class Options { [[nodiscard]] bool isGood() const; [[nodiscard]] bool isTooLong() const; [[nodiscard]] bool isCorrect() const; + [[nodiscard]] bool uploadingMedia() const; + bool refreshMediaIfStale(crl::time threshold); [[nodiscard]] bool hasFocus() const; void setFocus() const; - void clearValue(); void setPlaceholder() const; void removePlaceholder() const; + void showAddIcon(bool show); - not_null field() const; + [[nodiscard]] not_null field() const; + [[nodiscard]] not_null wrapWidget() const; [[nodiscard]] PollAnswer toPollAnswer(int index) const; - [[nodiscard]] rpl::producer removeClicks() const; + [[nodiscard]] Ui::RpWidget *handleWidget() const; private: - void createRemove(); + void createAttach(); void createWarning(); - void toggleCorrectSpace(bool visible); + void createHandle(); void updateFieldGeometry(); base::unique_qptr> _wrap; not_null _content; - base::unique_qptr> _correct; - Ui::Animations::Simple _correctShown; + base::unique_qptr> _correct; + base::unique_qptr> _handle; bool _hasCorrect = false; Ui::InputField *_field = nullptr; base::unique_qptr _shadow; - base::unique_qptr _remove; - rpl::variable *_removeAlways = nullptr; + base::unique_qptr _attach; + AttachCallback _attachCallback; + FieldDropCallback _fieldDropCallback; + WidgetDropCallback _widgetDropCallback; + std::shared_ptr _media; + Ui::FadeWrapScaled *_addIcon = nullptr; }; @@ -142,21 +245,36 @@ class Options { void fixShadows(); void removeEmptyTail(); void addEmptyOption(); + void insertOption( + int beforeIndex, + const QString &text, + anim::type animated); + void initOptionField(not_null field); void checkLastOption(); void validateState(); void fixAfterErase(); void destroy(std::unique_ptr