diff --git a/bskyogcard/src/components/Post.tsx b/bskyogcard/src/components/Post.tsx index 6a019054ea..6733ebae6b 100644 --- a/bskyogcard/src/components/Post.tsx +++ b/bskyogcard/src/components/Post.tsx @@ -9,6 +9,7 @@ import { AppBskyFeedPost, AppBskyGraphDefs, AppBskyGraphStarterpack, + ModerationDecision, } from '@atproto/api' import {atoms as a, gradient, theme as t} from '../theme/index.js' @@ -31,15 +32,18 @@ import {Text} from './Text.js' export function Post({ post, data, + moderation, }: { post: AppBskyFeedDefs.PostView data: PostData + moderation: ModerationDecision }) { if (AppBskyFeedPost.isRecord(post.record)) { const avatar = data.images.get(post.author.avatar) const text = post.record.text const rt = data.texts.get(text) const hasInteractions = post.likeCount > 0 || post.repostCount > 0 + console.log(moderation.ui('contentView')) return ( l.val === `!no-unauthenticated`, ) if (notPublic) { return res.status(404).end('not found') } + + const [postData, moderationOptions] = await Promise.all([ + resolvePostData(post, ctx.appviewAgent), + getModerationOptions(ctx.appviewAgent), + ]) + + const svg = await satori( + , + { + fonts: ctx.fonts, + width: WIDTH, + loadAdditionalAsset: async (code, text) => { + if (code === 'emoji') { + return await loadEmojiAsSvg(text) + } + }, + }, + ) + const output = await resvg.renderAsync(svg, { + fitTo: { + mode: 'width', + value: WIDTH * 2, + }, + logLevel: 'trace', + }) + + res.statusCode = 200 + res.setHeader('content-type', 'image/png') + res.setHeader('cdn-tag', [...postData.images.keys()].join(',')) + + return res.end(output.asPng()) } catch (err) { httpLogger.warn({err, uri: uri.toString()}, 'could not fetch post') return res.status(404).end('not found') } - - if (!AppBskyFeedPost.isRecord(post.record)) { - return res.status(404).end('not found') - } - - const data = await resolvePostData(post, ctx.appviewAgent) - const svg = await satori(, { - fonts: ctx.fonts, - width: WIDTH, - loadAdditionalAsset: async (code, text) => { - if (code === 'emoji') { - return await loadEmojiAsSvg(text) - } - }, - }) - const output = await resvg.renderAsync(svg, { - fitTo: { - mode: 'width', - value: WIDTH * 2, - }, - logLevel: 'trace', - }) - res.statusCode = 200 - res.setHeader('content-type', 'image/png') - res.setHeader('cdn-tag', [...data.images.keys()].join(',')) - return res.end(output.asPng()) }), ) } diff --git a/bskyogcard/src/util/moderation.ts b/bskyogcard/src/util/moderation.ts new file mode 100644 index 0000000000..6d9db5e14c --- /dev/null +++ b/bskyogcard/src/util/moderation.ts @@ -0,0 +1,76 @@ +/* eslint-disable-next-line no-restricted-imports */ +import { + AppBskyLabelerDefs, + AtpAgent, + BSKY_LABELER_DID, + DEFAULT_LABEL_SETTINGS, + interpretLabelValueDefinitions, + moderatePost as defaultModeratePost, +} from '@atproto/api' + +type ModeratePost = typeof defaultModeratePost +export type Subject = Parameters[0] +export type Options = Parameters[1] + +function translateOldLabels(subject: Parameters[0]) { + if (subject.labels) { + for (const label of subject.labels) { + if ( + label.val === 'gore' && + (!label.src || label.src === BSKY_LABELER_DID) + ) { + label.val = 'graphic-media' + } + } + } +} + +export const DEFAULT_LABELS: typeof DEFAULT_LABEL_SETTINGS = Object.fromEntries( + Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']), +) + +export function moderatePost(post: Subject, opts: Options) { + // HACK + // temporarily translate 'gore' into 'graphic-media' during the transition period + // can remove this in a few months + // -prf + translateOldLabels(post) + + return defaultModeratePost(post, opts) +} + +export async function getModerationOptions(agent: AtpAgent) { + const labelDefs = await getLabelDefinitions(agent) + const opts: Options = { + userDid: undefined, + prefs: { + adultContentEnabled: false, + labels: DEFAULT_LABELS, + mutedWords: [], + hiddenPosts: [], + labelers: [ + { + did: BSKY_LABELER_DID, + labels: DEFAULT_LABELS, + }, + ], + }, + labelDefs: { + [BSKY_LABELER_DID]: labelDefs, + }, + } + return opts +} + +export async function getLabelDefinitions(agent: AtpAgent) { + const {data} = await agent.app.bsky.labeler.getServices({ + dids: [BSKY_LABELER_DID], + detailed: true, + }) + if (!data || !data.views[0]) { + throw new Error(`Could not fetch label definitions`) + } + const labeler = data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed + + return interpretLabelValueDefinitions(labeler) +}