mirror of
https://github.com/FxEmbed/FxEmbed.git
synced 2026-10-02 01:54:54 +08:00
Merge branch 'main' into fix/telegram-photo-size
This commit is contained in:
@@ -17,6 +17,12 @@ https://d.fxtwitter.com/user/status/123456789/photo/3
|
||||
https://d.fixupx.com/user/status/123456789/video/2
|
||||
```
|
||||
|
||||
For Bluesky image posts, use the same `/photo/N` suffix:
|
||||
|
||||
```
|
||||
https://fxbsky.app/profile/user.bsky.social/post/abc123/photo/2
|
||||
```
|
||||
|
||||
<img
|
||||
src="/guide/readme/selectphoto.png"
|
||||
alt="Discord embed showing a specific photo or video from a post"
|
||||
|
||||
Generated
+20
-4
@@ -31,7 +31,7 @@
|
||||
"@sentry/esbuild-plugin": "^5.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
||||
"@typescript-eslint/parser": "^8.70.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"dotenv": "^18.0.1",
|
||||
"eslint": "^10.10.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-config-typescript": "^3.0.0",
|
||||
@@ -2962,6 +2962,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/bundler-plugins/node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli": {
|
||||
"version": "2.58.6",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz",
|
||||
@@ -4499,11 +4512,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||
"version": "18.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.1.tgz",
|
||||
"integrity": "sha512-0eR4m4D/jH5eaI3evo2ZqqMii5mrTCR12v12VChdie6O1gPHs7XAjzTep0CWc1Bd+oNx3vVfbi734dK4zghigw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"dotenv": "dist/index.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
"@sentry/esbuild-plugin": "^5.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
||||
"@typescript-eslint/parser": "^8.70.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"dotenv": "^18.0.1",
|
||||
"eslint": "^10.10.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-config-typescript": "^3.0.0",
|
||||
|
||||
@@ -260,6 +260,13 @@ export const handleStatus = async (
|
||||
console.log(api);
|
||||
return returnError(c, Strings.ERROR_API_FAIL);
|
||||
}
|
||||
if (
|
||||
provider === DataProvider.Bluesky &&
|
||||
mediaNumber &&
|
||||
!status.media?.photos?.[mediaNumber - 1]
|
||||
) {
|
||||
return c.text('Photo not found', 404);
|
||||
}
|
||||
/* Should sensitive statuses be allowed Instant View? */
|
||||
let useIV = false;
|
||||
|
||||
|
||||
@@ -19,7 +19,21 @@ bluesky.use(trimTrailingSlash());
|
||||
bluesky.get('/owoembed', oembed);
|
||||
bluesky.get('/api/v1/statuses/:snowcode', activityRequest);
|
||||
bluesky.get('/:prefix/:handle/post/:id', blueskyStatusRequest);
|
||||
bluesky.get('/:prefix/:handle/post/:id/photo/:mediaNumber{[1-9][0-9]*}', blueskyStatusRequest);
|
||||
bluesky.get(
|
||||
'/:prefix/:handle/post/:id/photo/:mediaNumber{[1-9][0-9]*}/:language',
|
||||
blueskyStatusRequest
|
||||
);
|
||||
bluesky.get('/profile/:handle/post/:id', blueskyStatusRequest);
|
||||
bluesky.get('/profile/:handle/post/:id/photo/:mediaNumber{[1-9][0-9]*}', blueskyStatusRequest);
|
||||
bluesky.get(
|
||||
'/profile/:handle/post/:id/photo/:mediaNumber{[1-9][0-9]*}/:language',
|
||||
blueskyStatusRequest
|
||||
);
|
||||
bluesky.get(
|
||||
'/:prefix/profile/:handle/post/:id/photo/:mediaNumber{[1-9][0-9]*}/:language',
|
||||
blueskyStatusRequest
|
||||
);
|
||||
bluesky.get('/:prefix/profile/:handle/post/:id/:language', blueskyStatusRequest);
|
||||
bluesky.get('/profile/:handle/post/:id/:language', blueskyStatusRequest);
|
||||
bluesky.get('/profile/:handle/feed.xml', profileFeedRssBluesky);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isHorizonEmbedParam } from '../../twitter/router';
|
||||
|
||||
export const blueskyStatusRequest = async (c: Context) => {
|
||||
console.log('bluesky status request!!!');
|
||||
const { handle, id, language } = c.req.param();
|
||||
const { handle, id, mediaNumber, language } = c.req.param();
|
||||
const actualId = id.match(/\w+/g)?.[0] ?? '';
|
||||
|
||||
const userAgent = c.req.header('User-Agent') || '';
|
||||
@@ -73,7 +73,7 @@ export const blueskyStatusRequest = async (c: Context) => {
|
||||
c,
|
||||
actualId,
|
||||
handle,
|
||||
undefined, //mediaNumber ? parseInt(mediaNumber) : undefined,
|
||||
mediaNumber ? parseInt(mediaNumber, 10) : undefined,
|
||||
userAgent,
|
||||
flags,
|
||||
language,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
import { app } from '../src/worker';
|
||||
import threadMultiImage from './fixtures/bluesky/thread-multi-image.json';
|
||||
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
function mockMultiImageThread() {
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url.includes('app.bsky.feed.getPostThread')) return Response.json(threadMultiImage);
|
||||
if (url.includes('app.bsky.actor.getProfiles')) return Response.json({ profiles: [] });
|
||||
if (url.includes('mosaic.fxbsky.app')) return Response.json({ ok: true });
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
}
|
||||
|
||||
test.each([
|
||||
['photo/1', 'https://cdn.bsky.app/full1'],
|
||||
['photo/2', 'https://cdn.bsky.app/full2'],
|
||||
['photo/2/en', 'https://cdn.bsky.app/full2']
|
||||
])('Bluesky /%s embeds only the selected image', async (path, expected) => {
|
||||
mockMultiImageThread();
|
||||
const response = await app.request(`https://fxbsky.app/profile/pics.test/post/rkeypics/${path}`, {
|
||||
headers: { 'User-Agent': 'Twitterbot' }
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const html = await response.text();
|
||||
expect(html).toContain(`<meta property="og:image" content="${expected}"`);
|
||||
});
|
||||
|
||||
test('Bluesky prefixed localized photo URL embeds the selected image', async () => {
|
||||
mockMultiImageThread();
|
||||
const response = await app.request(
|
||||
'https://fxbsky.app/bsky/profile/pics.test/post/rkeypics/photo/2/en',
|
||||
{ headers: { 'User-Agent': 'Twitterbot' } }
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
const html = await response.text();
|
||||
expect(html).toContain('<meta property="og:image" content="https://cdn.bsky.app/full2"');
|
||||
expect(html).not.toContain('<meta property="og:image" content="https://cdn.bsky.app/full1"');
|
||||
});
|
||||
|
||||
test('Bluesky /photo/N rejects an unavailable image', async () => {
|
||||
mockMultiImageThread();
|
||||
const response = await app.request(
|
||||
'https://fxbsky.app/profile/pics.test/post/rkeypics/photo/99',
|
||||
{
|
||||
headers: { 'User-Agent': 'Twitterbot' }
|
||||
}
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
test('Bluesky photo selection reaches the Discord activity response', async () => {
|
||||
mockMultiImageThread();
|
||||
const response = await app.request('https://fxbsky.app/profile/pics.test/post/rkeypics/photo/2', {
|
||||
headers: { 'User-Agent': 'Discordbot/2.0' }
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const html = await response.text();
|
||||
const activityHref = html.match(
|
||||
/<link href='([^']+)' rel='alternate' type='application\/activity\+json'/
|
||||
)?.[1];
|
||||
expect(activityHref).toBeDefined();
|
||||
const snowcode = activityHref?.split('/').at(-1);
|
||||
const activity = await app.request(`https://fxbsky.app/api/v1/statuses/${snowcode}`, {
|
||||
headers: { 'User-Agent': 'Discordbot/2.0' }
|
||||
});
|
||||
expect(activity.status).toBe(200);
|
||||
const body = await activity.text();
|
||||
expect(body).toContain('https://cdn.bsky.app/full2');
|
||||
expect(body).not.toContain('https://cdn.bsky.app/full1');
|
||||
});
|
||||
Reference in New Issue
Block a user