StarRailCopilot/module/ocr/keyword.py
Hengyu 012fffbb31
Add: daily_request_recognition; get_daily_rewards (#3)
* Add: daily_request_recognition; get_daily_rewards

* Fix: typo

* Fix: typo

* Upd: delete eval; use one loop to handle 5 active point rewards;

* Upd: change DAILY_QUEST_GOTO/REWARD pattern; update swipe private method name; extract keyword compare method

* Upd: move warning to single page recognition

* Upd: merge from main

* Add: methods that load daily quests keywords from QuestData.json

* Upd: avoid read TextMap twice

* Upd: revert Keyword.find method

* Add: preprocess of keyword extract; after_process of daily quest ocr

* Upd: move assets to daily/reward

* Upd: simplify ocr result replacement
2023-06-13 00:24:38 +08:00

162 lines
4.3 KiB
Python

import re
from dataclasses import dataclass
from functools import cached_property
from typing import ClassVar
from module.exception import ScriptError
import module.config.server as server
REGEX_PUNCTUATION = re.compile(r'[ ,.\'",。\-—/\\\n\t()()「」『』【】]')
def parse_name(n):
n = REGEX_PUNCTUATION.sub('', str(n)).lower()
return n
def text_to_variable(text):
text = re.sub(r'[ \-]', '_', text)
text = re.sub(r'[(),#]|</?\w+>|\'s', '', text)
text = re.sub(r'[#_]?\d+(_times?)?', '', text)
return text
@dataclass
class Keyword:
id: int
cn: str
en: str
jp: str
cht: str
"""
Instance attributes and methods
"""
@cached_property
def cn_parsed(self) -> str:
return parse_name(self.cn)
@cached_property
def en_parsed(self) -> str:
return parse_name(self.en)
@cached_property
def jp_parsed(self) -> str:
return parse_name(self.jp)
@cached_property
def cht_parsed(self) -> str:
return parse_name(self.cht)
@cached_property
def name(self) -> str:
return text_to_variable(self.en)
def __str__(self):
return self.name
__repr__ = __str__
def __eq__(self, other):
return str(self) == str(other)
def __hash__(self):
return hash(self.name)
def __bool__(self):
return True
def _keywords_to_find(self, in_current_server=False, ignore_punctuation=True):
if in_current_server:
match server.server:
case 'cn':
if ignore_punctuation:
return [self.cn_parsed]
else:
return [self.cn]
case 'en':
if ignore_punctuation:
return [self.en_parsed]
else:
return [self.en]
case 'jp':
if ignore_punctuation:
return [self.jp_parsed]
else:
return [self.jp]
case 'cht':
if ignore_punctuation:
return [self.cht_parsed]
else:
return [self.cht]
else:
if ignore_punctuation:
return [
self.cn_parsed,
self.en_parsed,
self.jp_parsed,
self.cht_parsed,
]
else:
return [
self.cn,
self.en,
self.jp,
self.cht,
]
"""
Class attributes and methods
Note that dataclasses inherited `Keyword` must override `instances` attribute,
or `instances` will still be a class attribute of base class.
```
@dataclass
class DungeonNav(Keyword):
instances: ClassVar = {}
```
"""
instances: ClassVar = {}
def __post_init__(self):
self.__class__.instances[self.id] = self
@classmethod
def find(cls, name, in_current_server=False, ignore_punctuation=True):
"""
Args:
name: Name in any server or instance id.
in_current_server: True to search the names from current server only.
ignore_punctuation: True to remove punctuations and turn into lowercase before searching.
Returns:
Keyword instance.
Raises:
ScriptError: If nothing found.
"""
# Already a keyword
if isinstance(name, Keyword):
return name
# Probably an ID
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
try:
return cls.instances[int(name)]
except KeyError:
pass
if ignore_punctuation:
name = parse_name(name)
else:
name = str(name)
instance: Keyword
for instance in cls.instances.values():
for keyword in instance._keywords_to_find(
in_current_server=in_current_server, ignore_punctuation=ignore_punctuation):
if name == keyword:
return instance
raise ScriptError(f'Cannot find a {cls.__name__} instance that matches "{name}"')