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: add moehu support #56

Merged
merged 11 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/.vitepress/config/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
{ "text": "Lolibooru", "link": "/plugins/lolibooru" },
{ "text": "Lolicon", "link": "/plugins/lolicon" },
{ "text": "Local", "link": "/plugins/local" },
{ "text": "萌虎", "link": "/plugins/moehu" },
{ "text": "Pixiv", "link": "/plugins/pixiv" },
{ "text": "Safebooru", "link": "/plugins/safebooru" },
{ "text": "Sankaku", "link": "/plugins/sankaku" },
Expand All @@ -49,4 +50,4 @@
}]
}
}
}
}
1 change: 1 addition & 0 deletions docs/zh-CN/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Booru 插件支持多个图源站点,每个图源站点都有一个对应的
| [Konachan.net](https://konachan.net/) | [`koishi-plugin-booru-konachan`](./plugins/konachan.md) |
| [Lolibooru](https://lolibooru.moe/) | [`koishi-plugin-booru-lolibooru`](./plugins/lolibooru.md) |
| [Lolicon](https://lolicon.app/) | [`koishi-plugin-booru-lolicon`](./plugins/lolicon.md) |
| [萌虎](https://img.moehu.org/) | [`koishi-plugin-booru-moehu`](./plugins/moehu.md) |
| [Pixiv](https://www.pixiv.net/) | [`koishi-plugin-booru-pixiv`](./plugins/pixiv.md) |
| [Safebooru](https://safebooru.org/) | [`koishi-plugin-booru-safebooru`](./plugins/safebooru.md) |
| [SankakuComplex](https://chan.sankakucomplex.com/) | [`koishi-plugin-booru-sankaku`](./plugins/sankaku.md) |
Expand Down
7 changes: 7 additions & 0 deletions docs/zh-CN/plugins/moehu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# koishi-plugin-booru-moehu

[萌虎](https://img.moehu.org/)图源支持

## 安装

1. 在 Koishi 中下载并安装本插件,若你还未安装 [Booru 插件](../index.md),请先安装。
1 change: 1 addition & 0 deletions packages/core/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [koishi-plugin-booru-lolibooru](https://booru.koishi.chat/plugins/lolibooru.html)
- [koishi-plugin-booru-lolicon](https://booru.koishi.chat/plugins/lolicon.html)
- [koishi-plugin-booru-local](https://booru.koishi.chat/plugins/local.html)
- [koishi-plugin-booru-moehu](https://booru.koishi.chat/plugins/moehu.html)
- [koishi-plugin-booru-pixiv](https://booru.koishi.chat/plugins/pixiv.html)
- [koishi-plugin-booru-safebooru](https://booru.koishi.chat/plugins/safebooru.html)
- [koishi-plugin-booru-sankaku](https://booru.koishi.chat/plugins/sankaku.html)
Expand Down
66 changes: 66 additions & 0 deletions packages/moehu/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "koishi-plugin-booru-moehu",
"description": "Image service by moehu for Koishi",
"version": "1.0.0",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"files": [
"lib",
"dist"
],
"author": {
"email": "[email protected]",
"name": "kbd",
"url": "https://github.com/Kabuda-czh"
},
"maintainers": [
{
"name": "MaikoTan",
"email": "[email protected]",
"url": "https://github.com/MaikoTan"
}
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/koishijs/koishi-plugin-booru.git",
"directory": "packages/moehu"
},
"bugs": {
"url": "https://github.com/koishijs/koishi-plugin-booru/issues"
},
"homepage": "https://booru.koishi.chat/plugins/moehu.html",
"keywords": [
"chatbot",
"koishi",
"service",
"plugin",
"booru",
"image",
"picture",
"random",
"setu",
"moehu"
],
"koishi": {
"description": {
"zh": "最好的涩图插件! - moehu 图源支持"
},
"service": {
"require": [
"booru"
]
}
},
"peerDependencies": {
"koishi": "^4.17.0",
"koishi-plugin-booru": "^1.1.0"
},
"devDependencies": {
"koishi": "^4.17.0"
},
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"fastest-levenshtein": "^1.0.16"
}
}
7 changes: 7 additions & 0 deletions packages/moehu/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# koishi-plugin-booru-moehu

Booru plugin for [萌虎](https://img.moehu.org/).

## Documentation

[Documentation](https://booru.koishi.chat/plugins/moehu.html)
31 changes: 31 additions & 0 deletions packages/moehu/scripts/fetch-ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { writeFile } from 'fs/promises'

import * as cheerio from 'cheerio'
import { resolve } from 'path';

const url = 'https://img.moehu.org/'

;(async () => {
const response = await fetch(url)

if (response.status === 200) {
const $ = cheerio.load(await response.text())

const menuDivs = $('.menuDiv').slice(0, -3)

const keyMap: Record<string, string> = {}

menuDivs.each((index, menu) => {
const menuUl = $(menu).find('ul')
menuUl.find('pre').each((_, pre) => {
const match = /(?<name>.*)→.*id=(?<id>.*)">/g.exec($(pre).html() ?? '')
if (match && match.groups) {
const { name, id } = match.groups
keyMap[name.trim()] = id.trim()
}
})
})

await writeFile(resolve(__dirname, '..', 'src', 'data', 'ids.json'), JSON.stringify(keyMap))
}
})()
1 change: 1 addition & 0 deletions packages/moehu/src/data/ids.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"图片一":"img1","图片二":"img2","竖屏壁纸":"sjpic","横屏壁纸":"pc","黑丝":"acghs","白丝":"acgbs","兽耳":"kemonomimi","白毛":"yin","星空":"xingk","loli":"loli","小姐姐":"xjj","高清风景壁纸":"gqbz","虚拟主播[合集]":"vtb","小鲨鱼":"gawr-gura","雪花菈米":"yukihana","夏色祭":"natsuiro","润羽露西娅":"uruha-rushia","花园Serena":"hanazono-serena","笹木咲":"sasaki-saku","角卷绵芽":"tsunomaki-watame","常暗永远":"tokoyami-towa","天宫心":"amamiya-kokoro","兔田佩克菈":"usada-pekora","一伊那尔栖":"ninomae","大神澪":"ookami-mio","星川莎拉":"sara-hoshikawa","樱巫女":"sakura-miko","木口EN":"holoen","绊爱":"kizunaai","神乐七奈":"kagura-nana","神乐Mea":"kagura-mea","白上吹雪":"fubuki","戌神沁音":"inugami-korone","阿夸":"aqua","猫宫日向":"nekomiya-hinata","猫羽雫":"myn","樱岛麻衣":"ydmy","初音未来":"miku","洛天依":"tianyi","五更琉璃":"gokou-ruri","椎名真白":"mashiro","鹿乃":"kano","Saber":"saber","四系乃":"yoshino","见崎鸣":"misakimei","阿卡林":"akari","康娜":"kanna","喵帕斯":"miaops","妮姆芙":"nymph","诺艾尔":"noel","时崎狂三":"kurumi","薇尔莉特":"violet","忍野忍":"shinobu","风见一姬":"kazuki","伊莉雅":"iliya","碧翠丝":"beatrice","土间埋":"umr","雷姆":"rem","阿波连":"aharen","国家队02":"02","阿尼亚":"aniya","高木":"takagi","御坂美琴":"misaka-mikoto","约尔":"yor","水原千鹤":"mizuhara","矢泽妮可":"nico","唐可可":"tangkk","千反田爱瑠":"eru","亚丝娜":"asuna","香风智乃":"chiro","凯露":"karyl","灰原哀":"haibara","雏鹤爱":"hinatsuru","志摩凛":"shimarin","小鸟游六花":"rikka","加藤惠":"katoumegumi","雪之下雪乃":"yukino","谢丝塔":"siesta","早坂爱":"hayasakaai","四宫辉夜":"kaguya","凉宫春日":"haruhi","藤原千花":"chika","祢豆子":"nezuko","小野寺小咲":"onoderaoosaki","中野三玖":"nakanomiku","伊蕾娜":"elaina","佐天泪子":"ruiko","白井黑子":"kuroko","泉此方":"konata","白银圭":"shiroganekei","伊井野弥子":"linomiko","立华奏":"kanade","喜多川海梦":"kitagawa-marin","熊污女[雨宿町]":"amayadori-machi","牧濑红莉栖":"makise-kurisu","艾拉":"lsla","蝶祈":"yuzuriha-inori","伊卡洛斯":"uranus-queen","八寻宁宁":"yashiro-nene","菲洛":"filo","食蜂操祈":"shokuho-isaki","我妻由乃":"gasai-yuno","长瀞同学":"nagatoro-hayase","蜘蛛子":"noname-kumo","和泉纱雾":"izumi-sagiri","栗山未来":"kuriyama-mirai","奈亚子":"nyaruko","沙优":"ogiwara-sayu","维包子":"blois","摘希":"miniwa-tsumiki","原神":"ys","明日方舟":"mrfz","碧蓝航线":"blhx","车万":"dongf","碧蓝档案":"blda","缘之空":"yzk","少女前线":"snqx","崩坏三":"bh3","赛马娘":"saima","RE0":"re0","SAO":"sao","妖精的尾巴":"yaowei","鬼灭之刃":"gmzr","五等分的花嫁":"5huajia","冰菓":"bingg","你的名字":"kiminame","公主连接":"gongzhulj","间谍过家家":"spyfamily","摇曳露营":"camp","摇曳百合":"yuruyuri","天使降临到我身边":"miyone","我们无法一起学习":"xuebulai","悠哉日常大王":"nobiyori","黄金拼图":"kin-iro-mosaic","转生恶役大小姐":"flag-ojousama","崛与宫村":"hori-to-miyamura","路人女主":"saenai-heroine","喜欢本大爷竟然就你一个":"mydcy","高原魔女":"slime-300","幼妻狐仙":"fox-senko","游戏王":"yu-gi-oh","莉可丽丝":"lycoris-recoil","斩.赤红之瞳":"akame-ga-kill","Fgo":"fgo","轻音":"k-on","Lovelive":"lovelive","Overlord[骨王]":"overlord","变态王子与不笑猫":"hentaiandneko","不正经魔术师":"majutsu-koushi","空之境界":"kara-no-kyoukai","小林家的龙女仆":"kobayashi-no-dragon","龙与虎":"toradora","关于我转生变成史莱姆这件事":"tensei-slime","未闻花名":"hana-no-amae","乌贼娘":"ika-usume","小老师":"celia-claire","熊熊勇闯异世界":"kuma-bear","为美好的世界献上祝福":"sekai-shukufuku","二次元表情包":"bqb","甘城猫猫表情包":"gcmm","MC酱":"mc","kemomimi兽耳酱":"kemomimi","猫猫表情包":"miao"}
63 changes: 63 additions & 0 deletions packages/moehu/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { closest } from 'fastest-levenshtein'
import { Context, Schema, trimSlash } from 'koishi'
import { ImageSource } from 'koishi-plugin-booru'
import ids from './data/ids.json'
import { Moehu } from './types'

const availableTags: string[] = Object.entries(ids).map(([k, v]) => [k, v]).flat()

class MoehuImageSource extends ImageSource<MoehuImageSource.Config> {
languages = ['en']
MaikoTan marked this conversation as resolved.
Show resolved Hide resolved

constructor(ctx: Context, config: MoehuImageSource.Config) {
super(ctx, config)
}

async get(query: ImageSource.Query): Promise<ImageSource.Result[]> {
// API docs: https://img.moehu.org/
const params = {
id: this.getSimilarTag(query.raw),
num: query.count,
return: "json"
}
const url = trimSlash(this.config.endpoint) + '?' + Object.entries(params).map(([key, value]) => `${key}=${value}`).join('&')
const data = await this.ctx.http.get<Moehu.Response>(url, { responseType: 'json' })

if (!Array.isArray(data.pic)) {
return
}

return data.pic.map((img) => {
return {
url: img,
nsfw: false,
}
})
}

getSimilarTag(tags: string) {
if (!tags?.trim()) {
// Return random tag from ids
const t = Object.values(ids)
return t[Math.floor(Math.random() * t.length)]
}
const c = closest(tags, availableTags)
// TODO: Maybe we should check the distance of `c` and `tags` then only return the resonable one?
return c
}
}

namespace MoehuImageSource {
export interface Config extends ImageSource.Config {
endpoint: string
}

export const Config: Schema<Config> = Schema.intersect([
ImageSource.createSchema({ label: 'moehu' }),
Schema.object({
endpoint: Schema.string().description('Moehu 的 URL。').default('https://img.moehu.org/pic.php'),
}).description('搜索设置'),
])
}

export default MoehuImageSource
7 changes: 7 additions & 0 deletions packages/moehu/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export namespace Moehu {
export interface Response {
code: string
pic: string[]
}
}

12 changes: 12 additions & 0 deletions packages/moehu/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"resolveJsonModule": true,
},
"include": [
"src/**/*.ts",
"src/**/*.json"
],
}