first commit

master v0.0.1-bate
MelodyKnit 3 years ago
commit 3b19154426

12
.env

@ -0,0 +1,12 @@
ENVIRONMENT=dev
SUPERUSERS=[""]
COMMAND_START=[""]
COMMAND_SEP=["."]
SESSION_EXPIRE_TIMEOUT=90
API_TIMEOUT=300
MYSQL_DB = ""
MYSQL_USER = ""
MYSQL_HOST = ""
MYSQL_PASSWORD = ""

@ -0,0 +1,4 @@
HOST=127.0.0.1
PORT=8080
SECRET=
ACCESS_TOKEN=

146
.gitignore vendored

@ -0,0 +1,146 @@
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Data
data/class_system/*
data/chatword/*
tests
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
doc/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env.dev
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# End of https://www.toptal.com/developers/gitignore/api/python

@ -0,0 +1,9 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
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
COPY ./pyproject.toml ./poetry.lock* /app/
RUN poetry install --no-root --no-dev

@ -0,0 +1,31 @@
<h1 align="center">班级管理机器人</h1>
<p align="center">基于Nonebot2编写与go-cqhttp编写的班级助手</p>
<div align="center">
<img src="https://img.shields.io/badge/python-3.8+-blue" alt="python">
<img src="https://img.shields.io/badge/nonebot2-red" alt="nonebot">
<img src="https://img.shields.io/badge/gocqhttp-purple" alt="go-cqhttp">
<br/>
</div>
- 班级管理实现功能
- 导入班级信息(只有班级教师可以导入)
- 增加院系,班级,教师(用于导入班级信息)
- 班级成员查询与at导入班级信息后可使用以下内容
- 签到
- 德育分记录
- 导出德育
- 导出证明
- 其它功能
- bilibili视频解析
- 天气查询
- 疫情查询
- 动漫资源获取
- 获取ip
- 随机内容
- 聊天
<details>
<summary>班级管理功能</summary>
</details>

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import nonebot
from os import getcwd
from nonebot.adapters.cqhttp import Bot
# Custom your logger
#
# from nonebot.log import logger, default_format
# logger.add("error.log",
# rotation="00:00",
# diagnose=False,
# level="ERROR",
# format=default_format)
# You can pass some keyword args config to init function
nonebot.init()
app = nonebot.get_asgi()
driver = nonebot.get_driver()
driver.register_adapter("cqhttp", Bot)
nonebot.load_plugins("src/utils")
nonebot.load_from_toml("pyproject.toml")
nonebot.load_plugins("src/class_management_system")
# Modify some config / config depends on loaded configs
#
# config = driver.config
# do something...
if __name__ == "__main__":
nonebot.logger.warning("Always use `nb run` to start the bot instead of manually running!")
nonebot.run(app="__mp_main__:app")

@ -0,0 +1,100 @@
CREATE TABLE IF NOT EXISTS faculty_table (
faculty VARCHAR(30) PRIMARY KEY COMMENT '院系',
invitee VARCHAR(99) NOT NULL COMMENT '添加人'
);
CREATE TABLE IF NOT EXISTS teacher (
name VARCHAR(20) NOT NULL COMMENT '教师姓名',
qq BIGINT PRIMARY KEY COMMENT '教师qq号',
invitee VARCHAR(99) NOT NULL COMMENT '添加人',
telephone BIGINT NOT NULL UNIQUE COMMENT '教师电话'
);
CREATE TABLE IF NOT EXISTS expertise_table (
faculty VARCHAR(30) NOT NULL COMMENT '院系',
expertise VARCHAR(60) PRIMARY KEY COMMENT '专业',
FOREIGN KEY (faculty) REFERENCES faculty_table(faculty)
);
CREATE TABLE IF NOT EXISTS class_table (
class_id BIGINT NOT NULL COMMENT '班级号',
expertise VARCHAR(60) NOT NULL COMMENT '专业',
class_group BIGINT NOT NULL UNIQUE COMMENT '班级QQ群',
class_name VARCHAR(255) PRIMARY KEY COMMENT '班级群名',
class_teacher BIGINT NOT NULL COMMENT '班主任QQ',
FOREIGN KEY (class_teacher) REFERENCES teacher(qq),
FOREIGN KEY (expertise) REFERENCES expertise_table(expertise)
);
CREATE TABLE IF NOT EXISTS score_log (
class_name VARCHAR(255) NOT NULL COMMENT '班级群名',
score_type VARCHAR(50) COMMENT '分数类型',
explain_reason TEXT COMMENT '解释原因原因',
name VARCHAR(20) NOT NULL COMMENT '学生姓名',
student_id BIGINT NOT NULL COMMENT '学生学号',
score INT COMMENT '加减的分数',
qq BIGINT NOT NULL COMMENT '学生qq',
log_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '日志时间',
prove VARCHAR(255) DEFAULT "" COMMENT "证明文件"
);
CREATE TABLE IF NOT EXISTS user_info (
VARCHAR(20) NOT NULL COMMENT '学生姓名',
VARCHAR(255) NOT NULL COMMENT '班级名称',
INT NOT NULL COMMENT '个人在班级中的序号',
BIGINT NOT NULL COMMENT '学号',
VARCHAR(10) NOT NULL COMMENT '性别',
BIGINT NOT NULL COMMENT "学生联系方式",
VARCHAR(20) COMMENT '学生身份证',
TIMESTAMP COMMENT '出生日期',
VARCHAR(10) COMMENT '寝室号',
VARCHAR(5) COMMENT '是否为寝室长',
VARCHAR(100) COMMENT '微信号',
QQ BIGINT PRIMARY KEY COMMENT 'QQ',
VARCHAR(100) COMMENT '邮箱号',
VARCHAR(200) COMMENT '民族',
VARCHAR(200) COMMENT '籍贯',
VARCHAR(50) NOT NULL DEFAULT '学生' COMMENT '学生',
VARCHAR(10) COMMENT '是否为团员',
VARCHAR(10) COMMENT '是否为入党积极分子',
VARCHAR(100) COMMENT '团学干部',
VARCHAR(200) COMMENT '加入的社团',
INT NOT NULL DEFAULT 0 COMMENT '分数',
FOREIGN KEY () REFERENCES class_table(class_name)
);
CREATE TABLE IF NOT EXISTS shop (
teacher BIGINT NOT NULL COMMENT '教师QQ',
shop_name VARCHAR(255) NOT NULL COMMENT '商品',
shop_price INT NOT NULL DEFAULT 0 COMMENT '商品价格',
FOREIGN KEY (teacher) REFERENCES teacher(qq)
);
CREATE TABLE IF NOT EXISTS receiving (
title VARCHAR(255) NOT NULL COMMENT '收取标题',
type VARCHAR(255) NOT NULL COMMENT '收取类型',
class_name VARCHAR(255) NOT NULL COMMENT '班级名称',
initiate BIGINT NOT NULL COMMENT '发起人',
completed boolean NOT NULL DEFAULT FALSE COMMENT '是否已经完成',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
);
CREATE TABLE IF NOT EXISTS receiving_pictures (
title VARCHAR(255) NOT NULL COMMENT '收取标题',
user_id BIGINT NOT NULL COMMENT '提交人QQ',
user_name VARCHAR(20) NOT NULL COMMENT '提交人姓名',
class_name VARCHAR(255) NOT NULL COMMENT '班级名称',
file_name VARCHAR(255) NOT NULL COMMENT '文件名称md5',
create_time TIMESTAMP NOT NULL COMMENT '创建时间',
push_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间'
);
CREATE TABLE IF NOT EXISTS cost (
thing TEXT NOT NULL COMMENT '费用所花费在某件事情',
money DOUBLE NOT NULL COMMENT '花费金额',
)

Binary file not shown.

@ -0,0 +1,19 @@
version: "2"
services:
# 其他配置参考 https://hub.docker.com/r/tiangolo/uvicorn-gunicorn-fastapi/
nonebot:
build: .
volumes:
- "/etc/localtime:/etc/localtime"
- "./:/app/"
ports:
- "8080:8080" # 映射端口到宿主机 宿主机端口:容器端口
env_file:
- ".env.prod" # fastapi 使用的环境变量文件
environment:
- ENVIRONMENT=prod # 配置 nonebot 运行环境,此项会被 .env 文件覆盖
- APP_MODULE=bot:app # 配置 asgi 入口
- SECRET # 通过 SECRET=xxx nb up -d 传递密钥
- ACCESS_TOKEN # 通过 ACCESS_TOKEN=xxx nb up -d 传递密钥
- MAX_WORKERS=1 # 如果你有多个QQ且存在 self_id 指定,多个 worker 会导致无法找到其他 websocket 连接
network_mode: bridge

@ -0,0 +1,22 @@
[tool.poetry]
name = "classRobot"
version = "0.1.0"
description = "classRobot"
authors = []
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.7"
nonebot2 = "^2.0.0.a1"
nb-cli = "^0.1.0"
[tool.poetry.dev-dependencies]
nonebot-test = "^0.1.0"
[nonebot.plugins]
plugins = []
plugin_dirs = ["src/plugins"]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

Binary file not shown.

@ -0,0 +1,13 @@
from nonebot import on_request, require
from nonebot.adapters.cqhttp import Bot, FriendRequestEvent
friends = on_request()
IS_USER = require("rule").IS_USER
@friends.handle()
async def friends_handle(bot: Bot, event: FriendRequestEvent, state: dict):
if await IS_USER(bot, event, state):
text = f"{state['user_info']['班级']}-{state['user_info']['姓名']}"
await bot.set_friend_add_request(flag=event.flag, approve=True, remark=text)

@ -0,0 +1,3 @@
# 生日插件
每天检查是否有用户生日,会在用户所在班级群内发送生日消息

@ -0,0 +1,21 @@
from pprint import pprint
from nonebot import require, get_driver, get_bot
from asyncio import sleep
from nonebot.adapters.cqhttp import Bot, Message
from .data_source import config, SelectBirthdayUser
driver = get_driver()
scheduler = require("nonebot_plugin_apscheduler").scheduler
async def birthday(bot: Bot):
async with SelectBirthdayUser() as res:
for txt, group_id in res.text():
await sleep(5)
await bot.send_group_msg(group_id=group_id, message=txt)
@driver.on_bot_connect
async def _(bot: Bot):
scheduler.add_job(birthday, 'cron', hour=0, minute=0, id='birthday', args=[bot])

@ -0,0 +1,7 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"
class_table = "class_table"
notification_time = 0

@ -0,0 +1,44 @@
from datetime import datetime
from nonebot import require
from nonebot.adapters.cqhttp import Message, MessageSegment
from pandas import DataFrame
from .config import Config
mysql = require("botdb").MySQLdbMethods()
config = Config()
class SelectBirthdayUser:
def __init__(self):
self.now = datetime.now()
self.month = self.now.month
self.day = self.now.day
self.class_user = dict()
self.reply = ""
async def birthday_user(self) -> DataFrame:
"""获取生日的用户"""
await mysql.execute(f"select 姓名,qq,班级,出生日期,class_group 群号 from {config.user_table},{config.class_table} "
f"where month(出生日期)=%s and day(出生日期)=%s and class_name=班级",
[self.month, self.day])
return mysql.form()
async def __aenter__(self):
self.all_user = await self.birthday_user()
self.all_class = set(self.all_user["群号"])
for i in self.all_class:
self.class_user[i] = self.all_user[self.all_user["群号"] == i].reset_index().drop("index", axis=1)
return self
def text(self):
for group_id in self.class_user:
for txt in [
"咚咚咚,还有人嘛 (′▽`〃)。",
f"今天是{self.now.date()},然后呢人家想悄悄告你们一个小秘密 (/▽\)",
f"今天是”{','.join(list(self.class_user[group_id]['姓名']))}"
f"同学的生日噢 (✧◡✧)\n\n好啦好啦!你知道的太多了啦,接下来该做什么人家也不用多说了吧。 (>▽<) 嘻嘻~"
]:
yield txt, group_id
async def __aexit__(self, *args):
...

@ -0,0 +1,101 @@
from nonebot import on_command, require
from nonebot.rule import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message, GroupMessageEvent, PrivateMessageEvent
from .data_source import SetSignIn, SignIn, QuerySignIn
rule = require("rule")
CLASS_CADRE = rule.CLASS_CADRE
IS_USER = rule.IS_USER
query_sign_in = on_command("签到情况", aliases={"签到状态"})
set_login_in = on_command("添加签到", aliases={"设置签到"})
set_group_login = on_command("添加群签到")
set_private_login = on_command("添加个人签到", aliases={"添加单独签到"})
login_in = on_command("签到")
# ---------- 发起签到 ----------
async def set_login_in_handle(bot: Bot, event: MessageEvent, state: T_State):
if await CLASS_CADRE(bot, event, state):
state["param"] = state["class_cadre"]
key = event.message
if key:
state["key"] = key
else:
await set_login_in.finish()
def set_login_in_got(typeof: str):
"""添加签到"""
async def _(bot: Bot, event: MessageEvent, state: T_State):
state["key"] = Message(state["key"])
async with SetSignIn(state["param"]["班级"],
state["key"], typeof) as res:
if not res.existence:
await set_login_in.finish(res.text())
else:
await set_login_in.send(res.text())
return _
def set_login_in_reset(typeof: str):
"""判断是否重置签到"""
async def _(bot: Bot, event: MessageEvent, state: T_State):
if "" in state["existence"]:
async with SetSignIn(state["param"]["班级"], state["key"], typeof) as res:
res.reset_sign_in()
await set_login_in.finish(res.text())
return _
set_login_in.handle()(set_login_in_handle)
set_private_login.handle()(set_login_in_handle)
set_group_login.handle()(set_login_in_handle)
set_login_in.got("key", prompt="请输入签到关键字")(set_login_in_got("any"))
set_private_login.got("key", prompt="请输入签到关键字")(set_login_in_got("private"))
set_group_login.got("key", prompt="请输入签到关键字")(set_login_in_got("group"))
set_login_in.got("existence")(set_login_in_reset("any"))
set_private_login.got("existence")(set_login_in_reset("private"))
set_group_login.got("existence")(set_login_in_reset("group"))
# ---------- 签到 ----------
@login_in.handle()
async def login_in_handle(bot: Bot, event: MessageEvent, state: T_State):
if await IS_USER(bot, event, state):
if event.message:
state["key"] = event.message
else:
await login_in.finish()
@login_in.got("key", prompt="请输入签到关键字")
async def login_in_got(bot: Bot, event: MessageEvent, state: T_State):
state["key"] = Message(state["key"])
with SignIn(
event.user_id,
state["user_info"]["姓名"],
state["user_info"]["班级"],
state["key"],
event.message_type) as res:
await login_in.finish(res.text())
# ---------- 查询签到 ----------
@query_sign_in.handle()
async def query_sign_in_handle(bot: Bot, event: MessageEvent, state: T_State):
if await CLASS_CADRE(bot, event, state):
state["param"] = state["class_cadre"]
else:
await query_sign_in.finish()
@query_sign_in.got("param")
async def query_sign_in_got(bot: Bot, event: MessageEvent, state: T_State):
with QuerySignIn(state["param"]["班级"]) as res:
for msg in res.text():
await query_sign_in.send(msg)

@ -0,0 +1,5 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"

@ -0,0 +1,142 @@
from nonebot import require
from typing import Dict
from .config import Config
from nonebot.adapters.cqhttp import Message, MessageSegment
from pandas import DataFrame, concat, Series
mysql = require("botdb").MySQLdbMethods()
config = Config()
sign_ins = {}
def reset_index(df: DataFrame) -> DataFrame:
return df.reset_index().drop("index", axis=1)
class SetSignIn:
def __init__(self, class_name: str, key: Message, typeof: str):
self.class_name = class_name # 班级名称
self.key = key # 签到关键字
self.sign_users = [] # 已签到用户
self.existence = False
self.reply = ""
self.typeof = typeof
def add_sign_in(self):
"""
添加签到
判断班级是否有加入签到
"""
if self.class_name not in sign_ins.keys() or self.existence:
self.existence = False
sign_ins[self.class_name] = self
self.reply = f"{self.class_name}“班级成功设置班级签到,班级成员共{self.users.shape[0]}"
else:
# 签到已经存在
self.reply = Message(f"{self.class_name}已添加签到,是否重置签到(是/否)")
self.existence = True
def reset_sign_in(self):
"""重新设置签到"""
self.add_sign_in()
self.reply = f"成功重置”{self.class_name}“签到。"
def del_sign_in(self):
"""删除签到"""
sign_ins.pop(self.class_name)
async def __aenter__(self):
"""寻找出班级所有成员"""
await mysql.execute(f"select 姓名, qq from {config.user_table} where 班级=%s", [self.class_name])
self.users: DataFrame = mysql.form()
self.add_sign_in()
return self
def text(self) -> Message:
return Message(self.reply)
async def __aexit__(self, *args):
...
class SignIn:
def __init__(self,
user_id: int,
user_name: str,
class_name: str,
key: str,
typeof: str):
self.class_name = class_name
self.user_name = user_name
self.user_id = user_id
self.reply = ""
self.key = key
self.typeof = typeof
def is_key(self):
return self.sign_class.key == self.key
def __enter__(self):
# 签到的班级
self.sign_class: SetSignIn = sign_ins.get(self.class_name)
if self.sign_class:
if self.sign_class.typeof == "any" or self.sign_class.typeof == self.typeof:
if self.is_key():
self.start_sign()
else:
self.reply = "关键字输入错误!!!"
else:
self.reply = "签到失败,您不该在这里签到!"
else:
self.reply = "您的班级未发起签到!!!"
return self
def start_sign(self):
"""
开始签到
查询该用户在班级群内(sign_class.users)并且用户不在已到内(sign_users)
不在sign_users就进行添加作为签到成功
"""
user = (reset_index(self.sign_class.users[self.sign_class.users.qq == self.user_id]).loc[0])
if user.shape[0] and self.user_id not in self.sign_class.sign_users:
self.sign_class.sign_users.append(self.user_id)
self.reply = f"{user['姓名']}“签到成功"
else:
self.reply = "您已经签到过了!"
def text(self):
return Message(self.reply)
def __exit__(self, *args):
...
class QuerySignIn:
def __init__(self, class_name: str):
self.class_name = class_name
self.not_sign_user: DataFrame = DataFrame()
self.reply = ""
def __enter__(self):
self.sign_class: SetSignIn = sign_ins.get(self.class_name)
if self.sign_class:
# 已签到用户
self.sign_user = (self.sign_class.users[self.sign_class.users["qq"].isin(self.sign_class.sign_users)]
.reset_index().drop("index", axis=1))
# 未签到用户
self.not_sign_user = reset_index(concat([self.sign_class.users, self.sign_user])
.drop_duplicates(keep=False))
else:
self.reply = f"{self.class_name}并未发起签到!!!"
return self
def text(self):
if self.sign_class:
yield Message("已签到\n" + " ".join(list(self.sign_user["姓名"])))
yield Message("未签到\n" + " ".join(list(self.not_sign_user["姓名"])))
else:
yield Message("未发起签到!!!")
def __exit__(self, *args):
...

@ -0,0 +1,30 @@
# 任务功能(文件收取)
## 命令
### 添加任务
- 参数:任务名称
- 说明:在你添加任务后会在这个班级内出现该任务,当班级内有任务时可以开始提交\查询\导出\删除任务
### 查询任务
- 参数:任务序号(可选)
- 说明:
- 不添加参数:列出所有任务
- 添加参数:查询任务后当添加序号会列出该任务已经提交的用户与未提交的用户,消息分两次发送
### 提交任务
- 参数1任务序号
- 参数2图片
- 说明:提交的文件会与所有任务的文件进行检查,避免利用别人文件去提交
### 导出任务
- 参数:任务序号
- 说明:导出任务只可以在群内导出
### 删除任务
- 参数1任务序号
- 参数2确认/不删除
- 说明:
- 删除任务后,任务提交的用户与文件会一并删除(不可恢复)
- 当任务被删除后,提交文件的检查重复文件会减少

@ -0,0 +1,160 @@
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, MessageEvent
from .data_source import SelectTasks, AddTask, PushTaskFile, Image, ExportTask, DeleteTask
rule = require("rule")
IS_USER = rule.IS_USER
CLASS_CADRE = rule.CLASS_CADRE
query_task = on_command("查询任务")
add_task = on_command("添加任务")
push_task_file = on_command("提交任务")
export_task = on_command("导出任务")
delete_task = on_command("删除任务")
# 查询任务
@query_task.handle()
async def _(bot: Bot, event: MessageEvent, state: dict):
if await IS_USER(bot, event, state):
text = event.get_plaintext()
task: SelectTasks = await SelectTasks(
state["user_info"]["班级"],
int(text) - 1 if text.isdigit() else None)
async for msg in task.text():
await query_task.send(msg)
# 添加任务
@add_task.handle()
async def _(bot: Bot, event: MessageEvent, state: dict):
if await CLASS_CADRE(bot, event, state):
text = event.get_plaintext()
if text:
state["title"] = text
@add_task.got("title", prompt="这个任务的标题叫什么呢?")
async def _(bot: Bot, event: MessageEvent, state: dict):
text = event.get_plaintext()
if text:
async with AddTask(text, event.user_id, state["class_cadre"]["班级"], state["class_cadre"]["职位"]) as text:
await add_task.finish(text)
else:
await add_task.finish("不告诉我就不给你添加了,哼~")
# 提交任务
@push_task_file.handle()
async def _(bot: Bot, event: MessageEvent, state: dict):
if await IS_USER(bot, event, state):
text = event.get_plaintext()
task: PushTaskFile = await PushTaskFile(state["user_info"]["班级"], int(text) - 1 if text.isdigit() else None)
state["task"] = task
if not task:
await push_task_file.finish(await task.text_one())
elif task.index is None:
await push_task_file.send(await task.text_one())
else:
state['index'] = task.index
@push_task_file.got("index", prompt="你想交第几个任务呀!")
async def _(bot: Bot, event: MessageEvent, state: dict):
index = state["index"]
task: PushTaskFile = state["task"]
if not isinstance(index, int):
text = event.get_plaintext()
index = int(text) - 1 if text.isdigit() else None
if index is None:
await push_task_file.finish(f"不提交就算了,哼~")
elif index not in task:
await push_task_file.finish(f"你有看到我给你列出{index+1}号吗?哼~")
task(state["user_info"]["姓名"], event.user_id, index)
@push_task_file.got('file', prompt="文件给我吧。")
async def _(bot: Bot, event: MessageEvent, state: dict):
task: PushTaskFile = state["task"]
image = [Image(msg.data) for msg in event.message if msg.type == "image"]
if image:
image = image[0]
if await task.file_exists(image):
await push_task_file.finish("你这是拿别人的文件吧,不要瞎搞哦!")
else:
await push_task_file.finish(await task.save(image))
else:
await push_task_file.finish("文件呢?")
# 导出任务
@export_task.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: dict):
if await CLASS_CADRE(bot, event, state):
text = event.get_plaintext()
task: ExportTask = await ExportTask(state["class_cadre"]["班级"], int(text) - 1 if text.isdigit() else None)
state["task"] = task
if not task:
await export_task.finish(await task.text_one())
elif task.index is None:
await export_task.send(await task.text_one())
else:
state['index'] = task.index
@export_task.got("index", prompt="你想导出第几个任务呀!")
async def _(bot: Bot, event: GroupMessageEvent, state: dict):
index = state["index"]
task: ExportTask = state["task"]
if not isinstance(index, int):
text = event.get_plaintext()
index = int(text) - 1 if text.isdigit() else None
if index is None:
await export_task.finish(f"不提交就算了,哼~")
elif index not in task:
await export_task.finish(f"你有看到我给你列出{index+1}号吗?哼~")
if task(index).task_user.shape[0]:
await export_task.send("开始进行打包咯...")
file, name = task.zip()
await export_task.send("打包完成,开始发送文件...")
await bot.call_api("upload_group_file", group_id=event.group_id, file=file, name=name)
else:
await export_task.finish("都没人提交,你这叫我怎么导出嘛!")
# 删除任务
@delete_task.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: dict):
if await CLASS_CADRE(bot, event, state):
text = event.get_plaintext()
task: DeleteTask = await DeleteTask(state["class_cadre"]["班级"], int(text) - 1 if text.isdigit() else None)
state["task"] = task
if not task:
await delete_task.finish(await task.text_one())
elif task.index is None:
await delete_task.send(await task.text_one())
else:
state['index'] = task.index
@delete_task.got("index", prompt="你想删除第几个任务呀!")
async def _(bot: Bot, event: MessageEvent, state: dict):
index = state["index"]
task: ExportTask = state["task"]
if not isinstance(index, int):
text = event.get_plaintext()
index = int(text) - 1 if text.isdigit() else None
if index is None:
await delete_task.finish(f"取消删除")
elif index not in task:
await delete_task.finish(f"你有看到我给你列出{index + 1}号吗?哼~")
state["index"] = index
await delete_task.send(f"如果真的要删除”{task[index]['title']}“请发送”确认“。")
@delete_task.got("delete")
async def _(bot: Bot, event: MessageEvent, state: dict):
if "确认" in event.get_plaintext():
task: DeleteTask = state["task"](state["index"])
await delete_task.finish(await task.delete())

@ -0,0 +1,307 @@
from typing import Union, List, Tuple, Iterator, Any
from zipfile import ZipFile, ZIP_DEFLATED
from aiohttp import ClientSession, ClientTimeout
from nonebot import require
from pandas import DataFrame, Series
from os import path
task = "receiving"
task_file = "receiving_pictures"
mysql = require("botdb").MySQLdbMethods()
readfile = require("readfile").ReadFile("data", "class_system")
class Image:
def __init__(self, data):
self.name = data.get("file").split(".")[0]
self.url = data.get("url")
class SaveImage:
def __init__(self, class_name: str, file_name: str = None):
self.class_name = class_name
self.image_path = path.join(class_name, "images", "tasks_file")
self.file_name = file_name
def join(self, img: Union[Image, str]) -> str:
return path.join(readfile.path, self.image_path, f"{img if isinstance(img, str) else img.name}.jpg")
def for_in_name(self, images: List[Union[Image, str]]) -> Iterator[str]:
for img in images:
yield self.join(img)
async def save(self, images: List[Image]):
for img in images:
async with self.session.get(img.url) as res:
await readfile.write(
path.join(self.image_path, f"{self.file_name or img.name}.jpg"),
await res.read(), mode="wb")
def remove(self, image: Union[str, Image]):
readfile.remove(
self.image_path,
f"{image if isinstance(image, str) else image.name}.jpg"
)
async def __aenter__(self):
self.session = ClientSession(timeout=ClientTimeout(total=10 * 60))
return self
async def __aexit__(self, *args):
await self.session.close()
# 查询任务
class SelectTasks:
def __init__(self, class_name: str, index: int = None):
self.class_name = class_name
self.index = index
self.index_err = False
def __await__(self):
return self._init().__await__()
async def get_tasks(self) -> DataFrame:
"""获取所有任务"""
await mysql.execute(f"select * from {task} where class_name=%s", [self.class_name])
return mysql.form()
async def select_user_in_task(self, series: Union[Series, int]) -> DataFrame:
"""选择某项任务,列出所有提交任务的用户"""
if isinstance(series, int):
series = self[series]
await mysql.execute(f"select * from {task_file} where create_time=%s and class_name=%s and title=%s",
[str(series["create_time"]), self.class_name, series["title"]])
return mysql.form()
async def select_user_not_in_task(self, users: Union[list, set]) -> DataFrame:
await mysql.execute(f"select * from user_info where 班级=%s and qq not in({','.join(['%s'] * len(users))})",
[self.class_name, *users])
return mysql.form()
async def set_task(self, index: int = None) -> List[DataFrame]:
"""设置task的已经提交人数并且返回索引值对应的用户如果索引值为空则返回所有超出则不返回"""
task_user: List[DataFrame] = [await self.select_user_in_task(series) for i, series in self
if i == index or index is None]
if index is None:
self.tasks['completed'] = [f"(提交{i.shape[0]}人)" for i in task_user]
elif index in self:
self.tasks.loc[index, 'completed'] = f"(提交{task_user[0].shape[0]}人)"
else:
self.index_err = True
return task_user
async def _init(self):
self.tasks: DataFrame = await self.get_tasks()
self.length = self.tasks.shape[0]
self.task_user = await self.set_task(self.index)
return self
@staticmethod
def submitted(user: DataFrame) -> str:
if user.shape[0]:
return "已提交\n" + ("\n".join([f"{v['user_name']} | {str(v['push_time']).split('.')[0]}"
for i, v in user.iterrows()]))
else:
return "咦,文件呢?怎么还没人提交呀!"
@staticmethod
def not_submitted(user: DataFrame) -> str:
if user.shape[0]:
return "未提交\n" + (" ".join(user['姓名']))
return "咦,所有人都交了哎!"
async def text_one(self):
"""只回复一条消息"""
return await self.text().__anext__()
async def text(self):
if self.index_err:
yield f"咦,我怎么不知道有{self.index + 1}号???"
else:
if self.length:
if self.index is None:
yield "\n".join(
[f"{i + 1}.{v['title']}{v['completed']} \n "
f"发起时间:{v['create_time'].date()}" for i, v in self])
else:
yield self.submitted(self.task_user[0])
user = await self.select_user_not_in_task(list(self.task_user[0]["user_id"]))
yield self.not_submitted(user)
else:
yield "干什么呀!你们班还没添加任务呢!"
def __str__(self):
return self.text()
def __iter__(self) -> Iterator[Tuple[Any, Series]]:
return iter(self.tasks.iterrows())
def __getitem__(self, item: int) -> Union[Series, None]:
if 0 <= item < self.length:
return self.tasks.loc[item]
return None
def __contains__(self, index: int):
return index is not None and 0 <= index < self.length
def __bool__(self):
return not (not self.length or self.index_err)
# 添加任务
class AddTask(SelectTasks):
def __init__(self, title: str, user_id: int, class_name: str, jop: str):
super().__init__(class_name)
self.jop = jop # 发起人所担任职务
self.title = title # 发起的标题
self.user_id = user_id # 发起人的id
self.class_name = class_name # 发起的班级
async def repeat_task(self, tasks: DataFrame = None):
"""
查询是否为重复任务
重复任务返回False
不是重复任务返回True
"""
if not tasks:
tasks = await self.get_tasks()
return self.title not in list(tasks["title"])
async def add_task(self):
"""创建一个文件收取的任务"""
await mysql.execute_commit(f"insert into {task} (title,initiate,type,class_name) value (%s,%s,%s,%s)",
[self.title, self.user_id, "image", self.class_name])
async def __aenter__(self):
if await self.repeat_task():
await self.add_task()
return f"{self.title}”创建成功 ^_^"
return "歪,你有个还有一个一模一样的任务呢!能不能先删掉那个重复的 >_<!!"
async def __aexit__(self, *args):
...
# 提交任务
class PushTaskFile(SelectTasks):
def __call__(self, user_name: str, user_id: int, index: int):
self.user_name = user_name
self.user_id = user_id
self.index = index
return self
def is_exists(self) -> Union[bool, str]:
"""用户是否提交过"""
task_user = self.task_user[0] if len(self.task_user) == 1 else self.task_user[self.index]
user_id = list(task_user["user_id"])
if self.user_id in user_id:
return task_user["file_name"][user_id.index(self.user_id)]
return False
@staticmethod
async def file_exists(image: Image) -> bool:
"""文件是否已存在"""
await mysql.execute(f"select file_name from {task_file} where file_name=%s", [image.name])
return bool(list(mysql.form()["file_name"]))
async def insert(self, file_name: str, index: int = None):
_task = self[index or self.index]
await mysql.execute_commit(f"insert into {task_file} (title,user_id,user_name,class_name,file_name,create_time)"
"value (%s,%s,%s,%s,%s,%s)",
[_task['title'], self.user_id, self.user_name,
self.class_name, file_name, str(_task['create_time']).split(".")[0]])
async def delete(self, index: int = None):
index = index or self.index
await mysql.execute_commit(f"delete from {task_file} where class_name=%s and title=%s and user_id=%s",
[self.class_name, self[index]['title'], self.user_id])
async def save(self, image: Image):
async with SaveImage(self.class_name) as res:
file = self.is_exists()
if file:
res.remove(file)
await self.delete()
await res.save([image])
await self.insert(image.name)
return "提交成功! ^_^"
# 导出任务
class ExportTask(SelectTasks):
def __call__(self, index: int):
"""
:参数 index:
任务序号
:返回: ExportTask
"""
self.index = index
self.task_user = self.task_user[0] if len(self.task_user) == 1 else self.task_user[index]
return self
def zip(self) -> Tuple[str, str]:
"""
:说明:
将文件打包成压缩文件保存到本地
:返回:
文件路径 文件名称
"""
file = SaveImage(self.class_name)
file_name = f"{self.class_name}{self[self.index]['title']}.zip"
file_path = path.join(readfile.path, self.class_name, file_name)
with ZipFile(file_path, "w", ZIP_DEFLATED) as zip_file:
for i, img in self.task_user.iterrows():
# 文件名以用户名称+qq组成
zip_file.write(file.join(img["file_name"]), f"{img['user_name']}{img['user_id']}.jpg")
return file_path, file_name
# 删除任务
class DeleteTask(SelectTasks):
def __call__(self, index: int):
"""
:参数 index:
任务序号
:返回: DeleteTask
"""
self.index = index
self.task_user = self.task_user[0] if len(self.task_user) == 1 else self.task_user[index]
return self
async def delete_table(self) -> str:
"""
:说明:
删除数据表先删除文件表在删除任务表
:返回:
表格标题
"""
title = self[self.index]["title"]
await mysql.execute_commit(f"delete from {task_file} where class_name=%s and title=%s",
[self.class_name, title])
await mysql.execute_commit(f"delete from {task} where class_name=%s and title=%s",
[self.class_name, title])
return title
def delete_file(self):
"""
:说明:
删除任务文件
"""
file = SaveImage(self.class_name)
for i, img in self.task_user.iterrows():
file.remove(img["file_name"])
async def delete(self):
"""
:说明:
先删除文件然后删除表格
:返回:
回复消息
"""
self.delete_file()
title = await self.delete_table()
return f"{title}”删除成功!"

@ -0,0 +1,28 @@
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
from .query import QueryUsers
find = on_command("find")
rule = require("rule")
IS_USER = rule.IS_USER
CLASS_GROUP = rule.CLASS_GROUP
MASTER = rule.MASTER
@find.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: dict):
if await CLASS_GROUP(bot, event, state):
search = event.get_plaintext().split("-")
# 判断第0个是否有输入需要搜索的某位用户如果没有就默认为自己
if not search[0]:
user_split = QueryUsers.split()
user_split.user_id.append(event.user_id)
else:
user_split = QueryUsers.split(event.message)
# 判断是否存在第二个值,作为搜索展示出的内容
search = (search[1].split() if search[-1] else None) if len(search) > 1 else None
users = QueryUsers(state["class_group"]["class_name"], search)
await find.finish(await users.text(user_split))

@ -0,0 +1,151 @@
from nonebot import require
from nonebot.adapters.cqhttp import Message
from pandas import DataFrame, Series
mysql = require("botdb").MySQLdbMethods()
def get(data: Series):
def _(key: str, fmt: str = None, is_str: str = "") -> str:
"""
:param key: 关键字
:param fmt: 替换字符
:param is_str: 满足条件
:return:
"""
value = data.get(key)
if value == is_str or " " != value != "None" and value:
return fmt if fmt else f"{key}{value}"
return ""
return _
def dataMap(data: DataFrame):
def _(key, arg, na_action=None):
values: Series = data.get(key)
if values is not None:
data[key] = values.map(arg, na_action)
return _
class SplitUser:
def __init__(self, message: Message = None):
self.message = message
self.index = []
self.user_id = []
self.name = []
if message:
for msg in message:
if msg.type == "at":
self.user_id.append(msg.data.get("qq"))
elif msg.type == "text":
for text in msg.data.get("text").split():
if text.isdigit():
if len(text) < 5:
self.index.append(int(text))
else:
self.user_id.append(int(text))
else:
self.name.append(text)
def __bool__(self) -> bool:
return bool(self.user_id or self.index or self.name)
class QueryUsers:
exhibition = {
"序号", "姓名", "班级", "学号", "性别", "联系方式", "出生日期", "寝室", "寝室长", "微信", "qq",
"邮箱", "民族", "职位", "团员", "入党积极份子", "团学", "社团", "分数"
}
def __init__(self, class_name: str, screen: set = None):
screen = screen or self.exhibition
self.class_name = class_name
self.screen = {"姓名", *screen} & self.exhibition
def single_reply(self, users: Series) -> str:
"""单条回复信息"""
find = get(users)
return "\n".join([i for i in [find(v) for v in self.screen] if i])
def multiple_replies(self, users: DataFrame) -> str:
"""多条回复信息"""
return "\n- - - - -\n".join([self.single_reply(v) for i, v in users.iterrows()])
async def teacher_reply(self, users: SplitUser) -> str:
"""查找到的教师回复词"""
user = await self.query_teacher(users)
length = user.shape[0]
if length:
return "\n- - - - -\n".join([f"姓名:{u['name']}\nQQ{u['qq']}\n联系方式:{u['telephone']}"
for i, u in user.iterrows()])
else:
return ""
async def users_reply(self, users: SplitUser) -> str:
"""查找到的学生回复词"""
user = await self.query_user(users)
length = user.shape[0]
if length == 1:
return self.single_reply(user.loc[0])
elif length > 1:
return self.multiple_replies(user)
else:
return ""
async def text(self, users: SplitUser = None):
text = await self.users_reply(users)
if text:
return Message(text)
text = await self.teacher_reply(users)
if text:
return Message("查询到为教师:\n- - - - -\n" + text)
return Message("未查找到该用户!!")
async def query_user(self, users: SplitUser) -> DataFrame:
"""查找班级某些用户"""
await mysql.execute(f"""select {','.join(self.screen)} from user_info where 班级=%s and ({self.set_where(
qq=users.user_id,
学号=users.user_id,
序号=users.index,
姓名=users.name
)})""", [self.class_name, *(users.user_id * 2), *users.index, *users.name])
data: DataFrame = mysql.form()
_map = dataMap(data)
_map("出生日期", lambda x: str(x).split()[0])
_map("寝室长", lambda x: "寝室长" if x == "" else None)
return data
@classmethod
async def query_teacher(cls, users: SplitUser) -> DataFrame:
"""查找教师"""
await mysql.execute(f"""select * from teacher where {cls.set_where(
qq=users.user_id,
name=users.name,
)}""", [*users.user_id, *users.name])
return mysql.form()
@staticmethod
def in_method(tag: str, al: list):
"""sql的in方法"""
if al:
return f"{tag} in({','.join(['%s'] * len(al))})"
@classmethod
def set_where(cls, format_str: str = "or", **kwargs: list):
"""
设置where条件
:param format_str: 需要格式化的字符
:param kwargs: 列名与包含的数据
:return:
如果有数据例如 qq=[123456]
返回 qq in(%s)
如果有多个条件例如 qq=[123456], name=[654897]
返回 qq in(%s) or name in(%s)
"""
return f" {format_str} ".join([cls.in_method(i, kwargs[i]) for i in kwargs if kwargs[i]])
@staticmethod
def split(message: Message = None) -> SplitUser:
"""拆分出用户,索引,号码"""
return SplitUser(message)

@ -0,0 +1,24 @@
from nonebot import require, on_command
from nonebot.adapters.cqhttp import Bot, MessageEvent
from .data_source import SetWatermark
watermark = on_command("水印", aliases={"打水印", "添加水印"})
IS_USER = require("rule").IS_USER
@watermark.handle()
async def watermark_handler(bot: Bot, event: MessageEvent, state: dict):
if await IS_USER(bot, event, state):
for msg in event.message:
if msg.type == "image":
state['params'] = True
break
@watermark.got("params", prompt="请发送图片")
async def watermark_got(bot: Bot, event: MessageEvent, state: dict):
for msg in event.message:
if msg.type == "image":
async with SetWatermark(msg.data["url"], state["user_info"]) as res:
await watermark.send(res.image())

@ -0,0 +1,35 @@
from nonebot import require
from io import BytesIO
from nonebot.adapters.cqhttp import MessageSegment
from PIL.PngImagePlugin import PngImageFile
from pandas import DataFrame
Watermark = require("tools").Watermark
class SetWatermark:
def __init__(self, file, user_info: DataFrame):
self.file = file
self.user_info = user_info
def image(self):
"""发送图片"""
img_bytes = BytesIO()
self.img.save(img_bytes, format="JPEG")
return MessageSegment.image(img_bytes)
async def draw(self) -> PngImageFile:
"""绘制"""
async with Watermark(
url=self.file,
text=f'{self.user_info["姓名"]}\n{self.user_info["班级"]}',
size=100,
xy=(50, 50)) as res:
return res.img
async def __aenter__(self):
self.img = await self.draw()
return self
async def __aexit__(self, *args):
...

@ -0,0 +1,85 @@
from nonebot import on_command, require
from nonebot.rule import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message, GroupMessageEvent
from .data_source import AddScoreLog, SaveDialog, GetMessage, ExportProve
add_log = on_command("德育", aliases={"德育分"})
export_log = on_command("导出德育")
export_prove = on_command("导出证明")
rule = require("rule")
IS_USER = rule.IS_USER
CLASS_CADRE = rule.CLASS_CADRE
DORM_ADMIN = rule.DORM_ADMIN
def get_names(message: str):
names = message.split(" ")
new_names = []
for name in names:
if name:
new_names.append(name)
return new_names
@add_log.handle()
async def add_log_handle(bot: Bot, event: MessageEvent, state: T_State):
"""拆分出姓名"""
if await IS_USER(bot, event, state):
users = event.get_plaintext().split()
state["users"] = None
if users:
if await CLASS_CADRE(bot, event, state):
state["users"] = users
else:
await add_log.finish("歪,你不是班干部不能在后面写别人名字呢!")
else:
await add_log.finish()
@add_log.got("msg", prompt="说明一下这是什么事情吧。 ^_^")
async def add_log_got(bot: Bot, event: MessageEvent, state: T_State):
message = GetMessage(Message(state["msg"]))
if message.images:
state["images"] = message.images
if message.text:
state["msg"] = message.text
else:
await add_log.reject(Message("咦,怎么只有图片呀,你倒是说明一下呀,哼~"))
@add_log.got("images", prompt="图呢?")
async def add_log_got_images(bot: Bot, event: MessageEvent, state: T_State):
images = state["images"]
if isinstance(images, str):
images = GetMessage(Message(images)).images
if images:
names = state["users"] or state["user_info"]['姓名']
async with AddScoreLog(state["user_info"]["班级"], names, event.user_id,
state["user_info"]["学号"], images, state["msg"]) as res:
await add_log.finish(res.text())
else:
await add_log.finish("不给图就不给加!(☆--)")
@export_log.handle()
async def export_log_handle(bot: Bot, event: GroupMessageEvent, state: T_State):
if await CLASS_CADRE(bot, event, state):
async with SaveDialog(state["class_cadre"]["班级"], get_names(event.get_plaintext())) as res:
if res.not_value:
await export_log.finish(res.text())
else:
await bot.call_api("upload_group_file", group_id=event.group_id, file=res.file_path, name=res.name)
@export_prove.handle()
async def export_prove_handle(bot: Bot, event: GroupMessageEvent, state: T_State):
if await CLASS_CADRE(bot, event, state):
async with ExportProve(state["class_cadre"]["班级"], get_names(event.get_plaintext())) as res:
if res.not_value:
await export_prove.finish(res.text())
else:
for i in res.zip():
await export_prove.send(i)
await bot.call_api("upload_group_file", group_id=event.group_id, file=res.file_path, name=res.name)

@ -0,0 +1,6 @@
from pydantic import BaseSettings
class Config(BaseSettings):
score_log = "score_log"
user_table = "user_info"

@ -0,0 +1,203 @@
from nonebot import require
from typing import List, Union
from .config import Config
from aiohttp import ClientSession
from zipfile import ZipFile, ZIP_DEFLATED
from pandas import DataFrame, Series
from json import loads, dumps
from os import path
from datetime import datetime
from nonebot.adapters.cqhttp import Message, MessageSegment
config = Config()
mysql = require("botdb").MySQLdbMethods()
readfile = require("readfile").ReadFile("data", "class_system")
def select_names(names, typeof="name"):
"""查看是否有需要查找的用户的名字"""
return f" and {typeof} in({','.join(['%s'] * len(names))})" if names else ""
class Image:
def __init__(self, data):
self.name = data.get("file").split(".")[0]
self.url = data.get("url")
class GetMessage:
def __init__(self, message: Message):
self.text = ""
self.images: List[Image] = []
for msg in message:
if msg.type == "text":
txt = msg.data.get("text").strip()
if txt:
self.text = txt
elif msg.type == "image":
self.images.append(Image(msg.data))
class AddScoreLog:
def __init__(self, class_name: str,
name: Union[str, list],
user_id: Union[int, list],
student_id: Union[str, list],
images: List[Image],
explain_reason: str):
self.class_name = class_name
self.name = name
self.image = images[0]
self.user_id = user_id
self.student_id = int(student_id)
self.explain_reason = explain_reason
self.reply = ""
self.image_path = path.join(self.class_name, "images")
self.prove_file = path.join(self.class_name, "prove.json")
async def read_prove(self):
"""读取证明"""
try:
return loads(await readfile.read(self.prove_file))
except FileNotFoundError:
"""读取失败时候创建新证明"""
prove = {"all_file": [], "user_file": {}}
readfile.mkdir(self.class_name)
await readfile.write(self.prove_file, dumps(prove))
return prove
async def save_prove(self):
"""保存证明"""
await readfile.write(self.prove_file, dumps(self.prove))
async def query_user(self) -> DataFrame:
"""按照班级查找用户"""
await mysql.execute(f"select 姓名, qq, 学号 from {config.user_table} where 班级=%s{select_names(self.name, '姓名')}",
[self.class_name, *self.name])
return mysql.form()
async def add_logger(self, name, user_id, student_id):
await mysql.execute_commit(f"insert into {config.score_log} "
"(class_name, name, qq, student_id, explain_reason, prove) value (%s,%s,%s,%s,%s,%s)",
[self.class_name, name, user_id, student_id, self.explain_reason, self.image.name])
async def save_image(self):
"""将图片保存到本地"""
readfile.mkdir(self.image_path)
if self.image.name not in self.prove["all_file"]:
self.prove["all_file"].append(self.image.name)
async with ClientSession() as session:
async with session.get(self.image.url) as res:
data = await res.read()
await readfile.write(path.join(self.image_path, f"{self.image.name}.jpg"), data, mode="wb")
async def __aenter__(self):
self.prove = await self.read_prove()
if isinstance(self.name, list):
self.user_info = await self.query_user()
for i, user in self.user_info.iterrows():
self.reply += user["姓名"] + ","
await self.add_logger(user["姓名"], user["qq"], user["学号"])
else:
await self.add_logger(self.name, self.user_id, self.student_id)
self.reply += "德育日志添加成功!!"
return self
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
await self.save_image()
await self.save_prove()
class SaveDialog:
def __init__(self, class_name: str, names: list):
self.date = datetime.now()
self.class_name = class_name
self.names = names
self.class_file = path.join(readfile.path, class_name) # 班级文件路径
self.not_value = True # 是否为空数据
self.file_path = ""
self.reply = ""
def save(self):
"""保存到本地文件"""
try:
self.data.to_excel(self.file_path, index=False)
except FileNotFoundError:
readfile.mkdir(self.class_name)
self.save()
def file_name(self, suffix: str):
"""根据班级名+时间保存为文件名"""
if self.names:
file_name = '_'.join(set(self.data["姓名"]))
else:
file_name = self.class_name
date = str(self.date).split(".")[0].replace(" ", "-").replace(":", "-")
return file_name + date + "." + suffix
@staticmethod
def set_data(data: DataFrame) -> DataFrame:
"""设置数据,将说明与时间相结合,并且添加附加分名称与分数"""
data["说明"] = [str(data.loc[i]["log_time"].date()) + data.loc[i]["说明"] for i in range(data.shape[0])]
data["附加分名称"], data["分数"] = "", ""
data.drop(columns="log_time", axis=0, inplace=True)
data.sort_values(by="姓名", ascending=False, inplace=True) # 按照姓名排序
return data[["学号", "姓名", "附加分名称", "分数", "说明"]]
async def get_data(self) -> DataFrame:
"""查找用户,并且按照当月筛选出用户"""
await mysql.execute(
f"select student_id 学号, name 姓名, explain_reason 说明, log_time, prove from {config.score_log} "
f"where year(log_time)=%s and month(log_time)=%s and class_name=%s{select_names(self.names)}",
[self.date.year, self.date.month, self.class_name, *self.names])
return mysql.form()
async def __aenter__(self):
data: DataFrame = await self.get_data()
if data.shape[0]:
self.not_value = False
self.data: DataFrame = self.set_data(data)
self.name = self.file_name("xlsx") # 文件名
self.file_path = path.join(self.class_file, self.name)
self.save()
else:
self.reply = "未发现日志!!!"
return self
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
...
class ExportProve(SaveDialog):
def __init__(self, class_name: str, names: list):
super().__init__(class_name, names)
async def __aenter__(self):
self.data: DataFrame = await self.get_data()
if self.data.shape[0]:
self.not_value = False
self.name = self.file_name("zip") # 文件名
self.file_path = path.join(self.class_file, self.name)
self.zip()
else:
self.reply = "未发现日志!!!"
return self
def zip(self):
prove = set(self.data["prove"])
length = len(prove)
yield "开始进行打包..."
with ZipFile(self.file_path, "w", ZIP_DEFLATED) as zip_file:
for img in prove:
try:
zip_file.write(path.join(self.class_file, "images", f"{img}.jpg"), f"{img}.jpg")
except FileNotFoundError as err:
length -= 1
print(err)
yield f"打包完成\n文件数量:{length}\n开始发送请耐心等待..."

@ -0,0 +1,58 @@
from nonebot import on_command, require
from .data_source import InsertUser
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, Message
from nonebot.rule import T_State
from typing import Union
import re
rule = require("rule")
permission = require("permission")
TEACHER = permission.TEACHER
MASTER = rule.MASTER
push_user = on_command("导入班级信息", aliases={"导入班级"})
def re_docs(text: str) -> str:
return re.search(r"https://docs.qq.com/sheet/\S[^\"']+", str(text).replace("\\", ""), re.I).group()
def get_url(message: Union[Message, str]) -> str:
url = None
if isinstance(message, Message):
for msg in message:
if msg.type == "json" or msg.type == "xml":
url = re_docs(msg.data["data"])
elif msg.type == "text":
url = re_docs(msg.data["text"])
if url:
return url
else:
return re_docs(message)
def get_text(message: Message):
"""获取文本"""
for msg in message:
if msg.type == "text":
return msg.data.get("text").strip()
@push_user.handle()
async def push_user_handler(bot: Bot, event: GroupMessageEvent, state: T_State):
if await MASTER(bot, event, state):
url = get_url(event.message)
if url:
state["param"] = url
else:
await push_user.finish()
@push_user.got("param", "请转发腾讯在线文档,或者在线文档的链接!!!")
async def push_user_got(bot: Bot, event: GroupMessageEvent, state: T_State):
url = get_url(state["param"])
if url:
# 获取班级名称
class_name = state["all_group"]["class_name"][state["all_group"]["class_group"].index(event.group_id)]
async with InsertUser(url, class_name) as res:
await push_user.finish(res.text())

@ -0,0 +1,35 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"
user_table_param_not_null = {
"姓名": str,
"序号": int,
"学号": int,
"性别": str,
"联系方式": int
}
user_table_param_type = {
"序号": int,
"姓名": str,
"班级": str,
"学号": int,
"性别": str,
"联系方式": int,
"身份证号": str,
"出生日期": str,
"寝室": str,
"寝室长": str,
"微信": str,
"QQ": int,
"邮箱": str,
"民族": str,
"籍贯": str,
"职位": str,
"团员": str,
"入党积极分子": str,
"团学": str,
"社团": str,
"分数": int
}

@ -0,0 +1,173 @@
from aiohttp import ClientSession
from pandas import DataFrame, Series
from datetime import datetime, timedelta
from aiomysql import IntegrityError
from nonebot import require
from nonebot.adapters.cqhttp import Message
import re
from .config import Config
config = Config()
mysql = require("botdb").MySQLdbMethods()
main_url = "https://docs.qq.com/dop-api/opendoc"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/94.0.4606.61 Safari/537.36 ",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"appName": "Opera"
}
class Frame:
def __init__(self, data: list):
data = self._raw_data(data)
self.columns = data[0][-1] + 1
self.rows = data[0][-3] + 1
self.raw_data = data[1]
self.data = self._data_frame()
def _data_frame(self):
"""提取数据"""
df = []
index = 0
# 将列表转为二维
for i in self.raw_data:
if not int(i) % self.columns:
df.append([])
index += 1
value = self.raw_data[i].get("2")
df[index - 1].append(value[1] if value else value)
# 清除空行空列
df = DataFrame(df, dtype="object").dropna(axis=0, how='all').dropna(axis=1, how='all')
index = 0
columns = False
# 提取columns标题
for i in df.values:
index += 1
if not all(i) and not columns:
continue
elif not columns:
return DataFrame(df.values[index:], columns=list(i), dtype="object")
@staticmethod
def _raw_data(data):
"""解析数据,并且可以得出行列与出未加工的数据"""
for values in data["clientVars"]["collab_client_vars"]["initialAttributedText"]["text"][0]:
for value in values:
if isinstance(value["c"][1], dict):
for i in value:
if isinstance(value[i], list):
return value[i]
@staticmethod
def get_excel_date(date_int: int):
"""对于excel日期进行转换以便写入数据"""
if date_int:
return str((datetime(1899, 12, 30) + timedelta(int(date_int) + 1)).date())
return None
def reset_date(self, typeof: str = "出生日期"):
"""重置表格日期表格"""
try:
self.data[typeof] = [self.get_excel_date(i) for i in self.data[typeof]]
except IndexError:
...
finally:
return self
async def get(url: str, params: dict = None) -> Frame:
params = params or []
async with ClientSession() as session:
async with session.get(url, headers=headers, params=params) as res:
return Frame(await res.json())
class InsertUser:
"""
写入班级信息
"""
def __init__(self, url: str, class_name: str):
self.url = url
self.class_name = class_name
self.reply = Message()
def get_params(self) -> dict:
"""提取URL所需params"""
params = {
"outformat": 1,
"normal": 1
}
url = self.url.split("/")[-1]
param = re.findall(r"^\w+|tab=\w+", url)
params["id"] = param[0]
if len(param) > 1:
params["tab"] = param[1].replace("tab=", "")
return params
def set_sql(self, params: Series):
"""
从DataFrame中提取出的每一个Series
筛选出列名然后筛选出必填选项也就是不能为空的参数
:return: sql语句, sql参数数据
"""
keys = set(params.keys()) & set(config.user_table_param_type.keys())
null_param = set(config.user_table_param_not_null.keys()) - keys
for i in keys:
if params[i]:
params[i] = config.user_table_param_type[i](params[i])
if not null_param:
sql = f"insert into {config.user_table} ({','.join([*keys, '班级'])}) value ({','.join(['%s'] * (len(keys) + 1))})"
param = list([*params.loc[keys].values, self.class_name])
return sql, param
else:
return None, null_param
@staticmethod
async def execute_commit(sql: str, params: list = None):
"""向数据库写入数据"""
params = params or []
try:
await mysql.execute_commit(sql, params)
except IntegrityError:
"""数据已存在"""
async def insert(self):
"""写入数据库"""
try:
for i, v in enumerate(self.data.loc): # type: Series
sql, params = self.set_sql(v)
if sql and params:
await self.execute_commit(sql, params)
elif params:
self.reply.append(f"没缺少必要参数有”{','.join(params)}“并且该参数不能为空")
break
except KeyError as err:
"""暂时未知的报错原因大概原因是因为索引从0开始没有len所描述的最后一位所以报错"""
if isinstance(err.args[0], int):
self.reply.append("成功更新班级成员信息,可以使用查询来查看自己信息,如有错误需要修改可以使用修改命令修改本人信息!")
else:
self.reply.append("缺少必要参数%s,并且该参数不能为空!!!" % err)
async def delete_user_info(self):
print(self.class_name)
# await mysql.execute_commit("delete from user_info where 班级=%s", [self.class_name])
async def __aenter__(self):
data = (await get(main_url, self.get_params())).reset_date()
# self.class_name = await self.get_class_name()
await self.delete_user_info()
self.data = data.data
await self.insert()
return self
def text(self):
if isinstance(self.reply, Message):
return self.reply
return Message(self.reply)
async def __aexit__(self, *args):
...

@ -0,0 +1,7 @@
# 查询用户
编写功能
- 查询
- 查询 姓名 qq 序号 学号
- at
- at 姓名 qq 序号 学号

@ -0,0 +1,55 @@
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, Message, GroupMessageEvent
from nonebot.rule import T_State
from .data_source import QueryUser, AtUser
CLASS_GROUP = require("permission").CLASS_GROUP
query_user = on_command("查询", aliases={"查找", "搜索"})
at_user = on_command("at", aliases={"艾特", "@"})
def get_text(message: Message) -> str:
"""获取文本"""
for msg in message:
if msg.type == "text":
return msg.data.get("text").strip()
if msg.type == "at":
return " " + str(msg.data.get("qq"))
@query_user.handle()
async def query_user_handle(bot: Bot, event: GroupMessageEvent, state: T_State):
"""获取查询的用户,如果没有填写查询对象则默认查询自己"""
if await CLASS_GROUP(bot, event):
features = get_text(event.message)
state["param"] = features.split() if features else [event.get_user_id()]
else:
await query_user.finish()
@query_user.got("param")
async def query_user_got(bot: Bot, event: GroupMessageEvent, state: T_State):
async with QueryUser(state["param"], event.group_id) as res:
await query_user.finish(res.text())
@at_user.handle()
async def at_user_handle(bot: Bot, event: GroupMessageEvent, state: T_State):
if await CLASS_GROUP(bot, event):
features = get_text(event.message)
if features:
state["param"] = features.split()
else:
await query_user.finish()
@at_user.got("param", prompt="请输入您需要at的成员名称/序号/qq/学号。")
async def at_user_got(bot: Bot, event: GroupMessageEvent, state: T_State):
if isinstance(state["param"], str):
state["param"] = state["param"].split()
if state["param"]:
async with AtUser(state["param"], event.group_id) as res:
if not res.is_err:
await at_user.finish(res.text())

@ -0,0 +1,7 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"
teacher_table = "teacher"
class_table = "class_table"

@ -0,0 +1,205 @@
from nonebot import require
from nonebot.adapters.cqhttp import Message, MessageSegment
from .config import Config
from pandas import Series, DataFrame
config = Config()
mysql = require("botdb").MySQLdbMethods()
class Query:
def __init__(self, param: list, group_id: int):
self.group_id = group_id
self.param = param
self.index = []
self.user_name = []
self.uid = []
def _split(self):
for i in self.param: # type: str
if i.isdigit():
if len(i) < 4:
self.index.append(int(i))
else:
self.uid.append(int(i))
else:
self.user_name.append(i)
@staticmethod
def _fmt_in(tag: str, al: list):
if al:
return f"{tag} in({','.join(['%s'] * len(al))})"
def _where(self, is_user: bool = True):
fmt = []
if is_user:
for i in [self._fmt_in("qq", self.uid),
self._fmt_in("学号", self.uid),
self._fmt_in("序号", self.index),
self._fmt_in("姓名", self.user_name)]:
if i:
fmt.append(i)
else:
for i in [self._fmt_in("qq", self.uid),
self._fmt_in("telephone", self.uid),
self._fmt_in("name", self.user_name)]:
if i:
fmt.append(i)
return " or ".join(fmt)
async def __aexit__(self, *args):
...
class QueryUser(Query):
def __init__(self, param: list, group_id: int):
super().__init__(param, group_id)
self.not_user = True
self.is_err = False
self.reply = Message()
self._split()
async def _get_class_name(self):
"""获取班级名称并且查询是否为班级群不是班级群则报错KeyError"""
await mysql.execute(f"select class_group, class_name from {config.class_table}")
df: DataFrame = mysql.form()
class_group = list(df['class_group'])
if self.group_id in class_group:
return "and 班级='%s'" % df["class_name"][class_group.index(self.group_id)]
raise KeyError
async def get_user(self):
"""获取用户,是否有该用户,如果有则对信息进行提取并筛选出需要展示的信息"""
try:
await mysql.execute(f"select * from {config.user_table} where ({self._where()}) {await self._get_class_name()}",
[*(self.uid * 2), *self.index, *self.user_name])
data: DataFrame = mysql.form()
length = data.shape[0] # 提取的用户数量
if length < 1:
self.reply.append("抱歉,未查询到该用户!!!")
self.not_user = True
elif length == 1:
self.user(data.loc[0])
self.not_user = False
else:
self.users(data)
self.not_user = False
except KeyError:
self.is_err = True
self.reply = "此群并非班级群,无法使用查询!!!"
async def get_teacher(self):
"""获取教师"""
await mysql.execute(f"select * from {config.teacher_table} where ({self._where(False)})",
[*(self.uid * 2), *self.user_name])
data: DataFrame = mysql.form()
length = data.shape[0]
teachers = []
if length:
teachers.append("查询到为教师:")
for i in range(length):
teacher = data.loc[i]
teachers.append(f"姓名:{teacher['name']}\n"
f"QQ{teacher['qq']}\n"
f"联系方式:{teacher['telephone']}")
self.reply = Message('\n-----\n'.join(teachers))
@staticmethod
def user_key(user: Series, auto_end: str = "\n"):
"""从用户key中查看是否存在值如果存在则返回该数据加key"""
def _(key, end: str = auto_end):
"""
:param key: 数据的key
:param end: 在结尾追加字符
"""
value = user.get(key)
if value != "None" and value != "":
return f"{key}{value}{end}"
return ""
return _
def user(self, user: Series):
info = self.user_key(user)
birthday, dorm = [""] * 2
if user.get("出生日期"):
birthday = user["出生日期"].date()
if user.get("寝室长"):
if user['寝室长'] == '':
dorm = f"{user['寝室']}(寝室长)"
else:
dorm = user["寝室"]
self.reply.append(f"{info('序号')}"
f"{info('姓名')}"
f"{info('性别')}"
f"{info('班级')}"
f"{info('社团')}"
f"{info('团学')}"
f"{info('团员')}"
f"{info('入党积极份子')}"
f"{info('民族')}"
f"寝室:{dorm}\n"
f"出生日期:{birthday}\n"
f"{info('学号')}"
f"{info('微信')}"
f"{info('联系方式')}"
f"{info('邮箱')}"
f"{info('分数', '')}")
def users(self, users: DataFrame):
for i in range(users.shape[0]):
user = users.loc[i]
self.user(user)
if i < users.shape[0] - 1:
self.reply.append("\n-----\n")
def text(self):
return self.reply
async def __aenter__(self):
"""
查找用户查看是否有用户并且没有出现报错如果没有报错并且没有用户则进行查找是否为教师
"""
await self.get_user()
if not self.is_err and self.not_user:
await self.get_teacher()
return self
class AtUser(Query):
def __init__(self, param: list, group_id: int):
super().__init__(param, group_id)
self.not_user = True
self.is_err = False
self.users = []
self.reply = Message()
self._split()
async def _get_class_name(self):
"""从数据库中检索出班级,提取班级名称,班级群号是唯一的"""
await mysql.execute(f"select class_name from {config.class_table} where class_group=%s",
[self.group_id])
self.class_name = mysql.form()["class_name"][0]
async def get_user(self):
await mysql.execute(f"select qq from {config.user_table} where ({self._where()}) and 班级=%s",
[*(self.uid * 2), *self.index, *self.user_name, self.class_name])
self.users = mysql.form()["qq"]
def text(self):
for i in self.users:
self.reply += MessageSegment.at(i)
return self.reply
async def __aenter__(self):
try:
await self._get_class_name()
await self.get_user()
except KeyError:
self.is_err = True
return self

@ -0,0 +1,23 @@
from pprint import pprint
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
from .data_source import UserName
reset_name = on_command("重命名", aliases={"班级重命名", "学生重命名", "自动改备注"})
IS_USER = require("rule").IS_USER
def get_group_user_id(users: list):
return [int(user["user_id"]) for user in users]
@reset_name.handle()
async def reset_name_handle(bot: Bot, event: GroupMessageEvent, state: dict):
if await IS_USER(bot, event, state):
group_user = get_group_user_id(await bot.get_group_member_list(group_id=event.group_id))
async with UserName(group_user) as res:
await reset_name.send(res.load_text())
await res.reset_name(event.group_id, bot.set_group_card)
await reset_name.finish(res.text())

@ -0,0 +1,6 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"

@ -0,0 +1,41 @@
from nonebot import require
from nonebot.adapters.cqhttp import Message
from asyncio import sleep
from pandas import DataFrame
from .config import Config
mysql = require("botdb").MySQLdbMethods()
config = Config()
class UserName:
def __init__(self, users: list):
self.users = users
self.reply = ""
self.sleep = 5
async def select_user(self):
await mysql.execute(f"select * from {config.user_table} where qq in({','.join(['%s'] * len(self.users))})",
self.users)
return mysql.form()
async def __aenter__(self):
self.all_user: DataFrame = await self.select_user()
return self
def load_text(self):
return Message(f"请稍等,预计需要{self.all_user.shape[0] * self.sleep}秒完成!")
async def reset_name(self, group_id: int, func):
for index, user in self.all_user.iterrows():
await sleep(self.sleep)
# print(f"{user['姓名']} {user['寝室']} {user['联系方式']}")
await func(group_id=group_id, user_id=user["QQ"], card=f"{user['姓名']} {user['寝室']} {user['联系方式']}")
self.reply = "重命名成功"
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
...

@ -0,0 +1,11 @@
# 增加或减少数据
- 增加
- 院系
- 教师
- 专业
- 班级
## 教师权限
可以

@ -0,0 +1,208 @@
import re
from nonebot import on_command, require
from nonebot.rule import T_State
from .data_source import ADD, DEL, Faculty, Expertise, Teacher, Class
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent, Message
permission = require("permission")
rule = require("rule")
TEACHER = permission.TEACHER
MASTER = rule.MASTER
def teacher(matcher):
@matcher.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
# 超级用户与教师才可执行
if await TEACHER(Bot, event):
text = get_text(event.message)
if text:
state["param"] = text
else:
await matcher.finish()
def master(matcher):
@matcher.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
# 超级用户与教师才可执行
if await MASTER(Bot, event, state):
state["param"] = get_text(event.message) or str(event.group_id)
else:
await matcher.finish()
def get_text(message: Message):
"""获取文本"""
for msg in message:
if msg.type == "text":
return msg.data.get("text").strip()
# -------------------- 添加数据 --------------------
add_faculty = on_command("添加院系", aliases={"新增院系", "添加学院", "新增学院"})
add_expertise = on_command("添加专业", aliases={"新增专业"})
add_teacher = on_command("添加教师", aliases={"新增教师"})
add_class = on_command("添加班级", aliases={"新增班级"})
teacher(add_faculty)
teacher(add_expertise)
teacher(add_teacher)
teacher(add_class)
# -------------------- 删除数据 --------------------
del_faculty = on_command("删除院系", aliases={"移除院系", "剔除院系"})
del_expertise = on_command("删除专业", aliases={"移除专业", "剔除专业"})
del_teacher = on_command("删除教师", aliases={"移除教师", "剔除教师"})
del_class = on_command("删除班级", aliases={"移除班级", "剔除班级"})
teacher(del_faculty)
teacher(del_expertise)
teacher(del_teacher)
master(del_class)
# -------------------- 院系 --------------------
@add_faculty.got("param", prompt="您需要”添加“的院系名称(例如:回复信息工程, 电气工程等等...")
async def add_faculty_got(bot: Bot, event: MessageEvent):
text = get_text(event.message)
if text:
async with Faculty(ADD, text, event.user_id) as res:
await add_faculty.finish(res.text())
# 删除
@del_faculty.got("param", prompt="您需要”删除“的院系名称(例如:回复信息工程, 电气工程等等...")
async def add_faculty_got(bot: Bot, event: MessageEvent):
text = get_text(event.message)
if text:
async with Faculty(DEL, text, event.user_id) as res:
await del_faculty.finish(res.text())
# -------------------- 专业 --------------------
@add_expertise.got("param", prompt="您需要”添加“的专业名称(例如:回复人工智能, 服装设计等等...")
async def add_expertise_got(bot: Bot, event: MessageEvent, state: T_State):
state["param"] = get_text(event.message)
if state["param"]:
await add_expertise.send("请输入这个专业所在的院系。")
else:
await add_expertise.finish(Message("请认真填写内容!!!"))
@add_expertise.got("faculty")
async def expertise_in_faculty(bot: Bot, event: MessageEvent, state: T_State):
"""
param 为专业名称
faculty 为该专业所在院系
"""
state["faculty"] = get_text(event.message)
if state["param"] and state["faculty"]:
async with Expertise(ADD, state["param"], state["faculty"]) as res:
await add_expertise.finish(res.text())
else:
await add_expertise.finish(Message("请认真填写内容!!!"))
# 删除
@del_expertise.got("param", prompt="您需要”删除“的专业名称(例如:回复人工智能, 服装设计等等...")
async def del_expertise_got(bot: Bot, event: MessageEvent, state: T_State):
if state["param"]:
async with Expertise(DEL, state["param"]) as res:
await del_expertise.finish(res.text())
# -------------------- 教师 --------------------
@add_teacher.got("param", prompt="请输入您需要添加的教师的姓名")
async def add_teacher_got(bot: Bot, event: MessageEvent, state: T_State):
state["param"] = get_text(event.message)
if state["param"]:
await add_teacher.send("请输入这位教师的QQ")
else:
await add_teacher.finish(Message("请认真填写内容!!!"))
@add_teacher.got("qq")
async def add_teacher_qq_got(bot: Bot, event: MessageEvent, state: T_State):
state["qq"] = get_text(event.message)
if state["qq"]:
await add_teacher.send("请输入这位教师的手机号")
else:
await add_teacher.finish(Message("请认真填写内容!!!"))
@add_teacher.got("tel")
async def add_teacher_tel_got(bot: Bot, event: MessageEvent, state: T_State):
state["tel"] = get_text(event.message)
if state["tel"]:
async with Teacher(ADD, state["param"], state["qq"], state["tel"], event.user_id) as res:
await add_teacher.finish(res.text())
else:
await add_teacher.finish(Message("请认真填写内容!!!"))
# 删除
@del_teacher.got("param", prompt="请输入这位教师的QQ号或姓名")
async def del_teacher_got(bot: Bot, event: MessageEvent, state: T_State):
if state["param"]:
is_name = "name"
if state["param"].isdigit():
is_name = "qq"
async with Teacher(DEL, **{is_name: state["param"]}) as res:
await del_teacher.finish(res.text())
else:
await add_teacher.finish(Message("请认真填写内容!!!"))
# -------------------- 班级 ---------------------
@add_class.got("param", prompt="请输入班级名称例如人工智能2101等等...)如果您在此群使用该命令,此群将会变为班级群,并且您会被认定为此群班主任,请认真确定!!!")
async def add_class_got(bot: Bot, event: GroupMessageEvent, state: T_State):
class_name = state["param"]
if class_name:
split = re.findall(r"\S[^\d]+|\d+", class_name)[:2]
if len(class_name) > 1:
expertise, class_id = split
if expertise.isdigit():
expertise, class_id = class_id, class_name
# 参数较多就这样写了
async with Class(ADD,
class_name=class_name,
class_id=class_id,
class_group=event.group_id,
class_teacher=event.user_id,
expertise=expertise) as res:
await add_class.finish(res.text())
else:
await add_class.finish("您缺少重要参数,您输入的班级必须包含班级名称+班级号例如人工智能2101等等...")
else:
await add_teacher.finish(Message("请认真填写内容!!!"))
# 删除
@del_class.got("param", prompt="请输入班级名称或群号")
async def del_class_got(bot: Bot, event: GroupMessageEvent, state: T_State):
is_admin = True
state["del_type"] = "class_group"
if state["param"]:
if state["param"].isdigit():
state["class_group"] = int(state["param"])
is_admin = state["class_group"] in state["all_group"]["class_group"]
else:
state["del_type"] = "class_name"
state["class_group"] = state["param"]
is_admin = state["class_group"] in state["all_group"]["class_name"]
else:
state["class_group"] = event.group_id
if not is_admin:
await del_class.finish(Message("您并不是班主任,无权操作此群!!!"))
await del_class.send(Message(f"您确认要将“{state['class_group']}”从班级管理系统中永久删除吗?(是/否)"))
@del_class.got("is_del")
async def del_class_is_got(bot: Bot, event: GroupMessageEvent, state: T_State):
if "" in state["is_del"]:
async with Class(DEL, **{state["del_type"]: state["class_group"]}) as res:
await del_class.finish(res.text())
else:
await del_class.finish("已为您取消。")

@ -0,0 +1,8 @@
from pydantic import BaseSettings
class Config(BaseSettings):
faculty = "faculty_table"
expertise = "expertise_table"
teacher = "teacher"
class_table = "class_table"

@ -0,0 +1,209 @@
from nonebot import require
from .config import Config
from nonebot.adapters.cqhttp import Message
from aiomysql import IntegrityError
ADD = "add"
DEL = "del"
MySQLdbMethods = require("botdb").MySQLdbMethods
mysql = MySQLdbMethods()
config = Config()
# 学院院系
class Faculty:
def __init__(self,
typeof: str,
faculty: str,
invitee: int
):
"""
:param faculty: 需要添加的院系
:param invitee: 添加人的QQ
"""
self.typeof = typeof
self.faculty = faculty # 院系
self.invitee = invitee # 添加人
self.is_err = False
self.reply_text = None
self.table_name = config.faculty
async def __aenter__(self):
try:
if self.typeof == ADD:
# ----- 添加数据 -----
await mysql.execute_commit(
f"insert into {self.table_name} value (%s,%s)", param=[
self.faculty, self.invitee
])
# ----- 添加数据回复 -----
self.reply_text = f"{self.faculty}“添加成功。"
elif self.typeof == DEL:
# ----- 删除数据回复 -----
await mysql.execute_commit(f"delete from {self.table_name} where faculty=%s", param=[
self.faculty])
# ----- 删除成功回复 -----
self.reply_text = f"{self.faculty}”已删除。"
except IntegrityError:
if self.typeof == DEL:
# ----- 删除失败回复 -----
self.reply_text = f"{self.faculty}”删除失败,可能”{self.faculty}“下还所在专业未删除!!!"
elif self.typeof == ADD:
# ----- 添加失败回复 -----
self.reply_text = f"{self.faculty}“已经添加,无需重复执行!!!"
self.is_err = True
return self
def text(self) -> Message:
return Message(self.reply_text)
async def __aexit__(self, *args):
...
# 院系的专业
class Expertise:
def __init__(self,
typeof,
expertise: str,
faculty: str = None
):
self.typeof = typeof
self.faculty = faculty # 院系
self.expertise = expertise # 专业
self.is_err = False
self.reply_text = None
self.table_name = config.expertise
async def __aenter__(self):
try:
if self.typeof == ADD:
# ----- 添加数据 -----
await mysql.execute_commit(
f"insert into {self.table_name} value (%s,%s)", param=[
self.faculty, self.expertise
])
# ----- 添加成功回复 -----
self.reply_text = f"{self.expertise}”添加成功,所在院系”{self.faculty}“。"
elif self.typeof == DEL:
# ----- 删除数据 -----
await mysql.execute_commit(f"delete from {self.table_name} where expertise=%s", param=[
self.expertise])
# ----- 删除成功回复 -----
self.reply_text = f"{self.expertise}”已删除。"
except IntegrityError:
if self.typeof == ADD:
# ----- 添加失败回复 -----
self.reply_text = f"{self.expertise}“添加失败,可能是”{self.faculty}“不存在,或者是”{self.expertise}“已经添加过!!!"
self.is_err = True
return self
def text(self) -> Message:
return Message(self.reply_text)
async def __aexit__(self, *args):
...
class Teacher:
def __init__(self,
typeof: str,
name: str = None,
qq: int = None,
tel: int = None,
invitee: int = None
):
self.qq = qq # 教师QQ
self.tel = tel # 教师电话
self.name = name # 教师姓名
self.invitee = str(invitee) # 添加人
self.typeof = typeof
self.is_err = False
self.reply_text = None
self.table_name = config.teacher
async def __aenter__(self):
try:
if self.typeof == ADD:
# ----- 添加教师信息 -----
await mysql.execute_commit(f"insert into {self.table_name} values (%s,%s,%s,%s)", param=[
self.name, self.qq, self.invitee, self.tel
])
# ----- 添加成功回复 -----
self.reply_text = f"{self.name}“添加成功。"
elif self.typeof == DEL:
# # ----- 删除教师信息 -----
if self.name:
sql = f"delete from {self.table_name} where name=%s"
else:
sql = f"delete from {self.table_name} where qq=%s"
await mysql.execute_commit(sql, param=[self.name or self.qq])
# # ----- 删除成功回复 -----
self.reply_text = f"{self.name or self.qq}“已删除。"
except IntegrityError:
if self.typeof == ADD:
# ----- 添加失败回复 -----
self.reply_text = f"{self.name}”添加失败,可能重复添加某条数据,或者缺少某些数据!!!"
elif self.typeof == DEL:
# ----- 删除失败回复 -----
self.reply_text = f"{self.name}”删除失败,可能“{self.name}”下还有班级未删除!!!"
return self
def text(self):
return Message(self.reply_text)
async def __aexit__(self, *args):
...
class Class:
def __init__(self,
typeof: str,
class_name: str = None,
class_id: int = None,
class_group: int = None,
class_teacher: int = None,
expertise: str = None
):
self.typeof = typeof
self.class_name = class_name # 班级全称
self.class_id = class_id # 班级id
self.class_group = class_group # 班级群
self.class_teacher = class_teacher # 班主任
self.expertise = expertise # 专业
self.is_err = False
self.reply_text = None
self.table_name = config.class_table
async def __aenter__(self):
try:
if self.typeof == ADD:
# ----- 添加班级 -----
await mysql.execute_commit(f"insert into {self.table_name} values (%s,%s,%s,%s,%s)", param=[
self.class_id, self.expertise, self.class_group, self.class_name, self.class_teacher
])
# ----- 添加成功回复 -----
self.reply_text = f"{self.class_name}“添加完成,您已成为”{self.class_name}“班主任,可以在此群导入班级表格。"
elif self.typeof == DEL:
# ----- 删除班级 -----
if self.class_name:
sql = f"delete from {self.table_name} where class_name=%s"
else:
sql = f"delete from {self.table_name} where class_group=%s"
await mysql.execute_commit(sql, param=[self.class_name or self.class_group])
# ----- 删除成功回复 -----
self.reply_text = f"{self.class_name or self.class_group}“已删除"
except IntegrityError:
if self.typeof == ADD:
# ----- 添加失败回复 -----
self.reply_text = f"{self.class_name}”添加失败,错误原因可能如下:\n1.教师不在数据库中。\n2.此专业存在。\n3.此班级已创建!!!"
elif self.typeof == DEL:
# ----- 删除失败回复 -----
self.reply_text = f"{self.class_name}”删除失败,请先清空该班级下的学生信息!!!"
return self
def text(self):
return Message(self.reply_text)
async def __aexit__(self, *args):
...

@ -0,0 +1,45 @@
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, MessageEvent
from .data_source import SetScore, config, GiftScore
rule = require("rule")
IS_USER = rule.IS_USER
CLASS_CADRE = rule.CLASS_CADRE
add_score = on_command("加分", aliases={"添加分数", "减分", "扣分", "设置分数"})
gift_score = on_command("转让", aliases={"赠与"})
@add_score.handle()
async def add_score_handler(bot: Bot, event: MessageEvent, state: dict):
if (await CLASS_CADRE(bot, event, state)) and state["class_cadre"]["职位"] in config.score:
text = event.get_plaintext()
if text:
state["param"] = text.split()
else:
await add_score.finish()
@add_score.got("param", prompt="请输入成员名称与分数")
async def add_score_got(bot: Bot, event: MessageEvent, state: dict):
text = event.get_plaintext()
if text:
async with SetScore(state["class_cadre"]["班级"], text) as res:
await add_score.finish(res.text())
@gift_score.handle()
async def gift_score_handle(bot: Bot, event: MessageEvent, state: dict):
if await IS_USER(bot, event, state):
text = event.get_plaintext()
if text:
state["param"] = text
@gift_score.got("param", prompt="请输入需要转让的那一位同学姓名与数量")
async def gift_score_got(bot: Bot, event: MessageEvent, state: dict):
text = event.get_plaintext()
if text:
async with GiftScore(state["user_info"]["班级"], event.user_id, state["user_info"]["分数"], text) as res:
await gift_score.finish(res.text())

@ -0,0 +1,6 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"
score = ["学习委员"]

@ -0,0 +1,71 @@
from nonebot import require
from .config import Config
from nonebot.adapters.cqhttp import Message
import re
config = Config()
mysql = require("botdb").MySQLdbMethods()
class SetScore:
def __init__(self, class_name: str, text: str):
self.class_name = class_name
self.params = [i.split() for i in re.findall(r"\S.*?\d+", text)]
async def set_score(self, score: int, names: list):
await mysql.execute_commit(f"update {config.user_table} set 分数=分数+%s "
f"where 班级=%s and 姓名 in({','.join(['%s'] * len(names))})",
[score, self.class_name, *names])
async def __aenter__(self):
if self.params:
for param in self.params:
await self.set_score(int(param[-1]), param[:-1])
self.reply = "分数增改成功!"
else:
self.reply = "未写入加分名单!!!"
return self
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
...
class GiftScore:
def __init__(self, class_name: str, user_id: int, my_score: int, to_name: str):
self.class_name = class_name
self.my_score = my_score
self.user_id = user_id
self.to_name = to_name
self.reply = ""
async def gift_score(self):
await mysql.execute_commit(f"update {config.user_table} set 分数=分数+%s "
f"where 班级=%s and 姓名=%s", [self.score, self.class_name, self.to_name])
await mysql.execute_commit(f"update {config.user_table} set 分数=分数-%s "
f"where 班级=%s and qq=%s", [self.score, self.class_name, self.user_id])
async def __aenter__(self):
self.to_name = self.to_name.split()
if len(self.to_name) > 1:
self.to_name, self.score = self.to_name[:2]
if self.score.isdigit():
self.score = int(self.score)
if 0 < self.my_score >= self.score:
await self.gift_score()
self.reply = f"成功转让{self.score}{self.to_name}"
else:
self.reply = f"抱歉,您的分数不足{self.score}"
else:
self.reply = "输入有误,您的第二个参数应该是纯数字!!!"
else:
self.reply = "输入有误,确认输入格式:姓名 数量"
return self
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
...

@ -0,0 +1,52 @@
from nonebot import on_command, require
from nonebot.adapters.cqhttp import Bot, MessageEvent
from .data_source import get_url, AddGoods, SelectGoods
add_goods = on_command("更新商品", aliases={"添加商品", "更新列表"})
select_goods = on_command("查看商品", aliases={"商品列表", "兑换列表"})
# purchase = on_command("购买", aliases={"兑换"})
TEACHER = require("permission").TEACHER
IS_USER = require("rule").IS_USER
@add_goods.handle()
async def add_goods_handle(bot: Bot, event: MessageEvent, state: dict):
if await TEACHER(bot, event):
url = get_url(event.message)
if url:
state["url"] = url
else:
await add_goods.finish()
@add_goods.got("url", prompt="请发送在线文档或者链接")
async def add_goods_got(bot: Bot, event: MessageEvent, state: dict):
url = get_url(state["url"])
if url:
async with AddGoods(event.user_id, url) as res:
await add_goods.finish(res.text())
@select_goods.handle()
async def select_goods_handle(bot: Bot, event: MessageEvent, state: dict):
if await IS_USER(bot, event, state):
async with SelectGoods(event.user_id) as res:
await select_goods.finish(res.text())
# @purchase.handle()
# async def purchase_handle(bot: Bot, event: MessageEvent, state: dict):
# if await IS_USER(bot, event, state):
# text = event.get_plaintext()
# if text:
# state["param"] = text
# @purchase.got("param", prompt="请输入序号或名称")
# async def purchase_got(bot: Bot, event: MessageEvent, state: dict):
# if not isinstance(state["param"], list):
# state["param"] = state["param"].split()
# if state["param"]:
# ...

@ -0,0 +1,5 @@
from pydantic import BaseSettings
class Config(BaseSettings):
shop_table = "shop"

@ -0,0 +1,93 @@
from nonebot import require
from nonebot.adapters.cqhttp import Message
from pandas import DataFrame
from .config import Config
MySQLdbMethods = require("botdb").MySQLdbMethods
tools = require("tools")
get_url = tools.get_url
GetDocsSheet = tools.GetDocsSheet
mysql = MySQLdbMethods()
config = Config()
class AddGoods:
"""添加商品"""
def __init__(self, user_id: int, url: str):
self.url = url
self.user_id = user_id
self.reply = ""
async def get_sheet(self) -> DataFrame:
async with GetDocsSheet(self.url) as res:
return res.data
def text(self):
return Message(self.reply)
async def delete_goods(self):
"""删除原本的所有商品"""
await mysql.execute_commit(f"delete from {config.shop_table} where teacher=%s",
[self.user_id])
async def __aenter__(self):
"""
获取后删除商品然后写入新商品
"""
self.data = await self.get_sheet()
await self.delete_goods()
if self.data.shape[0]:
for k, v in self.data.iterrows():
await self.insert(v["奖励"], v["分数"])
self.reply = "商品更新完成。"
else:
self.reply = "商品已全部清空!!!"
return self
async def insert(self, name: str, price: int):
"""写入商品"""
await mysql.execute_commit(f"insert into {config.shop_table} value (%s,%s,%s)",
[self.user_id, name, price])
async def __aexit__(self, *args):
...
class SelectGoods:
"""查询商品"""
def __init__(self, user_id: int):
self.user_id = user_id
self.reply = ""
async def __aenter__(self):
self.data = await self.select_goods()
if self.data.shape[0]:
self.reply = "商品列表:"
for k, v in self.data.iterrows():
self.reply += f"\n{v['shop_name']} | {v['shop_price']}"
else:
self.reply = "抱歉,未发现上架可以兑换商品!!!"
return self
async def select_goods(self) -> DataFrame:
"""
依照用户的id与班级参数查询相关教师在查找教师所上架的商品
"""
await mysql.execute("SELECT shop_name,shop_price FROM class_table,user_info,shop where user_info.qq=%s and "
"user_info.班级=class_table.class_name and class_table.class_teacher=shop.teacher",
self.user_id)
return mysql.form()
def text(self):
return Message(self.reply)
async def __aexit__(self, *args):
...
class Purchase:
def __init__(self, user_id: int, goods: list):
self.user_id = user_id
self.goods = goods
self.goods_index = []
self.goods_name = []

@ -0,0 +1,77 @@
from nonebot import on_command, require
from nonebot.rule import T_State
from .data_source import get_url, GetExcelNotUser, CheckUser
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent
push_user_info = on_command("未填写", aliases={"查表格", "查表", "为填表"})
check_user = on_command("核对信息", aliases={"核验", "检查", "检查信息"})
deep_check_user = on_command("深度检查", aliases={"严格检查", "深度核对"})
super_check_user = on_command("超级核对", aliases={"超级检查"})
IS_USER = require("rule").IS_USER
# 添加班级成员需要按群添加,因为群内唯一群号绑定一个班级名
@push_user_info.handle()
async def _push_user_info_handle(bot: Bot, event: GroupMessageEvent, state: T_State):
if await IS_USER(bot, event, state):
url = get_url(event.message)
if url:
state["url"] = url
else:
await push_user_info.finish()
# 未接受到班级表格时
@push_user_info.got("url", prompt="请发送班级表格")
async def _push_user_info_got(bot: Bot, event: GroupMessageEvent, state: T_State):
url = get_url(state["url"])
if url:
async with GetExcelNotUser(state["user_info"]["班级"], url) as res:
await push_user_info.finish(res.text())
def set_url(matcher):
@matcher.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
if await IS_USER(bot, event, state):
url = get_url(event.message)
if url:
state["url"] = url
else:
await matcher.finish()
# 检查班级信息
set_url(check_user)
set_url(deep_check_user)
set_url(super_check_user)
# 未接受到班级表格时
@check_user.got("url", prompt="请发送班级表格")
async def _push_user_info_got(bot: Bot, event: GroupMessageEvent, state: T_State):
url = get_url(state["url"])
if url:
async with CheckUser(state["user_info"]["班级"], url) as res:
res.select_info()
await check_user.finish(res.text())
# 未接受到班级表格时
@deep_check_user.got("url", prompt="请发送班级表格")
async def _push_user_info_got(bot: Bot, event: GroupMessageEvent, state: T_State):
url = get_url(state["url"])
if url:
async with CheckUser(state["user_info"]["班级"], url) as res:
res.deep_select_info()
await deep_check_user.finish(res.text())
# 未接受到班级表格时
@super_check_user.got("url", prompt="请发送班级表格")
async def _push_user_info_got(bot: Bot, event: GroupMessageEvent, state: T_State):
url = get_url(state["url"])
if url:
async with CheckUser(state["user_info"]["班级"], url) as res:
res.super_select_info()
await super_check_user.finish(res.text())

@ -0,0 +1,5 @@
from pydantic import BaseSettings
class Config(BaseSettings):
user_table = "user_info"

@ -0,0 +1,123 @@
from datetime import datetime, timedelta
from pprint import pprint
from typing import Union
from pandas import DataFrame, Series, merge, concat
from .config import Config
from nonebot import require
from nonebot.adapters.cqhttp import Message
config = Config()
mysql = require("botdb").MySQLdbMethods()
tools = require("tools")
re_docs = tools.re_docs
get_url = tools.get_url
GetDocsSheet = tools.GetDocsSheet
class GetExcelNotUser:
def __init__(self, class_name: str, url: str):
self.url = url
self.class_name = class_name
self.reply = None
async def select_user(self) -> DataFrame:
await mysql.execute(f"select 姓名, QQ from {config.user_table} where 班级=%s",
[self.class_name])
return mysql.form()
async def get_sheet(self) -> DataFrame:
try:
async with GetDocsSheet(self.url) as res:
return res.data
except IndexError:
self.reply = "查询失败,似乎表格没有设置成“所有人可编辑”请检查表格分享设置,或者重新分享一次!!"
async def __aenter__(self):
self.sheet = await self.get_sheet()
self.users = await self.select_user()
return self
async def get_names(self):
names = self.sheet.get("姓名")
if not names:
names = self.sheet.get("学生姓名")
return names
def text(self):
if self.reply:
return Message(self.reply)
user = set(self.users.get("姓名")) - set(self.sheet.get("姓名"))
if user:
reply = ["未填写名单"]
for name in user:
reply.append(name)
reply = ' '.join(reply)
else:
reply = "未发现成员!!!"
return Message(reply)
async def __aexit__(self, *args):
...
class CheckUser(GetExcelNotUser):
async def __aenter__(self):
self.sheet = await self.get_sheet()
self.users = await self.select_user()
self.columns = self.get_column()
self.sheet = self.sheet[self.columns].astype("str")
self.users = self.users[self.columns].astype("str")
self.select_info()
self.reply: DataFrame
return self
def super_select_info(self):
"""
对班级进行超级校对只要与班级信息相关的
"""
mer = merge(self.users, self.sheet, how="inner")
err = concat([mer, self.users], join="inner", axis=0).drop_duplicates(keep=False)
self.reply = err
return err
def deep_select_info(self):
"""
校对班级用户
校对班级用户并且包括不在表格内的
"""
mer = self.get_class()
err = merge(mer, self.users, how="inner")
err = concat([self.users, err], join="inner", axis=0).drop_duplicates(keep=False)
self.reply = err
return err
def get_class(self):
c = self.sheet.get("班级")
if c is not None:
return self.sheet[c == self.class_name]
return self.sheet
def select_info(self):
"""
校对用户
查询检查班级用户并且是表格中有的
"""
mer = self.get_class()
err = merge(mer, self.users, how="inner")
err = concat([mer, err], join="inner", axis=0).drop_duplicates(keep=False)
self.reply = err
return err
def get_column(self) -> list:
return list(set(self.sheet.columns) & set(self.users.columns))
async def select_user(self) -> DataFrame:
await mysql.execute(f"select 姓名,班级,联系方式,身份证号,qq,学号,民族,籍贯 from {config.user_table} where 班级=%s",
[self.class_name])
return mysql.form()
def text(self) -> Message:
text = self.reply
if text.shape[0]:
return Message(str(text))
return Message("未发现错误!")

@ -0,0 +1,15 @@
# 动漫资源获取
动漫资源获取是利用python爬取某网站的资源进行分类获取根据网站给出的选项进行分类选择选项后会去将选项的第一个选择出来对于网站资源的爬取只会去爬取第一页内容
### 命令
```shell
资源 <参数>
```
![file](../../../tests/images/animeres1.png)
FAQ:
资源类型可以用空格分割,以便于灵活的查找资源
![file](../../../tests/images/animeres2.png)

@ -0,0 +1,42 @@
from nonebot.rule import T_State
from nonebot import on_command
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message
from .data_source import GetDMHY
anime_res = on_command("资源", aliases={"动漫资源"})
@anime_res.handle()
async def anime_res_handle(bot: Bot, event: MessageEvent, state: T_State):
msg = event.get_plaintext()
if msg:
state["msg"] = msg
@anime_res.got("msg", prompt="动漫名字叫什么呀!")
async def anime_res_got(bot: Bot, event: MessageEvent, state: T_State):
msg = event.get_plaintext()
if not msg:
await anime_res.finish("...")
async with GetDMHY(msg) as res:
if not res:
await anime_res.finish("没有找到你想要的,看看是不是输错了吧!")
state["res"] = res
types = '\n'.join([f'{i+1}:{v}' for i, v in enumerate(res)])
await anime_res.send(f"你需要什么类型的资源:{types}")
@anime_res.got("index")
async def anime_res_index(bot: Bot, event: MessageEvent, state: T_State):
index = state["index"]
if index.isdigit():
res: GetDMHY = state["res"]
index = int(index)
if 0 < index <= res.length:
anime = res[res.tags[index-1]][0]
await anime_res.finish(f"资源名称:{anime.title}\n{anime.href}")
else:
await anime_res.finish("有这个序号吗?")
else:
await anime_res.finish("您输入的是什么呀,让你输入数字呢!")

@ -0,0 +1,60 @@
from typing import Any, List, Tuple
from aiohttp import ClientSession, ClientTimeout
from feedparser import parse
from pydantic import BaseSettings
class AnimeRes(BaseSettings):
def __init__(self, value: dict, **values: Any):
super().__init__(**values)
self.title = value['title']
try:
self.href = value["links"][1]['href'].split("&")[0]
except KeyError:
self.href = None
self.tags = [i["term"] for i in value["tags"]]
self.tag = value["tags"][0]['term']
class Config:
extra = "allow"
class GetDMHY:
main_url = "https://dmhy.anoneko.com/topics/rss/rss.xml"
def __init__(self, keyword: str):
self.params = {"keyword": keyword}
def classification(self) -> Tuple[dict, list]:
values, tags = {}, []
for i in self.resources:
if i.href:
if values.get(i.tag):
values[i.tag].append(i)
else:
values[i.tag] = [i]
tags.append(i.tag)
return values, tags
def __getitem__(self, item: str) -> List[AnimeRes]:
return self.values.get(item)
def __iter__(self):
return iter(self.tags)
async def get(self) -> List[AnimeRes]:
async with self.session.get(self.main_url, params=self.params) as res:
return [AnimeRes(i) for i in parse(await res.text()).get("entries")]
def __bool__(self):
return bool(self.tags)
async def __aenter__(self):
self.session = ClientSession(timeout=ClientTimeout(600))
self.resources = await self.get()
self.values, self.tags = self.classification()
self.length = len(self.tags)
return self
async def __aexit__(self, *args):
await self.session.close()

@ -0,0 +1,52 @@
from nonebot import on_command, on_message
from nonebot.permission import SUPERUSER
from nonebot.rule import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from .config import Config
from .data_source import AddChatWord, get_url, BotChat
# export_chat = on_command("导入词库", aliases={"添加词库", "更新词库"})
on_chat = on_message(priority=100)
chat_ends = ["再见", "不聊了", "结束对话", "拜拜", "bye"]
# @export_chat.handle()
# async def export_chat_handle(bot: Bot, event: MessageEvent, state: T_State):
# if await SUPERUSER(bot, event):
# url = get_url(event.message)
# if url:
# state["url"] = url
# else:
# await export_chat.finish()
#
#
# @export_chat.got("url", prompt="请发送在线文档链接或卡片")
# async def export_chat_got(bot: Bot, event: MessageEvent, state: T_State):
# url = get_url(state["url"])
# if url:
# async with AddChatWord(url) as res:
# await export_chat.finish(res.text())
# else:
# await export_chat.finish("添加失败!!!")
@on_chat.handle()
async def on_chat_handle(bot: Bot, event: MessageEvent, state: T_State):
if event.to_me:
state["start"] = "开始聊天"
else:
await on_chat.finish()
@on_chat.got("start")
async def on_chat_got(bot: Bot, event: MessageEvent, state: T_State):
text = event.get_plaintext()
for txt in chat_ends:
if txt in text:
await on_chat.finish("拜拜!")
async with BotChat(text) as res:
if isinstance(event, GroupMessageEvent):
await on_chat.reject(await res.text())
await on_chat.finish(await res.text())

@ -0,0 +1,8 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# Your Config Here
class Config:
extra = "ignore"

@ -0,0 +1,132 @@
from aiohttp import ClientSession
from nonebot import require
from pandas import DataFrame
from nonebot.adapters.cqhttp import Message
from json import dumps, loads
from random import choice
from typing import List
tools = require("tools")
readfile = require("readfile").ReadFile("data", "chatword")
base_url = "http://i.itpk.cn/api.php"
def get_params(msg: str):
return {
"question": msg,
"limit": 8,
"api_key": "1447d08d8e018247e2ce829e9d0380e8",
"api_secret": "p782g4wij4b0"
}
GetDocsSheet = tools.GetDocsSheet
get_url = tools.get_url
bot_chat: dict = {
"words": []
}
def chat_sorted(words: dict) -> List:
return [list(i) for i in sorted(words.items(), key=lambda x: -len(x[0]))]
class AddChatWord:
def __init__(self, url: str):
self.url = url
self.reply = ""
async def get_sheet(self):
"""获取腾讯文档"""
async with GetDocsSheet(self.url) as res:
return res
async def __aenter__(self):
"""
从腾讯文档中获取数据
取出转为dafaframe的数据
提取词库
保存到本地
"""
self.sheet = await self.get_sheet()
self.data: dict = self.sheet.data
self.df: DataFrame = self.sheet.data
self.words = self.chat_word()
await self.save()
self.reply = "词库更新成功。"
return self
def chat_word(self):
"""
提取词库整理成字典
再将字典排序转为列表
"""
words = {word["关键字"]: [txt for txt in word['随机回复词1': "随机回复词20"] if txt] for i, word in self.df.iterrows()}
return [list(i) for i in sorted(words.items(), key=lambda x: -len(x[0]))]
def text(self):
return Message(self.reply)
async def save(self):
"""
将数据保存到本地
"""
bot_chat["words"] = self.words
await readfile.write("chat_word.json", dumps(self.words))
async def __aexit__(self, *args):
...
class BotChat:
def __init__(self, word: str):
self.word = word
@staticmethod
async def data_in_msg(msg: str):
data = await readfile.read("data.json")
for i in data:
if i in msg:
return choice(data[i])
@staticmethod
async def get_message_reply(msg: str):
async with ClientSession() as session:
async with session.get(url=base_url, params=get_params(msg)) as resp:
try:
text = loads((await resp.text()).encode("utf-8"))
try:
return text["content"]
except KeyError:
return "抱歉,为对数据进行整理目前无法使用"
except ValueError:
return await resp.text()
@staticmethod
async def get_words():
"""
从全局变量中查看是否存在词库
如果不存在则从本地取出
:return:
"""
words = bot_chat["words"]
if not words:
words = loads(await readfile.read("data.json"))
bot_chat["words"] = words
return words
async def text(self):
for i in [
self.data_in_msg,
self.get_message_reply
]:
msg = await i(self.word)
if msg:
return msg
async def __aenter__(self):
# self.words = await self.get_words()
return self
async def __aexit__(self, *args):
...

@ -0,0 +1,41 @@
from nonebot import get_driver, on_command
from aiohttp import ClientSession
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message
from nonebot.rule import T_State
from .config import Config
covid = on_command("疫情", aliases={"疫情查询"})
get_covid = []
@covid.handle()
async def covid_handler(bot: Bot, event: MessageEvent, state: T_State):
text = event.get_plaintext()
if text and event.user_id not in get_covid:
state["region"] = text
get_covid.append(event.user_id)
else:
await covid.finish("请勿频繁获取!!!")
@covid.got("region", prompt="请发送地区")
async def covid_got(bot: Bot, event: MessageEvent, state: T_State):
text = state["region"]
await covid.send(Message("正在获取中..."))
try:
async with ClientSession() as session:
async with session.get("https://api.iyk0.com/yq/", params={"msg": text}) as res:
data = await res.json()
if data["code"] == 200:
await covid.send(Message(f'{data["msg"]}\n'
f'查询地区:{data["查询地区"]}\n'
f'目前确诊:{data["目前确诊"]}\n'
f'死亡人数:{data["死亡人数"]}\n'
f'治愈人数:{data["治愈人数"]}\n'
f'新增确诊:{data["新增确诊"]}\n'
f'现存无症状:{data["现存无症状"]}\n'
f'更新时间:{data["time"]}'))
elif data["code"] == 202:
await covid.send(Message(data["msg"]))
finally:
get_covid.remove(event.user_id)

@ -0,0 +1,8 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# Your Config Here
class Config:
extra = "ignore"

@ -0,0 +1,51 @@
from nonebot import get_driver, on_command, on_notice
from aiohttp import ClientSession
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message, PokeNotifyEvent, MessageSegment
from nonebot.rule import T_State
from .config import Config
encyclopedia = on_command("百科", aliases={"百科搜索"})
echo = on_command("echo")
get_covid = []
poke = on_notice()
@echo.handle()
async def echo_handler(bot: Bot, event: MessageEvent):
await echo.finish(event.message)
@poke.handle()
async def poke_handle(bot: Bot, event: PokeNotifyEvent, state: T_State):
if event.is_tome():
await poke.finish("不,不要戳我>_<!!")
else:
...
@encyclopedia.handle()
async def covid_handler(bot: Bot, event: MessageEvent, state: T_State):
text = event.get_plaintext()
if text and event.user_id not in get_covid:
state["region"] = text
get_covid.append(event.user_id)
else:
await encyclopedia.finish("请勿频繁获取!!!")
@encyclopedia.got("region", prompt="请发送搜索内容")
async def covid_got(bot: Bot, event: MessageEvent, state: T_State):
text = state["region"]
await encyclopedia.send(Message("正在获取中..."))
try:
async with ClientSession() as session:
async with session.get("https://api.iyk0.com/sgbk/", params={"msg": text}) as res:
data = await res.json()
if data["code"] == 200:
await encyclopedia.send(Message(f"内容:{data['data']}\n---\n"
f"类型:{data['type']}\n---\n"
f"更多:{data['more']}"))
elif data["code"] == 202:
await encyclopedia.send(Message(data["msg"]))
finally:
get_covid.remove(event.user_id)

@ -0,0 +1,8 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# Your Config Here
class Config:
extra = "ignore"

@ -0,0 +1,16 @@
# 获取BiliBili用户信息与视频信息插件
#### 回复消息发送一下几类消息
- b站xml卡片
- 视频卡片
- up主卡片
- 聊天消息中包含的关键字
- av号
- bv号
- 视频链接
- up主链接
- up主名字需要使用命令
- up
- UP
- mid

@ -0,0 +1,72 @@
import re
from pprint import pprint
from .data_source import GetVideoInfo, GetUserInfo, get_link_redirection
from nonebot.rule import Rule, T_State
from nonebot import on_regex, on_command, on_message
from nonebot.adapters.cqhttp import Bot, MessageEvent, Event
bv_av = r"[ab]v\w+"
""" ====== rule ====== """
def is_bili_id() -> Rule:
"""
读取消息是否包含有av或bv号或者存在bili的xml卡片
判断是否是消息事件循环判断消息内是否存在xml如果不是xml将作为纯文本读取
:return: Rule
"""
async def _bili_xml(bot: Bot, event: Event, state: T_State) -> bool:
if event.get_type() == "message":
event: MessageEvent
for msg in event.message:
if msg.type == "xml" or msg.type == "json":
pprint(msg.data["data"][0])
text = re.search(r"https://b23.tv/\w+", str(msg.data["data"]).replace("\\", ""), re.I)
if text:
state["xml_url"] = await get_link_redirection(text.group())
return True
else:
text = re.search(bv_av, event.get_plaintext(), flags=re.I)
if text:
state["xml_url"] = text.group()
return True
return False
return Rule(_bili_xml)
""" ====== matcher ====== """
user_info = on_command("mid", aliases={"UP", "up", "https://space.bilibili.com/"})
video_msg = on_message(rule=is_bili_id())
@video_msg.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
xml_url = state.get("xml_url")
text = re.search(bv_av, xml_url, flags=re.I)
if text:
async with GetVideoInfo(text.group()) as res:
await video_msg.finish(res.text())
elif re.search(r"space.bilibili.com/\d+", xml_url):
# 处理用户的xml卡片
async with GetUserInfo(re.search(r"\d+", xml_url).group()) as res:
await user_info.finish(res.text())
@user_info.handle()
async def _(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
if text:
async with GetUserInfo(text) as res:
await user_info.finish(res.text())

@ -0,0 +1,16 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# 视频信息地址
video_info_url = "https://api.bilibili.com/x/web-interface/view"
# 用户信息地址
user_info_url = "http://api.bilibili.com/x/space/acc/info"
# 视频链接
video_url = "https://www.bilibili.com/video/"
# 用户链接
user_url = "https://space.bilibili.com/"
# 用户名搜索
user_name_search_url = "https://api.bilibili.com/x/web-interface/search/type"
# bili直播地址
bili_live_url = "https://live.bilibili.com/"

@ -0,0 +1,162 @@
from aiohttp import ClientSession
from nonebot.adapters.cqhttp import MessageSegment, Message
from .config import Config
from typing import Optional, Union
from asyncio import get_event_loop
from pprint import pprint
import re
BV = "bvid"
AV = "aid"
urls = Config()
async def get_link_redirection(url):
async with ClientSession() as session:
async with session.get(url) as reply:
return str(reply.url)
class MessageBar(Message):
def __getattr__(self, item):
return lambda *args, **kwargs: self.append(getattr(MessageSegment, item)(*args, **kwargs))
""" ====== 获取视频信息 ====== """
class GetVideoInfo:
def __init__(self, vid):
self.vid: str = vid
self.raw_data: Optional[dict] = None
self.data: Optional[dict] = None
self.code: Optional[int] = None
self.url = urls.video_url
def params(self) -> dict:
"""
将id号进行分析是bv还是av号然后将号转为各自的参数字典
:return: dict
"""
if re.match(r"av", self.vid, re.I):
return {AV: re.sub("av", "", self.vid, flags=re.I)}
elif re.match(r"bv", self.vid, re.I):
return {BV: self.vid}
return {}
def text(self) -> Union[Message, None]:
"""将json数据转为可读性高的Message实列"""
if self.code == 0 and self.data:
stat = self.data['stat']
reply = (MessageBar()
.text(f"{self.data['title']}\n"
f"{'-' * 16}\n"
f"作者(up): {self.data['owner']['name']}\n"
f"作者id: {self.data['owner']['mid']}\n"
f"{'-' * 16}\n"
f"aid: av{self.data['aid']}\n"
f"bvid: {self.data['bvid']}\n"
f"cid: {self.data['cid']}\n")
.image(self.data["pic"])
.text(f"\n播放量: {stat['view']} | 投币数: {stat['coin']} | 点赞数: {stat['like']}"
f"\n弹幕数: {stat['danmaku']} | 收藏数: {stat['favorite']} | 评论数: {stat['reply']}"
f"\n{'-' * 16}"
f"\n{self.data['desc']}\n"
f"链接: {self.url}"))
return reply
return None
async def get(self):
"""获取数据"""
async with self.session.get(urls.video_info_url, params=self.params()) as res:
data = await res.json()
self.raw_data = data
self.url += self.vid
self.code = data["code"]
self.data = data["data"] if self.code == 0 else self.data
async def __aenter__(self):
self.session = ClientSession()
await self.get()
return self
async def close(self):
await self.session.close()
async def __aexit__(self, *args):
await self.close()
""" ====== 获取用户信息 ====== """
class GetUserInfo:
def __init__(self, username: str):
self.username: str = username
self.url: Optional[str] = None
self.code: Optional[int] = None
self.data: Optional[dict] = None
self.params: Optional[dict] = None
def user_silence(self):
"""查看用户是否被封"""
return "\n账号状态: 被封" if self.data["silence"] else ""
def live_status(self):
"""查看用户是否在直播"""
live_room = self.data["live_room"]
if live_room["liveStatus"]:
return f"\n这位UP正在直播中-直通车: {urls.bili_live_url}{live_room['roomid']}"
return ""
def text(self) -> Message:
return (MessageBar()
.text(self.data["name"])
.text(f"\n性别: {self.data['sex']}"
f"\n生日: {self.data['birthday'] or '不知道'}"
f"\n等级: level{self.data['level']}"
f"\n用户id: {self.username}"
f"{self.user_silence()}")
.image(self.data["face"])
.text(f"\n简介: {self.data['sign']}"
f"\n友情链接: {urls.user_url + self.username}"
f"{self.live_status()}"))
async def get_user_info(self):
"""
获取用户信息
判断数据是用户id还是用户名称如果不是用户id则搜索用户并获取用户列表中的第一位作
获取到第一位用户的id后利用id再次发送请求获取改用户的详细信息
"""
if self.username.isdigit():
self.url = urls.user_info_url
self.params = {"mid": self.username}
await self.get()
else:
self.url = urls.user_name_search_url
self.params = {
"search_type": "bili_user",
"keyword": self.username,
"changing": "mid",
"page": 1,
}
await self.get()
self.username = str(self.data["result"][0]["mid"])
await self.get_user_info()
async def get(self):
async with self.session.get(self.url, params=self.params) as reply:
info = await reply.json()
self.code = info["code"]
self.data = info["data"] if self.code == 0 else self.data
async def __aenter__(self):
self.session = ClientSession()
await self.get_user_info()
return self
async def close(self):
await self.session.close()
async def __aexit__(self, *args):
await self.close()

@ -0,0 +1,14 @@
from nonebot import on_command
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, MessageSegment
from nonebot.permission import SUPERUSER
from .data_source import Peeping
from json import dumps
peeping = on_command("窥屏", permission=SUPERUSER, block=False, priority=1)
@peeping.handle()
async def peeping_handle(bot: Bot, event: GroupMessageEvent):
async with Peeping() as res:
await peeping.send(MessageSegment.xml(res.get_xml()))
await peeping.finish(await res.get_send_msg())

@ -0,0 +1,35 @@
{
"app": "com.tencent.structmsg",
"config": {
"autosize": true,
"ctime": 1636171890,
"forward": true,
"token": "7264596b2c6ae2063f045293b78db1dc",
"type": "normal"
},
"desc": "新闻",
"extra": {
"app_type": 1,
"appid": 100951776,
"msg_seq": 7027304746066715522,
"uin": 2711402357
},
"meta": {
"news": {
"action": "",
"android_pkg_name": "",
"app_type": 1,
"appid": 100951776,
"desc": "你感兴趣的视频都在B站",
"jumpUrl": "https://b23.tv/mGMAnD?share_medium=android&share_source=qq&bbid=XX5AE4A57C36D652CA32EB08BA6E8735D8FDF&ts=1636171884331",
"preview":"https://open.gtimg.cn/open/app_icon/00/95/17/76/100951776_100_m.png?t=1635933215?date=20211106",
"source_icon":"",
"source_url":"",
"tag":"哔哩哔哩",
"title":"MelodyKnit的个人空间"
}
},
"prompt":"[分享]MelodyKnit的个人空间",
"ver":"0.0.0.1",
"view":"news"
}

@ -0,0 +1,93 @@
from pprint import pprint
from aiohttp import ClientSession
from asyncio import sleep
from json import loads
from typing import Union, List
from json import dumps
main_url = "http://melodyknit.club:8000/peeping"
xml = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="12345" action="web" brief="这个视频很nb" sourceMsgId="0" url="https://b23.tv/T4eA6C" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="{url}" w="0" h="0" /><title>我爱死这个视频了</title><summary>这简直是视觉盛宴!!!</summary></item><source name="" icon="" action="" appid="0" /></msg>"""
class Peeping:
card = {
"app": "com.tencent.structmsg",
"config": {
"autosize": True,
"ctime": 1636171890,
"forward": True,
"token": "7264596b2c6ae2063f045293b78db1dc",
"type": "normal"
},
"desc": "新闻",
"extra": {
"app_type": 1,
"appid": 100951776,
"msg_seq": 7027304746066715522,
"uin": 2711402357
},
"meta": {
"news": {
"action": "",
"android_pkg_name": "",
"app_type": 1,
"appid": 100951776,
"desc": "你感兴趣的视频都在B站",
"jumpUrl": "https://b23.tv/mGMAnD?share_medium=android&share_source=qq&bbid=XX5AE4A57C36D652CA32EB08BA6E8735D8FDF&ts=1636171884331",
"preview": "https://open.gtimg.cn/open/app_icon/00/95/17/76/100951776_100_m.png?t=1635933215?date=20211106",
"source_icon": "",
"source_url": "",
"tag": "哔哩哔哩",
"title": "MelodyKnit的个人空间"
}
},
"prompt": "[分享]MelodyKnit的个人空间",
"ver": "0.0.0.1",
"view": "news"
}
def __init__(self, interval: int = 5):
self._xml = xml
self._session = ClientSession()
self.uid = None
self.interval = interval
self.img_url = f"{main_url}/image?uid="
async def _get(self, url, params=None) -> Union[dict, list]:
async with self._session.get(url, params=params) as res:
return loads(await res.read())
async def __aenter__(self):
self.uid = (await self._get(main_url))["uid"]
self.img_url += str(self.uid)
return self
def get_xml(self) -> str:
return self._xml.format(url=self.img_url)
def get_json(self):
# self.card["meta"]["news"]["source_icon"] = self.img_url
return dumps(self.card)
@staticmethod
def _msg(info) -> str:
return (f"时间:{info['time']}\n"
f"IP{info['host']}\n"
f"所在地:{info['referer']}\n"
f"设备:{info['ua']}")
async def get_data(self) -> List[dict]:
await sleep(self.interval - 1)
return await self._get(main_url, params={"uid": self.uid})
async def get_send_msg(self) -> str:
data = await self.get_data()
if data:
return "\n---\n".join([self._msg(info) for info in data])
else:
return "未发现"
async def __aexit__(self, *ags):
await self._session.close()

@ -0,0 +1,13 @@
from nonebot import on_command
from random import choice
from nonebot.adapters.cqhttp import Bot, MessageEvent, Message
on_random = on_command("随机")
@on_random.handle()
async def random_handle(bot: Bot, event: MessageEvent, state: dict):
params = event.get_plaintext().split()
if len(params) > 1:
await on_random.finish(Message(choice(params)))

@ -0,0 +1,62 @@
from nonebot import on_command
from nonebot.adapters.cqhttp import Bot, MessageEvent
from aiohttp import ClientSession, ClientConnectionError
cmd = on_command("bot")
host = None
port = 78
is_run = False
class JetBot:
def __init__(self, command: str = None, typeof="move"):
self.cmd = command
self.typeof = typeof
self.reply = ""
async def __aenter__(self):
self.session = ClientSession()
return self
async def get(self):
global is_run
try:
async with self.session.get(f"http://{host}:{port}/login"):
is_run = True
return f"{host}连接成功!"
except ClientConnectionError:
is_run = False
return f"{host}未发现jetbot连接失败"
async def post(self):
global is_run
try:
async with self.session.post(f"http://{host}:{port}/move", params={
"cmd": self.cmd
}) as res:
if (await res.text()) == "false":
return "无该命令!"
except ClientConnectionError:
is_run = False
return f"{host}未发现jetbot连接失败"
async def __aexit__(self, *args):
await self.session.close()
@cmd.handle()
async def cmd_(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
if text:
async with JetBot(text) as res:
if "login" in text:
global host
host = text.split()[-1]
await cmd.finish(await res.get())
else:
if is_run:
rep = await res.post()
if rep:
await cmd.finish(rep)
else:
await cmd.finish("未与机器人建立连接")

@ -0,0 +1,3 @@
# 天气插件
命令: 天气

@ -0,0 +1,31 @@
from nonebot import require, on_command, get_bot, get_driver
from .data_source import GetCurrentWeather, config, ToDayWeather
from asyncio import sleep
from nonebot.adapters.cqhttp import Bot, MessageEvent
weather = on_command("天气")
scheduler = require("nonebot_plugin_apscheduler").scheduler
@weather.handle()
async def _(bot: Bot, event: MessageEvent):
if not config.loading_weather:
await weather.send("正在获取当前天气,请稍后...")
async with GetCurrentWeather() as res:
await weather.finish(res.text())
else:
await weather.finish("请勿重复请求天气!")
async def today_weather(bot=None):
bot: Bot = get_bot()
async with ToDayWeather() as res:
text = list(res.text())
# for group_id in await res.get_class():
# for msg in text:
# await sleep(1)
# await bot.send_group_msg(group_id=, message=msg)
# get_driver().on_bot_connect(today_weather)
scheduler.add_job(today_weather, 'cron', hour=6, minute=0, id='today_weather')

@ -0,0 +1,23 @@
from pydantic import BaseSettings
class Config(BaseSettings):
loading_weather = False
# 中国天气气象局
class CmaWeatherApi:
url = "https://weather.cma.cn/api/now/57679"
params = {}
# oppo color 天气
class OppoWeatherApi:
url = "https://weather.oppomobile.com/dailyForecastAd/v1/00282430113087"
params = {
"language": "zh-cn",
"unit": "c",
"appVerCode": "8002026",
"appVerName": "8.2.26",
"caller": "WEATHER_APP",
"frontCode": "2.0",
"fromWeatherApp": "true"
}

@ -0,0 +1,158 @@
import re
from asyncio import sleep
from json import loads
from time import time
from aiohttp import ClientSession, ClientConnectionError
from nonebot.adapters.cqhttp import MessageSegment, Message
from nonebot import require
from .config import Config
from typing import Optional
from datetime import datetime
config = Config()
weather_api = Config.OppoWeatherApi
mysql = require("botdb").MySQLdbMethods()
class MessageBar(Message):
def __getattr__(self, item):
return lambda *args, **kwargs: self.append(getattr(MessageSegment, item)(*args, **kwargs))
class GetCurrentWeather:
weather_api = Config.CmaWeatherApi
"""获取当时天气"""
async def close(self):
await self.session.close()
def __init__(self):
config.loading_weather = True
self.data: Optional[dict] = None
async def __aenter__(self):
self.session = ClientSession()
await self.get()
return self
async def get(self):
async with self.session.get(self.weather_api.url, params=self.weather_api.params) as res:
self.data = (await res.json())["data"]
def precipitation(self):
if self.data["now"]["precipitation"] >= 100:
return 0.0
return self.data["now"]["precipitation"]
def text(self):
config.loading_weather = False
location = self.data["location"]
now = self.data["now"]
return (MessageBar().text(
f"{location['name']}当前天气状况\n"
f"温度: {now['temperature']}°C\n"
f"湿度: {now['humidity']}%\n"
f"降雨: {now['precipitation']}mm\n"
f"风向: {now['windDirection']} {now['windScale']}\n"
f"大气压: {now['pressure']}hPa\n"
f"数据更新时间: {self.data['lastUpdate']}"
))
async def __aexit__(self, *args):
await self.session.close()
class ToDayWeather:
url = "https://wis.qq.com/weather/common"
direction = ["北风", "东北风", "东风", "东南风", "南风", "西南风", "西风", "西北风"]
@property
def params(self):
return {
"source": "pc",
"weather_type": "observe|index|tips|air|forecast_24h",
"province": "湖南省",
"city": "长沙市",
"county": "",
"callback": "weather",
"_": time()
}
def forecast(self):
day = str(self.date.date())
forecast = self.data["forecast_24h"]
for i in forecast:
if forecast[i]["time"] == day:
return forecast[i]["max_degree"] + "-" + forecast[i]["min_degree"] + "°C"
async def get(self, err_: int = 0) -> dict:
try:
async with ClientSession() as session:
async with session.get(self.url, params=self.params) as res:
return loads(re.sub(r"^\S+\(|\)$", "", await res.text()))["data"]
except ClientConnectionError as err:
if err_ < 3:
return await self.get(err_ + 1)
else:
raise ClientConnectionError(err)
def suggest(self):
"""今日天气建议"""
text = []
index: dict = self.data.get("index")
for i in {"sports",
"clothes",
# "comfort",
# "dry",
"makeup",
"morning",
"umbrella",
# "allergy",
"ultraviolet"
# "sunscreen"
}:
value = index[i]["detail"] or index[i]["info"]
if value:
text.append(f'{index[i]["name"]}{value}')
return "\n- - - - - - - - - - -\n".join(text)
def weather(self):
"""天气"""
w = self.data["observe"]
return (f"今天是{self.date.date()}长沙当前天气\n"
f"天气:{w['weather']}\n"
f"气温:{self.forecast()}\n"
f"当前气温:{w['degree']}°C\n"
f"气压:{w['pressure']}hPa\n"
f"湿度:{w['humidity']}%\n"
f"降雨率:{w['precipitation']}%\n"
f"风向:{self.direction[int(w['wind_direction'])]} {w['wind_power']}")
def air(self):
"""空气质量"""
air = self.data["air"]
return (f"空气质量:{air['aqi']} {air['aqi_name']}\n"
f"co{air['co']}\n"
f"no2{air['no2']}\n"
f"o3{air['o3']}\n"
f"pm10{air['pm10']}\n"
f"pm2.5{air['pm2.5']}\n"
f"so2{air['so2']}"
)
def text(self):
yield self.weather() + "\n- - - - - - - - - -\n" + self.air()
yield self.suggest()
@staticmethod
async def get_class():
await mysql.execute("select * from class_table")
class_id = list(mysql.form().get("class_group"))
return class_id or []
async def __aenter__(self):
self.date = datetime.now()
self.data = await self.get()
return self
async def __aexit__(self, *args):
...

@ -0,0 +1,12 @@
# 功能插件
提高代码混淆度
功能
- 数据库操作
- permission
- rule
- 文件读取
- 读取和写入数据,方便直接获取文件路径保存文件
- 工具箱
- 常用爬虫或功能集成

@ -0,0 +1,6 @@
from nonebot import export
from .data_source import MySQLdbMethods
export = export()
export.MySQLdbMethods = MySQLdbMethods

@ -0,0 +1,89 @@
from aiomysql import connect, Warning, Connection, Cursor, IntegrityError, InternalError, OperationalError, Error
from nonebot import get_driver
from warnings import filterwarnings
from nonebot.log import logger
from pandas import DataFrame
driver = get_driver()
filterwarnings("error", category=Warning)
class MySQLdbMethods:
__slots__ = ("_conn", "cur", "description")
cur: Cursor
is_run = False # 数据库是否启动
_conn: Connection
config = {
"db": driver.config.mysql_db,
"host": driver.config.mysql_host,
"user": driver.config.mysql_user,
"password": str(driver.config.mysql_password),
}
async def execute(self, query: str, param: list = None, err_num: int = 5):
try:
await self.cur.execute(query, param)
except (RuntimeError, InternalError) as err:
if err_num:
logger.warning("数据库连接超时,重新建立连接!")
await self.close()
await self.connect()
await self.execute(query, param, err_num-1)
else:
print(query, param)
raise err
except IntegrityError:
await self._conn.rollback()
raise IntegrityError
async def execute_commit(self, query: str, param: list = None):
await self.execute(query, param)
await self._conn.commit()
def fetchall(self):
return self.cur.fetchall()
def form(self) -> DataFrame:
"""将表格数据转为DataFrame"""
data = self.fetchall().result()
return DataFrame({t[0]: [v[i] for v in data] for i, t in enumerate(self.cur.description)})
@classmethod
async def connect(cls):
"""与数据库建立连接"""
cls._conn = await connect(**cls.config)
cls.cur = await cls._conn.cursor()
@classmethod
async def close(cls):
"""关闭数据库"""
try:
await cls.cur.close()
cls._conn.close()
except Error as err:
logger.error("数据库断开出现错误")
logger.error(err)
@classmethod
async def start(cls):
"""开启数据库"""
try:
await cls.connect()
logger.success("成功与MySQL数据库建立连接")
except AssertionError as err:
logger.error(err)
except OperationalError as err:
logger.error("数据库连接失败!请检查配置文件是否输入有误!")
logger.error(err)
else:
cls.is_run = True
@classmethod
async def stop(cls):
"""停止数据库"""
if cls.is_run:
await cls.close()
driver.on_startup(MySQLdbMethods.start)
driver.on_shutdown(MySQLdbMethods.stop)

@ -0,0 +1,40 @@
from nonebot import require, export
from nonebot.permission import Permission, SUPERUSER
from nonebot.adapters.cqhttp import Bot, Event, MessageEvent
MySQLdbMethods = require("botdb").MySQLdbMethods
mysql = MySQLdbMethods()
export = export()
async def _teacher(bot: Bot, event: Event):
"""分析是否为教师或超级用户,如果是则满足条件"""
if event.get_type() == "message":
event: MessageEvent
user_id = event.get_user_id()
# 是否为超级用户
if user_id in bot.config.superusers:
return True
else:
# 是否为教师
await mysql.execute("select qq from teacher where qq=%s" % event.user_id)
return event.user_id in set(mysql.form()["qq"])
return False
async def _class_group(bot: Bot, event: Event):
if event.get_type() == "message":
event: MessageEvent
user_id = event.get_user_id()
# 是否为超级用户
if user_id in bot.config.superusers:
return True
else:
await mysql.execute("select qq from user_info union select qq from teacher")
return event.user_id in set(mysql.form()["qq"])
return False
CLASS_GROUP = Permission(_class_group)
TEACHER = Permission(_teacher)
export.CLASS_GROUP = CLASS_GROUP
export.TEACHER = TEACHER

@ -0,0 +1,63 @@
from aiofile import async_open
from nonebot import export
from os.path import join, dirname
import os
export = export()
global_default_path: str = os.getcwd()
def set_path(path):
"""设置读取的文件路径"""
global global_default_path
global_default_path = path
class ReadFile:
# 默认路径
default_path: str = global_default_path
def __init__(self, *args: str):
"""
:param args: 默认路径下的其它文件
"""
self.path = join(self.default_path, *args)
async def read(self, file_name, *, mode="r"):
async with async_open(join(self.path, file_name), mode=mode) as file:
return await file.read()
async def write(self, file_name, data, *, mode="w", end=True):
try:
async with async_open(join(self.path, file_name), mode=mode) as file:
return await file.write(data)
except FileNotFoundError as err:
if end:
self.mkdir(dirname(file_name))
await self.write(file_name, data, mode=mode, end=False)
else:
raise err
def listdir(self, *args: str) -> list:
return os.listdir(join(self.path, *args))
def remove(self, *args: str) -> bool:
try:
os.remove(join(self.path, *args))
return True
except FileNotFoundError:
return False
def mkdir(self, *args: str) -> bool:
try:
os.makedirs(join(self.path, *args))
return True
except FileExistsError:
return False
# 设置读取的文件路径
export.set_path = set_path
export.ReadFile = ReadFile

@ -0,0 +1,11 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# Your Config Here
dir_name = "src"
data_dir_name = "data"
config_dir_name = "config"
class Config:
extra = "ignore"

@ -0,0 +1,105 @@
from nonebot import require, export
from nonebot.exception import FinishedException
from pandas import DataFrame
from nonebot.rule import T_State, Rule
from .config import Config
from nonebot.adapters.cqhttp import Bot, Event, GroupMessageEvent, MessageEvent
MySQLdbMethods = require("botdb").MySQLdbMethods
mysql = MySQLdbMethods()
config = Config()
export = export()
# 是否为班主任
async def _master(bot: Bot, event: Event, state: T_State, is_raise: bool = True):
"""分析是否是班主任"""
if event.get_type() == "message":
event: GroupMessageEvent
await mysql.execute("select class_group, class_name from class_table where class_teacher=%s" % event.user_id)
data = mysql.form()
state["all_group"] = {
"class_group": list(data["class_group"]),
"class_name": list(data["class_name"])
}
if event.group_id in state["all_group"]["class_group"]:
return True
if is_raise:
raise FinishedException
return False
# 是否为班干部
async def _class_cadre(bot: Bot, event: Event, state: T_State, is_raise: bool = True):
"""
分析是否为班干部
会返回用户的姓名班级职位到state内
"""
if event.get_type() == "message":
event: MessageEvent
await mysql.execute("select 姓名, 职位, 班级 from user_info where qq=%s" % event.user_id)
data = mysql.form()
if data.shape[0]:
state["class_cadre"] = data.loc[0]
if state["class_cadre"]["职位"] in config.class_cadre:
return True
if is_raise:
raise FinishedException
return False
# 是否为学生
async def _is_user(bot: Bot, event: Event, state: T_State, is_raise: bool = True):
"""
分析是否为班级学生并且返回学生信息姓名职位班级学号
"""
event_type = event.get_type()
if event_type == "message" or event_type == "request":
event: MessageEvent
await mysql.execute("select 姓名, 职位, 班级, 学号, 分数 from user_info where qq=%s" % event.user_id)
data: DataFrame = mysql.form()
if data.shape[0]:
state["user_info"] = data.loc[0]
return True
if is_raise:
raise FinishedException
return False
# 是否为班级群
async def _class_group(bot: Bot, event: Event, state: T_State, is_raise: bool = True):
event_type = event.get_type()
if event_type == "message":
event: GroupMessageEvent
await mysql.execute("select * from class_table where class_group=%s", [event.group_id])
data: DataFrame = mysql.form()
if data.shape[0]:
state["class_group"] = data.loc[0]
return True
if is_raise:
raise FinishedException
return False
# 是否为寝室长
async def _dorm_admin(bot: Bot, event: Event, state: T_State, is_raise: bool = True):
event_type = event.get_type()
if event_type == "message":
event: GroupMessageEvent
await mysql.execute("select * from user_info where 寝室长='' and qq=%s", [event.user_id])
data: DataFrame = mysql.form()
if data.shape[0]:
state["dorm_admin"] = data.loc[0]
return True
if is_raise:
raise FinishedException
return False
export.DORM_ADMIN = Rule(_dorm_admin)
export.MASTER = Rule(_master)
export.CLASS_GROUP = Rule(_class_group)
export.IS_USER = Rule(_is_user)
export.CLASS_CADRE = Rule(_class_cadre)
export.cadres = config.class_cadre

@ -0,0 +1,7 @@
from pydantic import BaseSettings
class Config(BaseSettings):
class_cadre = ["组织委员", "心理委员", "宣传委员", "体育委员", "男生委员",
"女生委员", "治保委员", "副班长", "学习委员", "生卫委员",
"权益委员", "班长", "团支书"]

@ -0,0 +1,32 @@
import re
from nonebot import export
from nonebot.adapters.cqhttp import Message
from .data_source import GetDocsSheet
from .config import Config
from .watermark import Watermark
from typing import Union
def re_docs(text: str) -> str:
return re.search(r"https://docs.qq.com/sheet/\S[^\"']+", str(text).replace("\\", ""), re.I).group()
def get_url(message: Union[Message, str]) -> str:
url = None
if isinstance(message, Message):
for msg in message:
if msg.type == "json" or msg.type == "xml":
url = re_docs(msg.data["data"])
elif msg.type == "text":
url = re_docs(msg.data["text"])
if url:
return url
else:
return re_docs(message)
export = export()
export.GetDocsSheet = GetDocsSheet
export.Watermark = Watermark
export.get_url = get_url
export.re_docs = re_docs

@ -0,0 +1,8 @@
from pydantic import BaseSettings
class Config(BaseSettings):
# Your Config Here
class Config:
extra = "ignore"

@ -0,0 +1,153 @@
from pandas import DataFrame
from aiohttp import ClientSession
from datetime import datetime, timedelta
import re
from typing import List
docs_url = "https://docs.qq.com/dop-api/opendoc"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/94.0.4606.61 Safari/537.36 ",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"appName": "Opera"
}
class GetDocsSheet:
def __init__(self, url: str):
self.url = url
self.session = ClientSession()
@property
def params(self):
"""提取URL所需params"""
params = {
"outformat": 1,
"normal": 1
}
url = self.url.split("/")[-1]
param = re.findall(r"^\w+|tab=\w+", url)
params["id"] = param[0]
if len(param) > 1:
params["tab"] = param[1].replace("tab=", "")
return params
async def get(self):
async with self.session.get(docs_url, headers=headers, params=self.params) as res:
return await res.json()
@staticmethod
def extract(data: dict):
for values in data["clientVars"]["collab_client_vars"]["initialAttributedText"]["text"][0]:
for value in values:
if isinstance(value["c"][1], dict):
for i in value:
if isinstance(value[i], list):
return value[i]
def convert2d(self) -> list:
"""
转换未二维列表
从列表第一位开始取按照长度推算出后面内容的序号
然后按照序号去循环推导出内容
如果序号存在就查看序号内是否有值取出值
不存在则全部替换为空
"""
data = []
keys = list(self.raw_data.keys())
length = len(keys)
index = 0
while index < length:
key = int(keys[index])
if not key % self.columns:
arr = []
for key in range(key, key + self.columns):
values = self.raw_data.get(str(key))
if values:
index += 1
value = values.get("2")
arr.append(value[1] if value else value)
continue
arr.append(values)
data.append(arr)
else:
index += 1
return data
def data_frame(self):
"""
找出columns所在位置
"""
# 清除空行空列
df = DataFrame(self.convert2d(), dtype="str").dropna(axis=0, how='all').dropna(axis=1, how='all')
for i, v in enumerate(df.values):
if all(v):
return DataFrame(df.values[i+1:], columns=list(v))
async def __aenter__(self):
data = self.extract(await self.get())
self.columns = data[0][-1] + 1
self.rows = data[0][-3] + 1
self.raw_data = data[1]
self.data = self.data_frame()
return self
@staticmethod
def get_excel_date(date_int: int):
"""对于excel日期进行转换以便写入数据"""
if date_int:
return str((datetime(1899, 12, 30) + timedelta(int(date_int) + 1)).date())
return None
def reset_date(self, typeof: str = "出生日期"):
"""重置表格日期表格"""
try:
self.data[typeof] = [self.get_excel_date(i) for i in self.data[typeof]]
except IndexError:
...
finally:
return self
async def __aexit__(self, *args):
await self.session.close()
class QueryUser:
def __init__(self, params: List[str], class_name: str = None, group_id: int = None):
if not(class_name or group_id):
raise "Missing required parameters class_name or group_id"
self.params = params
self.class_name = class_name
self.group_id = group_id
self.index = []
self.name = []
self.user_id = []
def split_param(self):
"""
拆分参数
字符串作为名字传入 name
如果是数字判断数字是序号还是id
满足长度四位的为序号
超过的为id
"""
for i in self.params:
if i.isdigit():
i = int(i)
if i < 1000:
self.index.append(i)
else:
self.user_id.append(i)
else:
self.name.append(i)
async def __aenter__(self):
...
async def __aexit__(self, *args):
...

@ -0,0 +1,62 @@
from typing import Tuple, Union
from PIL import Image, ImageDraw, ImageFont
from PIL.PngImagePlugin import PngImageFile
from aiohttp import ClientSession
from io import BytesIO
class Watermark:
def __init__(self,
file: Union[str, bytes] = None,
url: str = None,
text: str = None,
size: int = 50,
font: str = "simhei.ttf",
xy: Tuple[int, int] = None,
fill: Tuple[int, int, int] = None):
self.url = url
self.file = file
self.text = text
self.size = size
self.font = font
self.xy = xy or (0, 0)
self.fill = fill or (255, 0, 0)
async def url_img(self) -> PngImageFile:
"""下载图片"""
async with ClientSession() as session:
async with session.get(self.url) as res:
return Image.open(BytesIO(await res.read()))
def byte_img(self) -> PngImageFile:
"""数据流"""
return Image.open(BytesIO(self.file))
def local_img(self):
"""本地图片"""
return Image.open(self.file)
def draw(self, img: PngImageFile = None) -> PngImageFile:
"""绘制"""
img = self.img
draw = ImageDraw.Draw(img)
draw.text(xy=self.xy, text=self.text, fill=self.fill,
font=ImageFont.truetype(font=self.font, size=self.size))
return img
async def __aenter__(self):
if self.file:
if isinstance(self.file, bytes):
self.img = self.byte_img()
else:
self.img = self.local_img()
elif self.url:
self.img = await self.url_img()
else:
raise TypeError("String or Bytes")
self.draw()
return self
async def __aexit__(self, *args):
...
Loading…
Cancel
Save