feat: 更清晰的目录结构,新的markdown构建

This commit is contained in:
远野千束 2024-04-14 21:39:27 +08:00
parent 65dcf36fe7
commit 15a329029d
31 changed files with 269 additions and 141 deletions

View File

@ -9,12 +9,12 @@ from nonebot.exception import MockApiException
from nonebot.internal.matcher import Matcher from nonebot.internal.matcher import Matcher
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from liteyuki.utils.config import get_config, load_from_yaml from liteyuki.utils.base.config import get_config, load_from_yaml
from liteyuki.utils.data_manager import StoredConfig, TempConfig, common_db from liteyuki.utils.base.data_manager import StoredConfig, TempConfig, common_db
from liteyuki.utils.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message import Markdown as md, broadcast_to_superusers from liteyuki.utils.message import *
from liteyuki.utils.reloader import Reloader from liteyuki.utils.base.reloader import Reloader
from .api import update_liteyuki from .api import update_liteyuki
require("nonebot_plugin_alconna"), require("nonebot_plugin_apscheduler") require("nonebot_plugin_alconna"), require("nonebot_plugin_apscheduler")

View File

@ -1,12 +1,9 @@
import os.path
import shutil
import nonebot.plugin import nonebot.plugin
from liteyuki.utils import init_log from liteyuki.utils import init_log
from liteyuki.utils.data_manager import InstalledPlugin, plugin_db from liteyuki.utils.base.data_manager import InstalledPlugin, plugin_db
from liteyuki.utils.resource import load_resource_from_dir, load_resources from liteyuki.utils.base.resource import load_resources
from liteyuki.utils.tools import check_for_package from liteyuki.utils.message.tools import check_for_package
load_resources() load_resources()
init_log() init_log()

View File

@ -1,4 +1,3 @@
import json
import platform import platform
import nonebot import nonebot
@ -7,13 +6,12 @@ from cpuinfo import get_cpu_info
from nonebot import on_command from nonebot import on_command
from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from playwright.async_api import async_playwright
from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml from liteyuki.utils import __NAME__, __VERSION__, load_from_yaml
from liteyuki.utils.htmlrender import template2image from liteyuki.utils.message.html_tool import template2image
from liteyuki.utils.language import Language, get_default_lang, get_user_lang from liteyuki.utils.base.language import Language, get_default_lang, get_user_lang
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.resource import get_path from liteyuki.utils.base.resource import get_path
from liteyuki.utils.tools import convert_size from liteyuki.utils.message.tools import convert_size
stats = on_command("status", aliases={"状态"}, priority=5, permission=SUPERUSER) stats = on_command("status", aliases={"状态"}, priority=5, permission=SUPERUSER)

View File

@ -2,9 +2,9 @@ import nonebot
from nonebot import on_message, require from nonebot import on_message, require
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from liteyuki.utils.data import Database, LiteModel from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna from nonebot_plugin_alconna import on_alconna

View File

@ -1,13 +1,13 @@
import nonebot
from nonebot import on_command from nonebot import on_command
from nonebot.params import CommandArg from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent, v11 from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
md_test = on_command("mdts", aliases={"会话md"}, permission=SUPERUSER) md_test = on_command("mdts", permission=SUPERUSER)
btn_test = on_command("btnts", permission=SUPERUSER)
placeholder = { placeholder = {
"[": "[", "[": "[",
@ -28,6 +28,15 @@ async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
session_id=event.user_id if event.message_type == "private" else event.group_id session_id=event.user_id if event.message_type == "private" else event.group_id
) )
@btn_test.handle()
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
await md.send_btn(
str(arg),
bot,
message_type=event.message_type,
session_id=event.user_id if event.message_type == "private" else event.group_id
)
__author__ = "snowykami" __author__ = "snowykami"
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="轻雪Markdown测试", name="轻雪Markdown测试",

View File

@ -1,6 +1,6 @@
import random import random
from pydantic import BaseModel from pydantic import BaseModel
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md
class Dot(BaseModel): class Dot(BaseModel):

View File

