Skip to content

Commit 4619d23

Browse files
committed
refactor: 重构缓存相关逻辑,以及触发额外搜图条件时先发送之前的搜图结果,提高响应速度
1 parent 181eb21 commit 4619d23

File tree

7 files changed

+109
-117
lines changed

7 files changed

+109
-117
lines changed

YetAnotherPicSearch/__init__.py

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import asyncio
22
import re
3-
from asyncio import Lock
43
from collections import defaultdict
54
from contextlib import suppress
6-
from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union
5+
from typing import (
6+
Any,
7+
DefaultDict,
8+
Dict,
9+
List,
10+
Optional,
11+
Tuple,
12+
Union,
13+
)
714

815
import arrow
916
from aiohttp import ClientSession
10-
from diskcache import Cache
17+
from cachetools import TTLCache
1118
from nonebot.adapters.onebot.v11 import (
1219
ActionFailed,
1320
Bot,
@@ -22,20 +29,31 @@
2229
from nonebot.plugin.on import on_message, on_metaevent
2330
from nonebot.rule import Rule
2431
from PicImageSearch import Network
32+
from shelved_cache import PersistentCache
2533
from tenacity import retry, stop_after_attempt, stop_after_delay
2634

2735
from .ascii2d import ascii2d_search
2836
from .baidu import baidu_search
29-
from .cache import exist_in_cache, upsert_cache
3037
from .config import config
3138
from .ehentai import ehentai_search
3239
from .iqdb import iqdb_search
3340
from .saucenao import saucenao_search
34-
from .utils import DEFAULT_HEADERS, get_bot_friend_list, handle_img, handle_reply_msg
41+
from .utils import (
42+
DEFAULT_HEADERS,
43+
SEARCH_FUNCTION_TYPE,
44+
get_bot_friend_list,
45+
handle_reply_msg,
46+
)
3547

3648
sending_lock: DefaultDict[Tuple[Union[int, str], str], asyncio.Lock] = defaultdict(
3749
asyncio.Lock
3850
)
51+
pic_search_cache = PersistentCache(
52+
TTLCache,
53+
filename="pic_search_cache",
54+
maxsize=config.cache_expire * 100,
55+
ttl=config.cache_expire * 24 * 60 * 60,
56+
)
3957

4058

4159
def check_first_connect(_: LifecycleMetaEvent) -> bool:
@@ -91,42 +109,58 @@ async def handle_first_receive(event: MessageEvent, matcher: Matcher) -> None:
91109

92110

93111
async def image_search(
112+
bot: Bot,
113+
event: MessageEvent,
94114
url: str,
95115
md5: str,
96116
mode: str,
97117
purge: bool,
98-
_cache: Cache,
99118
client: ClientSession,
100-
) -> List[str]:
119+
index: Optional[int] = None,
120+
) -> None:
101121
url = await get_universal_img_url(url)
102-
if not purge and (result := exist_in_cache(_cache, md5, mode)):
103-
return [f"[缓存] {i}" for i in result]
122+
cache_key = f"{md5}_{mode}"
104123
try:
105-
result = await handle_search_mode(url, md5, mode, _cache, client)
124+
if not purge and cache_key in pic_search_cache:
125+
result, extra_handle = pic_search_cache[cache_key]
126+
await send_result_message(bot, event, [f"[缓存] {i}" for i in result], index)
127+
if callable(extra_handle):
128+
await send_result_message(
129+
bot, event, await extra_handle(url, client), index
130+
)
131+
return
132+
133+
result, extra_handle = await handle_search_mode(url, md5, mode, client)
134+
await send_result_message(bot, event, result, index)
135+
if callable(extra_handle):
136+
await send_result_message(
137+
bot, event, await extra_handle(url, client), index
138+
)
106139
except Exception as e:
107140
logger.exception(f"该图 [{url}] 搜图失败")
108-
result = [f"该图搜图失败\nE: {repr(e)}"]
109-
return result
141+
await send_result_message(bot, event, [f"该图搜图失败\nE: {repr(e)}"], index)
110142

111143

112144
@retry(stop=(stop_after_attempt(3) | stop_after_delay(30)), reraise=True)
113145
async def handle_search_mode(
114-
url: str, md5: str, mode: str, _cache: Cache, client: ClientSession
115-
) -> List[str]:
146+
url: str, md5: str, mode: str, client: ClientSession
147+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
148+
extra_handle = None
149+
116150
if mode == "a2d":
117151
result = await ascii2d_search(url, client)
118152
elif mode == "ex":
119-
result = await ehentai_search(url, client)
153+
result, extra_handle = await ehentai_search(url, client)
120154
elif mode == "iqdb":
121-
result = await iqdb_search(url, client)
155+
result, extra_handle = await iqdb_search(url, client)
122156
elif mode == "baidu":
123157
result = await baidu_search(url, client)
124158
else:
125-
result = await saucenao_search(url, client, mode)
126-
# 仅对涉及到 saucenao 的搜图结果做缓存
127-
upsert_cache(_cache, md5, mode, result)
159+
result, extra_handle = await saucenao_search(url, client, mode)
128160

