-
Notifications
You must be signed in to change notification settings - Fork 46
/
chara.py
222 lines (183 loc) · 6.99 KB
/
chara.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import importlib
from io import BytesIO
import pygtrie
import requests
from fuzzywuzzy import fuzz, process
from PIL import Image
import hoshino
from hoshino import R, log, sucmd, util
from hoshino.typing import CommandSession
from . import _pcr_data
logger = log.new_logger('chara', hoshino.config.DEBUG)
UNKNOWN = 1000
UnavailableChara = {
1067, # 穗希
1068, # 晶
1069, # 霸瞳
1072, # 可萝爹
1073, # 拉基拉基
1102, # 泳装大眼
}
try:
gadget_equip = R.img('priconne/gadget/equip.png').open()
gadget_star = R.img('priconne/gadget/star.png').open()
gadget_star_dis = R.img('priconne/gadget/star_disabled.png').open()
gadget_star_pink = R.img('priconne/gadget/star_pink.png').open()
unknown_chara_icon = R.img(f'priconne/unit/icon_unit_{UNKNOWN}31.png').open()
except Exception as e:
logger.exception(e)
class Roster:
def __init__(self):
self._roster = pygtrie.CharTrie()
self.update()
def update(self):
importlib.reload(_pcr_data)
self._roster.clear()
for idx, names in _pcr_data.CHARA_NAME.items():
for n in names:
n = util.normalize_str(n)
if n not in self._roster:
self._roster[n] = idx
else:
logger.warning(f'priconne.chara.Roster: 出现重名{n}于id{idx}与id{self._roster[n]}')
self._all_name_list = self._roster.keys()
def get_id(self, name):
name = util.normalize_str(name)
return self._roster[name] if name in self._roster else UNKNOWN
def guess_id(self, name):
"""@return: id, name, score"""
name, score = process.extractOne(name, self._all_name_list)
return self._roster[name], name, score
def parse_team(self, namestr):
"""@return: List[ids], unknown_namestr"""
namestr = util.normalize_str(namestr)
team = []
unknown = []
while namestr:
item = self._roster.longest_prefix(namestr)
if not item:
unknown.append(namestr[0])
namestr = namestr[1:].lstrip()
else:
team.append(item.value)
namestr = namestr[len(item.key):].lstrip()
return team, ''.join(unknown)
roster = Roster()
def name2id(name):
return roster.get_id(name)
def fromid(id_, star=0, equip=0):
return Chara(id_, star, equip)
def fromname(name, star=0, equip=0):
id_ = name2id(name)
return Chara(id_, star, equip)
def guess_id(name):
"""@return: id, name, score"""
return roster.guess_id(name)
def is_npc(id_):
if id_ in UnavailableChara:
return True
else:
return not ((1000 < id_ < 1200) or (1800 < id_ < 1900))
def gen_team_pic(team, size=64, star_slot_verbose=True):
num = len(team)
des = Image.new('RGBA', (num*size, size), (255, 255, 255, 255))
for i, chara in enumerate(team):
src = chara.render_icon(size, star_slot_verbose)
des.paste(src, (i * size, 0), src)
return des
def download_chara_icon(id_, star):
url = f'https://redive.estertion.win/icon/unit/{id_}{star}1.webp'
save_path = R.img(f'priconne/unit/icon_unit_{id_}{star}1.png').path
logger.info(f'Downloading chara icon from {url}')
try:
rsp = requests.get(url, stream=True, timeout=5)
except Exception as e:
logger.error(f'Failed to download {url}. {type(e)}')
logger.exception(e)
if 200 == rsp.status_code:
img = Image.open(BytesIO(rsp.content))
img.save(save_path)
logger.info(f'Saved to {save_path}')
else:
logger.error(f'Failed to download {url}. HTTP {rsp.status_code}')
class Chara:
def __init__(self, id_, star=0, equip=0):
self.id = id_
self.star = star
self.equip = equip
@property
def name(self):
return _pcr_data.CHARA_NAME[self.id][0] if self.id in _pcr_data.CHARA_NAME else _pcr_data.CHARA_NAME[UNKNOWN][0]
@property
def is_npc(self) -> bool:
return is_npc(self.id)
@property
def icon(self):
star = '3' if 1 <= self.star <= 5 else '6'
res = R.img(f'priconne/unit/icon_unit_{self.id}{star}1.png')
if not res.exist:
res = R.img(f'priconne/unit/icon_unit_{self.id}31.png')
if not res.exist:
res = R.img(f'priconne/unit/icon_unit_{self.id}11.png')
if not res.exist: # FIXME: 不方便改成异步请求
download_chara_icon(self.id, 6)
download_chara_icon(self.id, 3)
download_chara_icon(self.id, 1)
res = R.img(f'priconne/unit/icon_unit_{self.id}{star}1.png')
if not res.exist:
res = R.img(f'priconne/unit/icon_unit_{self.id}31.png')
if not res.exist:
res = R.img(f'priconne/unit/icon_unit_{self.id}11.png')
if not res.exist:
res = R.img(f'priconne/unit/icon_unit_{UNKNOWN}31.png')
return res
def render_icon(self, size, star_slot_verbose=True) -> Image:
try:
pic = self.icon.open().convert('RGBA').resize((size, size), Image.LANCZOS)
except FileNotFoundError:
logger.error(f'File not found: {self.icon.path}')
pic = unknown_chara_icon.convert('RGBA').resize((size, size), Image.LANCZOS)
l = size // 6
star_lap = round(l * 0.15)
margin_x = ( size - 6*l ) // 2
margin_y = round(size * 0.05)
if self.star:
for i in range(5 if star_slot_verbose else min(self.star, 5)):
a = i*(l-star_lap) + margin_x
b = size - l - margin_y
s = gadget_star if self.star > i else gadget_star_dis
s = s.resize((l, l), Image.LANCZOS)
pic.paste(s, (a, b, a+l, b+l), s)
if 6 == self.star:
a = 5*(l-star_lap) + margin_x
b = size - l - margin_y
s = gadget_star_pink
s = s.resize((l, l), Image.LANCZOS)
pic.paste(s, (a, b, a+l, b+l), s)
if self.equip:
l = round(l * 1.5)
a = margin_x
b = margin_x
s = gadget_equip.resize((l, l), Image.LANCZOS)
pic.paste(s, (a, b, a+l, b+l), s)
return pic
@sucmd('reload-pcr-chara', force_private=False, aliases=('重载花名册'))
async def reload_pcr_chara(session: CommandSession):
try:
roster.update()
await session.send('ok')
except Exception as e:
logger.exception(e)
await session.send(f'Error: {type(e)}')
@sucmd('download-pcr-chara-icon', force_private=False, aliases=('下载角色头像'))
async def download_pcr_chara_icon(session: CommandSession):
try:
id_ = roster.get_id(session.current_arg_text.strip())
assert id_ != UNKNOWN, '未知角色名'
download_chara_icon(id_, 6)
download_chara_icon(id_, 3)
download_chara_icon(id_, 1)
await session.send('ok')
except Exception as e:
logger.exception(e)
await session.send(f'Error: {type(e)}')