@ -1,7 +1,7 @@
from nonebot import require from nonebot import require
from ...utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from ...utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from .game import Minesweeper from .game import Minesweeper

View File

@ -4,9 +4,9 @@ from typing import Optional
import aiofiles import aiofiles
import nonebot.plugin import nonebot.plugin
from liteyuki.utils.data import LiteModel from liteyuki.utils.base.data import LiteModel
from liteyuki.utils.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db from liteyuki.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
from liteyuki.utils.ly_typing import T_MessageEvent from liteyuki.utils.base.ly_typing import T_MessageEvent
class PluginTag(LiteModel): class PluginTag(LiteModel):

View File

@ -12,12 +12,12 @@ from nonebot.internal.matcher import Matcher
from nonebot.message import run_preprocessor from nonebot.message import run_preprocessor
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.plugin import Plugin from nonebot.plugin import Plugin
from liteyuki.utils.data_manager import InstalledPlugin from liteyuki.utils.base.data_manager import InstalledPlugin
from liteyuki.utils.language import get_user_lang from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.ly_typing import T_Bot from liteyuki.utils.base.ly_typing import T_Bot
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.utils.permission import GROUP_ADMIN, GROUP_OWNER from liteyuki.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
from liteyuki.utils.tools import clamp from liteyuki.utils.message.tools import clamp
from .common import * from .common import *
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")

View File

@ -1,16 +1,14 @@
# 轻雪资源包管理器 # 轻雪资源包管理器
import os import os
import nonebot
import yaml import yaml
from nonebot import require from nonebot import require
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from liteyuki.utils.config import get_config from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.language import get_user_lang from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.utils.message import Markdown as md from liteyuki.utils.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
from liteyuki.utils.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand

View File

@ -3,11 +3,11 @@ from typing import Optional
import pytz import pytz
from nonebot import require from nonebot import require
from liteyuki.utils.data import LiteModel from liteyuki.utils.base.data import LiteModel
from liteyuki.utils.data_manager import User, user_db from liteyuki.utils.base.data_manager import User, user_db
from liteyuki.utils.language import Language, get_all_lang, get_user_lang from liteyuki.utils.base.language import Language, get_all_lang, get_user_lang
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message import Markdown as md from liteyuki.utils.message.message import MarkdownMessage as md
from .const import representative_timezones_list from .const import representative_timezones_list
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")

View File

@ -1,5 +1,4 @@
from nonebot import on_command from liteyuki.utils.base.data import LiteModel
from liteyuki.utils.data import LiteModel
class Location(LiteModel): class Location(LiteModel):

View File

@ -1,10 +1,9 @@
from nonebot import require from nonebot import require
from jieba import lcut
from liteyuki.utils.ly_typing import T_MessageEvent from liteyuki.utils.base.ly_typing import T_MessageEvent
require("nonebot_plugin_alconna") require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma
@on_alconna( @on_alconna(

View File

@ -10,9 +10,9 @@ __VERSION__ = "6.2.8" # 60201
import requests import requests
from liteyuki.utils.config import load_from_yaml, config from liteyuki.utils.base.config import load_from_yaml, config
from .log import init_log from liteyuki.utils.base.log import init_log
from .data_manager import auto_migrate from liteyuki.utils.base.data_manager import auto_migrate
major, minor, patch = map(int, __VERSION__.split(".")) major, minor, patch = map(int, __VERSION__.split("."))
__VERSION_I__ = major * 10000 + minor * 100 + patch __VERSION_I__ = major * 10000 + minor * 100 + patch

View File

@ -4,9 +4,9 @@ import nonebot
import yaml import yaml
from pydantic import BaseModel from pydantic import BaseModel
from liteyuki.utils.data_manager import StoredConfig, common_db from .data_manager import StoredConfig, common_db
from liteyuki.utils.ly_typing import T_Bot from .ly_typing import T_Bot
from liteyuki.utils.tools import random_hex_string from ..message.tools import random_hex_string
config = {} # 全局配置,确保加载后读取 config = {} # 全局配置,确保加载后读取

View File

@ -2,7 +2,7 @@ import os
from pydantic import Field from pydantic import Field
from liteyuki.utils.data import LiteModel, Database as DB from .data import LiteModel, Database as DB
DATA_PATH = "data/liteyuki" DATA_PATH = "data/liteyuki"

View File

@ -2,13 +2,13 @@ import sys
import loguru import loguru
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .config import load_from_yaml from .config import load_from_yaml
from .language import Language, get_default_lang from .language import get_default_lang
logger = loguru.logger logger = loguru.logger
if TYPE_CHECKING: if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed # avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually # because loguru module do not have `Logger` class actually
from loguru import Logger, Record from loguru import Record
def default_filter(record: "Record"): def default_filter(record: "Record"):

View File

@ -8,8 +8,8 @@ import psutil
import requests import requests
from aiohttp import FormData from aiohttp import FormData
from . import __VERSION_I__, __VERSION__, __NAME__ from .. import __VERSION_I__, __VERSION__, __NAME__
from .config import config, load_from_yaml from .config import load_from_yaml
class LiteyukiAPI: class LiteyukiAPI:

View File

@ -1,7 +1,5 @@
from nonebot.adapters.onebot import v11 from nonebot.adapters.onebot import v11
from liteyuki.utils.ly_typing import T_GroupMessageEvent, T_MessageEvent
GROUP_ADMIN = v11.GROUP_ADMIN GROUP_ADMIN = v11.GROUP_ADMIN
GROUP_OWNER = v11.GROUP_OWNER GROUP_OWNER = v11.GROUP_OWNER

View File

@ -42,7 +42,7 @@ def load_resource_from_dir(path: str):
metadata["path"] = path metadata["path"] = path
metadata["folder"] = os.path.basename(path) metadata["folder"] = os.path.basename(path)
if os.path.exists(os.path.join(path, "lang")): if os.path.exists(os.path.join(path, "lang")):
from liteyuki.utils.language import load_from_dir from liteyuki.utils.base.language import load_from_dir
load_from_dir(os.path.join(path, "lang")) load_from_dir(os.path.join(path, "lang"))
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata)) _loaded_resource_packs.insert(0, ResourceMetadata(**metadata))

View File

View File

