Skip to content

SJTU 教务

1. 插件简介

插件名称 父类 触发关键词 触发权限 内容
JwcGroup StandardPlugin '-jwc' None 获取交大教务处信息
SjtuJwcMonitor StandardPlugin
CronStandardPlugin
None None 广播交大教务处更新信息

2. 示范样例

代码部分:

ROOT_ADMIN_ID = [111, # 用户A  
                 222, # 用户B
                 444] # 用户D
GroupPluginList = [ # 群聊启用插件
    # .....
    JwcGroup()
    # .....
]

群聊部分:

# 群内尚未打开jwc
333> -jwc
# 无事发生

# 接下来管理员打开canvas
111> -grpcfg enable jwc
bot> OK

# 用户发送jwc指令
222> -jwc
bot>    【1】2022-09-30 2022年大学生英语竞赛闵行校区考生退费通知 https://jwc.sjtu.edu.cn/info/1285/11905.htm
        【2】2022-09-30 关于上海交通大学2022年立项教材评审结果的公示 https://jwc.sjtu.edu.cn/info/1257/11904.htm
        【3】2022-09-29 关于举办第十四届全国大学生数学竞赛赛前培训的通知 https://jwc.sjtu.edu.cn/info/1222/11900.htm
        【4】2022-09-27 上海交通大学2023年推荐优秀应届本科毕业生免试攻读研究生公示名单 https://jwc.sjtu.edu.cn/content.jsp?urltype=news.NewsContentUrl&wbtreeid=1222&wbnewsid=11897
        【5】2022-09-27 关于2022年普通话水平测试工作延期到本学期举办的通知 https://jwc.sjtu.edu.cn/info/1222/11894.htm

# 某时刻bot检测到教务处通知更新
bot> ${教务处通知的图片}

图片展示:

注意:旧的绘图代码

这是早期完成的插件,绘图部分代码并未使用推荐的 responseImage 库

3. 代码分析

代码位于 plugins/getJwc.py

from utils.standardPlugin import StandardPlugin, Any, Union, CronStandardPlugin
from utils.responseImage import *
import requests
from bs4 import BeautifulSoup as BS
from utils.basicEvent import *
from utils.basicConfigs import *
from threading import Timer, Semaphore
from pathlib import Path
import json
from datetime import datetime
from urllib.parse import urljoin
import qrcode
import os, os.path

def getJwc()->list:
    pageUrl = 'https://jwc.sjtu.edu.cn/xwtg/tztg.htm'
    req = requests.get(pageUrl)
    if req.status_code != requests.codes.ok:
        warning("jwc.sjtu.edu.cn API failed!")
        return []
    page = str(req.content, 'utf-8')
    page = BS(page, 'lxml')
    news = page.find(class_='Newslist').ul.find_all(class_='clearfix')
    newsList = []
    for n in news:
        try:
            sj = n.find(class_='sj')
            year, month = sj.p.contents[0].split('.')
            day = sj.h2.contents[0]
            content = n.find(class_='wz')
            title:str = content.h2.contents[0]
            link:str = content.a.get('href')
            link = urljoin(pageUrl, link)
            detail:str = content.p.contents[0]
            newsList.append({
                'year': year,
                'month': month,
                'day': day,
                'title': title,
                'detail': detail,
                'link': link,
            })
        except BaseException as e:
            print("exception in getJwc: {}".format(e))
    return newsList
class SjtuJwcMonitor(StandardPlugin, CronStandardPlugin):
    monitorSemaphore = Semaphore()
    def __init__(self) -> None:
        if SjtuJwcMonitor.monitorSemaphore.acquire(blocking=False):
            self.start(20, 180)
    def judgeTrigger(self, msg:str, data:Any) -> bool:
        return False
    def executeEvent(self, msg:str, data:Any) -> Union[None, str]:
        return "OK"
    def tick(self, ):
        exact_path='data/jwc.json'
        if not os.path.isfile(exact_path):
            with open(exact_path, 'w') as f:
                f.write('[]')
        url_list:list = json.load(open(exact_path, 'r'))
        updateFlag = len(url_list) > 0
        for j in getJwc():
            if j['link'] not in url_list:
                url_list.append(j['link'])
                if not updateFlag: continue
                pic = DrawNoticePIC(j)
                pic = pic if os.path.isabs(pic) else os.path.join(ROOT_PATH, pic)
                for group_id in getPluginEnabledGroups('jwc'):
                    send(group_id, '已发现教务通知更新:\n【'+j['title']+'】\n'+j['link'])
                    send(group_id, '[CQ:image,file=files://%s,id=40000]'%pic)
        with open(exact_path, 'w') as f:
            json.dump(url_list, f, indent=4)
    def getPluginInfo(self, )->Any:
        return {
            'name': 'SjtuJwcMonitor',
            'description': '教务通知更新广播',
            'commandDescription': 'None',
            'usePlace': ['group', ],
            'showInHelp': True,
            'pluginConfigTableNames': [],
            'version': '1.0.4',
            'author': 'Unicorn',
        }

