├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── app
├── browser.py
├── config.py
├── jd.png
├── job
│ ├── __init__.py
│ ├── bean.py
│ ├── bean_app.py
│ ├── common.py
│ ├── daka.py
│ ├── daka_app.py
│ ├── data_station.py
│ ├── double_sign.py
│ ├── red_packet.py
│ └── sign_jr.py
└── main.py
├── conf
└── config.default.json
├── docs
├── browser.png
└── qq.png
├── reference
├── daka_app_main.js
├── doJobMoney.html
├── red_packet_index.js
├── 京东小金库现金红包.md
├── 京东惠赚钱签到领钢镚.md
├── 京东金融签到.js
├── 双签赢奖励.js
├── 打卡.md
├── 新版客户端京豆签到.md
└── 流量加油站.md
└── requirements.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | reference/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
91 | #############################################
92 |
93 | # IDE / Editors
94 | .idea/
95 |
96 | #############################################
97 |
98 | /conf/
99 | !/conf/config.default.json
100 |
101 | /data/
102 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 自动登录京东,打卡领钢镚,签到领京豆
2 |
3 | [](https://www.python.org)
4 |
5 |
6 | ### 使用方法:
7 |
8 | 1. 安装`Python` (3.5 或更高版本)
9 |
10 | 2. 建立虚拟运行环境(可选)
11 |
12 | 3. 下载代码
13 |
14 | 4. 安装依赖:`pip install -r requirements.txt`
15 |
16 | 5. 创建配置文件(可选)
17 |
18 | 6. 运行:`python app/main.py`
19 |
20 |
21 |
22 |
23 | ## 说明
24 |
25 | 直接登录京东较复杂,不易实现,因此采用了以下两种方式进行登录:
26 |
27 | #### 方式一:
28 |
29 | > 2017-08-13 更新:即现在的默认分支`browser`。
30 |
31 | 借助内置浏览器登录。本方式中使用 `PyQt5` 的 `WebEngine` 构建了个简易浏览器,在其中登录京东即可。
32 |
33 | 登录后浏览器窗口会自动关闭,程序会获取到 cookie,然后就可以继续签到了~
34 |
35 | 
36 |
37 |
38 | #### 方式二:
39 |
40 | > 2017-08-13 更新:目前此方式[依赖的包](https://github.com/gera2ld/qqlib)存在一些问题,暂不可用,请使用「浏览器方式」登录。
41 |
42 | 通过第三方登录的方式,登录了[绑定的 QQ 帐号](https://safe.jd.com/union/index.action),也就登录了京东。
43 |
44 | 在登录 QQ 时有时会出现需要输入验证码的情况,若是在 [iTerm2](http://www.iterm2.com/) 中运行,验证码图片会显示在终端中,直接输入即可;否则会调用系统关联应用打开验证码图片。
45 |
46 | 
47 |
48 |
49 | ## 其他
50 |
51 | ### 配置文件说明
52 |
53 | #### 帐号/密码:
54 |
55 | 可以将帐号/密码保存到配置文件中(若使用浏览器方式,可以只保存帐号),这样就不用在每次登录时手动输入了(虽然使用了 cookie 保存登录状态,但京东还是会每隔几天就让你重新登录的...)。
56 |
57 | 将默认配置文件复制为`config.json`,然后使用 [Base85](https://en.wikipedia.org/wiki/Ascii85) 方式将对应的帐号、密码编码后填入配置文件中即可,完成后是这样子的:
58 |
59 | ```json
60 | {
61 | "debug": false,
62 | "jd": {
63 | "username": "b#rBMZeeX@",
64 | "password": "aA9+EcW-iJ"
65 | }
66 | }
67 | ```
68 |
69 | (是不是比明文安全性多了一点点呢?^_^)
70 |
71 | 编码示例(Python):
72 |
73 | ```python
74 | >>> import base64
75 | >>> base64.b85encode(b'username').decode()
76 | 'b#rBMZeeX@'
77 | ```
78 |
79 | #### 我没有小白卡/我想跳过某些任务:
80 |
81 | 将想要跳过的任务填写到配置文件中的 `jobs_skip` 中即可。比如想跳过「小白卡钢镚打卡」任务,填写 `Daka` 即可:
82 |
83 | ```json
84 | "jobs_skip": ["Daka"]
85 | ```
86 |
87 | 跳过多个任务:
88 |
89 | ```json
90 | "jobs_skip": ["DataStation", "Daka"]
91 | ```
92 |
93 | 任务列表:
94 |
95 | | 任务 | 描述 |
96 | | --- | --- |
97 | | DaKa | 小白卡钢镚打卡(已下线) |
98 | | DakaApp | 京东客户端钢镚打卡 |
99 | | BeanApp | 京东客户端签到领京豆 |
100 | | DoubleSign | 客户端双签赢奖励活动(不定时开放) |
101 | | Bean | 京东会员页签到领京豆 |
102 | | SignJR | 京东金融签到领奖励 |
103 | | DataStation | 流量加油站签到领流量 |
104 | | RedPacket | 京东小金库现金红包(已下线) |
105 |
106 |
107 |
108 |
109 | ### 设置网络代理
110 |
111 | 设置环境变量 `HTTP_PROXY` / `HTTPS_PROXY` 即可。
112 |
113 |
114 |
115 |
116 | ## Example
117 |
118 | ```log
119 | 2017-03-15 10:38:48,711 root[config] INFO: 使用配置文件 "config.json".
120 | 2017-03-15 10:38:48,745 root[main] INFO: # 从文件加载 cookies 成功.
121 | 2017-03-15 10:38:48,745 jobs[daka] INFO: Job Start: 小白卡钢镚打卡
122 | 2017-03-15 10:38:49,734 jobs[daka] INFO: 登录状态: True
123 | 2017-03-15 10:38:50,642 jobs[daka] INFO: 今日已打卡: False; 打卡天数: 2
124 | 2017-03-15 10:38:50,742 jobs[daka] INFO: 打卡成功: True; Message: 打卡成功
125 | 2017-03-15 10:38:50,743 jobs[daka] INFO: Job End.
126 | 2017-03-15 10:38:50,743 jobs[daka] INFO: Job Start: 京东客户端钢镚打卡
127 | 2017-03-15 10:38:50,843 jobs[daka] INFO: 登录状态: True
128 | 2017-03-15 10:38:50,923 jobs[daka_app] INFO: 今日已打卡: False; 打卡天数: 2
129 | 2017-03-15 10:38:51,105 jobs[daka_app] INFO: 打卡成功: True; Message: 打卡成功,成功领取了0.1个钢镚!
130 | 2017-03-15 10:38:51,105 jobs[daka] INFO: Job End.
131 | 2017-03-15 10:38:51,105 jobs[daka] INFO: Job Start: 京东客户端签到领京豆
132 | 2017-03-15 10:38:51,249 jobs[daka] INFO: 登录状态: True
133 | 2017-03-15 10:38:51,344 jobs[bean_app] INFO: 今日已签到: False; 签到天数: 2
134 | 2017-03-15 10:38:51,452 jobs[bean_app] INFO: 签到成功; 获得 2 个京豆.
135 | 2017-03-15 10:38:51,452 jobs[daka] INFO: Job End.
136 | 2017-03-15 10:38:51,452 jobs[daka] INFO: Job Start: 京东会员页签到领京豆
137 | 2017-03-15 10:38:51,967 jobs[daka] INFO: 登录状态: True
138 | 2017-03-15 10:38:52,472 jobs[bean] INFO: 今日已签到: False; 现在有 1087 个京豆.
139 | 2017-03-15 10:38:52,922 jobs[bean] INFO: 签到成功,获得 20 个京豆.
140 | 2017-03-15 10:38:52,923 jobs[daka] INFO: Job End.
141 | 2017-03-15 10:38:52,923 jobs[daka] INFO: Job Start: 京东金融签到领京豆
142 | 2017-03-15 10:38:53,514 jobs[daka] INFO: 登录状态: True
143 | 2017-03-15 10:38:53,582 jobs[bean_jr] INFO: 今天已签到: False; 签到天数: 2
144 | 2017-03-15 10:38:53,681 jobs[bean_jr] INFO: 签到成功,获得 5 个京豆.
145 | 2017-03-15 10:38:53,681 jobs[daka] INFO: Job End.
146 | =================================
147 | = 任务数: 5; 失败数: 0
148 | = 全部成功 ~
149 | =================================
150 | ```
151 |
--------------------------------------------------------------------------------
/app/browser.py:
--------------------------------------------------------------------------------
1 | import locale
2 | import urllib.parse
3 | import urllib.request
4 | from http.cookies import SimpleCookie
5 | from pathlib import Path
6 |
7 | from PyQt5 import QtCore
8 | from PyQt5.QtCore import QUrl, QTimer
9 | from PyQt5.QtGui import QIcon
10 | from PyQt5.QtNetwork import QNetworkProxy
11 | from PyQt5.QtWebEngineWidgets import QWebEngineView
12 | from PyQt5.QtWidgets import QApplication
13 | from requests.cookies import RequestsCookieJar
14 |
15 | from config import config
16 |
17 | # 过滤掉一些不需要的 Qt WebEngine 日志输出
18 | # https://stackoverflow.com/questions/35894171/redirect-qdebug-output-to-file-with-pyqt5
19 | QtCore.qInstallMessageHandler(lambda *args: None)
20 |
21 | APP = None
22 |
23 |
24 | class MobileBrowser(QWebEngineView):
25 | def __init__(self):
26 | QWebEngineView.__init__(self)
27 |
28 | self.config()
29 | self.set_trigger()
30 |
31 | # WebEngine 的 cookie store 没提供获取 cookie 的方法,因此只能通过 cookieAdded 事件捕获。
32 | # 不能用 SimpleCookie, 因为 SimpleCookie 仅以 cookie name 作为 key, 不能存储 name 相同而 domain 不同
33 | # 的 cookie, 后者会覆盖前者, 导致 cookie 丢失. 比如, 京东登录成功后会返回:
34 | # pin=***; expires=Fri, 14-Apr-2017 17:29:28 GMT; domain=.jd.com; path=/
35 | # pin=***; expires=Fri, 14-Apr-2017 17:29:28 GMT; domain=.360buy.com; path=/
36 | # 等一系列同名 cookie.
37 | self.cookies = RequestsCookieJar()
38 |
39 | # 当到达 target 时自动关闭浏览器窗口
40 | self.target = None
41 |
42 | def config(self):
43 | self.page().profile().setHttpUserAgent(config.ua)
44 |
45 | proxies = urllib.request.getproxies()
46 | http_proxy = proxies.get('http') or proxies.get('https')
47 |
48 | if http_proxy:
49 | parsed = urllib.parse.urlparse(http_proxy)
50 | proxy = QNetworkProxy()
51 | proxy.setType(QNetworkProxy.HttpProxy)
52 | proxy.setHostName(parsed.hostname)
53 | proxy.setPort(parsed.port)
54 | QNetworkProxy.setApplicationProxy(proxy)
55 |
56 | # NoPersistentCookies, Both session and persistent cookies are stored in memory.
57 | # http://doc.qt.io/qt-5/qwebengineprofile.html#PersistentCookiesPolicy-enum
58 | # cookies 会同步到 python 中,无需由 WebEngine 保存。若保存了,cookieAdded 会触发两次,一次从文件(缓存)加载,
59 | # 一次页面中 Set-Cookie 指令加载,反而复杂了。
60 | self.page().profile().setPersistentCookiesPolicy(0)
61 | self.setZoomFactor(1.2) # 放大一下, 验证码看的清楚...
62 |
63 | def set_trigger(self):
64 | self.titleChanged.connect(self.title_changed)
65 | self.loadFinished.connect(self.load_finished)
66 |
67 | cookie_store = self.page().profile().cookieStore()
68 | cookie_store.cookieAdded.connect(self.cookie_added)
69 |
70 | def title_changed(self, title):
71 | self.setWindowTitle(title)
72 |
73 | def cookie_added(self, cookie):
74 | raw_form = bytes(cookie.toRawForm()).decode()
75 | simple_cookie = SimpleCookie(raw_form)
76 |
77 | for cookie in simple_cookie.values():
78 | self.cookies.set(cookie.key, cookie)
79 |
80 | def load_and_show(self, url: QUrl):
81 | self.target = url
82 | super().load(url)
83 |
84 | self.show()
85 | self.raise_() # 最前显示
86 | self.activateWindow()
87 |
88 | def load_finished(self, success):
89 | """
90 | 自动登录动作
91 | """
92 | if success:
93 | self.apply_actions(self.url().host())
94 |
95 | def apply_actions(self, host):
96 | """
97 | 根据地址完成自动填充/登录/关闭窗口动作
98 | """
99 | code = None
100 |
101 | if host == 'plogin.m.jd.com':
102 | code = """
103 | $('#username').val('{username}');
104 | $('#password').val('{password}');
105 |
106 | if ({auto_submit}) {{
107 | $('#loginBtn').addClass('btn-active');
108 | $('#loginBtn').click();
109 | }} else {{
110 | $('#username').focus();
111 | }}
112 | """
113 |
114 | elif host == 'passport.jd.com':
115 | code = """
116 | $(document).scrollLeft($(document).width()); // 移动到页面最右侧
117 | $('.login-tab-r').click();
118 | $('#loginname').val('{username}');
119 | $('#nloginpwd').val('{password}');
120 |
121 | if ({auto_submit}) {{
122 | // 等待页面相关组件加载完成,如 jdSlide 等
123 | setTimeout(function() {{
124 | $('#loginsubmit').click();
125 | }}, 1000);
126 | }}
127 | """
128 |
129 | if code:
130 | code = code.format_map(config.jd)
131 | self.page().runJavaScript(code)
132 |
133 | if host == self.target.host():
134 | self.setWindowTitle('👌 登录成功,窗口即将关闭...')
135 |
136 | timer = QTimer(self)
137 | timer.timeout.connect(self.close)
138 | timer.start(1000)
139 |
140 |
141 | def get_cookies(url):
142 | starting_up = QApplication.startingUp()
143 |
144 | if starting_up:
145 | global APP
146 | APP = QApplication([])
147 | icon_path = str(Path(__file__, '../jd.png').resolve())
148 | APP.setWindowIcon(QIcon(icon_path))
149 |
150 | the_browser = MobileBrowser()
151 | the_browser.load_and_show(QUrl(url))
152 |
153 | if starting_up:
154 | # On Unix/Linux Qt is configured to use the system locale settings by default. This can cause a conflict when using POSIX functions.
155 | # http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings
156 | # 重设 locale, 否则某些依赖 locale 的代码可能产生错误, 如 Requests 中解析 cookie 时间的代码.
157 | locale.setlocale(locale.LC_TIME, 'C')
158 |
159 | APP.exec()
160 |
161 | return the_browser.cookies
162 |
163 |
164 | def main():
165 | test_url = 'https://m.jd.com'
166 | cookies = get_cookies(test_url)
167 |
168 |
169 | if __name__ == '__main__':
170 | main()
171 |
--------------------------------------------------------------------------------
/app/config.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import json
3 | import logging
4 | import sys
5 | from base64 import b85decode
6 | from pathlib import Path
7 |
8 | log_format = '%(asctime)s %(name)s[%(module)s] %(levelname)s: %(message)s'
9 | logging.basicConfig(format=log_format, level=logging.INFO)
10 |
11 |
12 | class Config:
13 | def __init__(self):
14 | self.debug = False
15 | self.log_format = log_format
16 | self.ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0'
17 |
18 | self.jd = {
19 | 'username': '',
20 | 'password': ''
21 | }
22 |
23 | self.jobs_skip = []
24 |
25 | @classmethod
26 | def load(cls, d):
27 | the_config = Config()
28 |
29 | the_config.debug = d.get('debug', False)
30 |
31 | try:
32 | the_config.jd = {
33 | 'username': b85decode(d['jd']['username']).decode(),
34 | 'password': b85decode(d['jd']['password']).decode()
35 | }
36 | except Exception as e:
37 | logging.error('获取京东帐号出错: ' + repr(e))
38 |
39 | if not (the_config.jd['username'] and the_config.jd['password']):
40 | # 有些页面操作还是有用的, 比如移动焦点到输入框... 滚动页面到登录表单位置等
41 | # 所以不禁止 browser 的 auto_login 动作了, 但两项都有才自动提交, 否则只进行自动填充动作
42 | the_config.jd['auto_submit'] = 0 # used in js
43 | logging.info('用户名/密码未找到, 自动登录功能将不可用.')
44 |
45 | else:
46 | the_config.jd['auto_submit'] = 1
47 |
48 | the_config.jobs_skip = d.get('jobs_skip', [])
49 |
50 | return the_config
51 |
52 |
53 | def load_config():
54 | parser = argparse.ArgumentParser()
55 | parser.add_argument('-c', '--config', help='config file name')
56 | args = parser.parse_args()
57 |
58 | config_name = args.config or 'config.json'
59 | logging.info('使用配置文件 "{}".'.format(config_name))
60 |
61 | config_file = Path(__file__).parent.joinpath('../conf/', config_name)
62 |
63 | if not config_file.exists():
64 | config_name = 'config.default.json'
65 | logging.warning('配置文件不存在, 使用默认配置文件 "{}".'.format(config_name))
66 | config_file = config_file.parent.joinpath(config_name)
67 |
68 | try:
69 | # 略坑, Path.resolve() 在 3.5 和 3.6 上表现不一致... 若文件不存在 3.5 直接抛异常, 而 3.6
70 | # 只有 Path.resolve(strict=True) 才抛, 但 strict 默认为 False.
71 | # 感觉 3.6 的更合理些...
72 | config_file = config_file.resolve()
73 | config_dict = json.loads(config_file.read_text())
74 | except Exception as e:
75 | sys.exit('# 错误: 配置文件载入失败: {}'.format(e))
76 |
77 | the_config = Config.load(config_dict)
78 |
79 | return the_config
80 |
81 |
82 | config = load_config()
83 |
--------------------------------------------------------------------------------
/app/jd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaoZ/JD-Coin/287af555c530d68b095018416b67c7d2fb1bad73/app/jd.png
--------------------------------------------------------------------------------
/app/job/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger('jobs')
4 |
5 | from config import config
6 | from .bean import Bean
7 | from .bean_app import BeanApp
8 | from .sign_jr import SignJR
9 | from .daka_app import DakaApp
10 | from .double_sign import DoubleSign
11 | from .data_station import DataStation
12 |
13 | __all__ = ['jobs_all', 'logger']
14 |
15 | jobs_mobile = [DakaApp, BeanApp, DataStation]
16 | jobs_web = [Bean, SignJR]
17 | jobs_all = jobs_mobile + jobs_web + [DoubleSign]
18 |
19 |
20 | def set_logger():
21 | logger.propagate = False
22 | logger.setLevel(logging.INFO)
23 | handler = logging.StreamHandler()
24 | formatter = logging.Formatter(config.log_format)
25 | handler.setFormatter(formatter)
26 | logger.addHandler(handler)
27 |
28 |
29 | set_logger()
30 |
--------------------------------------------------------------------------------
/app/job/bean.py:
--------------------------------------------------------------------------------
1 | from pyquery import PyQuery
2 | from requests import HTTPError, ReadTimeout
3 |
4 | from .daka import Daka
5 |
6 |
7 | class Bean(Daka):
8 | job_name = '京东会员页签到领京豆'
9 |
10 | index_url = 'https://vip.jd.com'
11 | info_url = 'https://vip.jd.com/member/getUserInfo.html'
12 | sign_url = 'https://vip.jd.com/sign/index'
13 | test_url = 'https://vip.jd.com/member/myJingBean/index.html'
14 | login_url = test_url
15 |
16 | def __init__(self, session):
17 | super().__init__(session)
18 | self.page_data = ''
19 |
20 | def is_signed(self):
21 | page_data = self._get_page_data()
22 | signed = '已签到' in PyQuery(page_data)('.sign-in').text()
23 |
24 | detail = self.session.get(self.info_url).json()
25 |
26 | if detail['success']:
27 | user_info = detail['result']['userInfo']
28 | beans_count = user_info['userJingBeanNum']
29 | self.logger.info('今日已签到: {}; 现在有 {} 个京豆.'.format(signed, beans_count))
30 |
31 | else:
32 | self.logger.info('今日已签到: {}'.format(signed))
33 |
34 | return signed
35 |
36 | def sign(self):
37 | try:
38 | r = self.session.get(self.sign_url, timeout=10)
39 | r.raise_for_status()
40 |
41 | sign_message = PyQuery(r.text)('.day-info.active .title').text()
42 | self.logger.info(sign_message)
43 | return True # 似乎没有签到失败的情况,暂且认为签到成功
44 |
45 | except (HTTPError, ReadTimeout) as e:
46 | self.logger.error('签到失败: {}'.format(e))
47 | return False
48 |
49 | def _get_page_data(self):
50 | if not self.page_data:
51 | self.page_data = self.session.get(self.index_url).text
52 |
53 | return self.page_data
54 |
--------------------------------------------------------------------------------
/app/job/bean_app.py:
--------------------------------------------------------------------------------
1 | from .common import RequestError
2 | from .daka import Daka
3 |
4 |
5 | class BeanApp(Daka):
6 | """
7 | 京东客户端签到领京豆. 由于是 App (Mobile) 端页面, 登录方式与领钢镚的相同, 不同于电脑端领京豆.
8 | """
9 | job_name = '京东客户端签到领京豆'
10 |
11 | index_url = 'https://bean.m.jd.com'
12 | info_url = 'https://api.m.jd.com/client.action?functionId=findBeanIndex'
13 | sign_url = 'https://api.m.jd.com/client.action?functionId=signBeanIndex'
14 | test_url = 'https://home.m.jd.com'
15 |
16 | client_info = {
17 | 'client': 'ld',
18 | 'clientVersion': '1.0.0'
19 | }
20 |
21 | def is_signed(self):
22 | try:
23 | data = self.fetch_data(self.info_url)
24 | except RequestError as e:
25 | self.logger.error('签到信息获取失败: {}'.format(e.message))
26 | return False
27 |
28 | # 根据测试, 1 表示已签到, 2 表示未签到, 3 表示未登录
29 | signed = (data['status'] == '1')
30 | sign_days = int(data['continuousDays'])
31 | beans_count = int(data['totalUserBean'])
32 |
33 | self.logger.info('今日已签到: {}; 签到天数: {}; 现有京豆: {}'.format(signed, sign_days, beans_count))
34 | return signed
35 |
36 | def sign(self):
37 | try:
38 | data = self.fetch_data(self.sign_url)
39 | except RequestError as e:
40 | self.logger.error('签到失败: {}'.format(e.message))
41 | return False
42 |
43 | sign_success = (data['status'] == '1')
44 |
45 | if sign_success:
46 | bean_count = data['dailyAward']['beanAward']['beanCount']
47 | message = '获得京豆 {} 个.'.format(bean_count)
48 | else:
49 | message = data['dailyAward']['title']
50 |
51 | self.logger.info('签到成功: {}; Message: {}'.format(sign_success, message))
52 |
53 | return sign_success
54 |
55 | def fetch_data(self, url, payload=None):
56 | payload = {**payload, **self.client_info} if payload else self.client_info
57 |
58 | r = self.session.get(url, params=payload)
59 |
60 | try:
61 | as_json = r.json()
62 | except ValueError:
63 | raise RequestError('unexpected response: url: {}; http code: {}'.format(url, r.status_code), response=r)
64 |
65 | if as_json['code'] != '0' or 'errorCode' in as_json or 'errorMessage' in as_json:
66 | error_msg = as_json.get('echo') or as_json.get('errorMessage') or str(as_json)
67 | error_code = as_json.get('errorCode') or as_json.get('code')
68 | raise RequestError(error_msg, code=error_code, response=r)
69 |
70 | # 请求成功
71 | return as_json['data']
72 |
--------------------------------------------------------------------------------
/app/job/common.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from requests import Response
4 |
5 |
6 | class RequestError(Exception):
7 | def __init__(self, message, code: str = None, response: Response = None):
8 | self.message = message
9 | self.code = code
10 | self.response = response
11 |
12 |
13 | def find_value(pattern, string, default=None, flags=0):
14 | """
15 | 根据正则表达式在字符串中搜索值,若未找到,返回 default
16 | """
17 | m = re.search(pattern, string, flags)
18 |
19 | if m:
20 | return m.group(1)
21 | else:
22 | return default
23 |
--------------------------------------------------------------------------------
/app/job/daka.py:
--------------------------------------------------------------------------------
1 | import traceback
2 |
3 | from requests import Session
4 |
5 | import browser
6 | import job
7 | from .common import find_value, RequestError
8 |
9 |
10 | class Daka:
11 | job_name = '小白卡钢镚打卡'
12 |
13 | index_url = 'https://bk.jd.com/m/channel/login/daka.html'
14 | login_url = 'https://home.m.jd.com'
15 | sign_url = 'https://bk.jd.com/m/channel/login/clock.html'
16 | test_url = index_url
17 | job_gb_url = 'https://bk.jd.com/m/channel/login/recDakaGb.html'
18 | logger = job.logger
19 |
20 | def __init__(self, session: Session):
21 | self.session = session
22 | self.job_success = False
23 |
24 | def run(self):
25 | self.logger.info('Job Start: {}'.format(self.job_name))
26 |
27 | is_login = self.is_login()
28 | self.logger.info('登录状态: {}'.format(is_login))
29 |
30 | if not is_login:
31 | self.logger.info('进行登录...')
32 | try:
33 | self.login()
34 | is_login = True
35 | self.logger.info('登录成功')
36 | except Exception as e:
37 | self.logger.error('登录失败: {}'.format(repr(e)))
38 |
39 | if is_login:
40 | if self.is_signed():
41 | self.job_success = True
42 | else:
43 | self.job_success = self.sign()
44 |
45 | self.logger.info('Job End.')
46 |
47 | def is_login(self):
48 | r = self.session.get(self.test_url, allow_redirects=False)
49 |
50 | if r.is_redirect and '/login' in r.headers['Location']:
51 | return False
52 | else:
53 | return True
54 |
55 | def login(self):
56 | cookies = browser.get_cookies(self.login_url)
57 | self.session.cookies.update(cookies)
58 |
59 | def is_signed(self):
60 | r = self.session.get(self.index_url)
61 | signed = False
62 |
63 | if r.ok:
64 | sign_pattern = r'dakaed:\s*(\w+)'
65 | days_pattern = r'dakaNumber:\s*(\d+)'
66 |
67 | try:
68 | signed = ('true' == find_value(sign_pattern, r.text))
69 | sign_days = int(find_value(days_pattern, r.text))
70 | self.logger.info('今日已打卡: {}; 打卡天数: {}'.format(signed, sign_days))
71 |
72 | except Exception as e:
73 | self.logger.error('返回数据结构可能有变化, 获取打卡数据失败: {}'.format(e))
74 | traceback.print_exc()
75 |
76 | return signed
77 |
78 | def sign(self):
79 | try:
80 | data = self.fetch_data(self.sign_url)
81 | self.logger.info('打卡成功: ' + data['resultMessage'])
82 | return True
83 |
84 | except RequestError as e:
85 | if e.code == '0003':
86 | # 已打卡 7 次, 需要先去 "任务" 里完成一个领钢镚任务...
87 | self.logger.info('已打卡 7 次, 去完成领钢镚任务...')
88 | pick_success = self.pick_gb()
89 |
90 | if pick_success:
91 | # 钢镚领取成功, 重新开始打卡任务
92 | return self.sign()
93 | else:
94 | e.message = '钢镚领取任务未成功完成.'
95 |
96 | self.logger.error('打卡失败: ' + e.message)
97 | return False
98 |
99 | def pick_gb(self):
100 | # 任务列表在 https://bk.jd.com/m/money/doJobMoney.html 中看
101 | # 领钢镚的任务的 id 是 82
102 | try:
103 | data = self.fetch_data(self.job_gb_url)
104 | self.logger.info('钢镚领取成功: {}'.format(data['resultMessage']))
105 | return True
106 |
107 | except RequestError as e:
108 | self.logger.error('领钢镚 -> 钢镚领取失败: {}'.format(e.message))
109 | return False
110 |
111 | def fetch_data(self, url, payload=None):
112 | r = self.session.get(url, params=payload)
113 |
114 | try:
115 | as_json = r.json()
116 | except ValueError:
117 | raise RequestError('unexpected response: url: {}; http code: {}'.format(url, r.status_code), response=r)
118 |
119 | if as_json['success']:
120 | # 请求成功
121 | return as_json
122 |
123 | else:
124 | error_msg = as_json.get('resultMessage') or str(as_json)
125 | error_code = as_json.get('resultCode')
126 | raise RequestError(error_msg, error_code)
127 |
--------------------------------------------------------------------------------
/app/job/daka_app.py:
--------------------------------------------------------------------------------
1 | import traceback
2 |
3 | from .daka import Daka
4 |
5 |
6 | class DakaApp(Daka):
7 | job_name = '京东客户端钢镚打卡'
8 |
9 | index_url = 'https://m.jr.jd.com/spe/qyy/main/index.html?userType=41'
10 | sign_url = 'https://ms.jr.jd.com/gw/generic/base/h5/m/baseSignInEncrypt'
11 | test_url = 'https://ms.jr.jd.com/gw/generic/base/h5/m/baseGetMessByGroupType'
12 |
13 | def __init__(self, session):
14 | super().__init__(session)
15 | self.sign_data = {}
16 |
17 | def get_sign_data(self):
18 | payload = {
19 | 'reqData': '{"clientType":"outH5","userType":41,"groupType":154}',
20 | 'sid': self.session.cookies.get('sid'),
21 | 'source': 'jrm'
22 | }
23 |
24 | sign_data = {}
25 |
26 | try:
27 | # 参见 daka_app_min.js -> h.getSign, 第 1825 行开始
28 | r = self.session.post(self.test_url, data=payload)
29 | as_json = r.json()
30 |
31 | if 'resultData' in as_json:
32 | sign_data = r.json()['resultData']['53']
33 |
34 | else:
35 | error_msg = as_json.get('resultMsg') or as_json.get('resultMessage')
36 | self.logger.error('获取打卡数据失败: {}'.format(error_msg))
37 |
38 | except Exception as e:
39 | self.logger.error('获取打卡数据失败: {}'.format(e))
40 |
41 | return sign_data
42 |
43 | def is_login(self):
44 | sign_data = self.get_sign_data()
45 |
46 | # 参见 daka_app_min.js, 第 1835 行
47 | is_login = 'suitable' in sign_data
48 |
49 | if is_login:
50 | # 用户已登录, sign_data 有效, 存储下
51 | self.sign_data = sign_data
52 |
53 | return is_login
54 |
55 | def is_signed(self):
56 | sign_data = self.sign_data or self.get_sign_data()
57 |
58 | signed = False
59 |
60 | try:
61 | signed = sign_data['signInStatus'] == 1
62 | self.logger.info('今日已打卡: {}'.format(signed))
63 |
64 | except Exception as e:
65 | self.logger.error('返回数据结构可能有变化, 获取打卡数据失败: {}'.format(e))
66 | traceback.print_exc()
67 |
68 | return signed
69 |
70 | def sign(self):
71 | payload = {
72 | 'reqData': '{}',
73 | 'sid': self.session.cookies.get('sid'),
74 | 'source': 'jrm'
75 | }
76 |
77 | r = self.session.post(self.sign_url, data=payload)
78 | as_json = r.json()
79 |
80 | if 'resultData' in as_json:
81 | result_data = as_json['resultData']
82 | # statusCode 14 似乎是表示延期到帐的意思, 如: 签到成功,钢镚将于15个工作日内发放到账
83 | sign_success = result_data['isSuccess'] or result_data['statusCode'] == 14
84 | message = result_data['showMsg']
85 |
86 | # 参见 daka_app_min.js, 第 1893 行
87 | continuity_days = result_data['continuityDays']
88 |
89 | if continuity_days > 1:
90 | message += '; 签到天数: {}'.format(continuity_days)
91 |
92 | else:
93 | sign_success = False
94 | message = as_json.get('resultMsg') or as_json.get('resultMessage')
95 |
96 | self.logger.info('打卡成功: {}; Message: {}'.format(sign_success, message))
97 |
98 | return sign_success
99 |
--------------------------------------------------------------------------------
/app/job/data_station.py:
--------------------------------------------------------------------------------
1 | from .daka import Daka
2 |
3 |
4 | class DataStation(Daka):
5 | """
6 | 流量加油站
7 | """
8 | job_name = '流量加油站签到领流量'
9 |
10 | index_url = 'https://fbank.m.jd.com'
11 | info_url = 'https://fbank.m.jd.com/api.json?functionId=getFbankIndex'
12 | sign_url = 'https://fbank.m.jd.com/api.json?functionId=fBankSign'
13 | test_url = 'https://home.m.jd.com'
14 |
15 | def is_signed(self):
16 | response = self.session.get(self.info_url).json()
17 | signed = False
18 |
19 | if response['success']:
20 | sign_info = response['signInfo']
21 | signed = (sign_info['signCode'] != '0')
22 | message = sign_info['message']
23 |
24 | self.logger.info('今日已签到: {}; Message: {}.'.format(signed, message))
25 |
26 | else:
27 | message = response.get('message') or response.get('errorMessage')
28 | self.logger.error('签到信息获取失败: {}'.format(message))
29 |
30 | return signed
31 |
32 | def sign(self):
33 | response = self.session.get(self.sign_url).json()
34 |
35 | if response['success']:
36 | sign_success = ('errorCode' not in response)
37 | message = response.get('errorMessage') or response.get('message')
38 | self.logger.info('签到成功: {}; Message: {}.'.format(sign_success, message))
39 | return sign_success
40 |
41 | else:
42 | message = response.get('message') or response.get('errorMessage')
43 | self.logger.error('签到失败: {}'.format(message))
44 | return False
45 |
--------------------------------------------------------------------------------
/app/job/double_sign.py:
--------------------------------------------------------------------------------
1 | import traceback
2 |
3 | from pyquery import PyQuery
4 |
5 | from .common import RequestError
6 | from .daka import Daka
7 |
8 |
9 | class DoubleSign(Daka):
10 | job_name = '双签赢奖励'
11 |
12 | index_url = 'https://ljd.m.jd.com/countersign/index.action'
13 | sign_url = 'https://ljd.m.jd.com/countersign/receiveAward.json'
14 | test_url = index_url
15 |
16 | def is_signed(self):
17 | signed = False
18 |
19 | try:
20 | signed = PyQuery(self.page_data())('#awardFlag').val() == '2'
21 | self.logger.info('今日已双签: {}'.format(signed))
22 |
23 | except Exception as e:
24 | self.logger.error('返回数据结构可能有变化, 获取双签数据失败: {}'.format(e))
25 | traceback.print_exc()
26 |
27 | return signed
28 |
29 | def sign(self):
30 | # 参见 https://ljd.m.jd.com/js/countersign/countersign.js
31 |
32 | sign_success = True
33 | message = ''
34 |
35 | document = PyQuery(self.page_data())
36 |
37 | jd_signed = document('#jdHasSign').val() == 'true'
38 | jr_signed = document('#jrHasSign').val() == 'true'
39 |
40 | if not (jd_signed and jr_signed):
41 | sign_success = False
42 | message = '完成双签才可领取礼包'
43 |
44 | else:
45 | try:
46 | res = self.do_sign()
47 | except RequestError as e:
48 | self.logger.error('双签失败: {}'.format(e.message))
49 | return False
50 |
51 | if res['code'] == '0':
52 | award_data = res.get('data')
53 |
54 | if not award_data:
55 | message = '运气不佳,领到一个空空的礼包'
56 |
57 | else:
58 | award = award_data[0]
59 | sign_success = True
60 | message = '领到 {} 个{}'.format(award['awardCount'], award['awardName'])
61 |
62 | else:
63 | # 活动不定时开启,将活动时间未开始/已结束等情况都视作签到成功
64 |
65 | if res['code'] == 'DS102':
66 | message = '来早了,活动还未开始'
67 | elif res['code'] == 'DS103':
68 | message = '来晚了,活动已经结束了'
69 | elif res['code'] == 'DS104':
70 | message = '运气不佳,领到一个空空的礼包'
71 | elif res['code'] == 'DS106':
72 | sign_success = False
73 | message = '完成双签才可领取礼包'
74 | else:
75 | sign_success = False
76 | message = '未知错误,Code={}'.format(res['code'])
77 |
78 | self.logger.info('双签成功: {}; Message: {}'.format(sign_success, message))
79 |
80 | return sign_success
81 |
82 | def do_sign(self):
83 | r = self.session.post(self.sign_url)
84 |
85 | try:
86 | as_json = r.json()
87 | except ValueError:
88 | raise RequestError('unexpected response: url: {}; http code: {}'.format(self.sign_url, r.status_code), response=r)
89 |
90 | if 'res' in as_json and 'code' in as_json['res']:
91 | # 请求成功
92 | return as_json['res']
93 |
94 | else:
95 | error_msg = as_json.get('message') or str(as_json)
96 | error_code = as_json.get('businessCode') or as_json.get('code')
97 | raise RequestError(error_msg, error_code)
98 |
99 | def page_data(self):
100 | if not hasattr(self, '_page_data'):
101 | self._page_data = self.session.get(self.index_url).text
102 |
103 | return self._page_data
104 |
--------------------------------------------------------------------------------
/app/job/red_packet.py:
--------------------------------------------------------------------------------
1 | from .daka import Daka
2 |
3 |
4 | class RedPacket(Daka):
5 | job_name = '京东小金库现金红包'
6 |
7 | index_url = 'https://m.jr.jd.com/udc-active/2017/618RedPacket/html/index.html'
8 | sign_url = 'https://ms.jr.jd.com/gw/generic/activity/h5/m/receiveZhiBoXjkRedPacket'
9 | test_url = 'https://home.m.jd.com'
10 |
11 | def is_signed(self):
12 | # 这个任务在领取前不能知道今天是否领取过, 因此返回 None 以便任务能够执行.
13 | return None
14 |
15 | def sign(self):
16 | # 参见 red_packet_index.js
17 |
18 | payload = {
19 | 'reqData': '{"activityCode":"ying_yong_bao_618"}',
20 | 'sid': self.session.cookies.get('sid')
21 | }
22 |
23 | response = self.session.post(self.sign_url, data=payload).json()
24 |
25 | if response['resultCode'] == 0:
26 | sign_success = response['resultData']['success']
27 |
28 | if sign_success:
29 | self.logger.info('领取成功, 获得 {} 元.'.format(response['resultData']['data']))
30 |
31 | else:
32 | message = response['resultData'].get('msg') or response.get('resultMsg')
33 | self.logger.info('领取结果: {}'.format(message))
34 |
35 | if response['resultData'].get('code') == '03':
36 | # 当 code 为 03 时, 表示今天已领过了, 因为领取前无法知道是否领过, 此处也当做任务成功返回
37 | sign_success = True
38 |
39 | return sign_success
40 |
41 | else:
42 | message = response.get('resultMsg')
43 | self.logger.error('领取失败: {}'.format(message))
44 | return False
45 |
--------------------------------------------------------------------------------
/app/job/sign_jr.py:
--------------------------------------------------------------------------------
1 | from .bean import Bean
2 |
3 |
4 | class SignJR(Bean):
5 | job_name = '京东金融签到领奖励'
6 |
7 | index_url = 'https://vip.jr.jd.com'
8 | info_url = 'https://vip.jr.jd.com/newSign/querySignRecord'
9 | sign_url = 'https://vip.jr.jd.com/newSign/doSign'
10 | test_url = 'https://vip.jr.jd.com/coupon/myIntegralDetail'
11 |
12 | def is_signed(self):
13 | r = self.session.post(self.info_url)
14 | signed = False
15 |
16 | if r.ok:
17 | data = r.json()
18 | signed = data['isFlag']
19 | sign_days = data['signContinuity']
20 | self.logger.info('今日已签到: {}; 签到天数: {}; 现有钢镚: {}'.format(signed, sign_days, data['accountBalance']))
21 |
22 | return signed
23 |
24 | def sign(self):
25 | headers = {'Referer': self.index_url}
26 | response = self.session.post(self.sign_url, headers=headers).json()
27 |
28 | sign_success = response['signSuccess']
29 | sign_data = response['signResData']
30 |
31 | if sign_success and sign_data:
32 | unit = ['', '京豆', '金币', '钢镚'][sign_data['rewardType']]
33 | count = sign_data['thisAmount'] / 100 if sign_data['rewardType'] == 3 else sign_data['thisAmount']
34 | self.logger.info('签到成功, 获得 {} 个{}.'.format(count, unit))
35 |
36 | else:
37 | self.logger.error('签到失败: Code={}'.format(response['resBusiCode']))
38 |
39 | return sign_success
40 |
--------------------------------------------------------------------------------
/app/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import pickle
4 | import traceback
5 | from pathlib import Path
6 |
7 | import requests
8 |
9 | from config import config
10 | from job import jobs_all
11 |
12 |
13 | def main():
14 | session = make_session()
15 |
16 | jobs = [job for job in jobs_all if job.__name__ not in config.jobs_skip]
17 | jobs_failed = []
18 |
19 | for job_class in jobs:
20 | job = job_class(session)
21 |
22 | try:
23 | job.run()
24 | except Exception as e:
25 | logging.error('# 任务运行出错: ' + repr(e))
26 | traceback.print_exc()
27 |
28 | if not job.job_success:
29 | jobs_failed.append(job.job_name)
30 |
31 | print('=================================')
32 | print('= 任务数: {}; 失败数: {}'.format(len(jobs), len(jobs_failed)))
33 |
34 | if jobs_failed:
35 | print('= 失败的任务: {}'.format(jobs_failed))
36 | else:
37 | print('= 全部成功 ~')
38 |
39 | print('=================================')
40 |
41 | save_session(session)
42 |
43 |
44 | def make_session() -> requests.Session:
45 | session = requests.Session()
46 |
47 | session.headers.update({
48 | 'User-Agent': config.ua
49 | })
50 |
51 | data_file = Path(__file__).parent.joinpath('../data/cookies')
52 |
53 | if data_file.exists():
54 | try:
55 | bytes = data_file.read_bytes()
56 | cookies = pickle.loads(bytes)
57 | session.cookies = cookies
58 | logging.info('# 从文件加载 cookies 成功.')
59 | except Exception as e:
60 | logging.info('# 未能成功载入 cookies, 从头开始~')
61 |
62 | return session
63 |
64 |
65 | def save_session(session):
66 | data = pickle.dumps(session.cookies)
67 |
68 | data_dir = Path(__file__).parent.joinpath('../data/')
69 | data_dir.mkdir(exist_ok=True)
70 | data_file = data_dir.joinpath('cookies')
71 | data_file.write_bytes(data)
72 |
73 |
74 | def proxy_patch():
75 | """
76 | Requests 似乎不能使用系统的证书系统, 方便起见, 不验证 HTTPS 证书, 便于使用代理工具进行网络调试...
77 | http://docs.python-requests.org/en/master/user/advanced/#ca-certificates
78 | """
79 | import warnings
80 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
81 |
82 | class XSession(requests.Session):
83 | def __init__(self):
84 | super().__init__()
85 | self.verify = False
86 |
87 | requests.Session = XSession
88 | warnings.simplefilter('ignore', InsecureRequestWarning)
89 |
90 |
91 | if __name__ == '__main__':
92 | if config.debug and os.getenv('HTTPS_PROXY'):
93 | proxy_patch()
94 |
95 | main()
96 |
--------------------------------------------------------------------------------
/conf/config.default.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "jd": {
4 | "username": "",
5 | "password": ""
6 | },
7 | "jobs_skip": ["DataStation"]
8 | }
9 |
--------------------------------------------------------------------------------
/docs/browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaoZ/JD-Coin/287af555c530d68b095018416b67c7d2fb1bad73/docs/browser.png
--------------------------------------------------------------------------------
/docs/qq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaoZ/JD-Coin/287af555c530d68b095018416b67c7d2fb1bad73/docs/qq.png
--------------------------------------------------------------------------------
/reference/daka_app_main.js:
--------------------------------------------------------------------------------
1 | // https://m.jr.jd.com/spe/qyy/main/js/main.min.js
2 |
3 | 'use strict';
4 | !function (a) {
5 | a.tools = {
6 | beReqdata: function (b) {
7 | return {
8 | reqData: JSON.stringify(b),
9 | sid: a.tools.getSid(),
10 | source: a.tools.getSource()
11 | }
12 | },
13 | isQyyOut: function () {
14 | return a.tools.getString('userType') ? !1 : !0
15 | },
16 | getSource: function () {
17 | return a.tools.isApp() ? 'app' : 'jrm'
18 | },
19 | hasDom: function (a) {
20 | return 0 === a.length ? !1 : !0
21 | },
22 | getFractionToDecimal: function (a) {
23 | var b = '';
24 | return - 1 != a.indexOf('%') ? (b = a.split('%'), b[0] / 100) : (b = a.split('/'), b[0] / b[1])
25 | },
26 | getString: function (a) {
27 | var b = new RegExp('(^|&)' + a + '=([^&]*)(&|$)', 'i'),
28 | c = window.location.search.substr(1).match(b);
29 | return null != c ? decodeURIComponent(c[2]) : null
30 | },
31 | getSid: function () {
32 | return a.tools.isApp() ? a.tools.getString('token') || a.tools.getString('sid') || a.tools.getCookie('sid') : a.tools.getString('sid') || a.tools.getCookie('sid')
33 | },
34 | hasSid: function () {
35 | var b = a.tools.getSid();
36 | return null === b || '' === b ? !1 : !0
37 | },
38 | changeToNumber: function (a) {
39 | return 'string' == typeof a ? Number(a) : a
40 | },
41 | changeToBoolean: function (a) {
42 | return 'boolean' == typeof a ? Boolean(a) : a
43 | },
44 | getCookie: function (a, b) {
45 | b = b || {
46 | };
47 | var c,
48 | d = b.raw ? function (a) {
49 | return a
50 | }
51 | : decodeURIComponent;
52 | return (c = new RegExp('(?:^|; )' + encodeURIComponent(a) + '=([^;]*)').exec(document.cookie)) ? d(c[1]) : null
53 | },
54 | newSetCookie: function (b, c, d) {
55 | if (d = a.extend({
56 | }, {
57 | domain: 'jr.jd.com',
58 | path: '/'
59 | }, d), null === c && (d.expires = - 1), 'number' == typeof d.expires) {
60 | var e = d.expires,
61 | f = d.expires = new Date;
62 | f.setTime(f.getTime() + 1000 * e * 60 * 60)
63 | } else if ('24h' === d.expires) {
64 | var g = new Date,
65 | f = g.getTime(),
66 | h = g.getHours(),
67 | i = g.getMinutes(),
68 | j = g.getSeconds(),
69 | k = '';
70 | k = f - 60 * h * 60 * 1000 - 60 * i * 1000 - 1000 * j + 86400000,
71 | k = new Date(k),
72 | d.expires = k.toUTCString()
73 | }
74 | return c = '' + c,
75 | document.cookie = [
76 | b,
77 | '=',
78 | d.raw ? c : c,
79 | d.expires ? '; expires=' + d.expires : '',
80 | d.path ? '; path=' + d.path : '',
81 | d.domain ? '; domain=' + d.domain : '',
82 | d.secure ? '; secure' : ''
83 | ].join('')
84 | },
85 | setCookie: function (b, c, d) {
86 | if (d = a.extend({
87 | }, {
88 | domain: 'jr.jd.com',
89 | path: '/'
90 | }, d), null === c && (d.expires = - 1), 'number' == typeof d.expires) {
91 | var e = d.expires,
92 | f = d.expires = new Date;
93 | f.setTime(f.getTime() + 1000 * e * 60 * 60)
94 | }
95 | return c = '' + c,
96 | document.cookie = [
97 | b,
98 | '=',
99 | d.raw ? c : c,
100 | d.expires ? '; expires=' + d.expires.toUTCString() : '',
101 | d.path ? '; path=' + d.path : '',
102 | d.domain ? '; domain=' + d.domain : '',
103 | d.secure ? '; secure' : ''
104 | ].join('')
105 | },
106 | isChinese: function (a) {
107 | var a = a.replace(/(^\s*)|(\s*$)/g, '');
108 | return !/^[\u4E00-\uFA29]*$/.test(a) || /^[\uE7C7-\uE7F3]*$/.test(a) ? !1 : !0
109 | },
110 | hasChinese: function (a) {
111 | var b = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
112 | return b.test(a)
113 | },
114 | isApp: function () {
115 | var a = navigator.userAgent.toLowerCase();
116 | return 'jdjr-app' == a.match(/jdjr-app/i) ? !0 : !1
117 | },
118 | clearCookie: function () {
119 | },
120 | isQQ: function () {
121 | var a = navigator.userAgent.toLowerCase(),
122 | b = /sq/.test(a);
123 | return b
124 | },
125 | isJDApp: function () {
126 | var a = navigator.userAgent.toLowerCase();
127 | return 'jdapp' == a.match(/jdapp/i) ? !0 : !1
128 | },
129 | isIos: function () {
130 | var a = /(iPhone|iPad|iPod)/i.test(navigator.userAgent);
131 | return a
132 | },
133 | isWeiXin: function () {
134 | var a = window.navigator.userAgent.toLowerCase();
135 | return 'micromessenger' == a.match(/MicroMessenger/i) ? !0 : !1
136 | },
137 | isPc: function () {
138 | for (var a = navigator.userAgent, b = new Array('Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'), c = !0, d = 0; d < b.length; d++) if (a.indexOf(b[d]) > 0) {
139 | c = !1;
140 | break
141 | }
142 | return c
143 | },
144 | checkIosVersion: function () {
145 | var a = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),
146 | b = parseInt(a[1]),
147 | c = parseInt(a[2]),
148 | d = parseInt(a[3]),
149 | e = [
150 | b,
151 | c,
152 | d
153 | ];
154 | return e
155 | },
156 | checkIos10_2_1: function () {
157 | if (a.tools.isIos()) {
158 | var b = a.tools.checkIosVersion(),
159 | c = b[0],
160 | d = b[1];
161 | return c >= 10 && d >= 2 ? !0 : !1
162 | }
163 | return !1
164 | },
165 | getClientType: function () {
166 | var a = '';
167 | return 1 == tools.checkUrlStatus() ? a = 8 : tools.isWeiXin() ? a = 3 : tools.isJDApp() ? (a = 5, tools.isIos() || (a = 7)) : a = tools.isApp() ? 2 : tools.isPc() ? 1 : 4,
168 | a
169 | },
170 | checkUrlStatus: function () {
171 | var a = 0,
172 | b = location.origin,
173 | c = 'http://m.jr.jd.com',
174 | d = 'https://m.jr.jd.com',
175 | e = 'http://minner.jr.jd.com';
176 | return a = b == c ? 0 : b == d ? 0 : b == e ? 10 : 2
177 | },
178 | isEmptyObject: function (a) {
179 | return '{}' == JSON.stringify(a)
180 | },
181 | jointUrlParam: function (a, b, c) {
182 | var d = new RegExp('/?/');
183 | return d.test(a) ? a + '&' + b + '=' + c : a + '?' + b + '=' + c
184 | },
185 | transmitParam: function (a, b, c, d, e) {
186 | return a.test(c) ? tools.jointUrlParam(d, b, e) : d
187 | },
188 | getDeviceId: function () {
189 | function b(a) {
190 | var b = new RegExp('(^|&)' + a + '=([^&]*)(&|$)', 'i'),
191 | d = c.substr(1).match(b);
192 | return null != d ? decodeURIComponent(d[2]) : null
193 | }
194 | var c = navigator.userAgent;
195 | c = c.replace(/\;/g, '&'),
196 | c = c.replace(/\//g, '=');
197 | var d = null;
198 | return a.tools.isIos() ? d = b('adid') || b('deviceid') : (d = b('deviceid'), null === d && (d = b('uid'))),
199 | d
200 | },
201 | forbidScroll: function () {
202 | document.body.style.overflow = 'hidden'
203 | },
204 | permitScroll: function () {
205 | document.body.style.overflow = 'auto'
206 | },
207 | passportLogin: function () {
208 | var a = tools.getString('sid');
209 | '' === a && null === a && (window.location.href = qyy.links.mLogin + encodeURIComponent(location.origin + location.pathname))
210 | },
211 | jdjrAppLogin: function () {
212 | var a = tools.getString('token');
213 | '' === a && null === a && jsBridgeV3.onReady().then(function () {
214 | this.jsToGetResp(function (a) {
215 | a = 'object' == typeof a ? a : JSON.parse(a),
216 | a.data && (window.location.href = location.origin + location.pathname + '?token=' + decodeURIComponent(a.data))
217 | }, {
218 | type: 1,
219 | data: ''
220 | })
221 | })
222 | },
223 | unifyLogin: function () {
224 | this.isApp() ? this.jdjrAppLogin() : this.passportLogin()
225 | },
226 | loadSource: function (a, b, c, d) {
227 | if (null === document.getElementById(c)) {
228 | var e = document.createElement(b);
229 | e.src = a,
230 | e.id = c,
231 | document.getElementsByTagName('body') [0].appendChild(e),
232 | document.getElementById(c).onload = function () {
233 | void 0 != d && d()
234 | }
235 | }
236 | },
237 | checkHost: function (a) {
238 | var b = location.host,
239 | c = function () {
240 | return 'link' === a ? !0 : 'interface' === a ? !1 : !0
241 | }();
242 | switch (b) {
243 | case 'm.jr.jd.com':
244 | return c ? 'm' : 'ms';
245 | case 'minner.jr.jd.com':
246 | return c ? 'minner' : 'msinner';
247 | case 'localhost:8080':
248 | return c ? 'minner' : 'msinner';
249 | default:
250 | return c ? 'm' : 'ms'
251 | }
252 | },
253 | dealSystemError: function () {
254 | var b = a('#system-error-wrap'),
255 | c = a('#system-error-reload');
256 | b.removeClass('hide'),
257 | a.tools.forbidBodyScroll(),
258 | c.click(function () {
259 | location.reload()
260 | })
261 | },
262 | forbidBodyScroll: function () {
263 | document.getElementsByTagName('body') [0].style['overflow-y'] = 'hidden'
264 | }
265 | },
266 | a.jdjr = {
267 | getSid: function () {
268 | var b = a.tools.getString('sid');
269 | return '' === b || null === b ? !1 : !0
270 | },
271 | getToken: function () {
272 | var b = a.tools.getString('token');
273 | return '' === b || null === b ? !1 : !0
274 | }
275 | }
276 | }(Zepto);
277 | var qyy = {
278 | $obj: {
279 | html: $('html'),
280 | body: $('body'),
281 | wrap: $('.wrap'),
282 | dataInput: $('#qyy-data-input')
283 | },
284 | tabProducts: {
285 | skuMenuMap: [
286 | ],
287 | skuMenuItemMap: [
288 | ]
289 | },
290 | isEnterInLoaing: !0,
291 | isJrUser: !1,
292 | isQyyOut: $.tools.isQyyOut(),
293 | isStatic: 'static' === $('html').data('type') ? !0 : !1,
294 | sid: $.tools.getSid(),
295 | hasSid: $.tools.hasSid(),
296 | token: $.tools.getString('token'),
297 | isIos: $.tools.isIos(),
298 | isInApp: $.tools.isApp(),
299 | isInJdApp: $.tools.isJDApp(),
300 | isInWeixin: $.tools.isWeiXin(),
301 | isInQQ: $.tools.isQQ(),
302 | userType: parseInt($.tools.getString('userType')) || $('#qyy-usertype').attr('data-qyy-usertype'),
303 | isActionErr: !1,
304 | isReturnTop: 0,
305 | needLogin: !1,
306 | isLogin: !1,
307 | inAppLoginInFlag: !0,
308 | headerTitle: '',
309 | hasFixedTop: !1,
310 | hasComFixedTop: !1,
311 | fixedTopSeizeClass: '',
312 | hasFixedBottom: !1,
313 | canChangeAppColor: !0,
314 | protocol: location.protocol
315 | };
316 | qyy.imgHost = qyy.protocol + '//m.jr.jd.com/spe/qyy/main/',
317 | qyy.links = {
318 | mLogin: qyy.protocol + '//passport.m.jd.com/user/login.action?v=t&sid=&returnurl=',
319 | imgPlaceHoldUrl: qyy.protocol + '//img12.360buyimg.com/jrpmobile/jfs/t2392/247/2876588652/1253/b393637/5719d996N2f175f2f.jpg',
320 | defaultHeaderImg: qyy.protocol + '//img12.360buyimg.com/jrpmobile/jfs/t2644/238/1420176553/1442/96e2885/573d96deN06201af5.png',
321 | xbxyIcon: qyy.protocol + '//m.jr.jd.com/spe/qyy/main/images/icon_xbxy.png',
322 | checkName: 'https://msc.jd.com/auth/loginpage/wcoo/toAuthPage?' + (qyy.isInApp ? 'source=1&businessType=69' : 'source=4&businessType=68') + '&sid=',
323 | checkNameV1: 'https://msc.jd.com/auth/loginpage/wcoo/toAuthPage?' + (qyy.isInApp ? 'source=1&businessType=344' : 'source=2&businessType=344') + '&directReturnUrl=' + encodeURIComponent(location.href),
324 | jimuQb: '//m.jr.jd.com/mjractivity/14757-3.html?sid=',
325 | xjk: qyy.protocol + '//ms.jr.jd.com/xjk/h5/xjk/onekeyredirect.action?sid=',
326 | acZhye: qyy.protocol + '//m.jdpay.com/wallet/login/sid?toUrl=' + encodeURIComponent(qyy.protocol + '//m.jdpay.com/wallet/balance/index.htm?style=normal') + '&sid='
327 | },
328 | qyy.statics = {
329 | jrlogo: qyy.protocol + '//m.jr.jd.com/statics/logo.jpg',
330 | successIcon: qyy.protocol + '//m.jr.jd.com/statics/images/success@100.png',
331 | errorIcon: qyy.protocol + '//m.jr.jd.com/statics/images/error@100.png'
332 | },
333 | qyy.resource = {
334 | vip: 'js/vip/vip.js',
335 | balance: 'js/balance/balance.js'
336 | },
337 | qyy.wangGuan = qyy.protocol + '//' + $.tools.checkHost('interface') + '.jr.jd.com/gw/generic/base/h5/m/',
338 | qyy.actionMap = {
339 | getAllInfo: qyy.wangGuan + 'baseGetOutH5MessageList',
340 | baseGetMessByGroupTypeEncrypt: qyy.wangGuan + 'baseGetMessByGroupTypeEncrypt',
341 | baseGetMessByGroupType: qyy.wangGuan + 'baseGetMessByGroupType',
342 | getPageInfo: qyy.wangGuan + 'baseOutH5Page',
343 | outH5ReceiveMission: qyy.wangGuan + 'baseOutH5ReceiveMission',
344 | outH5RewardMission: qyy.wangGuan + 'baseOutH5RewardMission',
345 | getMissionAwardlist: qyy.wangGuan + 'baseGetMissionAwardlist',
346 | getMissionDetail: qyy.wangGuan + 'baseGetMissionDetailEncrypt',
347 | baseSignInEncrypt: qyy.wangGuan + 'baseSignInEncrypt',
348 | qyyExposure: '//jrmfp.jr.jd.com/hpvuv'
349 | },
350 | function (a) {
351 | var b = {
352 | setTitleRight: function () {
353 | },
354 | inAppUnifyLogin: function (a) {
355 | jsBridgeV3.onReady().then(function () {
356 | this.jsOpenWeb({
357 | jumpUrl: a,
358 | jumpType: 8,
359 | productId: '',
360 | isclose: !1
361 | })
362 | })
363 | },
364 | unifyAppLogin: function () {
365 | var a = location.href;
366 | qyy.inAppLoginInFlag = !1,
367 | qyy.isInApp ? jsBridgeV3.onReady().then(function () {
368 | this.jsOpenWeb({
369 | jumpUrl: a,
370 | jumpType: 8,
371 | productId: '',
372 | isclose: !0
373 | })
374 | }) : location.href = qyy.links.mLogin + a
375 | },
376 | changeJrAppColor: function (a) {
377 | jsBridgeV3.onReady().then(function () {
378 | this.jsToGetResp(function () {
379 | }, {
380 | type: 3,
381 | colorArr: a.colorArr
382 | }),
383 | void 0 != a.btnText && this.jsToNaWeiXin({
384 | isShow: !0,
385 | optionType: 2,
386 | btnText: a.btnText,
387 | jumpLiDate: {
388 | isLogin: 2,
389 | jumpLink: a.jumpLink,
390 | isclose: !1
391 | }
392 | })
393 | })
394 | },
395 | initJrAppShare: function (a, b, c, d, e) {
396 | jsBridgeV3.onReady().then(function () {
397 | this.jsToNaWeiXin({
398 | isShow: !0,
399 | optionType: 1,
400 | btnText: '分享',
401 | jumpLiDate: {
402 | isLogin: 0,
403 | jumpLink: '',
404 | isclose: !1
405 | },
406 | jumpNaDate: {
407 | type: 7,
408 | productId: 71824013,
409 | isclose: !1
410 | },
411 | shareDate: {
412 | appId: '',
413 | img: e,
414 | link: d,
415 | desc: c,
416 | title: a,
417 | friendesc: a,
418 | type: 3475734
419 | }
420 | })
421 | })
422 | },
423 | initWeixinShare: function (a, b, c, d, e) {
424 | var f = {
425 | appId: '',
426 | imgUrl: e,
427 | link: d,
428 | desc: c,
429 | title: a
430 | };
431 | WeixinApi.ready(function (a) {
432 | var b = {
433 | ready: function () {
434 | },
435 | cancel: function () {
436 | },
437 | fail: function () {
438 | },
439 | confirm: function () {
440 | },
441 | all: function () {
442 | }
443 | };
444 | a.shareToFriend(f, b),
445 | a.shareToWeibo(f, b),
446 | a.shareToTimeline(f, b)
447 | })
448 | },
449 | initJdAppShare: function (a, b, c, d, e) {
450 | var f = encodeURIComponent(d),
451 | g = e;
452 | try {
453 | var h = {
454 | },
455 | i = [
456 | ];
457 | i[0] = a,
458 | i[1] = b,
459 | i[2] = f,
460 | i[3] = g,
461 | h.version = navigator.userAgent.split(';'),
462 | h.isInApp = 'jdapp' == h.version[0].toLowerCase(),
463 | h.isRightVersion = h.isInApp && h.version[2].replace(/\./g, '') >= 400,
464 | h.isRightVersion && ('iphone' == h.version[1].toLowerCase() ? location.href = 'openapp.jdmobile://communication?params={"action":"syncShareData","title":"' + i[0] + '","content":"' + i[1] + '","shareUrl":"' + i[2] + '","iconUrl":"' + i[3] + '"}' : 'android' == h.version[1].toLowerCase() && shareHelper.setShareInfo(i[0], i[1], d, i[3]))
465 | } catch (j) {
466 | }
467 | },
468 | init: function () {
469 | }
470 | };
471 | a.communication = b
472 | }(window, Zepto),
473 | function (a, b) {
474 | function c(a, b, c) {
475 | this.countDownId = a,
476 | this.endTime = b,
477 | this.position = c,
478 | this.needClear = !1,
479 | this.formatDateStr = function (a) {
480 | return 0 >= a ? a = '00' : a > 0 && 10 > a && (a = '0' + a),
481 | a
482 | },
483 | this.initDom = function () {
484 | var a = new Date,
485 | b = 0,
486 | d = 0,
487 | e = 0,
488 | f = 0,
489 | g = 0;
490 | a = a.getTime(),
491 | b = this.endTime - a,
492 | 0 >= b ? (d = e = f = g = '00', this.needClear = !0) : (d = Math.floor(b / 1000 / 60 / 60 / 24), e = Math.floor(b / 1000 / 60 / 60 % 24), f = Math.floor(b / 1000 / 60 % 60), g = Math.floor(b / 1000 % 60), e = this.formatDateStr(e + 24 * d), f = this.formatDateStr(f), g = this.formatDateStr(g));
493 | var h = '
' + a.etitle1 + '
' + a.name + '
' + q + '
' + j + '
暂无可做任务,明天再来看看吧~
钱包余额(元)' + b + '
'; 793 | break; 794 | case 10: 795 | d = '小白信用' + b + '
京东支付银行卡' + b + '
金币' + b + '
' + a.etitle1 + '
' + f + '' + a.etitle1 + '
'; 872 | return c 873 | }, 874 | getSingleTextGroup: function (a, b) { 875 | var c = '总资产(元)
' + f + '
今日收益' + g + '
' + d + '白条可用额度(元)
' + h + '
' + i + '
' + d + '活期定期
' + j + '
' + d + '基金
' + k + '
' + d + '小白理财
' + a.etitle1 + '
' + d + '京东小金库
' + l + '
' + d + '小白信用
' + m + '
' + d + '金条(可借额度)
' + n + '
' + d + '' + d + '
'), 946 | 9 === b ? h = '钱包余额(元)
账户余额(元)
' + a.etitle1 + '
' + a.etitle2 + '
' + a.etitle3 + '
' + a.etitle1 + '
¥' + b + '
' + a.etitle1 + '
¥' + f + '起
' + h + '' + a.etitle1 + '
¥' + f + '起
' + i + '' + a.etitle1 + '
¥' + b + '
' + d + '
' + d + '
' + this.resultData.showMsg + '
' 1887 | }, 1888 | setClickSwitch: function () { 1889 | (14 === this.resultData.statusCode || 15 === this.resultData.statusCode) && b('#appSign-btn').text(c.hasSign).attr('qyy-stop-click', ''), 1890 | 0 === this.resultData.statusCode && b('#appSign-btn').text(c.hasSign) 1891 | }, 1892 | continuitySignStr: function () { 1893 | return this.resultData.continuityDays > 1 ? ',已连续签到 ' + this.resultData.continuityDays + ' 天' : '' 1894 | }, 1895 | extraPrizeStr: function () { 1896 | return this.resultData.isContinuity ? '获得额外奖励 ' + this.resultData.continuityAmount + ' ' + this.resultData.additionalPrizeName : '' 1897 | }, 1898 | appendSignResult: function () { 1899 | var a = '' + this.comPrizeStr() + this.continuitySignStr() + '
' + this.extraPrizeStr() + '
'; 1900 | b('#appSign-gb-wrap').empty().append(a).removeClass('hide') 1901 | }, 1902 | init: function () { 1903 | this.showBannerAndSwitch(), 1904 | this.appendSignResult(), 1905 | this.setClickSwitch() 1906 | } 1907 | }; 1908 | d.init() 1909 | }, 1910 | eventGoback: function () { 1911 | b('.appSign').on('click', '#appSign-display-switch', function () { 1912 | b('.appSign-switch').addClass('hide') 1913 | }) 1914 | }, 1915 | init: function () { 1916 | this.setDay(), 1917 | this.setYearAndMonth(), 1918 | this.appendDraw(), 1919 | this.setSignBtn(), 1920 | this.eventGoback(), 1921 | this.login.checkIsLogin() && this.sign() 1922 | } 1923 | }; 1924 | d.init() 1925 | } 1926 | }; 1927 | a._ajax = h 1928 | }(window, Zepto); 1929 | var tools = $.tools; 1930 | !function (a, b) { 1931 | var c = a._ajax, 1932 | d = b.tools, 1933 | e = d.getCookie('sid'); 1934 | ('' === e || null === e) && (e = d.getString('sid')), 1935 | a.memberMission = { 1936 | checkName: function () { 1937 | location.href = qyy.links.checkNameV1 1938 | }, 1939 | popCheckName: function (a) { 1940 | if (2 === parseInt(a)) return !0; 1941 | if (qyy.isJrUser) return !0; 1942 | if (40 === parseInt(d.getString('missionId'))) return !0; 1943 | var c = { 1944 | img90: 'images/dailogue_icon_identifiaction.png', 1945 | title1: '请先开通实名认证', 1946 | label_desc2: '您的账户尚未实名,无法完成此任务,请先前往实名认证。完成后还可领30元支付礼包!', 1947 | btn: '关闭', 1948 | btn2: '去实名' 1949 | }, 1950 | e = popUi.simplePop(c); 1951 | return popUi.showSimplePop(e), 1952 | b('#qyy-button_check_name').click(function () { 1953 | memberMission.checkName() 1954 | }), 1955 | !1 1956 | }, 1957 | initMemberMissionEvent: function (a) { 1958 | return 0 === a.parents('.section-34').length ? !0 : a.hasClass('orange') ? !1 : a.hasClass('needAjax') ? !1 : a.hasClass('gray') ? !1 : a.hasClass('blue') ? !1 : void 0 1959 | }, 1960 | assembleUserData: function (a) { 1961 | var b = { 1962 | }, 1963 | a = a, 1964 | c = ''; 1965 | return 1 == d.checkUrlStatus() ? c = 'APP_TEST' : d.isWeiXin() ? c = 'WX_Q' : d.isJDApp() ? (c = 'JD_APP', d.isIos() || (c = 'JD_APP_ANDROID')) : c = d.isPc() ? 'PC' : d.isApp() ? 'JR_APP' : 'M', 1966 | b = d.isApp() ? { 1967 | missionId: a, 1968 | channel: c 1969 | } 1970 | : { 1971 | missionId: a, 1972 | channel: c 1973 | } 1974 | }, 1975 | bindPopFloat: function () { 1976 | qyy.$obj.body.children().on('click', '.info-wrap', function () { 1977 | var a = b(this).parents('.member-mission-all'), 1978 | c = (a.find('.info-btn'), a.attr('data-qyy-frequencytype')), 1979 | d = a.attr('data-areaShow'), 1980 | e = a.attr('data-qyy-status'); 1981 | if (!memberMission.popCheckName(d)) return !1; 1982 | var f = { 1983 | img: a.find('.icon').attr('src'), 1984 | title: a.attr('data-qyy-title'), 1985 | subtitle: a.attr('data-qyy-awardName'), 1986 | label: '任务说明', 1987 | label_desc: a.attr('data-qyy-desc'), 1988 | label1: '当前状态', 1989 | label_desc1: a.attr('data-qyy-statusText'), 1990 | scheduleShowValue: a.attr('data-qyy-scheduleShowValue'), 1991 | progress: !1, 1992 | btn: '关闭', 1993 | btn1: a.find('.info-btn').text() 1994 | }; 1995 | if ('0' === e && parseInt(a.attr('data-qyy-scheduleTargetValue')) > 0 && (f.progress = !0), '0' != e && parseInt(a.attr('data-qyy-scheduleTargetValue')) > 0 && (f.scheduleShowValue = ''), 0 != a.attr('data-qyy-frequencyType')) { 1996 | var g = [ 1997 | '', 1998 | '日任务', 1999 | '周任务', 2000 | '月任务' 2001 | ]; 2002 | f.topLabel = g[a.attr('data-qyy-frequencyType')] 2003 | } 2004 | if (0 != c && '2' === e) { 2005 | var h = [ 2006 | '', 2007 | '当日', 2008 | '当周', 2009 | '当月' 2010 | ]; 2011 | f.label_desc1 = h[c] + f.label_desc1 2012 | } 2013 | '2' === e && (f.topImg = 'images/md/dialogue_finish.png'); 2014 | var i = popUi.simplePop(f); 2015 | popUi.showSimplePop(i), 2016 | '1' === e ? b('#qyy-button_jump').addClass('orange') : '2' === e && b('#qyy-button_jump').remove(); 2017 | var j = b(this).next().children('.info-btn').attr('data-qyy-missionid'); 2018 | b('#qyy-button_jump').attr({ 2019 | 'data-qyy-missionid': j, 2020 | 'data-areaShow': d 2021 | }) 2022 | }) 2023 | }, 2024 | bindGetPrize: function () { 2025 | qyy.$obj.body.children().on('click', '.info-btn,.mission-btn,#qyy-button_jump', function () { 2026 | var a = b(this), 2027 | e = '', 2028 | f = a.parents('.member-mission-all').attr('data-areaShow') || a.attr('data-areaShow'), 2029 | g = { 2030 | }; 2031 | return memberMission.popCheckName(f) ? ('qyy-button_jump' === b(this).attr('id') && (e = b(this).attr('data-qyy-missionId'), a = b('span[data-qyy-missionid="' + e + '"]')), void (a.hasClass('check-name') ? memberMission.checkName() : a.hasClass('do-mission') ? (g = memberMission.assembleUserData(a.attr('data-qyy-missionId')), c.init(qyy.actionMap.outH5ReceiveMission, 'post', 'json', d.beReqdata(g), !0, c.getMemberMission, a.attr('data-qyy-jumpt'))) : a.hasClass('get-prize') ? (g = memberMission.assembleUserData(a.attr('data-qyy-missionId')), c.init(qyy.actionMap.outH5RewardMission, 'post', 'json', d.beReqdata(g), !0, c.getMemberPrize, a)) : a.hasClass('doing-mission') && setTimeout(function () { 2032 | window.location.href = a.attr('data-qyy-jumpt') 2033 | }, 50))) : !1 2034 | }) 2035 | }, 2036 | bindCloseFloat: function () { 2037 | qyy.$obj.body.children().on('click', '#qyy-pop_com_button,#qyy-button_cancel', function () { 2038 | 'button_success' === b(this).attr('id') && main.requestAllInfo(), 2039 | popUi.hideSimplePop() 2040 | }) 2041 | }, 2042 | bindCloseFloatAndReload: function () { 2043 | qyy.$obj.html.children().on('click', '#qyy-button_success', function () { 2044 | setTimeout(function () { 2045 | window.location.reload() 2046 | }, 150) 2047 | }) 2048 | }, 2049 | bindMemberBalanceRemind: function () { 2050 | qyy.$obj.html.children().on('click', '.member-product .count-icon', function () { 2051 | event.stopPropagation(); 2052 | var a = { 2053 | title1: b(this).parents('.item').find('.title').html(), 2054 | label_desc: b(this).attr('data-qyy-desc'), 2055 | btn: '关闭' 2056 | }; 2057 | b('.pop_com_wrap').remove(); 2058 | var c = popUi.simplePop(a); 2059 | popUi.showSimplePop(c) 2060 | }) 2061 | }, 2062 | init: function () { 2063 | memberMission.bindGetPrize(), 2064 | memberMission.bindCloseFloat(), 2065 | memberMission.bindCloseFloatAndReload(), 2066 | memberMission.bindMemberBalanceRemind(), 2067 | memberMission.bindPopFloat() 2068 | } 2069 | }, 2070 | memberMission.init() 2071 | }(window, Zepto), 2072 | function (a, b) { 2073 | var c = b.tools, 2074 | d = a.popUi, 2075 | e = a.CountDown, 2076 | f = a.scrollNotice, 2077 | g = a.checkAjax, 2078 | h = a._ajax; 2079 | qyy.isQyyOut && g.init(0 != b('.qyy-body').length ? g.getSuccessData({ 2080 | type: '2' 2081 | }) : g.getErrorData({ 2082 | type: '2' 2083 | })); 2084 | var i = (c.getString('pin'), c.getString('qingfrom')), 2085 | j = a.memberMission, 2086 | k = { 2087 | initPage: function () { 2088 | var a = { 2089 | userType: qyy.userType 2090 | }; 2091 | h.init(qyy.actionMap.getPageInfo, 'post', 'json', c.beReqdata(a), !1, h.initPage, '', 1), 2092 | 1 === qyy.isReturnTop && this.initReturnTop() 2093 | }, 2094 | requestAllInfo: function () { 2095 | d.showLoading(); 2096 | var a = { 2097 | }, 2098 | e = c.getDeviceId(); 2099 | a = { 2100 | clientType: 'outH5', 2101 | userType: qyy.userType, 2102 | missionPlatformsEnumCode: c.getClientType(), 2103 | sid: qyy.sid, 2104 | deviceId: e 2105 | }, 2106 | h.init(qyy.actionMap.getAllInfo, 'post', 'json', c.beReqdata(a), !1, h.loadAllFloor), 2107 | k.initEventAndSetCookie(), 2108 | k.initMemberBalance(), 2109 | k.initMemberMission(), 2110 | this.initIsHasFixed(), 2111 | this.initExitLogin(), 2112 | this.initGroupData(), 2113 | this.initMenuList(), 2114 | this.initWeixinFloat(), 2115 | this.initCountDown(), 2116 | this.initScrollNotice(), 2117 | this.initJdHeader(), 2118 | this.introduceSource(), 2119 | this.initFixedBtn(), 2120 | setTimeout(function () { 2121 | if (d.hideLoading(), qyy.isEnterInLoaing = !1, !qyy.isActionErr) { 2122 | d.enterInAnimation(); 2123 | var a = 0 === b('.swiper-container').length ? !1 : !0; 2124 | a && ('undefined' == typeof Swiper ? b.tools.loadSource('//m.jr.jd.com/spe/qyy/main/js/libs/swiper-3.4.0.jquery.min.js', 'script', 'qyy-swiper-js', function () { 2125 | k.initGallery() 2126 | }) : k.initGallery()), 2127 | k.imgLazyLoad(), 2128 | setTimeout(function () { 2129 | k.initBigBanner(), 2130 | k.initGostBtn() 2131 | }, 100) 2132 | } 2133 | }, 50) 2134 | }, 2135 | initScrollNotice: function () { 2136 | b('.auto-notice').each(function () { 2137 | var a = b(this).attr('id'), 2138 | c = b(this).children('.auto-notice-wrap').attr('id'); 2139 | setTimeout(function () { 2140 | f(a, c) 2141 | }, 1800) 2142 | }) 2143 | }, 2144 | initMemberBalance: function () { 2145 | var a = b('.member-product'), 2146 | c = a.find('.item').length; 2147 | c > 3 && (a.after('做任务有钱赚
3 | 4 |5 |
6 |' + content +'
', 144 | btnWraper: '朕知道了', 145 | handlerEvent: function (e) { 146 | 147 | }, 148 | confirmBack: function (e) { 149 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 150 | e.remove(); 151 | } 152 | }) 153 | } 154 | }); 155 | }); 156 | 157 | //领奖 158 | $("#receiveAward").on("click", function () { 159 | $("#loading").removeClass('hide'); 160 | createMpingEvent("MJingDouDouble_Get", "", "", "","JingDou_Double"); 161 | var functionId = 'receiveAward'; 162 | // if($("#receiveAward").hasClass("status-disable")){ 163 | // $("#loading").addClass('hide'); 164 | // $.mToast({ 165 | // msg:'完成双签才可领取礼包,快去签到吧', 166 | // isDone:false, 167 | // time:3000, 168 | // callBack: function () { 169 | // 170 | // } 171 | // }); 172 | // return; 173 | // } 174 | if ($("#receiveAward").hasClass("status-exception")) { 175 | $("#loading").addClass('hide'); 176 | $.mToast({ 177 | msg:'活动太火爆了,稍后再来吧~', 178 | isDone:false, 179 | time:3000, 180 | callBack: function () { 181 | location.reload(); 182 | } 183 | }) 184 | return; 185 | } 186 | if ($("#receiveAward").hasClass('status-viewaward')) {//查看礼包 187 | functionId = 'queryAward'; 188 | } else if ($("#receiveAward").hasClass('status-toreceive')) { 189 | functionId = 'receiveAward'; 190 | } 191 | var url = "/countersign/" + functionId + ".json"; 192 | 193 | $.ajax({ 194 | type: 'post', 195 | dataType: "json", 196 | async: false, 197 | url: url, 198 | success: function (resultAjax) { 199 | $("#loading").addClass('hide'); 200 | if (functionId == 'receiveAward') { 201 | //领奖流程 202 | if (null != resultAjax && null != resultAjax.res && resultAjax.res.code != 0) { 203 | //异常流程 204 | countersign.processExp(resultAjax); 205 | } 206 | if (null != resultAjax && null != resultAjax.res && resultAjax.res.code == 0) { 207 | //展示领奖结果 208 | var html = '' + content +'
', 238 | btnWraper: '朕知道了', 239 | handlerEvent: function (e) { 240 | 241 | }, 242 | confirmBack: function (e) { 243 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 244 | var html = '' + content +'
', 273 | btnWraper: '朕知道了', 274 | handlerEvent: function (e) { 275 | 276 | }, 277 | confirmBack: function (e) { 278 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 279 | e.remove(); 280 | } 281 | }) 282 | } 283 | }); 284 | }); 285 | }, 286 | getAward: function (resultAjax) { 287 | if (null != resultAjax.res.data) { 288 | var awardResult = resultAjax.res.data; 289 | if(awardResult.length == 0){ 290 | //空礼包 291 | var content = "运气不佳,领到一个空空的礼包"; 292 | $.mConfirm({ 293 | title: content, 294 | btnWraper: '朕知道了', 295 | handlerEvent: function (e) { 296 | 297 | }, 298 | confirmBack: function (e) { 299 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 300 | var html = '' + awardResult[i].awardName + ' ' + 'x' + awardResult[i].awardCount + '
'; 328 | } else { 329 | html += '' + awardResult[i].awardName + ' ' + '
'; 330 | } 331 | 332 | html += '' + content +'
', 390 | btnWraper: '朕知道了', 391 | handlerEvent: function (e) { 392 | 393 | }, 394 | confirmBack: function (e) { 395 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 396 | e.remove(); 397 | var link = "https://bean.m.jd.com"; 398 | var url = 'openApp.jdMobile://virtual?params={"sourceType" : "sale-act","sourceValue" : "jumpFromShare","category" : "jump","des" : "DM","dmurl" : "' + link + '"}'; 399 | toJDApp.to(url); 400 | } 401 | }) 402 | }else if (resultAjax.res.code == 'DS102') { 403 | //活动已结束 404 | var title = "来早了,活动还未开始"; 405 | var content = "去看看其他活动吧~"; 406 | 407 | $.mConfirm({ 408 | title: title, 409 | content:'' + content +'
', 410 | btnWraper: '朕知道了', 411 | handlerEvent: function (e) { 412 | 413 | }, 414 | confirmBack: function (e) { 415 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 416 | e.remove(); 417 | var link = "https://bean.m.jd.com"; 418 | var url = 'openApp.jdMobile://virtual?params={"sourceType" : "sale-act","sourceValue" : "jumpFromShare","category" : "jump","des" : "DM","dmurl" : "' + link + '"}'; 419 | toJDApp.to(url); 420 | } 421 | }) 422 | } else if (resultAjax.res.code == 'DS106') { 423 | //活动已结束 424 | var title = "完成双签才可领取礼包"; 425 | var content = "快去签到吧~"; 426 | $.mConfirm({ 427 | title: title, 428 | content:'' + content +'
', 429 | btnWraper: '朕知道了', 430 | handlerEvent: function (e) { 431 | 432 | }, 433 | confirmBack: function (e) { 434 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 435 | e.remove(); 436 | window.location.reload(); 437 | } 438 | }) 439 | } else { 440 | //其余异常 当做未领奖处理 441 | //活动已结束 442 | var content = "活动太火爆了,稍后再来吧~"; 443 | $.mConfirm({ 444 | title: content, 445 | btnWraper: '朕知道了', 446 | handlerEvent: function (e) { 447 | 448 | }, 449 | confirmBack: function (e) { 450 | createMpingEvent("MJingDouDouble_Know", "", "", "","JingDou_Double"); 451 | window.location.reload(); 452 | e.remove(); 453 | } 454 | }) 455 | } 456 | }, 457 | useAward: function (url,awardType) { 458 | var event_id = ""; 459 | var report_lvl = ""; 460 | if(awardType == 1){ 461 | event_id = "MJingDouHome_SKULevelFour1"; 462 | report_lvl = 4; 463 | } 464 | if(awardType == 2){ 465 | event_id = "MJingDouDouble_CoinUse"; 466 | } 467 | if(awardType == 3){ 468 | event_id = "MJingDouHome_ActivityLevelThree3"; 469 | report_lvl = 3; 470 | } 471 | createMpingEvent(event_id, "", "", "","JingDou_Double",report_lvl); 472 | if (url.indexOf("http") >= 0) { 473 | window.location.href = url; 474 | } else { 475 | if($("#isM").val() == 'true'){ 476 | toJDApp.to(url); 477 | }else{ 478 | //客户端内部直接openApp 479 | window.location.href = url; 480 | } 481 | } 482 | }, 483 | 484 | shareSign: function () { 485 | createMpingEvent("MJingDouDouble_Share", "", "", "","JingDou_Double"); 486 | var shareInfo = $("#counterSignShare").val(); 487 | if(shareInfo == ''){ 488 | var shareParam = { 489 | title: '一大波京豆、神券、金币等你领取', 490 | content: '来京东APP、京东金融APP签到,三重奖励天天领', 491 | url: location.origin + '/countersign/index.action', 492 | img: 'https://m.360buyimg.com/njmobilecms/jfs/t10393/117/1880465648/15598/ffc609c1/59e86165N3494fb59.png', 493 | channel: 'Wxfriends,Wxmoments,Sinaweibo', 494 | callback: null, // 不要依赖回调,不要在回调中加入业务逻辑,不要在回调中处理耗时的操作 495 | clickcallback:null, // 5.2新增 分享面板中点击分享渠道成功后回调 注意 sendDirectShare 不支持这个回调方法 496 | qrparam:null, // 具体配置详见 5.2新增 二维码分享 497 | timeline_title:'' // 5.4新增 朋友圈字段 498 | } 499 | }else{ 500 | var activityShare = $.parseJSON(shareInfo); 501 | var shareParam = { 502 | title: activityShare.shareTitle, 503 | content: activityShare.shareContent, 504 | url: activityShare.shareUrl, 505 | img: activityShare.shareImage, 506 | channel: 'Wxfriends,Wxmoments', 507 | callback: null, // 不要依赖回调,不要在回调中加入业务逻辑,不要在回调中处理耗时的操作 508 | clickcallback:null, // 5.2新增 分享面板中点击分享渠道成功后回调 注意 sendDirectShare 不支持这个回调方法 509 | qrparam:null, // 具体配置详见 5.2新增 二维码分享 510 | timeline_title:'' // 5.4新增 朋友圈字段 511 | } 512 | } 513 | jdShare.callSharePane(shareParam); 514 | }, 515 | 516 | 517 | }; 518 | -------------------------------------------------------------------------------- /reference/打卡.md: -------------------------------------------------------------------------------- 1 | # 打卡 2 | 3 | ## 日常打卡 4 | 5 | `GET https://bk.jd.com/m/money/home/daka.html` 6 | 7 | #### 打卡成功 8 | 9 | ```json 10 | { 11 | "resultCode": "0000", 12 | "resultMessage": "打卡成功", 13 | "data": null, 14 | "success": true 15 | } 16 | ``` 17 | 18 | #### 打卡失败, 今日已领 19 | 20 | ```json 21 | { 22 | "resultCode": "0001", 23 | "resultMessage": "已经打过卡了,每天只能打一次!", 24 | "data": null, 25 | "success": false 26 | } 27 | ``` 28 | 29 | #### 打卡失败, 需先完成领钢镚任务 30 | 31 | ```json 32 | { 33 | "resultCode": "0003", 34 | "resultMessage": "请先领取钢镚", 35 | "data": null, 36 | "success": false 37 | } 38 | ``` 39 | 40 | ## 领钢镚任务 41 | 42 | `GET https://bk.jd.com/m/money/home/recDoJobMoney.html?pcId=82` 43 | 44 | #### 领取成功 45 | 46 | ```json 47 | { 48 | "resultCode": null, 49 | "resultMessage": null, 50 | "data": null, 51 | "success": true 52 | } 53 | ``` 54 | 55 | #### 领取失败 (比如已经领了...) 56 | 57 | ```json 58 | { 59 | "resultCode": "-1", 60 | "resultMessage": "没有找到当前奖品", 61 | "data": null, 62 | "success": false 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /reference/新版客户端京豆签到.md: -------------------------------------------------------------------------------- 1 | ### 未登录 2 | 3 | ```json 4 | { 5 | "code": "3" 6 | } 7 | ``` 8 | 9 | 10 | ### 参数错误, 如 `client` 或 `functionId` 不对 11 | 12 | ```json 13 | { 14 | "code": "1", 15 | "echo": "required string parameter 'client' is not present" 16 | } 17 | ``` 18 | 19 | 20 | ### 签到信息: 21 | 22 | ```json 23 | { 24 | "code": "0", 25 | "data": { 26 | "status": "2", 27 | "totalUserBean": "604", 28 | "signRuleInfo": { 29 | "signCycle": "2", 30 | "title": "连续7天签到得奖励" 31 | }, 32 | "continuousDays": "2", 33 | "awardDaysLimit": "7" 34 | } 35 | } 36 | ``` 37 | 38 | `data`.`status`: `2`: 已签到, `4`: 未签到, `5`: 未登录 39 | 40 | 41 | ### 签到: 成功 42 | 43 | ```json 44 | { 45 | "code": "0", 46 | "data": { 47 | "status": "1", 48 | "signShowType": "3", 49 | "signShowBean": { 50 | "signText": "签到成功,已领signAward京豆,翻牌得奖励", 51 | "signAward": "3", 52 | "complated": 0, 53 | "cardText": "每日可翻1张牌", 54 | "awardList": [{}, {}, {}] 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | 61 | ### 签到: 已签到 62 | 63 | ```json 64 | { 65 | "code": "0", 66 | "data": { 67 | "status": "2", 68 | "signShowType": "3", 69 | "signShowBean": { 70 | "signText": "今日已签到,已翻牌,共领signAward京豆", 71 | "signAward": "5", 72 | "complated": 1, 73 | "cardText": "今日已翻1张牌", 74 | "awardList": [{}, {}, {}] 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | 81 | ### 翻牌: 成功 82 | 83 | ```json 84 | { 85 | "code": "0", 86 | "data": { 87 | "signText": "今日已签到,已翻牌,共领signAward京豆", 88 | "signAward": "5", 89 | "complated": 1, 90 | "cardText": "今日已翻1张牌", 91 | "awardList": [{}, {}, {}] 92 | } 93 | } 94 | ``` 95 | 96 | 97 | ### 翻牌: 已翻牌 98 | 99 | ```json 100 | { 101 | "code": "0", 102 | "errorCode": "F301", 103 | "errorMessage": "已翻牌" 104 | } 105 | ``` 106 | -------------------------------------------------------------------------------- /reference/流量加油站.md: -------------------------------------------------------------------------------- 1 | ### 未登录 2 | 3 | ```json 4 | { 5 | "message": "用户未登录", 6 | "businessCode": "-99", 7 | "code": "3", 8 | "success": false 9 | } 10 | ``` 11 | 12 | ### 首页: 13 | 14 | ```json 15 | { 16 | "code": "0", 17 | "signInfo": { 18 | "signCode": "0", 19 | "message": "签到领取今日流量" 20 | }, 21 | "success": true 22 | } 23 | ``` 24 | 25 | ```json 26 | { 27 | "code": "0", 28 | "signInfo": { 29 | "signCode": "403", 30 | "message": "您今日已签到" 31 | }, 32 | "success": true 33 | } 34 | ``` 35 | 36 | ### 签到: 37 | 38 | ```json 39 | { 40 | "message": "您今日已签到", 41 | "errorMessage": "签到成功,流量+1M", 42 | "pin": "***", 43 | "data": 100, 44 | "code": "0", 45 | "success": true 46 | } 47 | ``` 48 | 49 | ```json 50 | { 51 | "errorMessage": "您已成功参与", 52 | "pin": "***", 53 | "errorCode": "302", 54 | "code": "0", 55 | "success": true 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyQt5==5.10.1 2 | pyquery 3 | requests 4 | --------------------------------------------------------------------------------