├── LICENSE
├── LuoguPaintBoard.gpl
├── LuoguPaintBoard.irs
├── README.md
├── cookies.json
├── data
├── ImageToData.py
├── board.json
└── board2.json
├── paint.py
├── preview.html
├── runbattle.bat
├── runbattle.sh
├── runorder.bat
├── runorder.sh
├── runrand.bat
└── runrand.sh
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 ouuan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LuoguPaintBoard.gpl:
--------------------------------------------------------------------------------
1 | GIMP Palette
2 | Name LuoguPaintBoard
3 | #
4 | 255 255 255
5 | 170 170 170
6 | 85 85 85
7 | 254 211 199
8 | 255 196 206
9 | 250 172 142
10 | 255 139 131
11 | 244 67 54
12 | 233 30 99
13 | 226 102 158
14 | 156 39 176
15 | 103 58 183
16 | 63 81 181
17 | 0 70 112
18 | 5 113 151
19 | 33 150 243
20 | 0 188 212
21 | 59 229 219
22 | 151 253 220
23 | 22 115 0
24 | 55 169 60
25 | 137 230 66
26 | 215 255 7
27 | 255 246 209
28 | 248 203 140
29 | 255 235 59
30 | 255 193 7
31 | 255 152 0
32 | 255 87 34
33 | 184 63 39
34 | 121 85 72
35 |
--------------------------------------------------------------------------------
/LuoguPaintBoard.irs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ouuan/LuoguPaintBoard/31c3f9ee550c10b88dcba6435b9b5e40804cf31f/LuoguPaintBoard.irs
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 关于这个项目
2 |
3 | **本项目已停止维护**,大约明年会写一个新的。你可以继续使用,但作者**不会回答问题、修复 bug、更新功能**。
4 |
5 | 1. 这个项目是我两年前写的,当时我,非常菜 :new_moon_with_face:
6 | 2. 这个项目是同步(sync)的,所以,Cookies 数量比较多(我之前用的时候大约是超过 30~40 个)时,即使把两次绘制的间隔时间调到 0 也无法充分利用每个 Cookie(即,一个 Cookie 的冷却时间已经到了,但还没轮到它)。
7 | 3. 我想写一个异步的工具。
8 | 4. 紧接着,我想:为什么不写一个把绘制绘板的各个流程集成到一起的工具呢?
9 | 5. 我想用 Electron 写。
10 | 6. Electron 太难用了。
11 | 7. 我今年高三。
12 |
13 | 只不过我还是写了一个 [模拟洛谷冬日绘板服务器](https://github.com/ouuan/fake-luogu-paintboard-server)。
14 |
15 | ## 使用教程
16 |
17 | ### 准备环境
18 |
19 | - [python 3](https://www.python.org/downloads/)
20 |
21 | - [GIMP](https://www.gimp.org/downloads/) 或者 Photoshop
22 |
23 | - pillow:`python -m pip install pillow`
24 |
25 | - requests:`python -m pip install requests`
26 |
27 | ### 处理图像
28 |
29 | 于以下两项中选择一项:
30 |
31 | #### GIMP
32 |
33 | 1. 将 `LuoguPaintBoard.gpl` 复制到 `GIMP安装路径\share\gimp\2.0\palettes`。
34 |
35 | 2. 用 GIMP 打开原始图片。
36 |
37 | 3. 图像 → 缩放图像。
38 |
39 | 4. 图像 → 模式 → 索引,Use custom palette:LuoguPaintBoard,**不要** 勾选“从颜色表中移除无用和重复的颜色”。“递色”选项可以自己试试看哪种效果比较好。
40 |
41 | 5. 文件 → 导出为 → 选一个路径 → 选择文件类型:**bmp** → 导出。
42 |
43 | #### PS
44 |
45 | 1. 将`LuoguPaintBoard.irs` 复制到 `Photoshop安装路径\Presets\Optimized Settings`。
46 |
47 | 2. 用 Photoshop 打开原始图片。
48 |
49 | 3. 缩放图像。
50 |
51 | 4. Ctrl+Shift+Alt+S, 然后 Preset 选择 LuoguPaintBoard, Dither 自行选择。
52 |
53 | 5. 导出为 bmp 即可。
54 |
55 | ### 生成数据
56 |
57 | 请在 `data` 文件夹下生成数据。
58 |
59 | 1. `python ImageToData.py 图片路径 左上角X坐标 左上角Y坐标`,依次对每张图片运行(此脚本为增量更新)。
60 |
61 | 2. 打开 `preview.html` 检查数据生成是否有问题。
62 |
63 | ### 填写 cookies
64 |
65 | 在 `cookies.json` 里按如下格式填写:
66 |
67 | ```
68 | [
69 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx",
70 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx",
71 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx"
72 | ]
73 | ```
74 |
75 | ### 运行脚本
76 |
77 | `python paint.py `
78 |
79 | 三种模式分别为:
80 |
81 | - order: 每次画需要画的点中在 `board.json` 里最靠前的一个。
82 |
83 | - rand: 每次在需要画的点中随机选择。
84 |
85 | - battle: 优先画最近被修改过的格子。
86 |
87 | 为防止出现未被捕获的异常,建议循环运行脚本。
88 |
89 | ### 常量设置
90 |
91 | 时间的单位均为秒。
92 |
93 | - `WIDTH`:绘板宽度。
94 | - `HEIGHT`:绘板高度。
95 | - `COLORS`:颜色数量。
96 | - `GETTIMEOUT`:单次读取绘板(GET 请求)用时上限。
97 | - `POSTTIMEOUT`:单次画绘板(POST 请求)用时上限。
98 | - `GETBOARDFREQ`:读取绘板间隔时间。
99 | - `COOKIETIMEOUT`:一轮 Cookies 画完之后的等待时间。
100 | - `PAINTBOARDURL`:绘板地址。
101 | - `UA`:header 中使用的 User Agent。
102 |
--------------------------------------------------------------------------------
/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx",
3 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx",
4 | "_uid=xxxxx;__client_id=xxxxxxxxxxxxx"
5 | ]
--------------------------------------------------------------------------------
/data/ImageToData.py:
--------------------------------------------------------------------------------
1 | from PIL import Image
2 | import json
3 | import sys
4 |
5 | DataPath = "board.json"
6 | Data2Path = "board2.json"
7 |
8 | if len(sys.argv) < 4:
9 | print("ImageToData.py ")
10 | quit()
11 |
12 | try:
13 | im = Image.open(sys.argv[1])
14 | except:
15 | print('Open image ' + sys.argv[1] + ' failed.')
16 | quit()
17 |
18 | if im.mode != "P":
19 | print("Index image required. Please read README.md for more information.")
20 | quit()
21 |
22 | img_array = im.load()
23 |
24 | try:
25 | with open(DataPath, 'r') as boardjson:
26 | board = json.load(boardjson)
27 | except:
28 | board = []
29 |
30 | w, h = im.size
31 | for i in range(w):
32 | for j in range(h):
33 | board.append([i + int(sys.argv[2]), j + int(sys.argv[3]), img_array[i, j]])
34 |
35 | board = json.dumps(board)
36 | with open(DataPath, 'w+') as f:
37 | f.write(board)
38 | with open(Data2Path, 'w+') as f:
39 | f.write("var board = " + board)
40 |
41 | print("Finished.")
42 |
--------------------------------------------------------------------------------
/data/board.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/data/board2.json:
--------------------------------------------------------------------------------
1 | var board = []
--------------------------------------------------------------------------------
/paint.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import random
3 | import time
4 | import json
5 | import sys
6 |
7 | WIDTH = 800
8 | HEIGHT = 400
9 | COLORS = 32
10 | GETTIMEOUT = 5
11 | POSTTIMEOUT = 5
12 | GETBOARDFREQ = 20
13 | COOKIETIMEOUT = 10
14 | PAINTBOARDURL = "https://www.luogu.com.cn/paintBoard"
15 | UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
16 |
17 | if len(sys.argv) == 1 or not(sys.argv[1] in ['order', 'rand', 'battle']):
18 | print('python paint.py ')
19 | quit()
20 |
21 | mode = sys.argv[1]
22 |
23 | with open('cookies.json', 'r') as cookiesjson:
24 | cookies = json.load(cookiesjson)
25 |
26 | if len(cookies) == 0:
27 | print('No cookie found.')
28 | quit()
29 |
30 | with open("data/board.json", 'r') as boardjson:
31 | board = json.load(boardjson)
32 |
33 | if len(board) == 0:
34 | print('The plan is empty.')
35 | quit()
36 |
37 | cur = 0
38 | cnt = 0
39 |
40 | now = [[2 for j in range(HEIGHT)] for i in range(WIDTH)]
41 | old = [[2 for j in range(HEIGHT)] for i in range(WIDTH)]
42 | lastChange = [[0 for j in range(HEIGHT)] for i in range(WIDTH)]
43 |
44 | def getCookie():
45 | global cur, COOKIETIMEOUT
46 | cur = (cur + 1) % len(cookies)
47 | if cur == 0:
48 | time.sleep(COOKIETIMEOUT)
49 | return cookies[cur]
50 |
51 | def paint(x, y, col):
52 | global cur, PAINTBOARDURL, UA, POSTTIMEOUT
53 | data = {
54 | 'x': x,
55 | 'y': y,
56 | 'color': col
57 | }
58 | headers = {
59 | "refer": PAINTBOARDURL,
60 | "user-agent": UA,
61 | "cookie": getCookie()
62 | }
63 | try:
64 | response = requests.post(PAINTBOARDURL + "/paint", data = data, headers = headers, timeout = POSTTIMEOUT)
65 | except:
66 | print("POST error: ", cur)
67 | return -1
68 | try:
69 | if response.json()['status'] == 200:
70 | now[x][y] = col
71 | print(cur, x, y, col)
72 | return 0
73 | elif response.json()['status'] == 500:
74 | print(cur, response.json()['data'])
75 | return 1
76 | else:
77 | print(cur, response.json()['data'])
78 | return 2
79 | except:
80 | print(cur, response.text)
81 | return 3
82 |
83 | lastGet = 0
84 |
85 | def getBoard():
86 | global cnt, lastGet, HEIGHT, WIDTH, COLORS, PAINTBOARDURL, GETTIMEOUT
87 | lastGet = time.time()
88 | try:
89 | getheader = {
90 | "refer": PAINTBOARDURL,
91 | "user-agent": UA
92 | }
93 | response = requests.get(PAINTBOARDURL + "/board", headers = getheader, timeout = GETTIMEOUT)
94 | except:
95 | print("Get board failed.")
96 | else:
97 | try:
98 | cnt = cnt + 1
99 | for i in range(WIDTH):
100 | for j in range(HEIGHT):
101 | old[i][j] = now[i][j]
102 | res = response.text
103 | for i in range(WIDTH):
104 | for j in range(HEIGHT):
105 | now[i][j] = int(res[i * (HEIGHT + 1) + j], COLORS)
106 | if now[i][j] != old[i][j]:
107 | lastChange[i][j] = cnt
108 | remain = 0
109 | for i in board:
110 | if now[i[0]][i[1]] != i[2]:
111 | remain = remain + 1
112 | print('Remain: ', remain)
113 | except:
114 | print('Get response parse error.')
115 |
116 | def chooseCell():
117 | if mode == 'order':
118 | for cell in board:
119 | x = cell[0]
120 | y = cell[1]
121 | if now[x][y] != cell[2]:
122 | return cell
123 | return random.choice(board)
124 | elif mode == 'rand':
125 | todolist = []
126 | for cell in board:
127 | x = cell[0]
128 | y = cell[1]
129 | if now[x][y] != cell[2]:
130 | todolist.append(cell)
131 | if len(todolist) == 0:
132 | return random.choice(board)
133 | return random.choice(todolist)
134 | elif mode == 'battle':
135 | mostRecentChanged = -1
136 | for cell in board:
137 | x = cell[0]
138 | y = cell[1]
139 | if now[x][y] != cell[2] and lastChange[x][y] > mostRecentChanged:
140 | mostRecentChanged = lastChange[x][y]
141 | if mostRecentChanged >= 0:
142 | print('Most recent changed: ' + str(mostRecentChanged))
143 | else:
144 | return random.choice(board)
145 | todolist = []
146 | for cell in board:
147 | x = cell[0]
148 | y = cell[1]
149 | if now[x][y] != cell[2] and lastChange[x][y] == mostRecentChanged:
150 | todolist.append(cell)
151 | return random.choice(todolist)
152 |
153 | getBoard()
154 |
155 | while True:
156 | if (time.time() - lastGet > GETBOARDFREQ):
157 | getBoard()
158 | cell = chooseCell()
159 | x = cell[0]
160 | y = cell[1]
161 | col = cell[2]
162 | if now[x][y] != col:
163 | while True:
164 | if paint(x, y, col) == 0:
165 | break
166 | elif time.time() - lastGet > 1:
167 | getBoard()
168 |
--------------------------------------------------------------------------------
/preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 图片预览
6 |
7 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/runbattle.bat:
--------------------------------------------------------------------------------
1 | :loop
2 | python paint.py battle
3 | goto loop
--------------------------------------------------------------------------------
/runbattle.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | while :
3 | do
4 | python3 paint.py battle
5 | done
--------------------------------------------------------------------------------
/runorder.bat:
--------------------------------------------------------------------------------
1 | :loop
2 | python paint.py order
3 | goto loop
--------------------------------------------------------------------------------
/runorder.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | while :
3 | do
4 | python3 paint.py order
5 | done
--------------------------------------------------------------------------------
/runrand.bat:
--------------------------------------------------------------------------------
1 | :loop
2 | python paint.py rand
3 | goto loop
--------------------------------------------------------------------------------
/runrand.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | while :
3 | do
4 | python3 paint.py rand
5 | done
--------------------------------------------------------------------------------