随机图片回复与关注统计

随机图片回复

通过对werobot和腾讯公众号开发文档的阅读与研究,回复图片消息是通过返回MediaId实现的,那这个MediaId是怎么来的?是在微信上传图片等素材后才会有。其中素材分为临时素材与永久素材,临时素材要经过认证的公众号才会可以用,一般会保存三天。既然认证后采用,那就与个人搞的公众号无关了。永久素材是在微信后台或者通过api上传的,如果不删除会一直存在下去。目前看来图片是有500张的限制的。

要制作图片随机回复功能,首先要解决图片上传的问题,图片可以通过网页端上传,手机端上传也可以通过api上传。既然有着500张的上传限制,那么用api上传就没什么迫切的需求了,而且图片来源主要在手机,所以直接用手机端上传。

获取access_token

普通的个人订阅号虽然不能使用临时素材接口,但还是可以通过调用api的方式获得永久素材列表。由于上传的时间是随机的,所以必须定期检查素材列表,然后把最新的图片列表存到redis里面的一个set里面,便于werobot从中随机选取。要想获得图片列表首先要拿到access_token,关于这个access_token,腾讯的文档上面是这样说的:

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

公众平台的API调用所需的access_token的使用及生成方式说明:

1、建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;

2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;

3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

4、对于可能存在风险的调用,在开发者进行获取 access_token调用时进入风险调用确认流程,需要用户管理员确认后才可以成功获取。具体流程为:

开发者通过某IP发起调用->平台返回错误码[89503]并同时下发模板消息给公众号管理员->公众号管理员确认该IP可以调用->开发者使用该IP再次发起调用->调用成功。

如公众号管理员第一次拒绝该IP调用,用户在1个小时内将无法使用该IP再次发起调用,如公众号管理员多次拒绝该IP调用,该IP将可能长期无法发起调用。平台建议开发者在发起调用前主动与管理员沟通确认调用需求,或请求管理员开启IP白名单功能并将该IP加入IP白名单列表。

简而言之,access_token有效期只有两小时,如果提前获取新的access_token,原来的access_token就会失效。access_token的获取需要AppSecret

感觉这个设计好奇葩啊,为什么非要通过access_token这个中间体,而不是直接拿着AppSecret去调用API?

为了应对这个机制,可以搞一个定时脚本,每小时获取一次access_token,然后存到redis的一个键值对里,其他需要调用API的程序或者脚本直接去redis里面去捞access_token

按照文档里的说法,搞了这样一个脚本:


import requests as r
import redis

r2 = redis.Redis(host='127.0.0.1', port= 7478, password= 'password', db= 2,decode_responses=True)

URL='https://api.weixin.qq.com/cgi-bin/token'

pdog={"grant_type":"client_credential","appid":"wx6neko98dog91d39cat","secret":"dogdogdogdog"}

res=r.get(URL,params=pdog)
res_json=res.json()
AK=res_json['access_token']
d=r2.set('AK',AK)

很简单,然后设置定时,一小时运行一次即可

获取图片列表

有了access_token就好办了,直接按照文档里的方法调用就完了。解析获取到的图片列表然后把MediaId存到一个set里。调用接口并不能返回所有图片,最多可以获得20张,剩下的需要用偏移量的方式得到。

顺便添加了一个依照名称的过滤功能,在发订阅号图文消息的时候上传的图片会自动出现在永久素材里,但有些并不适合出现在返回的随机图片里面,所以要有所区分。

最后也要整一个定时脚本,可以跟在前一个脚本后面,每次更新就行了。


import requests as r
import redis
import json
import math

r2 = redis.Redis(host='127.0.0.1', port= 7198, password= 'passwd', db= 2,decode_responses=True)
AT=r2.get('AK')
URL='https://api.weixin.qq.com/cgi-bin/material/get_materialcount'
URL2='https://api.weixin.qq.com/cgi-bin/material/batchget_material'
AST='?access_token={}'.format(AT)

def Get_item_num():
    dog_res=r.get(URL,params={"access_token":AT})
    dog_res_enc=json.loads(dog_res.text)
    return dog_res_enc

def Get_pic_list(page):
    dog_json={"type":'image',"offset":page,"count":20}
    dog_res=r.post(URL2,params={"access_token":AT},json=dog_json)
    dog_res_json=dog_res.json()
    dog_res_enc=json.loads(dog_res.text)
    print(dog_res_json)
    return dog_res_enc

