33 Commits

Author SHA1 Message Date
81f730ea68 各种适配新Android Api,修复各种Bug 2019-01-31 23:05:48 +08:00
a1cfc9b914 Update : Replace delete downloaded files With move to oss 2019-01-31 17:23:18 +08:00
81763fdf37 Emeg Update : Fix isLive is always True 2019-01-31 13:09:21 +08:00
884122b007 Emeg Update: dynamic roomId fix 2019-01-30 19:07:07 +08:00
8c0320c97e 全面改为AndroidApi,添加抽奖识别及消息提示 2019-01-29 13:50:29 +08:00
14525ee76e 添加搜索功能[Beta]
Update ReadMe.md

Signed-off-by: Jerry Yan <792602257@qq.com>
2019-01-28 20:26:23 +08:00
cd4d7605f5 Fix Conflicts
# Conflicts:
#	api.py
2019-01-28 18:58:16 +08:00
5da70e81f9 Api使用Android app弹幕协议 2019-01-28 18:54:24 +08:00
f7727a3c39 Emeg: fix default room 2019-01-27 22:26:06 +08:00
e46e4ec0d7 Update README.md 2019-01-27 22:20:23 +08:00
a4f4c10efc 修正Linux版显示 2019-01-27 22:18:17 +08:00
82b076df3f 修正因未开播而导致的Queue内占满了False的BUG 2019-01-26 18:54:41 +08:00
cf8cbaae8c 修复删除已上传视频的逻辑,添加 追加上传功能 2019-01-26 16:29:04 +08:00
50216dc3e7 Create README.md 2019-01-26 14:09:26 +08:00
90435ccbaf 上传完成后自动删除片段,以节省内存 2019-01-26 14:04:23 +08:00
c41ed1f950 Bug Fixed 2019-01-26 13:21:44 +08:00
48cb2edc58 Fix Bugs And Improve Perfomance 2019-01-26 04:18:57 +08:00
798b59e3fd 修改Bilibili.py,尝试下载好片段就上传,等待下播后结束上传 2019-01-25 19:39:15 +08:00
d290c89345 初版自动投稿工具 to Bilibili
直播缓存完就直接上传了,正在测试中,感谢bilibili.py原代码作者:comwrg
2019-01-25 14:26:07 +08:00
33739d21b3 更新:添加未知消息的提示,添加设置房管消息检测 2019-01-23 19:43:53 +08:00
2df0dc07bd 临时更新:未返回导致的错误 2019-01-23 19:12:47 +08:00
746f993a8d 准备加入自动投递功能 2019-01-23 14:50:01 +08:00
4270612a98 更新大部分判断,WinMain加入全部显示参数启动 2019-01-22 21:37:44 +08:00
52a49e5e2d 临时更新,因西瓜视频WEB API变化 2019-01-22 15:22:30 +08:00
4572fd63ea Emeg Update 2019-01-22 15:08:05 +08:00
10754d8ca0 修正部分代码 2019-01-22 00:29:37 +08:00
ef732aa965 增加DEBUG模式 2019-01-22 00:28:25 +08:00
414aef7eb3 修正房间提示信息 2019-01-22 00:21:32 +08:00
e3da5d98b3 增加禁言消息 2019-01-22 00:13:49 +08:00
5d5a0a9432 改进标题文字 2019-01-20 23:29:53 +08:00
e3b82555d6 修正部分代码 2019-01-20 23:28:04 +08:00
ab06868f06 改进部分代码,增加Linux可运行的版本,可自己选择想要进入的房间
Signed-off-by: Jerry Yan <792602257@qq.com>
2019-01-20 23:10:35 +08:00
c3015eba65 误删除代码,暂时恢复部分,优化代码结构
待写文档,待写README
2019-01-17 11:42:04 +08:00
12 changed files with 1319 additions and 281 deletions

31
Chat.py Normal file
View File

@ -0,0 +1,31 @@
from User import User
from Lottery import Lottery
class Chat:
content: str =""
user: User=None
filterString:list = ["",]
isFiltered = False
def __init__(self, json=None, lottery:Lottery = None):
if json:
self.parse(json)
if lottery:
self.filterString.append(lottery.content)
def parse(self, json):
self.user = User(json)
if "extra" in json:
if "content" in json["extra"]:
self.content = json["extra"]['content']
if self.content in self.filterString:
self.isFiltered = True
def __str__(self):
return "{} : {}".format(self.user,self.content)
def __unicode__(self):
return self.__str__()

0
Digg.py Normal file
View File

53
Gift.py Normal file
View File

@ -0,0 +1,53 @@
import requests
from User import User
class Gift:
ID:int = 0
count:int = 0
roomID:int = 0
giftList:dict = {10001: {"Name": "西瓜", "Price": 0}}
amount:int = 0
user:User = None
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
self.user = User(json)
if "common" in json and json["common"] is not None:
if Gift.roomID != int(json["common"]["room_id"]):
Gift.roomID = int(json["common"]["room_id"])
self.update()
if "extra" in json and json["extra"] is not None:
if "present_info" in json["extra"] and json["extra"]['present_info'] is not None:
self.ID = int(json["extra"]['present_info']['id'])
self.count = json["extra"]['present_info']['repeat_count']
elif "present_end_info" in json["extra"] and json["extra"]['present_end_info'] is not None:
self.ID = int(json["extra"]['present_end_info']['id'])
self.count = json["extra"]['present_end_info']['count']
if self.ID in self.giftList:
self.amount = self.giftList[self.ID]["Price"] * self.count
else:
self.update()
def update(self):
p = requests.get("https://i.snssdk.com/videolive/gift/get_gift_list?room_id={roomID}".format(roomID = self.roomID))
d = p.json()
if "gift_info" not in d:
print("错误:礼物更新失败")
else:
for i in d["gift_info"]:
_id = int(i["id"])
Gift.giftList[_id] = {"Name": i["name"], "Price": i["diamond_count"]}
def __str__(self):
if self.ID in self.giftList:
giftN = self.giftList[self.ID]["Name"]
else:
giftN = "未知礼物[{}]".format(self.ID)
return "{user} 送出的 {count}{name}".format(user= self.user, count= self.count, name= giftN)
def __unicode__(self):
return self.__str__()

58
Lottery.py Normal file
View File