129-
return result
161+
pic_search_cache[f"{md5}_{mode}"] = result, extra_handle
162+
163+
return result, extra_handle
130164

131165

132166
async def get_universal_img_url(url: str) -> str:
@@ -196,7 +230,7 @@ async def send_message_with_lock(
196230
bot: Bot,
197231
event: MessageEvent,
198232
msg_list: List[str],
199-
current_sending_lock: Lock,
233+
current_sending_lock: asyncio.Lock,
200234
index: Optional[int] = None,
201235
) -> None:
202236
start_time = arrow.now()
@@ -269,14 +303,16 @@ async def handle_image_search(bot: Bot, event: MessageEvent, matcher: Matcher) -
269303
else Network(proxies=config.proxy)
270304
)
271305
async with network as client:
272-
with Cache("picsearch_cache") as _cache:
273-
for index, (url, md5) in enumerate(image_urls_with_md5):
274-
await send_result_message(
275-
bot,
276-
event,
277-
await image_search(url, md5, mode, purge, _cache, client),
278-
index if len(image_urls_with_md5) > 1 else None,
279-
)
280-
_cache.expire()
306+
for index, (url, md5) in enumerate(image_urls_with_md5):
307+
await image_search(
308+
bot,
309+
event,
310+
url,
311+
md5,
312+
mode,
313+
purge,
314+
client,
315+
index if len(image_urls_with_md5) > 1 else None,
316+
)
281317

282318
await bot.delete_msg(message_id=searching_tips["message_id"])

YetAnotherPicSearch/cache.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

YetAnotherPicSearch/ehentai.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from asyncio import sleep
44
from collections import defaultdict
55
from difflib import SequenceMatcher
6-
from typing import Any, Dict, List
6+
from typing import Any, Dict, List, Optional, Tuple
77

88
import arrow
99
from aiohttp import ClientSession
@@ -13,7 +13,7 @@
1313

1414
from .ascii2d import ascii2d_search
1515
from .config import config
16-
from .utils import DEFAULT_HEADERS, handle_img, shorten_url
16+
from .utils import DEFAULT_HEADERS, SEARCH_FUNCTION_TYPE, handle_img, shorten_url
1717

1818
EHENTAI_HEADERS = (
1919
{"Cookie": config.exhentai_cookies, **DEFAULT_HEADERS}
@@ -22,7 +22,9 @@
2222
)
2323

2424

