Skip to content

Commit

Permalink
JTools v3.8.0
Browse files Browse the repository at this point in the history
功能变动:

- 适配 JKit Alpha
- 上榜文章查询工具昵称更改提示优化
- 上榜文章查询工具默认倒序
- 更新捉虫计划奖励名单
- 更新开源库列表
- 更新依赖库
- 移除无用导入

错误修复:

- 修复上榜文章查询工具数据不完整提示布局溢出问题
- 修复部分生僻字无法通过数据校验的问题

CI / CD:

- 初始化静态检查
  • Loading branch information
FHU-yezi committed Dec 28, 2023
2 parents 8ad8448 + 01bbbfe commit 46f73dd
Show file tree
Hide file tree
Showing 19 changed files with 1,332 additions and 1,397 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/static-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Static Check

on:
push:
workflow_call:

jobs:
static-check-backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v4
- name: Install Poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: "3.x"
cache: "poetry"
- name: Install Dependencies
run: poetry install --all-extras --no-root
- name: Lint With Ruff
run: poetry run ruff check --output-format=github .
- name: Type checking with Pyright
run: poetry run pyright --warnings .
static-check-frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
run_install: true
package_json_file: "frontend/package.json"
- uses: actions/setup-node@v4
with:
cache: "pnpm"
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Lint with Eslint
run: pnpm eslint .
- name: Style check with Prettier
run: pnpm prettier --check .
70 changes: 27 additions & 43 deletions backend/api/v1/articles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@

from bson import ObjectId
from httpx import AsyncClient
from JianshuResearchTools.article import (
GetArticleText,
GetArticleTitle,
GetArticleTotalFPCount,
)
from JianshuResearchTools.assert_funcs import AssertArticleStatusNormal
from JianshuResearchTools.convert import ArticleSlugToArticleUrl, UserSlugToUserUrl
from JianshuResearchTools.exceptions import InputError, ResourceError
from jkit.article import Article
from jkit.exceptions import ResourceUnavailableError
from litestar import Response, Router, get
from litestar.openapi.spec.example import Example
from litestar.params import Parameter
Expand All @@ -25,7 +19,6 @@
generate_response_spec,
success,
)
from sspeedup.sync_to_async import sync_to_async

from utils.config import config
from utils.db import ARTICLE_FP_RANK_COLLECTION
Expand All @@ -45,14 +38,6 @@
# fmt: on


async def get_author_url(article_slug: str) -> str:
response = await CLIENT.get(f"https://www.jianshu.com/asimov/p/{article_slug}")
response.raise_for_status()

json = response.json()
return UserSlugToUserUrl(json["user"]["slug"])


async def get_latest_onrank_record(
author_url: str, *, minimum_ranking: Optional[int] = None
) -> Optional[Dict[str, Any]]:
Expand Down Expand Up @@ -173,25 +158,24 @@ async def get_word_freq_handler(
],
) -> Response:
try:
article_url = ArticleSlugToArticleUrl(article_slug)
title, article_text = await gather(
sync_to_async(GetArticleTitle, article_url),
sync_to_async(GetArticleText, article_url),
)
except InputError:
article = Article.from_slug(article_slug)
await article.check()
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的文章链接无效",
msg="文章 slug 无效",
)
except ResourceError:
except ResourceUnavailableError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="文章已被删除、锁定或正在审核中",
msg="文章不存在或已被锁定 / 私密 / 删除",
)

word_freq = dict((await splitter.get_word_freq(article_text)).most_common(100))
title, text = await gather(article.title, article.text_content)

word_freq = dict((await splitter.get_word_freq(text)).most_common(100))

return success(
data=GetWordFreqResponse(
Expand Down Expand Up @@ -234,39 +218,39 @@ async def get_LP_recommend_check_handler( # noqa: N802
],
) -> Response:
try:
article_url = ArticleSlugToArticleUrl(article_slug)
await sync_to_async(AssertArticleStatusNormal, article_url)
except InputError:
article = Article.from_slug(article_slug)
await article.check()
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的文章链接无效",
msg="文章 slug 无效",
)
except ResourceError:
except ResourceUnavailableError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="文章已被删除、锁定或正在审核中",
msg="文章不存在或已被锁定 / 私密 / 删除",
)

author_url = await get_author_url(article_slug)
article_info = await article.info

article_title, FP_reward, next_can_recommend_date = await gather( # noqa: N806
sync_to_async(GetArticleTitle, article_url),
sync_to_async(GetArticleTotalFPCount, article_url),
caculate_next_can_recommend_date(author_url),
)
author_url = article_info.author_info.to_user_obj().url
article_title = article_info.title
article_fp_reward = article_info.earned_fp_amount
article_next_can_recommend_date = await caculate_next_can_recommend_date(author_url)