class GetJwc(StandardPlugin): 
    def judgeTrigger(self, msg:str, data:Any) -> bool:
        return msg=='-jwc'
    def executeEvent(self, msg:str, data:Any) -> Union[None, str]:
        target = data['group_id'] if data['message_type']=='group' else data['user_id']
        jwc = sorted(getJwc(), key=lambda x: '%s-%s-%s'%(x['year'], x['month'], x['day']), reverse=True)
        jwcStr = ""
        idx = 1
        for j in jwc[:7]:
            jwcStr += '【%d%s-%s-%s %s %s\n'%(idx, j['year'], j['month'], j['day'], j['title'], j['link'])
            idx += 1
        jwcStr = jwcStr[:-1]
        send(target, jwcStr, data['message_type'])
        return "OK"
    def getPluginInfo(self, )->Any:
        return {
            'name': 'GetJwc',
            'description': '获取教务通知',
            'commandDescription': '-jwc',
            'usePlace': ['group', 'private', ],
            'showInHelp': True,
            'pluginConfigTableNames': [],
            'version': '1.0.0',
            'author': 'Unicorn',
        }

class GetSjtuNews(StandardPlugin): 
    monitorSemaphore = Semaphore()
    def __init__(self) -> None:
        self.checkTimer = Timer(20,self.updateAndCheck)
        if GetSjtuNews.monitorSemaphore.acquire(blocking=False):
            self.checkTimer.start()
    def judgeTrigger(self, msg:str, data:Any) -> bool:
        return msg=='-sjtu news'
    def executeEvent(self, msg:str, data:Any) -> Union[None, str]:
        target = data['group_id'] if data['message_type']=='group' else data['user_id']
        pic_path = os.path.join(SAVE_TMP_PATH, 'sjtu_news.png')
        pic_path = pic_path if os.path.isabs(pic_path) else os.path.join(ROOT_PATH, pic_path)
        if not os.path.isfile(pic_path):
            drawSjtuNews()
        send(target, '[CQ:image,file=files://%s,id=40000]'%pic_path, data['message_type'])
        return "OK"
    def getPluginInfo(self, )->Any:
        return {
            'name': 'GetSjtuNews',
            'description': '获取交大新闻网',
            'commandDescription': '-sjtu news',
            'usePlace': ['group', 'private', ],
            'showInHelp': True,
            'pluginConfigTableNames': [],
            'version': '1.0.0',
            'author': 'fangtiancheng',
        }
    def updateAndCheck(self, ):
        self.checkTimer.cancel()
        self.checkTimer = Timer(900,self.updateAndCheck)
        self.checkTimer.start()
        drawSjtuNews()

def DrawNoticePIC(notice)->str:
    width = 720
    txt_line=""
    txt_parse, title_parse=[], []
    for word in notice['title']:
        if txt_line=="" and word in [',',';','。','、','"',':','.','”']: #避免标点符号在首位
            title_parse[-1]+=word
            continue
        txt_line+=word
        if font_syhtmed_24.getsize(txt_line)[0]>width-180:
            title_parse.append(txt_line)
            txt_line=""
    if txt_line!="":
        title_parse.append(txt_line)

    txt_line=""
    for word in notice['detail'].strip():
        if txt_line=="" and word in [',',';','。','、','"',':','.','”']: #避免标点符号在首位
            txt_parse[-1]+=word
            continue
        txt_line+=word
        if font_syhtmed_18.getsize(txt_line)[0]>width-240:
            txt_parse.append(txt_line)
            txt_line=""
    if txt_line!="":
        txt_parse.append(txt_line)
    if txt_parse[-1][-1]!='.':
        txt_parse[-1]+='...'
    height = 525+len(txt_parse)*27+len(title_parse)*33
    img, draw, h = init_image_template('教务通知·更新提醒', width, height, (167,32,56,255))
    h+=130
    l=60
    img=draw_rounded_rectangle(img, 60, h, width-60, height-90, (255,255,255,255))
    txt_size=draw.textsize(notice['title'], font = font_syhtmed_24)
    for line in title_parse:
        txt_size=draw.textsize(line, font = font_syhtmed_24)
        draw.text(((width-txt_size[0])/2,h+33), line, fill=(0,0,0,255),font = font_syhtmed_24)
        h+=33
    # draw.text(((width-txt_size[0])/2,h+30),notice['title'], fill=(0,0,0,255),font = font_syhtmed_24)
    # h+=(30+txt_size[1])
    h+=5
    time_txt = '%s-%s-%s'%(notice['year'], notice['month'], notice['day'])
    txt_size=draw.textsize(time_txt, font = font_syhtmed_18)
    draw.text(((width-txt_size[0])/2,h+30),time_txt, fill=(115,115,115,255),font = font_syhtmed_18)
    h+=(30+txt_size[1])
    for line in txt_parse:
        txt_size=draw.textsize(line, font = font_syhtmed_18)
        draw.text(((width-txt_size[0])/2,h+27), line, fill=(115,115,115,255),font = font_syhtmed_18)
        h+=27
    qrc = qrcode.QRCode(
        version=3,
        error_correction=qrcode.constants.ERROR_CORRECT_Q,
        box_size=15,
        border=4,
    )
    qrc.add_data(notice['link'])
    qrc.make(fit=True)
    imgqrc = qrc.make_image().resize((180,180))
    img.paste(imgqrc, ((width-180)//2,h+30))
    save_path=os.path.join(SAVE_TMP_PATH, 'jwc_notice.png')
    img.save(save_path)
    return save_path