@ -0,0 +1,186 @@
import base64
from io import BytesIO
from urllib.parse import quote
import aiohttp
from PIL import Image
from ..base.config import get_config
from ..base.ly_typing import T_Bot
def markdown_escape(text: str) -> str:
"""
转义Markdown特殊字符
Args:
text: str: 文本
Returns:
str: 转义后文本
"""
text = text.replace("\n", r"\n").replace('"', r'\\\"')
spacial_chars = r"\`*_{}[]()#+-.!"
for char in spacial_chars:
text = text.replace(char, "\\" + char)
return text
def escape_decorator(func):
def wrapper(text: str):
return func(markdown_escape(text))
return wrapper
class MarkdownComponent:
@staticmethod
@escape_decorator
def heading(text: str, level: int = 1) -> str:
"""标题"""
assert 1 <= level <= 6, "标题级别应在 1-6 之间"
return f"{'#' * level} {text}"
@staticmethod
@escape_decorator
def bold(text: str) -> str:
"""粗体"""
return f"**{text}**"
@staticmethod
@escape_decorator
def italic(text: str) -> str:
"""斜体"""
return f"*{text}*"
@staticmethod
@escape_decorator
def strike(text: str) -> str:
"""删除线"""
return f"~~{text}~~"
@staticmethod
@escape_decorator
def code(text: str) -> str:
"""行内代码"""
return f"`{text}`"
@staticmethod
@escape_decorator
def code_block(text: str, language: str = "") -> str:
"""代码块"""
return f"```{language}\n{text}\n```"
@staticmethod
@escape_decorator
def quote(text: str) -> str:
"""引用"""
return f"> {text}"
@staticmethod
@escape_decorator
def link(text: str, url: str, symbol: bool = True) -> str:
"""
链接
Args:
text: 链接文本
url: 链接地址
symbol: 是否显示链接图标, mqqapi请使用False
"""
return f"[{'🔗' if symbol else ''}{text}]({quote(url)})"
@staticmethod
@escape_decorator
def image(url: str, *, size: tuple[int, int]) -> str:
"""
图片本地图片不建议直接使用
Args:
url: 图片链接
size: 图片大小
Returns:
markdown格式的图片
"""
return f"![image #{size[0]}px #{size[1]}px]({url})"
@staticmethod
@escape_decorator
async def auto_image(image: str | bytes, bot: T_Bot) -> str:
"""
自动获取图片大小
Args:
image: 本地图片路径 | 图片url http/file | 图片bytes
bot: bot对象用于上传图片到图床
Returns:
markdown格式的图片
"""
if isinstance(image, bytes):
# 传入为二进制图片
image_obj = Image.open(BytesIO(image))
base64_string = base64.b64encode(image_obj.tobytes()).decode("utf-8")
url = await bot.call_api("upload_image", file=f"base64://{base64_string}")
size = image_obj.size
elif isinstance(image, str):
# 传入链接或本地路径
if image.startswith("http"):
# 网络请求
async with aiohttp.ClientSession() as session:
async with session.get(image) as resp:
image_data = await resp.read()
url = image
size = Image.open(BytesIO(image_data)).size
else:
# 本地路径/file://
image_obj = Image.open(image.replace("file://", ""))
base64_string = base64.b64encode(image_obj.tobytes()).decode("utf-8")
url = await bot.call_api("upload_image", file=f"base64://{base64_string}")
size = image_obj.size
else:
raise ValueError("图片类型错误")
return MarkdownComponent.image(url, size=size)
@staticmethod
@escape_decorator
def table(data: list[list[any]]) -> str:
"""
表格
Args:
data: 表格数据二维列表
Returns:
markdown格式的表格
"""
# 表头
table = "|".join(map(str, data[0])) + "\n"
table += "|".join([":-:" for _ in range(len(data[0]))]) + "\n"
# 表内容
for row in data[1:]:
table += "|".join(map(str, row)) + "\n"
return table
class Mqqapi:
@staticmethod
@escape_decorator
def cmd(text: str, cmd: str, enter: bool = True, reply: bool = False, use_cmd_start: bool = True) -> str:
"""
生成点击回调文本
Args:
text: 显示内容
cmd: 命令
enter: 是否自动发送
reply: 是否回复
use_cmd_start: 是否使用配置的命令前缀
Returns:
[text](mqqapi://) markdown格式的可点击回调文本类似于链接
"""
if use_cmd_start:
command_start = get_config("command_start", [])
if command_start:
# 若命令前缀不为空,则使用配置的第一个命令前缀
cmd = f"{command_start[0]}{cmd}"
return f"[{text}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})"

View File

