南昌大学(NCU)羽毛球场地预约脚本及APP

        在冬天进行羽毛球运动是一个很好的选择,它能帮助你保持身体活力,增强心肺功能,并促进血液循环。但是室友和师弟师妹反应,学校的羽毛球场地有限,手速慢的根本预约不到场地。

       中午12:00准时开放预约,1秒钟不到,就只剩下08:00-09:00和12:00-13:00的不好的时间段,如果想要预约到晚上19:00-21:00的黄金时间段就需要用脚本了。

2980ede8085b4a45ae09600fdb6a7df2.png

一、总代码 

import sys
import json
from PySide6.QtWidgets import (QApplication, QWidget, QFormLayout, QLineEdit,
                               QComboBox, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QLabel)
from PySide6.QtCore import QThread, Signal, QMutex
import requests
from datetime import datetime, timedelta
import queue
import threading
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Value
import aiohttp
import asyncio
import nest_asyncio

# 允许在Jupyter等环境中运行异步代码
nest_asyncio.apply()

class LogWorker(QThread):
    log_signal = Signal(str)
    
    def __init__(self):
        super().__init__()
        self.log_queue = queue.Queue()
        self._stop = False
        self.mutex = QMutex()
        
    def run(self):
        while not self._stop:
            try:
                message = self.log_queue.get(timeout=0.1)
                self.log_signal.emit(message)
            except queue.Empty:
                continue
                
    def add_log(self, message):
        self.mutex.lock()
        self.log_queue.put(message)
        self.mutex.unlock()
        
    def stop(self):
        self._stop = True

