├── Dockerfile ├── README.md ├── __init__.py ├── _config.yaml ├── chat ├── itchatHelper.py ├── message.py └── wechat.py ├── coupon ├── jd.py ├── pdd.py ├── sn.py └── tb.py ├── images ├── 6050dfdc-dfef-43c0-94b8-33148f6f5bd8.jpg ├── jdyangli.jpg ├── pddyangli.jpg ├── suningyangli.jpg ├── yangli.jpg ├── 唯品会联盟API接入流程文档v1.9.pdf └── 苏宁联盟开放平台API接入操作指导2.7.pdf ├── main.py ├── requirements.txt └── untils ├── ad.py ├── common.py ├── config.py ├── jd_api.py ├── pdd_api.py ├── qq_nlpchat.py ├── scheduler.py ├── suo_im.py └── tb_top_api.py /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | 3 | MAINTAINER Snow Wang 4 | 5 | WORKDIR /youxiang 6 | COPY requirements.txt requirements.txt 7 | COPY . /youxiang 8 | 9 | ENV TZ=Asia/Shanghai 10 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone && \ 11 | mkdir /youxiang && \ 12 | pip install -r requirements.txt 13 | 14 | ENTRYPOINT ["python", "/youxiang/main.py"] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python 3.7](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/release/python-370/) 2 | [![itchat_vesion](https://img.shields.io/badge/itchat--uos-1.4.1-green)](https://github.com/why2lyj/ItChat-UOS) 3 | [![EverydayWechat](https://img.shields.io/badge/sfyc23/Powered%20By-EverydayWechat-brightgreen.svg)](https://github.com/sfyc23/EverydayWechat/) 4 | [![GitHub issues](https://img.shields.io/github/issues/why2lyj/youxiang.svg)](https://github.com/why2lyj/youxiang/issues) 5 | [![Page Views Count](https://badges.toozhao.com/badges/01EJD3TT6C1J7T283S3A3JZGE3/green.svg)](https://badges.toozhao.com/badges/01EJD3TT6C1J7T283S3A3JZGE3/green.svg) 6 | 7 | ## 项目背景 8 | 9 | 无非就是想撸羊毛,自己又懒的一个一个找,一个一个发。已知目前的返佣app非常的多,比如:好省,蜜源,粉象生活,高佣联盟,芬香,花生日记,惠鲸等等。归根到底无非是利用淘宝、京东、拼多多、苏宁的开放平台做的。所以想到是否可以利用已有的开放平台来做一个属于自己的撸羊毛项目。 10 | 11 | 其实说白了就是 : 12 | 1. 建立微信群 13 | 2. 向微信群里发送自己的推广链接和商品图片 14 | 3. 剩下的尽人事听天命了 15 | 16 | ## 功能说明 17 | 18 | 项目主要参考 [EverydayWechat](https://github.com/sfyc23/EverydayWechat) 19 | 20 | - 支持对多个微信好友自动回复。 (保留原[EverydayWechat](https://github.com/sfyc23/EverydayWechat)功能,自动回复仅保留**智能闲聊(腾讯)**) 21 | - 群助手功能,仅保留进群自动回复及@时自动回复功能。 22 | - 淘宝优惠券自动分发。 23 | > 创建定时任务,通过api获取淘宝推广客的优惠信息,发送到群聊。 24 | - 京东优惠券自动分发。 25 | > 创建定时任务,通过api获取京东联盟的优惠信息,发送到群聊。 26 | - 拼多多优惠券自动分发。 27 | > 创建定时任务,通过api获取多多进宝(多宝客)的优惠信息,发送到群聊。 28 | - 苏宁易购优惠券自动分发。 29 | > 创建定时任务,通过官方sdk获取苏宁联盟(苏宁推客)的优惠信息,发送到群聊。 30 | - 唯品会优惠券自动分发(未完成)。 31 | > 创建定时任务,通过官方sdk获取唯品会的优惠信息,发送到群聊。 32 | 33 | ## 对于微信Web端无法登陆的小伙伴请注意 34 | 35 | 请使用 ```pip install itchat-uos``` 更新后,web版本可用。 36 | 37 | 如手机确认登陆后出现 'wxsid' 登陆失败的字样,请将微信绑定手机号,绑定银行卡后再进行重试。 38 | 39 | 如果依然不可使用,请下载统信UOS系统,利用统信UOS系统的Web版本微信登陆一次。 40 | 41 | 若登陆成功即可使用本项目。若登陆不成功,请根据相关提示进行修正,直至登陆成功。 42 | 43 | 统信UOS系统的安装教程请查看[统信UOS系统安装教程](https://www.7uos.com/uos-ti-yan.html) 44 | 45 | ## 配置信息 46 | 47 | 仅介绍**推广客设置**,其余配置请参考[EverydayWechat](https://github.com/sfyc23/EverydayWechat),不做多余赘述。 48 | 49 | 参数说明: 50 | 51 | 淘宝联盟 52 | 53 | | 名称 | 示例 | 必填 | 说明 | 54 | | -------- | -------------- | ---------- |---------- | 55 | | is_open | True/False | 必填 | 是否开启淘宝联盟推广| 56 | | app_key | 淘宝联盟 app_key | 必填 | 淘宝联盟申请下来的 app_key | 57 | | app_secret | 淘宝联盟 app_secret | 必填 | 淘宝联盟申请下来的 app_secret | 58 | | adzone_id | 淘宝联盟广告位 | 必填 | 淘宝联盟推广中的广告位 | 59 | | chat_groups | | 必填 | 详情见举例 | 60 | | group_name | 群名称 | 必填 | 对应微信群的群名称 | 61 | | group_material_id | 物料id | 必填 | 淘宝联盟[material_id](https://market.m.taobao.com/app/qn/toutiao-new/index-pc.html#/detail/10628875?_k=gpov9a)| 62 | | minute | 分钟 | 必填 | 定时任务对应的分钟,逗号分隔,注意空格 | 63 | | hour | 小时 | 必填 | 定时任务对应的小时,逗号分隔,注意空格 | 64 | 65 | 京东联盟 66 | 67 | | 名称 | 示例 | 必填 | 说明 | 68 | | -------- | -------------- | ---------- |---------- | 69 | | is_open | True/False | 必填 | 是否开启京东联盟推广| 70 | | app_key | 京东联盟 app_key | 必填 | 京东联盟申请下来的 app_key | 71 | | app_secret | 京东联盟 app_secret | 必填 | 京东联盟申请下来的 app_secret | 72 | | site_id | 京东联盟网站id或app id | 必填 | 京东联网站id或app id | 73 | | chat_groups | | 必填 | 详情见举例 | 74 | | group_name | 群名称 | 必填 | 对应微信群的群名称 | 75 | | group_material_id | 物料id | 必填 | 京东联盟物料id| 76 | | minute | 分钟 | 必填 | 定时任务对应的分钟,逗号分隔,注意空格 | 77 | | hour | 小时 | 必填 | 定时任务对应的小时,逗号分隔,注意空格 | 78 | 79 | 拼多多(多多进宝、多多客) 80 | 81 | | 名称 | 示例 | 必填 | 说明 | 82 | | -------- | -------------- | ---------- |---------- | 83 | | is_open | True/False | 必填 | 是否开启拼多多推广| 84 | | app_key | 拼多多 Client_id | 必填 | 拼多多申请下来的 Client_id | 85 | | app_secret | 拼多多 Client_secret | 必填 | 拼多多申请下来的 Client_secret | 86 | | site_id | 推广位 | 必填 | 利用拼多多[接口](https://open.pinduoduo.com/application/document/apiTools?scopeName=pdd.ddk.goods.pid.generate&catId=12)得到的推广位`pid` | 87 | | chat_groups | | 必填 | 详情见举例 | 88 | | group_name | 群名称 | 必填 | 对应微信群的群名称 | 89 | | group_material_id | 栏目 | 非必填 | 保留字段,底层无用| 90 | | minute | 分钟 | 必填 | 定时任务对应的分钟,逗号分隔,注意空格 | 91 | | hour | 小时 | 必填 | 定时任务对应的小时,逗号分隔,注意空格 | 92 | 93 | 苏宁易购(苏宁推客) 94 | 95 | | 名称 | 示例 | 必填 | 说明 | 96 | | -------- | -------------- | ---------- |---------- | 97 | | is_open | True/False | 必填 | 是否开启苏宁推广| 98 | | app_key | 苏宁易购 appKey | 必填 | 苏宁易购开放平台新建应用的 appKey | 99 | | app_secret | 苏宁易购 secretKey | 必填 | 苏宁易购开放平台新建应用的 secretKey | 100 | | ad_book_id | 推广位 | 必填 | 利用苏宁联盟得到的推广位 | 101 | | chat_groups | | 必填 | 详情见举例 | 102 | | group_name | 群名称 | 必填 | 对应微信群的群名称 | 103 | | group_material_id | 栏目 | 非必填 | 保留字段,底层无用| 104 | | minute | 分钟 | 必填 | 定时任务对应的分钟,逗号分隔,注意空格 | 105 | | hour | 小时 | 必填 | 定时任务对应的小时,逗号分隔,注意空格 | 106 | 107 | **”实例1**,每天7点到23点,每小时的第10分,第40分,将淘宝物料id:19810,发送至群聊 <口碑KFC必胜客麦当劳优惠券>: 108 | > {group_name: '口碑KFC必胜客麦当劳优惠券', group_material_id: '19810', minute: '10,40', hour: '7-23'} 109 | 110 | **实例2**,每天7点,12点,15点的第30分,将淘宝物料id:3767,27448,13367,3788的优惠券,发送至群聊 <淘宝内部优惠群-女装类①> : 111 | > {group_name: '淘宝内部优惠群-女装类①', group_material_id: '3767,27448,13367,3788', minute: '30', hour: '9,12,15'} 112 | 113 | *提示* 在运行程序前确保群名已经有且已经保存到通讯录 114 | 115 | ## 前提准备 116 | 117 | --- 118 | 119 | 要使用淘宝联盟的api,需要三个东西:`App Key` , `App Secret`,广告位`adzone_id` 120 | 121 | 申请参考: 122 | 123 | 申请淘宝联盟api: 124 | [申请地址](https://pub.alimama.com/?spm=a219t.7664554.a214tr8.19.2f5835d9zBLGBR) 125 | [文档参考](https://open.taobao.com/doc.htm?docId=73&docType=1) 126 | 127 | 努力看文档操作,获取到 `App Key` 和 `App Secret`,同时利用商品推广得到 广告位 `adzone_id` 128 | 129 | --- 130 | 131 | 要使用京东联盟api,需要`App Key` , `App Secret`,站点ID`siteId`,还有一个suowo的`token` 132 | 133 | 申请参考: 134 | 135 | 申请京东联盟api: 136 | [申请地址](https://union.jd.com/) 137 | [文档参考](https://union.jd.com/helpcenter/13246-13247-46301) 138 | 139 | 要使用京东联盟获取推广优惠券需要有siteId(站点ID是指在联盟后台的推广管理中的网站Id、APPID),此申请需要网站备案或有实际app。如没有尽早申请。 140 | 141 | 另外由于京东联盟生成短址的接口需要申请,申请资质要求([参考](https://union.jd.com/helpcenter/13246-13247-46301))目前非力所能及,故采用[suo.mi](https://suowo.cn/)转换短址,区别如下: 142 | 143 | | 名称 | 短址示例 | 说明 | 144 | | -------- | -------------- | ---------- | 145 | | 京东短址 | [http://u.jd.com/XXXX](https://github.com/why2lyj/youxiang) | api申请门槛高| 146 | | 缩我短址 | [http://suo.mi/XXXX](https://github.com/why2lyj/youxiang) | 门槛低,免费| 147 | 148 | *关于短址:建议选择微信或腾讯的短址服务进行转换以免被屏,没用的另外原因是没有相关token,其他网络上的api没有遇到合适的。* 149 | 150 | *缩我短址在2020年7月更变域名suowo.cn,原有suo.mi依然可用,所以作者并无相关代码更变* 151 | 152 | --- 153 | 154 | 申请苏宁易购的api请直接参考以下文档,文档来自苏宁联盟的接口人: 155 | 156 | [苏宁联盟开放平台API接入操作指导2.7-20200526.pdf](images/苏宁联盟开放平台API接入操作指导2.7.pdf) 157 | 158 | --- 159 | 160 | 申请拼多多api接口,需要`Client_id`,`Client_secret`,推广位`pid` 161 | 162 | 申请拼多多(多多客)api: 163 | 164 | 首先去拼多多开放平台申请一个应用 [申请地址](https://open.pinduoduo.com/),得到`Client_id`和`Client_secret`,然后去多多进宝绑定`Client_id`后可以调用接口[接口文档](https://jinbao.pinduoduo.com/third-party/rank),利用接口得到推广位`pid` 165 | 166 | *拼多多接口每天调用仅5000次* 167 | 168 | --- 169 | 申请唯品会api: 170 | 171 | 申请唯品会只能是机构账户,机构账户的申请需要工商营业执照。如果没有营业执照的小伙伴,去[订单侠](https://www.dingdanxia.com/)申请调用api,这个是唯品会官方建议的。 172 | 173 | 如果你有工商营业执照,请查看文档继续申请[唯品会联盟API接入流程文档v1.9.pdf](images/唯品会联盟API接入流程文档v1.9.pdf) 174 | 175 | 吐槽下唯品会,申请贼费劲,审核极慢,提交申请近一个月,才有回复。最后是加了一位唯品会内部负责人的微信才问明白。 176 | 177 | 作者没有工商营业执照,所以...也不打算继续处理唯品会了。 178 | 179 | 有消息称唯品会将于2021年7月份开放个人开发者api,若开放,本项目会主动添加该功能。尽情知晓。 180 | 181 | ## 快速启动 182 | 183 | 直接下载此项目或 clone 项目到本地。 184 | 185 | 使用 pip 安装依赖: 186 | 187 | ``` 188 | pip3 install -r requirements.txt 189 | # 或者是使用 pip 190 | # pip install -r requirements.txt 191 | ``` 192 | 运行: 193 | ```python 194 | python main.py 195 | ``` 196 | 197 | 扫码后,即可使用。 198 | 199 | 如果你想使用docker启动(请确保`_config.yaml`文件已改成指定) 200 | 201 | 1. 首先创建镜像(请确保在项目所在目录中运行),执行 202 | ```shell 203 | docker build -f Dockerfile -t youxiang:v1.0.0 . 204 | ``` 205 | 206 | 2. 启动容器,运行 207 | ```shell 208 | docker run -it -d --name youxiang youxiang:1.0.0 209 | ``` 210 | 3. 运行以下脚本获取二维码,然后微信登陆 211 | ```shell 212 | docker logs -f --tail=1000 youxiang 213 | ``` 214 | 215 | 如果你不想每次都进容器改`_config.yaml`在第2步的时候可以将项目目录映射到本地 216 | ```shell 217 | docker run -it -d -v $pwd:/youxiang --name youxiang youxiang:1.0.0 218 | ``` 219 | ## 示例截图: 220 | --- 221 | 淘宝: 222 | 223 | ![发送淘宝优惠信息](https://github.com/why2lyj/youxiang/blob/master/images/yangli.jpg?raw=true) 224 | 225 | --- 226 | 京东: 227 | 228 | ![发送京东优惠信息](https://github.com/why2lyj/youxiang/blob/master/images/jdyangli.jpg?raw=true) 229 | 230 | --- 231 | 拼多多: 232 | 233 | ![发送拼多多优惠信息](https://github.com/why2lyj/youxiang/blob/master/images/pddyangli.jpg?raw=true) 234 | 235 | --- 236 | 苏宁易购: 237 | 238 | ![发送苏宁优惠信息](https://github.com/why2lyj/youxiang/blob/master/images/suningyangli.jpg?raw=true) 239 | 240 | ## 声明 241 | 242 | **禁止将本工具用于商业用途**,如产生法律纠纷与本人无关。 243 | 244 | 本项目已经完全迁移至非Web端版本(`python-wechaty`版本),后期仅维护bug,不再增添新的功能,还请各位小主知晓。 245 | 246 | ## Credits 致谢 247 | 248 | 本项目受以下项目或文章启发,参考了其中一部分思路,向这些开发者表示感谢。 249 | - [EverydayWechat](https://github.com/sfyc23/EverydayWechat) 250 | - [python 淘宝OPEN API 调用示例](https://www.jianshu.com/p/f9b5e3020789) 251 | 252 | ## 最后最后最后还是建个群什么的做下交流。留个二维码。 253 | 254 | 备注写【github】,否则不同过哦。 255 | ![加不加随意](https://github.com/why2lyj/youxiang/blob/master/images/6050dfdc-dfef-43c0-94b8-33148f6f5bd8.jpg?raw=true) 256 | 257 | ## 加个starchart,在此感谢您能够专心致志的读到这里,给项目点个赞吧~ 258 | [![Stargazers over time](https://starchart.cc/why2lyj/youxiang.svg)](https://starchart.cc/why2lyj/youxiang) 259 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'why2lyj' 3 | __email__ = 'admin@farseer.vip' -------------------------------------------------------------------------------- /_config.yaml: -------------------------------------------------------------------------------- 1 | # 配置文件请严格遵循 yaml 语法格式,yaml 学习地址: 2 | # https://ansible-tran.readthedocs.io/en/latest/docs/YAMLSyntax.html 3 | # http://einverne.github.io/post/2015/08/yaml.html 4 | 5 | wechat_uuid: '' 6 | 7 | #---------------------------------------自动回复功能设置--------------------------------------start 8 | auto_reply_info: 9 | # 是否开启自动回复 10 | is_auto_reply: False 11 | 12 | # 是否自动回复所有好友(慎重启动!),开启后,仅仅有黑名单的成员才不会自动回复 13 | is_auto_reply_all: False 14 | 15 | # 智能闲聊(腾讯)https://ai.qq.com/product/nlpchat.shtml。免费且无限量 16 | qqnlpchat_conf: 17 | app_id: '' 18 | app_key: '' 19 | 20 | # 自动回复消息前缀(不需要则设置为空) 21 | auto_reply_prefix: '' 22 | # 自动回复消息后缀(不需要则设置为空) 23 | auto_reply_suffix: '' 24 | 25 | # 是否允许通过关键字自动加好友 26 | is_auto_add_friend: True 27 | 28 | # 加好友时通过的关键字 29 | auto_add_friend_keywords: 30 | - '优惠群' 31 | - '加群' 32 | - '线报' 33 | 34 | # 白名单,is_auto_reply_all: False 生效。此名单的用户才可自动回复。(填:文件传输助手,可回复自己,测试效果) 35 | auto_reply_white_list: 36 | - '文件传输助手' 37 | 38 | # 黑名单,is_auto_reply_all:True 生效。此名单不会自动回复。 39 | auto_reply_black_list: 40 | - '' 41 | 42 | #---------------------------------------自动回复功能设置--------------------------------------end 43 | 44 | 45 | ##---------------------------群聊助手设置--------------------------start 46 | group_helper_conf: 47 | is_open: True # 开启群助手 48 | is_all: False # 是否对所有群开启。当开启时,只有黑名单的名单才不受影响(慎重开启!) 49 | 50 | # 白名单用户。当 is_all:False。只处理这个群里的消息 51 | group_name_white_list: 52 | - '淘宝天猫内部优惠群-综合类①' 53 | - '淘宝内部优惠群-潮流范①' 54 | - '淘宝内部优惠群-零食类①' 55 | - '淘宝内部优惠群-女装类①' 56 | - '口碑KFC必胜客麦当劳优惠券' 57 | - '京东内部优惠群-9.9专区①' 58 | - '拼多多内部优惠群①' 59 | 60 | # 黑名单用户。当 is_all :True 。这个群里的用户不受影响。 61 | group_name_black_list: 62 | - '' 63 | 64 | is_at: True # 艾特标记。只有当别人艾特机器人,才会处理消息(慎重关闭!) 65 | 66 | # 是否重复加入 67 | enter_mult_group: False 68 | 69 | group_admin: 70 | - '' ## 该微信聊天的管理员,管理发广告不被踢 71 | 72 | #--------------------------- 群聊助手设置 --------------------------end 73 | 74 | 75 | #--------------------------- 推广客设置 --------------------------start 76 | 77 | taobao: 78 | is_open: True 79 | app_key: '' 80 | app_secret: '' 81 | adzone_id: '' 82 | chat_groups: 83 | - {group_name: '淘宝天猫内部优惠群-综合类①', group_material_id: '3756,28026,27446,13366,3786', minute: '15,45', hour: '7-23'} 84 | - {group_name: '淘宝内部优惠群-潮流范①', group_material_id: '4093', minute: '20', hour: '7-23'} 85 | - {group_name: '口碑KFC必胜客麦当劳优惠券', group_material_id: '19810', minute: '30', hour: '7-23'} 86 | - {group_name: '淘宝内部优惠群-女装类①', group_material_id: '3767,27448,13367,3788', minute: '10,40', hour: '7-23'} 87 | - {group_name: '淘宝内部优惠群-零食类①', group_material_id: '13375,3761,27451,3791', minute: '10,40', hour: '7-23'} 88 | 89 | jingdong: 90 | is_open: False 91 | app_key: '' 92 | app_secret: '' 93 | site_id: '' 94 | suo_im: '' 95 | chat_groups: 96 | - {group_name: '京东内部优惠群-9.9专区①', group_material_id: '10', minute: '10,40', hour: '7-23'} 97 | 98 | pinduoduo: 99 | is_open: True 100 | app_key: '' 101 | app_secret: '' 102 | p_id: '' 103 | chat_groups: 104 | - {group_name: '拼多多内部优惠群①', group_material_id: '1,2', minute: '0,30', hour: '7-23'} 105 | 106 | suning: 107 | is_open: False 108 | app_key: '' 109 | app_secret: '' 110 | ad_book_id: '' 111 | chat_groups: 112 | - {group_name: '苏宁内部优惠群', group_material_id: '2179', minute: '5,35', hour: '7-23'} 113 | #--------------------------- 推广客设置 --------------------------end 114 | 115 | -------------------------------------------------------------------------------- /chat/itchatHelper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 4 | """ 5 | 6 | import itchat 7 | import re 8 | from datetime import datetime 9 | from datetime import timedelta 10 | from importlib import import_module 11 | from untils import config 12 | from untils.common import ( 13 | md5_encode, 14 | FILEHELPER_MARK, 15 | FILEHELPER, 16 | ) 17 | 18 | __all__ = ['init_wechat_config', 'set_system_notice', 'get_group', 'get_friend'] 19 | 20 | TIME_COMPILE = re.compile(r'^\s*([01]?[0-9]|2[0-3])\s*[::\-]\s*([0-5]?[0-9])\s*$') 21 | 22 | 23 | def init_wechat_config(): 24 | """ 初始化微信所需数据 """ 25 | # print('初始化微信所需数据开始..') 26 | # 从config copy ,用于保存新的接口内容。 27 | myset = config.copy() 28 | print('=' * 80) 29 | 30 | base_wechat_info = itchat.search_friends() # 获取此微信号的基础信息 31 | wechat_nick_name = base_wechat_info['NickName'] # 获取此微信号的昵称 32 | wechat_uuid = base_wechat_info['UserName'] # 获取此微信号的uuid 33 | myset['wechat_nick_name'] = wechat_nick_name 34 | myset['wechat_uuid'] = wechat_uuid 35 | 36 | # start---------------------------处理自动回复好友---------------------------start 37 | reply = myset.get('auto_reply_info') 38 | if reply is not None and reply.get('is_auto_reply'): 39 | if reply.get('is_auto_reply_all'): 40 | auto_reply_list_key = 'auto_reply_black_list' 41 | auto_reply_list_uuid_name = 'auto_reply_black_uuids' 42 | else: 43 | auto_reply_list_key = 'auto_reply_white_list' 44 | auto_reply_list_uuid_name = 'auto_reply_white_uuids' 45 | 46 | auto_reply_uuids_list = [] 47 | for name in reply.get(auto_reply_list_key): 48 | if not name.strip(): 49 | continue 50 | if name.lower() in FILEHELPER_MARK: # 判断是否文件传输助手 51 | auto_reply_uuids_list.append(FILEHELPER) 52 | continue 53 | friend = get_friend(name) 54 | if friend: 55 | auto_reply_uuids_list.append(friend['UserName']) 56 | else: 57 | print('自动回复中的好友昵称『{}』有误。'.format(name)) 58 | reply[auto_reply_list_uuid_name] = set(auto_reply_uuids_list) 59 | # print('已开启图灵自动回复...') 60 | 61 | # end---------------------------处理自动回复好友---------------------------end 62 | 63 | # start ----------------------------------- 群功能初始化 ----------------------------------- start 64 | helper = myset.get('group_helper_conf') 65 | if helper is not None and helper.get('is_open'): 66 | if helper.get('is_all', False): 67 | group_list_key = 'group_name_black_list' 68 | group_list_uuid_name = 'group_black_uuids' 69 | else: 70 | group_list_key = 'group_name_white_list' 71 | group_list_uuid_name = 'group_white_uuids' 72 | group_uuid_list = [] 73 | for name in helper.get(group_list_key): 74 | if not name.strip(): 75 | continue 76 | group = get_group(name) 77 | if group: 78 | group_uuid_list.append(group['UserName']) 79 | else: 80 | print('群助手中的群聊名称『{}』有误。' 81 | '(注意:必须要把需要的群聊保存到通讯录)'.format(name)) 82 | helper[group_list_uuid_name] = set(group_uuid_list) 83 | # end ----------------------------------- 群功能初始化 ----------------------------------- end 84 | 85 | alarm = myset.get('alarm_info') 86 | alarm_dict = {} 87 | if alarm is not None and alarm.get('is_alarm'): 88 | for gi in alarm.get('girlfriend_infos'): 89 | ats = gi.get('alarm_timed') 90 | if not ats: 91 | continue 92 | uuid_list = [] 93 | nickname_list = [] 94 | # start---------------------------处理好友---------------------------start 95 | friends = gi.get('wechat_name') 96 | if isinstance(friends, str): 97 | friends = [friends] 98 | if isinstance(friends, list): 99 | for name in friends: 100 | if name.lower() in FILEHELPER_MARK: # 判断是否文件传输助手 101 | uuid_list.append(FILEHELPER) 102 | nickname_list.append(name) 103 | continue 104 | name_info = get_friend(name) 105 | if not name_info: 106 | print('定时提醒中的好友昵称『{}』无效'.format(name)) 107 | else: 108 | uuid_list.append(name_info['UserName']) 109 | nickname_list.append(name) 110 | # end---------------------------处理好友---------------------------end 111 | 112 | # start---------------------------群组处理---------------------------start 113 | group_names = gi.get('group_name') 114 | if isinstance(group_names, str): 115 | group_names = [group_names] 116 | if isinstance(group_names, list): 117 | for name in group_names: 118 | name_info = get_group(name) 119 | if not name_info: 120 | print('定时任务中的群聊名称『{}』有误。' 121 | '(注意:必须要把需要的群聊保存到通讯录)'.format(name)) 122 | else: 123 | uuid_list.append(name_info['UserName']) 124 | nickname_list.append(name) 125 | # end---------------------------群组处理---------------------------end 126 | 127 | # start---------------------------定时处理---------------------------start 128 | 129 | if isinstance(ats, str): 130 | ats = [ats] 131 | if isinstance(ats, list): 132 | for at in ats: 133 | times = TIME_COMPILE.findall(at) 134 | if not times: 135 | print('时间{}格式出错'.format(at)) 136 | continue 137 | hour, minute = int(times[0][0]), int(times[0][1]) 138 | temp_dict = {'hour': hour, 'minute': minute, 'uuid_list': uuid_list, 'nickname_list': nickname_list} 139 | temp_dict.update(gi) 140 | alarm_dict[md5_encode(str(temp_dict))] = temp_dict 141 | # end---------------------------定时处理---------------------------end 142 | alarm['alarm_dict'] = alarm_dict 143 | 144 | # 将解析的数据保存于 config 中。 145 | config.update(myset) 146 | # print(json.dumps(alarm_dict, ensure_ascii=False)) 147 | # print('初始化微信所需数据结束..') 148 | 149 | # log_all_config() 150 | 151 | 152 | def set_system_notice(text): 153 | """ 154 | 给文件传输助手发送系统日志。 155 | :param text:str 日志内容 156 | """ 157 | if text: 158 | text = '系统通知:' + text 159 | itchat.send(text, toUserName=FILEHELPER) 160 | 161 | 162 | def get_group(group_name, update=False): 163 | """ 164 | 根据群组名获取群组数据 165 | :param group_name:str, 群组名 166 | :param update: bool 强制更新群组数据 167 | :return: obj 单个群组信息 168 | """ 169 | if update: itchat.get_chatrooms(update=True) 170 | if not group_name: return None 171 | groups = itchat.search_chatrooms(name=group_name) 172 | if not groups: return None 173 | return groups[0] 174 | 175 | 176 | def get_friend(wechat_name, update=False): 177 | """ 178 | 根据用户名获取用户数据 179 | :param wechat_name: str 用户名 180 | :param update: bool 强制更新用户数据 181 | :return: obj 单个好友信息 182 | """ 183 | if update: itchat.get_friends(update=True) 184 | if not wechat_name: return None 185 | friends = itchat.search_friends(name=wechat_name) 186 | if not friends: return None 187 | return friends[0] 188 | 189 | 190 | def get_mps(mp_name, update=False): 191 | """ 192 | 根据公众号的名称获取用户数据 193 | :param mp_name: str 用户名 194 | :param update: bool 强制更新用户数据 195 | :return: obj 单个公众号信息 196 | """ 197 | if update: itchat.get_mps(update=True) 198 | if not mp_name: return None 199 | mps = itchat.search_mps(name=mp_name) 200 | if not mps: return None 201 | # mpuuid = mps[0]['UserName'] 公众号的uuid 202 | return mps[0] 203 | 204 | 205 | # import pysnooper 206 | # @pysnooper.snoop() 207 | def log_all_config(): 208 | """ 209 | 用于打印设置日志 210 | :return: 211 | """ 212 | print('=' * 80) 213 | channel = config.get('auto_reply_info').get('bot_channel', 7) 214 | source = 'ownthink_robot' 215 | addon = import_module('everyday_wechat.control.bot.' + source, __package__) 216 | bot_name = addon.BOT_NAME 217 | print('自动回复机器人渠道:{}'.format(bot_name)) 218 | 219 | # start ----------------------------------- 微信好友自动回复的功能日志 ----------------------------------- start 220 | reply = config.get('auto_reply_info', None) 221 | if not reply or not reply.get('is_auto_reply'): 222 | print('未开启微信好友自动回复。') 223 | else: 224 | if reply.get('is_auto_reply_all'): 225 | auto_uuids = reply.get('auto_reply_black_uuids') 226 | nicknames = [] 227 | for auid in auto_uuids: 228 | if auid == 'filehelper': 229 | nicknames.append(auid) 230 | else: 231 | friends = itchat.search_friends(userName=auid) 232 | nickname = friends.nickName 233 | nicknames.append(nickname) 234 | nns = ','.join(nicknames) 235 | print('开启对全部微信好友全部回复,除了:{}'.format(nns)) 236 | else: 237 | auto_uuids = reply.get('auto_reply_white_uuids') 238 | nicknames = [] 239 | for auid in auto_uuids: 240 | if auid == 'filehelper': 241 | nicknames.append(auid) 242 | else: 243 | friends = itchat.search_friends(userName=auid) 244 | nickname = friends.nickName 245 | nicknames.append(nickname) 246 | nns = ','.join(nicknames) 247 | print('对微信好友 {},进行自动回复'.format(nns)) 248 | 249 | print('=' * 80) 250 | 251 | # start ----------------------------------- 群功能日志说明 ----------------------------------- start 252 | helper = config.get('group_helper_conf') 253 | if not helper or not helper.get('is_open'): 254 | print('未开启群助手功能。') 255 | else: 256 | if helper.get('is_all'): 257 | auto_uuids = helper.get('group_black_uuids') 258 | nicknames = [] 259 | for auid in auto_uuids: 260 | chatrooms = itchat.search_chatrooms(userName=auid) 261 | nickname = chatrooms['NickName'] # 群聊名称 262 | nicknames.append(nickname) 263 | nns = ','.join(nicknames) 264 | print('已开启对全部微信群的监听,除了群:{}。'.format(nns)) 265 | else: 266 | auto_uuids = helper.get('group_white_uuids') 267 | nicknames = [] 268 | for auid in auto_uuids: 269 | chatroom = itchat.search_chatrooms(userName=auid) 270 | nickname = chatroom['NickName'] # 群聊名称 271 | nicknames.append(nickname) 272 | nns = ','.join(nicknames) 273 | 274 | print('已对微信群:{},开启了群助手功能。'.format(nns)) 275 | 276 | if helper.get('is_at'): 277 | print('只有群里用户@机器人,才会触发群助手功能。') 278 | if helper.get('is_auto_reply'): 279 | print('已开启对微信群内用户的自动回复。') 280 | if helper.get('is_weather'): 281 | print('已开启天气查询功能,具体使用方法请输入:“help” 查看。') 282 | if helper.get('is_calendar'): 283 | print('已开启日志查询功能,具体使用方法请输入:“help” 查看。') 284 | if helper.get('is_rubbish'): 285 | print('已开启垃圾分类查询功能,具体使用方法请输入:“help” 查看。') 286 | if helper.get('is_moviebox'): 287 | print('已开启票房查询功能,具体使用方法请输入:“help” 查看。') 288 | if helper.get('is_air_quality'): 289 | print('已开启空气质量查询功能,具体使用方法请输入:“help” 查看。') 290 | 291 | print('=' * 80) 292 | 293 | # start ----------------------------------- 提醒功能的日志说明 ----------------------------------- start 294 | alarm = config.get('alarm_info') 295 | if not alarm or not alarm.get('is_alarm'): 296 | print('未开启每日提醒功能。') 297 | else: 298 | print('已开启定时发送提醒功能。') 299 | alarm_dict = alarm.get('alarm_dict') 300 | for value in alarm_dict.values(): 301 | nickname_list = value.get('nickname_list') 302 | nns = ','.join(nickname_list) 303 | # temp_dict = {'hour': hour, 'minute': minute, 'uuid_list': uuid_list, 'nickname_list': nickname_list} 304 | hour = value.get('hour') 305 | minute = value.get('minute') 306 | alarm_time = "{hour:0>2d}:{minute:0>2d}".format(hour=hour, minute=minute) 307 | 308 | # 计算在哪个区间给朋友发送信息 309 | jitter = value.get("alarm_jitter", 0) 310 | if jitter != 0: 311 | set_time = datetime.strptime(alarm_time, '%H:%M') 312 | jitter_time = timedelta(seconds=jitter) 313 | start_time = (set_time - jitter_time).strftime("%H:%M") 314 | end_time = (set_time + jitter_time).strftime("%H:%M") 315 | alarm_time = "{start_time}——{end_time} 期间".format(start_time=start_time, end_time=end_time) 316 | 317 | print('定时:{alarm_time},给:{nicknames},发送提醒内容一次。'.format(alarm_time=alarm_time, nicknames=nns)) 318 | 319 | print('=' * 80) 320 | 321 | # 判断传入的uuid,是否属于我们的群 322 | def is_white_group(uuid) -> bool: 323 | ''' 324 | 判断传入的uuid,是否属于我们的群 325 | :param uuid: 326 | :return: 327 | ''' 328 | helper = config.get('group_helper_conf') 329 | auto_uuids = helper.get('group_name_white_list') 330 | 331 | for auid in auto_uuids: 332 | chatrooms = itchat.search_chatrooms(name=auid) 333 | print(f'''room: {chatrooms}''') 334 | for chat in chatrooms: 335 | if chat.UserName == str(uuid): 336 | return True 337 | return False -------------------------------------------------------------------------------- /chat/message.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 4 | """ 5 | import re 6 | import time 7 | import random 8 | import itchat 9 | from untils import config 10 | from untils.common import del_pic 11 | from untils.ad import QRcode_detection 12 | from chat.itchatHelper import set_system_notice, is_white_group 13 | from untils.common import FILEHELPER 14 | from untils.qq_nlpchat import get_auto_reply 15 | 16 | __all__ = ['handle_friends_message', 'handle_groups_message'] 17 | 18 | at_compile = r'(@.*?\s{1,}).*?' 19 | 20 | help_complie = r'^(?:0|帮忙|帮助|help)\s*$' 21 | 22 | common_msg = '@{ated_name}\u2005\n{text} \n ----机器人智能回复 \n 您如有事儿加机器人问话吧' 23 | 24 | help_group_content = """@{ated_name} 25 | 群助手功能: 26 | 27 | 有啥事儿问管理员吧,群功能不开放 28 | 29 | """ 30 | 31 | def handle_friends_message(msg): 32 | """ 处理好友信息 """ 33 | try: 34 | # 自己通过手机微信发送给别人的消息(文件传输助手除外)不作处理。 35 | if msg['FromUserName'] == config.get('wechat_uuid') and msg['ToUserName'] != FILEHELPER: 36 | return 37 | 38 | conf = config.get('auto_reply_info') 39 | if not conf.get('is_auto_reply'): 40 | return 41 | # 获取发送者的用户id 42 | uuid = FILEHELPER if msg['ToUserName'] == FILEHELPER else msg['FromUserName'] 43 | is_all = conf.get('is_auto_reply_all') 44 | auto_uuids = conf.get('auto_reply_black_uuids') if is_all else conf.get('auto_reply_white_uuids') 45 | # 开启回复所有人,当用户是黑名单,不回复消息 46 | if is_all and uuid in auto_uuids: 47 | return 48 | 49 | # 关闭回复所有人,当用户不是白名单,不回复消息 50 | if not is_all and uuid not in auto_uuids: 51 | return 52 | 53 | receive_text = msg.text # 好友发送来的消息内容 54 | # 好友叫啥,用于打印 55 | nick_name = FILEHELPER if uuid == FILEHELPER else msg.user.nickName 56 | reply_text = get_auto_reply(receive_text, uuid) # 获取自动回复 57 | if reply_text: # 如内容不为空,回复消息 58 | time.sleep(random.randint(1, 2)) # 休眠一秒,保安全。想更快的,可以直接注释。 59 | 60 | prefix = conf.get('auto_reply_prefix', '') # 前缀 61 | if prefix: 62 | reply_text = '{}{}'.format(prefix, reply_text) 63 | 64 | suffix = conf.get('auto_reply_suffix', '') # 后缀 65 | if suffix: 66 | reply_text = '{}{}'.format(reply_text, suffix) 67 | 68 | itchat.send(reply_text, toUserName=uuid) 69 | else: 70 | set_system_notice(f'''自动回复失败:\n『{nick_name}』发来信息:{receive_text} \n''') 71 | except Exception as exception: 72 | print(str(exception)) 73 | 74 | 75 | def handle_groups_message(msg): 76 | """ 77 | 处理群消息, 78 | :param msg: 79 | :return: 80 | """ 81 | 82 | uuid = msg.fromUserName # 群 uid 83 | ated_uuid = msg.actualUserName # 艾特你的用户的uuid 84 | ated_name = msg.actualNickName # 艾特你的人的群里的名称 85 | # print(msg) 86 | 87 | # 自己通过手机端微信发出的消息不作处理 88 | if ated_uuid == config.get('wechat_uuid'): 89 | return 90 | 91 | conf = config.get('group_helper_conf') 92 | text = msg['Text'] # 发送到群里的消息。 93 | # 如果是我们的群,不是管理组人员发送的消息,且长度大于30,直接踢 94 | # 我们认为 wechat 中一个人的聊天长度不会超过30, 95 | # 特别是对于一个优惠券群来说, 96 | # 一个可跟你说30个字以上的又如此“谆谆教导”的人值得被踢 97 | # 对于广告,宁可错杀不放过一个,随有极端但不菲是一种办法 98 | # TODO 此处需要文本审核,但市面上的文本审核都付费,如有免费的请及时通知我 99 | group_admins = conf.get('group_admin') 100 | if is_white_group(uuid) and (ated_name not in group_admins): 101 | if len(str(text)) >= 30: 102 | itchat.delete_member_from_chatroom(uuid, [{'UserName': msg.ActualUserName}]) 103 | 104 | if not conf.get('is_open'): 105 | return 106 | 107 | # 如果开启了 『艾特才回复』,而群用户又没有艾特你。走垃圾分类 108 | if conf.get('is_at') and not msg.isAt: 109 | return 110 | 111 | is_all = conf.get('is_all', False) 112 | group_uuids = conf.get('group_name_black_list') if is_all else conf.get('group_name_white_list') 113 | # 开启回复所有群,而群组是黑名单,不处理消息 114 | if is_all: 115 | for group_name in group_uuids: 116 | chatrooms = itchat.search_chatrooms(name=f'''{group_name}''') 117 | # print(chatrooms) 118 | for room in chatrooms: 119 | if uuid == room['UserName']: 120 | return 121 | 122 | # 未开启回复所有群,而群组不是白名单,不处理消息 123 | in_white_flag = False 124 | if not is_all: 125 | for group_name in group_uuids: 126 | chatrooms = itchat.search_chatrooms(name=f'''{group_name}''') 127 | for room in chatrooms: 128 | if uuid == room['UserName']: 129 | in_white_flag = True 130 | if not in_white_flag: 131 | return 132 | 133 | # 去掉 at 标记 134 | text = re.sub(at_compile, '', text) 135 | 136 | reply_text = get_auto_reply(text, ated_uuid) # 获取自动回复 137 | if reply_text: # 如内容不为空,回复消息 138 | reply_text = common_msg.format(ated_name=ated_name, text=reply_text) 139 | itchat.send(reply_text, uuid) 140 | # print('回复{}:{}'.format(ated_name, reply_text)) 141 | else: 142 | print('自动回复失败\n') 143 | 144 | def handle_group_pictures(msg): 145 | ''' 146 | :return: 147 | ''' 148 | 149 | # 自己通过手机微信发送给别人的消息(文件传输助手除外)不作处理。 150 | if msg['FromUserName'] == config.get('wechat_uuid') and msg['ToUserName'] != FILEHELPER: 151 | return 152 | # 判断是否来自指定群 153 | uuid = msg.fromUserName # 群 uid 154 | # print(f'''这个群聊的id是{uuid}''') 155 | # ated_uuid = msg.actualUserName # 发送人的用户uuid 156 | # ated_name = msg.actualNickName # 发送人群里的名称 157 | # file_name = msg['FileName'] # 文件默认文件名 158 | msg.download(msg.fileName) 159 | if is_white_group(uuid): 160 | if QRcode_detection(msg.fileName): 161 | itchat.delete_member_from_chatroom(msg.FromUserName, [{'UserName': msg.ActualUserName}]) 162 | del_pic(msg.fileName) -------------------------------------------------------------------------------- /chat/wechat.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | """ 4 | 5 | """ 6 | import time 7 | import platform 8 | import os 9 | import re 10 | import random 11 | import itchat 12 | from itchat.content import ( 13 | TEXT, 14 | FRIENDS, 15 | NOTE, 16 | PICTURE 17 | ) 18 | 19 | from collections import OrderedDict 20 | from untils import config 21 | from untils.scheduler import job_tasks 22 | from chat.itchatHelper import init_wechat_config, set_system_notice 23 | from chat.message import handle_friends_message, handle_groups_message, handle_group_pictures 24 | 25 | __all__ = ['run', 'delete_cache'] 26 | 27 | group_infos_dict = OrderedDict() # 群信息字典 28 | 29 | def run(): 30 | """ 主运行入口 """ 31 | conf = config.init() 32 | # conf = get_yaml() 33 | if not conf: # 如果 conf,表示配置文件出错。 34 | print('程序中止...') 35 | return 36 | # 判断是否登录,如果没有登录则自动登录,返回 False 表示登录失败 37 | print('开始登录...') 38 | if not is_online(auto_login=True): 39 | print('程序已退出...') 40 | return 41 | 42 | 43 | def is_online(auto_login=False): 44 | """ 45 | 判断是否还在线。 46 | :param auto_login: bool,当为 Ture 则自动重连(默认为 False)。 47 | :return: bool,当返回为 True 时,在线;False 已断开连接。 48 | """ 49 | 50 | def _online(): 51 | """ 52 | 通过获取好友信息,判断用户是否还在线。 53 | :return: bool,当返回为 True 时,在线;False 已断开连接。 54 | """ 55 | try: 56 | if itchat.search_friends(): 57 | return True 58 | except IndexError: 59 | return False 60 | return True 61 | 62 | if _online(): return True # 如果在线,则直接返回 True 63 | if not auto_login: # 不自动登录,则直接返回 False 64 | print('微信已离线..') 65 | return False 66 | 67 | hotReload = False # 68 | loginCallback = init_data 69 | exitCallback = exit_msg 70 | try: 71 | for _ in range(2): # 尝试登录 2 次。 72 | if platform.system() in ('Windows', 'Darwin'): 73 | itchat.auto_login(hotReload=hotReload, 74 | loginCallback=loginCallback, exitCallback=exitCallback) 75 | itchat.run(blockThread=True) 76 | else: 77 | # 命令行显示登录二维码。 78 | itchat.auto_login(enableCmdQR=2, hotReload=hotReload, loginCallback=loginCallback, 79 | exitCallback=exitCallback) 80 | itchat.run(blockThread=True) 81 | if _online(): 82 | print('登录成功') 83 | return True 84 | except Exception as exception: # 登录失败的错误处理。 85 | sex = str(exception) 86 | if sex == "'User'": 87 | print('此微信号不能登录网页版微信,不能运行此项目。没有任何其它解决办法!可以换个号再试试。') 88 | else: 89 | print(sex) 90 | 91 | delete_cache() # 清理缓存数据 92 | print('登录失败。') 93 | return False 94 | 95 | 96 | def delete_cache(): 97 | """ 清除缓存数据,避免下次切换账号时出现 """ 98 | file_names = ('QR.png', 'itchat.pkl') 99 | for file_name in file_names: 100 | if os.path.exists(file_name): 101 | os.remove(file_name) 102 | 103 | 104 | def init_data(): 105 | """ 初始化微信所需数据 """ 106 | set_system_notice('登录成功') 107 | itchat.get_friends(update=True) # 更新好友数据。 108 | itchat.get_chatrooms(update=True) # 更新群聊数据。 109 | 110 | conf = config.get('group_helper_conf') 111 | group_name_list = conf.get('group_name_white_list') 112 | 113 | init_chatsroom(group_name_list) 114 | init_wechat_config() # 初始化所有配置内容 115 | init_alarm() 116 | 117 | print('初始化完成,开始正常工作。') 118 | 119 | def init_chatsroom(group_name_list): 120 | 121 | uidlist_compile = re.compile( 122 | r"(?3万单 或 月点击量>30万次 7 | 8 | c) 如开通后2个自然月内如无返回数据,或月订单量达不到要求的,联盟平台将可以关闭高级权限。 9 | 10 | 11 | 就上面第二条是我未来所期待的,所以折中选了个其他短址的 12 | ''' 13 | 14 | import random 15 | import time 16 | import json 17 | from untils.jd_api import JdApiClient 18 | from untils.suo_im import Suo_mi 19 | from untils.common import save_pic, del_pic 20 | import itchat 21 | from chat.itchatHelper import set_system_notice 22 | 23 | def jingfen_query(group_name:str, group_material_id:str, app_key:str, secret_key:str, site_id:str, suo_mi_token:str): 24 | ''' 方法效率不咋地,不管了 25 | https://union.jd.com/openplatform/api/10421 26 | :return: 27 | ''' 28 | info = [] 29 | try: 30 | page_no = str(random.randint(1, 25)) 31 | page_size = str(random.randint(3, 5)) # 不建议发很多,图片接口会跪 32 | 33 | client = JdApiClient(app_key=app_key, secret_key=secret_key) 34 | resp = client.call("jd.union.open.goods.jingfen.query", 35 | {"goodsReq": 36 | {"sort": "desc", 37 | "pageSize": page_size, 38 | "pageIndex": page_no, 39 | "eliteId": group_material_id 40 | }}) 41 | except Exception as e: 42 | print(e) 43 | set_system_notice(f'''page_no: {page_no},\npage_size:{page_size}\n, eliteId:{group_material_id}\n发现问题''') 44 | jingfen_query(group_name, group_material_id, app_key, secret_key, site_id, suo_mi_token) 45 | 46 | # pprint.pprint(json.loads(resp.json()['jd_union_open_goods_jingfen_query_response']['result'])) 47 | for data in json.loads(resp.json()['jd_union_open_goods_jingfen_query_response']['result'])['data']: 48 | print(data) 49 | sku_name = data['skuName'] ## 商品全名 50 | sku_id = data['skuId'] ## 商品 sku 51 | material_url = f'''http://{(data['materialUrl'])}''' ## 商品url 52 | 53 | couponInfos = data['couponInfo'] ## 优惠券列表 54 | # 查找最优优惠券 55 | coupon_link = "" 56 | discount = 0 57 | share_text = "" 58 | lowest_price_type = data['priceInfo']['lowestPriceType'] ## 什么类型 59 | is_coupon = False 60 | for couponInfo in couponInfos['couponList']: 61 | if 'isBest' in couponInfo: 62 | if int(couponInfo['isBest']) == 1: 63 | discount = couponInfo['discount'] ## 优惠券额度 64 | coupon_link = couponInfo['link'] ## 优惠券领取地址 65 | is_coupon = True 66 | else: 67 | discount = couponInfo['discount'] ## 优惠券额度 68 | coupon_link = couponInfo['link'] ## 优惠券领取地址 69 | is_coupon = True 70 | 71 | if is_coupon: # 如果有券 72 | if lowest_price_type == 3: # 秒杀 73 | price = data['seckillInfo']['seckillOriPrice'] # 原价 74 | lowest_price = data['priceInfo']['lowestCouponPrice'] # 秒杀价 75 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 76 | share_text = f'''【秒杀】{sku_name}\n——————————\n 【原价】¥{price}\n 【券后秒杀价】¥{lowest_price}\n抢购地址:{duanzhi}''' 77 | elif lowest_price_type == 2: # 拼购 78 | price = data['priceInfo']['price'] # 原价 79 | lowest_price = data['priceInfo']['lowestCouponPrice'] # 用券拼购 80 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 81 | share_text = f'''【拼购】{sku_name}\n——————————\n 【原价】¥{price}\n 【券后拼购价】¥{lowest_price}\n抢购地址:{duanzhi}''' 82 | else: 83 | price = data['priceInfo']['price'] ## 商品价格 84 | lowest_price = data['priceInfo']['lowestCouponPrice'] 85 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 86 | share_text = f'''【京东】{sku_name}\n——————————\n 【爆款价】¥{price}\n 【用卷价】¥{lowest_price}\n抢购地址:{duanzhi}''' 87 | 88 | 89 | else: ## 如果没有券 90 | if lowest_price_type == 3: # 秒杀 91 | price = data['seckillInfo']['seckillOriPrice'] # 原价 92 | lowest_price = data['seckillInfo']['seckillPrice'] # 秒杀价 93 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 94 | share_text = f'''【秒杀】{sku_name}\n——————————\n 【原价】¥{price}\n 【秒杀价】¥{lowest_price}\n抢购地址:{duanzhi}''' 95 | 96 | elif lowest_price_type == 2: # 拼购 97 | price = data['priceInfo']['price'] # 原价 98 | lowest_price = data['priceInfo']['lowestPrice'] # 用券拼购 99 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 100 | share_text = f'''【拼购】{sku_name}\n——————————\n 【原价】¥{price}\n 【拼购价】¥{lowest_price}\n抢购地址:{duanzhi}''' 101 | else: 102 | lowest_price = data['priceInfo']['price'] 103 | # 得到短址 104 | duanzhi = tb_share_text(app_key, secret_key, material_url, coupon_link, site_id, suo_mi_token) 105 | share_text = f'''【京东】{sku_name}\n——————————\n 【爆款价】¥{lowest_price}\n抢购地址:{duanzhi}''' 106 | 107 | ## 获取 images 108 | image_list = [] 109 | images_count = 0 110 | for image in data['imageInfo']['imageList']: 111 | images_count += 1 112 | if images_count > 3: ## 3个以上图片就不发了 113 | pass 114 | else: 115 | image_url = image['url'] 116 | filename = save_pic(image_url, sku_id) 117 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 118 | for room in groups: 119 | room_name = room['UserName'] 120 | time.sleep(random.randint(5,10)) 121 | itchat.send('@img@%s' % (f'''{filename}'''), room_name) 122 | del_pic(filename) 123 | # print(image_url) 124 | 125 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 126 | for room in groups: 127 | room_name = room['UserName'] 128 | time.sleep(random.randint(3, 5)) 129 | itchat.send(share_text, room_name) 130 | 131 | def tb_share_text(app_key, secret_key, material_url, coupon_url, site_id, suo_mi_token): 132 | ''' 133 | :param material_url: 物料的url 134 | :param coupon_url: 优惠券的url 135 | :param site_id: 网站id 136 | :param suo_mi_token: suo_mi网站的token 137 | :return: string ,返回一个suo_mi的短址 138 | ''' 139 | print(f'''{app_key}''') 140 | print(f'''{secret_key}''') 141 | print(f'''{material_url}''') 142 | print(f'''{coupon_url}''') 143 | print(f'''{site_id}''') 144 | print(f'''{suo_mi_token}''') 145 | client = JdApiClient(app_key=app_key, secret_key=secret_key) 146 | if coupon_url == "": 147 | resp = client.call("jd.union.open.promotion.common.get", 148 | {"promotionCodeReq": 149 | { 150 | "siteId": site_id, 151 | "materialId": material_url 152 | }}) 153 | else: 154 | resp = client.call("jd.union.open.promotion.common.get", 155 | {"promotionCodeReq": 156 | { 157 | "siteId": site_id, 158 | "materialId": material_url, 159 | "couponUrl": coupon_url 160 | }}) 161 | try: 162 | x = json.loads(resp.json()['jd_union_open_promotion_common_get_response']['result'])['data']['clickURL'] 163 | except Exception as e: 164 | print(f'''转码异常:{resp.json()}\n material_url: {material_url} \n coupon_url: {coupon_url}''') 165 | x = material_url 166 | # 直接返回短址 167 | url = x 168 | c = Suo_mi(app_key=suo_mi_token).get_short_url(url) 169 | return c 170 | 171 | if __name__ == '__main__': 172 | pass 173 | # jingfen_query() 174 | -------------------------------------------------------------------------------- /coupon/pdd.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import itchat 4 | import random 5 | from untils.common import save_pic, del_pic 6 | from untils.pdd_api import PddApiClient 7 | from chat.itchatHelper import set_system_notice 8 | 9 | def pdd_share_text(group_name: str, group_material_id: str, app_key:str, secret_key:str, p_id: str): 10 | ''' 11 | :param group_name: 12 | :param material_id: 13 | :return: 14 | ''' 15 | try: 16 | offset = str(random.randint(1, 295)) # top.goods.list.query 好像只有300个商品 17 | limit = str(random.randint(3, 5)) # 18 | 19 | client = PddApiClient(app_key=app_key, secret_key=secret_key) 20 | resp = client.call("pdd.ddk.top.goods.list.query", 21 | {"offset": offset, 22 | "limit": limit, 23 | "p_id": p_id 24 | }) 25 | except Exception as e: 26 | print(e) 27 | set_system_notice(f'''offset: {offset},\nlimit:{limit}\n\n发现问题''') 28 | pdd_share_text(group_name, group_material_id, app_key, secret_key, secret_key, p_id) 29 | 30 | for data in json.loads(resp.text)['top_goods_list_get_response']['list'] : 31 | goods_id = data['goods_id'] 32 | goods_name = data['goods_name'] 33 | search_id = data['search_id'] 34 | goods_thumbnail_url = data['goods_thumbnail_url'] 35 | min_normal_price = int(data['min_normal_price']) # 原价 36 | min_group_price = int(data['min_group_price']) # 折扣价 37 | coupon_discount = int(data['coupon_discount']) # 券价 38 | if min_group_price < min_normal_price: 39 | cal_price = min_group_price 40 | else: 41 | cal_price = min_normal_price 42 | cal_price_str = str(cal_price)[:len(str(cal_price))-2] if len(str(cal_price)[:len(str(cal_price))-2]) > 0 else '0' + '.' + str(cal_price)[len(str(cal_price))-2:] 43 | price = str(cal_price - coupon_discount)[:len(str(cal_price - coupon_discount))-2] \ 44 | if len(str(cal_price - coupon_discount)[:len(str(cal_price - coupon_discount))-2]) > 0 else '0'+ '.' \ 45 | + str(cal_price - coupon_discount)[len(str(cal_price - coupon_discount))-2:] 46 | short_url = promotion_url_generate(app_key=app_key, secret_key=secret_key, p_id=p_id, goods_id_list=int(goods_id), search_id= search_id) 47 | 48 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 49 | for room in groups: 50 | room_name = room['UserName'] 51 | time.sleep(random.randint(1, 5)) 52 | filename = save_pic(goods_thumbnail_url, goods_id) 53 | # 发送图片 54 | itchat.send('@img@%s' % (f'''{filename}'''), room_name) 55 | time.sleep(random.randint(1, 3)) 56 | itchat.send(f''' {goods_name} \n【现价】¥{cal_price_str}\n【内部价】¥{price}\n-----------------\n抢购地址:\n{short_url}''', room_name) 57 | del_pic(filename) 58 | 59 | def promotion_url_generate(app_key:str, secret_key:str, p_id: str, goods_id_list: int, search_id:str): 60 | client = PddApiClient(app_key=app_key, secret_key=secret_key) 61 | resp = client.call("pdd.ddk.goods.promotion.url.generate", 62 | {"goods_id_list": f'''[{goods_id_list}]''', 63 | "search_id": search_id, 64 | "p_id": p_id 65 | }) 66 | try: 67 | short_url = json.loads(resp.text)['goods_promotion_url_generate_response']['goods_promotion_url_list'][0]['mobile_short_url'] 68 | except Exception as e: 69 | print(e) 70 | set_system_notice(f'''goods_id_list: {goods_id_list},\nsearch_id:{search_id}\np_id:{p_id}\n\n无法获取连接''') 71 | short_url = "" 72 | return short_url 73 | -------------------------------------------------------------------------------- /coupon/sn.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ''' 3 | 4 | import random 5 | import time 6 | from untils.common import save_pic, del_pic 7 | import itchat 8 | import suning.api as api 9 | from chat.itchatHelper import set_system_notice 10 | 11 | def sn_share_text(group_name: str, group_material_id: str, app_key:str, secret_key:str, ad_book_id: str): 12 | ''' 13 | :param group_name: 14 | :param material_id: 15 | :return: 16 | ''' 17 | try: 18 | offset = str(random.randint(1, 71)) 19 | limit = str(random.randint(5, 10)) 20 | print(f'''offset:{offset},limit:{limit}''') 21 | client = api.RecommendcommodityQueryRequest() 22 | client.setDomainInfo("open.suning.com", "80") 23 | client.setAppInfo(app_key, secret_key) 24 | client.couponMark = '1' 25 | client.pageIndex = offset 26 | client.size = limit 27 | resp = client.getResponse()['sn_responseContent']['sn_body']['queryRecommendcommodity'] 28 | for data in resp: 29 | title = data['commodityInfo']['commodityName'] # 商品名称 30 | commodityCode = data['commodityInfo']['commodityCode'] # 商品编码 31 | supplierCode = data['commodityInfo']['supplierCode'] # 店铺编码 32 | sellingPoint = data['commodityInfo']['sellingPoint'] # 卖点 33 | snPrice = data['commodityInfo']['snPrice'] # 原价 34 | commodityPrice = data['commodityInfo']['commodityPrice'] # 内部价 35 | baoyou = data['commodityInfo']['baoyou'] # 内部价 36 | if baoyou == 1: 37 | sellingPoint = f'''包邮 {sellingPoint} ''' 38 | images_count = 0 39 | for image in data['commodityInfo']['pictureUrl']: 40 | images_count += 1 41 | if images_count > 3: ## 3个以上图片就不发了 42 | pass 43 | else: 44 | image_url = image['picUrl'].replace('_200w_200h_4e','') 45 | print(image_url) 46 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 47 | for room in groups: 48 | room_name = room['UserName'] 49 | time.sleep(random.randint(5, 10)) 50 | filename = save_pic(image_url, commodityCode) 51 | itchat.send('@img@%s' % (f'''{filename}'''), room_name) 52 | del_pic(filename) 53 | 54 | pgPrice = data['pgInfo']['pgPrice'] # 拼购价 55 | pgNum = data['pgInfo']['pgNum'] # 拼购价 56 | 57 | couponValue = data['couponInfo']['couponValue'] # 优惠券面额 58 | bounsLimit = data['couponInfo']['bounsLimit'] # 使用下限(满多少可用) 59 | afterCouponPrice = data['couponInfo']['afterCouponPrice'] # 使用下限(满多少可用) 60 | 61 | sn_share_url = promotion_url_generate(app_key, secret_key, ad_book_id, commodityCode, supplierCode.zfill(10)) 62 | 63 | if pgPrice == '': #不是拼购单 64 | if couponValue == '': # 没有券 65 | if float(snPrice) == float(commodityPrice): 66 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【爆款价】¥{commodityPrice}\n抢购地址:\n{sn_share_url}''' 67 | else: 68 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【原价】¥{snPrice}\n【爆款价】¥{commodityPrice}\n抢购地址:\n{sn_share_url}''' 69 | else: # 有券 70 | bounsLimit = float(bounsLimit) 71 | couponValue = float(couponValue) 72 | commodityPrice = float(commodityPrice) 73 | if commodityPrice >= bounsLimit: # 如果商品满足满用券下限 74 | if float(snPrice) == float(commodityPrice): 75 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n领券再减{data['couponInfo']['couponValue']}元!\n——————————\n 【券后内部价】¥{round(float(commodityPrice-couponValue),2)}\n抢购地址:\n{sn_share_url}''' 76 | else: 77 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n领券再减{data['couponInfo']['couponValue']}元!\n——————————\n 【原价】¥{snPrice}\n【券后内部价】¥{round(float(commodityPrice-couponValue),2)}\n抢购地址:\n{sn_share_url}''' 78 | else: 79 | buy_count = int(bounsLimit // commodityPrice + 1) 80 | if float(snPrice) == float(commodityPrice): 81 | if float(commodityPrice)*buy_count - float(data['couponInfo']['couponValue']) <=0: 82 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【爆款价】¥{afterCouponPrice}\n部分地区用户可领券再减,以实际优惠为准![哇][哇]\n抢购地址:\n{sn_share_url}''' 83 | else: 84 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【爆款价】¥{commodityPrice}\n拍{buy_count}件,用券再减{data['couponInfo']['couponValue']}元!{buy_count}件约{round(float(commodityPrice)*buy_count - float(data['couponInfo']['couponValue']),2)}元!\n抢购地址:\n{sn_share_url}''' 85 | 86 | else: 87 | if float(commodityPrice) * buy_count - float(data['couponInfo']['couponValue']) <= 0: 88 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【原价】¥{snPrice}\n【爆款价】¥{afterCouponPrice}\n部分地区用户可领券再减,具体以实际优惠为准![哇][哇]\n抢购地址:\n{sn_share_url}''' 89 | else: 90 | share_text = f'''{sellingPoint}\n【苏宁】{title}\n——————————\n 【原价】¥{snPrice}\n【爆款价】¥{commodityPrice}\n拍{buy_count}件,用券再减{data['couponInfo']['couponValue']}元!{buy_count}件约{round(float(commodityPrice)*buy_count - float(data['couponInfo']['couponValue']),2)}元!\n抢购地址:\n{sn_share_url}''' 91 | else: # 拼购单 92 | if couponValue == '': # 没有券 93 | if float(snPrice) == float(commodityPrice): 94 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【拼购价】¥{pgPrice}\n抢购地址:\n{sn_share_url}''' 95 | else: 96 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【原价】¥{snPrice}\n【拼购价】¥{pgPrice}\n抢购地址:\n{sn_share_url}''' 97 | else: # 有券 98 | bounsLimit = float(bounsLimit) 99 | pgPrice = float(pgPrice) 100 | if pgPrice >= bounsLimit:# 如果拼购价格满足满用券下限 101 | if float(snPrice) == float(pgPrice): 102 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n领券再减{data['couponInfo']['couponValue']}元!\n——————————\n 【券后拼购价】¥{pgPrice}\n抢购地址:\n{sn_share_url}''' 103 | else: 104 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n领券再减{data['couponInfo']['couponValue']}元!\n——————————\n 【原价】¥{snPrice}\n【券后拼购价】¥{pgPrice}\n抢购地址:\n{sn_share_url}''' 105 | else: 106 | buy_count = int(bounsLimit // pgPrice + 1) 107 | if float(snPrice) == float(commodityPrice): 108 | if float(commodityPrice) * buy_count - float(data['couponInfo']['couponValue']) <= 0: 109 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【爆款价】¥{afterCouponPrice}\n部分地区用户可领券再减,以实际优惠为准![哇][哇]\n抢购地址:\n{sn_share_url}''' 110 | else: 111 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【爆款价】¥{commodityPrice}\n拍{buy_count}件,用券再减{data['couponInfo']['couponValue']}元!{buy_count}件约{round(float(commodityPrice)*buy_count - float(data['couponInfo']['couponValue']),2)}元!\n抢购地址:\n{sn_share_url}''' 112 | else: 113 | if float(commodityPrice) * buy_count - float(data['couponInfo']['couponValue']) <= 0: 114 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【原价】¥{snPrice}\n【爆款价】¥{afterCouponPrice}\n部分地区用户可领券再减,以实际优惠为准![哇][哇]\n抢购地址:\n{sn_share_url}''' 115 | else: 116 | share_text = f'''{sellingPoint}\n【苏宁{pgNum}人拼购】{title}\n——————————\n 【原价】¥{snPrice}\n【爆款价】¥{commodityPrice}\n拍{buy_count}件,用券再减{data['couponInfo']['couponValue']}元!{buy_count}件约{round(float(commodityPrice)*buy_count - float(data['couponInfo']['couponValue']),2)}元!\n抢购地址:\n{sn_share_url}''' 117 | 118 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 119 | for room in groups: 120 | room_name = room['UserName'] 121 | time.sleep(random.randint(3, 5)) 122 | print(share_text) 123 | itchat.send(share_text, room_name) 124 | 125 | except Exception as e: 126 | print(e) 127 | set_system_notice(f'''苏宁:offset: {offset},\nlimit:{limit}\n\n发现问题''') 128 | sn_share_text(group_name, group_material_id, app_key, secret_key, ad_book_id) 129 | 130 | def promotion_url_generate(app_key:str, secret_key:str, ad_book_id: str, comm_code: int, mert_code:str): 131 | client = api.StorepromotionurlQueryRequest() 132 | client.setAppInfo(app_key, secret_key) 133 | client.setDomainInfo("open.suning.com", "80") 134 | client.adBookId = ad_book_id 135 | client.commCode = comm_code 136 | client.mertCode = mert_code 137 | client.urlType = '2' 138 | try: 139 | resp = client.getResponse() 140 | short_url = resp['sn_responseContent']['sn_body']['queryStorepromotionurl']['wapExtendUrl'] 141 | short_url = short_url.replace('%3A%2F%2F', '://').replace('%2F', '/') 142 | except Exception as e: 143 | print(e) 144 | set_system_notice(f'''comm_code: {comm_code},\nmert_code:{mert_code}\nad_book_id:{ad_book_id}\n\n无法获取推广连接''') 145 | short_url = "" 146 | return short_url 147 | 148 | if __name__ == '__main__': 149 | pass 150 | -------------------------------------------------------------------------------- /coupon/tb.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import itchat 4 | import random 5 | from untils.common import save_pic, del_pic 6 | from untils.tb_top_api import TbApiClient 7 | 8 | def tb_share_text(group_name: str, material_id: str, app_key, app_secret, adzone_id): 9 | ''' 10 | 11 | :param group_name: 12 | :param material_id: 13 | :return: 14 | ''' 15 | try: 16 | material_id = str(random.choices(material_id.split(','))[0]) 17 | print(material_id) 18 | groups = itchat.search_chatrooms(name=f'''{group_name}''') 19 | for room in groups: 20 | group_name = room['UserName'] 21 | time.sleep(random.randint(1, 5)) 22 | tb_client = TbApiClient(app_key=app_key, secret_key=app_secret, adzone_id=adzone_id) 23 | res = tb_client.taobao_tbk_dg_optimus_material(material_id) 24 | json_data = json.loads(res)['tbk_dg_optimus_material_response']['result_list']['map_data'] 25 | count = 0 26 | for item in json_data: 27 | count += 1 28 | coupon_amount = 0 29 | coupon_share_url = "" 30 | title = "" 31 | if str(item).find("coupon_share_url") > -1: 32 | coupon_share_url = "https:" + item['coupon_share_url'] 33 | coupon_amount = item['coupon_amount'] 34 | pict_url = "https:" + str(item['pict_url']) 35 | title = item['title'] 36 | item_id = item['item_id'] 37 | filename = save_pic(pict_url, item_id) 38 | zk_final_price = item['zk_final_price'] 39 | # 发送图片 40 | itchat.send('@img@%s' % (f'''{filename}'''), group_name) 41 | 42 | time.sleep(2) 43 | itchat.send(f'''{title}\n【在售价】¥{zk_final_price}\n【券后价】¥{round(float(zk_final_price) - float(coupon_amount), 2)}''', group_name) 44 | time.sleep(random.randint(1, 3)) 45 | text = f'''{tb_client.taobao_tbk_tpwd_create(title, coupon_share_url)}''' 46 | start_index = text.find('¥') 47 | itchat.send(f'''({text[start_index: 13+start_index]})''', group_name) 48 | time.sleep(2) 49 | del_pic(filename) 50 | else: 51 | click_url = "https:" + item['click_url'] 52 | title = item['title'] 53 | item_id = item['item_id'] 54 | pict_url = "https:" + str(item['pict_url']) 55 | zk_final_price = item['zk_final_price'] 56 | print(pict_url) 57 | filename = save_pic(pict_url, item_id) 58 | itchat.send('@img@%s' % (f'''{filename}'''), group_name) 59 | time.sleep(2) 60 | itchat.send(f'''{title}\n【在售价】¥{zk_final_price}\n【券后价】¥{round(float(zk_final_price) - float(coupon_amount), 2)}''', group_name) 61 | time.sleep(random.randint(1, 3)) 62 | text = f'''{tb_client.taobao_tbk_tpwd_create(title, coupon_share_url)}''' 63 | start_index = text.find('¥') 64 | itchat.send(f'''({text[start_index: 13+start_index]})''', group_name) 65 | time.sleep(2) 66 | del_pic(filename) 67 | time.sleep(2) 68 | del_pic(filename) 69 | except Exception as e: 70 | print(e) 71 | tb_share_text(group_name, material_id, app_key, app_secret, adzone_id) 72 | 73 | 74 | if __name__ == '__main__': 75 | print(f'''tb function''') 76 | -------------------------------------------------------------------------------- /images/6050dfdc-dfef-43c0-94b8-33148f6f5bd8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/6050dfdc-dfef-43c0-94b8-33148f6f5bd8.jpg -------------------------------------------------------------------------------- /images/jdyangli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/jdyangli.jpg -------------------------------------------------------------------------------- /images/pddyangli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/pddyangli.jpg -------------------------------------------------------------------------------- /images/suningyangli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/suningyangli.jpg -------------------------------------------------------------------------------- /images/yangli.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/yangli.jpg -------------------------------------------------------------------------------- /images/唯品会联盟API接入流程文档v1.9.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/唯品会联盟API接入流程文档v1.9.pdf -------------------------------------------------------------------------------- /images/苏宁联盟开放平台API接入操作指导2.7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KingRan217/youxiang-Itchat/e1b87284a528fc2c395234c137c71dce7b1e5a44/images/苏宁联盟开放平台API接入操作指导2.7.pdf -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | """ 4 | 5 | """ 6 | from chat.wechat import * 7 | 8 | if __name__ == '__main__': 9 | run() 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | retrying==1.3.3 2 | cn2an==0.4.1 3 | APScheduler==3.6.3 4 | selenium 5 | itchat-uos==1.4.1 6 | pandas==0.24.2 7 | pyzbar==0.1.8 8 | Pillow==8.3.2 9 | suning-sdk==1.0.0 10 | -------------------------------------------------------------------------------- /untils/ad.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 广告监测 4 | """ 5 | import pyzbar.pyzbar as pyzbar 6 | from PIL import Image 7 | 8 | def QRcode_detection(image: str) -> bool: 9 | ''' 10 | 判断图片是否存在二维码 11 | :param image: images的实际路径 12 | :return: True or False, True 说明发送图片有二维码信息 13 | ''' 14 | 15 | # image = "test.jpg" 16 | img = Image.open(image) 17 | barcodes = pyzbar.decode(img) 18 | for barcode in barcodes: 19 | return True 20 | # barcodeData = barcode.data.decode("utf-8") 21 | return False 22 | -------------------------------------------------------------------------------- /untils/common.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 工具类 4 | """ 5 | import re 6 | import hashlib 7 | import json 8 | import os 9 | import urllib.request 10 | import datetime 11 | import sys 12 | 13 | __all__ = [ 14 | 'FILEHELPER_MARK', 'FILEHELPER', 15 | 'is_json', 'md5_encode', ''] 16 | 17 | FILEHELPER_MARK = ['文件传输助手', 'filehelper'] # 文件传输助手标识 18 | FILEHELPER = 'filehelper' 19 | 20 | def is_json(resp): 21 | """ 22 | 判断数据是否能被 Json 化。 True 能,False 否。 23 | :param resp: request. 24 | :return: bool, True 数据可 Json 化;False 不能 JOSN 化。 25 | """ 26 | try: 27 | json.loads(resp.text) 28 | return True 29 | except AttributeError as error: 30 | return False 31 | return False 32 | 33 | def md5_encode(text): 34 | """ 把數據 md5 化 """ 35 | if not isinstance(text, str): 36 | text = str(text) 37 | md5 = hashlib.md5() 38 | md5.update(text.encode('utf-8')) 39 | encodedStr = md5.hexdigest().upper() 40 | return encodedStr 41 | 42 | def _progress(block_num, block_size, total_size): 43 | '''回调函数 44 | @block_num: 已经下载的数据块 45 | @block_size: 数据块的大小 46 | @total_size: 远程文件的大小 47 | ''' 48 | sys.stdout.write('\r>> Downloading file %.1f%%' % ( 49 | float(block_num * block_size) / float(total_size) * 100.0)) 50 | sys.stdout.flush() 51 | 52 | def save_pic(img_url, item_id): 53 | ''' 54 | :param img_url: 图片url 55 | :param item_id: 物料id,仅限用于图片命名 56 | :return: filename str, 返回一个图片名称,用来定位删除的。 57 | ''' 58 | try: 59 | file_suffix = os.path.splitext(img_url)[1] 60 | # print(file_suffix) 61 | # 拼接图片名(包含路径) 62 | filename = f'''tb_{datetime.datetime.now().strftime("%y%m%d-%H%M%S")}_{item_id}{file_suffix}''' 63 | # print(filename) 64 | # 下载图片,并保存到文件夹中 65 | urllib.request.urlretrieve(img_url, filename=filename) 66 | print(f'''图片下载成功:{filename}''') 67 | return filename 68 | except IOError as e: 69 | print(e) 70 | return 71 | except Exception as e: 72 | print(e) 73 | return 74 | 75 | def del_pic(filename): 76 | os.remove(filename) 77 | 78 | def short_2_long(short_url: str) -> str: 79 | ''' 80 | 短址还原 81 | :param short_url: str 短址 82 | :return: str, 真实地址 83 | ''' 84 | req = urllib.request.Request(short_url) 85 | req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko') 86 | response = urllib.request.urlopen(req) 87 | dlurl = response.geturl() # 跳转后的真实下载链接 88 | return dlurl 89 | 90 | if __name__ == '__main__': 91 | print(md5_encode('aeryou')) 92 | pass 93 | -------------------------------------------------------------------------------- /untils/config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 用于管理缓存的配置数据 4 | 使用前必须先调用 init() 。 5 | """ 6 | import os 7 | import copy as mycopy 8 | import yaml 9 | 10 | def init(): 11 | """ 12 | 将 yaml 里的配置文件导入到 config.py 中 13 | :return: bool ,true 表示数据导入成功。 14 | """ 15 | global opts 16 | opts = get_yaml() 17 | if opts: 18 | return True 19 | return False 20 | 21 | def get_yaml(): 22 | """ 23 | 解析 yaml 24 | :return: s 字典 25 | """ 26 | path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '_config.yaml') 27 | try: 28 | with open(path, 'r', encoding='utf-8') as file: 29 | config = yaml.safe_load(file) 30 | return config 31 | except Exception as exception: 32 | print(str(exception)) 33 | print('你的 _config.yaml 文件配置出错...') 34 | return None 35 | 36 | 37 | def set(key, value): 38 | """ 通过 key 设置某一项值 """ 39 | opts[key] = value 40 | 41 | def get(key, default=None): 42 | """ 通过 key 获取值 """ 43 | return opts.get(key, default) 44 | 45 | def copy(): 46 | """ 复制配置 """ 47 | return mycopy.deepcopy(opts) 48 | 49 | def update(new_opts): 50 | """ 全部替换配置 """ 51 | opts.update(new_opts) 52 | 53 | if __name__ == '__main__': 54 | # init() 55 | # print(copy()) 56 | pass 57 | -------------------------------------------------------------------------------- /untils/jd_api.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import requests 3 | import hashlib 4 | import json 5 | 6 | JD_API_ROOT = 'https://router.jd.com/api' 7 | 8 | class JdApiClient(object): 9 | def __init__(self, app_key, secret_key): 10 | self.app_key = app_key 11 | self.secret_key = secret_key 12 | 13 | def get_sign(self, params): 14 | params_list = sorted(list(params.items()), key=lambda x: x[0]) 15 | params_bytes = (self.secret_key + ''.join("%s%s" % (k, v) for k, v in params_list) + self.secret_key).encode('utf-8') 16 | sign = hashlib.md5(params_bytes).hexdigest().upper() 17 | return sign 18 | 19 | def call(self, method, param_json, **kwargs): 20 | params = { 21 | "v": "1.0", 22 | "method": method, 23 | "app_key": self.app_key, 24 | "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 25 | "format": "json", 26 | "sign_method": "md5" 27 | } 28 | if isinstance(param_json, (dict, list)): 29 | params["param_json"] = json.dumps(param_json) 30 | else: 31 | params["param_json"] = param_json 32 | params['sign'] = self.get_sign(params) 33 | resp = requests.get(JD_API_ROOT, params=params, **kwargs) 34 | return resp 35 | 36 | def jd_union_open_goods_jingfen_query(self): 37 | ''' 38 | https://union.jd.com/openplatform/api/10421 39 | :return: 40 | ''' 41 | return 42 | -------------------------------------------------------------------------------- /untils/pdd_api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import hashlib 3 | import time 4 | 5 | PDD_API_ROOT = 'https://gw-api.pinduoduo.com/api/router' 6 | 7 | 8 | class PddApiClient(object): 9 | def __init__(self, app_key, secret_key): 10 | self.app_key = app_key 11 | self.secret_key = secret_key 12 | 13 | def get_sign(self, params): 14 | params_list = sorted(list(params.items()), key=lambda x: x[0]) 15 | params_bytes = (self.secret_key + ''.join("%s%s" % (k, v) for k, v in params_list) + self.secret_key).encode( 16 | 'utf-8') 17 | sign = hashlib.md5(params_bytes).hexdigest().upper() 18 | return sign 19 | 20 | def call(self, method, param_json, **kwargs): 21 | params = { 22 | "type": method, 23 | "data_type": "JSON", 24 | "client_id": self.app_key, 25 | "timestamp": int(time.time()), 26 | } 27 | if isinstance(param_json, (dict, list)): 28 | for key in param_json: 29 | params[key] = param_json[key] 30 | params['sign'] = self.get_sign(params) 31 | resp = requests.get(PDD_API_ROOT, params=params, **kwargs) 32 | print(resp.url) 33 | return resp 34 | 35 | 36 | if __name__ == '__main__': 37 | pass 38 | # pdd = PddApiClient(app_key='', secret_key='') 39 | # resp = pdd.call("pdd.ddk.top.goods.list.query",{"p_id": ""}) 40 | # print(resp) 41 | -------------------------------------------------------------------------------- /untils/qq_nlpchat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Introduction: 智能闲聊(腾讯) 4 | 官网:https://ai.qq.com/product/nlpchat.shtml 5 | 免费试用,得申请 app_id,app_key。 6 | """ 7 | 8 | import hashlib 9 | from urllib import parse 10 | import time 11 | import random 12 | import string 13 | import requests 14 | from untils.common import ( 15 | md5_encode 16 | ) 17 | from untils import config 18 | 19 | __all__ = ['get_auto_reply', 'BOT_INDEX', 'BOT_NAME'] 20 | BOT_INDEX = 4 21 | BOT_NAME = '腾讯智能闲聊' 22 | 23 | URL = 'https://api.ai.qq.com/fcgi-bin/nlp/nlp_textchat' 24 | 25 | 26 | def get_nlp_textchat(text, userId): 27 | """ 28 | 智能闲聊(腾讯) 29 | 接口文档: 30 | :param text: 请求的话 31 | :param userId: 用户标识 32 | :return: str 33 | """ 34 | try: 35 | 36 | # config.init() 37 | info = config.get('auto_reply_info')['qqnlpchat_conf'] 38 | app_id = info['app_id'] 39 | app_key = info['app_key'] 40 | if not app_id or not app_key: 41 | print('app_id 或 app_key 为空,请求失败') 42 | return 43 | 44 | # 产生随机字符串 45 | nonce_str = ''.join(random.sample( 46 | string.ascii_letters + string.digits, random.randint(10, 16))) 47 | time_stamp = int(time.time()) # 时间戳 48 | params = { 49 | 'app_id': app_id, # 应用标识 50 | 'time_stamp': time_stamp, # 请求时间戳(秒级) 51 | 'nonce_str': nonce_str, # 随机字符串 52 | 'session': md5_encode(userId), # 会话标识 53 | 'question': text # 用户输入的聊天内容 54 | } 55 | # 签名信息 56 | params['sign'] = getReqSign(params, app_key) 57 | resp = requests.get(URL, params=params) 58 | if resp.status_code == 200: 59 | # print(resp.text) 60 | content_dict = resp.json() 61 | if content_dict['ret'] == 0: 62 | data_dict = content_dict['data'] 63 | return data_dict['answer'] 64 | 65 | print('智能闲聊 获取数据失败:{}'.format(content_dict['msg'])) 66 | return None 67 | except Exception as exception: 68 | print(str(exception)) 69 | 70 | 71 | def getReqSign(parser, app_key): 72 | ''' 73 | 获取请求签名,接口鉴权 https://ai.qq.com/doc/auth.shtml 74 | 1.将 请求参数对按 key 进行字典升序排序,得到有序的参数对列表 N 75 | 2.将列表 N 中的参数对按 URL 键值对的格式拼接成字符串,得到字符串 T(如:key1=value1&key2=value2), 76 | URL 键值拼接过程 value 部分需要 URL 编码,URL 编码算法用大写字母,例如 %E8,而不是小写 %e8 77 | 3.将应用密钥以 app_key 为键名,组成 URL 键值拼接到字符串 T 末尾,得到字符串 S(如:key1=value1&key2=value2&app_key = 密钥) 78 | 4.对字符串 S 进行 MD5 运算,将得到的 MD5 值所有字符转换成大写,得到接口请求签名 79 | :param parser: dect 80 | :param app_key: str 81 | :return: str,签名 82 | ''' 83 | params = sorted(parser.items()) 84 | uri_str = parse.urlencode(params, encoding="UTF-8") 85 | sign_str = '{}&app_key={}'.format(uri_str, app_key) 86 | # print('sign =', sign_str.strip()) 87 | hash_md5 = hashlib.md5(sign_str.encode("UTF-8")) 88 | return hash_md5.hexdigest().upper() 89 | 90 | 91 | get_auto_reply = get_nlp_textchat 92 | 93 | if __name__ == '__main__': 94 | to_text = '你会爱我吗' 95 | userId = 'userId' 96 | form_text = get_nlp_textchat(to_text, userId) 97 | print("会") -------------------------------------------------------------------------------- /untils/scheduler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from apscheduler.schedulers.background import BackgroundScheduler 3 | from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR 4 | from untils import config 5 | from coupon.tb import tb_share_text 6 | from coupon.jd import jingfen_query 7 | from coupon.pdd import pdd_share_text 8 | from coupon.sn import sn_share_text 9 | 10 | def job_tasks(): 11 | 12 | scheduler = BackgroundScheduler(timezone="Asia/Shanghai") 13 | 14 | tb_job_tasks(scheduler) 15 | jd_job_task(scheduler) 16 | pdd_job_task(scheduler) 17 | sn_job_task(scheduler) 18 | 19 | # 加一个监控 20 | scheduler.add_listener(scheduler_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR) 21 | scheduler.start() 22 | 23 | def tb_job_tasks(scheduler): 24 | 25 | conf = config.get_yaml() 26 | conf = conf.get('taobao') 27 | if not conf.get('is_open'): 28 | return 29 | 30 | if conf.get('app_key') =='' or conf.get('app_secret') =='' or conf.get('adzone_id') =='': 31 | return 32 | 33 | app_key = conf.get('app_key') 34 | app_secret = conf.get('app_secret') 35 | adzone_id = conf.get('adzone_id') 36 | 37 | chat_groups = conf.get('chat_groups') 38 | for chat_group in chat_groups: 39 | print(chat_group['group_name']) 40 | scheduler.add_job(func=tb_share_text, 41 | kwargs={'group_name': chat_group['group_name'], 'material_id': chat_group['group_material_id'], 42 | 'app_key': app_key, 'app_secret': app_secret, 'adzone_id': adzone_id}, 43 | trigger='cron', hour=f'''{chat_group['hour']}''', minute=f'''{chat_group['minute']}''', second=0, jitter=300, id=f'''{chat_group['group_name']}''') 44 | def jd_job_task(scheduler): 45 | 46 | conf = config.get_yaml() 47 | conf = conf.get('jingdong') 48 | if not conf.get('is_open'): 49 | return 50 | 51 | if conf.get('app_key') =='' or conf.get('app_secret') =='' or conf.get('site_id') =='' or conf.get('suo_im') =='': 52 | return 53 | 54 | app_key = conf.get('app_key') 55 | app_secret = conf.get('app_secret') 56 | site_id = conf.get('site_id') 57 | suo_im = conf.get('suo_im') 58 | 59 | chat_groups = conf.get('chat_groups') 60 | for chat_group in chat_groups: 61 | print(chat_group['group_name']) 62 | scheduler.add_job(func=jingfen_query, 63 | kwargs={'group_name': chat_group['group_name'], 'group_material_id': chat_group['group_material_id'], 64 | 'app_key': app_key, 'secret_key': app_secret, 'site_id': site_id, 'suo_mi_token': suo_im}, 65 | trigger='cron', hour=f'''{chat_group['hour']}''', minute=f'''{chat_group['minute']}''', second=0, jitter=300, id=f'''{chat_group['group_name']}''') 66 | 67 | def pdd_job_task(scheduler): 68 | 69 | conf = config.get_yaml() 70 | conf = conf.get('pinduoduo') 71 | if not conf.get('is_open'): 72 | return 73 | 74 | if conf.get('app_key') == '' or conf.get('app_secret') == '' or conf.get('p_id') == '': 75 | return 76 | 77 | app_key = conf.get('app_key') 78 | app_secret = conf.get('app_secret') 79 | p_id = conf.get('p_id') 80 | 81 | chat_groups = conf.get('chat_groups') 82 | for chat_group in chat_groups: 83 | print(chat_group['group_name']) 84 | scheduler.add_job(func=pdd_share_text, 85 | kwargs={'group_name': chat_group['group_name'], 'group_material_id': chat_group['group_material_id'], 86 | 'app_key': app_key, 'secret_key': app_secret, 'p_id': p_id}, 87 | trigger='cron', hour=f'''{chat_group['hour']}''', minute=f'''{chat_group['minute']}''', second=0, jitter=0, id=f'''{chat_group['group_name']}''') 88 | 89 | def sn_job_task(scheduler): 90 | 91 | conf = config.get_yaml() 92 | conf = conf.get('suning') 93 | if not conf.get('is_open'): 94 | return 95 | 96 | if conf.get('app_key') == '' or conf.get('app_secret') == '' or conf.get('ad_book_id') == '': 97 | return 98 | 99 | app_key = conf.get('app_key') 100 | app_secret = conf.get('app_secret') 101 | ad_book_id = conf.get('ad_book_id') 102 | 103 | chat_groups = conf.get('chat_groups') 104 | for chat_group in chat_groups: 105 | print(chat_group['group_name']) 106 | scheduler.add_job(func=sn_share_text, 107 | kwargs={'group_name': chat_group['group_name'], 'group_material_id': chat_group['group_material_id'], 108 | 'app_key': app_key, 'secret_key': app_secret, 'ad_book_id': ad_book_id}, 109 | trigger='cron', hour=f'''{chat_group['hour']}''', minute=f'''{chat_group['minute']}''', second=0, jitter=0, id=f'''{chat_group['group_name']}''') 110 | 111 | 112 | def scheduler_listener(event): 113 | ''' 114 | 监听程序,如果发现错误程序终止 115 | :param event: 116 | :return: 117 | ''' 118 | if event.exception: 119 | print(f'''Error: JOB_ID: {event.job_id}, 运行时间:{(event.scheduled_run_time).strftime("%Y-%m-%d %H:%M:%S.%f")[0:19]}, 任务出错了!所有程序暂停!''') 120 | # 别闹,不会暂停,就是一轮错误罢了。 121 | else: 122 | print(f'''Success: JOB_ID: {event.job_id}, 运行时间:{(event.scheduled_run_time).strftime("%Y-%m-%d %H:%M:%S.%f")[ 123 | :-3]}, 任务运行成功,继续运行...''') 124 | 125 | if __name__ == '__main__': 126 | job_tasks() 127 | -------------------------------------------------------------------------------- /untils/suo_im.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 4 | """ 5 | import json 6 | import urllib 7 | import urllib.parse 8 | import urllib.request 9 | import datetime 10 | 11 | class Suo_mi(object): 12 | ''' 13 | 需要注册 http://suo.im/ , 而后获得 key 14 | ''' 15 | 16 | def __init__(self, app_key): 17 | self.app_key = app_key 18 | # 我们默认短址一年后过期 19 | self.expireDate = (datetime.date.today() + datetime.timedelta(days=365)).strftime('%Y-%m-%d') 20 | 21 | def get_short_url(self, url: str) -> str: 22 | ''' 23 | :param url: 长址 24 | :return: 返回suo.im的短址 25 | ''' 26 | # 取值地址,接口地址 27 | api_url = f'''http://suo.im/api.htm?format=json&url={urllib.parse.quote(url)}&key={self.app_key}&expireDate={self.expireDate}''' 28 | request = urllib.request.Request(url=api_url) 29 | response = urllib.request.urlopen(request) 30 | data = response.read() 31 | short_url = json.loads(data)['url'] 32 | return short_url 33 | 34 | if __name__ == '__main__': 35 | 36 | # example 37 | url = 'https://union-click.jd.com/jdc?e=&p=AyIGZRtcFAsRAlEfWxQyEg9QGlIQBxAPUhNrUV1KWQorAlBHU0VeBUVOWk1RAk8ECllHGAdFBwtaV1MJBAJQXk8JF0EfGQIaAlQSXhAAGgBdDBsZdmtdPGwoFUJlbilLL0xLRXA8azxhW0dEIkMnRWETb1NsOXJxZnM2WS9KVHV%2BJhscYQBmYSFSMFVhe3MNbBJARWZuMXssTHFFYB18JHV2YkUCTTBecVtOEGwDbVJyZCZbLE12dGQMb15ja0RULH8oVXVNVQVsP2kFcW4maDthcVd%2FLG8%2FYUttWyFiK3d1cGdBGS4le3V5LHsaHUFwbCMdMHF6blwrQyNRch4LZR5aFAMSDlYfWBIyEgZUGFIQBxEGUCtrFQMiRjscXREKEQJlGmsVBhoHVRxYHAMaD1wTaxUKFjcNRgVSVktTBVwPSjIiN1YrayUCETdWKwV7A0EHXRwORgF8XQVTEh0GUQY7GF4RChMCXB1rFwMTBVc%3D' 38 | print(urllib.parse.quote(url)) 39 | app_key = '' 40 | c = Suo_mi(app_key).get_short_url(url) 41 | print(c) 42 | 43 | -------------------------------------------------------------------------------- /untils/tb_top_api.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 首先要感谢下这篇文章: 4 | https://www.jianshu.com/p/f9b5e3020789 5 | 6 | 值得看的一篇文章: 7 | http://g.alicdn.com/tmapp/tida-doc/docs/top/00API%E8%B0%83%E7%94%A8%E8%AF%B4%E6%98%8E.html 8 | 9 | """ 10 | import hashlib 11 | import json 12 | import random 13 | import time 14 | import urllib 15 | import urllib.parse 16 | import urllib.request 17 | 18 | TB_API_ROOT = 'http://gw.api.taobao.com/router/rest?' 19 | 20 | class TbApiClient(object): 21 | 22 | def __init__(self, app_key, secret_key, adzone_id): 23 | self.app_key = app_key 24 | self.secret_key = secret_key 25 | self.adzone_id = adzone_id 26 | 27 | #排序 28 | def ksort(self, d): 29 | return [(k, d[k]) for k in sorted(d.keys())] 30 | 31 | #MD5加密 32 | def md5(self, s, raw_output=False): 33 | """Calculates the md5 hash of a given string""" 34 | res = hashlib.md5(s.encode()) 35 | if raw_output: 36 | return res.digest() 37 | return res.hexdigest() 38 | 39 | #计算sign 40 | def createSign(self, paramArr): 41 | sign = self.secret_key 42 | paramArr = self.ksort(paramArr) 43 | paramArr = dict(paramArr) 44 | for k, v in paramArr.items(): 45 | if k != '' and v != '': 46 | sign += k + v 47 | sign += self.secret_key 48 | sign = self.md5(sign).upper() 49 | return sign 50 | 51 | #参数排序 52 | def createStrParam(self, paramArr): 53 | strParam = '' 54 | for k, v in paramArr.items(): 55 | if k != '' and v != '': 56 | strParam += k + '=' + urllib.parse.quote_plus(v) + '&' 57 | return strParam 58 | 59 | #高效API调用示例 60 | def taobao_tbk_dg_optimus_material(self, material_id: str): 61 | ''' 62 | 通用物料推荐,传入官方公布的物料id,可获取指定物料 63 | 淘宝接口文档: 64 | http://bigdata.taobao.com/api.htm?spm=a219a.7386797.0.0.4ad5669aWaaQFi&source=search&docId=33947&docType=2 65 | 66 | :param material_id: 详见https://market.m.taobao.com/app/qn/toutiao-new/index-pc.html#/detail/10628875?_k=gpov9a 67 | :param adzone_id: 广告位 68 | :return: 69 | ''' 70 | # 请求参数,根据API文档修改 71 | # TODO 72 | # 把分页现在这里随机有一定考虑 73 | # 原因是:1. 不同 material_id 得到的数据不一,且刷新周期不一 74 | # 2. 微信发送不可太频繁,我仅是怕被封,决定取很小一部分数据 75 | page_no = str(random.choices(['1','2','3','4', '5', '6', '7', '8', '9'])[0]) 76 | page_size = str(random.randint(8, 10)) 77 | 78 | postparm = { 79 | 'page_no': page_no, 80 | 'page_size': page_size, 81 | 'adzone_id': self.adzone_id, 82 | 'material_id': material_id, 83 | 'method': 'taobao.tbk.dg.optimus.material' 84 | } 85 | # 公共参数,一般不需要修改 86 | paramArr = {'app_key': self.app_key, 87 | 'v': '2.0', 88 | 'sign_method': 'md5', 89 | 'format': 'json', 90 | 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S') 91 | } 92 | 93 | paramArr = {**paramArr, **postparm} 94 | sign = self.createSign(paramArr) 95 | strParam = self.createStrParam(paramArr) 96 | strParam += 'sign=' + sign 97 | url = TB_API_ROOT + strParam 98 | print(url) 99 | res = urllib.request.urlopen(url).read() 100 | return res 101 | 102 | def taobao_tbk_tpwd_create(self, text: str, url: str): 103 | ''' 104 | 提供淘客生成淘口令接口,淘客提交口令内容、logo、url等参数,生成淘口令关键key如:¥SADadW¥,后续进行文案包装组装用于传播 105 | 淘宝接口文档: 106 | http://bigdata.taobao.com/api.htm?spm=a219a.7386797.0.0.494b669atcwg9a&source=search&docId=31127&docType=2 107 | 108 | :param text: 口令弹框内容 109 | :param url: 口令跳转目标页 110 | :return: 返回淘口令,如<¥SADadW¥> 111 | ''' 112 | 113 | postparm = { 114 | 'text': text, 115 | 'url': url, 116 | 'method': 'taobao.tbk.tpwd.create' 117 | } 118 | # 公共参数,一般不需要修改 119 | paramArr = {'app_key': self.app_key, 120 | 'v': '2.0', 121 | 'sign_method': 'md5', 122 | 'format': 'json', 123 | 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S') 124 | } 125 | 126 | paramArr = {**paramArr, **postparm} 127 | sign = self.createSign(paramArr) 128 | strParam = self.createStrParam(paramArr) 129 | strParam += 'sign=' + sign 130 | url = TB_API_ROOT + strParam 131 | res = urllib.request.urlopen(url).read() 132 | tao_command = json.loads(res)['tbk_tpwd_create_response']['data']['model'] 133 | return tao_command 134 | 135 | def tkl_parser(self, tkl): 136 | ''' 137 | :param tkl: str 淘口令,例如 ¥ABCDEFG¥ 138 | :return: str 返回自己的淘口令 139 | ''' 140 | # 取值地址,接口地址 141 | url = f'''http://www.taofake.com/index/tools/gettkljm.html?tkl={urllib.parse.quote(tkl)}''' 142 | # 伪装定义浏览器header 143 | headers = { 144 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'} 145 | 146 | request = urllib.request.Request(url=url, headers=headers) 147 | response = urllib.request.urlopen(request) 148 | data = response.read() 149 | return self.taobao_tbk_tpwd_create(json.loads(data)['data']['content'], json.loads(data)['data']['url']) 150 | --------------------------------------------------------------------------------