├── README.md
└── 通过 Serverless 实现 B 站定时签到
├── BiliExp.py
├── README.md
├── config.json
├── models
├── Article.py
├── Biliapi.py
├── Manga.py
├── PushMessage.py
├── Video.py
└── aria2py.py
└── serverless.yml
/README.md:
--------------------------------------------------------------------------------
1 | # 腾讯云 Serverless 最佳实践
2 |
3 | ## 产品简介
4 | 腾讯云 Severless 是腾讯云提供的安全稳定、管理简化、高效易用且低成本的无服务器产品平台。它通过多种 Serverless 产品组合,快速落地 Serverless 架构及应用,加速互联网和传统企业的业务迭代与升级,让您全面享受 Serverless 架构带来的弹性伸缩、秒级部署、按需付费、免运维等好处。
5 |
6 | ## 最佳实践
7 | | 项目名称 | 简介 |
8 | | :------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9 | | [Bilibili 自动签到模版](https://github.com/TencentCloud/serverless/blob/master/%E9%80%9A%E8%BF%87%20Serverless%20%E5%AE%9E%E7%8E%B0%20B%20%E7%AB%99%E5%AE%9A%E6%97%B6%E7%AD%BE%E5%88%B0/README.md) | 基于腾讯云 Serverless,实现 B 站定时打卡签到 |
10 | |[基于 PG SQL 的全栈应用模版](https://github.com/serverless-components/tencent-examples/tree/master/fullstack) | 基于 PostgreSQL 和云函数,快速部署一个全栈应用 |
11 |
12 |
13 | ## 官方组件
14 |
15 | | 组件名称 | serverless.yml 全量配置指引 |
16 | | :------------------------------------------------------------------------------------------- |:------------------------------------------------------------------------------------------- |
17 | | [scf 组件](https://github.com/serverless-components/tencent-scf) | [全量配置](https://github.com/serverless-components/tencent-scf/blob/master/docs/configure.md)|
18 | |[express 组件](https://github.com/serverless-components/tencent-express) | [全量配置](https://github.com/serverless-components/tencent-express/blob/master/docs/configure.md)|
19 | |[website 组件](https://github.com/serverless-components/tencent-website) | [全量配置](https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md)|
20 | |[apigateway 组件](https://github.com/serverless-components/tencent-apigateway)|[全量配置](https://github.com/serverless-components/tencent-apigateway/blob/master/docs/configure.md)|
21 | |[egg 组件](https://github.com/serverless-components/tencent-egg)|[全量配置](https://github.com/serverless-components/tencent-egg/blob/master/docs/configure.md)|
22 | |[nextjs 组件](https://github.com/serverless-components/tencent-nextjs)|[全量配置](https://github.com/serverless-components/tencent-nextjs/blob/master/docs/configure.md)|
23 | |[nuxtjs 组件](https://github.com/serverless-components/tencent-nuxtjs)|[全量配置](https://github.com/serverless-components/tencent-nuxtjs/blob/master/docs/configure.md)|
24 | |[cdn 组件](https://github.com/serverless-components/tencent-cdn)|[全量配置](https://github.com/serverless-components/tencent-cdn/blob/master/docs/configure.md)|
25 | |[cos 组件](https://github.com/serverless-components/tencent-cos)|[全量配置](https://github.com/serverless-components/tencent-cos/blob/master/docs/configure.md)|
26 | |[django 组件](https://github.com/serverless-components/tencent-django/)|[全量配置](https://github.com/serverless-components/tencent-django/blob/master/docs/configure.md)|
27 | |[flask 组件](https://github.com/serverless-components/tencent-flask)|[全量配置](https://github.com/serverless-components/tencent-flask/blob/master/docs/configure.md)|
28 | |[koa 组件](https://github.com/serverless-components/tencent-koa/)|[全量配置](https://github.com/serverless-components/tencent-koa/blob/master/docs/configure.md)|
29 | |[laravel 组件](https://github.com/serverless-components/tencent-laravel)|[全量配置](https://github.com/serverless-components/tencent-laravel/blob/master/docs/configure.md)|
30 | |[layer 组件](https://github.com/serverless-components/tencent-layer)|[全量配置](https://github.com/serverless-components/tencent-layer/blob/master/docs/configure.md)|
31 | |[postgresql 组件](https://github.com/serverless-components/tencent-postgresql)|[全量配置](https://github.com/serverless-components/tencent-postgresql/blob/master/docs/configure.md)|
32 | |[vpc 组件](https://github.com/serverless-components/tencent-vpc/)|[全量配置](https://github.com/serverless-components/tencent-vpc/blob/master/docs/configure.md)|
33 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/BiliExp.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from models.Biliapi import BiliWebApi
3 | from models.PushMessage import PushMessage
4 | import json, time
5 | import logging
6 |
7 | def bili_exp(cookieData, pm):
8 | "B站直播签到,投币分享获取经验,模拟观看一个视频"
9 | try:
10 | biliapi = BiliWebApi(cookieData)
11 | except Exception as e:
12 | logging.info(f'登录验证id为{cookieData["DedeUserID"]}的账户失败,原因为({str(e)}),跳过此账户后续所有操作')
13 | pm.addMsg(f'id为:{cookieData["DedeUserID"]} 的账户登录失败')
14 | return
15 |
16 | pm.addMsg(f'目前账户为:({biliapi.getUserName()})')
17 | logging.info(f'登录账户 ({biliapi.getUserName()}) 成功')
18 |
19 | rdata = {
20 | "直播签到": False,
21 | "投币数量": 0,
22 | "视频观看": False,
23 | "视频分享": False,
24 | "脚本执行前经验": 0,
25 | "脚本执行前硬币": 0,
26 | }
27 |
28 | try:
29 | if biliapi.vipPrivilegeReceive(1)["code"] == 0:
30 | rdata["领取大会员B币"] = True
31 | if biliapi.vipPrivilegeReceive(2)["code"] == 0:
32 | rdata["领取会员购优惠券"] = True
33 | except:
34 | pass
35 |
36 | try:
37 | xliveInfo = biliapi.xliveSign()
38 | logging.info(f'bilibili直播签到信息:{str(xliveInfo)}')
39 | rdata["直播签到"] = (xliveInfo["code"] == 0)
40 | except Exception as e:
41 | logging.warning(f'直播签到异常,原因为{str(e)}')
42 |
43 | try:
44 | room_id = biliapi.xliveGetRecommendList()["data"]["list"][6]["roomid"]
45 | uid = biliapi.xliveGetRoomInfo(room_id)["data"]["room_info"]["uid"]
46 | now_time = int(time.time())
47 | bagList = biliapi.xliveGiftBagList()["data"]["list"]
48 | for x in bagList:
49 | if x["expire_at"] - now_time < 172800: #礼物到期时间小于2天
50 | ret = biliapi.xliveBagSend(room_id, uid, x["bag_id"], x["gift_id"], x["gift_num"])
51 | if ret["code"] == 0:
52 | logging.info(f'{ret["data"]["send_tips"]} {ret["data"]["gift_name"]} 数量{ret["data"]["gift_num"]}')
53 | except Exception as e:
54 | logging.warning(f'直播送出即将过期礼物异常,原因为{str(e)}')
55 |
56 | try:
57 | reward = biliapi.getReward()
58 | logging.info(f'经验脚本开始前经验信息 :{str(reward)}')
59 | except Exception as e:
60 | logging.warning(f'获取账户经验信息异常,原因为{str(e)},跳过此账户后续所有操作')
61 | pm.addMsg(str(rdata))
62 | return
63 |
64 | rdata["脚本执行前经验"] = reward["level_info"]["current_exp"]
65 |
66 | try:
67 | coin_num = biliapi.getCoin()
68 | except Exception as e:
69 | logging.warning(f'获取账户剩余硬币数异常,原因为{str(e)}')
70 | coin_num = 0
71 |
72 | rdata["脚本执行前硬币"] = coin_num
73 |
74 | coin_exp_num = (50 - reward["coins_av"]) // 10
75 | toubi_num = coin_exp_num if coin_num > coin_exp_num else coin_num
76 |
77 | try:
78 | datas = biliapi.getRegions()
79 | except Exception as e:
80 | logging.warning(f'获取B站分区视频信息异常,原因为{str(e)},跳过此账户后续所有操作')
81 | pm.addMsg(str(rdata))
82 | return
83 |
84 | if(toubi_num > 0):
85 | for i in range(toubi_num):
86 | try:
87 | info = biliapi.coin(datas[i]["aid"], 1, 1)
88 | logging.info(f'投币信息 :{str(info)}')
89 | if(info["code"] == 0):
90 | rdata["投币数量"] += 1
91 | except Exception as e:
92 | logging.warning(f'投币异常,原因为{str(e)}')
93 |
94 | try:
95 | info = biliapi.report(datas[5]["aid"], datas[5]["cid"], 300)
96 | logging.info(f'模拟视频观看进度上报:{str(info)}')
97 | rdata["视频观看"] = (info["code"] == 0)
98 | except Exception as e:
99 | logging.warning(f'模拟视频观看异常,原因为{str(e)}')
100 |
101 | try:
102 | info = biliapi.share(datas[5]["aid"])
103 | logging.info(f'分享视频结果:{str(info)}')
104 | rdata["视频分享"] = (info["code"] == 0)
105 | except Exception as e:
106 | logging.warning(f'分享视频异常,原因为{str(e)}')
107 |
108 | pm.addMsg(str(rdata))
109 | logging.info('本账户操作全部完成')
110 |
111 | def main(*args):
112 | try:
113 | logging.basicConfig(filename="exp.log", filemode='a', level=logging.INFO, format="%(asctime)s: %(levelname)s, %(message)s", datefmt="%Y/%d/%m %H:%M:%S")
114 | except:
115 | pass
116 |
117 | with open('config.json','r',encoding='utf-8') as fp:
118 | configData = json.load(fp)
119 |
120 | pm = PushMessage(title="B站经验脚本消息推送", email=configData["email"])
121 |
122 | for x in configData["cookieDatas"]:
123 | bili_exp(x, pm)
124 |
125 | try:
126 | pm.pushMessage()
127 | except Exception as e:
128 | logging.warning(f'消息推送异常,原因为{str(e)}')
129 |
130 | if __name__=="__main__":
131 | main()
132 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/README.md:
--------------------------------------------------------------------------------
1 | ## Bilibili 自动签到模版
2 |
3 | ### 项目说明
4 | 本项目为您提供了基于腾讯云云函数部署的B站定时签到模版
5 |
6 | ### 部署前提
7 | 1. 主账号已开通 [SCF 云函数](https://console.cloud.tencent.com/scf)与 [Serverless Framework](https://console.cloud.tencent.com/sls) 服务
8 | 2. 如果为子账号,已拥有[调用服务角色 SLS_QcsRole](https://cloud.tencent.com/document/product/1154/43006#4) 的权限
9 |
10 | ### 部署说明
11 | 1. 通过一下指令安装命令行工具 **Serverless Framework**, 如果您已经安装过命令行工具,也可通过该指令更新至最新版本
12 |
13 | ```
14 | npm install -g serverless
15 | ```
16 |
17 | 2. 下载项目模版代码并进入模版目录
18 | ```
19 | sls init biliexp-demo
20 | cd biliexp-demo
21 | ```
22 |
23 | 3. 打开 config.json 文档,根据说明填入对应内容
24 | ```
25 | {
26 | "cookieDatas":[
27 | {
28 | "SESSDATA": "",
29 | "bili_jct": "",
30 | "DedeUserID": ""
31 | }
32 | ],
33 | "email": "" ,
34 | "说明":"cookieDatas由浏览器获取, email 处填入您用于接受通知的邮件名"
35 | }
36 | ```
37 | cookieDatas 获取方式: 浏览器打开B站主页并登陆--》按F12打开开发者工具--》application--》cookies
38 |
39 | 
40 |
41 |
42 | 4. 通过 `sls deploy` 完成部署,部署成功后,每日可自动触发
43 |
44 | 部署时,您可以选择扫码授权,或配置 `.env ` 文件使用永久密钥进行授权
45 |
46 | ### 更多
47 | 1. 项目模版默认配置每天 15:30 的定时触发,部署地域在广州,您可以通过修改 serverless.yml 配置文件,修改您的更多项目配置信息
48 |
49 | 2. `.env` 文件配置指引
50 | ```
51 | # .env
52 | TENCENT_SECRET_ID=xxx # 您账号的SecretId
53 | TENCENT_SECRET_KEY=xxx # 您账号的SecretKey
54 | ```
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookieDatas":[
3 | {
4 | "SESSDATA": "",
5 | "bili_jct": "",
6 | "DedeUserID": ""
7 | }
8 | ],
9 | "email": "" ,
10 | "说明":"cookieDatas由浏览器获取, email 处填入您用于接受通知的邮件名"
11 | }
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/Article.py:
--------------------------------------------------------------------------------
1 | from models.Biliapi import BiliWebApi
2 |
3 | class Article(object):
4 | "B站专栏类,用于发表B站专栏"
5 | createArticle = BiliWebApi.createArticle
6 | deleteArticle = BiliWebApi.deleteArticle
7 | getArticle = BiliWebApi.getArticle
8 | articleUpcover = BiliWebApi.articleUpcover
9 | articleCreateVote = BiliWebApi.articleCreateVote
10 | articleCardsBvid = BiliWebApi.articleCardsBvid
11 | articleCardsCvid = BiliWebApi.articleCardsCvid
12 | articleMangas = BiliWebApi.articleMangas
13 | #本类只继承BiliWebApi中与Article有关的方法
14 | class Content(object):
15 | "文本类用来处理B站奇葩的专栏提交格式"
16 | def __init__(self):
17 | self.__content = ""
18 | def add(self, text):
19 | "添加内容"
20 | self.__content = f'{self.__content}{text}'
21 | return self
22 | def startH(self):
23 | "开始一个标题"
24 | self.__content = f'{self.__content}
'
25 | return self
26 | def endH(self):
27 | "结束一个标题"
28 | self.__content = f'{self.__content}
'
29 | return self
30 | def startP(self, align=""):
31 | "开始一段正文"
32 | if align == "":
33 | self.__content = f'{self.__content}'
34 | elif align == "left":
35 | self.__content = f'{self.__content}
'
36 | elif align == "center":
37 | self.__content = f'{self.__content}
'
38 | elif align == "right":
39 | self.__content = f'{self.__content}
'
40 | else:
41 | self.__content = f'{self.__content}
'
42 | return self
43 | def endP(self):
44 | "结束一段正文"
45 | self.__content = f'{self.__content}
'
46 | return self
47 | def startD(self):
48 | "开始一段带下划线的文字"
49 | self.__content = f'{self.__content}'
50 | return self
51 | def endD(self):
52 | "结束一段带下划线的文字"
53 | self.__content = f'{self.__content}'
54 | return self
55 | def startD(self, size=16):
56 | "开始一段大小为size的文字"
57 | self.__content = f'{self.__content}'
58 | return self
59 | def endD(self):
60 | "结束一段特定大小的文字"
61 | self.__content = f'{self.__content}'
62 | return self
63 | def startB(self):
64 | "开始一段加粗的文字"
65 | self.__content = f'{self.__content}'
66 | return self
67 | def endB(self):
68 | "结束一段加粗的文字"
69 | self.__content = f'{self.__content}'
70 | return self
71 | def startY(self):
72 | "开始一段引用"
73 | self.__content = f'{self.__content}'
74 | return self
75 | def endY(self):
76 | "结束一段引用"
77 | self.__content = f'{self.__content}
'
78 | return self
79 | def br(self):
80 | "插入换行,不用结束,一般新段默认换行"
81 | self.__content = f'{self.__content}
'
82 | return self
83 | def line(self,type=0):
84 | "插入一段分割线,不用结束"
85 | ll = ('
',
86 | '
',
87 | '
',
88 | '
',
89 | '
',
90 | '
')
91 | self.__content = f'{self.__content}{ll[type]}'
92 | return self
93 | def startU(self):
94 | "开始一段无序列表"
95 | self.__content = f'{self.__content}'
96 | return self
97 | def endU(self):
98 | "结束一段无序列表"
99 | self.__content = f'{self.__content}
'
100 | return self
101 | def startO(self):
102 | "开始一段有序列表"
103 | self.__content = f'{self.__content}'
104 | return self
105 | def endO(self):
106 | "结束一段有序列表"
107 | self.__content = f'{self.__content}
'
108 | return self
109 | def startL(self):
110 | "开始列表中的一列"
111 | self.__content = f'{self.__content}'
112 | return self
113 | def endL(self):
114 | "结束列表中的一列"
115 | self.__content = f'{self.__content}'
116 | return self
117 | def startA(self, url=""):
118 | "插入站内链接,链接说明文字请用add方法添加"
119 | self.__content = f'{self.__content}'
120 | return self
121 | def endA(self):
122 | "结束插入站内链接"
123 | self.__content = f'{self.__content}'
124 | return self
125 | def picUrl(self, url="", text="", width="", height=""):
126 | "插入站内图片链接,添加图片说明,指定图片长宽,比如15px,25%"
127 | self.__content = f'{self.__content}
{text}'
133 | return self
134 | def picFile(self, article: "Article类的实例", file: "本地图片文件或图片Bytes", text="", width="", height=""):
135 | "插入本地图片文件或Bytes,添加图片说明,指定图片长宽,比如15px,25%"
136 | ret = article.articleUpcover(file)
137 | picurl = ret["data"]["url"]
138 | picurl = picurl.replace("http", "https")
139 | return self.picUrl(picurl, text, width, height)
140 | def vote(self, article: "Article类的实例", vote: "vote投票结构体字典"):
141 | "插入站内投票"
142 | id = article.articleCreateVote(vote)["data"]["vote_id"]
143 | self.__content = f'{self.__content}
{vote["title"]}'
144 | return self
145 | def card(self, Article: "Article类的实例", id: "根据type类型填写id", type: "video:视频标签 str,article:专栏标签,fanju:番剧标签,music:音乐标签,shop:会员购标签,caricature:漫画标签 int,live:直播标签 str"):
146 | "插入引用标签"
147 | def video():
148 | ret = Article.articleCardsBvid(id)
149 | picurl = ret["data"][id]["pic"]
150 | picurl = picurl.replace("http", "https")
151 | aid = ret["data"][id]["aid"]
152 | return f'
'
153 | def article():
154 | ret = Article.articleCardsCvid(id)
155 | picurl = ret["data"]["banner_url"]
156 | picurl = picurl.replace("http", "https")
157 | aid = ret["data"]["id"]
158 | return f'
'
159 | def fanju():
160 | ret = Article.articleCardsCvid(id)
161 | picurl = ret["data"]["cover"]
162 | picurl = picurl.replace("http", "https")
163 | return f'
'
164 | def music():
165 | ret = Article.articleCardsCvid(id)
166 | picurl = ret["data"]["cover_url"]
167 | picurl = picurl.replace("http", "https")
168 | return f'
'
169 | def shop():
170 | ret = Article.articleCardsCvid(id)
171 | picurl = ret["data"]["performance_image"]
172 | picurl = picurl.replace("http", "https")
173 | return f'
'
174 | def caricature():
175 | ret = Article.articleMangas(id)
176 | picurl = ret["data"][id]["vertical_cover"]
177 | picurl = picurl.replace("http", "https")
178 | return f'
'
179 | def live():
180 | ret = Article.articleCardsCvid(id)
181 | picurl = ret["data"]["cover"]
182 | picurl = picurl.replace("http", "https")
183 | aid = ret["data"]["room_id"]
184 | return f'
'
185 |
186 | index = {
187 | "video": video,
188 | "article": article,
189 | "fanju": fanju,
190 | "music": music,
191 | "shop": shop,
192 | "caricature": caricature,
193 | "live": live,
194 | }
195 | if type in index:
196 | self.__content = f'{self.__content}{index[type]()}'
197 | return self
198 | def output(self):
199 | "输出,用于Article类提交content"
200 | return self.__content
201 |
202 | def __init__(self, cookieData, tilte="", content="", aid=0, category=0, list_id=0, tid=4, original=1, image_urls="", origin_image_urls=""):
203 | "创建一个B站专栏草稿"
204 | self.DoNotDel = False #在本类销毁时删除未提交文章,如果想保留请设置为True
205 | BiliWebApi.__init__(self, cookieData)
206 | self.__tilte = tilte
207 | self.__content = content
208 | self.__category = category
209 | self.__list_id = list_id
210 | self.__tid = tid
211 | self.__original = original
212 | self.__image_urls = image_urls
213 | self.__origin_image_urls = origin_image_urls
214 | self.__issubmit = False
215 | if(aid == 0):
216 | ret = self.createArticle(tilte, content, aid, category, list_id, tid, original, image_urls, origin_image_urls)
217 | self.__aid = ret["data"]["aid"]
218 | else:
219 | self.__aid = aid
220 |
221 | def setTilte(self, tilte=0):
222 | "设置专栏标题"
223 | self.__tilte = tilte
224 | def setCategory(self, category=0):
225 | "设置专栏分类"
226 | self.__category = category
227 | def setListId(self, list_id=0):
228 | "设置文集编号"
229 | self.__list_id = list_id
230 | def setTid(self, tid=4):
231 | "设置专栏封面类型"
232 | self.__tid = tid
233 | def setOriginal(self, original=1):
234 | "设置专栏是否为原创,原创为1,非原创为0"
235 | self.__original = original
236 | def setImage(self, origin_image_urls: str, image_urls=""):
237 | "设置专栏缩略图,image_urls为缩略图网址,origin_image_urls为缩略图原图在文章中的网址"
238 | self.__origin_image_urls = origin_image_urls
239 | if image_urls:
240 | self.__image_urls = image_urls
241 | else:
242 | self.__image_urls = origin_image_urls
243 | def setContent(self, content):
244 | "设置文章内容"
245 | self.__content = content
246 |
247 | def __del__(self):
248 | "删除已创建的文章"
249 | if(self.__issubmit == False and self.__aid != 0 and self.DoNotDel == False):
250 | self.deleteArticle(self.__aid)
251 |
252 | def getAid(self, url=False):
253 | "返回创建文章的aid或url,可通过url在网页上修改此文章"
254 | if url:
255 | return f'https://member.bilibili.com/v2#/upload/text/edit?aid={self.__aid}'
256 | else:
257 | return self.__aid
258 |
259 | def refresh(self):
260 | "如果在本程序外(例如网页上)修改了本文章,执行此函数同步"
261 | ret = self.getArticle(self.__aid)
262 | self.__tilte = ret["data"]["tilte"]
263 | self.__content = ret["data"]["content"]
264 | self.__category = ret["data"]["category"]["id"]
265 | if(ret["data"]["list"] != None):
266 | self.__list_id = ret["data"]["list"]["id"]
267 | self.__tid = ret["data"]["template_id"]
268 | self.__original = ret["data"]["original"]
269 | self.__image_urls = ret["data"]["image_urls"][0]
270 | self.__origin_image_urls = ret["data"]["origin_image_urls"][0] #这里可能有丢失封面的问题
271 |
272 | def save(self):
273 | "保存至B站上草稿箱,不发布,网页上可编辑"
274 | return self.createArticle(self.__tilte, self.__content, self.__aid, self.__category, self.__list_id, self.__tid, self.__original, self.__image_urls, self.__origin_image_urls)
275 |
276 | def submit(self):
277 | "发布至B站上"
278 | self.__issubmit = True
279 | return self.createArticle(self.__tilte, self.__content, self.__aid, self.__category, self.__list_id, self.__tid, self.__original, self.__image_urls, self.__origin_image_urls, True)
280 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/Biliapi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import requests
3 | import json
4 | class BiliWebApi(object):
5 | "B站web的api接口"
6 | def __init__(self, cookieData=None):
7 | #创建session
8 | self.__session = requests.session()
9 | #设置header
10 | self.__session.headers.update({"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108","Referer": "https://www.bilibili.com/",'Connection': 'keep-alive'})
11 | if cookieData == None:
12 | return
13 |
14 | #添加cookie
15 | requests.utils.add_dict_to_cookiejar(self.__session.cookies, cookieData)
16 | if 'bili_jct' in cookieData:
17 | self.__bili_jct = cookieData["bili_jct"]
18 | else:
19 | self.__bili_jct = ''
20 | #self.__uid = cookieData["DedeUserID"]
21 |
22 | data = self.__session.get("https://api.bilibili.com/x/web-interface/nav").json()
23 | if data["code"] != 0:
24 | raise Exception("参数验证失败,登录状态失效")
25 | self.__name = data["data"]["uname"]
26 | self.__uid = data["data"]["mid"]
27 | #print(self.__session.cookies)
28 |
29 | def getUserName(self):
30 | "获取登录的账户用户名"
31 | return self.__name
32 |
33 | def getReward(self):
34 | "取B站经验信息"
35 | url = "https://account.bilibili.com/home/reward"
36 | return self.__session.get(url).json()["data"]
37 |
38 | def getWebNav(self):
39 | "取导航信息"
40 | url = "https://api.bilibili.com/x/web-interface/nav"
41 | return self.__session.get(url).json()["data"]
42 |
43 | @staticmethod
44 | def getId(url):
45 | "取B站指定视频链接的aid和cid号"
46 | import re
47 | content = requests.get(url, headers=Biliapi.__headers)
48 | match = re.search( 'https:\/\/www.bilibili.com\/video\/av(.*?)\/\">', content.text, 0)
49 | aid = match.group(1)
50 | match = re.search( '\"cid\":(.*?),', content.text, 0)
51 | cid = match.group(1)
52 | return {"aid": aid, "cid": cid}
53 |
54 | def getCoin(self):
55 | "获取剩余硬币数"
56 | url = "https://api.bilibili.com/x/web-interface/nav?build=0&mobi_app=web"
57 | return int(self.__session.get(url).json()["data"]["money"])
58 |
59 | def coin(self, aid, num, select_like):
60 | "给指定av号视频投币"
61 | url = "https://api.bilibili.com/x/web-interface/coin/add"
62 | post_data = {
63 | "aid": aid,
64 | "multiply": num,
65 | "select_like": select_like,
66 | "cross_domain": "true",
67 | "csrf": self.__bili_jct
68 | }
69 | return self.__session.post(url, post_data).json()
70 |
71 | def share(self, aid):
72 | "分享指定av号视频"
73 | url = "https://api.bilibili.com/x/web-interface/share/add"
74 | post_data = {
75 | "aid": aid,
76 | "csrf": self.__bili_jct
77 | }
78 | return self.__session.post(url, post_data).json()
79 |
80 | def report(self, aid, cid, progres):
81 | "B站上报观看进度"
82 | url = "http://api.bilibili.com/x/v2/history/report"
83 | post_data = {
84 | "aid": aid,
85 | "cid": cid,
86 | "progres": progres,
87 | "csrf": self.__bili_jct
88 | }
89 | return self.__session.post(url, post_data).json()
90 |
91 | def getHomePageUrls(self):
92 | "取B站首页推荐视频地址列表"
93 | import re
94 | url = "https://www.bilibili.com"
95 | content = self.__session.get(url)
96 | match = re.findall( '', content.text, 0)
97 | match = ["https:" + x for x in match]
98 | return match
99 |
100 | @staticmethod
101 | def getRegions(rid=1, num=6):
102 | "获取B站分区视频信息"
103 | url = "https://api.bilibili.com/x/web-interface/dynamic/region?ps=" + str(num) + "&rid=" + str(rid)
104 | datas = requests.get(url).json()["data"]["archives"]
105 | ids = []
106 | for x in datas:
107 | ids.append({"title": x["title"], "aid": x["aid"], "bvid": x["bvid"], "cid": x["cid"]})
108 | return ids
109 |
110 | @staticmethod
111 | def getRankings(rid=1, day=3):
112 | "获取B站分区排行榜视频信息"
113 | url = "https://api.bilibili.com/x/web-interface/ranking?rid=" + str(rid) + "&day=" + str(day)
114 | datas = requests.get(url).json()["data"]["list"]
115 | ids = []
116 | for x in datas:
117 | ids.append({"title": x["title"], "aid": x["aid"], "bvid": x["bvid"], "cid": x["cid"], "coins": x["coins"], "play": x["play"]})
118 | return ids
119 |
120 | def repost(self, dynamic_id, content="", extension='{"emoji_type":1}'):
121 | "转发B站动态"
122 | url = "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/repost"
123 | post_data = {
124 | "uid": self.__uid,
125 | "dynamic_id": dynamic_id,
126 | "content": content,
127 | "extension": extension,
128 | #"at_uids": "",
129 | #"ctrl": "[]",
130 | "csrf_token": self.__bili_jct
131 | }
132 | return self.__session.post(url, post_data).json()
133 |
134 | def dynamicRepostReply(self, rid, content="", type=1, repost_code=3000, From="create.comment", extension='{"emoji_type":1}'):
135 | "评论动态并转发"
136 | url = "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/reply"
137 | post_data = {
138 | "uid": self.__uid,
139 | "rid": rid,
140 | "type": type,
141 | "content": content,
142 | "extension": extension,
143 | "repost_code": repost_code,
144 | "from": From,
145 | "csrf_token": self.__bili_jct
146 | }
147 | return self.__session.post(url, post_data).json()
148 |
149 | def followed(self, followid: 'up的uid', isfollow=True):
150 | "关注或取关up主"
151 | url = "https://api.vc.bilibili.com/feed/v1/feed/SetUserFollow"
152 | post_data = {
153 | "type": 1 if isfollow else 0,
154 | "follow": followid,
155 | "csrf_token": self.__bili_jct
156 | }
157 | return self.__session.post(url, post_data).json()
158 |
159 | def followedModify(self, followid: 'up的uid', act=1, re_src=11):
160 | "改变关注状态(增加、删除关注的up)"
161 | url = "https://api.bilibili.com/x/relation/modify"
162 | post_data = {
163 | "fid": followid,
164 | "act": act,
165 | "re_src": re_src,
166 | "csrf": self.__bili_jct
167 | }
168 | return self.__session.post(url, post_data).json()
169 |
170 | def groupAddFollowed(self, followid: 'up的uid', tagids=0):
171 | "移动关注的up主的分组"
172 | url = "https://api.bilibili.com/x/relation/tags/addUsers?cross_domain=true"
173 | post_data = {
174 | "fids": followid,
175 | "tagids": tagids, #默认是0,特别关注是-10
176 | "csrf": self.__bili_jct
177 | }
178 | return self.__session.post(url, post_data).json()
179 |
180 | def getFollowing(self, uid=0, pn=1, ps=50, order='desc'):
181 | "获取指定账户的关注者(默认取本账户)"
182 | if uid == 0:
183 | uid = self.__uid
184 | url = f"https://api.bilibili.com/x/relation/followings?vmid={uid}&pn={pn}&ps={ps}&order={order}"
185 | return self.__session.get(url).json()
186 |
187 | def getTopicInfo(self, tag_name):
188 | "取B站话题信息"
189 | url = f'https://api.bilibili.com/x/tag/info?tag_name={tag_name}'
190 | return self.__session.get(url).json()
191 |
192 | def getTopicList(self, tag_name):
193 | "取B站话题列表,返回一个可迭代对象"
194 | topic_id = self.getTopicInfo(tag_name)["data"]["tag_id"]
195 | url = f'https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_new?topic_id={topic_id}'
196 | jsobj = self.__session.get(url).json()
197 | cards = jsobj["data"]["cards"]
198 | for x in cards:
199 | yield x
200 | has_more = (jsobj["data"]["has_more"] == 1)
201 | while has_more:
202 | offset = jsobj["data"]["offset"]
203 | jsobj = self.__session.get(f'https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_history?topic_name={tag_name}&offset_dynamic_id={offset}').json()
204 | if not 'cards' in jsobj["data"]:
205 | break
206 | cards = jsobj["data"]["cards"]
207 | for x in cards:
208 | yield x
209 | has_more = (jsobj["data"]["has_more"] == 1)
210 |
211 | def getDynamicNew(self, type_list='268435455'):
212 | "取B站用户最新动态数据"
213 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid={self.__uid}&type_list={type_list}'
214 | content = self.__session.get(url)
215 | content.encoding = 'utf-8' #需要指定编码
216 | return json.loads(content.text)
217 |
218 | def getDynamic(self, uid=0, type_list='268435455'):
219 | "取B站用户动态数据,生成器"
220 | if not uid:
221 | uid = self.__uid
222 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid={uid}&type_list={type_list}'
223 | content = self.__session.get(url)
224 | content.encoding = 'utf-8' #需要指定编码
225 | jsobj = json.loads(content.text)
226 | cards = jsobj["data"]["cards"]
227 | for x in cards:
228 | yield x
229 | hasnext = True
230 | offset = cards[len(cards) - 1]["desc"]["dynamic_id"]
231 | while hasnext:
232 | content = self.__session.get(f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid={uid}&offset_dynamic_id={offset}&type={type_list}')
233 | content.encoding = 'utf-8'
234 | jsobj = json.loads(content.text)
235 | hasnext = (jsobj["data"]["has_more"] == 1)
236 | #offset = jsobj["data"]["next_offset"]
237 | cards = jsobj["data"]["cards"]
238 | for x in cards:
239 | yield x
240 | offset = cards[len(cards) - 1]["desc"]["dynamic_id"]
241 |
242 | def getMyDynamic(self, uid=0):
243 | "取B站用户自己的动态列表,生成器"
244 | import time
245 | def retry_get(url):
246 | times = 3
247 | while times:
248 | try:
249 | jsobj = self.__session.get(url).json()
250 | assert jsobj["code"] == 0
251 | return jsobj
252 | except:
253 | times -= 1
254 | time.sleep(3)
255 | raise Exception(str(jsobj))
256 |
257 | if uid == 0:
258 | uid = self.__uid
259 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid={uid}&need_top=1&offset_dynamic_id='
260 | hasnext = True
261 | offset = ''
262 | while hasnext:
263 | jsobj = retry_get(f'{url}{offset}')
264 | hasnext = (jsobj["data"]["has_more"] == 1)
265 | if not 'cards' in jsobj["data"]:
266 | continue
267 | cards = jsobj["data"]["cards"]
268 | for x in cards:
269 | yield x
270 | offset = x["desc"]["dynamic_id_str"]
271 |
272 | def removeDynamic(self, dynamic_id: int):
273 | "删除自己的动态"
274 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic'
275 | post_data = {
276 | "dynamic_id": dynamic_id,
277 | "csrf_token": self.__bili_jct
278 | }
279 | return self.__session.post(url, post_data).json()
280 |
281 | def getLotteryNotice(self, dynamic_id: int):
282 | "取指定抽奖信息"
283 | url = f'https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?dynamic_id={dynamic_id}'
284 | content = self.__session.get(url)
285 | content.encoding = 'utf-8'#不指定会出错
286 | return json.loads(content.text)
287 |
288 | def xliveSign(self):
289 | "B站直播签到"
290 | url = "https://api.live.bilibili.com/xlive/web-ucenter/v1/sign/DoSign"
291 | return self.__session.get(url).json()
292 |
293 | def xliveGetStatus(self):
294 | "B站直播获取金银瓜子状态"
295 | url = "https://api.live.bilibili.com/pay/v1/Exchange/getStatus"
296 | return self.__session.get(url).json()
297 |
298 | def silver2coin(self):
299 | "银瓜子兑换硬币"
300 | url = "https://api.live.bilibili.com/pay/v1/Exchange/silver2coin"
301 | post_data = {
302 | "csrf_token": self.__bili_jct
303 | }
304 | return self.__session.post(url, post_data).json()
305 |
306 | def createArticle(self, tilte="", content="", aid=0, category=0, list_id=0, tid=4, original=1, image_urls="", origin_image_urls="", submit=False):
307 | "发表专栏"
308 | post_data = {
309 | "title": tilte,
310 | "content": content,
311 | "category": category,#专栏分类,0为默认
312 | "list_id": list_id,#文集编号,默认0不添加到文集
313 | "tid": 4, #4为专栏封面单图,3为专栏封面三图
314 | "reprint": 0,
315 | "media_id": 0,
316 | "spoiler": 0,
317 | "original": original,
318 | "csrf": self.__bili_jct
319 | }
320 | url = 'https://api.bilibili.com/x/article/creative/draft/addupdate'#编辑地址,发表前可以通过这个来编辑草稿,没打草稿不允许发表
321 | if aid:
322 | post_data["aid"] = aid
323 | if submit:
324 | url = 'https://api.bilibili.com/x/article/creative/article/submit'#正式发表地址
325 | if origin_image_urls and image_urls:
326 | post_data["origin_image_urls"] = origin_image_urls
327 | post_data["image_urls"] = image_urls
328 | return self.__session.post(url, post_data).json()
329 |
330 | def deleteArticle(self, aid: int):
331 | "删除专栏"
332 | url = 'https://member.bilibili.com/x/web/draft/delete'
333 | post_data = {
334 | "aid": aid,
335 | "csrf": self.__bili_jct
336 | }
337 | return self.__session.post(url, post_data).json()
338 |
339 | def getArticle(self, aid: int):
340 | "获取专栏内容"
341 | url = f'https://api.bilibili.com/x/article/creative/draft/view?aid={aid}'
342 | return self.__session.get(url).json()
343 |
344 | def articleUpcover(self, file):
345 | "上传本地图片,返回链接"
346 | url = 'https://api.bilibili.com/x/article/creative/article/upcover'
347 | files = {
348 | 'binary':(file)
349 | }
350 | post_data = {
351 | "csrf": self.__bili_jct
352 | }
353 | return self.__session.post(url, post_data, files=files, timeout=(5, 60)).json()
354 |
355 | def articleCardsBvid(self, bvid: 'str 加上BV前缀'):
356 | "根据bv号获取视频信息,在专栏引用视频时使用"
357 | url = f'https://api.bilibili.com/x/article/cards?ids={bvid}&cross_domain=true'
358 | return self.__session.get(url).json()
359 |
360 | def articleCardsCvid(self, cvid: 'str 加上cv前缀'):
361 | "根据cv号获取专栏,在专栏引用其他专栏时使用"
362 | url = f'https://api.bilibili.com/x/article/cards?id={cvid}&cross_domain=true'
363 | return self.__session.get(url).json()
364 |
365 | def articleCardsId(self, epid: 'str 加上ep前缀'):
366 | "根据ep号获取番剧信息,在专栏引用站内番剧时使用"
367 | return self.articleCardsCvid(epid)
368 |
369 | def articleCardsAu(self, auid: 'str 加上au前缀'):
370 | "根据au号获取音乐信息,在专栏引用站内音乐时使用"
371 | return self.articleCardsCvid(auid)
372 |
373 | def articleCardsPw(self, pwid: 'str 加上pw前缀'):
374 | "根据au号获取会员购信息,在专栏引用会员购时使用"
375 | return self.articleCardsCvid(pwid)
376 |
377 | def articleMangas(self, mcid: 'int 不加mc前缀'):
378 | "根据mc号获取漫画信息,在专栏引用站内漫画时使用"
379 | url = f'https://api.bilibili.com/x/article/mangas?id={mcid}&cross_domain=true'
380 | return self.__session.get(url).json()
381 |
382 | def articleCardsLv(self, lvid: 'str 加上lv前缀'):
383 | "根据lv号获取直播信息,在专栏引用站内直播时使用"
384 | return self.articleCardsCvid(lvid)
385 |
386 | def articleCreateVote(self, vote):
387 | "创建一个投票"
388 | '''
389 | vote = {
390 | "title": "投票标题",
391 | "desc": "投票说明",
392 | "type": 0, #0为文字投票,1为图片投票
393 | "duration": 604800,#投票时长秒,604800为一个星期
394 | "options":[
395 | {
396 | "desc": "选项1",
397 | "cnt": 0,#不知道什么意思
398 | "idx": 1, #选项序号,第一个选项为1
399 | #"img_url": "http://i0.hdslb.com/bfs/album/d74e83cf96a9028eb3e280d5f877dce53760a7e2.jpg",#仅图片投票需要
400 | },
401 | {
402 | "desc": "选项2",
403 | "cnt": 0,
404 | "idx": 2, #选项序号,第二个选项为2
405 | #"img_url": ""
406 | }
407 | ]
408 | }
409 | '''
410 | post_data = {
411 | "info": vote,
412 | "csrf": self.__bili_jct
413 | }
414 | return self.__session.post(url, post_data).json()
415 |
416 | def videoPreupload(self, filename, filesize):
417 | "申请上传,返回上传信息"
418 | from urllib.parse import quote
419 | name = quote(filename)
420 | url = f'https://member.bilibili.com/preupload?name={name}&size={filesize}&r=upos&profile=ugcupos%2Fbup&ssl=0&version=2.8.9&build=2080900&upcdn=bda2&probe_version=20200628'
421 | return self.__session.get(url).json()
422 |
423 | def videoUploadId(self, url, auth):
424 | "向上传地址申请上传,得到上传id等信息"
425 | return self.__session.post(f'{url}?uploads&output=json', headers={"X-Upos-Auth": auth}).json()
426 |
427 | def videoUpload(self, url, auth, upload_id, data, chunk, chunks, start, total):
428 | "上传视频分块"
429 | size = len(data)
430 | end = start + size
431 | content = self.__session.put(f'{url}?partNumber={chunk+1}&uploadId={upload_id}&chunk={chunk}&chunks={chunks}&size={size}&start={start}&end={end}&total={total}', data=data, headers={"X-Upos-Auth": auth})
432 | return True if content.text == "MULTIPART_PUT_SUCCESS" else False
433 |
434 | def videoUploadInfo(self, url, auth, parts, filename, upload_id, biz_id):
435 | "查询上传视频信息"
436 | from urllib.parse import quote
437 | name = quote(filename)
438 | return self.__session.post(f'{url}?output=json&name={name}&profile=ugcupos%2Fbup&uploadId={upload_id}&biz_id={biz_id}', json={"parts":parts}, headers={"X-Upos-Auth": auth}).json()
439 |
440 | def videoRecovers(self, fns: '视频编号'):
441 | "查询以前上传的视频信息"
442 | url = f'https://member.bilibili.com/x/web/archive/recovers?fns={fns}'
443 | return self.__session.get(url=url).json()
444 |
445 | def videoTags(self, title: '视频标题', filename: "上传后的视频名称", typeid="", desc="", cover="", groupid=1, vfea=""):
446 | "上传视频后获得推荐标签"
447 | from urllib.parse import quote
448 | url = f'https://member.bilibili.com/x/web/archive/tags?typeid={typeid}&title={quote(title)}&filename=filename&desc={desc}&cover={cover}&groupid={groupid}&vfea={vfea}'
449 | return self.__session.get(url=url).json()
450 |
451 | def videoAdd(self, videoData:"dict 视频参数"):
452 | "发布视频"
453 | url = f'https://member.bilibili.com/x/vu/web/add?csrf={self.__bili_jct}'
454 | return self.__session.post(url, json=videoData).json()
455 |
456 | def videoPre(self):
457 | "视频预操作"
458 | url = 'https://member.bilibili.com/x/geetest/pre'
459 | return self.__session.get(url=url).json()
460 |
461 | def videoDelete(self, aid, geetest_challenge, geetest_validate, geetest_seccode):
462 | "删除视频"
463 | url = 'https://member.bilibili.com/x/web/archive/delete'
464 | post_data = {
465 | "aid": aid,
466 | "geetest_challenge": geetest_challenge,
467 | "geetest_validate": geetest_validate,
468 | "geetest_seccode": geetest_seccode,
469 | "success": 1,
470 | "csrf": self.__bili_jct
471 | }
472 | return self.__session.post(url, post_data).json()
473 |
474 | @staticmethod
475 | def activityList(plat='2', mold=0, http=3, start_page=1, end_page=10):
476 | "获取B站活动列表,生成器"
477 | session = requests.session()
478 | url = f'https://www.bilibili.com/activity/page/list?plat={plat}&mold={mold}&http={http}&page={start_page}'
479 | list = session.get(url).json()["data"]["list"]
480 | while len(list):
481 | for x in list:
482 | yield x
483 | if start_page == end_page:
484 | break
485 | start_page += 1
486 | url = f'https://www.bilibili.com/activity/page/list?plat={plat}&mold={mold}&http={http}&page={start_page}'
487 | list = session.get(url).json()["data"]["list"]
488 | session.close()
489 |
490 | @staticmethod
491 | def activityAll():
492 | "获取B站活动列表"
493 | url = 'https://member.bilibili.com/x/app/h5/activity/videoall'
494 | return requests.get(url).json()
495 |
496 | def activityAddTimes(self, sid: 'str 活动sid', action_type: 'int 操作类型'):
497 | "增加B站活动的参与次数"
498 | url = 'https://api.bilibili.com/x/activity/lottery/addtimes'
499 | post_data = {
500 | "sid": sid,
501 | "action_type": action_type,
502 | "csrf": self.__bili_jct
503 | }
504 | #响应例子{"code":75405,"message":"获得的抽奖次数已达到上限","ttl":1}
505 | return self.__session.post(url, post_data).json()
506 |
507 | def activityDo(self, sid: 'str 活动sid', type: 'int 操作类型'):
508 | "参与B站活动"
509 | #B站有时候举行抽奖之类的活动,活动页面能查出活动的sid
510 | post_data = {
511 | "sid": sid,
512 | "type": type,
513 | "csrf": self.__bili_jct
514 | }
515 | #响应例子{"code":75415,"message":"抽奖次数不足","ttl":1,"data":null}
516 | return self.__session.post('https://api.bilibili.com/x/activity/lottery/do', post_data).json()
517 |
518 | def activityMyTimes(self, sid: 'str 活动sid'):
519 | "获取B站活动次数"
520 | url = f'https://api.bilibili.com/x/activity/lottery/mytimes?sid={sid}'
521 | #响应例子{"code":0,"message":"0","ttl":1,"data":{"times":0}}
522 | return self.__session.get(url=url).json()
523 |
524 | def xliveGetAward(self, platform="android"):
525 | "B站直播模拟客户端打开宝箱领取银瓜子"
526 | url = f'https://api.live.bilibili.com/lottery/v1/SilverBox/getAward?platform={platform}'
527 | return self.__session.get(url).json()
528 |
529 | def xliveGetCurrentTask(self, platform="android"):
530 | "B站直播模拟客户端获取时间宝箱"
531 | url = f'https://api.live.bilibili.com/lottery/v1/SilverBox/getCurrentTask?platform={platform}'
532 | return self.__session.get(url).json()
533 |
534 | def xliveGiftBagList(self):
535 | "B站直播获取背包礼物"
536 | url = 'https://api.live.bilibili.com/xlive/web-room/v1/gift/bag_list'
537 | return self.__session.get(url=url).json()
538 |
539 | def xliveGetRecommendList(self):
540 | "B站直播获取首页前10条直播"
541 | url = f'https://api.live.bilibili.com/relation/v1/AppWeb/getRecommendList'
542 | return self.__session.get(url=url).json()
543 |
544 | def xliveBagSend(self, biz_id, ruid, bag_id, gift_id, gift_num, storm_beat_id=0, price=0, platform="pc"):
545 | "B站直播送出背包礼物"
546 | url = 'https://api.live.bilibili.com/gift/v2/live/bag_send'
547 | post_data = {
548 | "uid": self.__uid,
549 | "gift_id": gift_id, #背包里的礼物id
550 | "ruid": ruid, #up主的uid
551 | "send_ruid": 0,
552 | "gift_num": gift_num, #送礼物的数量
553 | "bag_id": bag_id, #背包id
554 | "platform": platform, #平台
555 | "biz_code": "live",
556 | "biz_id": biz_id, #房间号
557 | #"rnd": rnd, #直播开始时间
558 | "storm_beat_id": storm_beat_id,
559 | "price": price, #礼物价格
560 | "csrf": self.__bili_jct
561 | }
562 | return self.__session.post(url,post_data).json()
563 |
564 | def xliveGetRoomInfo(self, room_id: 'int 房间id'):
565 | "B站直播获取房间信息"
566 | url = f'https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={room_id}'
567 | return self.__session.get(url=url).json()
568 |
569 | def xliveWebHeartBeat(self, biz_id, last=11, platform="web"):
570 | "B站直播 直播间心跳"
571 | import base64
572 | hb = base64.b64encode(f'{last}|{biz_id}|1|0'.encode('utf-8')).decode()
573 | url = f'https://live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb={hb}&pf={platform}'
574 | return self.__session.get(url).json()
575 |
576 | def xliveHeartBeat(self):
577 | "B站直播 心跳(大约2分半一次)"
578 | url = f'https://api.live.bilibili.com/relation/v1/Feed/heartBeat'
579 | return self.__session.get(url).json()
580 |
581 | def xliveUserOnlineHeart(self):
582 | "B站直播 用户在线心跳(很少见)"
583 | url = f'https://api.live.bilibili.com/User/userOnlineHeart'
584 | post_data = {
585 | "csrf": self.__bili_jct
586 | }
587 | content = self.__session.post(url, post_data)
588 | return self.__session.post(url, post_data).json()
589 |
590 | def mangaClockIn(self, platform="android"):
591 | "模拟B站漫画客户端签到"
592 | url = "https://manga.bilibili.com/twirp/activity.v1.Activity/ClockIn"
593 | post_data = {
594 | "platform": platform
595 | }
596 | return self.__session.post(url, post_data).json()
597 |
598 | def mangaGetWallet(self, platform="web"):
599 | "获取钱包信息"
600 | url = f'https://manga.bilibili.com/twirp/user.v1.User/GetWallet?platform={platform}'
601 | #{"code":0,"msg":"","data":{"remain_coupon":0,"remain_gold":0,"first_reward":false,"point":"1270","first_bonus_percent":0,"bonus_percent":0,"unusable_gold":0,"remain_item":0,"remain_tickets":0,"remain_discount":0,"account_level":0}}
602 | # 劵的数量 金币数量 是否第一次充值 积分
603 | return self.__session.post(url, json={}).json()
604 |
605 | def mangaComrade(self, platform="web"):
606 | "站友日漫画卷兑换"
607 | url = f'https://manga.bilibili.com/twirp/activity.v1.Activity/Comrade?platform={platform}'
608 | #{"code":0,"msg":"","data":{"now":"2020-09-20T21:10:38+08:00","received":0,"active":0,"lottery":0,"svip":1}}
609 | return self.__session.post(url, json={}).json()
610 |
611 | def mangaGetEpisodeBuyInfo(self, ep_id: int, platform="web"):
612 | "获取漫画购买信息"
613 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisodeBuyInfo?platform={platform}'
614 | post_data = {
615 | "ep_id": ep_id
616 | }
617 | return self.__session.post(url, json=post_data).json()
618 |
619 | def mangaBuyEpisode(self, ep_id: int, buy_method=1, coupon_id=0, auto_pay_gold_status=0, platform="web"):
620 | "购买漫画"
621 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/BuyEpisode?&platform={platform}'
622 | post_data = {
623 | "buy_method": buy_method,
624 | "ep_id": ep_id
625 | }
626 | #{"buy_method":2,"ep_id":283578,"coupon_id":2064528,"auto_pay_gold_status":2}
627 | if coupon_id:
628 | post_data["coupon_id"] = coupon_id
629 | if auto_pay_gold_status:
630 | post_data["auto_pay_gold_status"] = auto_pay_gold_status
631 |
632 | #{"code":1,"msg":"没有足够的卡券使用次数,请刷新重试。","data":{"auto_use_item":""}}
633 | return self.__session.post(url, json=post_data).json()
634 |
635 | def mangaGetTopic(self, page_num=1, platform='phone'):
636 | "B站漫画app活动中心列表"
637 | url = 'https://manga.bilibili.com/twirp/comic.v1.Comic/Topic'
638 | post_data = {
639 | "page_num": page_num,
640 | "platform": platform
641 | }
642 | return self.__session.post(url, post_data).json()
643 |
644 | def mangaListFavorite(self, page_num=1, page_size=50, order=1, wait_free=0, platform='web'):
645 | "B站漫画追漫列表"
646 | url = 'https://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/ListFavorite?platform={platform}'
647 | post_data = {
648 | "page_num": page_num,
649 | "page_size": page_size,
650 | "order": order,
651 | "wait_free": wait_free
652 | }
653 | return self.__session.post(url, json=post_data).json()
654 |
655 | def mangaPayBCoin(self, pay_amount: int, product_id=1, platform='web'):
656 | "B币购买漫画"
657 | url = f'https://manga.bilibili.com/twirp/pay.v1.Pay/PayBCoin?platform={platform}'
658 | post_data = {
659 | "pay_amount": pay_amount,
660 | "product_id": product_id
661 | }
662 | #{"code":0,"msg":"","data":{"id":"1600656017507211119"}}
663 | return self.__session.post(url, json=post_data).json()
664 |
665 | def mangaGetBCoin(self, platform='web'):
666 | "获取B币与漫读劵的信息"
667 | url = f'https://manga.bilibili.com/twirp/pay.v1.Pay/GetBCoin?platform={platform}'
668 | #{"code":0,"msg":"","data":{"amount":0,"exchange_rate":100,"first_max_coin":18800,"first_bonus_percent":0,"bonus_percent":0,"coupon_rate":2,"coupon_exp":30,"point_rate":200,"coin_amount":0,"coupon_amount":0,"is_old_version":true}}
669 | return self.__session.post(url, json={}).json()
670 |
671 | def mangaGetCoupons(self, not_expired=True, page_num=1, page_size=50, tab_type=1, platform='web'):
672 | "获取账户中的漫读劵信息"
673 | url = f'https://manga.bilibili.com/twirp/user.v1.User/GetCoupons?platform={platform}'
674 | post_data = {
675 | "not_expired": not_expired,
676 | "page_num": page_num,
677 | "page_size": page_size,
678 | "tab_type": tab_type
679 | }
680 | #{"code":0,"msg":"","data":{"total_remain_amount":4,"user_coupons":[{"ID":2093696,"remain_amount":2,"expire_time":"2020-10-21 10:40:17","reason":"B币兑换","type":"福利券","ctime":"2020-09-21 10:40:17","total_amount":2,"limits":[],"type_num":7,"will_expire":0,"discount":0,"discount_limit":0,"is_from_card":0},{"ID":2093703,"remain_amount":2,"expire_time":"2020-10-21 10:47:43","reason":"B币兑换","type":"福利券","ctime":"2020-09-21 10:47:43","total_amount":2,"limits":[],"type_num":7,"will_expire":0,"discount":0,"discount_limit":0,"is_from_card":0}],"coupon_info":{"new_coupon_num":0,"coupon_will_expire":0,"rent_will_expire":0,"new_rent_num":0,"discount_will_expire":0,"new_discount_num":0,"month_ticket_will_expire":0,"new_month_ticket_num":0,"silver_will_expire":0,"new_silver_num":0,"remain_item":0,"remain_discount":0,"remain_coupon":4,"remain_silver":0}}}
681 | return self.__session.post(url, json=post_data).json()
682 |
683 | def mangaDetail(self, comic_id: int, device='pc', platform='web'):
684 | "获取漫画信息"
685 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/ComicDetail?device={device}&platform={platform}'
686 | post_data = {
687 | "comic_id": comic_id
688 | }
689 | return self.__session.post(url, json=post_data).json()
690 |
691 | def mangaGetPoint(self):
692 | "获取漫画积分"
693 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/GetUserPoint'
694 | return self.__session.post(url, json={}).json()
695 |
696 | def mangaShopList(self):
697 | "漫画积分商城列表"
698 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/ListProduct'
699 | return self.__session.post(url, json={}).json()
700 |
701 | def mangaShopExchange(self, product_id: int, point: int, product_num=1):
702 | "漫画积分商城兑换"
703 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/Exchange'
704 | post_data = {
705 | "product_id": product_id,
706 | "point": point,
707 | "product_num": product_num
708 | }
709 | return self.__session.post(url, json=post_data).json()
710 |
711 | def mangaImageToken(self, urls=[], device='pc', platform='web'):
712 | "获取漫画图片token"
713 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken?device={device}&platform={platform}'
714 | post_data = {
715 | "urls": json.dumps(urls)
716 | }
717 | return self.__session.post(url, json=post_data).json()
718 |
719 | def mangaImageIndex(self, ep_id: int, device='pc', platform='web'):
720 | "获取漫画图片列表"
721 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex?device={device}&platform={platform}'
722 | post_data = {
723 | "ep_id": ep_id
724 | }
725 | return self.__session.post(url, json=post_data).json()
726 |
727 | def mangaGetImageBytes(self, url: str):
728 | "获取漫画图片"
729 | return self.__session.get(url).content
730 |
731 | def vipPrivilegeMy(self):
732 | "B站大会员权益列表"
733 | url = 'https://api.bilibili.com/x/vip/privilege/my'
734 | #{"code":0,"message":"0","ttl":1,"data":{"list":[{"type":1,"state":1,"expire_time":1601481599},{"type":2,"state":0,"expire_time":1601481599}]}}
735 | return self.__session.get(url).json()
736 |
737 | def vipPrivilegeReceive(self, type=1):
738 | "领取B站大会员权益"
739 | url = 'https://api.bilibili.com/x/vip/privilege/receive'
740 | post_data = {
741 | "type": type,
742 | "csrf": self.__bili_jct
743 | }
744 | #{"code":69801,"message":"你已领取过该权益","ttl":1}
745 | return self.__session.post(url, data=post_data).json()
746 |
747 | @staticmethod
748 | def webView(bvid: str):
749 | "通过bv号获取视频信息"
750 | url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}'
751 | return requests.get(url,headers={"User-Agent": "Mozilla/5.0","Referer": "https://www.bilibili.com/"}).json()
752 |
753 | @staticmethod
754 | def webStat(aid: int):
755 | "通过av号获取视频信息"
756 | url = f'https://api.bilibili.com/x/web-interface/archive/stat?aid={aid}'
757 | return requests.get(url,headers={"User-Agent": "Mozilla/5.0","Referer": "https://www.bilibili.com/"}).json()
758 |
759 | @staticmethod
760 | def playList(bvid='', aid=0):
761 | "获取播放列表"
762 | if bvid:
763 | url = f'https://api.bilibili.com/x/player/pagelist?bvid={bvid}'
764 | elif aid:
765 | url = f'https://api.bilibili.com/x/player/pagelist?bvid={aid}'
766 | return requests.get(url).json()
767 |
768 | @staticmethod
769 | def epPlayList(ep_or_ss: str):
770 | "获取番剧播放列表"
771 | import re
772 | url = f'https://www.bilibili.com/bangumi/play/{ep_or_ss}'
773 | text = requests.get(url, headers={'User-Agent':'Mozilla/5.0'}).text
774 | find = re.findall(r'window.__INITIAL_STATE__=({.*});\(function\(\)', text, re.S)
775 | return json.loads(find[0])
776 |
777 | def webPlayUrl(self, cid=0, aid=0, bvid='', epid=0, qn=16):
778 | "获取番剧播放地址(普通视频请用playerUrl方法)"
779 | url = 'https://api.bilibili.com/pgc/player/web/playurl'
780 | data = {"qn":qn}
781 | if cid:
782 | data["cid"] = cid
783 | if aid:
784 | data["avid"] = aid
785 | if bvid:
786 | data["bvid"] = bvid
787 | if epid:
788 | data["ep_id"] = epid
789 | #{'code': -10403, 'message': '抱歉您所在地区不可观看!'}
790 | #{'code': -10403, 'message': '大会员专享限制'}
791 | return self.__session.get(url, params=data).json()
792 |
793 | def playerUrl(self, cid: int, aid=0, bvid='', qn=16, reverse_proxy=''):
794 | "获取视频播放地址"
795 | if reverse_proxy:
796 | url = reverse_proxy
797 | else:
798 | url = 'https://api.bilibili.com/x/player/playurl'
799 | #data = {"qn":qn,"cid":cid,'fnval':80}
800 | data = {"qn":qn,"cid":cid}
801 | if aid:
802 | data["avid"] = aid
803 | if bvid:
804 | data["bvid"] = bvid
805 | return self.__session.get(url, params=data).json()
806 |
807 | @staticmethod
808 | def videoGetPart(url: str, start, end):
809 | "下载视频分段"
810 | headers = {"Range":f'bytes={start}-{end}',"Referer": "https://www.bilibili.com/"}
811 | return requests.get(url, headers=headers).content
812 |
813 | @staticmethod
814 | def dmList(oid: int):
815 | "获得弹幕xml"
816 | url = f'https://api.bilibili.com/x/v1/dm/list.so?oid={oid}'
817 | content = requests.get(url)
818 | content.encoding = 'utf-8'
819 | return content.text
820 |
821 | @staticmethod
822 | def dmHistory(oid: int, data: str):
823 | "获得历史弹幕xml"
824 | url = f'https://api.bilibili.com/x/v2/dm/history?type=1&oid={oid}&date={data}'
825 | content = requests.get(url)
826 | content.encoding = 'utf-8'
827 | return content.text
828 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/Manga.py:
--------------------------------------------------------------------------------
1 | from models.Biliapi import BiliWebApi
2 |
3 | class Manga(object):
4 | "B站漫画下载类"
5 | def __init__(self, comic_id=0, cookieData=None):
6 | if cookieData:
7 | BiliWebApi.__init__(self, cookieData)
8 | else:
9 | import requests
10 | session = requests.session()
11 | session.headers.update({"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36","Referer": "https://www.bilibili.com/",'Connection': 'keep-alive'})
12 | self._BiliWebApi__session = session
13 | if comic_id:
14 | self.__manga_detail = BiliWebApi.mangaDetail(self, comic_id)["data"]
15 | self.__comic_id = self.__manga_detail["id"]
16 | self.__manga_detail["ep_list"].sort(key=lambda elem: elem["ord"])
17 | else:
18 | self.__manga_detail = None
19 |
20 | def setComicId(comic_id: int):
21 | "设置当前漫画id"
22 | self.__manga_detail = BiliWebApi.mangaDetail(self, comic_id)["data"]
23 | self.__comic_id = self.__manga_detail["id"]
24 | self.__manga_detail["ep_list"].sort(key=lambda elem: elem["ord"])
25 |
26 | def getIndex(self):
27 | "获取漫画章节列表"
28 | return self.__manga_detail["ep_list"]
29 |
30 | def getTitle(self):
31 | "获取漫画名称"
32 | return self.__manga_detail["title"]
33 |
34 | def getAuthors(self):
35 | "获取漫画作者名称(数组)"
36 | return self.__manga_detail["author_name"]
37 |
38 | def getCover(self):
39 | "获取漫画封面图片链接"
40 | return self.__manga_detail["vertical_cover"]
41 |
42 | def getNum(self):
43 | "获取漫画章节数量"
44 | return self.__manga_detail["last_ord"]
45 |
46 | def getDownloadList(self, ep_id: int):
47 | "获取漫画章节下载列表"
48 | data = BiliWebApi.mangaImageIndex(self, ep_id)["data"]["images"]
49 | url_list = [x["path"] for x in data]
50 | data = BiliWebApi.mangaImageToken(self, url_list)["data"]
51 | url_list = [f'{x["url"]}?token={x["token"]}' for x in data]
52 | return url_list
53 |
54 | def download(self, ep_id: int, path: str):
55 | "下载一个章节"
56 | import os
57 | if not os.path.exists(path):
58 | os.mkdir(path)
59 |
60 | import requests
61 | _s = requests.session()
62 |
63 | list = self.getDownloadList(ep_id)
64 | n = 0
65 | for x in list:
66 | n += 1
67 | with open(f'{path}/{n:0>2}.jpg', 'wb') as f:
68 | f.write(_s.get(x).content)
69 |
70 | def downloadAll(self, path):
71 | "下载漫画所有可下载章节"
72 | import os
73 | if not os.path.exists(path):
74 | os.mkdir(path)
75 | title = self.getTitle()
76 | if path[-1] == '/':
77 | path = f'{path}{title}'
78 | else:
79 | path = f'{path}/{title}'
80 | if not os.path.exists(path):
81 | os.mkdir(path)
82 | print(f'开始下载漫画 "{title}"')
83 | bq = len(str(self.getNum()))
84 | for x in self.getIndex():
85 | name = x["title"]
86 | if name.replace(' ', '') == '':
87 | name = x["short_title"]
88 | if not x["is_locked"]:
89 | self.download(x['id'], f'{path}/{x["ord"]:0>{bq}}-{name}')
90 | print(f'{x["ord"]:0>{bq}}-{name} 下载完成')
91 | else:
92 | print(f'{x["ord"]:0>{bq}}-{name} 目前需要解锁')
93 |
94 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/PushMessage.py:
--------------------------------------------------------------------------------
1 | class PushMessage(object):
2 | "消息推送"
3 | def __init__(self, title, SCKEY=None, email=None):
4 | "初始化"
5 | self.__title = title
6 | self.__message = ""
7 | self.__send_serverchan = f'https://sc.ftqq.com/{SCKEY}.send' if SCKEY else None
8 | self.__send_email = f'http://liuxingw.com/api/mail/api.php?address={email}' if email else None
9 | self.__title = title
10 | self.__message = ""
11 |
12 | def sendText(self, text, desp):
13 | "发送消息"
14 | import requests
15 | if self.__send_email:
16 | requests.get(self.__send_email, params={"name": text,"certno": desp.replace("\n","
")})
17 |
18 | if self.__send_serverchan:
19 | requests.post(self.__send_serverchan, data={"text": text,"desp": desp})
20 |
21 | def addMsg(self, msg, newLine=True):
22 | "添加要推送的消息"
23 | self.__message = f"{self.__message}{msg}\n" if newLine else f"{self.__message}{msg}"
24 |
25 | def pushMessage(self):
26 | "推送已经添加消息"
27 | self.sendText(self.__title, self.__message)
28 |
29 | def setMsg(self, msg):
30 | "设置要推送的消息"
31 | self.__message = msg
32 |
33 | def getMsg(self):
34 | "获取要推送的消息"
35 | return self.__message
36 |
37 | def setTitle(self, title):
38 | "设置消息标题"
39 | self.__title = title
40 |
41 | def getTitle(self):
42 | "获取当前消息标题"
43 | return self.__title
44 |
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/Video.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from models.Biliapi import BiliWebApi
3 | import time
4 |
5 | class VideoUploader(object):
6 | "B站视频上传类"
7 | videoPreupload = BiliWebApi.videoPreupload
8 | videoUploadId = BiliWebApi.videoUploadId
9 | videoUpload = BiliWebApi.videoUpload
10 | videoUploadInfo = BiliWebApi.videoUploadInfo
11 | videoRecovers = BiliWebApi.videoRecovers
12 | videoTags = BiliWebApi.videoTags
13 | videoAdd = BiliWebApi.videoAdd
14 | videoPre = BiliWebApi.videoPre
15 | videoDelete = BiliWebApi.videoDelete
16 | #本类只继承BiliWebApi中与Video上传有关的方法
17 | def __init__(self, cookieData, title="", desc="", dtime=0, tag=[], copyright=2, tid=174, source="", cover="",desc_format_id=0, subtitle={"open":0,"lan":""}):
18 | "创建一个B站视频上传类" #简介
19 | BiliWebApi.__init__(self, cookieData)
20 | self.__data = {
21 | "copyright":copyright,
22 | "videos":[],
23 | "source":source,
24 | "tid":tid, #分区,174为生活,其他分区
25 | "cover":cover, #封面图片,可由recovers方法得到视频的帧截图
26 | "title":title,
27 | "tag":"",
28 | "desc_format_id":desc_format_id,
29 | "desc":desc,
30 | "dynamic":"",
31 | "subtitle":subtitle
32 | }
33 | if dtime and dtime - int(time.time()) > 14400:
34 | self.__data["dtime"] = dtime
35 |
36 | for i in range(len(tag)):
37 | if (i == len(tag) - 1):
38 | self.__data["tag"] += tag[i]
39 | else:
40 | self.__data["dynamic"] += f'{tag[i]},'
41 | self.__data["tag"] += f'#{tag[i]}#'
42 |
43 | def uploadFile(self, filepath: '视频路径', fsize=8388608):
44 | "上传本地视频文件,返回视频信息dict"
45 | import os, math
46 | path,name = os.path.split(filepath)#分离路径与文件名
47 |
48 | with open(filepath,'rb') as f:
49 | size = f.seek(0, 2) #获取文件大小
50 | chunks = math.ceil(size / fsize) #获取分块数量
51 |
52 | retobj = self.videoPreupload(name, size) #申请上传
53 | auth = retobj["auth"]
54 | endpoint = retobj["endpoint"]
55 | biz_id = retobj["biz_id"]
56 | upos_uri = retobj["upos_uri"][6:]
57 | rname = os.path.splitext(upos_uri[5:])[0]
58 | url = f'https:{endpoint}{upos_uri}' #视频上传路径
59 |
60 | retobj = self.videoUploadId(url, auth)
61 | upload_id = retobj["upload_id"] #得到上传id
62 |
63 | #开始上传
64 | parts = [] #分块信息
65 | f.seek(0, 0)
66 | for i in range(chunks): #单线程分块上传,官方支持三线程
67 | data = f.read(fsize) #一次读取一个分块大小
68 | self.videoUpload(url, auth, upload_id, data, i, chunks, i*fsize, size)#上传分块
69 | parts.append({"partNumber":i+1,"eTag":"etag"}) #添加分块信息,partNumber从1开始
70 | #print(f'{i} / {chunks}')#输出上传进度
71 |
72 | preffix = os.path.splitext(name)[0]
73 | retobj = self.videoUploadInfo(url, auth, parts, name, upload_id, biz_id)
74 | if (retobj["OK"] == 1):
75 | return {"title": preffix, "filename": rname, "desc": ""}
76 | return {"title": preffix, "filename": "", "desc": ""}
77 |
78 | def submit(self):
79 | if self.__data["title"] == "":
80 | self.__data["title"] = self.__data["videos"][0]["title"]
81 | retobj = self.videoAdd(self.__data)
82 | if retobj["code"] == 0:
83 | self.__submit = retobj["data"]
84 | else:
85 | self.__submit = retobj
86 | return self.__submit
87 |
88 | def delete(self):
89 | "立即撤销本视频的发布(会丢失硬币)"
90 | aid = self.__submit["aid"]
91 | retobj = self.videoPre()
92 | challenge = retobj["data"]["challenge"]
93 | gt = retobj["data"]["gt"]
94 | return (self.videoDelete(aid, challenge, gt, f'{gt}%7Cjordan')["code"] == 0)
95 |
96 | def recovers(self, upvideo: "由uploadFile方法返回的dict"):
97 | "返回官方生成的封面,刚上传可能获取不到"
98 | return self.videoRecovers(upvideo["filename"])
99 |
100 | def getTags(self, upvideo: "由uploadFile方法返回的dict"):
101 | "返回官方推荐的tag"
102 | return self.videoTags(upvideo["title"], upvideo["filename"])
103 |
104 | def add(self, upvideo: "由uploadFile方法返回的dict"):
105 | "添加已经上传的视频"
106 | self.__data["videos"].append(upvideo)
107 |
108 | def clear(self):
109 | "清除已经添加的视频"
110 | self.__data["videos"] = []
111 |
112 | def setDtime(self, dtime: int):
113 | "设置延时发布时间,距离提交大于4小时,格式为10位时间戳"
114 | if dtime - int(time.time()) > 14400:
115 | self.__data["dtime"] = dtime
116 |
117 | def setTitle(self, title: str):
118 | "设置标题"
119 | self.__data["title"] = title
120 |
121 | def setDesc(self, desc: str):
122 | "设置简介"
123 | self.__data["desc"] = desc
124 |
125 | def setTag(self, tag: []):
126 | "设置标签,tag为数组"
127 | tagstr = ""
128 | dynamic = ""
129 | for i in range(len(tag)):
130 | if (i == len(tag) - 1):
131 | tagstr += tag[i]
132 | else:
133 | tagstr += f'{tag[i]},'
134 | dynamic += f'#{tag[i]}#'
135 |
136 | self.__data["tag"] = tagstr
137 | self.__data["dynamic"] = dynamic
138 |
139 | def setCopyright(self, copyright=2):
140 | "设置copyright"
141 | self.__data["copyright"] = copyright
142 |
143 | def setTid(self, tid=174):
144 | "设置视频分区"
145 | self.__data["tid"] = tid
146 |
147 | def setTitle(self, tid=174):
148 | "设置标题"
149 | self.__data["tid"] = tid
150 |
151 | def setSource(self, source=""):
152 | "设置转载原地址"
153 | self.__data["source"] = source
154 |
155 | def setCover(self, cover=""):
156 | "设置视频封面url"
157 | self.__data["cover"] = cover
158 |
159 | def setDescFormatId(self, desc_format_id=0):
160 | "设置desc_format_id"
161 | self.__data["desc_format_id"] = desc_format_id
162 |
163 | def setSubtitle(self, subtitle={"open":0,"lan":""}):
164 | "设置subtitle"
165 | self.__data["subtitle"] = subtitle
166 |
167 | class VideoDownloader(object):
168 | '''B站视频下载类'''
169 | class __videos(object):
170 | class __videostream(object):
171 | def __init__(self, name: str,url: str, resolution: str, size: int):
172 | self.__name = name
173 | self.__url = url
174 | self.__resolution = resolution
175 | self.__size = size
176 |
177 | def __repr__(self):
178 | return f''
179 |
180 | def __str__(self):
181 | return f'filename={self.__name} ; resolution={self.__resolution} ; size={self.__size / 1024 / 1024:0.2f}MB'
182 |
183 | def download(self, path='', callback=None):
184 | '''下载当前视频流'''
185 | if path != '':
186 | if path[-1] == '/':
187 | path = f'{path}{self.__name}'
188 | else:
189 | path = f'{path}/{self.__name}'
190 | else:
191 | path = self.__name
192 |
193 | from models.aria2py import Aria2Py
194 | aria2 = Aria2Py()
195 | ret = aria2.addUri(self.__url, {'max-connection-per-server':8,'referer': "https://www.bilibili.com","header":["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)"],'out':path})
196 | gid = ret["result"]
197 | while aria2.tellStatus(gid, ["status","completedLength","totalLength"])["result"]["status"] == 'waiting':
198 | time.sleep(2)
199 | while True:
200 | time.sleep(2)
201 | ret = aria2.tellStatus(gid, ["status","completedLength","totalLength"])["result"]
202 | if callback:
203 | if int(ret["totalLength"])!=0:
204 | callback(float(ret["completedLength"])/float(ret["totalLength"]))
205 | if ret["status"] != 'active':
206 | if ret["status"] != 'complete':
207 | raise Exception(f'下载失败(status: {ret["status"]})')
208 | break
209 | if callback:
210 | if int(ret["totalLength"])==0:
211 | continue
212 | callback(float(ret["completedLength"])/float(ret["totalLength"]))
213 |
214 | def __init__(self, subtitle, bvid='', cid=0, epid=''):
215 | self.__title = subtitle.replace('/',' ')
216 | self.__bvid = bvid
217 | self.__cid = cid
218 |
219 | def __repr__(self):
220 | return f''
221 |
222 | def __str__(self):
223 | return self.__title
224 |
225 | def getTitle(self):
226 | '''获取当前视频标题'''
227 | return self.__title
228 |
229 | def allStream(self, cookieData:dict =None, reverse_proxy='', force_use_proxy=False):
230 | '''
231 | 获取所有视频流
232 | cookieData dict :包含"SESSDATA"值的字典,模拟用户登录
233 | reverse_proxy str :B站接口代理地址
234 | force_use_proxy bool :强制使用代理地址(默认请求失败才尝试代理地址)
235 | '''
236 | biliapi = BiliWebApi(cookieData)
237 | if force_use_proxy:
238 | RP = reverse_proxy
239 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP)
240 | if data["code"] != 0:
241 | raise Exception(f'解析失败,请尝试使用会员账号(错误信息:{data["message"]})')
242 | else:
243 | RP = ''
244 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP)
245 | if data["code"] != 0:
246 | if reverse_proxy == '':
247 | raise Exception(f'解析失败,请尝试使用代理或会员账号(错误信息:{data["message"]})')
248 | else:
249 | RP = reverse_proxy
250 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP)
251 | if data["code"] != 0:
252 | print(self.__bvid, self.__cid)
253 | raise Exception(f'解析失败,请尝试更换代理地区或使用会员账号(错误信息:{data["message"]})')
254 |
255 | accept_quality = data["data"]["accept_quality"]
256 | accept_description = data["data"]["accept_description"]
257 | ret = []
258 | for ii in range(len(accept_quality)):
259 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, qn=accept_quality[ii], reverse_proxy=RP)["data"]
260 | if data["quality"] != accept_quality[ii]:
261 | continue
262 | if 'flv' in data["format"]:
263 | ret.append(self.__videostream(f'{self.__title}.flv', data["durl"][0]["url"].replace('http:','https:'),accept_description[ii],data["durl"][0]["size"]))
264 | else:
265 | ret.append(self.__videostream(f'{self.__title}.mp4', data["durl"][0]["url"].replace('http:','https:'),accept_description[ii],data["durl"][0]["size"]))
266 | return ret
267 |
268 | def __init__(self, url: str):
269 | self.set(url)
270 |
271 | def all(self):
272 | '''取得当前所有视频(分P)'''
273 | if self.__type == 1:
274 | list = BiliWebApi.playList(self.__bvid)["data"]
275 | return [self.__videos(x["part"], self.__bvid, x["cid"]) for x in list]
276 | elif self.__type == 2:
277 | return [self.__videos(x[0], x[1], x[2]) for x in self.__eplist]
278 | else:
279 | return []
280 |
281 | def set(self, url: str):
282 | '''
283 | 解析视频
284 | url str: BV,av,ep,ss号以及包含这些号的网址
285 | '''
286 | import re
287 | self.__type = 0
288 | find = re.findall('(BV|av|ep|ss)([0-9 a-z A-Z]*)', url)
289 | if len(find):
290 | if find[0][0] == 'BV':
291 | self.__bvid = f'BV{find[0][1]}'
292 | self.__title = BiliWebApi.webView(self.__bvid)["data"]["title"]
293 | self.__type = 1
294 | elif find[0][0] == 'av':
295 | self.__bvid = self.av2bv(find[0][1])
296 | self.__title = BiliWebApi.webView(self.__bvid)["data"]["title"]
297 | self.__type = 1
298 | elif find[0][0] == 'ep' or find[0][0] == 'ss':
299 | data = BiliWebApi.epPlayList(find[0][0] + find[0][1])
300 | self.__title = data["mediaInfo"]["title"]
301 | self.__eplist = [[f'{x["titleFormat"]} {x["longTitle"]}', x["bvid"], x["cid"]] for x in data["epList"]]
302 | self.__type = 2
303 | else:
304 | raise Exception("不支持的参数")
305 |
306 | def getTitle(self):
307 | '''获取标题'''
308 | return self.__title
309 |
310 | @staticmethod
311 | def bv2av(bvid: str):
312 | '''B站bv号转av号'''
313 | return BiliWebApi.webView(bvid)["data"]["aid"]
314 |
315 | @staticmethod
316 | def av2bv(aid: int):
317 | "B站av号转bv号"
318 | return BiliWebApi.webStat(aid)["data"]["bvid"]
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/models/aria2py.py:
--------------------------------------------------------------------------------
1 | import json, time
2 | import subprocess
3 | import os
4 |
5 | DEFAULT_HOST = 'localhost'
6 | DEFAULT_PORT = 6800
7 | DEFAULT_SECRET = ''
8 |
9 | class Aria2Py(object):
10 | '''Aria2下载器'''
11 | def __init__(self,
12 | host=DEFAULT_HOST,
13 | port=DEFAULT_PORT,
14 | secret=DEFAULT_SECRET,
15 | session=None,
16 | remote=False
17 | ):
18 | self.server_uri = f'http://{host}:{port}/jsonrpc'
19 | self.secret = secret
20 | import requests
21 | self.session = requests.session()
22 |
23 | if not remote:
24 | if self.isAria2Running():
25 | return
26 | if not Aria2Py.isAria2Installed():
27 | raise Exception('未找到aria2')
28 |
29 | cmd = f'aria2c' \
30 | ' --enable-rpc' \
31 | f' --rpc-listen-port {port}' \
32 | ' --continue' \
33 | ' --max-concurrent-downloads=20' \
34 | ' --max-connection-per-server=10' \
35 | ' --rpc-max-request-size=1024M'
36 |
37 | if not session is None:
38 | cmd += f' --input-file={session}' \
39 | ' --save-session-interval=60' \
40 | f' --save-session={session}'
41 |
42 | subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
43 |
44 | try:
45 | data = self.getGlobalStat()
46 | if 'error' in data:
47 | raise Exception(f'连接aria2失败({data["error"]["Unauthorized"]})')
48 | except:
49 | raise Exception('连接aria2失败')
50 |
51 | def sendJsonRPC(self, data):
52 | '''发送RPC请求'''
53 | return self.session.post(self.server_uri, data=data).json()
54 |
55 | def getRPCBody(self, method, params=[]):
56 | '''创建RPC请求'''
57 | uid = '14515821564dsfdvzxvdf'
58 | if self.secret:
59 | params.insert(0, f'token:{self.secret}')
60 | j = json.dumps({
61 | 'jsonrpc': '2.0',
62 | 'id': uid,
63 | 'method': method,
64 | 'params': params
65 | })
66 | return j
67 |
68 | def addUri(self, uris, options=None, position=None):
69 | '''
70 | 添加一个HTTP(S)/FTP/BitTorrent Magnet下载任务.
71 | uris: list, 链接数组
72 | options: dict, 附加参数
73 | position: int, 队列里的位置
74 | '''
75 | params = [[uris]]
76 | if options:
77 | params.append(options)
78 | if position:
79 | params.append(position)
80 | return self.sendJsonRPC(data=self.getRPCBody('aria2.addUri', params))
81 |
82 | def remove(self, gid):
83 | '''
84 | 移除下载任务.
85 | gid: string, 任务GID.
86 | '''
87 | params = [gid]
88 | return self.sendJsonRPC(data=self.getRPCBody('aria2.remove', params))
89 |
90 | def forceRemove(self, gid):
91 | '''
92 | 强制移除下载任务.
93 | gid: string, 任务GID.
94 | '''
95 | params = [gid]
96 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forceRemove', params))
97 |
98 | def pause(self, gid):
99 | '''
100 | 暂停任务.
101 | gid: string, GID.
102 | return: GID.
103 | '''
104 | params = [gid]
105 | return self.sendJsonRPC(data=self.getRPCBody('aria2.pause', params))
106 |
107 | def pauseAll(self):
108 | '''
109 | 暂停全部任务.
110 | return: 成功返回OK.
111 | '''
112 | return self.sendJsonRPC(data=self.getRPCBody('aria2.pauseAll'))
113 |
114 | def forcePause(self, gid):
115 | '''
116 | 强制暂停任务.
117 | gid: string, GID.
118 | '''
119 | params = [gid]
120 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forcePause', params))
121 |
122 | def forcePauseAll(self):
123 | '''
124 | 强制暂停全部任务.
125 | return: 成功返回OK.
126 | '''
127 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forcePauseAll'))
128 |
129 | def unpause(self, gid):
130 | '''
131 | 解除任务暂停状态.
132 | gid: string, GID.
133 | '''
134 | params = [gid]
135 | return self.sendJsonRPC(data=self.getRPCBody('aria2.unpause', params))
136 |
137 | def unpauseAll(self):
138 | '''
139 | 解除所有任务暂停状态.
140 | return: 成功返回OK.
141 | '''
142 | return self.sendJsonRPC(data=self.getRPCBody('aria2.unpauseAll'))
143 |
144 | def tellStatus(self, gid, keys=None):
145 | '''
146 | 返回下载进度.
147 | gid: string, GID.
148 | keys: list, 需要返回的参数列表.
149 | '''
150 | params = [gid]
151 | if keys:
152 | params.append(keys)
153 | return self.sendJsonRPC(data=self.getRPCBody('aria2.tellStatus', params))
154 |
155 | def tellActive(self, keys=None):
156 | '''
157 | 返回活跃的任务.
158 | keys: list, 需要返回的参数列表.
159 | '''
160 | params = []
161 | if keys:
162 | params.append(keys)
163 | return self.sendJsonRPC(data=self.getRPCBody('aria2.tellActive', params))
164 |
165 | def getGlobalStat(self):
166 | '''
167 | 获得全局参数比如上传或下载速度.
168 | return: The method response is of type struct and contains following keys.
169 | '''
170 | return self.sendJsonRPC(data=self.getRPCBody('aria2.getGlobalStat'))
171 |
172 | @staticmethod
173 | def isAria2Installed():
174 | '''Aria2是否已经安装'''
175 | if os.path.exists('aria2c'):
176 | return True
177 | if os.path.exists('aria2c.exe'):
178 | return True
179 | for cmdpath in os.environ['PATH'].split(':'):
180 | if os.path.isdir(cmdpath) and 'aria2c' in os.listdir(cmdpath):
181 | return True
182 | return False
183 |
184 | def isAria2Running(self):
185 | '''Aria2是否已经启动并能成功连接'''
186 | ret = True
187 | try:
188 | data = self.getGlobalStat()
189 | if 'error' in data:
190 | ret = False
191 | except:
192 | ret = False
193 | return ret
--------------------------------------------------------------------------------
/通过 Serverless 实现 B 站定时签到/serverless.yml:
--------------------------------------------------------------------------------
1 | #组件信息
2 | app: bilibili
3 | component: scf # (必填) 引用 component 的名称,当前用到的是 tencent-scf 组件
4 | name: BiliExp # (必填) 创建的实例名称,请修改成您的实例名称
5 |
6 | #B站自动领取经验
7 | inputs:
8 | name: ${app}-BiliExp #函数名称
9 | #namespace: bilibili
10 | src: ./ #代码路径
11 | handler: BiliExp.main #入口
12 | runtime: Python3.6 # 云函数运行时的环境
13 | region: ap-guangzhou # 云函数所在区域
14 | description: 'B站自动签到'
15 | timeout: 60
16 | events: # 触发器
17 | - timer: # 定时触发器
18 | name: #触发器名称,默认timer-${name}-${stage}
19 | parameters:
20 | cronExpression: '0 30 15 * * * *'
21 | enable: true
--------------------------------------------------------------------------------