mirror of
https://github.com/283375/arcaea-offline.git
synced 2025-07-02 04:36:26 +00:00
Compare commits
4 Commits
v0.2.2
...
bb39a5912b
Author | SHA1 | Date | |
---|---|---|---|
bb39a5912b
|
|||
b78040a795
|
|||
2204338a5e
|
|||
55e76ef650
|
@ -10,10 +10,8 @@ description = "Manage your local Arcaea score database."
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"beautifulsoup4==4.12.2",
|
|
||||||
"SQLAlchemy==2.0.20",
|
"SQLAlchemy==2.0.20",
|
||||||
"SQLAlchemy-Utils==0.41.1",
|
"SQLAlchemy-Utils==0.41.1",
|
||||||
"Whoosh==2.7.4",
|
|
||||||
]
|
]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
|
@ -1,4 +1,2 @@
|
|||||||
beautifulsoup4==4.12.2
|
|
||||||
SQLAlchemy==2.0.20
|
SQLAlchemy==2.0.20
|
||||||
SQLAlchemy-Utils==0.41.1
|
SQLAlchemy-Utils==0.41.1
|
||||||
Whoosh==2.7.4
|
|
||||||
|
0
src/arcaea_offline/constants/__init__.py
Normal file
0
src/arcaea_offline/constants/__init__.py
Normal file
3
src/arcaea_offline/constants/enums/__init__.py
Normal file
3
src/arcaea_offline/constants/enums/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .clear_type import ArcaeaScoreClearType
|
||||||
|
from .modifier import ArcaeaScoreModifier
|
||||||
|
from .rating_class import ArcaeaRatingClass
|
10
src/arcaea_offline/constants/enums/clear_type.py
Normal file
10
src/arcaea_offline/constants/enums/clear_type.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ArcaeaScoreClearType(IntEnum):
|
||||||
|
TRACK_LOST = 0
|
||||||
|
NORMAL_CLEAR = 1
|
||||||
|
FULL_RECALL = 2
|
||||||
|
PURE_MEMORY = 3
|
||||||
|
HARD_CLEAR = 4
|
||||||
|
EASY_CLEAR = 5
|
7
src/arcaea_offline/constants/enums/modifier.py
Normal file
7
src/arcaea_offline/constants/enums/modifier.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ArcaeaScoreModifier(IntEnum):
|
||||||
|
NORMAL = 0
|
||||||
|
EASY = 1
|
||||||
|
HARD = 2
|
9
src/arcaea_offline/constants/enums/rating_class.py
Normal file
9
src/arcaea_offline/constants/enums/rating_class.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ArcaeaRatingClass(IntEnum):
|
||||||
|
PAST = 0
|
||||||
|
PRESENT = 1
|
||||||
|
FUTURE = 2
|
||||||
|
BEYOND = 3
|
||||||
|
ETERNAL = 4
|
1
src/arcaea_offline/database/__init__.py
Normal file
1
src/arcaea_offline/database/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .db import Database
|
@ -5,10 +5,16 @@ from typing import Iterable, List, Optional, Type, Union
|
|||||||
from sqlalchemy import Engine, func, inspect, select
|
from sqlalchemy import Engine, func, inspect, select
|
||||||
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, sessionmaker
|
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, sessionmaker
|
||||||
|
|
||||||
from .external.arcsong.arcsong_json import ArcSongJsonBuilder
|
from arcaea_offline.external.arcsong.arcsong_json import ArcSongJsonBuilder
|
||||||
from .external.exports import ArcaeaOfflineDEFV2_Score, ScoreExport, exporters
|
from arcaea_offline.external.exports import (
|
||||||
from .models.config import ConfigBase, Property
|
ArcaeaOfflineDEFV2_Score,
|
||||||
from .models.scores import (
|
ScoreExport,
|
||||||
|
exporters,
|
||||||
|
)
|
||||||
|
from arcaea_offline.singleton import Singleton
|
||||||
|
|
||||||
|
from .models.v4.config import ConfigBase, Property
|
||||||
|
from .models.v4.scores import (
|
||||||
CalculatedPotential,
|
CalculatedPotential,
|
||||||
Score,
|
Score,
|
||||||
ScoreBest,
|
ScoreBest,
|
||||||
@ -16,7 +22,7 @@ from .models.scores import (
|
|||||||
ScoresBase,
|
ScoresBase,
|
||||||
ScoresViewBase,
|
ScoresViewBase,
|
||||||
)
|
)
|
||||||
from .models.songs import (
|
from .models.v4.songs import (
|
||||||
Chart,
|
Chart,
|
||||||
ChartInfo,
|
ChartInfo,
|
||||||
Difficulty,
|
Difficulty,
|
||||||
@ -28,7 +34,6 @@ from .models.songs import (
|
|||||||
SongsBase,
|
SongsBase,
|
||||||
SongsViewBase,
|
SongsViewBase,
|
||||||
)
|
)
|
||||||
from .singleton import Singleton
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
0
src/arcaea_offline/database/models/__init__.py
Normal file
0
src/arcaea_offline/database/models/__init__.py
Normal file
@ -1,111 +0,0 @@
|
|||||||
from typing import List, Union
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from whoosh.analysis import NgramFilter, StandardAnalyzer
|
|
||||||
from whoosh.fields import ID, KEYWORD, TEXT, Schema
|
|
||||||
from whoosh.filedb.filestore import RamStorage
|
|
||||||
from whoosh.qparser import FuzzyTermPlugin, MultifieldParser, OrGroup
|
|
||||||
|
|
||||||
from .models.songs import Song, SongLocalized
|
|
||||||
from .utils.search_title import recover_search_title
|
|
||||||
|
|
||||||
|
|
||||||
class Searcher:
|
|
||||||
def __init__(self):
|
|
||||||
self.text_analyzer = StandardAnalyzer() | NgramFilter(minsize=2, maxsize=5)
|
|
||||||
self.song_schema = Schema(
|
|
||||||
song_id=ID(stored=True, unique=True),
|
|
||||||
title=TEXT(analyzer=self.text_analyzer, spelling=True),
|
|
||||||
artist=TEXT(analyzer=self.text_analyzer, spelling=True),
|
|
||||||
source=TEXT(analyzer=self.text_analyzer, spelling=True),
|
|
||||||
keywords=KEYWORD(lowercase=True, stored=True, scorable=True),
|
|
||||||
)
|
|
||||||
self.storage = RamStorage()
|
|
||||||
self.index = self.storage.create_index(self.song_schema)
|
|
||||||
|
|
||||||
self.default_query_parser = MultifieldParser(
|
|
||||||
["song_id", "title", "artist", "source", "keywords"],
|
|
||||||
self.song_schema,
|
|
||||||
group=OrGroup,
|
|
||||||
)
|
|
||||||
self.default_query_parser.add_plugin(FuzzyTermPlugin())
|
|
||||||
|
|
||||||
def import_songs(self, session: Session):
|
|
||||||
writer = self.index.writer()
|
|
||||||
songs = list(session.scalars(select(Song)))
|
|
||||||
song_localize_stmt = select(SongLocalized)
|
|
||||||
for song in songs:
|
|
||||||
stmt = song_localize_stmt.where(SongLocalized.id == song.id)
|
|
||||||
sl = session.scalar(stmt)
|
|
||||||
song_id = song.id
|
|
||||||
possible_titles: List[Union[str, None]] = [song.title]
|
|
||||||
possible_artists: List[Union[str, None]] = [song.artist]
|
|
||||||
possible_sources: List[Union[str, None]] = [song.source]
|
|
||||||
if sl:
|
|
||||||
possible_titles.extend(
|
|
||||||
[sl.title_ja, sl.title_ko, sl.title_zh_hans, sl.title_zh_hant]
|
|
||||||
)
|
|
||||||
possible_titles.extend(
|
|
||||||
recover_search_title(sl.search_title_ja)
|
|
||||||
+ recover_search_title(sl.search_title_ko)
|
|
||||||
+ recover_search_title(sl.search_title_zh_hans)
|
|
||||||
+ recover_search_title(sl.search_title_zh_hant)
|
|
||||||
)
|
|
||||||
possible_artists.extend(
|
|
||||||
recover_search_title(sl.search_artist_ja)
|
|
||||||
+ recover_search_title(sl.search_artist_ko)
|
|
||||||
+ recover_search_title(sl.search_artist_zh_hans)
|
|
||||||
+ recover_search_title(sl.search_artist_zh_hant)
|
|
||||||
)
|
|
||||||
possible_sources.extend(
|
|
||||||
[
|
|
||||||
sl.source_ja,
|
|
||||||
sl.source_ko,
|
|
||||||
sl.source_zh_hans,
|
|
||||||
sl.source_zh_hant,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# remove empty items in list
|
|
||||||
titles = [t for t in possible_titles if t != "" and t is not None]
|
|
||||||
artists = [t for t in possible_artists if t != "" and t is not None]
|
|
||||||
sources = [t for t in possible_sources if t != "" and t is not None]
|
|
||||||
|
|
||||||
writer.update_document(
|
|
||||||
song_id=song_id,
|
|
||||||
title=" ".join(titles),
|
|
||||||
artist=" ".join(artists),
|
|
||||||
source=" ".join(sources),
|
|
||||||
keywords=" ".join([song_id] + titles + artists + sources),
|
|
||||||
)
|
|
||||||
|
|
||||||
writer.commit()
|
|
||||||
|
|
||||||
def did_you_mean(self, string: str):
|
|
||||||
results = set()
|
|
||||||
|
|
||||||
with self.index.searcher() as searcher:
|
|
||||||
corrector_keywords = searcher.corrector("keywords") # type: ignore
|
|
||||||
corrector_song_id = searcher.corrector("song_id") # type: ignore
|
|
||||||
corrector_title = searcher.corrector("title") # type: ignore
|
|
||||||
corrector_artist = searcher.corrector("artist") # type: ignore
|
|
||||||
corrector_source = searcher.corrector("source") # type: ignore
|
|
||||||
|
|
||||||
results.update(corrector_keywords.suggest(string))
|
|
||||||
results.update(corrector_song_id.suggest(string))
|
|
||||||
results.update(corrector_title.suggest(string))
|
|
||||||
results.update(corrector_artist.suggest(string))
|
|
||||||
results.update(corrector_source.suggest(string))
|
|
||||||
|
|
||||||
if string in results:
|
|
||||||
results.remove(string)
|
|
||||||
|
|
||||||
return list(results)
|
|
||||||
|
|
||||||
def search(self, string: str, *, limit: int = 10):
|
|
||||||
query_string = f"{string}"
|
|
||||||
query = self.default_query_parser.parse(query_string)
|
|
||||||
with self.index.searcher() as searcher:
|
|
||||||
results = searcher.search(query, limit=limit)
|
|
||||||
return [result.get("song_id") for result in results]
|
|
Reference in New Issue
Block a user