Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resize oversize images before sending #235

Merged
merged 15 commits into from
Aug 5, 2024
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.1.1",
"jest-mock": "^28.1.3",
"jimp": "^0.22.12",
"mocha": "^9.2.2",
"prettier": "^3.2.5",
"sass": "^1.62.1",
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/command.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-fallthrough */
import { Channel, Context, Random, Session, User } from 'koishi'

import { Config, OutputType, SpoilerType, preferSizes } from '.'
import { Config, OutputType, SpoilerType, preferSizes, preferSizesToSize } from '.'

export const inject = {
required: ['booru'],
Expand Down Expand Up @@ -84,13 +84,20 @@ export function apply(ctx: Context, config: Config) {
url ||= image.url

if (config.asset && ctx.assets) {
if (config.autoResize && config.preferSize !== 'original') {
url = await ctx.booru.imgResize(url, preferSizesToSize[config.preferSize])
}
url = await ctx.booru.imgUrlToAssetUrl(url)
if (!url) {
children.unshift(<i18n path='commands.booru.messages.no-image'></i18n>)
continue
}
} else if (config.base64) {
url = await ctx.booru.imgUrlToBase64(url)
if (config.autoResize && config.preferSize !== 'original') {
url = await ctx.booru.imgResize(url, preferSizesToSize[config.preferSize])
} else if (!url.startsWith('data:')) {
url = await ctx.booru.imgUrlToBase64(url)
}
if (!url) {
children.unshift(<i18n path='commands.booru.messages.no-image'></i18n>)
continue
Expand Down
33 changes: 32 additions & 1 deletion packages/core/src/index.ts
SkyTNT marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Jimp from 'jimp'
import { Context, Logger, Quester, Schema, Service, remove } from 'koishi'
import LanguageDetect from 'languagedetect'

Expand Down Expand Up @@ -90,6 +91,34 @@ class ImageService extends Service {
return undefined
}

async imgResize(url: string, size: number): Promise<string> {
if (size < 0) {
return url
}
try {
const resp = await this.ctx.http(url, { method: 'GET', responseType: 'arraybuffer', proxyAgent: '' })
SkyTNT marked this conversation as resolved.
Show resolved Hide resolved
let img = await Jimp.read(Buffer.from(resp.data))
const width = img.bitmap.width
const height = img.bitmap.height
const ratio = size / Math.max(width, height)
if (ratio < 1) {
img = img.resize(Math.floor(width * ratio), Math.floor(height * ratio)).quality(80)
const buffer = await img.getBufferAsync('image/jpeg')
return `data:image/jpeg;base64,${buffer.toString('base64')}`
}
return url
} catch (err) {
if (Quester.Error.is(err)) {
logger.warn(
`Resize images failed with HTTP status ${err.response?.status}: ${JSON.stringify(err.response?.data)}.`,
)
} else {
logger.error(`Resize images failed with error: ${err.message}.`)
}
return url
}
}

async imgUrlToAssetUrl(url: string): Promise<string> {
return await this.ctx.assets.upload(url, Date.now().toString()).catch(() => {
logger.warn('Request failed when trying to store image with assets service.')
Expand All @@ -101,7 +130,7 @@ class ImageService extends Service {
return this.ctx
.http(url, { method: 'GET', responseType: 'arraybuffer', proxyAgent: '' })
.then((resp) => {
return `data:${resp.headers['content-type']};base64,${Buffer.from(resp.data).toString('base64')}`
return `data:${resp.headers.get('content-type')};base64,${Buffer.from(resp.data).toString('base64')}`
SkyTNT marked this conversation as resolved.
Show resolved Hide resolved
})
.catch((err) => {
if (Quester.Error.is(err)) {
Expand Down Expand Up @@ -144,6 +173,7 @@ export interface Config {
output: OutputType
outputMethod: 'one-by-one' | 'merge-multiple' | 'forward-all' | 'forward-multiple'
preferSize: ImageSource.PreferSize
autoResize: boolean
nsfw: boolean
asset: boolean
base64: boolean
Expand Down Expand Up @@ -199,6 +229,7 @@ export const Config = Schema.intersect([
])
.description('优先使用图片的最大尺寸。')
.default('large'),
autoResize: Schema.boolean().default(false).description('自动缩小过大的图片(需开启assets或者base64)。'),
asset: Schema.boolean().default(false).description('优先使用 [assets服务](https://assets.koishi.chat/) 转存图片。'),
base64: Schema.boolean().default(false).description('使用 base64 发送图片。'),
spoiler: Schema.union([
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ export namespace ImageSource {
}

export const preferSizes = ['thumbnail', 'large', 'medium', 'small', 'original'] as const
export const preferSizesToSize = { thumbnail: 128, large: 1280, medium: 640, small: 320, original: -1 } as const