├── .prettierignore
├── .github
├── FUNDING.yml
└── workflows
│ └── pypi-publish.yml
├── pyproject.toml
├── nonebot_plugin_eitherchoice
├── __init__.py
├── config.py
├── __main__.py
├── res
│ └── template.html.jinja
└── data_source.py
├── LICENSE
├── .gitignore
└── README.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | venv/
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['https://afdian.net/@lgc2333/']
2 |
--------------------------------------------------------------------------------
/.github/workflows/pypi-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python 🐍 distributions 📦 to PyPI
2 |
3 | on:
4 | release:
5 | types: [published]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build-n-publish:
10 | name: Use PDM to Build and publish Python 🐍 distributions 📦 to PyPI
11 | runs-on: ubuntu-latest
12 |
13 | permissions:
14 | # IMPORTANT: this permission is mandatory for trusted publishing
15 | id-token: write
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@master
20 | with:
21 | submodules: true
22 |
23 | - name: Setup PDM
24 | uses: pdm-project/setup-pdm@v3
25 |
26 | - name: Build and Publish distribution 📦 to PyPI
27 | run: pdm publish
28 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "nonebot-plugin-eitherchoice"
3 | version = "0.2.0.post1"
4 | description = "Let AI help you compare two things."
5 | authors = [{ name = "student_2333", email = "lgc2333@126.com" }]
6 | dependencies = [
7 | "nonebot2>=2.2.0",
8 | "nonebot-plugin-htmlrender>=0.2.1",
9 | "nonebot-plugin-send-anything-anywhere>=0.3.2",
10 | "pydantic>=1.10.0,<2",
11 | "httpx[http2]>=0.24.1",
12 | "beautifulsoup4>=4.12.2",
13 | "lxml>=4.9.3",
14 | ]
15 | requires-python = ">=3.9,<4.0"
16 | readme = "README.md"
17 | license = { text = "MIT" }
18 |
19 | [project.urls]
20 | homepage = "https://github.com/lgc-NB2Dev/nonebot-plugin-eitherchoice"
21 |
22 | [tool.pdm.build]
23 | includes = []
24 |
25 | [tool.pdm.dev-dependencies]
26 | dev = ["nb-cli>=1.0.5", "black>=23.3.0", "ruff>=0.0.260", "isort>=5.12.0"]
27 |
28 | [build-system]
29 | requires = ["pdm-backend"]
30 | build-backend = "pdm.backend"
31 |
--------------------------------------------------------------------------------
/nonebot_plugin_eitherchoice/__init__.py:
--------------------------------------------------------------------------------
1 | from nonebot import require
2 | from nonebot.plugin import PluginMetadata, inherit_supported_adapters
3 |
4 | require("nonebot_plugin_htmlrender")
5 | require("nonebot_plugin_saa")
6 |
7 | from . import __main__ as __main__ # noqa: E402
8 | from .config import ConfigModel # noqa: E402
9 |
10 | __version__ = "0.2.0"
11 | __plugin_meta__ = PluginMetadata(
12 | name="EitherChoice",
13 | description="让 AI 帮你对比两件事物",
14 | usage=(
15 | "▶ 指令:(对比/比较/锐评/评价/如何评价)[下/一下] 要顶的事物 (和/与/and/vs/&) 要踩的事物\n"
16 | "▶ 示例:\n"
17 | " ▷ 对比 Python 和 JavaScript\n"
18 | " ▷ 锐评一下 C# & Java\n"
19 | " ▷ 比较 下北泽 与 东京 (加上空格防止 `下` 或 `一下` 被当做指令的一部分去除)"
20 | ),
21 | type="application",
22 | homepage="https://github.com/lgc-NB2Dev/nonebot-plugin-eitherchoice",
23 | config=ConfigModel,
24 | supported_adapters=inherit_supported_adapters("nonebot_plugin_saa"),
25 | extra={"License": "MIT", "Author": "student_2333"},
26 | )
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 student_2333
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/nonebot_plugin_eitherchoice/config.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional
2 |
3 | from nonebot import get_driver
4 | from pydantic import BaseModel, validator
5 |
6 |
7 | class ConfigModel(BaseModel):
8 | proxy: Optional[str] = None
9 | either_choice_timeout: Optional[int] = None
10 | either_choice_retry: int = 2
11 | either_choice_lang: str = "zh-CN"
12 | either_choice_allow_public: str = "true"
13 | either_choice_force_ask: bool = True
14 | either_choice_pic_width: int = 1280
15 | either_choice_main_font: str = (
16 | "'Microsoft YaHei UI', 'Microsoft YaHei', "
17 | "'Source Han Sans CN', 'Source Han Sans SC', "
18 | "'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif"
19 | )
20 | either_choice_code_font: str = (
21 | "'JetBrains Mono', 'JetBrainsMono Nerd Font', "
22 | "'Victor Mono', 'VictorMono Nerd Font', "
23 | "'Fira Code', 'FiraCode Nerd Font', "
24 | "'Cascadia Code', 'CascadiaCode Nerd Font', "
25 | "'Consolas', 'Courier New', monospace"
26 | )
27 |
28 | @validator("either_choice_allow_public", pre=True)
29 | def either_choice_allow_public_validator(cls, v: Any): # noqa: N805
30 | v = str(v).lower()
31 | if v in ("true", "false"):
32 | return v
33 | raise ValueError("`either_choice_allow_public` must be `true` or `false`")
34 |
35 |
36 | config: ConfigModel = ConfigModel.parse_obj(get_driver().config.dict())
37 |
--------------------------------------------------------------------------------
/nonebot_plugin_eitherchoice/__main__.py:
--------------------------------------------------------------------------------
1 | import string
2 | from typing import Tuple
3 |
4 | from nonebot import logger, on_command
5 | from nonebot.adapters import Message
6 | from nonebot.params import CommandArg
7 | from nonebot.typing import T_State
8 | from nonebot_plugin_saa import Image, MessageFactory
9 |
10 | from .data_source import get_choice_pic
11 |
12 | CMD_PREFIX = ("对比", "比较", "锐评", "评价", "如何评价")
13 | CMD_SUFFIX = ("下", "一下")
14 | COMMANDS = CMD_PREFIX + tuple(f"{x}{y}" for x in CMD_PREFIX for y in CMD_SUFFIX)
15 |
16 | SEP_CHARS = ("和", "与", "and", "vs", "&")
17 | SEP_CHARS = tuple(f" {x}" for x in SEP_CHARS) + SEP_CHARS
18 | STRIP_CHARS = "'\"“”‘’"
19 |
20 |
21 | async def check_rule(state: T_State, arg: Message = CommandArg()) -> bool:
22 | arg_str = arg.extract_plain_text()
23 | sep = next((x for x in SEP_CHARS if x in arg_str), None)
24 | if not sep:
25 | return False
26 |
27 | things = tuple(
28 | x.strip(string.whitespace + STRIP_CHARS) for x in arg_str.split(sep, 1)
29 | )
30 | if len(things) != 2 or (not all(things)):
31 | return False
32 |
33 | state["things"] = things
34 | return True
35 |
36 |
37 | first_cmd, *other_cmd = COMMANDS
38 | cmd_choice = on_command(first_cmd, aliases=set(other_cmd), rule=check_rule)
39 |
40 |
41 | @cmd_choice.handle()
42 | async def _(state: T_State):
43 | things: Tuple[str, str] = state["things"]
44 |
45 | tip_receipt = await MessageFactory("请稍等,AI 正在帮你评价...").send()
46 |
47 | try:
48 | pic = await get_choice_pic(*things)
49 | except Exception:
50 | logger.exception("发生错误")
51 | await MessageFactory("发生错误,请稍后重试……").send(reply=True)
52 | else:
53 | await MessageFactory(Image(pic)).send(reply=True)
54 | finally:
55 | try:
56 | await tip_receipt.revoke()
57 | except Exception as e:
58 | logger.warning(f"撤回提示消息失败:{e!r}")
59 |
--------------------------------------------------------------------------------
/nonebot_plugin_eitherchoice/res/template.html.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
94 |
95 |
96 |
97 |
98 |
{{ a }} VS {{ b }}
99 |
{{ a }} for sure!
100 |
{{ table|safe }}
101 |
102 |
103 |
104 | {{ extra }}
105 |
106 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
3 |
4 | ### Python ###
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 | cover/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | .pybuilder/
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | # For a library or package, you might want to ignore these files since the code is
91 | # intended to run in multiple environments; otherwise, check them in:
92 | # .python-version
93 |
94 | # pipenv
95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
98 | # install all needed dependencies.
99 | #Pipfile.lock
100 |
101 | # poetry
102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103 | # This is especially recommended for binary packages to ensure reproducibility, and is more
104 | # commonly ignored for libraries.
105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106 | poetry.lock
107 |
108 | # pdm
109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110 | # pdm.lock
111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112 | # in version control.
113 | # https://pdm.fming.dev/#use-with-ide
114 | .pdm.toml
115 | .pdm-python
116 |
117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118 | __pypackages__/
119 |
120 | # Celery stuff
121 | celerybeat-schedule
122 | celerybeat.pid
123 |
124 | # SageMath parsed files
125 | *.sage.py
126 |
127 | # Environments
128 | .env
129 | .venv
130 | env/
131 | venv/
132 | ENV/
133 | env.bak/
134 | venv.bak/
135 |
136 | # Spyder project settings
137 | .spyderproject
138 | .spyproject
139 |
140 | # Rope project settings
141 | .ropeproject
142 |
143 | # mkdocs documentation
144 | /site
145 |
146 | # mypy
147 | .mypy_cache/
148 | .dmypy.json
149 | dmypy.json
150 |
151 | # Pyre type checker
152 | .pyre/
153 |
154 | # pytype static type analyzer
155 | .pytype/
156 |
157 | # Cython debug symbols
158 | cython_debug/
159 |
160 | # PyCharm
161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163 | # and can be added to the global gitignore or merged into this file. For a more nuclear
164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165 | #.idea/
166 |
167 | ### Python Patch ###
168 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
169 | # poetry.toml
170 |
171 | # End of https://www.toptal.com/developers/gitignore/api/python
172 |
173 | .vscode
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
38 |
39 | ## 📖 介绍
40 |
41 | ~~让 AI 帮你一本正经地胡说八道~~
42 |
43 | 灵感来自 [koishi-plugin-eitherchoice](https://www.npmjs.com/package/koishi-plugin-eitherchoice) ~~(电子蝈蝈,电子博弈,在线观看)~~
44 | 服务来自 [EitherChoice](https://eitherchoice.com/)
45 |
46 | ## 💿 安装
47 |
48 | 以下提到的方法 任选**其一** 即可
49 |
50 |
51 | [推荐] 使用 nb-cli 安装
52 | 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
53 |
54 | ```bash
55 | nb plugin install nonebot-plugin-eitherchoice
56 | ```
57 |
58 |
59 |
60 |
61 | 使用包管理器安装
62 | 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
63 |
64 |
65 | pip
66 |
67 | ```bash
68 | pip install nonebot-plugin-eitherchoice
69 | ```
70 |
71 |
72 |
73 | pdm
74 |
75 | ```bash
76 | pdm add nonebot-plugin-eitherchoice
77 | ```
78 |
79 |
80 |
81 | poetry
82 |
83 | ```bash
84 | poetry add nonebot-plugin-eitherchoice
85 | ```
86 |
87 |
88 |
89 | conda
90 |
91 | ```bash
92 | conda install nonebot-plugin-eitherchoice
93 | ```
94 |
95 |
96 |
97 | 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分的 `plugins` 项里追加写入
98 |
99 | ```toml
100 | [tool.nonebot]
101 | plugins = [
102 | # ...
103 | "nonebot_plugin_eitherchoice"
104 | ]
105 | ```
106 |
107 |
108 |
109 | ## ⚙️ 配置
110 |
111 | 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
112 |
113 | | 配置项 | 必填 | 默认值 | 说明 |
114 | | :--------------------------: | :--: | :-----: | :--------------------------------------------------: |
115 | | `PROXY` | 否 | 无 | 访问接口使用的代理 |
116 | | `EITHER_CHOICE_TIMEOUT` | 否 | `None` | 访问接口超时,单位秒,可以设置为 `None` 以禁用超时 |
117 | | `EITHER_CHOICE_RETRY` | 否 | `2` | 访问接口最大重试次数 |
118 | | `EITHER_CHOICE_LANG` | 否 | `zh-CN` | 目标语言 |
119 | | `EITHER_CHOICE_ALLOW_PUBLIC` | 否 | `True` | 是否允许 AI 上网搜索相关信息 |
120 | | `EITHER_CHOICE_FORCE_ASK` | 否 | `True` | 是否每次触发指令都调用 ask 接口,不检索网站缓存 |
121 | | `EITHER_CHOICE_PIC_WIDTH` | 否 | `1280` | 生成图片的宽度,单位像素(实际宽度可能比这里大一倍) |
122 | | `EITHER_CHOICE_MAIN_FONT` | 否 | ... | 生成图片的主字体,使用 CSS 语法 |
123 | | `EITHER_CHOICE_CODE_FONT` | 否 | ... | 生成图片的代码字体,使用 CSS 语法 |
124 |
125 | ## 🎉 使用
126 |
127 | ### 指令
128 |
129 | - 指令:`(对比/比较/锐评/评价/如何评价)[下/一下] 要顶的事物 (和/与/and/vs/&) 要踩的事物`
130 | - 示例:
131 | - 对比 Python 和 JavaScript
132 | - 锐评一下 C# & Java
133 | - 比较 下北泽 与 东京 (加上空格防止 `下` 或 `一下` 被当做指令的一部分去除)
134 |
135 | ### 效果图
136 |
137 | 
138 |
139 | ## 📞 联系
140 |
141 | QQ:3076823485
142 | Telegram:[@lgc2333](https://t.me/lgc2333)
143 | 吹水群:[1105946125](https://jq.qq.com/?_wv=1027&k=Z3n1MpEp)
144 | 邮箱:
145 |
146 | ## 💡 鸣谢
147 |
148 | ### [EitherChoice](https://eitherchoice.com/)
149 |
150 | - 服务提供
151 |
152 | ## 💰 赞助
153 |
154 | 感谢大家的赞助!你们的赞助将是我继续创作的动力!
155 |
156 | - [爱发电](https://afdian.net/@lgc2333)
157 | -
158 | 赞助二维码(点击展开)
159 |
160 | 
161 |
162 |
163 |
164 | ## 📝 更新日志
165 |
166 | ### 0.2.0
167 |
168 | 真是久违的更新,这玩意好像坏掉有一段时间了……但是这次修没修好还不确定……
169 |
170 | - 修这玩意用不了的 Bug
171 | - 默认禁用请求超时
172 | - 微调触发指令
173 | - 新配置 `EITHER_CHOICE_FORCE_ASK`
174 |
175 | ### 0.1.2
176 |
177 | - 修复由于 API 地址变动导致的显示 `[object Object]` 问题
178 | - 微调指令匹配方式
179 |
180 | ### 0.1.1
181 |
182 | - 添加配置项 `EITHER_CHOICE_RETRY`,当访问接口出现问题时,将会自动重试
183 |
--------------------------------------------------------------------------------
/nonebot_plugin_eitherchoice/data_source.py:
--------------------------------------------------------------------------------
1 | import time
2 | import urllib.parse
3 | from pathlib import Path
4 | from typing import Optional
5 |
6 | import jinja2
7 | from bs4 import BeautifulSoup, Tag
8 | from httpx import AsyncClient
9 | from nonebot import logger
10 | from nonebot_plugin_htmlrender.data_source import get_new_page, read_tpl
11 |
12 | from .config import config
13 |
14 | RES_PATH = Path(__file__).parent / "res"
15 | HTML_TEMPLATE = jinja2.Template((RES_PATH / "template.html.jinja").read_text("u8"))
16 |
17 | TEMPLATE_HEADERS = {
18 | "User-Agent": (
19 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
20 | "AppleWebKit/537.36 (KHTML, like Gecko) "
21 | "Chrome/114.0.0.0 "
22 | "Safari/537.36"
23 | ),
24 | "Origin": "https://eitherchoice.com",
25 | "Cookie": "ec_ask=1",
26 | }
27 |
28 |
29 | async def render_image(thing_a: str, thing_b: str, content: str) -> bytes:
30 | katex_js = await read_tpl("katex/katex.min.js")
31 | mathtex_js = await read_tpl("katex/mathtex-script-type.min.js")
32 | extra = f""
33 | css_txt = "\n\n".join(
34 | (
35 | await read_tpl("github-markdown-light.css"),
36 | await read_tpl("pygments-default.css"),
37 | await read_tpl("katex/katex.min.b64_fonts.css"),
38 | ),
39 | )
40 |
41 | rendered_html = HTML_TEMPLATE.render(
42 | css=css_txt,
43 | main_font=config.either_choice_main_font,
44 | code_font=config.either_choice_code_font,
45 | a=thing_a,
46 | b=thing_b,
47 | table=content,
48 | extra=extra,
49 | )
50 |
51 | async with get_new_page(
52 | viewport={"width": config.either_choice_pic_width, "height": 720},
53 | ) as page:
54 | await page.goto(RES_PATH.as_uri())
55 | await page.set_content(rendered_html, wait_until="networkidle")
56 |
57 | elem = await page.query_selector(".body")
58 | assert elem
59 | return await elem.screenshot(type="jpeg")
60 |
61 |
62 | def url_enc(s: str) -> str:
63 | return urllib.parse.quote(s, safe="")
64 |
65 |
66 | def build_referer(thing_a: str, thing_b: str) -> str:
67 | return (
68 | f"https://eitherchoice.com/fighting/{url_enc(thing_a)}-vs-{url_enc(thing_b)}?"
69 | f"t={time.time() * 1000:.0f}&p={config.either_choice_allow_public}"
70 | )
71 |
72 |
73 | async def get_from_page(
74 | thing_a: str,
75 | thing_b: str,
76 | referer: Optional[str] = None,
77 | ) -> str:
78 | headers = TEMPLATE_HEADERS.copy()
79 | if referer:
80 | headers["Referer"] = referer
81 |
82 | async with AsyncClient(
83 | http2=True,
84 | timeout=config.either_choice_timeout,
85 | proxies=config.proxy,
86 | ) as client:
87 | resp = await client.get(
88 | f"https://eitherchoice.com/fight/{url_enc(thing_a)}-vs-{url_enc(thing_b)}",
89 | follow_redirects=False,
90 | )
91 | resp.raise_for_status()
92 | text = resp.text
93 |
94 | logger.debug(text)
95 | soup = BeautifulSoup(text, "lxml")
96 | main = soup.find("main")
97 | assert isinstance(main, Tag), "main tag not found, or it is not a tag"
98 |
99 | elements = list(main.children)[3:-3] # 第四个到倒数第四个,剃掉标题和脚注
100 | assert len(elements), "no elements found"
101 |
102 | return "\n".join(str(x) for x in elements)
103 |
104 |
105 | async def ask_choice(
106 | thing_a: str,
107 | thing_b: str,
108 | referer: Optional[str] = None,
109 | ):
110 | headers = TEMPLATE_HEADERS.copy()
111 | if referer:
112 | headers["Referer"] = referer
113 |
114 | body = {
115 | "A": thing_a,
116 | "B": thing_b,
117 | "allowPublic": config.either_choice_allow_public,
118 | "lang": config.either_choice_lang,
119 | }
120 |
121 | async with AsyncClient(
122 | http2=True,
123 | timeout=config.either_choice_timeout,
124 | proxies=config.proxy,
125 | headers=headers,
126 | ) as client:
127 | resp = await client.post("https://eitherchoice.com/api/prompt/ask", json=body)
128 | if resp.status_code != 524: # server timeout
129 | resp.raise_for_status()
130 | else:
131 | logger.warning(
132 | "Server timeout! Maybe the content is not completely generated...",
133 | )
134 |
135 |
136 | async def get_choice(thing_a: str, thing_b: str) -> str:
137 | if not config.either_choice_force_ask:
138 | try:
139 | return await get_from_page(thing_a, thing_b)
140 | except Exception as e:
141 | logger.info(f"Fight page may not exist, asking now: {e!r}")
142 |
143 | referer = build_referer(thing_a, thing_b)
144 | logger.info(f"Referer: {referer}")
145 |
146 | # 这傻逼玩意真他妈头疼,搞了半天都没搞清楚这玩意的破逻辑,
147 | # 不搞了,我急了,摆烂!主打一个能用就行!
148 | for i in range(config.either_choice_retry + 1):
149 | try:
150 | await ask_choice(thing_a, thing_b, referer)
151 | return await get_from_page(thing_a, thing_b, referer)
152 | except Exception as e:
153 | if i == config.either_choice_retry:
154 | raise
155 | logger.warning(
156 | f"Failed to ask or failed to get content! "
157 | f"retrying ({config.either_choice_retry - i} left): {e!r}",
158 | )
159 |
160 | raise ValueError
161 |
162 |
163 | async def get_choice_pic(thing_a: str, thing_b: str) -> bytes:
164 | return await render_image(thing_a, thing_b, await get_choice(thing_a, thing_b))
165 |
--------------------------------------------------------------------------------