Skip to content

Commit

Permalink
bskyogcard: support emoji, more languages, long starter pack names (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
devinivy authored Jun 27, 2024
1 parent f6b138f commit 4939645
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 163 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/build-and-push-ogcard-aws.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: build-and-push-ogcard-aws
on:
push:
branches:
- divy/bskycard
workflow_dispatch:

env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,6 @@ src/locale/locales/**/*.js
*.apk
*.aab
*.ipa

# ogcard assets
bskyogcard/src/assets/fonts/noto-*
2 changes: 1 addition & 1 deletion Dockerfile.bskyogcard
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RUN yarn install --frozen-lockfile
COPY ./bskyogcard ./

# build then prune dev deps
RUN yarn build
RUN yarn install-fonts && yarn build
RUN yarn install --production --ignore-scripts --prefer-offline

# Uses assets from build stage to reduce build size
Expand Down
8 changes: 6 additions & 2 deletions bskyogcard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"main": "src/index.ts",
"scripts": {
"start": "node --loader ts-node/esm ./src/bin.ts",
"build": "tsc && cp -r src/assets dist/assets"
"dev": "node --watch-path ./src --loader ts-node/esm ./src/bin.ts",
"build": "tsc && cp -r src/assets dist/",
"install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts"
},
"dependencies": {
"@atproto/api": "0.12.19-next.0",
Expand All @@ -15,10 +17,12 @@
"http-terminator": "^3.2.0",
"pino": "^9.2.0",
"react": "^18.3.1",
"satori": "^0.10.13"
"satori": "^0.10.13",
"twemoji": "^14.0.2"
},
"devDependencies": {
"@types/node": "^20.14.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
}
}
40 changes: 40 additions & 0 deletions bskyogcard/scripts/install-fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {writeFile} from 'node:fs/promises'
import * as path from 'node:path'
import {fileURLToPath} from 'node:url'

const __DIRNAME = path.dirname(fileURLToPath(import.meta.url))

const FONTS = [
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/japanese-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/chinese-traditional-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/chinese-simplified-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/chinese-hongkong-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/korean-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/thai-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/arabic-700-normal.ttf',
'https://cdn.jsdelivr.net/fontsource/fonts/[email protected]/hebrew-700-normal.ttf',
]

async function main() {
await Promise.all(
FONTS.map(async urlStr => {
const url = new URL(urlStr)
const res = await fetch(url)
const font = await res.arrayBuffer()
const filename = url.pathname
.split('/')
.slice(-2)
.join('/')
.replace(/@[\d.]+\//, '-')
if (!res.ok) {
throw new Error(`HTTP ${res.status}: fetching failed for ${filename}`)
}
await writeFile(
path.join(__DIRNAME, '..', 'src', 'assets', 'fonts', filename),
Buffer.from(font),
)
}),
)
}

main()
File renamed without changes.
5 changes: 4 additions & 1 deletion bskyogcard/src/components/StarterPack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function StarterPack(props: {
} else {
imagesAcross.push(...imagesExceptCreator.slice(0, 7))
}
const isLongTitle = record ? record.name.length > 30 : false
return (
<div
style={{
Expand Down Expand Up @@ -130,7 +131,9 @@ export function StarterPack(props: {
<div
style={{
padding: '75px 30px 0px',
fontSize: 65,
fontSize: isLongTitle ? 55 : 65,
display: 'flex',
textAlign: 'center',
}}>
{record?.name || 'Starter Pack'}
</div>
Expand Down
20 changes: 11 additions & 9 deletions bskyogcard/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {readFileSync} from 'node:fs'
import {readdirSync, readFileSync} from 'node:fs'
import * as path from 'node:path'
import {fileURLToPath} from 'node:url'

import {AtpAgent} from '@atproto/api'
import * as path from 'path'
import {fileURLToPath} from 'url'

import {Config} from './config.js'

Expand All @@ -28,12 +28,14 @@ export class AppContext {

static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
const appviewAgent = new AtpAgent({service: cfg.service.appviewUrl})
const fonts = [
{
name: 'Inter',
data: readFileSync(path.join(__DIRNAME, 'assets', 'Inter-Bold.ttf')),
},
]
const fontDirectory = path.join(__DIRNAME, 'assets', 'fonts')
const fontFiles = readdirSync(fontDirectory)
const fonts = fontFiles.map(file => {
return {
name: path.basename(file, path.extname(file)),
data: readFileSync(path.join(fontDirectory, file)),
}
})
return new AppContext({
cfg,
appviewAgent,
Expand Down
1 change: 1 addition & 0 deletions bskyogcard/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {subsystemLogger} from '@atproto/common'

export const httpLogger = subsystemLogger('bskyogcard')
export const renderLogger = subsystemLogger('bskyogcard:render')
6 changes: 6 additions & 0 deletions bskyogcard/src/routes/starter-pack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '../components/StarterPack.js'
import {AppContext} from '../context.js'
import {httpLogger} from '../logger.js'
import {loadEmojiAsSvg} from '../util.js'
import {handler, originVerifyMiddleware} from './util.js'

export default function (ctx: AppContext, app: Express) {
Expand Down Expand Up @@ -65,6 +66,11 @@ export default function (ctx: AppContext, app: Express) {
fonts: ctx.fonts,
height: STARTERPACK_HEIGHT,
width: STARTERPACK_WIDTH,
loadAdditionalAsset: async (code, text) => {
if (code === 'emoji') {
return await loadEmojiAsSvg(text)
}
},
},
)
const output = await resvg.renderAsync(svg)
Expand Down
37 changes: 37 additions & 0 deletions bskyogcard/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import twemoji from 'twemoji'

import {renderLogger} from './logger.js'

const U200D = String.fromCharCode(0x200d)
const UFE0F_REGEXP = /\uFE0F/g

export async function loadEmojiAsSvg(chars: string) {
const cached = emojiCache.get(chars)
if (cached) return cached
const iconCode = twemoji.convert.toCodePoint(
chars.indexOf(U200D) < 0 ? chars.replace(UFE0F_REGEXP, '') : chars,
)
const res = await fetch(getEmojiUrl(iconCode))
const body = await res.arrayBuffer()
if (!res.ok) {
renderLogger.warn(
{status: res.status, err: Buffer.from(body).toString()},
'could not fetch emoji',
)
return
}
const svg =
'data:image/svg+xml;base64,' + Buffer.from(body).toString('base64')
emojiCache.set(chars, svg)
return svg
}

const emojiCache = new Map<string, string>()

function getEmojiUrl(code: string) {
return (
'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/' +
code.toLowerCase() +
'.svg'
)
}
Loading

0 comments on commit 4939645

Please sign in to comment.