class AsyncBookingThread(threading.Thread):
    def __init__(self, config, log_worker, request_count, stop_event):
        super().__init__()
        self.config = config
        self.log_worker = log_worker
        self.request_count = request_count
        self.stop_event = stop_event
        self.success = False
        self.loop = None

    async def make_request(self, session):
        url = f"https://ndyy.ncu.edu.cn/api/badminton/saveReservationInformation?\
role=ROLE_STUDENT&\
date={self.config['date']}&\
startTime={self.config['time']}&\
areaName=%E7%BE%BD%E6%AF%9B%E7%90%83{self.config['court']}%E5%8F%B7%E5%9C%BA%E5%9C%B0&\
areaNickname=hall{self.config['court']}"

        headers = {
            "token": self.config['token'].strip(),
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive'
        }

        try:
            async with session.get(url, headers=headers, timeout=1) as response:
                data = await response.json()
                with self.request_count.get_lock():
                    self.request_count.value += 1
                    count = self.request_count.value
                
                if data.get('msg', '') == '':
                    self.log_worker.add_log(f"✅ 预约成功!(共尝试{count}次)")
                    self.success = True
                    self.stop_event.set()
                    return True
                
                if count % 500 == 0:  # 改为每500次更新一次日志
                    error_msg = data.get('msg', '未知错误')
                    self.log_worker.add_log(f"⏳ 已尝试{count}次: {error_msg}")
                return False

        except Exception as e:
            return False

    async def run_async(self):
        async with aiohttp.ClientSession() as session:
            while not self.stop_event.is_set():
                success = await self.make_request(session)
                if success:
                    break

    def run(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.loop.run_until_complete(self.run_async())
        self.loop.close()

class BadmintonWorker(QThread):
    def __init__(self, config, log_worker):
        super().__init__()
        self.config = config
        self.log_worker = log_worker
        self._stop = False
        self.request_count = Value('i', 0)
        self.stop_event = threading.Event()
        self.threads = []

    def run(self):
        try:
            # 创建多个线程并发请求
            num_threads = 10  # 可以根据需要调整线程数量
            for _ in range(num_threads):
                thread = AsyncBookingThread(self.config, self.log_worker, self.request_count, self.stop_event)
                thread.start()
                self.threads.append(thread)

            # 等待所有线程完成
            for thread in self.threads:
                thread.join()

        except Exception as e:
            if not self._stop:
                self.log_worker.add_log(f"❌ 发生错误: {str(e)} (共尝试{self.request_count.value}次)")
        finally:
            self._stop = False

    def stop(self):
        self._stop = True
        self.stop_event.set()
        for thread in self.threads:
            thread.join()

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.worker = None
        self.log_worker = LogWorker()
        self.log_worker.log_signal.connect(self.update_log)
        self.log_worker.start()

    def initUI(self):
        self.setWindowTitle("NCU场馆预约")
        self.setFixedSize(400, 400)
 
        layout = QFormLayout()
 
        # 日期选择
        date_layout = QHBoxLayout()
        
        # 年份选择(2025-2030)
        self.year = QComboBox()
        for year in range(2025, 2030):
            self.year.addItem(str(year))
        self.year.setCurrentText(str(datetime.now().year))  # 默认选择当前年份
        
        # 月份选择
        self.month = QComboBox()
        self.month.addItems([f"{i:02d}" for i in range(1, 13)])
        self.month.setCurrentText(datetime.now().strftime("%m"))
        
        # 日期选择
        self.day = QComboBox()
        self.day.addItems([f"{i:02d}" for i in range(1, 32)])  # 固定1-31日
        self.day.setCurrentText(datetime.now().strftime("%d"))  # 默认选择当前日期
        
        # 连接月份变化信号
        self.month.currentTextChanged.connect(self.update_days)
        
        date_layout.addWidget(self.year)
        date_layout.addWidget(QLabel("年"))
        date_layout.addWidget(self.month)
        date_layout.addWidget(QLabel("月"))
        date_layout.addWidget(self.day)
        date_layout.addWidget(QLabel("日"))

        # 其他输入字段
        self.time = QComboBox()
        self.time.addItems(["08:00-09:00", "09:00-10:00", "10:00-11:00", "11:00-12:00","12:00-13:00", "13:00-14:00", "14:00-15:00", "15:00-16:00", "16:00-17:00", "17:00-18:00", "18:00-19:00", "19:00-20:00", "20:00-21:00", "21:00-22:00"])
        self.court = QComboBox()
        self.court.addItems([str(i) for i in range(1, 13)])
        self.token = QLineEdit()
        self.token.setStyleSheet("QLineEdit { border: none; }")  # 去掉下划线
 
        # 日志显示
        self.log = QTextEdit()
        self.log.setReadOnly(True)
        self.log.setStyleSheet("QTextEdit { border: none; }")  # 去掉下划线
 
        # 提交按钮
        self.btn = QPushButton("开始预约")
        self.btn.clicked.connect(self.start_booking)
 
        # 停止按钮
        self.stop_btn = QPushButton("停止预约")
        self.stop_btn.clicked.connect(self.stop_booking)
        self.stop_btn.setEnabled(False)
 
        # 添加组件
        layout.addRow("预约日期", date_layout)
        layout.addRow("时间段", self.time)
        layout.addRow("场地号", self.court)
        layout.addRow("Token", self.token)
        layout.addRow(self.log)
        layout.addRow(self.btn)
        layout.addRow(self.stop_btn)
 
        self.setLayout(layout)
 
    def update_days(self):
        """更新日期选项"""
        self.day.clear()
        year = int(self.year.currentText())
        month = int(self.month.currentText())
        
        # 获取当月天数
        if month == 12:
            next_month = 1
            next_year = year + 1
        else:
            next_month = month + 1
            next_year = year
            
        first_day = datetime(year, month, 1)
        last_day = datetime(next_year, next_month, 1) - timedelta(days=1)
        days_in_month = last_day.day
        
        # 添加日期选项
        self.day.addItems([f"{i:02d}" for i in range(1, days_in_month + 1)])
        
        # 设置当前日期
        current_day = datetime.now().day
        if month == datetime.now().month and year == datetime.now().year:
            self.day.setCurrentText(f"{current_day:02d}")

    def start_booking(self):
        if self.worker and self.worker.isRunning():
            QMessageBox.warning(self, "警告", "已有预约在进行中!")
            return

        config = {
            "date": f"{self.year.currentText()}-{self.month.currentText()}-{self.day.currentText()}",
            "time": self.time.currentText(),
            "court": self.court.currentText(),
            "token": self.token.text()
        }

        if not all(config.values()):
            QMessageBox.critical(self, "错误", "所有字段必须填写!")
            return

        self.worker = BadmintonWorker(config, self.log_worker)
        self.worker.finished.connect(self.on_worker_finished)  # 添加完成信号连接
        self.worker.start()
        self.btn.setEnabled(False)
        self.stop_btn.setEnabled(True)

    def stop_booking(self):
        if self.worker:
            self.worker.stop()
            self.worker.wait()
            self.worker = None
            self.log_worker.add_log("🛑 已停止预约")  # 使用日志线程添加停止消息
            self.btn.setEnabled(True)
            self.stop_btn.setEnabled(False)

    def update_log(self, message):
        self.log.append(message)

    def on_worker_finished(self):
        self.worker = None
        self.btn.setEnabled(True)
        self.stop_btn.setEnabled(False)

    def closeEvent(self, event):
        if self.worker:
            self.worker.stop()
            self.worker.wait()
        self.log_worker.stop()
        self.log_worker.wait()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

二、APP 使用

通过百度网盘分享的文件:NCU场馆预约助手.exe
链接: https://pan.baidu.com/s/183uUrd7sLAy6YKJfEmupgg?pwd=en67 提取码: en67

由于里面包含Pysid6(这个库比较大)和requests库, 所以APP有48M的大小。

import sys
import json
from PySide6.QtWidgets import (QApplication, QWidget, QFormLayout, QLineEdit,
                               QComboBox, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QLabel)
from PySide6.QtCore import QThread, Signal, QMutex
import requests
from datetime import datetime, timedelta
import queue
import threading
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Value
import aiohttp
import asyncio
import nest_asyncio

双击,弹出这个界面:

填写信息就可以了:注意token需要自己获取 ,token差不多15分钟更新一次。

“token” 更新 ----F12键打开开发者界面 

c4fca9ec54414077988fed6128190dad.png

最后2分钟内去网站页面交钱就行。

e09c6bcc3b144f1cb73f27fa0c6b23a1.gif

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码贾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值