def Get_all_pic():
    dog_num=Get_item_num()['image_count']
    dog_page=math.ceil(dog_num/20)
    all_pic=[]
    r_pic=[]
    for page in range(dog_page):
        page_res=Get_pic_list(page*20)
        for item_dog in page_res['item']:
            all_pic.append(item_dog['media_id'])
            if (item_dog['name'][0]!='P'):    #过滤出P开头的文件名称,不进入随机图片池的集合
                r_pic.append(item_dog['media_id'])
                pass
            pass
    return {'all_pic':all_pic,'r_pic':r_pic}

def Change_dog_lib(rlist,rs):
    rs.rename('Rpiclist','Rpiclistold')
    for dog_name in rlist:
        rs.sadd('Rpiclist',dog_name)
        pass
    pass

rs=Get_item_num()
ui=Get_all_pic()
Change_dog_lib(ui['r_pic'],r2)

在worobot框架下返回随机图片

这就很简单了,直接在数据库里拿mediaid就完了。

import werobot
import redis
import json
import time
import random
from  werobot.replies  import  ImageReply

r = redis.Redis(host='127.0.0.1', port= 7198, password= 'passwd', db= 2,decode_responses=True)
def Get_random_picid():
    return r.srandmember("Rpiclistold")

@robot.text
def hello(message):
    dog_wang=message.content
    dog_id=message.source
    dog_name=Get_dname(dog_id)
    dog_wang_sp=str(dog_wang).split()
    if (dog_wang_sp[0]=='.pic' or dog_wang_sp[0]=='图片'):
        re_dog=ImageReply(message=message,media_id=Get_random_picid())
        return re_dog

关注统计功能

werobot不仅可以接受消息,还可以接受订阅号的事件推送,在关注的时候进行计数然后告知用户是累计第几个关注的与首次关注的时间。


def Set_dog_info(message,rs):
    Dog_id=message.source
    if (rs.hexists("doglist",Dog_id)):
        Dog_info=rs.hget("doglist",Dog_id)
        return str(Dog_info)
    else:
        rs.hset("doglist",Dog_id,time.ctime())
        return False


@robot.subscribe
def subscribe(message):
    resdog=Set_dog_info(message,r)
    if resdog!=False:
        res3="\n首次关注时间(UTC+8): {}".format(resdog)
    else:
        res3="\n首次关注,回复“关于”、“帮助”或“help”查看更多信息。\n已分配默认昵称,回复.name查看,或回复.name 新名称 修改,更多信息查看帮助页面。"
    res="欢迎关注\n您是累计第{}位关注者".format(getnumber())
    res2='\n\nOpenID:{}'.format(message.source)
    return res+res3+res2

使用nginx反代与守护进程

这个东西需要一个nginx来反代一下,顺便解决https的问题。

server {
    server_name wx.dogcraft.top;
    # listen 80;
    listen 443 ssl http2;
    ssl_certificate /root/key/dog.pem;
    ssl_certificate_key /root/key/dog.key;
    access_log /var/log/nginx/wx.log main;
    location / {
         if ($request_method = GET) {
            return 301 https://www.dogcraft.top/;
        }
    proxy_pass_header Server;
        proxy_redirect off;
        proxy_pass http://127.0.0.1:6666;
    }
}

另外就是微信的回调请求都是以POST的形式,可以把GET请求转到别处去。

如果在服务器上长期运行需要安排上到systemctl,在/etc/systemd/system里新建一个wx.service

[Unit]
Description=WX daemon

[Service]
Type=simple
User=yu     
ExecStart=/usr/bin/python3 gzh.py    
WorkingDirectory=/home/yu/gzh         
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=wx     
Restart=always

[Install]
WantedBy=multi-user.target

然后就是

sudo systemctl daemon-reload
sudo systemctl enable wx
sudo systemclt start wx

就完了


目前就这些内容了,如果以后有新的功能加入或者遇到什么问题会继续更新的。目前所使用的代码安排在这里。感兴趣可以去看看。

目前订阅号已经开通了赞赏和付费阅读功能,抽空试验一下。

最后再放一下公众号二维码。

二维码
二维码