25-
async def ehentai_search(url: str, client: ClientSession) -> List[str]:
25+
async def ehentai_search(
26+
url: str, client: ClientSession
27+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
2628
ex = bool(config.exhentai_cookies)
2729
ehentai = EHentai(client=client)
2830

@@ -40,10 +42,11 @@ async def ehentai_search(url: str, client: ClientSession) -> List[str]:
4042

4143
if not res.raw and config.auto_use_ascii2d:
4244
final_res.append("自动使用 Ascii2D 进行搜索")
43-
final_res.extend(await ascii2d_search(url, client))
44-
return final_res
45+
return final_res, ascii2d_search
4546

46-
return ["EHentai 暂时无法使用"]
47+
return final_res, None
48+
49+
return ["EHentai 暂时无法使用"], None
4750

4851

4952
async def ehentai_title_search(title: str) -> List[str]:

YetAnotherPicSearch/iqdb.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
from typing import List
1+
from typing import List, Optional, Tuple
22

33
from aiohttp import ClientSession
44
from PicImageSearch import Iqdb
55
from yarl import URL
66

77
from .ascii2d import ascii2d_search
88
from .config import config
9-
from .utils import get_source, handle_img, shorten_url
9+
from .utils import SEARCH_FUNCTION_TYPE, get_source, handle_img, shorten_url
1010

1111

12-
async def iqdb_search(url: str, client: ClientSession) -> List[str]:
12+
async def iqdb_search(
13+
url: str, client: ClientSession
14+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
1315
iqdb = Iqdb(client=client)
1416
res = await iqdb.search(url)
1517
if not res.raw:
16-
return ["Iqdb 暂时无法使用"]
18+
return ["Iqdb 暂时无法使用,自动使用 Ascii2D 进行搜索"], ascii2d_search
1719

1820
final_res: List[str] = []
1921
# 如果遇到搜索结果相似度低的情况,去除第一个只有提示信息的空结果
@@ -49,6 +51,6 @@ async def iqdb_search(url: str, client: ClientSession) -> List[str]:
4951

5052
if low_acc and config.auto_use_ascii2d:
5153
final_res.append(f"相似度 {selected_res.similarity}% 过低,自动使用 Ascii2D 进行搜索")
52-
final_res.extend(await ascii2d_search(url, client))
54+
return final_res, ascii2d_search
5355

54-
return final_res
56+
return final_res, None

YetAnotherPicSearch/saucenao.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22
from asyncio import sleep
3-
from typing import List
3+
from typing import List, Optional, Tuple
44

55
from aiohttp import ClientSession
66
from PicImageSearch import SauceNAO
@@ -10,7 +10,7 @@
1010
from .ascii2d import ascii2d_search
1111
from .config import config
1212
from .ehentai import ehentai_title_search
13-
from .utils import get_source, handle_img, shorten_url
13+
from .utils import SEARCH_FUNCTION_TYPE, get_source, handle_img, shorten_url
1414
from .whatanime import whatanime_search
1515

1616
SAUCENAO_DB = {
@@ -23,7 +23,9 @@
2323
}
2424

2525

26-
async def saucenao_search(url: str, client: ClientSession, mode: str) -> List[str]:
26+
async def saucenao_search(
27+
url: str, client: ClientSession, mode: str
28+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
2729
db = SAUCENAO_DB[mode]
2830
if isinstance(db, list):
2931
saucenao = SauceNAO(
@@ -51,11 +53,10 @@ async def saucenao_search(url: str, client: ClientSession, mode: str) -> List[st
5153

5254
if not res or not res.raw:
5355
final_res = ["SauceNAO 暂时无法使用,自动使用 Ascii2D 进行搜索"]
54-
final_res.extend(await ascii2d_search(url, client))
55-
return final_res
56+
return final_res, ascii2d_search
5657

5758
selected_res = get_best_result(res, res.raw[0])
58-
return await get_final_res(url, client, mode, res, selected_res)
59+
return await get_final_res(mode, res, selected_res)
5960

6061

6162
def get_best_pixiv_result(
@@ -90,12 +91,8 @@ def get_best_result(res: SauceNAOResponse, selected_res: SauceNAOItem) -> SauceN
9091

9192

9293
async def get_final_res(
93-
url: str,
94-
client: ClientSession,
95-
mode: str,
96-
res: SauceNAOResponse,
97-
selected_res: SauceNAOItem,
98-
) -> List[str]:
94+
mode: str, res: SauceNAOResponse, selected_res: SauceNAOItem
95+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
9996
low_acc = selected_res.similarity < config.saucenao_low_acc
10097
hide_img = config.hide_img or (
10198
selected_res.hidden or low_acc and config.hide_img_when_low_acc
@@ -133,7 +130,9 @@ async def get_final_res(
133130
final_res.append("\n".join([i for i in res_list if i]))
134131

135132
if low_acc:
136-
final_res.extend(await handle_saucenao_low_acc(url, client, mode, selected_res))
133+
extra_res, extra_handle = await handle_saucenao_low_acc(mode, selected_res)
134+
final_res.extend(extra_res)
135+
return final_res, extra_handle
137136
elif selected_res.index_id in SAUCENAO_DB["doujin"]: # type: ignore
138137
title = selected_res.title.replace("-", "")
139138
final_res.extend(await ehentai_title_search(title))
@@ -143,23 +142,20 @@ async def get_final_res(
143142
await ehentai_title_search(f"{selected_res.author} {selected_res.title}")
144143
)
145144
elif selected_res.index_id in SAUCENAO_DB["anime"]: # type: ignore
146-
final_res.extend(await whatanime_search(url, client))
145+
return final_res, whatanime_search
147146

148-
return final_res
147+
return final_res, None
149148

150149

151150
async def handle_saucenao_low_acc(
152-
url: str,
153-
client: ClientSession,
154-
mode: str,
155-
selected_res: SauceNAOItem,
156-
) -> List[str]:
157-
final_res = []
151+
mode: str, selected_res: SauceNAOItem
152+
) -> Tuple[List[str], Optional[SEARCH_FUNCTION_TYPE]]:
153+
final_res: List[str] = []
158154
# 因为 saucenao 的动画搜索数据库更新不够快,所以当搜索模式为动画时额外增加 whatanime 的搜索结果
159155
if mode == "anime":
160-
final_res.extend(await whatanime_search(url, client))
156+
return final_res, whatanime_search
161157
elif config.auto_use_ascii2d:
162158
final_res.append(f"相似度 {selected_res.similarity}% 过低,自动使用 Ascii2D 进行搜索")
163-
final_res.extend(await ascii2d_search(url, client))
159+
return final_res, ascii2d_search
164160

165-
return final_res
161+
return final_res, None

0 commit comments

Comments
 (0)