♻️ 迁移到 uv 并重写 Dockerfile (#667)

Co-authored-by: BalconyJH <balconyjh@gmail.com>
Co-authored-by: suyiiyii <suyiiyii@gmail.com>
This commit is contained in:
Azide 2025-01-03 17:55:06 +08:00 committed by GitHub
parent eb2eae61bf
commit f35c6c2fd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 3794 additions and 5406 deletions

View File

@ -2,9 +2,9 @@
"name": "Default Linux Universal",
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
"ghcr.io/va-h/devcontainers-features/uv:1": {}
},
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && npm install -g pnpm && pnpm install",
"postCreateCommand": "uv venv && uv sync --all-groups --all-extras && uv run pre-commit install && npm install -g pnpm && pnpm install",
"customizations": {
"vscode": {
"settings": {
@ -12,7 +12,7 @@
"python.analysis.typeCheckingMode": "basic",
"ruff.organizeImports": false,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.fixAll.ruff": true,
"source.organizeImports": true
@ -44,8 +44,6 @@
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.isort",
"ms-python.black-formatter",
"charliermarsh.ruff",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",

View File

@ -1,19 +1,24 @@
*.ini
*.yaml
*.yml
*.md
*.json
*.toml
*.xml
tests
node_modules
admin-frontend
data*
htmlcov
docker
dist
docs
.*
venv
.venv
/admin-frontend/node_modules
/LICENSE
/.editorconfig
/.gitattributes
/.prettierignore
/.prettierrc
/.devcontainer/
/.github/
!pyproject.toml
# self install 时需要
!README.md

View File

@ -27,7 +27,7 @@ body:
description: 请选择安装方式
options:
- 通过 nb-cli 安装
- 使用 poetry/pdm 等现代包管理器安装
- 使用 uv/poetry/pdm 等现代包管理器安装
- 通过 pip install 安装
- 克隆或下载项目直接使用

View File

@ -6,6 +6,14 @@ inputs:
description: Python version
required: false
default: "3.10"
install-deps:
description: Install dependencies
required: false
default: "false"
install-playwright:
description: Install Playwright
required: false
default: "false"
runs:
using: "composite"
@ -14,7 +22,19 @@ runs:
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Setup Poetry
uses: Gr1N/setup-poetry@v9
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v4
with:
poetry-version: "1.7.1"
version: "latest"
enable-cache: true
- name: Install dependencies
if: ${{ inputs.install-deps == 'true' }}
run: uv sync --frozen
shell: bash
- name: Install Playwright
if: ${{ inputs.install-playwright == 'true' }}
run: uv run playwright install
shell: bash

View File

@ -11,7 +11,7 @@ on:
- nonebot_bison/**
- tests/**
- pyproject.toml
- poetry.lock
- uv.lock
- docker.env.prod
- .github/**
pull_request:
@ -21,7 +21,7 @@ on:
- nonebot_bison/**
- tests/**
- pyproject.toml
- poetry.lock
- uv.lock
- docker.env.prod
- .github/**
types:
@ -72,12 +72,11 @@ jobs:
uses: ./.github/actions/setup-python
with:
python-version: ${{ matrix.python-version }}
- name: Install prerequisites
run: poetry install
install-deps: "true"
install-playwright: "true"
- name: Run Pytest
run: poetry run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare and not render' -n auto
run: uv run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare and not render' -n auto
- name: Upload coverage report
uses: codecov/codecov-action@v5
@ -106,12 +105,11 @@ jobs:
uses: ./.github/actions/setup-python
with:
python-version: ${{ matrix.python-version }}
- name: Install prerequisites
run: poetry install
install-deps: "true"
install-playwright: "true"
- name: Run Pytest
run: poetry run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare' -n auto
run: uv run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare' -n auto
- name: Upload coverage report
uses: codecov/codecov-action@v5
@ -127,12 +125,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Download frontend files
uses: actions/download-artifact@v4
with:
name: frontend
path: ./nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -144,7 +136,7 @@ jobs:
if: github.event_name == 'pull_request'
with:
context: .
file: ./docker/Dockerfile_with_frontend
file: ./docker/Dockerfile
push: false
load: true
tags: felinae98/nonebot-bison:dummy
@ -174,26 +166,20 @@ jobs:
if: github.event_name != 'pull_request'
with:
context: .
file: ./docker/Dockerfile_with_frontend
file: ./docker/Dockerfile
push: true
tags: felinae98/nonebot-bison:${{ env.GIT_BRANCH_NAME }}
cache-from: type=gha
cache-to: type=gha,mode=max
docker-main-sentry:
name: Docker main sentry
docker-main-nobrowser:
name: Docker main without browser
runs-on: ubuntu-latest
needs: [build-frontend, test]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Download frontend files
uses: actions/download-artifact@v4
with:
name: frontend
path: ./nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -215,8 +201,8 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile_with_frontend_sentry
file: ./docker/Dockerfile_without_browser
push: ${{ github.event_name != 'pull_request' }}
tags: felinae98/nonebot-bison:${{ env.GIT_BRANCH_NAME }}-sentry
tags: felinae98/nonebot-bison:${{ env.GIT_BRANCH_NAME }}-nobrowser
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -9,35 +9,12 @@ on:
type: string
jobs:
build-frontend:
name: Build Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Frontend
uses: ./.github/actions/build-frontend
- name: Upload dist
uses: actions/upload-artifact@v4
with:
name: frontend
path: ./admin-frontend/build/
docker-main:
name: Docker main
runs-on: ubuntu-latest
needs: [build-frontend]
steps:
- uses: actions/checkout@v4
- name: Download frontend files
uses: actions/download-artifact@v4
with:
name: frontend
path: ./src/plugins/nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -54,25 +31,18 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile_with_frontend
file: ./docker/Dockerfile
push: true
tags: felinae98/nonebot-bison:${{ inputs.dockerTag }}
cache-from: type=gha
cache-to: type=gha,mode=max
docker-main-sentry:
name: Docker main sentry
docker-main-nobrowser:
name: Docker main without browser
runs-on: ubuntu-latest
needs: [build-frontend]
steps:
- uses: actions/checkout@v4
- name: Download frontend files
uses: actions/download-artifact@v4
with:
name: frontend
path: ./src/plugins/nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -89,7 +59,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile_with_frontend_sentry
tags: felinae98/nonebot-bison:${{ inputs.dockerTag }}-sentry
file: ./docker/Dockerfile_without_browser
tags: felinae98/nonebot-bison:${{ inputs.dockerTag }}-nobrowser
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -11,7 +11,7 @@ on:
- nonebot_bison/**
- tests/**
- pyproject.toml
- poetry.lock
- uv.lock
- docker.env.prod
- .github/**
pull_request:
@ -21,7 +21,7 @@ on:
- nonebot_bison/**
- tests/**
- pyproject.toml
- poetry.lock
- uv.lock
- docker.env.prod
- .github/**
types:
@ -55,7 +55,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install prerequisites
run: poetry add pydantic@^1.10 && poetry install
run: uv add pydantic==1.10 && uv sync --frozen && uv run playwright install
- name: Run Pytest
run: poetry run pytest -k 'not compare and not render' -n auto
run: uv run pytest -k 'not compare and not render' -n auto

View File

@ -16,7 +16,7 @@ jobs:
- name: Setup Python environment
uses: ./.github/actions/setup-python
- run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV
- run: echo "TAG_NAME=v$(uvx --from=toml-cli toml get --toml-path=pyproject.toml project.version)" >> $GITHUB_ENV
- name: Archive Changelog
uses: docker://ghcr.io/nonebot/auto-changelog:master
@ -30,6 +30,6 @@ jobs:
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git add .
git commit -m ":bookmark: Release $(poetry version -s)"
git commit -m ":bookmark: Release ${{ env.TAG_NAME }}"
git tag ${{ env.TAG_NAME }}
git push && git push --tags

View File

@ -54,24 +54,17 @@ jobs:
- name: Publish PyPI and Github
run: |
poetry publish --build -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} || echo "Already pushed to pypi"
uv publish --build -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} || echo "Already pushed to pypi"
gh release upload --clobber ${{ env.TAG_NAME }} dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-docker:
runs-on: ubuntu-latest
needs: build-frontend
steps:
- uses: actions/checkout@v4
- name: Download frontend files
uses: actions/download-artifact@v4
with:
name: frontend
path: ./nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -92,7 +85,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile_with_frontend
file: ./docker/Dockerfile
push: true
tags: |
felinae98/nonebot-bison:latest
@ -104,10 +97,10 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile_with_frontend_sentry
file: ./docker/Dockerfile_without_browser
push: true
tags: |
felinae98/nonebot-bison:${{ env.TAG_NAME }}-sentry
felinae98/nonebot-bison:sentry
felinae98/nonebot-bison:${{ env.TAG_NAME }}-nobrowser
felinae98/nonebot-bison:nobrowser
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,16 +1,207 @@
FROM node:22.11.0 AS frontend
ADD . /app
WORKDIR /app/admin-frontend
RUN npm install -g pnpm
RUN pnpm install && pnpm build
# syntax=docker/dockerfile:1.10
FROM python:3.12-slim AS metadata-stage
WORKDIR /tmp
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
TZ=Asia/Shanghai
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
python3-pip \
git \
&& pip install toml \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY ./pyproject.toml ./
RUN echo "📋 Collecting metadata..." && \
export CREATED_DATE="$(date -u '+%Y-%m-%d %H:%M:%S UTC')" && \
export PYPROJECT_NAME=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["name"])') && \
export DESCRIPTION=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["description"])') && \
export AUTHOR=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["authors"][0]["name"])') && \
echo "⏰ Created Date: $CREATED_DATE" && \
echo "📝 Project Name: $PYPROJECT_NAME" && \
echo "📄 Description: $DESCRIPTION" && \
echo "👤 Author: $AUTHOR" && \
echo "$CREATED_DATE" > /tmp/CREATED_DATE && \
echo "$PYPROJECT_NAME" > /tmp/PYPROJECT_NAME && \
echo "$DESCRIPTION" > /tmp/DESCRIPTION && \
echo "$AUTHOR" > /tmp/AUTHOR
RUN --mount=type=bind,source=./.git/,target=/tmp/.git/ \
echo "🏷️ Checking version information..." && \
VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD || echo "unknown") && \
echo "📌 Version: $VERSION" && \
echo "$VERSION" > /tmp/VERSION
LABEL org.opencontainers.image.created="$(cat /tmp/CREATED_DATE)" \
org.opencontainers.image.authors="$(cat /tmp/AUTHOR)" \
org.opencontainers.image.description="$(cat /tmp/DESCRIPTION)" \
org.opencontainers.image.name="$(cat /tmp/PYPROJECT_NAME)" \
org.opencontainers.image.version="$(cat /tmp/VERSION)"
FROM node:20-slim AS frontend-stage
WORKDIR /tmp/admin-frontend
ENV TZ=Asia/Shanghai \
PNPM_HOME="/pnpm" \
PATH="$PNPM_HOME:$PATH"
COPY ./admin-frontend/package.json ./admin-frontend/pnpm-lock.yaml ./
RUN corepack enable && \
corepack prepare pnpm@latest --activate && \
echo "🔔 Verifying Node.js installation..." && \
if NODE_VERSION=$(node --version 2>&1); then \
echo "✅ Node.js version ${NODE_VERSION} installed successfully"; \
else \
echo "❌ Node.js installation failed" && exit 1; \
fi && \
echo "🔔 Verifying pnpm installation..." && \
if PNPM_VERSION=$(pnpm --version 2>&1); then \
echo "✅ pnpm version ${PNPM_VERSION} installed successfully"; \
else \
echo "❌ pnpm installation failed" && exit 1; \
fi
RUN pnpm install --frozen-lockfile
COPY ./admin-frontend .
RUN mkdir -p ../nonebot_bison/admin_page/dist
RUN echo "🏗️ Starting frontend build..." && \
pnpm run build && \
echo "✅ Frontend build completed"
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS playwright-stage
WORKDIR /tmp
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
TZ=Asia/Shanghai
RUN echo "📦 Installing playwright..." && \
uvx playwright install --with-deps chromium && \
echo "✅ Playwright installed successfully"
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv-stage
FROM python:3.11
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
WORKDIR /app
COPY ./README.md ./pyproject.toml ./poetry.lock* /app/
RUN poetry install --only=main,docker
ADD src /app/src
ADD bot.py /app/
COPY --from=frontend /app/nonebot_bison/admin_page/dist /app/nonebot_bison/admin_page/dist
ENV HOST=0.0.0.0
CMD ["nb", "run"]
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_FROZEN=1 \
UV_LINK_MODE=copy \
TZ=Asia/Shanghai
RUN echo "🔔 Verifying uv installation..." && \
if UV_VERSION=$(uv --version 2>&1); then \
echo "✅ uv version ${UV_VERSION} installed successfully"; \
else \
echo "❌ uv installation failed" && exit 1; \
fi
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
echo "📦 Installing project dependencies..." && \
uv sync --no-install-project --no-dev --extra docker && \
echo "✅ Dependencies installed successfully"
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS production
WORKDIR /app
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_FROZEN=1 \
UV_LINK_MODE=copy \
TZ=Asia/Shanghai \
SHELL="/bin/bash"
RUN echo "📦 Installing system dependencies..." && \
apt-get update && \
apt-get install -y --no-install-recommends \
curl \
xvfb \
fonts-noto-color-emoji \
fonts-unifont \
libfontconfig1 \
libfreetype6 \
xfonts-scalable \
fonts-liberation \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-tlwg-loma-otf \
at-spi2-common \
fonts-freefont-ttf \
libasound2 \
libasound2-data \
libatk-bridge2.0-0 \
libatk1.0-0 \
libatspi2.0-0 \
libavahi-client3 \
libavahi-common-data \
libavahi-common3 \
libcairo2 \
libcups2 \
libdatrie1 \
libdbus-1-3 \
libfribidi0 \
libgbm1 \
libglib2.0-0 \
libgraphite2-3 \
libharfbuzz0b \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libthai-data \
libthai0 \
libwayland-server0 \
libxcb-render0 \
libxcomposite1 \
libxdamage1 \
libxi6 \
libxkbcommon0 && \
fc-cache -fv && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
echo "✅ System dependencies installed successfully"
# 少变动的放上面,频繁变动的放下面
COPY ./docker.env.prod ./.env
COPY --from=playwright-stage /root/.cache/ms-playwright /root/.cache/ms-playwright
COPY extra_plugins/ /app/extra_plugins/
COPY --from=frontend-stage /tmp/nonebot_bison/admin_page/dist ./nonebot_bison/admin_page/dist
COPY --from=uv-stage /app /app
COPY ./pyproject.toml ./uv.lock ./README.md ./
COPY nonebot_bison/ /app/nonebot_bison/
COPY --from=metadata-stage /tmp/VERSION /app/VERSION
RUN --mount=type=cache,target=/root/.cache/uv \
echo "📦 Installing bison project..." && \
uv sync --no-dev --extra docker && \
uv run nb adapter install nonebot-adapter-satori && \
uv run nb adapter install nonebot-adapter-qq && \
uv run nb plugin install nonebot-plugin-sentry && \
echo "✅ Bison installed successfully"
EXPOSE 8080
RUN echo '#!/bin/bash' > /app/start.sh && \
echo 'echo "📌 Current bison Version: $(cat /app/VERSION)"' >> /app/start.sh && \
echo 'echo "🚀 Starting service..."' >> /app/start.sh && \
echo 'source /app/.venv/bin/activate' >> /app/start.sh && \
echo 'nb run' >> /app/start.sh && \
chmod +x /app/start.sh
CMD ["/app/start.sh"]

View File

@ -1,10 +0,0 @@
FROM python:3.11
RUN apt-get update && apt-get install -y fonts-wqy-microhei chromium nano
RUN python3 -m pip config set global.index-url https://mirrors.aliyun.com/pypi/simple
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
WORKDIR /app
COPY ./README.md ./pyproject.toml ./poetry.lock* /app/
RUN poetry install --only=main,docker
ENV BISON_BROWSER=local:/usr/bin/chromium
ADD src /app/src
ENV HOST=0.0.0.0

View File

@ -1,9 +0,0 @@
FROM python:3.11
RUN python3 -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
WORKDIR /app
COPY ./README.md ./pyproject.toml ./poetry.lock* /app/
RUN poetry install --only=main,docker
ADD src /app/src
ENV HOST=0.0.0.0
CMD ["nb", "run"]

View File

@ -1,49 +0,0 @@
# syntax=docker/dockerfile:1.10
FROM python:3.11-slim-bullseye AS base
FROM base AS builder
ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
PATH="$PATH:/runtime/bin" \
# Versions:
POETRY_VERSION=1.7.1
RUN apt-get update && apt-get install -y build-essential unzip wget python-dev git
RUN pip install "poetry==$POETRY_VERSION"
WORKDIR /src
COPY README.md pyproject.toml poetry.lock /src/
RUN poetry export --only=main,docker --without-hashes --no-interaction --no-ansi -f requirements.txt -o requirements.txt
RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt
FROM base AS runtime
WORKDIR /app
RUN --mount=type=cache,target=/var/cache/apt \
--mount=type=cache,target=/var/lib/apt \
apt-get update && apt-get install -y xvfb fonts-noto-color-emoji ttf-unifont \
libfontconfig1 libfreetype6 xfonts-cyrillic xfonts-scalable fonts-liberation \
fonts-ipafont-gothic fonts-wqy-zenhei fonts-tlwg-loma-otf \
fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 \
libcairo2 libcups2 libdbus-1-3 libdrm2 libegl1 libgbm1 libglib2.0-0 libgtk-3-0 \
libnspr4 libnss3 libpango-1.0-0 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxdamage1 libxext6 libxfixes3 libxrandr2 libxshmfence1 nano
COPY --from=builder /runtime /usr/local
ADD . /app/
RUN pip install -e . && playwright install chromium
RUN mv docker.env.prod .env.prod && \
nb adapter install nonebot-adapter-red && \
nb adapter install nonebot-adapter-qq
ENV HOST=0.0.0.0
CMD ["nb", "run"]
# vim: ft=dockerfile

View File

@ -1,50 +0,0 @@
# syntax=docker/dockerfile:1.10
FROM python:3.11-slim-bullseye AS base
FROM base AS builder
ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
PATH="$PATH:/runtime/bin" \
# Versions:
POETRY_VERSION=1.7.1
RUN apt-get update && apt-get install -y build-essential unzip wget python3-dev git
RUN pip install "poetry==$POETRY_VERSION"
WORKDIR /src
COPY pyproject.toml poetry.lock /src/
RUN poetry export --only=main,docker --without-hashes --no-interaction --no-ansi -f requirements.txt -o requirements.txt
RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt
FROM base AS runtime
WORKDIR /app
RUN --mount=type=cache,target=/var/cache/apt \
--mount=type=cache,target=/var/lib/apt \
apt-get update && apt-get install -y xvfb fonts-noto-color-emoji ttf-unifont \
libfontconfig1 libfreetype6 xfonts-cyrillic xfonts-scalable fonts-liberation \
fonts-ipafont-gothic fonts-wqy-zenhei fonts-tlwg-loma-otf \
fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 \
libcairo2 libcups2 libdbus-1-3 libdrm2 libegl1 libgbm1 libglib2.0-0 libgtk-3-0 \
libnspr4 libnss3 libpango-1.0-0 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxdamage1 libxext6 libxfixes3 libxrandr2 libxshmfence1 nano
COPY --from=builder /runtime /usr/local
ADD . /app/
RUN pip install -e . && playwright install chromium
RUN mv docker.env.prod .env.prod && \
nb adapter install nonebot-adapter-red && \
nb adapter install nonebot-adapter-qq && \
nb plugin install nonebot-plugin-sentry
ENV HOST=0.0.0.0
CMD ["nb", "run"]
# vim: ft=dockerfile

View File

@ -0,0 +1,147 @@
# syntax=docker/dockerfile:1.10
FROM python:3.12-slim AS metadata-stage
WORKDIR /tmp
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
TZ=Asia/Shanghai
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
python3-pip \
git \
&& pip install toml \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY ./pyproject.toml ./
RUN echo "📋 Collecting metadata..." && \
export CREATED_DATE="$(date -u '+%Y-%m-%d %H:%M:%S UTC')" && \
export PYPROJECT_NAME=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["name"])') && \
export DESCRIPTION=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["description"])') && \
export AUTHOR=$(python3 -c 'import toml; print(toml.load("pyproject.toml")["project"]["authors"][0]["name"])') && \
echo "⏰ Created Date: $CREATED_DATE" && \
echo "📝 Project Name: $PYPROJECT_NAME" && \
echo "📄 Description: $DESCRIPTION" && \
echo "👤 Author: $AUTHOR" && \
echo "$CREATED_DATE" > /tmp/CREATED_DATE && \
echo "$PYPROJECT_NAME" > /tmp/PYPROJECT_NAME && \
echo "$DESCRIPTION" > /tmp/DESCRIPTION && \
echo "$AUTHOR" > /tmp/AUTHOR
RUN --mount=type=bind,source=./.git/,target=/tmp/.git/ \
echo "🏷️ Checking version information..." && \
VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD || echo "unknown") && \
echo "📌 Version: $VERSION" && \
echo "$VERSION" > /tmp/VERSION
LABEL org.opencontainers.image.created="$(cat /tmp/CREATED_DATE)" \
org.opencontainers.image.authors="$(cat /tmp/AUTHOR)" \
org.opencontainers.image.description="$(cat /tmp/DESCRIPTION)" \
org.opencontainers.image.name="$(cat /tmp/PYPROJECT_NAME)" \
org.opencontainers.image.version="$(cat /tmp/VERSION)"
FROM node:20-slim AS frontend-stage
WORKDIR /tmp/admin-frontend
ENV TZ=Asia/Shanghai \
PNPM_HOME="/pnpm" \
PATH="$PNPM_HOME:$PATH"
COPY ./admin-frontend/package.json ./admin-frontend/pnpm-lock.yaml ./
RUN corepack enable && \
corepack prepare pnpm@latest --activate && \
echo "🔔 Verifying Node.js installation..." && \
if NODE_VERSION=$(node --version 2>&1); then \
echo "✅ Node.js version ${NODE_VERSION} installed successfully"; \
else \
echo "❌ Node.js installation failed" && exit 1; \
fi && \
echo "🔔 Verifying pnpm installation..." && \
if PNPM_VERSION=$(pnpm --version 2>&1); then \
echo "✅ pnpm version ${PNPM_VERSION} installed successfully"; \
else \
echo "❌ pnpm installation failed" && exit 1; \
fi
RUN pnpm install --frozen-lockfile
COPY ./admin-frontend .
RUN mkdir -p ../nonebot_bison/admin_page/dist
RUN echo "🏗️ Starting frontend build..." && \
pnpm run build && \
echo "✅ Frontend build completed"
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv-stage
WORKDIR /app
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_FROZEN=1 \
UV_LINK_MODE=copy \
TZ=Asia/Shanghai
RUN echo "🔔 Verifying uv installation..." && \
if UV_VERSION=$(uv --version 2>&1); then \
echo "✅ uv version ${UV_VERSION} installed successfully"; \
else \
echo "❌ uv installation failed" && exit 1; \
fi
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
echo "📦 Installing project dependencies..." && \
uv sync --no-install-project --no-dev --extra docker && \
echo "✅ Dependencies installed successfully"
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS production
WORKDIR /app
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_FROZEN=1 \
UV_LINK_MODE=copy \
TZ=Asia/Shanghai \
SHELL="/bin/bash"
# 少变动的放上面,频繁变动的放下面
COPY ./docker.env.prod ./.env
COPY extra_plugins/ /app/extra_plugins/
COPY --from=frontend-stage /tmp/nonebot_bison/admin_page/dist ./nonebot_bison/admin_page/dist
COPY --from=uv-stage /app /app
COPY ./pyproject.toml ./uv.lock ./README.md ./
COPY nonebot_bison/ /app/nonebot_bison/
COPY --from=metadata-stage /tmp/VERSION /app/VERSION
RUN --mount=type=cache,target=/root/.cache/uv \
echo "📦 Installing bison project..." && \
uv sync --no-dev --extra docker && \
uv run nb adapter install nonebot-adapter-satori && \
uv run nb adapter install nonebot-adapter-qq && \
uv run nb plugin install nonebot-plugin-sentry && \
echo "✅ Bison installed successfully"
EXPOSE 8080
ENV BISON_SKIP_BROWSER_CHECK=True \
BISON_USE_BROWSER=False
RUN echo '#!/bin/bash' > /app/start.sh && \
echo 'echo "📌 Current bison Version: $(cat /app/VERSION)"' >> /app/start.sh && \
echo 'echo "🚀 Starting service..."' >> /app/start.sh && \
echo 'source /app/.venv/bin/activate' >> /app/start.sh && \
echo 'nb run' >> /app/start.sh && \
chmod +x /app/start.sh
CMD ["/app/start.sh"]

View File

@ -8,7 +8,7 @@ next: /dev/cookie
## 语言以及工具
1. 本项目使用了`python3.10`的特性进行开发,所以请确保你的 Python 版本>=3.10
2. 本项目使用 poetry 进行依赖管理,请确保开发之前已经进行过`poetry install`,运行时在`poetry shell`的环境中进行运行
2. 本项目使用 uv 进行依赖管理,请确保开发之前已经进行过`uv sync`,运行时在`uv venv`的环境中进行运行
3. 本项目使用的 node 项目管理工具是 pnpm
::: tip 参考
@ -30,7 +30,7 @@ next: /dev/cookie
2. 运行 bot
```bash
poetry run nb run
uv run nb run
```
3. 运行前端:
@ -50,7 +50,7 @@ next: /dev/cookie
## 代码格式
本项目使用了 pre-commit 来进行代码美化和格式化。在`poetry shell`状态下执行`pre-commit install`来安装 git hook可自动在 commit 时
本项目使用了 pre-commit 来进行代码美化和格式化。在`uv venv`状态下执行`pre-commit install`来安装 git hook可自动在 commit 时
格式化代码。
## 适配新网站

View File

@ -23,7 +23,7 @@ next: /usage/easy-use
参考视频教程 [保姆级新手教学 - Well404](https://www.bilibili.com/video/BV1984y1b7JY)
:::
::: warning 防止环境冲突!
建议所有操作都在虚拟环境下进行,推荐使用[`poetry`](https://python-poetry.org/)或者 python 自带的[`venv`](https://docs.python.org/zh-cn/3/library/venv.html)
建议所有操作都在虚拟环境下进行,推荐使用[`uv`](https://docs.astral.sh/uv/)或者 python 自带的[`venv`](https://docs.python.org/zh-cn/3/library/venv.html)
:::
### 使用 nb-cli 安装 <Badge type="tip" text="推荐" vertical="top" />
@ -43,17 +43,17 @@ next: /usage/easy-use
3. 在项目中添加依赖
```bash
poetry add nonebot-bison
uv add nonebot-bison
```
### 手动安装
1. 安装 pip 包`nonebot-bison`
::: code-tabs
@tab poetry
@tab uv
```bash
poetry add nonebot-bison
uv add nonebot-bison
```
@tab pip
@ -169,13 +169,13 @@ Bison 的 WebUI 是需要编译后才能使用的,直接克隆源代码需要
本项目中使用了 Python 3.10 的语法,如果出现问题,请检查 Python 版本
:::
1. 首先安装 poetry[安装方法](https://python-poetry.org/docs/#installation)
2. clone 本项目,在项目中`poetry install`安装依赖
1. 首先安装 uv[安装方法](https://docs.astral.sh/uv/getting-started/installation/)
2. clone 本项目,在项目中使用`uv sync`安装依赖
```bash
git clone https://github.com/felinae98/nonebot-bison.git
cd nonebot-bison
poetry install
uv sync
```
#### WebUI 安装
@ -235,5 +235,5 @@ Bison 的 WebUI 是需要编译后才能使用的,直接克隆源代码需要
2. 启动 Bot
```bash
poetry run nb run
uv run nb run
```

View File

@ -9,14 +9,16 @@ from nonebot import logger, require
from playwright.async_api import Cookie
from nonebot_bison.config.db_model import Cookie as CookieModel
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.utils import Site, http_client
from nonebot_bison.utils.site import CookieClientManager
if TYPE_CHECKING:
from .platforms import Bilibili
require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import get_browser
if plugin_config.bison_use_browser:
require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import get_browser
B = TypeVar("B", bound="Bilibili")

View File

@ -185,7 +185,7 @@ class SiteMeta(type):
elif not kwargs.get("abstract"):
# this is the subclass
if "name" in namespace:
site_manager[namespace["name"]] = cls
site_manager[namespace["name"]] = cls # type: ignore[reportArgumentType]
super().__init__(name, bases, namespace, **kwargs)

5067
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,34 @@
[tool.poetry]
[project]
authors = [{ name = "felinae98", email = "felinae225@qq.com" }]
license = { text = "MIT" }
requires-python = "<4.0.0,>=3.10"
dependencies = [
"beautifulsoup4>=4.12.3",
"feedparser<7.0.0,>=6.0.11",
"httpx>=0.27.2",
"nonebot2[fastapi]<3.0.0,>=2.3.3",
"nonebot-adapter-onebot<3.0.0,>=2.4.5",
"nonebot-plugin-htmlrender>=0.3.5,!=0.5.0",
"nonebot-plugin-datastore<2.0.0,>=1.3.0",
"nonebot-plugin-apscheduler>=0.5.0",
"nonebot-plugin-send-anything-anywhere<0.7.2,>=0.7.1",
"pillow<11.1,>=11.0.0",
"pyjwt<3.0.0,>=2.9.0",
"python-socketio<6.0.0,>=5.11.4",
"tinydb<5.0.0,>=4.8.0",
"qrcode<8.0.0,>=7.4.2",
"pydantic!=2.5.0,!=2.5.1,<3.0.0,>=2.9.2",
"lxml>=5.3.0",
"yarl>=1.11.1",
"hishel<1.0.0,>=0.0.30",
"expiringdictx<2.0.0,>=1.1.0",
"rapidfuzz<4.0.0,>=3.9.7",
]
name = "nonebot-bison"
version = "0.9.5"
description = "Subscribe message from social medias"
authors = ["felinae98 <felinae225@qq.com>"]
license = "MIT"
homepage = "https://github.com/felinae98/nonebot-bison"
keywords = ["nonebot", "nonebot2", "qqbot"]
readme = "README.md"
include = ["nonebot_bison/admin_page/dist/**/*"]
classifiers = [
"Development Status :: 4 - Beta",
"Operating System :: POSIX :: Linux",
@ -20,69 +41,49 @@ classifiers = [
"License :: OSI Approved :: MIT License",
]
[tool.poetry.dependencies]
python = ">=3.10,<4.0.0"
beautifulsoup4 = ">=4.12.3"
feedparser = "^6.0.11"
httpx = ">=0.27.2"
nonebot2 = { extras = ["fastapi"], version = "^2.3.3" }
nonebot-adapter-onebot = "^2.4.5"
nonebot-plugin-htmlrender = ">=0.3.5"
nonebot-plugin-datastore = ">=1.3.0,<2.0.0"
nonebot-plugin-apscheduler = ">=0.5.0"
nonebot-plugin-send-anything-anywhere = ">=0.7.1,<0.7.2"
pillow = ">=11.0.0,<11.1"
pyjwt = "^2.9.0"
python-socketio = "^5.11.4"
tinydb = "^4.8.0"
qrcode = "^7.4.2"
pydantic = ">=2.9.2,<3.0.0,!=2.5.0,!=2.5.1"
lxml = ">=5.3.0"
yarl = ">=1.11.1"
hishel = "^0.0.30"
expiringdictx = "^1.1.0"
rapidfuzz = "^3.9.7"
[project.urls]
homepage = "https://github.com/felinae98/nonebot-bison"
[tool.poetry.group.dev.dependencies]
ipdb = "^0.13.13"
nonemoji = "^0.1.4"
nb-cli = "^1.4.2"
pre-commit = "^4.0.1"
ruff = "^0.8.2"
[tool.poetry.group.test.dependencies]
flaky = "^3.8.1"
nonebug = "^0.3.7"
nonebug-saa = "^0.4.1"
pytest = ">=8.3.3,<9.0.0"
pytest-asyncio = ">=0.24.0,<0.24.1"
pytest-cov = ">=6.0.0,<7"
pytest-mock = "^3.14.0"
pytest-xdist = { extras = ["psutil"], version = "^3.6.1" }
respx = ">=0.21.1,<0.22"
freezegun = "^1.5.1"
[tool.poetry.group.docker]
optional = true
[tool.poetry.group.docker.dependencies]
nb-cli = "^1.4.2"
nonebot2 = { extras = ["fastapi", "aiohttp"], version = "^2.3.3" }
nonebot-adapter-red = "^0.9.0"
nonebot-adapter-qq = "^1.5.1"
poetry-core = "^1.9.0"
[tool.poetry.extras]
cli = ["anyio", "click", "typing-extensions"]
yaml = ["pyyaml"]
all = ["anyio", "click", "typing-extensions", "pyyaml"]
[tool.poetry.plugins.nb_scripts]
[project.entry-points.nb_scripts]
bison = "nonebot_bison.script.cli:main"
[dependency-groups]
dev = [
"ipdb<1.0.0,>=0.13.13",
"nonemoji<1.0.0,>=0.1.4",
"nb-cli<2.0.0,>=1.4.2",
"pre-commit<5.0.0,>=4.0.1",
"ruff<1.0.0,>=0.8.2",
]
test = [
"flaky<4.0.0,>=3.8.1",
"nonebug<1.0.0,>=0.3.7",
"nonebug-saa<1.0.0,>=0.4.1",
"pytest<9.0.0,>=8.3.3",
"pytest-asyncio<0.24.1,>=0.24.0",
"pytest-cov<7,>=6.0.0",
"pytest-mock<4.0.0,>=3.14.0",
"pytest-xdist[psutil]<4.0.0,>=3.6.1",
"respx<0.23.0,>=0.22.0",
"freezegun<2.0.0,>=1.5.1",
"pytz>=2024.2",
]
[project.optional-dependencies]
docker = [
"nb-cli<2.0.0,>=1.4.2",
"nonebot2[aiohttp,fastapi]<3.0.0,>=2.3.3",
"nonebot-adapter-qq<2.0.0,>=1.5.1",
"nonebot-adapter-satori>=0.13.1",
"pip>=24.3.1",
]
[tool.uv]
default-groups = ["dev", "test"]
[build-system]
requires = ["poetry-core>=1.9.0"]
build-backend = "poetry.core.masonry.api"
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pytest.ini_options]
markers = [
@ -157,8 +158,3 @@ executionEnvironments = [
{ root = "./" },
]
defineConstant = { PYDANTIC_V2 = true }
[[tool.poetry.source]]
name = "offical-source"
url = "https://pypi.org/simple/"
priority = "primary"

View File

@ -4,31 +4,7 @@ from nonebug.app import App
import pytest
from pytest_mock import MockerFixture
from tests.utils import BotReply, fake_admin_user, fake_private_message_event, fake_superuser
async def test_add_cookie_rule(app: App, mocker: MockerFixture):
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.sub_manager import add_cookie_matcher
mocker.patch.object(plugin_config, "bison_to_me", True)
async with app.test_matcher(add_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
event = fake_private_message_event(message=Message("添加cookie"), sender=fake_superuser)
ctx.receive_event(bot, event)
ctx.should_pass_rule()
ctx.should_pass_permission()
async with app.test_matcher(add_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
event = fake_private_message_event(message=Message("添加cookie"), sender=fake_admin_user)
ctx.receive_event(bot, event)
ctx.should_not_pass_rule()
ctx.should_pass_permission()
from tests.utils import BotReply, fake_private_message_event, fake_superuser
@pytest.mark.usefixtures("_clear_db")

View File

@ -42,4 +42,5 @@ async def test_without_permission(app: App):
True,
)
ctx.should_pass_rule()
ctx.should_pass_permission()
ctx.should_pass_permission(no_permission_matcher)
ctx.should_not_pass_permission(add_sub_matcher)

View File

@ -19,6 +19,6 @@ async def test_http_error(app: App):
"https://example.com Headers({'host': 'example.com', 'accept': '*/*', 'accept-encoding': 'gzip, deflate',"
" 'connection': 'keep-alive', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
" (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'}) | [403] Headers({'content-length': '"
"15', 'content-type':"
' \'application/json\'}) {"error": "gg"}'
"14', 'content-type':"
' \'application/json\'}) {"error":"gg"}'
]

3288
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff