Metadata-Version: 2.4
Name: touhou-world
Version: 0.1.0
Summary: Touhou Project character data query package, powered by thwiki.cc
Author: Patchouli-CN
License: MIT
Project-URL: Homepage, https://github.com/Patchouli-CN/touhou-world
Project-URL: Repository, https://github.com/Patchouli-CN/touhou-world
Project-URL: Issues, https://github.com/Patchouli-CN/touhou-world/issues
Keywords: touhou,东方,thbwiki,anime,game
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9
Requires-Dist: requests>=2.28
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: msgspec>=0.18
Requires-Dist: loguru>=0.7

# touhou-world

A Python package for querying [Touhou Project](https://en.wikipedia.org/wiki/Touhou_Project) character data, powered by [thwiki.cc](https://thwiki.cc).

```
pip install touhou-world
```

---

## Quick Start

### Async (recommended)

```python
import asyncio
from TouhouWorld import TouhouWorld

async def main():
    async with TouhouWorld() as tw:
        char = await tw.get_character("博丽灵梦")
        print(char.name, char.species, char.ability)

        names = await tw.all_characters()
        print(f"Total characters: {len(names)}")

asyncio.run(main())
```

### Sync

```python
from TouhouWorld import CharacterFetcher

with CharacterFetcher() as f:
    char = f.get("雾雨魔理沙")
    print(char)

    results = f.search("咲夜")
    print(results)
```

---

## Character Model

```python
class Character(msgspec.Struct, frozen=True):
    name: str                        # 中文名
    name_jp: str | None              # 日文名
    name_en: str | None              # 英文名
    species: str | None              # 种族
    ability: str | None              # 能力
    occupation: str | None           # 职业/身份
    location: str | None             # 居所
    first_appearance: str | None     # 初登场作品
    appearances: tuple[str, ...]     # 所有登场作品
    theme: str | None                # 主题曲
    source_url: str | None           # 数据来源页面
```

---

## TouhouWorld API

```python
async with TouhouWorld() as tw:
    # 查询单个角色
    char = await tw.get_character("十六夜咲夜")

    # 并发查询多个
    chars = await tw.get_characters(["博丽灵梦", "雾雨魔理沙"])

    # 搜索
    results = await tw.search_characters("红魔馆", limit=5)

    # 确认是否存在
    exists = await tw.character_exists("帕秋莉·诺蕾姬")

    # 所有官方角色名
    names = await tw.all_characters()
```

---

## Custom DataSource

Implement `IDataSource` (async) or `ISyncDataSource` (sync) to plug in your own backend.

```python
from TouhouWorld import TouhouWorld, IDataSource, AsyncCharacterFetcher

class MyDataSource(IDataSource):
    async def get_character(self, name): ...
    async def get_characters(self, names): ...
    async def search_characters(self, query, limit): ...
    async def all_characters(self): ...
    async def close(self): ...

async with TouhouWorld(datasource=MyDataSource()) as tw:
    char = await tw.get_character("博丽灵梦")

# Or use the fetcher layer directly
async with AsyncCharacterFetcher(datasource=MyDataSource()) as f:
    char = await f.get("博丽灵梦")
```

---

## Plugin System

```python
from TouhouWorld import Plugin, PluginBase, PluginConfig, TouhouWorld

class MyConfig(PluginConfig):
    prefix: str = "[LOG]"

@Plugin("logger", version="1.0.0", description="Log every ready event", config_class=MyConfig)
class LoggerPlugin(PluginBase):
    async def on_load(self, world):
        print(f"{self.config.prefix} loaded")

    async def on_ready(self, world):
        print(f"{self.config.prefix} world is ready")

    async def on_stop(self, world):
        print(f"{self.config.prefix} stopping")

async with TouhouWorld() as tw:
    await tw.plugins.load_all(configs={"logger": {"prefix": "[TOUHOU]"}})
```

### Plugin lifecycle

```
on_load → on_ready → [running] → on_stop → on_unload
```

Any hook that raises will call `on_error` and be isolated — other plugins continue normally.

### Load from module or directory

```python
# By module path (importlib)
await tw.plugins.load_module("myapp.plugins.logger")

# Scan a directory for all plugin files
await tw.plugins.load_directory("plugins/")
```

---

## Dependencies

| Package | Purpose |
|---|---|
| `aiohttp` | Async HTTP |
| `requests` | Sync HTTP |
| `beautifulsoup4` | HTML parsing |
| `msgspec` | Fast data models |
| `loguru` | Logging |

---

## License

MIT
