├── LICENSE
├── README.md
├── config.json
├── main.py
└── notify.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 hwkxk
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HeytapTask
2 | 欢太商城自动签到脚本
3 |
4 |
5 | ## 已实现功能
6 |
7 | * [x] 每日签到
8 | * [x] 每日浏览商品任务
9 | * [x] 每日分享商品任务
10 | * [x] 每日信息推送任务
11 |
12 | ## 使用方式
13 | ### 1.下载本项目
14 | ### 2.打开config.json,按说明填写
15 | ```json
16 | [
17 | {
18 | "cookies": "sa_distinct_id=xxxxxxxxxxxxxxx;TOKENSID=TOKEN_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
19 | "User-Agent": "Mozilla/5.0 (Linux; Android 7.1.2; xxx Build/xxxxx; wv)",
20 | "dingtalkWebhook": "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
21 | }
22 | ]
23 | ```
24 | 注意`json`格式,最后一个要删掉逗号。建议在填写之前,使用[json校验工具](https://www.bejson.com/)进行校验。
25 |
26 | 注意:不要fork后将个人信息填写到自己仓库`config.json`文件中(不要动这个文件就没事),请下载到本地编辑,以免泄露。
27 |
28 | cookies 和 User-Agent 信息请自行在手机登录 `欢太商城` APP后使用抓包工具获取!(具体抓包方式请百度\Google)
29 |
30 | # 申明
31 |
32 | 本项目仅用于学习研究,禁止任何人用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断.
33 | 如果任何单位或个人认为该项目的脚本可能涉及侵犯其权利,则应及时通知并提供相关证明,我将在收到认证文件后删除相关脚本.
34 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "cookies": "自行抓取的登录后cookies(必填)",
4 | "User-Agent": "自行抓取自己手机APP的UA,防黑(推测)",
5 | "dingtalkWebhook": "钉钉通知 https://oapi.dingtalk.com/robot/send?access_token=xxxx(选填,不填请删除此行)",
6 | }
7 | ]
8 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2021/7/13
3 | # @Author : 丶大K丶
4 | # @Email : k@hwkxk.cn
5 |
6 | import requests,json,time,logging,traceback,os,random,notify,datetime
7 |
8 | #用户登录全局变量
9 | client = None
10 | session = None
11 | #日志基础配置
12 | # 创建一个logger
13 | logger = logging.getLogger()
14 | logger.setLevel(logging.INFO)
15 | # 创建一个handler,用于写入日志文件
16 | # w 模式会记住上次日志记录的位置
17 | fh = logging.FileHandler('./log.txt', mode='a', encoding='utf-8')
18 | fh.setFormatter(logging.Formatter("%(message)s"))
19 | logger.addHandler(fh)
20 | # 创建一个handler,输出到控制台
21 | ch = logging.StreamHandler()
22 | ch.setFormatter(logging.Formatter("[%(asctime)s]:%(levelname)s:%(message)s"))
23 | logger.addHandler(ch)
24 |
25 | #读取用户配置信息
26 | def readConfig():
27 | try:
28 | #用户配置信息
29 | with open('./config.json','r') as fp:
30 | users = json.load(fp)
31 | return users
32 | except Exception as e:
33 | print(traceback.format_exc())
34 | logging.error('账号信息获取失败错误,原因为: ' + str(e))
35 | logging.error('1.请检查是否在目录下的config.json添加了账号cookies')
36 | logging.error('2.填写之前,是否在网站验证过Json格式的正确性。')
37 |
38 | #获取个人信息,判断登录状态
39 | def get_infouser(HT_cookies,HT_UA):
40 | flag = False
41 | global session
42 | session = requests.Session()
43 | headers = {
44 | 'Host': 'www.heytap.com',
45 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
46 | 'Content-Type': 'application/x-www-form-urlencoded',
47 | 'Connection': 'keep-alive',
48 | 'User-Agent': HT_UA,
49 | 'Accept-Language': 'zh-cn',
50 | 'Accept-Encoding': 'gzip, deflate, br',
51 | 'cookie': HT_cookies
52 | }
53 | response = session.get('https://www.heytap.com/cn/oapi/users/web/member/info', headers=headers)
54 | response.encoding='utf-8'
55 | try:
56 | result = response.json()
57 | if result['code'] == 200:
58 | logger.info('=== 欢太商城任务 ===')
59 | logger.info('【登录成功】: ' + result['data']['realName'])
60 | flag = True
61 | else:
62 | logger.info('【登录失败】: ' + result['errorMessage'])
63 | except Exception as e:
64 | print(traceback.format_exc())
65 | logger.error('【登录】: 发生错误,原因为: ' + str(e))
66 | if flag:
67 | return session
68 | else:
69 | return False
70 |
71 | #任务中心列表,获取任务及任务状态
72 | def taskCenter():
73 | headers = {
74 | 'Host': 'store.oppo.com',
75 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
76 | 'Content-Type': 'application/x-www-form-urlencoded',
77 | 'Connection': 'keep-alive',
78 | 'User-Agent': HT_UserAgent,
79 | 'Accept-Language': 'zh-cn',
80 | 'Accept-Encoding': 'gzip, deflate, br',
81 | 'cookie': HT_cookies,
82 | 'referer':'https://store.oppo.com/cn/app/taskCenter/index'
83 | }
84 | res1 = client.get('https://store.oppo.com/cn/oapi/credits/web/credits/show', headers=headers)
85 | res1 = res1.json()
86 | return res1
87 |
88 |
89 | #每日签到
90 | #位置: APP → 我的 → 签到
91 | def daySign_task():
92 | try:
93 | dated = time.strftime("%Y-%m-%d")
94 | headers = {
95 | 'Host': 'store.oppo.com',
96 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
97 | 'Content-Type': 'application/x-www-form-urlencoded',
98 | 'Connection': 'keep-alive',
99 | 'User-Agent': HT_UserAgent,
100 | 'Accept-Language': 'zh-cn',
101 | 'Accept-Encoding': 'gzip, deflate, br',
102 | 'cookie': HT_cookies,
103 | 'referer':'https://store.oppo.com/cn/app/taskCenter/index'
104 | }
105 | client.headers.update(headers)
106 | res = taskCenter()
107 | res = res['data']['userReportInfoForm']['gifts']
108 | for data in res:
109 | if data['date'] == dated:
110 | qd = data
111 | if qd['today'] == False:
112 | data = "amount=" + str(qd['credits'])
113 | res1 = client.post('https://store.oppo.com/cn/oapi/credits/web/report/immediately', headers=headers,data=data)
114 | res1 = res1.json()
115 | if res1['code'] == 200:
116 | logger.info('【每日签到成功】: ' + res1['data']['message'])
117 | else:
118 | logger.info('【每日签到失败】: ' + res1)
119 | else:
120 | print(str(qd['credits']),str(qd['type']),str(qd['gift']))
121 | data = "amount=" + str(qd['credits']) + "&type=" + str(qd['type']) + "&gift=" + str(qd['gift'])
122 | res1 = client.post('https://store.oppo.com/cn/oapi/credits/web/report/immediately', headers=headers,data=data)
123 | res1 = res1.json()
124 | if res1['code'] == 200:
125 | logger.info('【每日签到成功】: ' + res1['data']['message'])
126 | else:
127 | logger.info('【每日签到失败】')
128 | time.sleep(1)
129 | except Exception as e:
130 | print(traceback.format_exc())
131 | logging.error('【每日签到】: 错误,原因为: ' + str(e))
132 |
133 |
134 |
135 | #浏览商品 10个sku +20 分
136 | #位置: APP → 我的 → 签到 → 每日任务 → 浏览商品
137 | def daily_viewgoods():
138 | try:
139 | headers = {
140 | 'clientPackage': 'com.oppo.store',
141 | 'Host': 'msec.opposhop.cn',
142 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
143 | 'Content-Type': 'application/x-www-form-urlencoded',
144 | 'Connection': 'keep-alive',
145 | 'User-Agent': 'okhttp/3.12.12.200sp1',
146 | 'Accept-Encoding': 'gzip',
147 | 'cookie': HT_cookies,
148 | }
149 | res = taskCenter()
150 | res = res['data']['everydayList']
151 | for data in res:
152 | if data['name'] == '浏览商品':
153 | qd = data
154 | if qd['completeStatus'] == 0:
155 | client.headers.update(headers)
156 | shopList = client.get('https://msec.opposhop.cn/goods/v1/SeckillRound/goods/3016?pageSize=12¤tPage=1')
157 | res = shopList.json()
158 | if res['meta']['code'] == 200:
159 | for skuinfo in res['detail']:
160 | skuid = skuinfo['skuid']
161 | print('正在浏览商品ID:', skuid)
162 | client.get('https://msec.opposhop.cn/goods/v1/info/sku?skuId='+ str(skuid), headers=headers)
163 | time.sleep(5)
164 | res2 = cashingCredits(qd['marking'],qd['type'],qd['credits'])
165 | if res2 == True:
166 | logger.info('【每日浏览商品】: ' + '任务完成!积分领取+' + str(credits))
167 | else:
168 | logger.info('【每日浏览商品】: ' + "领取积分奖励出错!")
169 | else:
170 | ogger.info('【每日浏览商品】: ' + '错误,获取商品列表失败')
171 | except Exception as e:
172 | print(traceback.format_exc())
173 | logging.error('【每日浏览任务】: 错误,原因为: ' + str(e))
174 |
175 | def daily_sharegoods():
176 | try:
177 | headers = {
178 | 'clientPackage': 'com.oppo.store',
179 | 'Host': 'msec.opposhop.cn',
180 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
181 | 'Content-Type': 'application/x-www-form-urlencoded',
182 | 'Connection': 'keep-alive',
183 | 'User-Agent': 'okhttp/3.12.12.200sp1',
184 | 'Accept-Encoding': 'gzip',
185 | 'cookie': HT_cookies,
186 | }
187 | daySignList = taskCenter()
188 | res = daySignList
189 | res = res['data']['everydayList']
190 | for data in res:
191 | if data['name'] == '分享商品到微信':
192 | qd = data
193 | if qd['completeStatus'] == 0:
194 | count = qd['readCount']
195 | endcount = qd['times']
196 | while (count <= endcount):
197 | client.get('https://msec.opposhop.cn/users/vi/creditsTask/pushTask?marking=daily_sharegoods', headers=headers)
198 | count += 1
199 | res2 = cashingCredits(qd['marking'],qd['type'],qd['credits'])
200 | if res2 == True:
201 | logger.info('【每日分享商品】: ' + '任务完成!积分领取+' + str(credits))
202 | else:
203 | logger.info('【每日分享商品】: ' + '领取积分奖励出错!')
204 | except Exception as e:
205 | print(traceback.format_exc())
206 | logging.error('【每日分享商品】: 错误,原因为: ' + str(e))
207 |
208 | def daily_viewpush():
209 | try:
210 | headers = {
211 | 'clientPackage': 'com.oppo.store',
212 | 'Host': 'msec.opposhop.cn',
213 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
214 | 'Content-Type': 'application/x-www-form-urlencoded',
215 | 'Connection': 'keep-alive',
216 | 'User-Agent': 'okhttp/3.12.12.200sp1',
217 | 'Accept-Encoding': 'gzip',
218 | 'cookie': HT_cookies,
219 | }
220 | daySignList = taskCenter()
221 | res = daySignList
222 | res = res['data']['everydayList']
223 | for data in res:
224 | if data['name'] == '点推送消息':
225 | qd = data
226 | if qd['completeStatus'] == 0:
227 | count = qd['readCount']
228 | endcount = qd['times']
229 | while (count <= endcount):
230 | client.get('https://msec.opposhop.cn/users/vi/creditsTask/pushTask?marking=daily_viewpush', headers=headers)
231 | count += 1
232 | res2 = cashingCredits(qd['marking'],qd['type'],qd['credits'])
233 | if res2 == True:
234 | logger.info('【每日推送消息】: ' + '任务完成!积分领取+' + str(credits))
235 | else:
236 | logger.info('【每日推送消息】: ' + '领取积分奖励出错!')
237 | except Exception as e:
238 | print(traceback.format_exc())
239 | logging.error('【每日推送消息】: 错误,原因为: ' + str(e))
240 |
241 |
242 | #执行完成任务领取奖励
243 | def cashingCredits(info_marking,info_type,info_credits):
244 | headers = {
245 | 'Host': 'store.oppo.com',
246 | 'clientPackage': 'com.oppo.store',
247 | 'Accept': 'application/json, text/plain, */*',
248 | 'Content-Type': 'application/x-www-form-urlencoded',
249 | 'Connection': 'keep-alive',
250 | 'User-Agent': HT_UserAgent,
251 | 'Accept-Language': 'zh-cn',
252 | 'Accept-Encoding': 'gzip, deflate, br',
253 | 'cookie': HT_cookies,
254 | 'Origin': 'https://store.oppo.com',
255 | 'X-Requested-With': 'com.oppo.store',
256 | 'referer':' https://store.oppo.com/cn/app/taskCenter/index?us=gerenzhongxin&um=hudongleyuan&uc=renwuzhongxin'
257 | }
258 |
259 | data = "marking=" + str(info_marking) + "&type=" + str(info_type) + "&amount=" + str(info_credits)
260 | res = client.post('https://store.oppo.com/cn/oapi/credits/web/credits/cashingCredits', data=data, headers=headers)
261 | res = res.json()
262 | if res['code'] == 200:
263 | return True
264 | else:
265 | return False
266 |
267 | #腾讯云函数入口
268 | def main(event, context):
269 | users = readConfig()
270 | for user in users:
271 | #清空上一个用户的日志记录
272 | open('./log.txt',mode='w',encoding='utf-8')
273 | global client
274 | global HT_cookies
275 | global HT_UserAgent
276 | HT_cookies=user['cookies']
277 | HT_UserAgent=user['User-Agent']
278 | #print(user['cookies'],user['User-Agent'])
279 | client = get_infouser(HT_cookies,HT_UserAgent)
280 |
281 | if client != False:
282 | daySign_task() #执行每日签到
283 | daily_viewgoods() #执行每日商品浏览任务
284 | daily_sharegoods() #执行每日商品分享任务
285 | daily_viewpush() #执行每日推送消息任务
286 |
287 | if ('dingtalkWebhook' in user) :
288 | notify.sendDing(user['dingtalkWebhook']) #钉钉推送日记
289 | '''
290 | if ('telegramBot' in user) :
291 | notify.sendTg(user['telegramBot']) #电报Bot推送日记
292 | if ('pushplusToken' in user) :
293 | notify.sendPushplus(user['pushplusToken']) #pushplus推送日记
294 | if('enterpriseWechat' in user) :
295 | notify.sendWechat(user['enterpriseWechat']) #企业微信推送日记
296 | if('IFTTT' in user) :
297 | notify.sendIFTTT(user['IFTTT'])
298 | if('Bark' in user) :
299 | notify.sendBark(user['Bark'])
300 | '''
301 | #主函数入口
302 | if __name__ == '__main__':
303 | main("","")
304 |
--------------------------------------------------------------------------------
/notify.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2021/2/23 06:30
3 | # @Author : srcrs
4 | # @Email : srcrs@foxmail.com
5 |
6 | import smtplib,traceback,os,requests,urllib,json
7 | from email.mime.text import MIMEText
8 |
9 | #返回要推送的通知内容
10 | #对markdown的适配要更好
11 | #增加文件关闭操作
12 | def readFile(filepath):
13 | content = ''
14 | with open(filepath, encoding='utf-8') as f:
15 | for line in f.readlines():
16 | content += line + '\n\n'
17 | return content
18 |
19 | #返回要推送的通知内容
20 | #对text的适配要更好
21 | #增加文件关闭操作
22 | def readFile_text(filepath):
23 | content = ''
24 | with open(filepath, encoding='utf-8') as f:
25 | for line in f.readlines():
26 | content += line
27 | return content
28 |
29 | #返回要推送的通知内容
30 | #对html的适配要更好
31 | #增加文件关闭操作
32 | def readFile_html(filepath):
33 | content = ''
34 | with open(filepath, "r" , encoding='utf-8') as f:
35 | for line in f.readlines():
36 | content += line + '
'
37 | return content
38 |
39 | #邮件推送api来自流星云
40 | #备用方案推送api来自BER
41 | def sendEmail(email):
42 | try:
43 | #要发送邮件内容
44 | content = readFile('./log.txt')
45 | #接收方邮箱
46 | receivers = email
47 | #邮件主题
48 | subject = 'HeytapTask每日报表'
49 | param1 = '?address=' + receivers + '&name=' + subject + '&certno=' + content
50 | param2 = '?to=' + receivers + '&title=' + subject + '&text=' + content
51 | res1 = requests.get('http://liuxingw.com/api/mail/api.php' + param1)
52 | res1.encoding = 'utf-8'
53 | res1 = res1.json()
54 | if res1['Code'] == '1':
55 | print(res1['msg'])
56 | else:
57 | #备用推送
58 | requests.get('https://email.berfen.com/api' + param2)
59 | print('email push BER')
60 | #这里不知道为什么,在很多情况下返回的不是 json,
61 | # 但在测试过程中成功率极高,因此直接输出
62 | except Exception as e:
63 | print('邮件推送异常,原因为: ' + str(e))
64 | print(traceback.format_exc())
65 |
66 | #钉钉群自定义机器人推送
67 | def sendDing(webhook):
68 | try:
69 | #要发送邮件内容
70 | content = readFile('./log.txt')
71 | data = {
72 | 'msgtype': 'markdown',
73 | 'markdown': {
74 | 'title': 'HeytapTask每日报表',
75 | 'text': content
76 | }
77 | }
78 | headers = {
79 | 'Content-Type': 'application/json;charset=utf-8'
80 | }
81 | res = requests.post(webhook,headers=headers,json=data)
82 | res.encoding = 'utf-8'
83 | res = res.json()
84 | print('dinngTalk push : ' + res['errmsg'])
85 | except Exception as e:
86 | print('钉钉机器人推送异常,原因为: ' + str(e))
87 | print(traceback.format_exc())
88 |
89 | #发送Tg通知
90 | def sendTg(tgBot):
91 | try:
92 | token = tgBot['tgToken']
93 | chat_id = tgBot['tgUserId']
94 | #发送内容
95 | content = readFile_text('./log.txt')
96 | data = {
97 | 'HeytapTask每日报表':content
98 | }
99 | content = urllib.parse.urlencode(data)
100 | #TG_BOT的token
101 | #token = os.environ.get('TG_TOKEN')
102 | #用户的ID
103 | #chat_id = os.environ.get('TG_USERID')
104 | url = f'https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}&text={content}'
105 | session = requests.Session()
106 | resp = session.post(url)
107 | print(resp)
108 | except Exception as e:
109 | print('Tg通知推送异常,原因为: ' + str(e))
110 | print(traceback.format_exc())
111 |
112 | #发送push+通知
113 | def sendPushplus(token):
114 | try:
115 | #发送内容
116 | data = {
117 | "token": token,
118 | "title": "HeytapTask每日报表",
119 | "content": readFile_html('./log.txt')
120 | }
121 | url = 'http://www.pushplus.plus/send'
122 | headers = {'Content-Type': 'application/json'}
123 | body = json.dumps(data).encode(encoding='utf-8')
124 | resp = requests.post(url, data=body, headers=headers)
125 | print(resp)
126 | except Exception as e:
127 | print('push+通知推送异常,原因为: ' + str(e))
128 | print(traceback.format_exc())
129 |
130 | #企业微信通知,普通微信可接收
131 | def sendWechat(wex):
132 | #获得access_token
133 | url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken'
134 | token_param = '?corpid=' + wex['id'] + '&corpsecret=' + wex['secret']
135 | token_data = requests.get(url + token_param)
136 | token_data.encoding = 'utf-8'
137 | token_data = token_data.json()
138 | access_token = token_data['access_token']
139 | #发送内容
140 | content = readFile_text('./log.txt')
141 | #创建要发送的消息
142 | data = {
143 | "touser": "@all",
144 | "msgtype": "text",
145 | "agentid": wex['agentld'],
146 | "text": {"content": content}
147 | }
148 | send_url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + access_token
149 | message = requests.post(send_url,json=data)
150 | message.encoding = 'utf-8'
151 | res = message.json()
152 | print('Wechat send : ' + res['errmsg'])
153 |
154 | #发送IFTTT通知
155 | def sendIFTTT(ifttt):
156 | try:
157 | content = readFile('./log.txt')
158 | body = { ifttt['subjectKey']: 'HeytapTask每日报表', ifttt['contentKey']: content }
159 | url = 'https://maker.ifttt.com/trigger/{event_name}/with/key/{key}'.format(event_name=ifttt['eventName'], key=ifttt['apiKey'])
160 | response = requests.post(url, json=body)
161 | print(response)
162 | except Exception as e:
163 | print('IFTTT通知推送异常,原因为: ' + str(e))
164 | print(traceback.format_exc())
165 |
166 | #发送Bark通知
167 | def sendBark(Bark):
168 | #发送内容
169 | Barkkey = Bark['Barkkey']
170 | Barksave = Bark['Barksave']
171 | content = readFile_text('./log.txt')
172 | data = {
173 | "title": "HeytapTask每日报表",
174 | "body": content
175 | }
176 | headers = {'Content-Type': 'application/json;charset=utf-8'}
177 | url = f'https://api.day.app/{Barkkey}/?isArchive={Barksave}'
178 | session = requests.Session()
179 | resp = session.post(url, json = data, headers = headers)
180 | state=json.loads(resp.text)
181 | print(state)
182 |
--------------------------------------------------------------------------------