Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## 2026

### 05-29 v1.0.2

- [*] 优化 Ascii2d 搜索返回信息

### 05-07 v1.0.1

- [*] 修复类型错误
- [*] 修复 AnimeTrace 会抛出 base64 错误

### 05-07 v1.0.0

- [*] 全新重构
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

| 搜索引擎 | url | path | 特殊选项 |
| ------------------------------------ | --- | ---- | ---------------------------------- |
| [Ascii2d](https://ascii2d.net/) | ✔ | ✔ | type, flaresolverr |
| [Ascii2d](https://ascii2d.net/) | ✔ | ✔ | type, flareSolverr |
| [Iqdb](https://iqdb.org/) | ✔ | ✔ | service, forcegray |
| [SauceNAO](https://saucenao.com) | ✔ | ✔ | hide, dbs |
| [TraceMoe](https://trace.moe/) | ✔ | ✔ | curBorders, anilistId, anilistInfo |
Expand Down Expand Up @@ -73,7 +73,7 @@ const result = await Ascii2d({
const result = await Ascii2d({
url: 'https://example.com/image.jpg',
type: 'bovw', // 搜索类型: 'color' 或 'bovw'
flaresolverr: 'https://flaresolverr.example.com' // FlareSolverr API地址
flareSolverr: 'https://flaresolverr.example.com' // FlareSolverr API地址
})
```

Expand Down Expand Up @@ -171,7 +171,7 @@ docker run -d -p 8191:8191 --name flaresolverr ghcr.io/flaresolverr/flaresolverr
```typescript
const result = await Ascii2d({
url: 'https://example.com/image.jpg',
flaresolverr: 'http://localhost:8191'
flareSolverr: 'http://localhost:8191'
})
```

Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"name": "image_searcher",
"type": "module",
"version": "1.0.0",
"version": "1.0.2",
"description": "image searcher of multiple search engines",
"author": "huan_kong",
"license": "MIT",
"repository": "https://github.com/HkTeamX/ImageSearcher",
"repository": {
"type": "git",
"url": "https://github.com/HkTeamX/ImageSearcher.git"
},
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
Expand All @@ -22,10 +25,7 @@
"domelementtype": "^3.0.0",
"file-type": "^22.0.1",
"flaresolverr-client": "^1.2.0",
"ky": "^2.0.2",
"tough-cookie": "^6.0.1",
"typescript": "^6.0.3"

"ky": "^2.0.2"
},
"devDependencies": {
"@antfu/eslint-config": "^8.2.0",
Expand All @@ -34,6 +34,7 @@
"eslint": "^10.2.1",
"jiti": "^2.6.1",
"tsdown": "^0.21.10",
"tsx": "^4.21.0"
"tsx": "^4.21.0",
"typescript": "^6.0.3"
}
}
30 changes: 3 additions & 27 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/libs/AnimeTrace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export async function AnimeTrace(options: AnimeTraceOptions): Promise<AnimeTrace
else if ('url' in options) {
form.append('url', options.url)
}
else if ('base64' in options) {
form.append('base64', options.base64)
}
else {
throw new Error('[AnimeTrace Error]: please input path or url')
}
Expand Down
79 changes: 52 additions & 27 deletions src/libs/Ascii2d/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ascii2dOptions, Ascii2dRes } from '@/libs/Ascii2d/types.js'
import type { Ascii2dItem, Ascii2dOptions, Ascii2dRes } from '@/libs/Ascii2d/types.js'
import { Buffer } from 'node:buffer'
import { basename } from 'node:path'
import { load } from 'cheerio'
import ky from 'ky'
Expand All @@ -10,6 +11,8 @@ const BASE_URL = 'https://ascii2d.net'
export async function Ascii2d(options: Ascii2dOptions): Promise<Ascii2dRes> {
let page: string
let url: string
let userAgent = ''
let cookie = ''

if ('url' in options) {
const response = await useFlareSolverr({
Expand All @@ -19,6 +22,8 @@ export async function Ascii2d(options: Ascii2dOptions): Promise<Ascii2dRes> {

page = response.solution.response
url = response.solution.url
userAgent = response.solution.userAgent
cookie = response.solution.cookies.map(c => `${c.name}=${c.value}`).join('; ')
}
else if ('path' in options) {
const form = new FormData()
Expand All @@ -40,34 +45,54 @@ export async function Ascii2d(options: Ascii2dOptions): Promise<Ascii2dRes> {
page = response.solution.response
}

const results: Ascii2dItem[] = []
const $ = load(page)
return $('.item-box')
.toArray()
.map((item) => {
const image = $('.image-box > img', item).first()
const src = image.attr('src') ?? ''
const elements = $('.item-box').toArray()
for (const element of elements) {
const image = $('.image-box > img', element).first()
const src = image.attr('src') ?? ''
const imageUrl = new URL(src, BASE_URL).toString()

const detailBox = $('.detail-box', item)
const [sourceLink, authorLink] = $('a', detailBox).toArray()

return {
hash: $('.hash', item).text(),
info: $('.info-box > .text-muted', item).text(),
image: new URL(src, BASE_URL).toString(),
source: sourceLink
? {
link: $(sourceLink).attr('href') ?? '',
text: $(sourceLink).text(),
}
: undefined,
author: authorLink
? {
link: $(authorLink).attr('href') ?? '',
text: $(authorLink).text(),
}
: undefined,
let base64 = ''
if (options.image2Base64) {
try {
const response = await ky.get(imageUrl, { headers: { cookie, 'User-Agent': userAgent } })
const buffer = await response.arrayBuffer()
const contentType = response.headers.get('content-type') ?? 'application/octet-stream'
const base64Data = Buffer.from(buffer).toString('base64')
base64 = `data:${contentType};base64,${base64Data}`
}
catch {}
}

const detailBox = $('.detail-box', element)
const [sourceLink, authorLink] = $('a', detailBox).toArray()

results.push({
hash: $('.hash', element).text(),
info: $('.info-box > .text-muted', element).text(),
image: imageUrl,
base64,
source: sourceLink
? {
link: $(sourceLink).attr('href') ?? '',
text: $(sourceLink).text(),
}
: undefined,
author: authorLink
? {
link: $(authorLink).attr('href') ?? '',
text: $(authorLink).text(),
}
: undefined,
})
.filter(item => item.hash && item.info)
.slice(1)
}

return {
userAgent,
cookie,
results: results
.filter(item => item.hash && item.info)
.slice(1),
}
}
10 changes: 8 additions & 2 deletions src/libs/Ascii2d/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { BaseOptions } from '@/utils/BaseOptions.js'
export type Ascii2dOptions = BaseOptions<{
type: 'color' | 'bovw'
flareSolverr: string
image2Base64?: boolean
}>

export interface Ascii2dSource {
Expand All @@ -13,9 +14,14 @@ export interface Ascii2dSource {
export interface Ascii2dItem {
hash: string
info: string
image: string | undefined
image: string
base64: string
source: Ascii2dSource | undefined
author: Ascii2dSource | undefined
}

export type Ascii2dRes = Ascii2dItem[]
export interface Ascii2dRes {
userAgent: string
cookie: string
results: Ascii2dItem[]
}
2 changes: 1 addition & 1 deletion src/libs/TraceMoe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function TraceMoe(options: TraceMoeOptions): Promise<TraceMoeRes> {
throw new Error('[TraceMoe Error]: please input path or url')
}

if (options.curBorders || !('cutBorders' in options)) {
if (options.cutBorders || !('cutBorders' in options)) {
searchParams.append('cutBorders', '')
}

Expand Down
2 changes: 1 addition & 1 deletion src/libs/TraceMoe/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseOptions } from '@/utils/BaseOptions.js'

export type TraceMoeOptions = BaseOptions<{
curBorders?: boolean
cutBorders?: boolean
anilistId?: number
anilistInfo?: boolean
}>
Expand Down
2 changes: 1 addition & 1 deletion src/utils/flaresolverr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export interface UseFlareSolverrOptions {

export async function useFlareSolverr(options: UseFlareSolverrOptions): Promise<V1ResponseIndex['request.get']> {
const flaresolverr = new FlareSolverrClient(options.api)
const sessionManager = await flaresolverr.createSession({ session: 'image_seacher' }, true)
const sessionManager = await flaresolverr.createSession({ session: 'image_searcher' }, true)
return await sessionManager.requestGet({ url: options.url, maxTimeout: 600000 })
}
4 changes: 2 additions & 2 deletions src/utils/readFileToBlob.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { fileTypeFromFile } from 'file-type'
import { fileTypeFromBuffer } from 'file-type'

export async function readFileToBlob(path: string): Promise<Blob> {
const buffer = await readFile(resolve(path))
const fileType = await fileTypeFromFile(path)
const fileType = await fileTypeFromBuffer(buffer)
return new Blob([buffer], { type: fileType?.mime ?? 'application/octet-stream' })
}
27 changes: 14 additions & 13 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { AnimeTrace, Ascii2d, Iqdb, SauceNAO, TinEye, TraceMoe } from '@/index.j
const url = 'https://img.huankong.top/i/2024/04/15/661cc5a5b583d.jpg'
const path = './test/test.jpg'

// Ascii2d({
// type: 'bovw',
// url,
// flareSolverr: 'https://flaresolverr.huankong.top',
// })
// .then((res) => {
// console.dir(res, { depth: null })
// })
Ascii2d({
type: 'color',
url,
flareSolverr: 'http://localhost:8191',
image2Base64: true,
})
.then((res) => {
console.dir(res, { depth: null })
})

// SauceNAO({
// path,
Expand All @@ -38,8 +39,8 @@ const path = './test/test.jpg'
// console.dir(res, { depth: null })
// })

AnimeTrace({
url,
}).then((res) => {
console.dir(res, { depth: null })
})
// AnimeTrace({
// url,
// }).then((res) => {
// console.dir(res, { depth: null })
// })
Loading