Skip to content

Commit 4d06f86

Browse files
committed
init
0 parents  commit 4d06f86

File tree

217 files changed

+13959
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+13959
-0
lines changed

.github/workflows/build-docker.yml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: NASbot Docker
2+
on:
3+
workflow_dispatch:
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
name: Build Docker Image
9+
steps:
10+
-
11+
name: Checkout
12+
uses: actions/checkout@master
13+
14+
-
15+
name: Release version
16+
id: release_version
17+
run: |
18+
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
19+
echo "app_version=$app_version" >> $GITHUB_ENV
20+
21+
-
22+
name: Set Up QEMU
23+
uses: docker/setup-qemu-action@v1
24+
25+
-
26+
name: Set Up Buildx
27+
uses: docker/setup-buildx-action@v1
28+
29+
-
30+
name: Login DockerHub
31+
uses: docker/login-action@v1
32+
with:
33+
username: ${{ secrets.DOCKER_USERNAME }}
34+
password: ${{ secrets.DOCKER_PASSWORD }}
35+
36+
-
37+
name: Build Image
38+
uses: docker/build-push-action@v2
39+
with:
40+
context: .
41+
file: docker/Dockerfile
42+
platforms: |
43+
linux/amd64
44+
push: true
45+
tags: |
46+
${{ secrets.DOCKER_USERNAME }}/nasbot:latest
47+
${{ secrets.DOCKER_USERNAME }}/nasbot:${{ env.app_version }}

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.idea/
2+
*.c
3+
build/
4+
test.py
5+
app/helper/sites.py
1.78 KB
Binary file not shown.

alembic/env.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from logging.config import fileConfig
2+
3+
from sqlalchemy import engine_from_config
4+
from sqlalchemy import pool
5+
6+
from alembic import context
7+
8+
from app.db.models import Base
9+
# this is the Alembic Config object, which provides
10+
# access to the values within the .ini file in use.
11+
config = context.config
12+
13+
# Interpret the config file for Python logging.
14+
# This line sets up loggers basically.
15+
if config.config_file_name is not None:
16+
fileConfig(config.config_file_name)
17+
18+
# add your model's MetaData object here
19+
# for 'autogenerate' support
20+
# from myapp import mymodel
21+
# target_metadata = mymodel.Base.metadata
22+
target_metadata = Base.metadata
23+
24+
# other values from the config, defined by the needs of env.py,
25+
# can be acquired:
26+
# my_important_option = config.get_main_option("my_important_option")
27+
# ... etc.
28+
29+
30+
def run_migrations_offline() -> None:
31+
"""Run migrations in 'offline' mode.
32+
33+
This configures the context with just a URL
34+
and not an Engine, though an Engine is acceptable
35+
here as well. By skipping the Engine creation
36+
we don't even need a DBAPI to be available.
37+
38+
Calls to context.execute() here emit the given string to the
39+
script output.
40+
41+
"""
42+
url = config.get_main_option("sqlalchemy.url")
43+
context.configure(
44+
url=url,
45+
target_metadata=target_metadata,
46+
literal_binds=True,
47+
dialect_opts={"paramstyle": "named"},
48+
render_as_batch=True
49+
)
50+
51+
with context.begin_transaction():
52+
context.run_migrations()
53+
54+
55+
def run_migrations_online() -> None:
56+
"""Run migrations in 'online' mode.
57+
58+
In this scenario we need to create an Engine
59+
and associate a connection with the context.
60+
61+
"""
62+
connectable = engine_from_config(
63+
config.get_section(config.config_ini_section),
64+
prefix="sqlalchemy.",
65+
poolclass=pool.NullPool,
66+
)
67+
68+
with connectable.connect() as connection:
69+
context.configure(
70+
connection=connection, target_metadata=target_metadata
71+
)
72+
73+
with context.begin_transaction():
74+
context.run_migrations()
75+
76+
77+
if context.is_offline_mode():
78+
run_migrations_offline()
79+
else:
80+
run_migrations_online()

alembic/script.py.mako

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
${imports if imports else ""}
11+
12+
# revision identifiers, used by Alembic.
13+
revision = ${repr(up_revision)}
14+
down_revision = ${repr(down_revision)}
15+
branch_labels = ${repr(branch_labels)}
16+
depends_on = ${repr(depends_on)}
17+
18+
19+
def upgrade() -> None:
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade() -> None:
24+
${downgrades if downgrades else "pass"}

app/__init__.py

Whitespace-only changes.
150 Bytes
Binary file not shown.

app/__pycache__/log.cpython-310.pyc

715 Bytes
Binary file not shown.

app/__pycache__/main.cpython-310.pyc

1.12 KB
Binary file not shown.
2 KB
Binary file not shown.

app/api/__init__.py

Whitespace-only changes.
154 Bytes
Binary file not shown.
659 Bytes
Binary file not shown.

app/api/apiv1.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from fastapi import APIRouter
2+
3+
from app.api.endpoints import login, users, sites, messages, webhooks, subscribes, media
4+
5+
api_router = APIRouter()
6+
api_router.include_router(login.router, tags=["login"])
7+
api_router.include_router(users.router, prefix="/users", tags=["users"])
8+
api_router.include_router(sites.router, prefix="/sites", tags=["sites"])
9+
api_router.include_router(messages.router, prefix="/messages", tags=["messages"])
10+
api_router.include_router(webhooks.router, prefix="/webhooks", tags=["webhooks"])
11+
api_router.include_router(subscribes.router, prefix="/subscribes", tags=["subscribes"])
12+
api_router.include_router(media.router, prefix="/media", tags=["media"])