@ -1,4 +1,3 @@
import asyncio
import base64 import base64
import io import io
from urllib.parse import quote from urllib.parse import quote
@ -8,12 +7,14 @@ from PIL import Image
import aiohttp import aiohttp
import nonebot import nonebot
from nonebot import require from nonebot import require
from nonebot.adapters.onebot import v11, v12 from nonebot.adapters.onebot import v11
from typing import Any from typing import Any, Type
from . import load_from_yaml from nonebot.internal.adapter import MessageSegment
from .ly_api import liteyuki_api from nonebot.internal.adapter.message import TM
from .ly_typing import T_Bot, T_Message, T_MessageEvent
from .. import load_from_yaml
from ..base.ly_typing import T_Bot, T_Message, T_MessageEvent
require("nonebot_plugin_htmlrender") require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import md_to_pic from nonebot_plugin_htmlrender import md_to_pic
@ -28,12 +29,12 @@ async def broadcast_to_superusers(message: str | T_Message, markdown: bool = Fal
for bot in nonebot.get_bots().values(): for bot in nonebot.get_bots().values():
for user_id in config.get("superusers", []): for user_id in config.get("superusers", []):
if markdown: if markdown:
await Markdown.send_md(message, bot, message_type="private", session_id=user_id) await MarkdownMessage.send_md(message, bot, message_type="private", session_id=user_id)
else: else:
await bot.send_private_msg(user_id=user_id, message=message) await bot.send_private_msg(user_id=user_id, message=message)
class Markdown: class MarkdownMessage:
@staticmethod @staticmethod
async def send_md( async def send_md(
markdown: str, markdown: str,
@ -158,7 +159,7 @@ class Markdown:
if method == 2: if method == 2:
base64_string = base64.b64encode(image).decode("utf-8") base64_string = base64.b64encode(image).decode("utf-8")
data = await bot.call_api("upload_image", file=f"base64://{base64_string}") data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
await Markdown.send_md(Markdown.image(data, Image.open(io.BytesIO(image)).size), bot, event=event, message_type=message_type, await MarkdownMessage.send_md(MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size), bot, event=event, message_type=message_type,
session_id=session_id, **kwargs) session_id=session_id, **kwargs)
# 其他实现端方案 # 其他实现端方案
@ -171,8 +172,8 @@ class Markdown:
))["message_id"] ))["message_id"]
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"] image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
image_size = Image.open(io.BytesIO(image)).size image_size = Image.open(io.BytesIO(image)).size
image_md = Markdown.image(image_url, image_size) image_md = MarkdownMessage.image(image_url, image_size)
return await Markdown.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event, **kwargs) return await MarkdownMessage.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event, **kwargs)
if data is None: if data is None:
data = await bot.send_msg( data = await bot.send_msg(
@ -251,7 +252,7 @@ class Markdown:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(url) as resp: async with session.get(url) as resp:
image = Image.open(io.BytesIO(await resp.read())) image = Image.open(io.BytesIO(await resp.read()))
return Markdown.image(url, image.size) return MarkdownMessage.image(url, image.size)
except Exception as e: except Exception as e:
nonebot.logger.error(f"get image error: {e}") nonebot.logger.error(f"get image error: {e}")
return "[Image Error]" return "[Image Error]"
@ -270,58 +271,3 @@ class Markdown:
for char in chars: for char in chars:
text = text.replace(char, f"\\\\{char}") text = text.replace(char, f"\\\\{char}")
return text return text
@staticmethod
def H1(text: str, end="\n") -> str:
"""H1标题"""
return f"# {text}{end}"
@staticmethod
def H2(text: str, end="\n") -> str:
"""H2标题"""
return f"## {text}{end}"
@staticmethod
def H3(text: str, end="\n") -> str:
"""H3标题"""
return f"### {text}{end}"
@staticmethod
def H4(text: str, end="\n") -> str:
"""H4标题"""
return f"#### {text}{end}"
@staticmethod
def H5(text: str, end="\n") -> str:
"""H5标题"""
return f"##### {text}{end}"
@staticmethod
def H6(text: str, end="\n") -> str:
"""H6标题"""
return f"###### {text}{end}"
@staticmethod
def Bold(text: str) -> str:
"""加粗"""
return f"**{text}**"
@staticmethod
def Italic(text: str) -> str:
"""斜体"""
return f"*{text}*"
@staticmethod
def BoldItalic(text: str) -> str:
"""粗斜体"""
return f"***{text}***"
@staticmethod
def Underline(text: str) -> str:
"""下划线"""
return f"__{text}__"
@staticmethod
def Strike(text: str) -> str:
"""删除线"""
return f"~~{text}~~"

View File

@ -1,12 +1,10 @@
import os.path
import nonebot import nonebot
from nonebot.adapters.onebot import v11, v12 from nonebot.adapters.onebot import v11, v12
from liteyuki.utils import init from liteyuki.utils import init
from liteyuki.utils.config import load_from_yaml from liteyuki.utils.base.config import load_from_yaml
from liteyuki.utils.data_manager import StoredConfig, common_db from liteyuki.utils.base.data_manager import StoredConfig, common_db
from liteyuki.utils.ly_api import liteyuki_api from liteyuki.utils.base.ly_api import liteyuki_api
init() init()