@ -0,0 +1,58 @@
import requests
from LuckyUser import LuckyUser
class Lottery:
ID: int = 0
isActive = False
content = ""
isFinished = False
luckyUsers = []
joinedUserCount = 0
prizeName = ""
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
if "lottery_info" not in json or json["lottery_info"] is not None:
self.isActive = int(json["lottery_info"]["status"]) > 0
self.ID = json["lottery_info"]["lottery_id"]
for i in json["lottery_info"]['conditions']:
if i['type'] != 3:
continue
self.content = i["content"]
self.joinedUserCount = int(json["lottery_info"]["candidate_num"])
self.prizeName = json["lottery_info"]["prize_info"]["name"]
def checkFinished(self):
p = requests.get("https://i.snssdk.com/videolive/lottery/check_user_right?lottery_id={}"
"&version_code=730&device_platform=android".format(
self.ID
))
d = p.json()
if d["base_resp"]["status_code"] != 0:
self.isActive = False
self.isFinished = False
return
self.isActive = int(d["lottery_info"]["status"]) == 1
self.isFinished = int(d["lottery_info"]["status"]) == 2
self.joinedUserCount = int(d["lottery_info"]["candidate_num"])
if self.isFinished:
for i in d["lottery_info"]["lucky_users"]:
self.luckyUsers.append(LuckyUser(i))
def __str__(self):
if self.isFinished:
ret = "恭喜以下中奖用户:\n"
for i in self.luckyUsers:
ret += "> {} {}\n".format(i,self.prizeName)
ret += "> 参与人数:{}".format(self.joinedUserCount)
return ret
elif self.isActive:
return "正在抽奖中。。。\n" \
"> 参与人数:{}".format(self.joinedUserCount)
else:
return "抽奖已失效"

19
LuckyUser.py Normal file
View File

@ -0,0 +1,19 @@
from User import User
class LuckyUser:
user = None
count = 0
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
self.user = User()
self.user.ID = json['user_id']
self.user.name = json['user_name']
self.count = int(json["grant_count"])
def __str__(self):
return "用户 {} 获得了 {}".format(self.user,self.count)

36
MemberMsg.py Normal file
View File

@ -0,0 +1,36 @@
from User import User
class MemberMsg:
type:int = 0
content:str = ""
user:User = None
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
self.user = User(json)
if "extra" in json:
if "action" in json["extra"]:
self.type = json["extra"]['action']
elif "content" in json["extra"]:
self.content = json["extra"]['content']
def __str__(self):
if self.type == 3:
return "{} 被禁言了".format(self.user)
elif self.type == 4:
return "{} 被取消禁言了".format(self.user)
elif self.type == 5:
return "{} 被任命为房管".format(self.user)
elif self.type == 1:
return "{} 进入了房间".format(self.user)
else:
if self.content == "":
return "未知消息{} 关于用户 {}".format(self.type, self.user)
return self.content.format(self.user)
def __unicode__(self):
return self.__str__()

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# XiguaLiveDanmakuHelper
### 西瓜直播弹幕助手--控制台版
界面版:[q792602257/XiguaDanmakuHelperGUI](https://github.com/q792602257/XiguaDanmakuHelperGUI "C# ver")
### 计划更新:
+ Digg消息点亮了喜欢实例化
+ √ ~~使用android app协议~~
已使用
除从用户ID获取roomID外其他均改为Android Api
+ √ ~~闲的无聊的时候看一看有没有好用的GUI轮子可以用用~~
已完成
已基于BiliLive_dm制作出初代西瓜直播弹幕姬api未跟进

54
User.py Normal file
View File

@ -0,0 +1,54 @@
class User:
ID: int = 0
name: str = ""
brand: str = ""
level: int = 0
type: int = 0
block: bool = False
mute: bool = False
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
if "extra" in json:
if "user" in json["extra"] and json["extra"]["user"] is not None:
self.ID = json["extra"]['user']['user_id']
self.name = json["extra"]['user']['name']
if "im_discipulus_info" in json["extra"] and json["extra"]["im_discipulus_info"] is not None:
self.level = json["extra"]["im_discipulus_info"]["level"]
self.brand = json["extra"]["im_discipulus_info"]["discipulus_group_title"]
if "user_room_auth_status" in json["extra"] and json["extra"]["user_room_auth_status"] is not None:
self.type = json["extra"]["user_room_auth_status"]["user_type"]
self.block = json["extra"]["user_room_auth_status"]["is_block"]
self.mute = json["extra"]["user_room_auth_status"]["is_silence"]
elif "room" in json and json["room"] is not None:
if "user_info" in json["room"] and json["room"]["user_info"] is not None:
self.ID = json["room"]['user_info']['user_id']
self.name = json["room"]['user_info']['name']
elif "anchor" in json and json["anchor"] is not None:
if "user_info" in json["anchor"] and json["anchor"]['user_info'] is not None:
self.ID = json["anchor"]['user_info']['user_id']
self.name = json["anchor"]['user_info']['name']
if self.type is None:
self.type = 0
if isinstance(self.level, str):
self.level = int(self.level)
def __str__(self):
if self.level == 0:
if self.type == 1:
return "[房管]{}".format(self.name)
elif self.type == 3:
return "[主播]{}".format(self.name)
else:
return "{}".format(self.name)
else:
if self.type != 0:
return "[{}{}]{}".format(self.brand, self.level, self.name)
return "<{}{}>{}".format(self.brand,self.level,self.name)
def __unicode__(self):
return self.__str__()

203
WinMain.py Normal file
View File

@ -0,0 +1,203 @@
import os
import sys
import time
from Gift import Gift
from Lottery import Lottery
from MemberMsg import MemberMsg
from User import User
from Chat import Chat
from api import XiGuaLiveApi as Api
import msvcrt
import ctypes
SHOW_ALL = False
def readInput(caption, default, timeout: int = 5):
start_time = time.time()
print('{}({})\r\n>'.format(caption, default), end="")
input = ''
while True:
if msvcrt.kbhit():
chr = msvcrt.getche()
if ord(chr) == 13: # enter_key
break
elif ord(chr) == 27:
break
elif ord(chr) == 8:
if input != "":
input = input[:-1]
msvcrt.putch(b" ")
msvcrt.putch(b"\b")
if len(input) == 0:
start_time = time.time()
elif 32 > ord(chr) or 255 > ord(chr) > 126: # space_char
continue
else:
input += chr.decode("utf8")
if len(input) == 0 and (time.time() - start_time) > timeout:
break
if len(input) > 0:
print()
return input
else:
print("使用默认值")
return default
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
# 字体颜色定义 ,关键在于颜色编码由2位十六进制组成分别取0~f前一位指的是背景色后一位指的是字体色
# 由于该函数的限制应该是只有这16种可以前景色与背景色组合。也可以几种颜色通过或运算组合组合后还是在这16种颜色中
# Windows CMD命令行 字体颜色定义 text colors
FOREGROUND_BLACK = 0x00 # black.
FOREGROUND_DARKBLUE = 0x01 # dark blue.
FOREGROUND_DARKGREEN = 0x02 # dark green.
FOREGROUND_DARKSKYBLUE = 0x03 # dark skyblue.
FOREGROUND_DARKRED = 0x04 # dark red.
FOREGROUND_DARKPINK = 0x05 # dark pink.
FOREGROUND_DARKYELLOW = 0x06 # dark yellow.
FOREGROUND_DARKWHITE = 0x07 # dark white.
FOREGROUND_DARKGRAY = 0x08 # dark gray.
FOREGROUND_BLUE = 0x09 # blue.
FOREGROUND_GREEN = 0x0a # green.
FOREGROUND_SKYBLUE = 0x0b # skyblue.
FOREGROUND_RED = 0x0c # red.
FOREGROUND_PINK = 0x0d # pink.
FOREGROUND_YELLOW = 0x0e # yellow.
FOREGROUND_WHITE = 0x0f # white.
# Windows CMD命令行 背景颜色定义 background colors
BACKGROUND_BLUE = 0x10 # dark blue.
BACKGROUND_GREEN = 0x20 # dark green.
BACKGROUND_DARKSKYBLUE = 0x30 # dark skyblue.
BACKGROUND_DARKRED = 0x40 # dark red.
BACKGROUND_DARKPINK = 0x50 # dark pink.
BACKGROUND_DARKYELLOW = 0x60 # dark yellow.
BACKGROUND_DARKWHITE = 0x70 # dark white.
BACKGROUND_DARKGRAY = 0x80 # dark gray.
BACKGROUND_BLUE = 0x90 # blue.
BACKGROUND_GREEN = 0xa0 # green.
BACKGROUND_SKYBLUE = 0xb0 # skyblue.
BACKGROUND_RED = 0xc0 # red.
BACKGROUND_PINK = 0xd0 # pink.
BACKGROUND_YELLOW = 0xe0 # yellow.
BACKGROUND_WHITE = 0xf0 # white.
def set_cmd_text_color(color, handle=std_out_handle):
Bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)
return Bool
def resetColor():
set_cmd_text_color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
class WinMain(Api):
_tmp = 0
def getTitle(self):
self._tmp += 1
if self._tmp > 10:
self._tmp = 0
if self._tmp < 5 :
return "{} 的直播间 --弹幕助手 by JerryYan".format(self.roomLiver)
else:
if self.roomPopularity == 0:
self._tmp = 0
return self.getTitle()
else:
return "人气:{} --弹幕助手 by JerryYan".format(self.roomPopularity)
def onMessage(self, msg: str):
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("消息 : ", msg)
resetColor()
def onJoin(self, user: User):
set_cmd_text_color(BACKGROUND_WHITE | FOREGROUND_BLACK)
print("欢迎", user, "加入了粉丝团")
resetColor()
def onSubscribe(self, user: User):
if SHOW_ALL:
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("用户", user, "关注了主播")
resetColor()
def onEnter(self, msg:MemberMsg):
if SHOW_ALL:
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("提示 :", msg)
resetColor()
def onChat(self, chat: Chat):
if SHOW_ALL:
resetColor()
if not chat.isFiltered:
print(chat)
def onLottery(self, i:Lottery):
set_cmd_text_color(FOREGROUND_WHITE | BACKGROUND_DARKGRAY)
print(i)
resetColor()
def onPresent(self, gift: Gift):
if SHOW_ALL:
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("连击 :", gift)
resetColor()
def onPresentEnd(self, gift: Gift):
set_cmd_text_color(BACKGROUND_WHITE | FOREGROUND_BLACK)
print("感谢", gift)
resetColor()
def onLike(self, user: User):
if SHOW_ALL:
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("用户", user, "点了喜欢")
resetColor()
def onLeave(self, json: any):
return
def warning(*args):
print(*args)
if __name__ == "__main__":
name = "永恒de草薙"
resetColor()
print("西瓜直播弹幕助手 by JerryYan")
if len(sys.argv) > 1:
name = sys.argv[1]
else:
name = readInput("请输入主播用户名(请用拼音字母),默认为", name, 3)
api = WinMain(name)
print("进入", api.roomLiver, "的直播间")
if not api.isValidRoom:
input("房间不存在")
sys.exit()
os.system("title {}".format(api.getTitle()))
print("=" * 30)
while True:
if api.isLive:
os.system("title {}".format(api.getTitle()))
try:
api.getDanmaku()
except Exception as e:
print(e.__str__())
time.sleep(1)
else:
set_cmd_text_color(FOREGROUND_RED)
print("主播未开播等待1分钟后重试")
resetColor()
time.sleep(60)
api.updRoomInfo()

