最近群友说想要我的小苦瓜机器人加一个以图搜图的功能,正好我经常用的隔壁群的以图搜图机器人寄了,于是我就想着自己写一个。
整体思路还是有的,就是找一些搜图网站的接口然后让机器人请求这些接口去搜图。
搜图及带有CQ码的文本构建我已经在Github开源了,可以直接点击查看。
Zhaokugua/anime_image_search: search anime image 自用机器人以图搜图封装 (github.com)
参考其他搜图机器人代码
市面上已经有很多搜图机器人了,所以我就想着能不能参考一些代码,首先我想到了之前很火的真寻bot
HibiKier/zhenxun_bot: 基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库,非常可爱的绪山真寻bot (github.com)
sauceNAO
可以看到真寻bot自带的plugins插件里面有一个search_image插件,点进去后只有一个saucenao.py,我一看原来真寻bot自带的这个插件只有saucenao这一个搜图接口,嘛万事开头难嘛,就先从这个开始好啦。
saucenao官方有个自带的api文档,可以注册账号之后获取一个apikey然后就可以调用搜图接口啦。
调用方法也很简单,直接给他的api地址发一个post请求,参数里带上图片链接,apikey等这些参数就行了。
然后他会返回给你一个json,在json里就有图片的相似度、预览图链接、来源地址等信息,我把这些信息简单整合了一下,图片用cq码以图片的形式发送,大致预览效果如下:
ascii2d
上面参考的真寻bot默认插件只有一个sauceNAO,但是我感觉就这一个搜索还不够,于是我想着加一个ascii2d搜图。
查询了部分现存代码并访问官网过后,我发现这个网站并没有接口给你调用,大部分代码的实现方法都是直接请求html页面的源代码然后用beautifulsoup或者xpath解析html来获取。
为了减少第三方库的调用,我决定自己拿正则写一个。
不过当时群友在我的群里聊恋爱历程聊的热火朝天,一个正则表达式写了好久还没写好一看是忘了勾多行模式了
写好正则表达式之后就很简单了,这个网站也很简单,直接https://ascii2d.net/search/url/在这个链接后面加上要搜索的图片的链接他就能自动搜索了。
刚开始我没改UA他把我当爬虫拦截了,把UA改成正常浏览器的UA就能正常请求到html页面了。
然后我用正则表达式提取了图片的名字、预览图链接以及图片来源地址等信息,然后这一个接口就也做好了。
trace.moe
然后我就在想搜图都做了两个了,这不再做一个搜番?然后就想到了trace.moe的搜番。
soruly/trace.moe: Anime Scene Search by Image (github.com)
这个是有提供官方API的,API文档在这里:API Docs (soruly.github.io)
调用方法也很简单,传一个url参数就行了。他也是返回一个json可以直接解析出相似度、番剧名称、预览缩略图、精确定位的时间等信息。
然后这个信息默认是有罗马音、英语、日语原版的番剧名,没有中文。
在文档的指引下我又找到了一个根据anime_id来查询中文名称的接口,写了个查询信息的辅助函数。
soruly/anilist-chinese: Translate anime titles on anilist.co to Chinese (github.com)
调用也很简单,把anime_id和要查询的信息query拿post发给接口就能有结果。
这样根据番剧截图搜番也做好了。
AnimeTrace
放群里用了一会,发现总有怪群友拿不是番剧截图的图,或者那种画师画的图去搜番,这怎么能搜得到?
于是我想到之前有一个二次元人脸识别的项目,可以上传一张图片,然后他会自动识别里面的二次元人脸然后自动识别出是哪部番里的什么角色。
AnimeTrace-以图识番-在线AI识番引擎|日漫识别|动漫查询|动漫基因库 (animedb.cn)
我看到这个网站顶栏里有个Github还挺高兴,想着又可以参考代码了,不用自己抓了,太好了。
结果照着他的代码用requests写了个测试,要么被他的宝塔防火墙拦截了说非法请求(宝塔WAF提醒您,from-data 请求异常,拒绝访问,如有误报请点击误报),要么就是接口报错说content type error。
然后去github搜其他的代码,第一个注释写了个“# fuck fuck fuck, only httpx can pass waf”,看来他也卡住了,,
于是我试着到他的在线网页去抓接口,接口地址和参数倒是都和开源的里面一样,就是content-type有点小问题,他是multipart/form-data; boundary=----WebKitFormBoundaryxxxxxx,我直接写前面的multipart/form-data;不行,我查了一下后面这个是浏览器自动生成的一个划分二进制数据和表单数据的标识,然后我又查了一下可以用requests的工具库——requests-toolbelt解决。
用其中的MultipartEncoder就能构建出这样一个同时带有文件和form表单数据的对象,'Content-Type'就是encoder.content_type,data就是encoder,这样他会自动构建这样一个对象然后自动生成boundary。
文件官方示例给的是直接open文件的方式,我要作为qq机器人用嘛,就试着拿requests直接请求然后吧content二进制数据直接传给他,他不认。
然后又参考了一下之前那个说fuck的代码,他是把文件名、文件二进制数据和文件类型以一个元祖的方式传过去的。
import requests from requests_toolbelt import MultipartEncoder def get_animedb_info(img_url, model='anime_model_lovelive'): image_data_content = requests.get(img_url).content encoder = MultipartEncoder( { 'image': ('test.jpg', image_data_content, "image/jpeg"), } ) headers = { 'Content-Type': encoder.content_type, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47', 'Referer': 'https://ai.animedb.cn/', 'Origin': 'https://ai.animedb.cn' } result_json = requests.post(f'{API_URL_ANIME_DB}ai/api/detect?force_one=1&model={model}&ai_detect=0', headers=headers, data=encoder).json()
我就这样照着超了一下然后终于成了。
返回的也是json数据,box就是对原图裁切一个小方框确定头像的位置,data里面就有角色名称和番剧名称了。
请求这个接口可以选择模型,其中还有搜索galgame角色的模型可以使用,于是又顺便做了个搜galgame的口令给机器人接上了。
搜图命令识别
然后关于搜图命令识别我做了两种方式,一种是发送的消息是“小苦瓜搜图”并且这条消息附带想要搜的图片。
后来发现这种方式可能不太方便,于是又加了一个回复搜图的方式,先发送图片,然后回复这张图片输入“小苦瓜搜图”,也能搜索这张图片。(回复里面会有[CQ:reoply,消息id],把这个消息id传到机器人查询消息的接口就能获取到所回复的消息的内容了,然后就能搜图了)。
在go-cqhttp中图片消息大概都是[CQ:image,file=xxx,url=xxx],这个url就是图片的链接地址,所以用图片链接的方式请求接口搜图非常方便。
消息发送失败(被腾讯屏蔽的消息)
最近发现有一些搜索结果会发不出来,本来以为是图片太涩了和谐了,但是对图片处理过之后还是发不出去,这才发现腾讯有关键字屏蔽,带有某些链接的消息是发不出去的。
这里我收集了一大堆链接,带有这些链接的消息全都发不出去,下面是我的解决方案,替换一下点。
# 1.腾讯会屏蔽部分链接,尝试替换链接发送 # https://yande.re/post/show/1114163 # https://yande.re/post/show/1114166 # https://yande.re/post/show/ status_1_flag = 0 if 'yande.re' in msg: msg = msg.replace('yande.re', 'yande点re') status_1_flag = 1 # https://danbooru.donmai.us/post/show/4542141 # https://danbooru.donmai.us/post/show if 'danbooru.donmai.us' in msg: msg = msg.replace('danbooru.donmai.us', 'danbooru.donmai点us') status_1_flag = 1 # https://gelbooru.com/index.php?page=post&s=view&id=9283591 # gelbooru.com/index.php if 'gelbooru.com' in msg: msg = msg.replace('gelbooru.com', 'gelbooru点com') status_1_flag = 1 # https://nijie.info/view.php?id=223719 # https://nijie.info/view.php if 'nijie.info' in msg: msg = msg.replace('nijie.info', 'nijie点info') status_1_flag = 1 # https://myanimelist.net/manga/12471/ # https://myanimelist.net/manga/ if 'myanimelist.net' in msg: msg = msg.replace('myanimelist.net', 'myanimelist点net') status_1_flag = 1 # https://mangadex.org/chapter/e99e5ebf-f6b7-4265-9f0b-985efcaa227e/ # https://mangadex.org if 'mangadex.org' in msg: msg = msg.replace('mangadex.org', 'mangadex点org') status_1_flag = 1 # https://e621.net/post/show/1030828 # https://e621.net/post if 'e621.net' in msg: msg = msg.replace('e621.net', 'e621点net') status_1_flag = 1 # https://www.furaffinity.net/view/32615121 # https://www.furaffinity.net if 'furaffinity.net' in msg: msg = msg.replace('furaffinity.net', 'furaffinity点net') status_1_flag = 1 # https://www.fakku.net/hentai/from-drunk-streams-to-horny-posting-english # https://www.fakku.net if 'www.fakku.net' in msg: msg = msg.replace('www.fakku.net', 'www.fakku点net') status_1_flag = 1 if status_1_flag == 1: res_info = send_group_msg(msg, group_id) return res_info
本文地址:https://blog.jixiaob.cn/?post=106
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。