app/api/endpoints/__init__.py

Whitespace-only changes.
Binary file not shown.
Binary file not shown.
887 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

app/api/endpoints/login.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from datetime import timedelta
2+
from typing import Any
3+
4+
from fastapi import APIRouter, Depends, HTTPException
5+
from fastapi.security import OAuth2PasswordRequestForm
6+
from sqlalchemy.orm import Session
7+
8+
from app import schemas
9+
from app.core import security, settings
10+
from app.db import get_db
11+
from app.db.models.user import User
12+
13+
router = APIRouter()
14+
15+
16+
@router.post("/login/access-token", response_model=schemas.Token)
17+
async def login_access_token(
18+
db: Session = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
19+
) -> Any:
20+
"""
21+
获取认证Token
22+
"""
23+
user = User.authenticate(
24+
db=db,
25+
email=form_data.username,
26+
password=form_data.password
27+
)
28+
if not user:
29+
raise HTTPException(status_code=400, detail="用户名或密码不正确")
30+
elif not user.is_active:
31+
raise HTTPException(status_code=400, detail="用户未启用")
32+
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
33+
return {
34+
"access_token": security.create_access_token(
35+
user.id, expires_delta=access_token_expires
36+
),
37+
"token_type": "bearer",
38+
}

app/api/endpoints/media.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from fastapi import APIRouter, HTTPException, Depends
2+
3+
from app import schemas
4+
from app.chain.identify import IdentifyChain
5+
from app.db.models.user import User
6+
from app.db.userauth import get_current_active_user
7+
8+
router = APIRouter()
9+
10+
11+
@router.post("/recognize", response_model=schemas.Context)
12+
async def recognize(title: str,
13+
subtitle: str = None,
14+
current_user: User = Depends(get_current_active_user)):
15+
"""
16+
识别媒体信息
17+
"""
18+
if not current_user:
19+
raise HTTPException(
20+
status_code=400,
21+
detail="需要授权",
22+
)
23+
# 识别媒体信息
24+
context = IdentifyChain().process(title=title, subtitle=subtitle)
25+
return context.to_dict()

app/api/endpoints/messages.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from typing import Union
2+
3+
from fastapi import APIRouter, BackgroundTasks
4+
from fastapi import Request
5+
6+
from app import schemas
7+
from app.chain.user_message import UserMessageChain
8+
from app.core import settings
9+
from app.log import logger
10+
from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt
11+
12+
router = APIRouter()
13+
14+
15+
def start_message_chain(request: Request):
16+
"""
17+
启动链式任务
18+
"""
19+
UserMessageChain().process(request)
20+
21+
22+
@router.post("/", response_model=schemas.Response)
23+
async def user_message(background_tasks: BackgroundTasks, request: Request):
24+
"""
25+
用户消息响应
26+
"""
27+
background_tasks.add_task(start_message_chain, request)
28+
return {"success": True}
29+
30+
31+
@router.get("/")
32+
async def wechat_verify(echostr: str, msg_signature: str, timestamp: Union[str, int], nonce: str):
33+
"""
34+
用户消息响应
35+
"""
36+
logger.info(f"收到微信验证请求: {echostr}")
37+
try:
38+
wxcpt = WXBizMsgCrypt(sToken=settings.WECHAT_TOKEN,
39+
sEncodingAESKey=settings.WECHAT_ENCODING_AESKEY,
40+
sReceiveId=settings.WECHAT_CORPID)
41+
except Exception as err:
42+
logger.error(f"微信请求验证失败: {err}")
43+
return str(err)
44+
ret, sEchoStr = wxcpt.VerifyURL(sMsgSignature=msg_signature,
45+
sTimeStamp=timestamp,
46+
sNonce=nonce,
47+
sEchoStr=echostr)
48+
if ret != 0:
49+
logger.error("微信请求验证失败 VerifyURL ret: %s" % str(ret))
50+
# 验证URL成功,将sEchoStr返回给企业号
51+
return sEchoStr

app/api/endpoints/sites.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import List
2+
3+
from fastapi import APIRouter, Depends, HTTPException
4+
from sqlalchemy.orm import Session
5+
6+
from app import schemas
7+
from app.chain.cookiecloud import CookieCloudChain
8+
from app.db import get_db
9+
from app.db.models.site import Site
10+
from app.db.models.user import User
11+
from app.db.userauth import get_current_active_user
12+
13+
router = APIRouter()
14+
15+
16+
@router.get("/", response_model=List[schemas.Site])
17+
async def read_sites(db: Session = Depends(get_db),
18+
current_user: User = Depends(get_current_active_user)) -> List[dict]:
19+
"""
20+
获取站点列表
21+
"""
22+
if not current_user:
23+
raise HTTPException(
24+
status_code=400,
25+
detail="需要授权",
26+
)
27+
return Site.list(db)
28+
29+
30+
@router.get("/cookiecloud", response_model=schemas.Response)
31+
async def cookie_cloud_sync(current_user: User = Depends(get_current_active_user)) -> dict:
32+
"""
33+
运行CookieCloud同步站点信息
34+
"""
35+
if not current_user:
36+
raise HTTPException(
37+
status_code=400,
38+
detail="需要授权",
39+
)
40+
status, error_msg = CookieCloudChain().process()
41+
if not status:
42+
return {"success": False, "message": error_msg}
43+
return {"success": True, "message": error_msg}

0 commit comments

Comments
 (0)