fir.im自动上传apk并在钉钉通知

2,014 阅读4分钟

简要记录开发中遇到的事情

事件起因

最近测试提了一个问题:虽然每次开发发布了测试版本的时候都要在钉钉群里面通知,但是也会经常忘记了,这样就会导致耽误测试及修改bug的后续整个进度。

一直知道钉钉里面有个webhook功能,同时看到fir.im里面可以设置webhook,基于此,便可以实现上传版本后自动通知到钉钉群里面。

解决办法

在钉钉里面的机器人 可以在GitHub等平台配置webhook后进行 push消息提示,所以每次push都会在钉钉里面提醒。如此,若在fir.im里面加入hook地址应该也会在上传apk后收到消息提示,但是打开fir.im后并没有看到钉钉平台的支持,


但是找到了一个自定义webhook设置,于是就进行了自定义的配置,但在钉钉上一直没有收到推送消息,无奈只好联系客服以帮助查找问题,虽然客服反应较慢但最后还是给了回复


要指向自己的服务器,这个太麻烦了,这么一个小功能没有必要专门开服务(这一点还是希望fir.im可以早点对一些大平台支持)。于是只好另想办法,查找fir.im的文档发现可以接口上传安装包,那就只好迂回路线——接口上传安装包,成功后调用钉钉给出webhook地址发送推送消息。其实后面就很简单了,主要就几个接口调用而已。

最近有看python,所以打算用python来显现一下。首先用python做了一个简单的界面,包括有选择安装包以及上传按钮等


python很不熟练,可能很多东西都使用不当,比如没有异步请求接口等,花了较久的时间写代码,最终达到了自己的目的。在发送到钉钉的提醒消息里面有下载地址,我这里是在代码里面配置死了的。推送钉钉的消息样式可以参考open-doc.dingtalk.com/docs/doc.ht…定制。以下是代码


# -*- coding: utf-8 -*-

from tkinter import *
from tkinter import messagebox
import tkinter.filedialog
import requests
import os
import re
import subprocess
import zipfile
import json

root = Tk()
root.title('APK上传fir.im')
root.geometry('600x400')
path = StringVar()
pathShow = StringVar() # 显示文件名

#默认的一个apitoken
apiTokenDefult = "你的默认apitoken"
packagename = ""
versionCode = 0
versionName = ""
name = ""
saveIconName = ""
mfmUrl = "XXX"
mfmPwd = "XXX"
fgcUrl = "XXX"
fgcPwd = "XXX"
fgjUrl = "XXX"
fgjPwd = "XXX"

#这里是钉钉消息需要@的人的手机号
atList = ["12345","12345"]
#这里是webhook地址
notifyUrl = "https://oapi.dingtalk.com/robot/send?access_token=XXX"

def appInfo():    
    #根据自己的aapt地址修改
    output = subprocess.Popen('D:/android_sdk/build-tools/21.0.0/aapt dump badging ' + path.get(),
                     shell=False,stdout=subprocess.PIPE).stdout.readlines()
    for line in output:
        if "package: name" in line.decode():
            match = re.compile("package: name='(\S+)' versionCode='(\d+)' versionName='(\S+)'").match(line.decode())
            global packagename
            packagename = match.group(1)  
            global versionCode
            versionCode = match.group(2)  
            global versionName
            versionName = match.group(3)
        if 'application: label' in line.decode():
            matchName = re.compile("application: label='(\S+)'").match(line.decode())
            global name
            name = matchName.group(1)
        if 'application-icon-320:' in line.decode():
            icon = re.compile("application-icon-320:'(\S+)'").match(line.decode())
            iconPath =  icon.group(1)
            #保存到本地
            zz = zipfile.ZipFile( path.get() )
            iconData = zz.read(iconPath)
            global saveIconName
            #临时保存icon的位置
            saveIconName = "D:/python3.6/icon.png";
            with open(saveIconName,'w+b') as saveIconFile:
                saveIconFile.write(iconData)
            
    
        
def selectPath():
    path_ = tkinter.filedialog.askopenfilename(filetypes=[("apk文件", ".apk")])
    path.set(path_)
    temp = path.get().split('/')
    pathShow.set(temp[len(temp)-1])
    
    appInfo()

def doSubmit():
     if not packagename or not versionName or not name or versionCode==0:
         messagebox.showinfo('提示','获取apk信息失败')
         return
     res = requests.post("http://api.fir.im/apps",data={"type":"android","bundle_id":packagename,
                                                        "api_token":apiToken.get() or apiTokenDefult})
     if res.status_code != 201:
         messagebox.showinfo('提示','请求返回错误')
         return
     data = res.json()
     icon = data['cert']['icon']
     apk = data['cert']['binary']

     #上传icon
     uploadIcon = requests.post(icon['upload_url'],data={"key":icon['key'],'token':icon['token']},
                            files={'file':('icon.png',open(saveIconName,'rb'))})
     if uploadIcon.status_code!=200:
         messagebox.showinfo('提示',"上传ICON成功")
         return
     #上传文件
     upload = requests.post(apk['upload_url'],data={"key":apk['key'],'token':apk['token'],
                                'x:name':name,'x:version':versionName,'x:build':versionCode,
                                'x:changelog':des.get()},
                            files={'file':(pathShow.get(),open(path.get(),'rb'))})

     if upload.status_code==200 and upload.json()["is_completed"]==True:
         messagebox.showinfo('提示',"上传成功")
         #通知推送消息
         downUrl = ""
         downPwd = ""
         if "maifangma" in packagename:
             downUrl = mfmUrl
             downPwd = mfmPwd
         elif "fangguancha" in packagename:
             downUrl = fgcUrl
             downPwd = fgcPwd
         elif "fangguanjia" in packagename:
             downUrl = fgjUrl
             downPwd = fgjPwd
         else:
             messagebox.showinfo('提示',"下载地址无效")
         listat = ""
         for at in atList:
             listat+="@"+at
         data = {"msgtype": "markdown",
                 "markdown":{"title":name+"新版本",
                             "text":"### "+name+"Android新版本\n"+"> 版本:"+versionName+"\n\n> 更新描述:"+
                             des.get()+"\n\n> [点击下载("+ ("无效链接" if not downPwd else ("密码:"+downPwd)) +")]("+downUrl+")\n\n > "+listat},
                 "at":{"atMobiles":atList}}
         notify = requests.post(notifyUrl,data=json.dumps(data),
                                headers={"Content-Type":"application/json;charset=utf-8"})
     else:
         messagebox.showinfo('提示',"上传失败:"+upload.status_code)      


Label(root,text='api_token:',width=20).grid(row=0,column=0)
apiToken = Entry(root)
apiToken.grid(row=0,column=1)

Label(root,text='文件路径:').grid(row=1,column=0)
Button(root, text = "选择文件", command = selectPath).grid(row = 1, column = 1)

Label(root, textvariable = pathShow,bg='white').grid(row = 2, column = 1,columnspan = 2)


Label(root,text='更新描述:').grid(row=3,column=0)
des = Entry(root)
des.grid(row=3,column=1,rowspan=3,sticky=E)

Button(root,text="上传",command=doSubmit).grid(row = 6,column = 1)

root.mainloop()

虽然代码很短,但在整个过程中花费了较多的时间,是一个还不错的经历,故做记录,同时希望对类似需求的人有所帮助。