HydroOJ 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hydrooj/HydroOJ.py +252 -0
- hydrooj/__init__.py +5 -0
- hydrooj-0.0.1.dist-info/METADATA +127 -0
- hydrooj-0.0.1.dist-info/RECORD +6 -0
- hydrooj-0.0.1.dist-info/WHEEL +5 -0
- hydrooj-0.0.1.dist-info/top_level.txt +1 -0
hydrooj/HydroOJ.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from selenium import webdriver
|
|
3
|
+
from selenium.webdriver.common.by import By
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LoginFailedError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProblemNotFoundError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CreateProblemFailedError(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProblemListNotFoundError(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ContestListNotFoundError(Exception):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ContestNotFoundError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UploadFileFailedError(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HydroOJ:
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
url="https://hydro.ac/",
|
|
39
|
+
webdriver_id="Edge",
|
|
40
|
+
webdriver_path=None,
|
|
41
|
+
headless=True,
|
|
42
|
+
disable_blink=True,
|
|
43
|
+
):
|
|
44
|
+
self.url = url
|
|
45
|
+
if self.url[len(url) - 1] != "/":
|
|
46
|
+
self.url += "/"
|
|
47
|
+
if webdriver_id == "Edge":
|
|
48
|
+
self.options = webdriver.EdgeOptions()
|
|
49
|
+
if headless:
|
|
50
|
+
self.options.add_argument("headless")
|
|
51
|
+
if disable_blink:
|
|
52
|
+
self.options.add_argument(
|
|
53
|
+
"--disable-blink-features=AutomationControlled"
|
|
54
|
+
)
|
|
55
|
+
if webdriver_path:
|
|
56
|
+
self.service = webdriver.EdgeService(webdriver_path)
|
|
57
|
+
else:
|
|
58
|
+
self.service = webdriver.EdgeService()
|
|
59
|
+
self.driver = webdriver.Edge(service=self.service, options=self.options)
|
|
60
|
+
elif webdriver_id == "Chrome":
|
|
61
|
+
self.options = webdriver.ChromeOptions()
|
|
62
|
+
if headless:
|
|
63
|
+
self.options.add_argument("headless")
|
|
64
|
+
if disable_blink:
|
|
65
|
+
self.options.add_argument(
|
|
66
|
+
"--disable-blink-features=AutomationControlled"
|
|
67
|
+
)
|
|
68
|
+
if webdriver_path:
|
|
69
|
+
self.service = webdriver.ChromeService(webdriver_path)
|
|
70
|
+
else:
|
|
71
|
+
self.service = webdriver.ChromeService()
|
|
72
|
+
self.driver = webdriver.Chrome(service=self.service, options=self.options)
|
|
73
|
+
elif webdriver_id == "Firefox":
|
|
74
|
+
self.options = webdriver.FirefoxOptions()
|
|
75
|
+
if headless:
|
|
76
|
+
self.options.add_argument("headless")
|
|
77
|
+
if disable_blink:
|
|
78
|
+
self.options.add_argument(
|
|
79
|
+
"--disable-blink-features=AutomationControlled"
|
|
80
|
+
)
|
|
81
|
+
if webdriver_path:
|
|
82
|
+
self.service = webdriver.FirefoxService(webdriver_path)
|
|
83
|
+
else:
|
|
84
|
+
self.service = webdriver.FirefoxService()
|
|
85
|
+
self.driver = webdriver.Firefox(service=self.service, options=self.options)
|
|
86
|
+
elif webdriver_id == "Ie":
|
|
87
|
+
self.options = webdriver.IeOptions()
|
|
88
|
+
if headless:
|
|
89
|
+
self.options.add_argument("headless")
|
|
90
|
+
if disable_blink:
|
|
91
|
+
self.options.add_argument(
|
|
92
|
+
"--disable-blink-features=AutomationControlled"
|
|
93
|
+
)
|
|
94
|
+
if webdriver_path:
|
|
95
|
+
self.service = webdriver.IeService(webdriver_path)
|
|
96
|
+
else:
|
|
97
|
+
self.service = webdriver.IeService()
|
|
98
|
+
self.driver = webdriver.Ie(service=self.service, options=self.options)
|
|
99
|
+
elif webdriver_id == "Safari":
|
|
100
|
+
self.options = webdriver.SafariOptions()
|
|
101
|
+
if headless:
|
|
102
|
+
self.options.add_argument("headless")
|
|
103
|
+
if disable_blink:
|
|
104
|
+
self.options.add_argument(
|
|
105
|
+
"--disable-blink-features=AutomationControlled"
|
|
106
|
+
)
|
|
107
|
+
if webdriver_path:
|
|
108
|
+
self.service = webdriver.SafariService(webdriver_path)
|
|
109
|
+
else:
|
|
110
|
+
self.service = webdriver.SafariService()
|
|
111
|
+
self.driver = webdriver.Safari(service=self.service, options=self.options)
|
|
112
|
+
elif webdriver_id == "WebKitGTK":
|
|
113
|
+
self.options = webdriver.WebKitGTKOptions()
|
|
114
|
+
if headless:
|
|
115
|
+
self.options.add_argument("headless")
|
|
116
|
+
if disable_blink:
|
|
117
|
+
self.options.add_argument(
|
|
118
|
+
"--disable-blink-features=AutomationControlled"
|
|
119
|
+
)
|
|
120
|
+
if webdriver_path:
|
|
121
|
+
self.service = webdriver.WebKitGTKService(webdriver_path)
|
|
122
|
+
else:
|
|
123
|
+
self.service = webdriver.WebKitGTKService()
|
|
124
|
+
self.driver = webdriver.WebKitGTK(
|
|
125
|
+
service=self.service, options=self.options
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError("Unsupported webdriver_id.")
|
|
129
|
+
|
|
130
|
+
def Logined(self):
|
|
131
|
+
self.driver.get(self.url)
|
|
132
|
+
try:
|
|
133
|
+
self.driver.find_element(By.NAME, "nav_login")
|
|
134
|
+
return False
|
|
135
|
+
except:
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def Login(self, username, password):
|
|
139
|
+
self.driver.get(self.url + "login")
|
|
140
|
+
self.driver.find_element(By.NAME, "uname").send_keys(username)
|
|
141
|
+
self.driver.find_element(By.NAME, "password").send_keys(password)
|
|
142
|
+
self.driver.find_element(By.NAME, "login_submit").click()
|
|
143
|
+
if not self.Logined():
|
|
144
|
+
raise LoginFailedError("Login failed.")
|
|
145
|
+
|
|
146
|
+
def ViewProblemList(self, page=1):
|
|
147
|
+
self.driver.get(self.url + "p?page=" + str(page))
|
|
148
|
+
try:
|
|
149
|
+
problem_list = self.driver.find_elements(
|
|
150
|
+
By.XPATH, '//div[@data-fragment-id="problem_list"]//a[@target="_blank"]'
|
|
151
|
+
)
|
|
152
|
+
if not problem_list:
|
|
153
|
+
raise ProblemListNotFoundError("Problem list not found.")
|
|
154
|
+
ret = ""
|
|
155
|
+
for i in problem_list:
|
|
156
|
+
ret += i.text + "\n"
|
|
157
|
+
return ret
|
|
158
|
+
except:
|
|
159
|
+
raise ProblemListNotFoundError("Problem list not found.")
|
|
160
|
+
|
|
161
|
+
def ViewProblem(self, problem_id):
|
|
162
|
+
self.driver.get(self.url + "p/" + problem_id)
|
|
163
|
+
try:
|
|
164
|
+
content = self.driver.execute_script("return UiContext.pdoc.content;")
|
|
165
|
+
language = self.driver.execute_script("return UserContext.viewLang;")
|
|
166
|
+
content = json.loads(content)
|
|
167
|
+
print(content[language])
|
|
168
|
+
except:
|
|
169
|
+
raise ProblemNotFoundError("Problem not found.")
|
|
170
|
+
|
|
171
|
+
def CreateProblem(self, pid, title, content, tag=[], difficulty=None, hidden=False):
|
|
172
|
+
self.driver.get(self.url + "problem/create")
|
|
173
|
+
try:
|
|
174
|
+
self.driver.find_element(By.NAME, "pid").send_keys(pid)
|
|
175
|
+
self.driver.find_element(By.NAME, "title").send_keys(title)
|
|
176
|
+
if hidden:
|
|
177
|
+
self.driver.find_element(
|
|
178
|
+
By.CSS_SELECTOR, ".checkbox:nth-child(2)"
|
|
179
|
+
).click()
|
|
180
|
+
for i in tag:
|
|
181
|
+
self.driver.find_element(By.NAME, "tag").send_keys(i)
|
|
182
|
+
if i != tag[len(tag) - 1]:
|
|
183
|
+
self.driver.find_element(By.NAME, "tag").send_keys(",")
|
|
184
|
+
if difficulty and difficulty >= 0 and difficulty <= 10:
|
|
185
|
+
self.driver.find_element(By.NAME, "difficulty").send_keys(difficulty)
|
|
186
|
+
self.driver.find_element(By.NAME, "content")
|
|
187
|
+
self.driver.execute_script(
|
|
188
|
+
"document.getElementsByName('content')[0].textContent=arguments[0]",
|
|
189
|
+
content,
|
|
190
|
+
)
|
|
191
|
+
self.driver.find_element(By.CSS_SELECTOR, ".rounded").click()
|
|
192
|
+
except:
|
|
193
|
+
raise CreateProblemFailedError("Failed to create problem.")
|
|
194
|
+
|
|
195
|
+
def ViewContestList(self, page=1):
|
|
196
|
+
self.driver.get(self.url + "contest?page=" + str(page))
|
|
197
|
+
try:
|
|
198
|
+
contest_list = self.driver.find_elements(
|
|
199
|
+
By.XPATH, '//*[contains(@class,"contest__title")]'
|
|
200
|
+
)
|
|
201
|
+
if not contest_list:
|
|
202
|
+
raise ContestListNotFoundError("Contest list not found.")
|
|
203
|
+
ret = ""
|
|
204
|
+
for i in contest_list:
|
|
205
|
+
i_a = i.find_element(By.XPATH, ".//a")
|
|
206
|
+
ret += (
|
|
207
|
+
i.text
|
|
208
|
+
+ ":"
|
|
209
|
+
+ i_a.get_attribute("href")
|
|
210
|
+
.replace("/contest/", "")
|
|
211
|
+
.replace(self.url[:-1], "")
|
|
212
|
+
+ "\n"
|
|
213
|
+
)
|
|
214
|
+
return ret
|
|
215
|
+
except:
|
|
216
|
+
raise ContestListNotFoundError("Contest list not found.")
|
|
217
|
+
|
|
218
|
+
def ViewContest(self, contest_id):
|
|
219
|
+
self.driver.get(self.url + "contest/" + contest_id)
|
|
220
|
+
try:
|
|
221
|
+
content = self.driver.execute_script("return UiContext.tdoc.content;")
|
|
222
|
+
language = self.driver.execute_script("return UserContext.viewLang;")
|
|
223
|
+
content = json.loads(content)
|
|
224
|
+
print(content[language])
|
|
225
|
+
except:
|
|
226
|
+
raise ContestNotFoundError("Contest not found.")
|
|
227
|
+
|
|
228
|
+
def UploadFileToProblem(self, problem_id, file_name, file_content):
|
|
229
|
+
self.driver.get(self.url + "p/" + problem_id + "/files")
|
|
230
|
+
try:
|
|
231
|
+
self.driver.find_element(By.NAME, "create_file").click()
|
|
232
|
+
assert self.driver.switch_to.alert.text == "文件名"
|
|
233
|
+
alert = self.driver.switch_to.alert
|
|
234
|
+
alert.send_keys(file_name)
|
|
235
|
+
alert.accept()
|
|
236
|
+
self.driver.find_element(By.NAME, "fileContent")
|
|
237
|
+
self.driver.execute_script(
|
|
238
|
+
"document.getElementsByName('fileContent')[0].textContent=arguments[0]",
|
|
239
|
+
file_content,
|
|
240
|
+
)
|
|
241
|
+
self.driver.find_element(By.CSS_SELECTOR, ".float-right > .primary").click()
|
|
242
|
+
except:
|
|
243
|
+
raise UploadFileFailedError("Failed to upload file.")
|
|
244
|
+
|
|
245
|
+
def GotoPage(self, url):
|
|
246
|
+
self.driver.get(url)
|
|
247
|
+
|
|
248
|
+
def RunJavaScript(self, script):
|
|
249
|
+
return self.driver.execute_script(script)
|
|
250
|
+
|
|
251
|
+
def Quit(self):
|
|
252
|
+
self.driver.quit()
|
hydrooj/__init__.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: HydroOJ
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Python SDK for interacting with HydroOJ via Selenium
|
|
5
|
+
Author-email: YANGRENRUIYRR <yangrenruiyrr@yeah.net>
|
|
6
|
+
Keywords: HydroOJ,selenium,online judge
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: selenium
|
|
14
|
+
|
|
15
|
+
# HydroOJ
|
|
16
|
+
|
|
17
|
+
基于Selenium开发的HydroOJ自动化操作库,支持账号登录、题目/比赛管理、附件上传等自动化操作。
|
|
18
|
+
|
|
19
|
+
## 安装方法
|
|
20
|
+
|
|
21
|
+
### PyPI在线安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install HydroOJ
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 依赖列表
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
selenium
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
支持浏览器:Edge、Chrome、Firefox、Ie、Safari、WebKitGTK
|
|
34
|
+
|
|
35
|
+
## 快速使用示例
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from HydroOJ import HydroOJ, LoginFailedError
|
|
39
|
+
|
|
40
|
+
# 初始化客户端
|
|
41
|
+
client = HydroOJ(
|
|
42
|
+
url="https://hydro.ac/",
|
|
43
|
+
webdriver_id="Chrome",
|
|
44
|
+
headless=True
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 登录账号
|
|
48
|
+
try:
|
|
49
|
+
client.Login("用户名", "密码")
|
|
50
|
+
print("登录成功")
|
|
51
|
+
except LoginFailedError:
|
|
52
|
+
print("登录失败,账号或密码错误")
|
|
53
|
+
|
|
54
|
+
# 获取第一页题目列表
|
|
55
|
+
print(client.ViewProblemList(page=1))
|
|
56
|
+
|
|
57
|
+
# 查看题目内容
|
|
58
|
+
client.ViewProblem("1001")
|
|
59
|
+
|
|
60
|
+
# 释放浏览器资源
|
|
61
|
+
client.Quit()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API 说明
|
|
65
|
+
|
|
66
|
+
### HydroOJ 初始化参数
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
HydroOJ(
|
|
70
|
+
url="https://hydro.ac/",
|
|
71
|
+
webdriver_id="Edge",
|
|
72
|
+
webdriver_path=None,
|
|
73
|
+
headless=True,
|
|
74
|
+
disable_blink=True
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- url:HydroOJ站点地址
|
|
79
|
+
- webdriver_id:浏览器标识,支持 Edge / Chrome / Firefox / Ie / Safari / WebKitGTK
|
|
80
|
+
- webdriver_path:浏览器驱动本地路径,留空自动寻找
|
|
81
|
+
- headless:是否启用无头模式,不弹出浏览器窗口
|
|
82
|
+
- disable_blink:关闭自动化检测特征,避免站点拦截
|
|
83
|
+
|
|
84
|
+
### 全部可用方法
|
|
85
|
+
|
|
86
|
+
1. Login(username, password)
|
|
87
|
+
登录站点,登录失败抛出 LoginFailedError
|
|
88
|
+
2. Logined()
|
|
89
|
+
检测当前登录状态,返回布尔值
|
|
90
|
+
3. ViewProblemList(page=1)
|
|
91
|
+
获取指定页码的题目列表文本,无数据抛出 ProblemListNotFoundError
|
|
92
|
+
4. ViewProblem(problem_id)
|
|
93
|
+
打开并打印指定ID题目详情,题目不存在抛出 ProblemNotFoundError
|
|
94
|
+
5. CreateProblem(pid, title, content, tag=[], difficulty=None, hidden=False)
|
|
95
|
+
创建新题目,创建失败抛出 CreateProblemFailedError
|
|
96
|
+
6. ViewContestList(page=1)
|
|
97
|
+
获取指定页码比赛列表,无数据抛出 ContestListNotFoundError
|
|
98
|
+
7. ViewContest(contest_id)
|
|
99
|
+
打开并打印指定ID比赛详情,比赛不存在抛出 ContestNotFoundError
|
|
100
|
+
8. UploadFileToProblem(problem_id, file_name, file_content)
|
|
101
|
+
为指定题目上传文本附件,上传失败抛出 UploadFileFailedError
|
|
102
|
+
9. GotoPage(url)
|
|
103
|
+
浏览器跳转至任意链接
|
|
104
|
+
10. RunJavaScript(script)
|
|
105
|
+
在当前页面执行JS脚本,返回执行结果
|
|
106
|
+
11. Quit()
|
|
107
|
+
关闭浏览器,释放进程资源
|
|
108
|
+
|
|
109
|
+
### 内置异常类
|
|
110
|
+
|
|
111
|
+
- LoginFailedError:登录校验失败
|
|
112
|
+
- ProblemNotFoundError:目标题目不存在
|
|
113
|
+
- CreateProblemFailedError:新建题目操作异常
|
|
114
|
+
- ProblemListNotFoundError:题目列表加载失败
|
|
115
|
+
- ContestListNotFoundError:比赛列表加载失败
|
|
116
|
+
- ContestNotFoundError:目标比赛不存在
|
|
117
|
+
- UploadFileFailedError:题目附件上传失败
|
|
118
|
+
|
|
119
|
+
## 使用注意事项
|
|
120
|
+
|
|
121
|
+
1. 高频批量操作建议添加 time.sleep() 降低访问频率,触发网站风控
|
|
122
|
+
2. 程序结束必须调用 Quit(),否则浏览器后台进程残留占用内存
|
|
123
|
+
3. 仅适配原版HydroOJ前端页面,二次修改部署的OJ可能存在元素定位失效问题
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
hydrooj/HydroOJ.py,sha256=Kau1b818Zr-AEqnJqwvZtKmKsdUoOPKRoifhd91bNPI,9704
|
|
2
|
+
hydrooj/__init__.py,sha256=iQbvB4S_hcTY8SL2Ps3igjKbcpnqc6dEt4oEkx-4EHM,108
|
|
3
|
+
hydrooj-0.0.1.dist-info/METADATA,sha256=RBcpLNTnxF1047Hkfp00xTMP7HL3pYZkgqoCKB3JL68,3740
|
|
4
|
+
hydrooj-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
hydrooj-0.0.1.dist-info/top_level.txt,sha256=h29DxaZ6m7a1vzKYl0vdWogrgpq8ovsWN_gqmeBfgm8,8
|
|
6
|
+
hydrooj-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hydrooj
|