438
api.py
View File

@ -1,334 +1,224 @@
import sys import sys
from MemberMsg import MemberMsg
from User import User
from Gift import Gift
from Chat import Chat
from Lottery import Lottery
import requests import requests
import time import time
import ctypes
import os
class UserStruct:
ID = 0
name = ""
brand= ""
level= 0
type = 0
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
if "Msg" in json:
if "user" in json["Msg"]:
self.ID = json["Msg"]['user']['user_id']
self.name = json["Msg"]['user']['name']
self.type = json["Msg"]['user']['user_type']
if "discipulus_info" in json["Msg"]:
self.level = json["Msg"]["discipulus_info"]["level"]
self.brand = json["Msg"]["discipulus_info"]["discipulus_group_title"]
if self.type is None:
self.type = 0
def __str__(self):
if self.level == 0:
if self.type != 0:
return "[]{}".format(self.name)
return "{}".format(self.name)
else:
if self.type != 0:
return "[{}{}]{}".format(self.brand, self.level, self.name)
return "<{}{}>{}".format(self.brand,self.level,self.name)
def __unicode__(self):
return self.__str__()
class GiftStruct:
ID = 0
count = 0
def __init__(self, json=None):
if json:
self.parse(json)
def parse(self, json):
if "Msg" in json:
if "present_end_info" in json["Msg"]:
self.ID = json["Msg"]['present_end_info']['id']
self.count = json["Msg"]['present_end_info']['count']
elif "present_info" in json["Msg"]:
self.ID = json["Msg"]['present_info']['id']
self.count = json["Msg"]['present_info']['repeat_count']
def readInput(caption, default, timeout=5):
start_time = time.time()
print('{}({})\r\n>'.format(caption,default), end="")
input = ''
while True:
if msvcrt.kbhit():
chr = msvcrt.getche()
if ord(chr) == 13: # enter_key
break
elif ord(chr) == 27:
break
elif ord(chr) >= 32: # space_char
input += str(chr)
if len(input) == 0 and (time.time() - start_time) > timeout:
break
if len(input) > 0:
print()
return input
else:
print("使用默认值")
return default
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
# 字体颜色定义 ,关键在于颜色编码由2位十六进制组成分别取0~f前一位指的是背景色后一位指的是字体色
#由于该函数的限制应该是只有这16种可以前景色与背景色组合。也可以几种颜色通过或运算组合组合后还是在这16种颜色中
# Windows CMD命令行 字体颜色定义 text colors
FOREGROUND_BLACK = 0x00 # black.
FOREGROUND_DARKBLUE = 0x01 # dark blue.
FOREGROUND_DARKGREEN = 0x02 # dark green.
FOREGROUND_DARKSKYBLUE = 0x03 # dark skyblue.
FOREGROUND_DARKRED = 0x04 # dark red.
FOREGROUND_DARKPINK = 0x05 # dark pink.
FOREGROUND_DARKYELLOW = 0x06 # dark yellow.
FOREGROUND_DARKWHITE = 0x07 # dark white.
FOREGROUND_DARKGRAY = 0x08 # dark gray.
FOREGROUND_BLUE = 0x09 # blue.
FOREGROUND_GREEN = 0x0a # green.
FOREGROUND_SKYBLUE = 0x0b # skyblue.
FOREGROUND_RED = 0x0c # red.
FOREGROUND_PINK = 0x0d # pink.
FOREGROUND_YELLOW = 0x0e # yellow.
FOREGROUND_WHITE = 0x0f # white.
# Windows CMD命令行 背景颜色定义 background colors
BACKGROUND_BLUE = 0x10 # dark blue.
BACKGROUND_GREEN = 0x20 # dark green.
BACKGROUND_DARKSKYBLUE = 0x30 # dark skyblue.
BACKGROUND_DARKRED = 0x40 # dark red.
BACKGROUND_DARKPINK = 0x50 # dark pink.
BACKGROUND_DARKYELLOW = 0x60 # dark yellow.
BACKGROUND_DARKWHITE = 0x70 # dark white.
BACKGROUND_DARKGRAY = 0x80 # dark gray.
BACKGROUND_BLUE = 0x90 # blue.
BACKGROUND_GREEN = 0xa0 # green.
BACKGROUND_SKYBLUE = 0xb0 # skyblue.
BACKGROUND_RED = 0xc0 # red.
BACKGROUND_PINK = 0xd0 # pink.
BACKGROUND_YELLOW = 0xe0 # yellow.
BACKGROUND_WHITE = 0xf0 # white.
s = requests.Session() s = requests.Session()
def set_cmd_text_color(color, handle=std_out_handle): DEBUG: bool = False
Bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)
return Bool
def resetColor():
set_cmd_text_color(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
class XiGuaLiveApi: class XiGuaLiveApi:
isLive = False isLive: bool = False
roomInfo = {} isValidRoom: bool = False
roomID = 0 _rawRoomInfo = {}
cursor = "" name: str = ""
giftList = {10001:"西瓜"} roomID: int = 0
roomTitle: str = ""
roomLiver: User = None
roomPopularity: int = 0
_cursor:str = "0"
_updRoomCount:int = 0
lottery:Lottery = None
def __init__(self, room: int): def __init__(self, name: str = "永恒de草薙"):
self.room = room self.name = name
self.updRoomInfo() self.updRoomInfo()
self.updGiftList()
def notLiveError(self): def _updateRoomInfo(self, json):
print("主播未开播") if "extra" in json:
if "member_count" in json["extra"] and json["extra"]["member_count"] > 0:
self.roomPopularity = json["extra"]["member_count"]
elif "data" in json:
if "popularity" in json["data"]:
self.roomPopularity = json["data"]["popularity"]
def apiChangedError(self, msg:str): def apiChangedError(self, msg: str, *args):
print(msg) print(msg)
print(*args)
def onPresent(self, user:UserStruct, gift:GiftStruct): def onPresent(self, gift: Gift):
if gift.ID not in self.giftList: print("礼物连击 :", gift)
giftN = "未知礼物:{}".format(gift.ID)
else:
giftN = self.giftList[gift.ID]
return
print("礼物连击:", user, giftN, "x", gift.count)
def onPresentEnd(self, user:UserStruct, gift:GiftStruct): def onPresentEnd(self, gift: Gift):
if gift.ID not in self.giftList: print("感谢", gift)
self.updGiftList()
giftN = "未知礼物:{}".format(gift.ID)
else:
giftN = self.giftList[gift.ID]
set_cmd_text_color(BACKGROUND_WHITE | FOREGROUND_BLACK)
print("感谢", user, "送出的", giftN, "x", gift.count)
resetColor()
def onAd(self, i): def onAd(self, i):
# print(i) # print(i)
pass pass
def onChat(self, user:UserStruct, content:str): def onChat(self, chat: Chat):
print(user, "", content) if not chat.isFiltered:
# pass print(chat)
def onEnter(self, user:UserStruct, content:str == ""): def onEnter(self, msg: MemberMsg):
if content == "": print("提示 :", msg)
if user.name == "三国空白" or user.name == "四维v":
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("消息:", user, "进入直播间")
resetColor()
else:
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("消息:", content.format(user))
resetColor()
def onSubscribe(self, user:UserStruct): def onSubscribe(self, user: User):
if user.level >= 6 and user.brand == "永恒": print("消息 :", user, "关注了主播")
set_cmd_text_color(FOREGROUND_DARKGRAY)
print("消息:", user, "关注了主播")
resetColor()
def onJoin(self, user:UserStruct): def onJoin(self, user: User):
set_cmd_text_color(BACKGROUND_WHITE | FOREGROUND_BLACK) print("欢迎", user, "加入了粉丝团")
print("感谢", user, "加入了粉丝团")
resetColor()
def onMessage(self, msg: str): def onMessage(self, msg: str):
set_cmd_text_color(FOREGROUND_DARKGRAY) print("消息 :", msg)
print("消息:", msg)
resetColor()
def onLike(self, user:UserStruct): def onLike(self, user: User):
return print("用户", user, "点了喜欢")
# set_cmd_text_color(FOREGROUND_DARKGRAY)
# print("用户", user, "点了喜欢")
# resetColor()
def onLeave(self, json: any): def onLeave(self, json: any):
print("消息", "主播离开一小会") print("消息 :", "主播离开")
self.debug(json)
return
def updGiftList(self): def onLottery(self, i:Lottery):
p = s.get("https://live.ixigua.com/api/gifts/{roomID}".format(roomID=self.roomID)) print("中奖消息 :", i)
d = p.json()
self.debug(d)
if "data" not in d:
self.warning("Warning: Api Has Changed")
return
for i in d["data"]:
self.debug(i["ID"], i["Name"])
self.giftList[i["ID"]] = i["Name"]
def warning(self, *args):
print(args)
def debug(self, *args):
# print(args)
pass
def enterRoom(self):
p = s.post("https://live.ixigua.com/api/room/enter/{roomID}".format(roomID=self.roomID))
self.debug(p.json())
def updRoomInfo(self): def updRoomInfo(self):
p = s.get("https://live.ixigua.com/api/room/{room}".format(room=self.room)) if self.isLive:
p = s.post("https://i.snssdk.com/videolive/room/enter?version_code=730"
"&device_platform=android",
data="room_id={roomID}&version_code=730"
"&device_platform=android".format(roomID=self.roomID),
headers={"Content-Type":"application/x-www-form-urlencoded"})
d = p.json() d = p.json()
self.debug(d) self.isValidRoom = d["base_resp"]["status_code"] == 0
if "data" not in d: if d["base_resp"]["status_code"] != 0:
self.apiChangedError("数据结构改变,请与我联系") return False
self.debug(d) if "room" not in d and d["room"] is None:
self.apiChangedError("Api发生改变请及时联系我")
return False
self._rawRoomInfo = d["room"]
self.isLive = d["room"]["status"] == 2
self.roomLiver = User(d)
self.roomTitle = d["room"]["title"]
self.roomPopularity = d["room"]["user_count"]
l = Lottery(d)
if l.isActive:
self.lottery = l
return True
else:
p = s.get("https://security.snssdk.com/video/app/search/live/?version_code=730&device_platform=android"
"&format=json&keyword={}".format(self.name))
d = p.json()
if "data" in d:
for i in d["data"]:
if i["block_type"] != 0:
continue
if len(i["cells"]) == 0:
return return
self.roomInfo = d["data"] self.isLive = i["cells"][0]["anchor"]["user_info"]["is_living"]
print("进入", self.roomInfo["anchorInfo"]["name"], "的直播间") self.roomID = int(i["cells"][0]["anchor"]["room_id"])
if "Id" in d["data"]: self.roomLiver = User(i["cells"][0])
self.roomID = d["data"]["Id"] if self.isLive:
return self.updRoomInfo()
else: else:
self.warning("无法获取RoomID请与我联系") return False
if "FinishTime" in d["data"]:
self.isLive = False @staticmethod
self.notLiveError() def findRoomByUserId(userId:int):
else: p = s.get("https://live.ixigua.com/api/room?anchorId={room}".format(room=userId))
self.isLive = True if DEBUG:
print(p.text)
d = p.json()
if "data" not in d or "title" not in d["data"] or "id" not in d["data"]:
XiGuaLiveApi.apiChangedError("无法获取RoomID请与我联系")
return XiGuaLiveApi()
return XiGuaLiveApi(d["data"]["id"])
@staticmethod
def searchLive(keyword):
ret = []
p = s.get("https://security.snssdk.com/video/app/search/live/?version_code=730&device_platform=android"
"&format=json&keyword={}".format(keyword))
d = p.json()
if "data" in d:
for i in d["data"]:
if i["block_type"] != 0:
continue
for _i in i["cells"]:
ret.append(_i["room"])
return ret
def getDanmaku(self): def getDanmaku(self):
p = s.get("https://live.ixigua.com/api/msg/list/{roomID}?AnchorID={room}&Cursor={cursor}".format( if not self.isValidRoom:
return
p = s.get("https://i.snssdk.com/videolive/im/get_msg?cursor={cursor}&room_id={roomID}"
"&version_code=730&device_platform=android".format(
roomID=self.roomID, roomID=self.roomID,
room=self.room, cursor=self._cursor
cursor=self.cursor
)) ))
d = p.json() d = p.json()
self.debug(d) if "data" not in d or "extra" not in d or "cursor" not in d["extra"]:
if "data" not in d: if DEBUG:
print(d)
self.apiChangedError("数据结构改变,请与我联系") self.apiChangedError("数据结构改变,请与我联系")
self.debug(d)
return
if "Extra" not in d["data"]:
self.apiChangedError("数据结构改变,请与我联系")
self.debug(d["data"])
return
if "Cursor" not in d["data"]["Extra"]:
self.apiChangedError("数据结构改变,请与我联系")
self.debug(d["data"])
return return
else: else:
self.cursor = d["data"]["Extra"]["Cursor"] self._cursor = d["extra"]["cursor"]
if "LiveMsgs" not in d["data"]: if DEBUG:
return print("Cursor", self._cursor)
for i in d['data']['LiveMsgs']: for i in d['data']:
if i['Method'] == "VideoLivePresentMessage": if DEBUG:
self.onPresent(UserStruct(i), GiftStruct(i)) print(i)
elif i['Method'] == "VideoLivePresentEndTipMessage": if "common" not in i and "method" not in i["common"]:
self.onPresentEnd(UserStruct(i), GiftStruct(i)) continue
elif i['Method'] == "VideoLiveRoomAdMessage": if i["common"]['method'] == "VideoLivePresentMessage":
self.onPresent(Gift(i))
elif i["common"]['method'] == "VideoLivePresentEndTipMessage":
self.onPresentEnd(Gift(i))
elif i["common"]['method'] == "VideoLiveRoomAdMessage":
self.onAd(i) self.onAd(i)
elif i['Method'] == "VideoLiveChatMessage": elif i["common"]['method'] == "VideoLiveChatMessage":
self.onChat(UserStruct(i), i["Msg"]['content']) self.onChat(Chat(i, self.lottery))
elif i['Method'] == "VideoLiveMemberMessage": elif i["common"]['method'] == "VideoLiveMemberMessage":
self.onEnter(UserStruct(i), i["Msg"]["content"]) self._updateRoomInfo(i)
elif i['Method'] == "VideoLiveSocialMessage": self.onEnter(MemberMsg(i))
self.onSubscribe(UserStruct(i)) elif i["common"]['method'] == "VideoLiveSocialMessage":
elif i['Method'] == "VideoLiveJoinDiscipulusMessage": self.onSubscribe(User(i))
self.onJoin(UserStruct(i)) elif i["common"]['method'] == "VideoLiveJoinDiscipulusMessage":
elif i['Method'] == "VideoLiveControlMessage": self.onJoin(User(i))
elif i["common"]['method'] == "VideoLiveControlMessage":
print("消息:", "主播离开一小会") print("消息:", "主播离开一小会")
elif i['Method'] == "VideoLiveDiggMessage": elif i["common"]['method'] == "VideoLiveDiggMessage":
self.onLike(UserStruct(i)) self.onLike(User(i))
else: else:
self.debug(i) pass
self._updRoomCount += 1
if self._updRoomCount > 120 or len(d['data']) == 0:
if self.lottery is not None:
self.lottery.checkFinished()
if self.lottery.isFinished:
self.onLottery(self.lottery)
self.lottery = None
self.updRoomInfo()
self._updRoomCount = 0
return
if __name__ == "__main__": if __name__ == "__main__":
room = 97621754276 #永恒 name = "永恒de草薙"
# room = 75366565294 if len(sys.argv) > 2:
# room = 83940182312 #Dae if sys.argv[-1] == "d":
if len(sys.argv)>1: DEBUG = True
room = int(sys.argv[1]) name = sys.argv[1]
resetColor()
print("西瓜直播弹幕助手 by JerryYan") print("西瓜直播弹幕助手 by JerryYan")
print("正在进入房间", room) api = XiGuaLiveApi(name)
api = XiGuaLiveApi(room) if not api.isValidRoom:
os.system("title {} {}".format(api.roomInfo["anchorInfo"]["name"],"的直播间 --西瓜直播弹幕助手 by JerryYan")) print(api.roomID)
api.enterRoom() input("房间不存在")
sys.exit()
print("进入", api.roomLiver, "的直播间")
print("=" * 30) print("=" * 30)
while True: while True:
if api.isLive: if api.isLive:
try: try:
api.getDanmaku() api.getDanmaku()
except Exception as e: except Exception as e:
api.warning(e) print(e)
time.sleep(1) time.sleep(1)
else: else:
print("主播未开播等待1分钟后重试") print("主播未开播等待1分钟后重试")

492
bilibili.py Normal file
View File

@ -0,0 +1,492 @@
# coding=utf-8
import os
import re
from datetime import datetime
import rsa
import math
import base64
import hashlib
import requests
from urllib import parse
class VideoPart:
def __init__(self, path, title='', desc=''):
self.path = path
self.title = title
self.desc = desc
class Bilibili:
def __init__(self, cookie=None):
self.files = []
self.videos = []
self.session = requests.session()
if cookie:
self.session.headers["cookie"] = cookie
self.csrf = re.search('bili_jct=(.*?);', cookie).group(1)
self.mid = re.search('DedeUserID=(.*?);', cookie).group(1)
self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid)
# session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
# session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
def login(self, user, pwd):
"""
:param user: username
:type user: str
:param pwd: password
:type pwd: str
:return: if success return True
else return msg json
"""
APPKEY = '1d8b6e7d45233436'
ACTIONKEY = 'appkey'
BUILD = 520001
DEVICE = 'android'
MOBI_APP = 'android'
PLATFORM = 'android'
APPSECRET = '560c52ccd288fed045859ed18bffd973'
def md5(s):
h = hashlib.md5()
h.update(s.encode('utf-8'))
return h.hexdigest()
def sign(s):
"""
:return: return sign
"""
return md5(s + APPSECRET)
def signed_body(body):
"""
:return: body which be added sign
"""
if isinstance(body, str):
return body + '&sign=' + sign(body)
elif isinstance(body, dict):
ls = []
for k, v in body.items():
ls.append(k + '=' + v)
body['sign'] = sign('&'.join(ls))
return body
def getkey():
"""
:return: hash, key
"""
r = self.session.post(
'https://passport.bilibili.com/api/oauth2/getKey',
signed_body({'appkey': APPKEY}),
)
# {"ts":1544152439,"code":0,"data":{"hash":"99c7573759582e0b","key":"-----BEGIN PUBLIC----- -----END PUBLIC KEY-----\n"}}
json = r.json()
data = json['data']
return data['hash'], data['key']
def cnn_captcha(img):
url = "http://47.95.255.188:5000/code"
data = {"image": img}
r = requests.post(url, data=data)
return r.text
self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
h, k = getkey()
pwd = base64.b64encode(
rsa.encrypt(
(h + pwd).encode('utf-8'),
rsa.PublicKey.load_pkcs1_openssl_pem(k.encode())
)
)
user = parse.quote_plus(user)
pwd = parse.quote_plus(pwd)
r = self.session.post(
'https://passport.bilibili.com/api/v2/oauth2/login',
signed_body('appkey={appkey}&password={password}&username={username}'
.format(appkey=APPKEY, username=user, password=pwd))
)
try:
json = r.json()
except:
return r.text
if json['code'] == -105:
# need captcha
self.session.headers['cookie'] = 'sid=xxxxxxxx'
r = self.session.get('https://passport.bilibili.com/captcha')
captcha = cnn_captcha(base64.b64encode(r.content))
r = self.session.post(
'https://passport.bilibili.com/api/v2/oauth2/login',
signed_body('actionKey={actionKey}&appkey={appkey}&build={build}&captcha={captcha}&device={device}'
'&mobi_app={mobi_app}&password={password}&platform={platform}&username={username}'
.format(actionKey=ACTIONKEY,
appkey=APPKEY,
build=BUILD,
captcha=captcha,
device=DEVICE,
mobi_app=MOBI_APP,
password=pwd,
platform=PLATFORM,
username=user)),
)
json = r.json()
if json['code'] is not 0:
return r.text
ls = []
for item in json['data']['cookie_info']['cookies']:
ls.append(item['name'] + '=' + item['value'])
cookie = '; '.join(ls)
self.session.headers["cookie"] = cookie
self.csrf = re.search('bili_jct=(.*?);', cookie).group(1)
self.mid = re.search('DedeUserID=(.*?);', cookie).group(1)
self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid)
return True
def upload(self,
parts,
title,
tid,
tag,
desc,
source='',
cover='',
no_reprint=1,
):
"""
:param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
:type parts: VideoPart or list<VideoPart>
:param title: video's title
:type title: str
:param tid: video type, see: https://member.bilibili.com/x/web/archive/pre
or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8
:type tid: int
:param tag: video's tag
:type tag: list<str>
:param desc: video's description
:type desc: str
:param source: (optional) 转载地址
:type source: str
:param cover: (optional) cover's URL, use method *cover_up* to get
:type cover: str
:param no_reprint: (optional) 0=可以转载, 1=禁止转载(default)
:type no_reprint: int
"""
self.preUpload(parts)
self.finishUpload(title, tid, tag, desc, source, cover, no_reprint)
def preUpload(self, parts):
"""
:param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
:type parts: VideoPart or list<VideoPart>
"""
self.session.headers['Content-Type'] = 'application/json; charset=utf-8'
if not isinstance(parts, list):
parts = [parts]
for part in parts:
filepath = part.path
filename = os.path.basename(filepath)
filesize = os.path.getsize(filepath)
self.files.append(part)
r = self.session.get('https://member.bilibili.com/preupload?'
'os=upos&upcdn=ws&name={name}&size={size}&r=upos&profile=ugcupos%2Fyb&ssl=0'
.format(name=filename, size=filesize))
"""return example
{
"upos_uri": "upos://ugc/i181012ws18x52mti3gg0h33chn3tyhp.mp4",
"biz_id": 58993125,
"endpoint": "//upos-hz-upcdnws.acgvideo.com",
"endpoints": [
"//upos-hz-upcdnws.acgvideo.com",
"//upos-hz-upcdntx.acgvideo.com"
],
"chunk_retry_delay": 3,
"chunk_retry": 200,
"chunk_size": 4194304,
"threads": 2,
"timeout": 900,
"auth": "os=upos&cdn=upcdnws&uid=&net_state=4&device=&build=&os_version=&ak=×tamp=&sign=",
"OK": 1
}
"""
json = r.json()
upos_uri = json['upos_uri']
endpoint = json['endpoint']
auth = json['auth']
biz_id = json['biz_id']
chunk_size = json['chunk_size']
self.session.headers['X-Upos-Auth'] = auth # add auth header
r = self.session.post(
'https:{}/{}?uploads&output=json'.format(endpoint, upos_uri.replace('upos://', '')))
# {"upload_id":"72eb747b9650b8c7995fdb0efbdc2bb6","key":"\/i181012ws2wg1tb7tjzswk2voxrwlk1u.mp4","OK":1,"bucket":"ugc"}
json = r.json()
upload_id = json['upload_id']
with open(filepath, 'rb') as f:
chunks_num = math.ceil(filesize / chunk_size)
chunks_index = -1
while True:
chunks_data = f.read(chunk_size)
if not chunks_data:
break
chunks_index += 1 # start with 0
r = self.session.put('https:{endpoint}/{upos_uri}?'
'partNumber={part_number}&uploadId={upload_id}&chunk={chunk}&chunks={chunks}&size={size}&start={start}&end={end}&total={total}'
.format(endpoint=endpoint,
upos_uri=upos_uri.replace('upos://', ''),
part_number=chunks_index + 1, # starts with 1
upload_id=upload_id,
chunk=chunks_index,
chunks=chunks_num,
size=len(chunks_data),
start=chunks_index * chunk_size,
end=chunks_index * chunk_size + len(chunks_data),
total=filesize,
),
chunks_data,
)
print('{} : UPLOAD {}/{}'.format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), chunks_index,
chunks_num), r.text)
# NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769
self.session.post('https:{endpoint}/{upos_uri}?'
'output=json&name={name}&profile=ugcupos%2Fyb&uploadId={upload_id}&biz_id={biz_id}'
.format(endpoint=endpoint,
upos_uri=upos_uri.replace('upos://', ''),
name=filename,
upload_id=upload_id,
biz_id=biz_id,
),
{"parts": [{"partNumber": i, "eTag": "etag"} for i in range(1, chunks_num + 1)]},
)
self.videos.append({'filename': upos_uri.replace('upos://ugc/', '').split('.')[0],
'title': part.title,
'desc': part.desc})
def finishUpload(self,
title,
tid,
tag,
desc,
source='',
cover='',
no_reprint=1,
):
"""
:param title: video's title
:type title: str
:param tid: video type, see: https://member.bilibili.com/x/web/archive/pre
or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8
:type tid: int
:param tag: video's tag
:type tag: list<str>
:param desc: video's description
:type desc: str
:param source: (optional) 转载地址
:type source: str
:param cover: (optional) cover's URL, use method *cover_up* to get
:type cover: str
:param no_reprint: (optional) 0=可以转载, 1=禁止转载(default)
:type no_reprint: int
"""
self.session.headers['Content-Type'] = 'application/json; charset=utf-8'
copyright = 2 if source else 1
r = self.session.post('https://member.bilibili.com/x/vu/web/add?csrf=' + self.csrf,
json={
"copyright": copyright,
"source": source,
"title": title,
"tid": tid,
"tag": ','.join(tag),
"no_reprint": no_reprint,
"desc": desc,
"cover": cover,
"mission_id": 0,
"order_id": 0,
"videos": self.videos}
)
print(r.text)
for _p in self.files:
shutil.move(_p.path, "/tmp/oss/")
def appendUpload(self,
aid,
parts,
title="",
tid="",
tag="",
desc="",
source='',
cover='',
no_reprint=1,
):
"""
:param aid: just aid
:type aid: int
:param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
:type parts: VideoPart or list<VideoPart>
:param title: video's title
:type title: str
:param tid: video type, see: https://member.bilibili.com/x/web/archive/pre
or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8
:type tid: int
:param tag: video's tag
:type tag: list<str>
:param desc: video's description
:type desc: str
:param source: (optional) 转载地址
:type source: str
:param cover: (optional) cover's URL, use method *cover_up* to get
:type cover: str
:param no_reprint: (optional) 0=可以转载, 1=禁止转载(default)
:type no_reprint: int
"""
self.session.headers['Content-Type'] = 'application/json; charset=utf-8'
p = self.session.get("https://member.bilibili.com/x/web/archive/view?aid={}&history=".format(aid))
j = p.json()
if len(self.videos) == 0:
for i in j['data']['videos']:
self.videos.append({'filename': i['filename'],
'title': i["title"],
'desc': i["desc"]})
if (title == ""): title = j["data"]["archive"]['title']
if (tag == ""): tag = j["data"]["archive"]['tag']
if (no_reprint == ""): no_reprint = j["data"]["archive"]['no_reprint']
if (desc == ""): desc = j["data"]["archive"]['desc']
if (source == ""): source = j["data"]["archive"]['source']
if (tid == ""): tid = j["data"]["archive"]['tid']
self.preUpload(parts)
self.editUpload(aid, title, tid, tag, desc, source, cover, no_reprint)
def editUpload(self,
aid,
title,
tid,
tag,
desc,
source='',
cover='',
no_reprint=1,
):
"""
:param aid: just aid
:type aid: int
:param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
:type parts: VideoPart or list<VideoPart>
:param title: video's title
:type title: str
:param tid: video type, see: https://member.bilibili.com/x/web/archive/pre
or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8
:type tid: int
:param tag: video's tag
:type tag: list<str>
:param desc: video's description
:type desc: str
:param source: (optional) 转载地址
:type source: str
:param cover: (optional) cover's URL, use method *cover_up* to get
:type cover: str
:param no_reprint: (optional) 0=可以转载, 1=禁止转载(default)
:type no_reprint: int
"""
copyright = 2 if source else 1
r = self.session.post('https://member.bilibili.com/x/vu/web/edit?csrf=' + self.csrf,
json={
"aid": aid,
"copyright": copyright,
"source": source,
"title": title,
"tid": tid,
"tag": ','.join(tag),
"no_reprint": no_reprint,
"desc": desc,
"cover": cover,
"mission_id": 0,
"order_id": 0,
"videos": self.videos}
)
print(r.text)
for _p in self.files:
os.remove(_p.path)
def addChannel(self, name, intro=''):
"""
:param name: channel's name
:type name: str
:param intro: channel's introduction
:type intro: str
"""
r = self.session.post(
url='https://space.bilibili.com/ajax/channel/addChannel',
data={
'name': name,
'intro': intro,
'aids': '',
'csrf': self.csrf,
},
# name=123&intro=123&aids=&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497077610768
)
# return
# {"status":true,"data":{"cid":"15812"}}
print(r.json())
def channel_addVideo(self, cid, aids):
"""
:param cid: channel's id
:type cid: int
:param aids: videos' id
:type aids: list<int>
"""
r = self.session.post(
url='https://space.bilibili.com/ajax/channel/addVideo',
data={
'aids': '%2C'.join(aids),
'cid': cid,
'csrf': self.csrf
}
# aids=9953555%2C9872953&cid=15814&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497079332679
)
print(r.json())
def cover_up(self, img):
"""
:param img: img path or stream
:type img: str or BufferedReader
:return: img URL
"""
if isinstance(img, str):
f = open(img, 'rb')
else:
f = img
r = self.session.post(
url='https://member.bilibili.com/x/vu/web/cover/up',
data={
'cover': b'data:image/jpeg;base64,' + (base64.b64encode(f.read())),
'csrf': self.csrf,
}
)
# print(r.text)
# {"code":0,"data":{"url":"http://i0.hdslb.com/bfs/archive/67db4a6eae398c309244e74f6e85ae8d813bd7c9.jpg"},"message":"","ttl":1}
return r.json()['data']['url']

