├── .gitignore
├── requirements.txt
├── README.md
├── .github
└── workflows
│ └── soushuba.yml
├── discuz-login.py
└── soushuba.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /.venv/
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.32.5
2 | beautifulsoup4==4.13.5
3 | lxml==6.0.1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 搜书吧自动签到
2 |
3 | 使用 GitHub Actions 每天凌晨自动签到搜书吧。
4 |
5 | ## 配置
6 |
7 | 为了能够自动签到,需要设置几个 Actions secrets,如下:
8 |
9 | | secrets | 说明 | 例子 |
10 | | ----------------- | -------------- | -------------------- |
11 | | SOUSHUBA_HOSTNAME | 搜书吧永久地址 | `www.soushu2030.com` |
12 | | SOUSHUBA_USERNAME | 搜书吧用户名 | |
13 | | SOUSHUBA_PASSWORD | 搜书吧密码 | |
14 |
--------------------------------------------------------------------------------
/.github/workflows/soushuba.yml:
--------------------------------------------------------------------------------
1 | name: Run soushuba qiandao
2 | on:
3 | push:
4 | # Allows you to run this workflow manually from the Actions tab
5 | workflow_dispatch:
6 | schedule:
7 | - cron: '0 21 * * *'
8 |
9 | jobs:
10 | run:
11 | runs-on: ubuntu-latest
12 | env:
13 | SOUSHUBA_HOSTNAME: ${{ secrets.SOUSHUBA_HOSTNAME }}
14 | SOUSHUBA_USERNAME: ${{ secrets.SOUSHUBA_USERNAME }}
15 | SOUSHUBA_PASSWORD: ${{ secrets.SOUSHUBA_PASSWORD }}
16 |
17 | steps:
18 | - name: Check out repository code
19 | uses: actions/checkout@v3
20 | - name: Set up Python
21 | uses: actions/setup-python@v4
22 | with:
23 | python-version: '3.13'
24 | cache: 'pip'
25 | - run: pip install -r requirements.txt
26 | - run: python soushuba.py
27 |
--------------------------------------------------------------------------------
/discuz-login.py:
--------------------------------------------------------------------------------
1 | import re
2 | import requests
3 |
4 |
5 | class DiscuzLogin:
6 | proxies = {
7 | 'http': 'http://127.0.0.1:1080',
8 | 'https': 'https://127.0.0.1:1080'
9 | }
10 |
11 | def __init__(self, hostname, username, password, questionid='0', answer=None, proxies=None):
12 | self.session = requests.session()
13 | self.hostname = hostname
14 | self.username = username
15 | self.password = password
16 | self.questionid = questionid
17 | self.answer = answer
18 | if proxies:
19 | self.proxies = proxies
20 |
21 | @classmethod
22 | def user_login(cls, hostname, username, password, questionid='0', answer=None, proxies=None):
23 | user = DiscuzLogin(hostname, username, password, questionid, answer, proxies)
24 | user.login()
25 |
26 | def form_hash(self):
27 | rst = self.session.get(f'https://{self.hostname}/member.php?mod=logging&action=login').text
28 | loginhash = re.search(r'
', rst).group(1)
29 | formhash = re.search(r'
', rst).group(1)
30 | return loginhash, formhash
31 |
32 | def login(self):
33 | loginhash, formhash = self.form_hash()
34 | login_url = f'https://{self.hostname}/member.php?mod=logging&action=login&loginsubmit=yes&loginhash={loginhash}&inajax=1'
35 | form_data = {
36 | 'formhash': formhash,
37 | 'referer': f'https://{self.hostname}/',
38 | 'loginfield': self.username,
39 | 'username': self.username,
40 | 'password': self.password,
41 | 'questionid': self.questionid,
42 | 'answer': self.answer,
43 | 'cookietime': 2592000
44 | }
45 | login_rst = self.session.post(login_url, proxies=self.proxies, data=form_data)
46 | if self.session.cookies.get('xxzo_2132_auth'):
47 | print(f'Welcome {self.username}!')
48 | else:
49 | raise ValueError('Verify Failed! Check your username and password!')
50 |
51 |
52 | if __name__ == '__main__':
53 | DiscuzLogin.user_login('hostname', 'username', 'password')
--------------------------------------------------------------------------------
/soushuba.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | 实现搜书吧论坛登入和发布空间动态
4 | """
5 | import os
6 | import re
7 | import sys
8 | from copy import copy
9 |
10 | import requests
11 | from bs4 import BeautifulSoup
12 | from urllib.parse import urlparse
13 | import xml.etree.ElementTree as ET
14 | import time
15 | import logging
16 | import urllib3
17 |
18 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
19 |
20 | logger = logging.getLogger(__name__)
21 | logger.setLevel(logging.DEBUG)
22 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
23 |
24 | ch = logging.StreamHandler(stream=sys.stdout)
25 | ch.setLevel(logging.INFO)
26 | ch.setFormatter(formatter)
27 | logger.addHandler(ch)
28 |
29 | def get_refresh_url(url: str):
30 | try:
31 | response = requests.get(url)
32 | if response.status_code != 403:
33 | response.raise_for_status()
34 |
35 | soup = BeautifulSoup(response.text, 'html.parser')
36 | meta_tags = soup.find_all('meta', {'http-equiv': 'refresh'})
37 |
38 | if meta_tags:
39 | content = meta_tags[0].get('content', '')
40 | if 'url=' in content:
41 | redirect_url = content.split('url=')[1].strip()
42 | logger.info(f"Redirecting to: {redirect_url}")
43 | return redirect_url
44 | else:
45 | logger.error("No meta refresh tag found.")
46 | return None
47 | except Exception as e:
48 | logger.exception(f'An unexpected error occurred: {e}')
49 | return None
50 |
51 | def get_url(url: str):
52 | resp = requests.get(url)
53 | soup = BeautifulSoup(resp.content, 'html.parser')
54 |
55 | links = soup.find_all('a', href=True)
56 | for link in links:
57 | if link.text == "搜书吧":
58 | return link['href']
59 | return None
60 |
61 | class SouShuBaClient:
62 |
63 | def __init__(self, hostname: str, username: str, password: str, questionid: str = '0', answer: str = None,
64 | proxies: dict | None = None):
65 | self.session: requests.Session = requests.Session()
66 | self.hostname = hostname
67 | self.username = username
68 | self.password = password
69 | self.questionid = questionid
70 | self.answer = answer
71 | self._common_headers = {
72 | "Host": f"{ hostname }",
73 | "Connection": "keep-alive",
74 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
75 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
76 | "Accept-Language": "zh-CN,cn;q=0.9",
77 | "Content-Type": "application/x-www-form-urlencoded",
78 | }
79 | self.proxies = proxies
80 |
81 | def login_form_hash(self):
82 | rst = self.session.get(f'https://{self.hostname}/member.php?mod=logging&action=login', verify=False).text
83 | loginhash = re.search(r'
', rst).group(1)
84 | formhash = re.search(r'', rst).group(1)
85 | return loginhash, formhash
86 |
87 | def login(self):
88 | """Login with username and password"""
89 | loginhash, formhash = self.login_form_hash()
90 | login_url = f'https://{self.hostname}/member.php?mod=logging&action=login&loginsubmit=yes' \
91 | f'&handlekey=register&loginhash={loginhash}&inajax=1'
92 |
93 |
94 | headers = copy(self._common_headers)
95 | headers["origin"] = f'https://{self.hostname}'
96 | headers["referer"] = f'https://{self.hostname}/'
97 | payload = {
98 | 'formhash': formhash,
99 | 'referer': f'https://{self.hostname}/',
100 | 'username': self.username,
101 | 'password': self.password,
102 | 'questionid': self.questionid,
103 | 'answer': self.answer
104 | }
105 |
106 | resp = self.session.post(login_url, proxies=self.proxies, data=payload, headers=headers, verify=False)
107 | if resp.status_code == 200:
108 | logger.info(f'Welcome {self.username}!')
109 | else:
110 | raise ValueError('Verify Failed! Check your username and password!')
111 |
112 | def credit(self):
113 | credit_url = f"https://{self.hostname}/home.php?mod=spacecp&ac=credit&showcredit=1&inajax=1&ajaxtarget=extcreditmenu_menu"
114 | credit_rst = self.session.get(credit_url, verify=False).text
115 |
116 | # 解析 XML,提取 CDATA
117 | root = ET.fromstring(str(credit_rst))
118 | cdata_content = root.text
119 |
120 | # 使用 BeautifulSoup 解析 CDATA 内容
121 | cdata_soup = BeautifulSoup(cdata_content, features="lxml")
122 | hcredit_2 = cdata_soup.find("span", id="hcredit_2").string
123 |
124 | return hcredit_2
125 |
126 | def space_form_hash(self):
127 | rst = self.session.get(f'https://{self.hostname}/home.php', verify=False).text
128 | formhash = re.search(r'', rst).group(1)
129 | return formhash
130 |
131 | def space(self):
132 | formhash = self.space_form_hash()
133 | space_url = f"https://{self.hostname}/home.php?mod=spacecp&ac=doing&handlekey=doing&inajax=1"
134 |
135 | headers = copy(self._common_headers)
136 | headers["origin"] = f'https://{self.hostname}'
137 | headers["referer"] = f'https://{self.hostname}/home.php'
138 |
139 | for x in range(5):
140 | payload = {
141 | "message": "开心赚银币 {0} 次".format(x + 1).encode("GBK"),
142 | "addsubmit": "true",
143 | "spacenote": "true",
144 | "referer": "home.php",
145 | "formhash": formhash
146 | }
147 | resp = self.session.post(space_url, proxies=self.proxies, data=payload, headers=headers, verify=False)
148 | if re.search("操作成功", resp.text):
149 | logger.info(f'{self.username} post {x + 1}nd successfully!')
150 | time.sleep(120)
151 | else:
152 | logger.warning(f'{self.username} post {x + 1}nd failed!')
153 |
154 |
155 | if __name__ == '__main__':
156 | try:
157 | redirect_url = get_refresh_url('http://' + os.environ.get('SOUSHUBA_HOSTNAME', 'www.soushu2035.com'))
158 | time.sleep(2)
159 | redirect_url2 = get_refresh_url(redirect_url)
160 | url = get_url(redirect_url2)
161 | logger.info(f'{url}')
162 | client = SouShuBaClient(urlparse(url).hostname,
163 | os.environ.get('SOUSHUBA_USERNAME', "USERNAME"),
164 | os.environ.get('SOUSHUBA_PASSWORD', "PASSWORD"))
165 | client.login()
166 | client.space()
167 | credit = client.credit()
168 | logger.info(f'{client.username} have {credit} coins!')
169 | except Exception as e:
170 | logger.error(e)
171 | sys.exit(1)
172 |
--------------------------------------------------------------------------------