can_recommend_now = FP_reward < 35 and (
not next_can_recommend_date or next_can_recommend_date <= datetime.now()
can_recommend_now = article_fp_reward < 35 and (
not article_next_can_recommend_date
or article_next_can_recommend_date <= datetime.now()
)

return success(
data=GetLPRecommendCheckResponse(
article_title=article_title,
can_recommend_now=can_recommend_now,
FP_reward=FP_reward,
next_can_recommend_date=next_can_recommend_date,
FP_reward=article_fp_reward,
next_can_recommend_date=article_next_can_recommend_date,
)
)

Expand Down
4 changes: 2 additions & 2 deletions backend/api/v1/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def get_handler() -> Response:
unavaliable_tools = [
name
for name, config in TOOLS_CONFIG.items()
if config.status == ToolStatus.UNAVALIABLE
if config.status == ToolStatus.UNAVAILABLE
]

return success(
Expand Down Expand Up @@ -111,7 +111,7 @@ class GetToolStatusResponse(Struct, **RESPONSE_STRUCT_CONFIG):
},
)
async def get_tool_status_handler(
tool_name: Annotated[str, Parameter(description="小工具名称", max_length=100)]
tool_name: Annotated[str, Parameter(description="小工具名称", max_length=100)],
) -> Response:
tool_config = TOOLS_CONFIG.get(tool_name)
if not tool_config:
Expand Down
64 changes: 30 additions & 34 deletions backend/api/v1/users.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from asyncio import gather
from datetime import datetime
from typing import Annotated, Dict, List, Literal, Optional

from JianshuResearchTools.convert import UserSlugToUserUrl
from JianshuResearchTools.exceptions import InputError, ResourceError
from JianshuResearchTools.user import GetUserName, GetUserVIPInfo
from jkit.exceptions import ResourceUnavailableError
from jkit.user import MembershipEnum, User
from litestar import Response, Router, get
from litestar.params import Parameter
from litestar.status_codes import HTTP_400_BAD_REQUEST
Expand All @@ -16,15 +14,14 @@
generate_response_spec,
success,
)
from sspeedup.sync_to_async import sync_to_async

from utils.db import ARTICLE_FP_RANK_COLLECTION, LOTTERY_COLLECTION


class GetVipInfoResponse(Struct, **RESPONSE_STRUCT_CONFIG):
user_name: str
is_vip: bool = field(name="isVIP")
type: Optional[Literal["bronze", "sliver", "gold", "platina"]] # noqa: A003
type: Optional[Literal["铜牌", "银牌", "金牌", "白金"]] # noqa: A003
expire_date: Optional[datetime]


Expand All @@ -42,33 +39,34 @@ async def get_vip_info_handler(
],
) -> Response:
try:
user_url = UserSlugToUserUrl(user_slug)
user_name, vip_info = await gather(
sync_to_async(GetUserName, user_url),
sync_to_async(GetUserVIPInfo, user_url),
)
except InputError:
user = User.from_slug(user_slug)
await user.check()
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的简书个人主页链接无效",
msg="用户 slug 无效",
)
except ResourceError:
except ResourceUnavailableError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="用户已注销或被封禁",
msg="用户不存在或已注销 / 被封禁",
)

is_vip = vip_info["vip_type"] is not None
type_ = vip_info["vip_type"]
expire_date = vip_info["expire_date"]
user_info = await user.info
user_name = user_info.name
membership_info = user_info.membership_info

is_vip = membership_info.type != MembershipEnum.NONE
type_ = membership_info.type.value.replace("会员", "")
expire_date = membership_info.expired_at

return success(
data=GetVipInfoResponse(
user_name=user_name,
is_vip=is_vip,
type=type_,
type=type_, # type: ignore
expire_date=expire_date,
)
)
Expand Down Expand Up @@ -102,18 +100,18 @@ async def get_lottery_win_records(
] = None,
) -> Response:
try:
user_url = UserSlugToUserUrl(user_slug)
except InputError:
user = User.from_slug(user_slug)
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的简书个人主页链接无效",
msg="用户 slug 无效",
)

result = (
LOTTERY_COLLECTION.find(
{
"user.url": user_url,
"user.url": user.url,
"reward_name": {
"$nin": excluded_awards if excluded_awards else [],
},
Expand Down Expand Up @@ -173,16 +171,16 @@ async def get_on_article_rank_records_handler(
limit: Annotated[int, Parameter(description="结果数量", gt=0, lt=100)] = 20,
) -> Response:
try:
user_url = UserSlugToUserUrl(user_slug)
except InputError:
user = User.from_slug(user_slug)
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的简书个人主页链接无效",
msg="用户 slug 无效",
)

result = (
ARTICLE_FP_RANK_COLLECTION.find({"author.url": user_url})
ARTICLE_FP_RANK_COLLECTION.find({"author.url": user.url})
.sort(order_by, 1 if order_direction == "asc" else -1)
.skip(offset)
.limit(limit)
Expand Down Expand Up @@ -273,16 +271,16 @@ async def get_on_article_rank_summary_handler(
],
) -> Response:
try:
user_url = UserSlugToUserUrl(user_slug)
except InputError:
user = User.from_slug(user_slug)
except ValueError:
return fail(
http_code=HTTP_400_BAD_REQUEST,
api_code=Code.BAD_ARGUMENTS,
msg="输入的简书个人主页链接无效",
msg="用户 slug 无效",
)

records = ARTICLE_FP_RANK_COLLECTION.find(
{"author.url": user_url},
{"author.url": user.url},
{"_id": False, "ranking": True},
)

Expand Down Expand Up @@ -397,9 +395,7 @@ class GetHistoryNamesOnArticleRankSummaryResponse(Struct, **RESPONSE_STRUCT_CONF
},
)
async def get_history_names_on_article_rank_summary_handler(
user_name: Annotated[
str, Parameter(description="用户昵称", max_length=50)
],
user_name: Annotated[str, Parameter(description="用户昵称", max_length=50)],
) -> Response:
url_query = await ARTICLE_FP_RANK_COLLECTION.find_one({"author.name": user_name})
if not url_query:
Expand Down
1 change: 0 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import logging.config

from uvicorn import run as uvicorn_run

Expand Down
Loading

0 comments on commit 46f73dd

Please sign in to comment.