186
liveDownloader.py Normal file
View File

@ -0,0 +1,186 @@
import sys
import time
from datetime import datetime
import m3u8
import queue
import threading
from config import config
from api import XiGuaLiveApi
from bilibili import *
q = queue.Queue()
base_uri = ""
isUpload = False
uq = queue.Queue()
class downloader(XiGuaLiveApi):
files = []
playlist: str = None
def updRoomInfo(self):
super(downloader, self).updRoomInfo()
self.updPlayList()
def updPlayList(self):
if "stream_url" not in self._rawRoomInfo:
if self.playlist is None:
self.apiChangedError("无法获取直播链接")
self.playlist = False
else:
self.playlist = self._rawRoomInfo["stream_url"]["alternate_pull_url"]
self.playlist = self.playlist.replace("_uhd","").replace("_sd","").replace("_ld","")
def onLike(self, user):
pass
def onAd(self, i):
pass
def onChat(self, chat):
pass
def onEnter(self, msg):
pass
def onJoin(self, user):
pass
def onLeave(self, json):
self.updRoomInfo()
def onMessage(self, msg):
pass
def onPresent(self, gift):
pass
def onPresentEnd(self, gift):
pass
def onSubscribe(self, user):
pass
def preDownload(self):
global base_uri
if self.playlist:
try:
p = m3u8.load(self.playlist)
except:
self.updRoomInfo()
return
base_uri = p.base_uri
for i in p.files:
if i not in self.files:
self.files.append(i)
print("{} : Add Sequence {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"),
len(self.files)))
q.put(i)
self.genNewName()
def genNewName(self):
if len(self.files) > 800:
q.put(True)
self.files.clear()
def download(path=datetime.strftime(datetime.now(), "%Y%m%d_%H%M.ts")):
global isUpload
print("{} : Download Daemon Starting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
n = False
isUpload = False
i = q.get()
while True:
if isinstance(i, bool):
print("{} : Download Daemon Receive Command {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
break
print("{} : Download {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
try:
_p = requests.get("{}{}".format(base_uri, i))
except:
continue
f = open(path, "ab")
f.write(_p.content)
f.close()
n = True
i = q.get()
if n:
isUpload = True
uq.put(path)
print("{} : Download Daemon Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
def upload(date=datetime.strftime(datetime.now(), "%Y_%m_%d")):
print("{} : Upload Daemon Starting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
i = uq.get()
while True:
if isinstance(i, bool):
print("{} : Upload Daemon Receive Command {}"
.format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
if i is True:
print("自动投稿中,请稍后")
b.finishUpload(config["t_t"].format(date), 17, config["tag"], config["des"],
source=config["src"], no_reprint=0)
break
print("{} : Upload {}".format(datetime.strftime(datetime.now(), "%y%m%d %H%M"), i))
try:
b.preUpload(VideoPart(i, i))
except:
continue
i = uq.get()
print("{} : Upload Daemon Quiting".format(datetime.strftime(datetime.now(), "%y%m%d %H%M")))
b = Bilibili()
b.login(config["b_u"], config["b_p"])
if __name__ == "__main__":
name = "永恒de草薙"
# room = 75366565294
# room = 83940182312 #Dae
# room = 5947850784 #⑦
# room = 58649240617 #戏
if len(sys.argv) > 1:
name = sys.argv[1]
print("西瓜直播录播助手 by JerryYan")
api = downloader(name)
print("进入", api.roomLiver, "的直播间")
if not api.isValidRoom:
input("房间不存在")
sys.exit()
print("=" * 30)
d = datetime.strftime(datetime.now(), "%Y_%m_%d")
_preT = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.ts")
t = threading.Thread(target=download, args=(_preT,))
ut = threading.Thread(target=upload, args=(d,))
while True:
if api.isLive:
if d is None:
d = datetime.strftime(datetime.now(), "%Y_%m_%d")
if not t.is_alive():
_preT = datetime.strftime(datetime.now(), "%Y%m%d_%H%M.ts")
t = threading.Thread(target=download, args=(_preT,))
t.setDaemon(True)
t.start()
if not ut.is_alive():
ut = threading.Thread(target=upload, args=(d,))
ut.setDaemon(True)
ut.start()
try:
api.preDownload()
except:
pass
time.sleep(3)
else:
if d is not None:
q.put(False)
d = None
if isUpload:
uq.put(True)
isUpload = False
else:
del config
from config import config
# print("主播未开播等待1分钟后重试")
time.sleep(60)
api.updRoomInfo()