1.3.10-beta.2: фото из MAX первым способом MAX web — baseUrl как есть (оригинал: размер ровно как заявлен, 1080×1920 webp), потом url, склейка baseUrl+token последней — CDN отказывает ей 400 на ВСЕХ фото (сравнение на тесте 2026-10-16: 3 из 3); диагностика PHOTO_COMPARE убрана

This commit is contained in:
Folist 2026-10-16 11:02:45 +03:00
parent dcae9353ab
commit b0acbc146b
4 changed files with 11 additions and 34 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "telemax",
"version": "1.3.10-beta.1",
"version": "1.3.10-beta.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "telemax",
"version": "1.3.10-beta.1",
"version": "1.3.10-beta.2",
"license": "AGPL-3.0-only",
"dependencies": {
"dotenv": "^17.2.3",

View file

@ -2,7 +2,7 @@
"name": "telemax",
"private": true,
"license": "AGPL-3.0-only",
"version": "1.3.10-beta.1",
"version": "1.3.10-beta.2",
"type": "module",
"scripts": {
"dev": "tsx watch src/server/app.ts",

View file

@ -360,46 +360,23 @@ export function fitsTelegramPhoto({ width, height }: { width: number; height: nu
export const PHOTO_AS_FILE = new Set(['heic', 'tiff', 'avif']);
/**
* The ways to fetch a MAX photo, in order. First the bridge's own since the start: baseUrl with the
* token appended. Then MAX web's: baseUrl as it is (its «original»; the token is kept apart, never in
* the URL), or `url` for a photo without baseUrl. The CDN refused the first with a 400 for a phone
* screenshot (a user report on 1.3.8). Pure + exported for unit testing.
* The ways to fetch a MAX photo, in order. MAX web's first: baseUrl as it is — its «original», the
* photo at the size MAX declares (the token is kept apart, never in the URL) — or `url` for a photo
* without baseUrl. Last, the bridge's own way since the start, baseUrl with the token appended: the
* CDN began refusing it with a 400 for every photo (a phone screenshot first, a user report on 1.3.8;
* all of them on test, 2026-10-16). Pure + exported for unit testing.
*/
export function photoUrls(att: Pick<MaxAttachment, 'baseUrl' | 'photoToken' | 'url'>): { how: string; url: string }[] {
const all = [
{ how: 'baseUrl+token', url: att.baseUrl && att.photoToken ? att.baseUrl + att.photoToken : undefined },
{ how: 'baseUrl', url: att.baseUrl },
{ how: 'url', url: att.url },
{ how: 'baseUrl+token', url: att.baseUrl && att.photoToken ? att.baseUrl + att.photoToken : undefined },
];
return all.filter((v, i): v is { how: string; url: string } => !!v.url && all.findIndex((w) => w.url === v.url) === i);
}
/**
* TEMPORARY diagnostic (PHOTO_COMPARE=1, test server only, 2026-10-16): fetches a MAX photo every
* way — photoUrls plus MAX web's on-screen size (`&fn=w_1440`) — and logs what each gives, to settle
* which way goes first. Remove before the release.
*/
async function comparePhotoUrls(att: MaxAttachment): Promise<void> {
const id = String(att.photoId ?? '?');
const declared = att as { width?: unknown; height?: unknown };
logger.info(`PHOTO_COMPARE ${id}: MAX says ${String(declared.width)}×${String(declared.height)}`);
const ways = [...photoUrls(att)];
if (att.baseUrl) ways.push({ how: 'baseUrl&fn=w_1440', url: `${att.baseUrl}&fn=w_1440` });
for (const { how, url } of ways) {
try {
const res = await maxFetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
const buf = res.ok ? Buffer.from(await res.arrayBuffer()) : undefined;
const size = buf ? imageSize(buf) : null;
logger.info(`PHOTO_COMPARE ${id}: «${how}» → ${res.status}${buf ? `, ${buf.byteLength} bytes, ${imageExtension(buf)}${size ? `, ${size.width}×${size.height}` : ''}` : ''}`);
} catch (err) {
logger.info(`PHOTO_COMPARE ${id}: «${how}» → ${(err as Error).message}`);
}
}
}
/** A photo by the first of photoUrls that the CDN gives — the next one tried only after a refusal (an outage is no reason to). */
async function downloadPhoto(att: MaxAttachment): Promise<Buffer> {
if (process.env.PHOTO_COMPARE === '1') void comparePhotoUrls(att);
let miss: DownloadMissError | undefined;
for (const { how, url } of photoUrls(att)) {
try {

View file

@ -140,10 +140,10 @@ describe('downloadMaxAttachment transient reporting', () => {
});
describe('photoUrls', () => {
it("tries the bridge's own way first, then MAX web's: baseUrl as it is, or url", () => {
it("tries MAX web's way first — baseUrl as it is, or url — and the old glued one last", () => {
expect(photoUrls({ baseUrl: 'https://i.oneme.ru/i?r=AB', photoToken: 'TOK' }).map((v) => [v.how, v.url])).toEqual([
['baseUrl+token', 'https://i.oneme.ru/i?r=ABTOK'],
['baseUrl', 'https://i.oneme.ru/i?r=AB'],
['baseUrl+token', 'https://i.oneme.ru/i?r=ABTOK'],
]);
expect(photoUrls({ baseUrl: 'https://i.oneme.ru/i?r=AB' }).map((v) => v.how)).toEqual(['baseUrl']);
expect(photoUrls({ url: 'https://i.oneme.ru/p.jpg', photoToken: 'TOK' }).map((v) => v.how)).toEqual(['url']);