├── .gitignore ├── README.md ├── qito ├── __init__.py ├── __main__.py ├── config │ ├── __init__.py │ └── profile.py ├── ini │ └── __init__.py ├── main.py ├── parse │ ├── __init__.py │ ├── live │ │ ├── 56.py │ │ ├── __init__.py │ │ ├── bilibili.py │ │ ├── douyin.py │ │ ├── douyu.py │ │ ├── huajiao.py │ │ ├── huya.py │ │ └── pps.py │ ├── music │ │ ├── __init__.py │ │ └── qq.py │ ├── playlist │ │ ├── __init__.py │ │ ├── bilibili.py │ │ ├── ixigua.py │ │ ├── le.py │ │ ├── miguvideo.py │ │ ├── qq.py │ │ ├── sohu.py │ │ └── youku.py │ └── video │ │ ├── __init__.py │ │ ├── acfun.py │ │ ├── bilibili.py │ │ ├── cntv.py │ │ ├── douyin.py │ │ ├── douyu.py │ │ ├── fun.py │ │ ├── huya.py │ │ ├── iqiyi.py │ │ ├── ixigua.py │ │ ├── le.py │ │ ├── mgtv.py │ │ ├── miguvideo.py │ │ ├── pptv.py │ │ ├── qq.py │ │ ├── sohu.py │ │ ├── wasu.py │ │ ├── xiaohongshu.py │ │ └── youku.py ├── template.py ├── tool │ ├── __init__.py │ └── javascript │ │ ├── __init__.py │ │ ├── aes.js │ │ ├── crypto-js.min.js │ │ ├── douyin.sign.js │ │ └── iqiyi.cmd5x.js └── util │ ├── __init__.py │ ├── common.py │ ├── execute.py │ └── prepare.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | parse/.DS_Store 3 | .DS_Store 4 | .DS_Store 5 | parse/.DS_Store 6 | .DS_Store 7 | dist/ 8 | qito.egg-info/ 9 | *.pyc 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QITO音乐视频下载器 2 | 3 | qito: 基于python3编写的视频解析器 4 | 5 | --- 6 | 7 | ## 安装使用 8 | 9 | ``` 10 | pip3 install qito 11 | ``` 12 | ## 在线升级 13 | 14 | ``` 15 | qito upgrade 16 | ``` 17 | ## 源升级 18 | ``` 19 | pip3 install --upgrade qito 20 | ``` 21 | ### 全局配置 22 | 23 | ``` 24 | iniPath: 自定义ini文件夹路径, 默认为qito/ini 25 | MAC:qito config -ip xxx路径 26 | 27 | filePath: 自定义文件下载路径, 默认为cwd路径/download 28 | MAC:qito config -fp xxx路径 29 | 30 | ``` 31 | 32 | ### INI配置 33 | 34 | 以qq为例,配置文件名为qq.ini,存放在设定iniPath路径即可 35 | 36 | 如需请求cookie,在ini设置cookie节点即可 37 | 38 | ``` 39 | [cookie] 40 | xxx=xxxx; 41 | ``` 42 | 43 | # 使用说明: 44 | #### 默认打印JSON (Parse) 45 | ``` 46 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ 47 | {'hd': 6, 'type': 'bilibili', 'category': 'video', 'choose': False, 'playlist': False, 'iniPath': False, 'filePath': False, 'urls': ['https://www.bilibili.com/video/BV1Jx411r76d/'], 'parse': 'https://www.bilibili.com/video/BV1Jx411r76d/', 'site': 'bilibili', 'page': 1, 'aid': 9196627, 'title': '【补帧向/AMV】一花一草一世界', 'image': 'http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg', 'vid': 15195741, 'streams': {'flv': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv', 'segs': [{'url': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv', 'duration': 127.9, 'size': 30695642}], 'mp4': 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4'}, 'playback': 'mp4', 'extra': {'headers': {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0', 'Referer': 'https://www.bilibili.com/video/'}, 'playback': 'flv'}, 'otype': 'video', 'quality': ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'], 'multirates': 4, 'ext': 'flv', 'show': '高清 1080P', 'duration': 127.9, 'code': 0} 48 | ``` 49 | #### -f --format : 格式转换 (FFMPEG format) 50 | ``` 51 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -j 52 | { 53 | "hd": 6, 54 | "type": "bilibili", 55 | "category": "video", 56 | "choose": false, 57 | "playlist": false, 58 | "iniPath": false, 59 | "filePath": false, 60 | "urls": [ 61 | "https://www.bilibili.com/video/BV1Jx411r76d/" 62 | ], 63 | "parse": "https://www.bilibili.com/video/BV1Jx411r76d/", 64 | "site": "bilibili", 65 | "title": "【补帧向/AMV】一花一草一世界", 66 | "vid": 15195741, 67 | "aid": 9196627, 68 | "image": "http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg", 69 | "page": 1, 70 | "streams": { 71 | "segs": [ 72 | { 73 | "url": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv", 74 | "duration": 127.9, 75 | "size": 30695642 76 | } 77 | ], 78 | "flv": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv", 79 | "mp4": "http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4" 80 | }, 81 | "playback": "mp4", 82 | "show": "高清 1080P", 83 | "ext": "flv", 84 | "duration": 127.9, 85 | "otype": "video", 86 | "extra": { 87 | "headers": { 88 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0", 89 | "Referer": "https://www.bilibili.com/video/" 90 | }, 91 | "playback": "flv" 92 | }, 93 | "quality": [ 94 | "流畅 360P", 95 | "清晰 480P", 96 | "高清 720P", 97 | "高清 1080P" 98 | ], 99 | "multirates": 4 100 | } 101 | 102 | ``` 103 | 104 | #### -d --download : 下载 (Download) 105 | ``` 106 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -d 107 | Web: 哔哩哔哩视频(BILIBILI) 108 | Site: bilibili 109 | Title: 补帧向一花一草一世界 110 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 111 | Vid: 15195741 112 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 113 | Category: video 114 | Hd: 6 115 | Stream: 116 | - Ext: flv 117 | Playback: mp4 118 | Duration: 127.9s 119 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 120 | Show: 高清 1080P 121 | Multirates: 4 122 | Length: 1 123 | Dir: /dw 124 | [1 / 1] |-███████████████------------------|49% 586.04kb/s 14.49M/29.27M 125 | ``` 126 | #### -i --info : 打印信息 (Print Info) 127 | ``` 128 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -i 129 | Web: 哔哩哔哩视频(BILIBILI) 130 | Site: bilibili 131 | Title: 补帧向一花一草一世界 132 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 133 | Vid: 15195741 134 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 135 | Category: video 136 | Hd: 6 137 | Stream: 138 | - Ext: flv 139 | Playback: mp4 140 | Duration: 127.9s 141 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 142 | Show: 高清 1080P 143 | Multirates: 4 144 | Length: 1 145 | Location: 146 | - mp4: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-48.mp4 147 | part[1]: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv [30695642] 148 | ``` 149 | #### -q --query : 只获取链接信息 (Output only video information) 150 | ``` 151 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -q 152 | {'hd': 6, 'type': 'bilibili', 'category': 'video', 'choose': False, 'playlist': False, 'iniPath': False, 'filePath': False, 'urls': ['https://www.bilibili.com/video/BV1Jx411r76d/'], 'parse': 'https://www.bilibili.com/video/BV1Jx411r76d/', 'site': 'bilibili', 'page': 1, 'title': '【补帧向/AMV】一花一草一世界', 'aid': 9196627, 'vid': 15195741, 'image': 'http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg', 'code': 0} 153 | 154 | ``` 155 | #### -p --player : 播放器播放 (Directly play the video with PLAYER like mpv) 156 | ``` 157 | MAC: qito https://www.bilibili.com/video/BV1Jx411r76d/ -p mpv 158 | Web: 哔哩哔哩视频(BILIBILI) 159 | Site: bilibili 160 | Title: 【补帧向/AMV】一花一草一世界 161 | Image: http://i0.hdslb.com/bfs/archive/f2b6833f8d732c6b624e11572b9b86afac7d9687.jpg 162 | Vid: 15195741 163 | Parse: https://www.bilibili.com/video/BV1Jx411r76d/ 164 | Category: video 165 | Hd: 6 166 | Stream: 167 | - Ext: flv 168 | Playback: mp4 169 | Duration: 127.9s 170 | Quality: ['流畅 360P', '清晰 480P', '高清 720P', '高清 1080P'] 171 | Show: 高清 1080P 172 | Multirates: 4 173 | Length: 1 174 | Dir: /dw 175 | 176 | PlayBack: http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/41/57/15195741/15195741-1-80.flv 177 | (+) Video --vid=1 (h264 1280x720 60.000fps) 178 | (+) Audio --aid=1 (aac 2ch 48000Hz) 179 | AO: [coreaudio] 48000Hz stereo 2ch floatp 180 | 181 | ``` 182 | ### 额外参数 183 | 184 | | A | 选项 | 传参1 | 传参2 | 类型 | 示例 | 说明 | 185 | |:-----|:----------|:----------|:----------|:----------|:----------|:----------| 186 | | 1 | 分辨率 | -r | --hd | int | 6 | 默认最高分辨率输出 | 187 | | 2 | cookie | -c | --cookie | string | a=b;c=d | 加cookie后,解析会带此cookie请求数据 | 188 | | 3 | 播放列表 | -l | --playlist | bool | | 默认false,带此参数,会优先解析成剧集列表 | 189 | | 4 | 列表选择 | -b | --choose | string | a或a,b或a:b | ,作为分隔符时,会解析a和b集 :作为分隔符时,会解析a到b的剧集 | 190 | | 5 | 自定义文件名 | -n | --name | string | 测试A | 下载时的主体名称是 "测试A" | 191 | | 6 | 调试 | -k | --debug | bool | | 默认false,带此参数后,终端会输出相应请求 | 192 | | 7 | 剧集 | -s | --serial | string | 贝瓦儿歌 | 有此参数时,下载时会存储在存储目录的"贝瓦儿歌"里面 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /qito/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午4:43 6 | """ 7 | -------------------------------------------------------------------------------- /qito/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __main__.py 5 | @Time : 2022/9/10 下午1:39 6 | """ 7 | from argparse import ArgumentParser 8 | 9 | import coloredlogs, re, importlib, traceback, logging, json, sys, os, traceback, time 10 | 11 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 12 | 13 | from main import Parse 14 | 15 | 16 | def cmd(): 17 | parser = ArgumentParser( 18 | description="A simple video downloader written in python", 19 | usage="qito [OPTIONS] URL [URL...]", 20 | ) 21 | 22 | parser.add_argument( 23 | "-V", "--version", action="store_true", help="Print version and exit" 24 | ) 25 | parser.add_argument( 26 | "-c", "--cookie", type=str, help="Cookie [file.txt/Netscape Cookie]" 27 | ) 28 | parser.add_argument( 29 | "-e", 30 | "--exit", 31 | default=False, 32 | action="store_true", 33 | help="End process when parsing playlist error", 34 | ) 35 | parser.add_argument("-f", "--format", help="Video format") 36 | parser.add_argument( 37 | "-i", "--info", default=False, action="store_true", help="Print all information" 38 | ) 39 | parser.add_argument( 40 | "-j", "--json", default=False, action="store_true", help="Print json" 41 | ) 42 | 43 | parser.add_argument( 44 | "-q", 45 | "--query", 46 | default=False, 47 | action="store_true", 48 | help="Print video information only", 49 | ) 50 | parser.add_argument( 51 | "-s", 52 | "--serial", 53 | type=str, 54 | help="Stored in the serial folder when downloading ", 55 | ) 56 | parser.add_argument( 57 | "-r", "--hd", type=int, help="Video resolution selection [1,2, ...]" 58 | ) 59 | parser.add_argument( 60 | "-k", "--debug", default=False, action="store_true", help="Print messages" 61 | ) 62 | 63 | parser.add_argument("-t", "--type", help="Parsing type,Add when urls is not a URL") 64 | 65 | parser.add_argument("-v", "--category", help="Site category [video/music/live]") 66 | parser.add_argument( 67 | "-x", "--proxy", default=False, help="Proxy option [http/https/socks5]" 68 | ) 69 | parser.add_argument("-y", "--http", default=False, help="Http proy") 70 | parser.add_argument( 71 | "-z", 72 | "--timeout", 73 | default=False, 74 | type=int, 75 | help="Set httpGet timeout seconds, default 6s", 76 | ) 77 | playlist_grp = parser.add_argument_group("Playlist options") 78 | 79 | playlist_grp.add_argument( 80 | "-b", 81 | "--choose", 82 | default=False, 83 | help="Choose which range when the urls is a playlist", 84 | ) 85 | playlist_grp.add_argument( 86 | "-l", 87 | "--playlist", 88 | default=False, 89 | action="store_true", 90 | help="Add when urls is playlist", 91 | ) 92 | 93 | download_grp = parser.add_argument_group("Download options") 94 | download_grp.add_argument( 95 | "-d", "--download", default=False, action="store_true", help="Download" 96 | ) 97 | 98 | download_grp.add_argument( 99 | "-m", "--merge", default=True, action="store_false", help="Do not merge videos" 100 | ) 101 | download_grp.add_argument( 102 | "-n", 103 | "--name", 104 | default=False, 105 | type=str, 106 | help="Video downloaded with the FileName you want", 107 | ) 108 | download_grp.add_argument( 109 | "-u", 110 | "--multi", 111 | default=False, 112 | type=int, 113 | help="Multi-threaded download, default 10", 114 | ) 115 | download_grp.add_argument("-w", "--dir", default=False, help="Storage folder") 116 | download_grp.add_argument( 117 | "-a", 118 | "--capture", 119 | default=False, 120 | action="store_true", 121 | help=" Download clips only", 122 | ) 123 | player_grp = parser.add_argument_group("Player options") 124 | player_grp.add_argument( 125 | "-g", 126 | "--geometry", 127 | default=False, 128 | help="Adjust the initial window position or size,For example,1366 or 50﹪", 129 | ) 130 | player_grp.add_argument( 131 | "-lo", 132 | "--loop", 133 | default=False, 134 | help="Loop", 135 | ) 136 | player_grp.add_argument( 137 | "-p", 138 | "--player", 139 | default=False, 140 | help="Directly play the video with PLAYER like mpv", 141 | ) 142 | 143 | player_grp.add_argument( 144 | "-fs", 145 | "--fullscreen", 146 | default=False, 147 | action="store_true", 148 | help="Fullscreen playback", 149 | ) 150 | player_grp.add_argument( 151 | "-ts", "--start", default=False, help="Seek to given time position" 152 | ) 153 | player_grp.add_argument( 154 | "-te", "--end", default=False, help="Stop at given absolute time" 155 | ) 156 | player_grp.add_argument( 157 | "-tl", 158 | "--length", 159 | default=False, 160 | help="Stop after a given time relative to the start time", 161 | ) 162 | player_grp.add_argument( 163 | "-na", 164 | "--no_audio", 165 | default=False, 166 | action="store_true", 167 | help="Do not play sound", 168 | ) 169 | player_grp.add_argument( 170 | "-nv", 171 | "--no_video", 172 | default=False, 173 | action="store_true", 174 | help="Do not play video", 175 | ) 176 | player_grp.add_argument( 177 | "-vo", 178 | "--volume", 179 | default=False, 180 | help="Volume", 181 | ) 182 | extra_grp = parser.add_argument_group("Extra options") 183 | extra_grp.add_argument( 184 | "-in", 185 | "--init", 186 | default=False, 187 | help="Initial setup, backup or restore user.py", 188 | ) 189 | extra_grp.add_argument( 190 | "-o", "--language", default=False, help="Select language options" 191 | ) 192 | # 额外 193 | extra_grp.add_argument( 194 | "-it", "--itag", default=False, help="Youtube itag, 137[,value]" 195 | ) 196 | extra_grp.add_argument("-ccode", "--ccode", default=False, help="Youku ccode") 197 | extra_grp.add_argument("-ip", "--iniPath", default=False, help="Config iniPath") 198 | extra_grp.add_argument("-fp", "--filePath", default=False, help="Config filePath") 199 | extra_grp.add_argument( 200 | "-pw", "--password", default=False, help="Encryption password for video" 201 | ) 202 | extra_grp.add_argument( 203 | "-en", 204 | "--encoder", 205 | default=False, 206 | help="Select the video option to encode", 207 | ) 208 | 209 | parser.add_argument("urls", type=str, nargs="*", help="video urls") 210 | 211 | args = parser.parse_args() 212 | if args.version: 213 | from config.profile import version 214 | 215 | sys.exit(version) 216 | elif args.capture: 217 | args.download = True 218 | if not args.start: 219 | print("Please set the start time") 220 | sys.exit() 221 | return args 222 | 223 | 224 | def main(): 225 | params = vars(cmd()) 226 | if not params["urls"]: 227 | sys.exit("usage: qito [OPTION]... URL...") 228 | 229 | # logging.basicConfig(handlers=[log.ColorHandler()]) 230 | FIELD_STYLES = dict( 231 | asctime=dict(color="green"), 232 | hostname=dict(color="magenta"), 233 | levelname=dict(color="green"), 234 | filename=dict(color="magenta"), 235 | name=dict(color="red"), 236 | threadName=dict(color="green"), 237 | ) 238 | 239 | LEVEL_STYLES = dict( 240 | debug=dict(color="green"), 241 | info=dict(color="cyan"), 242 | warning=dict(color="yellow"), 243 | error=dict(color="red"), 244 | critical=dict(color="red"), 245 | ) 246 | 247 | coloredlogs.install( 248 | level="DEBUG", 249 | fmt="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 250 | level_styles=LEVEL_STYLES, 251 | field_styles=FIELD_STYLES, 252 | ) 253 | if params.get("debug"): 254 | logging.root.setLevel(logging.DEBUG) 255 | logging.getLogger("urllib3").setLevel(logging.WARNING) 256 | logging.getLogger("chardet").setLevel(logging.WARNING) 257 | else: 258 | logging.root.setLevel(logging.WARNING) 259 | try: 260 | for parse in params["urls"]: 261 | params["parse"] = parse 262 | Parse(params) 263 | except (AssertionError, IndexError) as e: 264 | info = sys.exc_info() 265 | try: 266 | err = traceback.extract_tb(info[2])[1] 267 | except: 268 | err = traceback.extract_tb(info[2])[0] 269 | error = { 270 | "message": str(e), 271 | "file": err[0], 272 | "line": err[1], 273 | "function": err[2], 274 | "code": err[3], 275 | } 276 | logging.warning(error) 277 | # logging.error(e.message) 278 | except (NotImplementedError, KeyError, NameError, AttributeError, TypeError) as e: 279 | logging.error(e.message) 280 | if params["exit"]: 281 | sys.exit() 282 | except KeyboardInterrupt: 283 | print("\r\n") 284 | logging.warning("ctrl c!") 285 | sys.exit() 286 | 287 | 288 | if __name__ == "__main__": 289 | main() 290 | -------------------------------------------------------------------------------- /qito/config/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午4:43 6 | """ 7 | -------------------------------------------------------------------------------- /qito/config/profile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : profile.py 5 | @Time : 2022/2/13 下午4:48 6 | """ 7 | version = "1.0.5" 8 | jsonFilter = "version|cookie|dir|exit|format|info|json|query|debug|proxy|http|timeout|download|merge|multi|dir|capture|geometry|loop|player|fullscreen|start|end|length|no_audio|no_video|volume|init|language|itag|ccode|password|encoder|name|context" 9 | -------------------------------------------------------------------------------- /qito/ini/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/8/30 下午6:34 6 | """ 7 | -------------------------------------------------------------------------------- /qito/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # -*- coding: utf-8 -*- 4 | """ 5 | @File : main.py 6 | @Time : 2022/2/13 下午6:14 7 | """ 8 | import time, importlib, os, re, logging, sys, requests, shutil 9 | from pathlib import Path 10 | 11 | from util import common 12 | 13 | 14 | class Parse(common.Common): 15 | def __init__(self, params): 16 | super().__init__() 17 | self.abspath = os.path.abspath(os.path.dirname(__file__)) 18 | self.cwd = os.getcwd() 19 | self.exit = 0 20 | if params.get("debug"): 21 | logging.basicConfig( 22 | format="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 23 | level=logging.DEBUG, 24 | ) 25 | 26 | logging.getLogger("urllib3").setLevel(logging.WARNING) 27 | logging.getLogger("chardet").setLevel(logging.WARNING) 28 | else: 29 | logging.basicConfig( 30 | format="%(levelname)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s", 31 | level=logging.WARNING, 32 | ) 33 | 34 | self.configuration() 35 | if params["parse"] == "config": 36 | if not Path(f"{self.abspath}/config/config.py").exists(): 37 | content = 'iniPath=""\nfilePath=""' 38 | self.write(f"{self.abspath}/config/config.py", content) 39 | print("Created: config.py created successfully") 40 | else: 41 | print("Exists: config.py already exists") 42 | 43 | from config import config 44 | 45 | if params.get("iniPath"): 46 | config.iniPath = params["iniPath"] 47 | if params.get("filePath"): 48 | config.filePath = params["filePath"] 49 | 50 | content = "\n".join( 51 | [ 52 | f'{i}="{getattr(config, i, None)}"' 53 | for i in [e for e in dir(config) if not e.startswith("_")] 54 | ] 55 | ) 56 | self.write(f"{self.abspath}/config/config.py", content) 57 | elif params["parse"] == "upgrade": 58 | try: 59 | file = self.abspath + "/qito-main.zip" 60 | if os.path.exists(file): 61 | os.remove(file) 62 | print( 63 | "Downloading github package [https://github.com/qitoqito/qito/archive/refs/heads/main.zip]..." 64 | ) 65 | r = requests.get( 66 | "https://github.com/qitoqito/qito/archive/refs/heads/main.zip" 67 | ) 68 | 69 | with open(file, "wb") as f: 70 | f.write(r.content) 71 | f.close() 72 | 73 | except: 74 | pass 75 | 76 | if os.path.exists(file): 77 | import zipfile 78 | import shutil 79 | 80 | filePath = self.abspath + "/qito-main" 81 | if os.path.exists(filePath): 82 | shutil.rmtree(filePath) 83 | zip = zipfile.ZipFile(file) 84 | print("The zip download is complete and will be unzipped soon...") 85 | for name in zip.namelist(): 86 | zip.extract(name, self.abspath) 87 | 88 | print("Moving files ...") 89 | if sys.version_info < (3, 8): 90 | shutil.copytree( 91 | self.abspath + "/qito-main/qito", 92 | self.abspath, 93 | ) 94 | else: 95 | shutil.copytree( 96 | self.abspath + "/qito-main/qito", 97 | self.abspath, 98 | dirs_exist_ok=True, 99 | ) 100 | print("exit...") 101 | else: 102 | print("Download failed...") 103 | 104 | elif params["parse"] == "ini": 105 | pass 106 | elif params.get("playlist"): 107 | self.playList(params) 108 | else: 109 | self.working(params) 110 | 111 | def configuration(self): 112 | self.getConfig("config") 113 | self.getConfig("profile") 114 | self.getConfig("user") 115 | if self.get("iniPath"): 116 | ini = self.parseIni(f"{self.iniPath}/config.ini") 117 | else: 118 | ini = self.parseIni(f"{self.abspath}/ini/config.ini") 119 | 120 | def working(self, params): 121 | params["category"] = params.get("category") or self.get("category") or "video" 122 | params["hd"] = params.get("hd") or self.get("hd") or 6 123 | params["parse"] = str(params["parse"]) 124 | if self.hasurl(params["parse"]): 125 | site = self.domain(params["parse"]) 126 | if site in self.prepare_location(): 127 | params["parse"] = self.curl( 128 | { 129 | "url": params["parse"], 130 | "response": "location", 131 | } 132 | ) 133 | type = self.prepare_change(site) 134 | 135 | for k, v in self.prepare_category().items(): 136 | if site in v: 137 | if self.match(v[site], params["parse"]): 138 | params["category"] = k 139 | break 140 | else: 141 | for k1, v1 in v.items(): 142 | if self.match(v1, params["parse"]): 143 | type = k1 144 | params["category"] = k 145 | site = k1 146 | break 147 | 148 | else: 149 | site = params["type"] 150 | 151 | type = self.prepare_change(site) 152 | 153 | if self.get("site") and site in self.siteChange.keys(): 154 | type = self.siteChange[site] 155 | 156 | params["site"] = site 157 | params["type"] = type 158 | try: 159 | imp = importlib.import_module(f"parse.{params['category']}.{type}") 160 | 161 | self.imp = imp 162 | a = imp.Main() 163 | 164 | a.init(params) 165 | except ModuleNotFoundError as e: 166 | print( 167 | f'The parsing of {params["category"]}.{params["site"]} is not supported...' 168 | ) 169 | except ValueError as e: 170 | print(e) 171 | 172 | except KeyboardInterrupt: 173 | # ctrl + c 终止运行 174 | print("\r\n") 175 | self.exit = 1 176 | logging.warning("End Process") 177 | sys.exit() 178 | 179 | def playList(self, params): 180 | domain = self.match(r"(\w+(?:-\w+)*).\w+\/", params["parse"]) 181 | 182 | type = params.get("type") or domain 183 | category = params.get("category") or "video" 184 | # try: 185 | 186 | try: 187 | imp = importlib.import_module(f"parse.playlist.{type}") 188 | a = imp.Main() 189 | data = getattr(a, f"{category}List")(params) 190 | assert len(data["data"]) > 0, "lists" 191 | params["category"] = data.get("category") 192 | params["type"] = data.get("type") 193 | 194 | params["serial"] = self.sub( 195 | "[’!\"#$%&'()*+,./:;<=>?@,。?★、…【】《》?“”‘’![\\]^`{|}~]+", 196 | " ", 197 | params.get("serial") or data.get("serial"), 198 | ).strip() 199 | if params.get("choose"): 200 | params["choose"] = str(params["choose"]) 201 | if ":" in params["choose"]: 202 | spl = [i for i in params["choose"].split(":") if i] 203 | start = int(spl[0]) - 1 204 | 205 | if len(spl) > 1: 206 | parseLists = data["data"][start : int(spl[1])] 207 | else: 208 | parseLists = data["data"][start:] 209 | 210 | else: 211 | spl = params["choose"].split(",") 212 | parseLists = [] 213 | for i in spl: 214 | try: 215 | parseLists.append(data["data"][int(i) - 1]) 216 | except: 217 | pass 218 | assert len(parseLists) > 0, "Exceeds the length of the episode..." 219 | else: 220 | parseLists = data["data"] 221 | params["total"] = len(data["data"]) 222 | except: 223 | parseLists = [params["parse"]] 224 | for i in parseLists: 225 | if isinstance(i, dict): 226 | params = {**params, **i} 227 | else: 228 | params["parse"] = i 229 | if self.exit: 230 | sys.exit("Exit...") 231 | if params.get("exit"): 232 | self.working(params) 233 | else: 234 | try: 235 | self.working(params) 236 | except: 237 | pass 238 | # except: 239 | # pass 240 | -------------------------------------------------------------------------------- /qito/parse/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午5:47 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/live/56.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : 56.py 5 | @Time : 2022/12/1 下午9:58 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "千帆直播(56)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://qf.56.com/{vid}?union=56_home" 23 | html = self.curl(url) 24 | anchorInfo = self.match("pageInfo.anchor\s*=\s*([^\}]+)", html) 25 | if anchorInfo: 26 | anchor = self.match("nickName:\s*'([^']+)',", anchorInfo) 27 | title = f"{anchor}的直播间" 28 | image = self.match("cover:\s*'([^']+)',", anchorInfo) 29 | 30 | return self.compact() 31 | 32 | def parse(self): 33 | p = self.params 34 | assert p["vid"], "vid" 35 | vid = p["vid"] 36 | url = f"https://qf.56.com/{vid}?union=56_home" 37 | html = self.curl(url) 38 | anchorRoom = self.match("pageInfo.anchorRoom\s*=\s*([^\}]+)", html) 39 | assert anchorRoom, "data" 40 | 41 | isLive = self.match("roomLive:\s*'([^']+)'", anchorRoom) 42 | assert isLive == "1", "close" 43 | 44 | anchorInfo = self.match("pageInfo.anchor\s*=\s*([^\}]+)", html) 45 | if anchorInfo: 46 | anchor = self.match("nickName:\s*'([^']+)',", anchorInfo) 47 | title = f"{anchor}的直播间" 48 | image = self.match("cover:\s*'([^']+)',", anchorInfo) 49 | 50 | match = self.matchAll("(rUrl|hUrl|lUrl)\s*:\s*'([^']+)'", html) 51 | lists = dict(match) 52 | 53 | assert lists, "lists" 54 | 55 | qualitys = list(lists.keys()) 56 | 57 | try: 58 | qualitys.sort(key=["lUrl", "hUrl", "rUrl"].index) 59 | except: 60 | pass 61 | dicts = {"lUrl": "标清", "hUrl": "高清", "rUrl": "超清"} 62 | quality = [dicts[i] for i in qualitys] 63 | show = self.data(quality, p["hd"]) 64 | flv = self.sub( 65 | ["\/\/(.*?)\/live", "\?", "get_url=\d+"], 66 | ["https://v-ngb.qf.56.com/live", ".flv?", "get_url=10"], 67 | lists[self.data(qualitys, p["hd"])], 68 | ) 69 | 70 | ext = playback = "flv" 71 | return self.compact() 72 | -------------------------------------------------------------------------------- /qito/parse/live/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/9/11 下午4:03 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/live/bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : bilibili.py 5 | @Time : 2022/11/17 下午3:48 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "()" 14 | self.require = ["importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | if p["parse"].startswith("http"): 19 | vid = self.match("\/(\d+)", p["parse"]) 20 | html = self.curl(p["parse"]) 21 | 22 | cid = self.match('"room_id":(\d+)', html) or vid 23 | else: 24 | cid = p["parse"] 25 | assert cid, "cid" 26 | 27 | if p.get("query"): 28 | url = f"https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={cid}" 29 | getinfo = self.curl(url) 30 | self.logging.debug(f"getInfo: {getinfo} \r\n") 31 | json = self.loads(getinfo) 32 | if json["code"] == 0: 33 | title = json["data"]["room_info"]["title"] 34 | image = json["data"]["room_info"]["keyframe"] 35 | anchor = json["data"]["anchor_info"]["base_info"]["uname"] 36 | return self.compact() 37 | 38 | def parse(self): 39 | p = self.params 40 | assert p["cid"], "cid" 41 | cid = p["cid"] 42 | 43 | url = f"https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={cid}" 44 | getinfo = self.curl(url) 45 | self.logging.debug(f"getInfo: {getinfo} \r\n") 46 | json = self.loads(getinfo) 47 | 48 | assert json["code"] == 0, "data" 49 | 50 | title = json["data"]["room_info"]["title"] 51 | image = json["data"]["room_info"]["keyframe"] 52 | anchor = json["data"]["anchor_info"]["base_info"]["uname"] 53 | # assert json["data"]["room_info"]["live_status"] == 1, "close" 54 | liveStatus = json["data"]["room_info"]["live_status"] 55 | 56 | if liveStatus == 0: 57 | raise NotImplementedError("close") 58 | elif liveStatus == 2: 59 | 60 | self.params["category"] = "video" 61 | 62 | url = f"https://api.live.bilibili.com/live/getRoundPlayVideo?room_id={cid}&type=flv&a=0.9907983099110425" 63 | html = self.curl(url) 64 | self.logging.debug(f"getExtra: {html} \r\n") 65 | json = self.loads(html) 66 | assert json["code"] == 0, "data" 67 | 68 | title = json["data"]["title"] 69 | videoCid = json["data"]["cid"] 70 | imp = self.modules["importlib"].import_module("parse.video.bilibili") 71 | a = imp.Main() 72 | a.params = { 73 | "vid": videoCid, 74 | "hd": p["hd"], 75 | } 76 | return a.parse() 77 | 78 | elif liveStatus == 1: 79 | 80 | # ary = {"150": "高清", "250": "超清", "400": "蓝光", "10000": "原画"} 81 | # key = list(ary.keys()) 82 | # quality = list(ary.values()) 83 | # qn = self.data(key, p['hd']) 84 | # show = self.data(quality, p['hd']) 85 | # 86 | # url = 'https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&qn=%s&platform=web' % (vid, qn) 87 | qn = 1000 88 | 89 | for i in range(2): 90 | url = ( 91 | "https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&qn=%s&platform=pc" 92 | % (cid, qn) 93 | ) 94 | html = self.curl(url) 95 | 96 | json = self.loads(html) 97 | assert json["code"] == 0, "data" 98 | quality_description = json["data"]["quality_description"][::-1] 99 | qns = [i["qn"] for i in quality_description] 100 | quality = [i["desc"] for i in quality_description] 101 | qn = self.data(qns, p["hd"]) 102 | if qn == 1000: 103 | break 104 | self.logging.debug(f"getLive: {html} \r\n") 105 | show = self.data(quality, p["hd"]) 106 | flv = json["data"]["durl"][0]["url"] 107 | ext = playback = "flv" 108 | extra = { 109 | "headers": { 110 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:71.0) Gecko/20100101 Firefox/71.0", 111 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 112 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", 113 | "Referer": url, 114 | } 115 | } 116 | return self.compact() 117 | -------------------------------------------------------------------------------- /qito/parse/live/douyin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyin.py 5 | @Time : 2022/9/11 下午4:03 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "抖音直播(DOUYIN)" 14 | self.require = [("quickjs", "Function", "fn"), "execjs", "importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | nonece = self.md5(self.timestamp)[:21] 19 | if self.hasurl(p["parse"]): 20 | if "live.douyin.com" in p["parse"]: 21 | html = self.curl( 22 | { 23 | "url": p["parse"], 24 | "cookie": f"__ac_nonce={nonece}; __ac_signature={self.getSign(nonece)};", 25 | } 26 | ) 27 | vid = self.match(["room_id%3D(\d+)", "roomId%22%3A%22(\d+)"], html) 28 | # vid = self.match("\/(\d+)", p["parse"]) 29 | else: 30 | url = self.curl({"url": p["parse"], "response": "location"}) 31 | vid = self.match("\/(\d+)", url) 32 | 33 | else: 34 | vid = p["parse"] 35 | if p.get("query"): 36 | url = f"https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=&type_id=0&live_id=1&room_id={vid}&sec_user_id=&app_id=1128&msToken=" 37 | for i in range(3): 38 | html = self.curl( 39 | { 40 | "url": url, 41 | # 'from':'', 42 | } 43 | ) 44 | if html: 45 | break 46 | data = self.jsonParse(html) 47 | try: 48 | data = self.jsonParse(html) 49 | room = self.haskey(data, "data.room") 50 | title = room["title"] 51 | image = room["cover"]["url_list"][0] 52 | anchor = room["owner"]["nickname"] 53 | except: 54 | pass 55 | return self.compact() 56 | 57 | def parse(self): 58 | p = self.params 59 | assert p["vid"], "vid" 60 | vid = p["vid"] 61 | extra = {"headers": {"remove": 1}} 62 | nonece = self.md5(self.timestamp)[:21] 63 | 64 | content = self.curl( 65 | { 66 | "url": p["parse"], 67 | "cookie": f"__ac_nonce={nonece}; __ac_signature={self.getSign(nonece)};", 68 | } 69 | ) 70 | render = self.match(r'id="RENDER_DATA"\s*.+?\>([^\<]+)', content) 71 | data = self.loads(self.unquote(render)) 72 | room = self.haskey(data, "app.initialState.roomStore.roomInfo.room") 73 | 74 | assert self.haskey(room, "status") == 2, "close" 75 | title = room["title"] 76 | image = room["cover"]["url_list"][0] 77 | anchor = room["owner"]["nickname"] 78 | stream = "" 79 | 80 | if "fifa_skin" in self.haskey(room, "background.uri") and self.haskey( 81 | room, "episode_extra.match_room_info.match_data.match_id" 82 | ): 83 | roomId = self.haskey( 84 | room, "episode_extra.match_room_info.match_data.match_id" 85 | ) 86 | html = self.curl( 87 | { 88 | "url": f"https://live.douyin.com/fifaworldcup/{roomId}?enter_from_merge=web_share_link&enter_method=web_share_link" 89 | } 90 | ) 91 | json = self.match(r'id="RENDER_DATA"\s*.+?>([^\<]+)', html) 92 | data = self.jsonParse(self.unquote(json)) 93 | for k, v in data.items(): 94 | a = self.jsonParse(v) 95 | if self.haskey(a, "roomRes"): 96 | room2 = self.haskey(a, "roomRes.newRoomInfo.room") 97 | stream = room2["stream_url"] 98 | camera = self.haskey( 99 | a, "roomRes.newRoomInfo.room.episode_extra.camera_infos" 100 | ) 101 | if camera: 102 | lang = [] 103 | eee = 1 104 | for abc in camera: 105 | lang.append( 106 | { 107 | "url": f"https://live.douyin.com/fifaworldcup/{roomId}.html", 108 | "langcode": eee, 109 | "language": abc["title"], 110 | } 111 | ) 112 | eee += 1 113 | if p.get("language") and ( 114 | p["language"] == abc["title"] 115 | or p["language"] == str(eee) 116 | ): 117 | stream = abc["stream_info"] 118 | language = abc["title"] 119 | extra["language"] = lang 120 | 121 | # 122 | 123 | if not stream: 124 | stream = room["stream_url"] 125 | 126 | try: 127 | streamData = self.loads( 128 | stream["live_core_sdk_data"]["pull_data"]["stream_data"] 129 | )["data"] 130 | 131 | qualitys = list( 132 | filter( 133 | lambda x: x in streamData.keys(), 134 | ["md", "ld", "sd", "hd", "uhd", "origin"], 135 | ) 136 | ) 137 | 138 | qualitys.sort(key=["md", "ld", "sd", "hd", "uhd", "origin"].index) 139 | show1 = self.data(qualitys, p["hd"]) 140 | dict = { 141 | "md": "清晰", 142 | "ld": "标清", 143 | "sd": "高清", 144 | "hd": "超清", 145 | "uhd": "蓝光", 146 | "origin": "原画", 147 | } 148 | quality = [dict[i] for i in qualitys] 149 | show = dict[show1] 150 | main = streamData[show1]["main"] 151 | flv = main["flv"] 152 | m3u8 = main["hls"] 153 | except: 154 | resolution_name = stream["resolution_name"] 155 | flvUrl = stream["flv_pull_url"] 156 | qualitys = list(flvUrl.keys())[::-1] 157 | qualitys.sort(key=["SD1", "SD2", "HD1", "FULL_HD1", "ORIGION"].index) 158 | quality = [resolution_name[i] for i in qualitys] 159 | show = self.data(quality, p["hd"]) 160 | resolution = self.data(qualitys, p["hd"]) 161 | flv = flvUrl[resolution] 162 | m3u8 = stream["hls_pull_url_map"][resolution] 163 | 164 | ext = "flv" 165 | playback = "m3u8" 166 | return self.compact() 167 | 168 | def getSign(self, nonce): 169 | js = self.read(self.abspath + "/tool/javascript/douyin.sign.js") 170 | try: 171 | ctx = self.modules["execjs"].compile(js) 172 | sign = ctx.call("get_sign", nonce) 173 | except: 174 | sign = "_02B4Z6wo00f011STaVgAAIDC0rtM.EB46GtUom3AALaN5qKTFC4qCqg-fvNhWfGihyk9zChBBYW39vA2w5aTK14w48Hspeg-fFmRHS3.KMjtyBBTNms0LNJ-dqHdw-e6dy08s1j8YLPSsWx479" 175 | return sign 176 | -------------------------------------------------------------------------------- /qito/parse/live/douyu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : douyu.py 5 | @Time : 2022/11/30 上午12:24 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "斗鱼直播(DOUYU)" 14 | self.require = [("quickjs", "Function", "fn"), "execjs", "importlib"] 15 | 16 | def query(self): 17 | p = self.params 18 | if self.hasurl(p["parse"]): 19 | cid = self.match("\/(\d+)", p["parse"]) 20 | else: 21 | cid = p["parse"] 22 | 23 | if cid: 24 | html = self.curl( 25 | {"url": f"https://www.douyu.com/{cid}", "encoding": "utf8"} 26 | ) 27 | else: 28 | html = self.curl(p["parse"]) 29 | vid = self.match( 30 | [ 31 | "ROOM.room_id\s*=\s*(\d+)", 32 | "room_id\s*=\s*(\d+)", 33 | '"room_id.?":(\d+)', 34 | "data-onlineid=(\d+)", 35 | ], 36 | html, 37 | ) 38 | title = self.match('Title-headlineH2">([^<]+)<', html) 39 | anchor = self.match('Title-anchorName" title="([^"]+)"', html) 40 | 41 | if not anchor or not title: 42 | url = f"https://open.douyucdn.cn/api/RoomApi/room/{vid}" 43 | content = self.curl({"url": url, "encoding": "utf8"}) 44 | 45 | self.logging.debug(f"getInfo: {content} \r\n") 46 | json = self.loads(content) 47 | 48 | if json["error"] == 0: 49 | anchor = json["data"]["owner_name"] 50 | title = json["data"]["room_name"].replace("\t", "") 51 | image = json["data"]["room_thumb"] 52 | 53 | return self.compact() 54 | 55 | def parse(self): 56 | p = self.params 57 | assert p["vid"], "vid" 58 | vid = p["vid"] 59 | timestamp = str(self.time) 60 | 61 | crypto = self.read(f"{self.abspath}/tool/javascript/crypto-js.min.js") 62 | url = f"https://www.douyu.com/swf_api/homeH5Enc?rids={vid}" 63 | get_enc = self.curl(url) 64 | json = self.loads(get_enc) 65 | assert json.get("error") == 0, "homeH5enc" 66 | jsEnc = json["data"][f"room{vid}"] 67 | replaceString = """ 68 | var newString = "'" + Ee + "';"; 69 | var evalString = eval(newString); 70 | evalString = evalString.replace(/(.*oog\(.*)/, "$1oog['test'] = function(a) { return 1; }"); 71 | eval(evalString); 72 | """ 73 | jsDom = jsEnc.replace("eval(Ee);", replaceString) 74 | 75 | dom = "let window = {},document = {};" 76 | 77 | try: 78 | f = self.modules["fn"]("ub98484234", f"{crypto};{dom};{jsDom};") 79 | ub98484234 = f(vid, self.md5(timestamp), timestamp) 80 | 81 | except: 82 | ctx = self.modules["execjs"].compile(f"{crypto};{dom};{jsDom};") 83 | ub98484234 = ctx.call("ub98484234", vid, self.md5(timestamp), timestamp) 84 | 85 | self.logging.debug(f"ub98484234: {ub98484234} \r\n") 86 | h5Url = f"https://www.douyu.com/lapi/live/getH5Play/{vid}" 87 | rateTemp = 0 88 | postData = self.qsl( 89 | f"{ub98484234}&cdn=&rate={rateTemp}&ver=Douyu_219042402&iar=1&ive=0" 90 | ) 91 | 92 | source = self.curl( 93 | { 94 | "url": f"https://www.douyu.com/lapi/live/getH5Play/{vid}", 95 | "method": "post", 96 | "form": postData, 97 | "encoding": "utf8", 98 | } 99 | ) 100 | self.logging.debug(f"getLive: {source} \r\n") 101 | playJson = self.loads(source) 102 | if playJson["error"] == 0: 103 | multirates = self.column(playJson["data"]["multirates"], "", "name") 104 | quality = list(multirates.keys())[::-1] 105 | show = self.data(quality, p["hd"]) 106 | rate = multirates[show]["rate"] 107 | if rateTemp != rate: 108 | postData = self.qsl( 109 | f"{ub98484234}&cdn=&rate={rateTemp}&ver=Douyu_219042402&iar=1&ive=0" 110 | ) 111 | 112 | request = {"method": "post", "form": postData, "encoding": "utf8"} 113 | source = self.curl(h5Url, request) 114 | playJson = self.loads(source) 115 | 116 | flv = f"{playJson['data']['rtmp_url']}/{playJson['data']['rtmp_live']}" 117 | ext = playback = "flv" 118 | 119 | # 获取M3U8 120 | rateUrl = "https://m.douyu.com/api/room/ratestream" 121 | rateDict = self.qsl(f"{ub98484234}&ver=219032101&rid={vid}&rate={rate}") 122 | 123 | request = {"method": "post", "form": rateDict, "decoding": "utf8"} 124 | 125 | rateSource = self.curl(rateUrl, request) 126 | self.logging.debug(f"ratestream: {rateSource} \r\n") 127 | streamDict = self.loads(rateSource) 128 | if streamDict["code"] == 0: 129 | m3u8 = streamDict["data"]["url"] 130 | playback = "m3u8" 131 | 132 | elif playJson["error"] == -5: 133 | vod_url = f"https://www.douyu.com/swf_api/getRoomCloseVod/{vid}" 134 | 135 | get_vod = self.curl(vod_url) 136 | self.logging.debug(f"getRoomCloseVod: {get_vod} \r\n") 137 | roomJson = self.loads(get_vod) 138 | info = roomJson.get("lastVidInfo") 139 | 140 | if info: 141 | self.params["category"] = "video" 142 | vid = info["vid"] 143 | aid = self.match("show\/(\w+)", info["url"]) 144 | imp = self.modules["importlib"].import_module("parse.video.douyu") 145 | a = imp.Main() 146 | a.modules = self.modules 147 | a.params = {"vid": vid, "hd": p["hd"], "aid": aid} 148 | return a.parse() 149 | extra = {"remove": 1} 150 | return self.compact() 151 | -------------------------------------------------------------------------------- /qito/parse/live/huajiao.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : huajiao.py 5 | @Time : 2022/12/1 下午9:44 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "花椒直播(HUAJIAO)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("\/(\d+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://www.huajiao.com/l/{vid}" 23 | turl = self.curl(url, {"response": "location"}) 24 | assert turl != "https://www.huajiao.com/", "close" 25 | html = self.curl(url) 26 | keywords = self.match('name="keywords" content="([^"]+)"', html) 27 | spl = keywords.split(",") 28 | title = spl[0] 29 | anchor = spl[1] 30 | 31 | return self.compact() 32 | 33 | def parse(self): 34 | p = self.params 35 | assert p["vid"], "vid" 36 | vid = p["vid"] 37 | 38 | url = f"https://www.huajiao.com/l/{vid}" 39 | turl = self.curl(url, {"response": "location"}) 40 | assert turl != "https://www.huajiao.com/", "close" 41 | html = self.curl(url) 42 | keywords = self.match('name="keywords" content="([^"]+)"', html) 43 | spl = keywords.split(",") 44 | title = spl[0] 45 | anchor = spl[1] 46 | sn = self.match('"sn":"([^"]+)"', html) 47 | uid = self.match('"uid":"(\d+)"', html) 48 | 49 | sUrl = f"https://live.huajiao.com/live/substream?guid=aff3a83c325ea1db22262bc87361b590&platform=android&rand=0.8804918002581339&time={ self.timestamp}&version=1.0.0&sn={sn}&uid={uid}&liveid={vid}&encode=h265" 50 | substream = self.curl(sUrl) 51 | self.logging.debug(f"getLive: {substream} \r\n") 52 | json = self.loads(substream) 53 | assert json["errno"] == 0, "data" 54 | flv = json["data"]["h264_url"] 55 | ext = playback = "flv" 56 | return self.compact() 57 | -------------------------------------------------------------------------------- /qito/parse/live/huya.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : huya.py 5 | @Time : 2022/11/29 下午11:04 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "虎牙直播(HUYA)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | vid = self.match("com\/(\w+)", p["parse"]) 19 | else: 20 | vid = p["parse"] 21 | if p.get("query"): 22 | url = f"https://www.huya.com/{vid}" 23 | html = self.curl(url) 24 | b64 = self.match(r"stream: ({.+)\n.*?};", html) 25 | json = self.jsonParse(b64) 26 | data = json["data"][0] 27 | anchor = data["gameLiveInfo"]["nick"] 28 | title = data["gameLiveInfo"]["introduction"] 29 | return self.compact() 30 | 31 | def parse(self): 32 | p = self.params 33 | assert p["vid"], "vid" 34 | vid = p["vid"] 35 | url = f"https://www.huya.com/{vid}" 36 | html = self.curl(url) 37 | b64 = self.match(r"stream: ({.+)\n.*?};", html) 38 | 39 | assert b64, "close" 40 | self.logging.debug(f"getStream: {b64} \r\n") 41 | json = self.jsonParse(b64) 42 | data = json["data"][0] 43 | vMultiStreamInfo = json["vMultiStreamInfo"] 44 | dicts = {i["sDisplayName"]: i["iBitRate"] for i in vMultiStreamInfo} 45 | quality = list(dicts.keys())[::-1] 46 | show = self.data(quality, p["hd"]) 47 | ratio = dicts[show] 48 | anchor = data["gameLiveInfo"]["nick"] 49 | title = data["gameLiveInfo"]["introduction"] 50 | 51 | lists = data["gameStreamInfoList"][0] 52 | 53 | m3u8 = "{}/{}.{}?{}&ratio={}".format( 54 | lists["sHlsUrl"], 55 | lists["sStreamName"], 56 | lists["sHlsUrlSuffix"], 57 | lists["sHlsAntiCode"].replace("&", "&"), 58 | ratio, 59 | ) 60 | flv = "{}/{}.{}?{}&ratio={}".format( 61 | lists["sFlvUrl"], 62 | lists["sStreamName"], 63 | lists["sFlvUrlSuffix"], 64 | lists["sFlvAntiCode"].replace("&", "&"), 65 | ratio, 66 | ) 67 | playback = "m3u8" 68 | ext = "flv" 69 | 70 | return self.compact() 71 | -------------------------------------------------------------------------------- /qito/parse/live/pps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : pps.py 5 | @Time : 2022/12/1 下午3:26 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "奇秀直播(PPS)" 14 | 15 | def query(self): 16 | p = self.params 17 | if "gamelive.iqiyi.com" in p["parse"]: 18 | p["parse"] = self.curl(p["parse"], {"response": "location"}) 19 | 20 | if self.hasurl(p["parse"]): 21 | 22 | vid = self.match("\/(\d+)", p["parse"]) 23 | else: 24 | vid = p["parse"] 25 | 26 | url = f"https://x.pps.tv/room/{vid}" 27 | 28 | html = self.curl(url) 29 | uid = self.match(['"user_id"\s*:\s*"(\d+)"', "user_id=(\d+)"], html) 30 | title = self.loads(self.match('"room_name":("[^"]*"),', html)) 31 | roomConfig = self.match("_room_config\s*=\s*([^;]+)", html) 32 | self.logging.debug(f"getRoomConfig: {roomConfig} \r\n") 33 | try: 34 | json = self.loads(roomConfig) 35 | anchor = json["anchor_info"]["nick_name"] 36 | title = json["live_info"]["live_title"] 37 | image = json["live_info"]["live_image"] 38 | except: 39 | pass 40 | 41 | return self.compact() 42 | 43 | def parse(self): 44 | p = self.params 45 | assert p["vid"], "vid" 46 | vid = p["vid"] 47 | timestamp = self.time 48 | assert p["uid"], "uid" 49 | uid = p["uid"] 50 | url = "https://m-glider-xiu.pps.tv/v2/stream/get.json" 51 | ary = { 52 | "smooth": "标清", 53 | "high": "高清", 54 | "source": "超清", 55 | } 56 | quality = list(ary.values()) 57 | show = self.data(quality, p["hd"]) 58 | rate = self.data(ary.keys(), p["hd"]) 59 | 60 | postData = { 61 | "anchor_id": uid, 62 | "app_key": "show_web_h5", 63 | "type_id": "1", 64 | "vid": "1", 65 | "version": "1.0.0", 66 | "platform": "1_10_101", 67 | "time": timestamp, 68 | "netstat": "wifi", 69 | "device_id": self.md5(uid), 70 | "bit_rate_type": rate, 71 | "protocol": "5", 72 | } 73 | postData["sign"] = self.getSign(postData) 74 | html = self.curl(url, {"method": "post", "form": postData}) 75 | self.logging.debug(f"getJson: {html} \r\n") 76 | json = self.loads(html) 77 | 78 | assert json["code"] == "A00000", "close" 79 | flv = json["data"]["https_flv"] 80 | playback = ext = "flv" 81 | return self.compact() 82 | 83 | def getSign(self, params): 84 | # string=self.self.urlencode(params)+"w!ytDgy#lEXWoJmN4HPf" 85 | s = [] 86 | for key in sorted(params.keys()): 87 | s.append(f"{key}:{params[key]}") 88 | s.append("w!ytDgy#lEXWoJmN4HPf") 89 | s = "".join(s) 90 | return self.sha1(s) 91 | -------------------------------------------------------------------------------- /qito/parse/music/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/10/8 上午10:49 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/music/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : qq.py 5 | @Time : 2022/10/8 上午10:49 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "腾讯音乐(QQ)" 14 | 15 | def query(self): 16 | p = self.params 17 | if p["parse"].startswith("http"): 18 | if "songid" in p["parse"]: 19 | vid = self.match(r"songid=(\w+)", p["parse"]) 20 | elif "song/" in p["parse"]: 21 | vid = self.match(r"song\/(\w+)", p["parse"]) 22 | elif "songDetail" in p["parse"]: 23 | vid = self.match(r"songDetail\/(\w+)", p["parse"]) 24 | 25 | else: 26 | vid = p["parse"] 27 | mParams = { 28 | "hostUin": "0", 29 | "format": "jsonp", 30 | "inCharset": "utf8", 31 | "outCharset": "utf-8", 32 | "notice": "0", 33 | "platform": "yqq", 34 | "needNewCode": "0", 35 | "data": '{"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"%s"},"module":"music.pf_song_detail_svr"}}' 36 | % vid, 37 | } 38 | url = "https://u.y.qq.com/cgi-bin/musicu.fcg?{}".format(self.urlencode(mParams)) 39 | html = self.curl(url) 40 | self.logging.debug(f"getInfo: {html} \r\n") 41 | json = self.loads(html) 42 | assert json["songinfo"]["data"], "data" 43 | data = json["songinfo"]["data"] 44 | 45 | company = self.haskey(data, "info.company.content.0.value") 46 | genry = self.haskey(data, "info.genre.content.0.value") 47 | 48 | album = data["track_info"]["album"]["title"].strip() 49 | duration = data["track_info"]["interval"] 50 | cover = data["track_info"]["album"]["mid"] 51 | image = f"https://y.gtimg.cn/music/photo_new/T002R300x300M000{cover}.jpg" 52 | title = data["track_info"]["title"].strip() 53 | cid = data["track_info"]["id"] 54 | publish = data["track_info"]["time_public"] 55 | language = data["info"]["lan"]["content"][0]["value"].strip() 56 | singer = " | ".join([i["title"].strip() for i in data["track_info"]["singer"]]) 57 | return self.compact() 58 | 59 | def parse(self): 60 | p = self.params 61 | vid = p["vid"] 62 | params = { 63 | "hostUin": "0", 64 | "format": "jsonp", 65 | "inCharset": "utf8", 66 | "outCharset": "utf-8", 67 | "notice": "0", 68 | "platform": "yqq", 69 | "needNewCode": "0", 70 | "data": '{"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"7152021848","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"7152021848","songmid":["%s"],"songtype":[0],"uin":"","loginflag":1,"platform":"20"}},"comm":{"uin":"","format":"json","ct":24,"cv":0}}' 71 | % vid, 72 | } 73 | url = "https://u.y.qq.com/cgi-bin/musicu.fcg?{}".format(self.urlencode(params)) 74 | html = self.curl(url) 75 | self.logging.debug(f"getSong: {html} \r\n") 76 | json = self.jsonParse(html) 77 | try: 78 | m4a = ( 79 | "http://isure.stream.qqmusic.qq.com/" 80 | + json["req_0"]["data"]["midurlinfo"][0]["purl"] 81 | ) 82 | # print(m4a) 83 | playback = ext = "m4a" 84 | except: 85 | raise NotImplementedError("data") 86 | 87 | return self.compact() 88 | -------------------------------------------------------------------------------- /qito/parse/playlist/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2022/8/28 上午10:50 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/playlist/bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : bilibili.py 5 | @Time : 2022/10/12 下午7:42 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "哔哩哔哩剧集列表" 14 | 15 | def videoList(self, params): 16 | serial = "" 17 | url = params["parse"] 18 | lists = [] 19 | if "space.bilibili.com" in url: 20 | uid = self.match("space.bilibili.com\/(\d+)", url) 21 | for i in range(1000): 22 | page = i + 1 23 | u = f"https://api.bilibili.com/x/space/arc/search?mid={uid}&ps=30&tid=0&pn={page}&keyword=&order=pubdate" 24 | html = self.curl(u) 25 | json = self.loads(html) 26 | if json["code"] != 0: 27 | break 28 | count = json["data"]["page"]["count"] 29 | y = count / 30 30 | vlist = self.column(json["data"]["list"]["vlist"], "aid") 31 | lists.extend(vlist) 32 | if page > y: 33 | break 34 | elif "/md" in url: 35 | content = self.curl(url) 36 | seasonId = self.match('"season_id":(\d+)', content) or self.match( 37 | "\/md(\d+)", url 38 | ) 39 | seasonUrl = ( 40 | f"https://api.bilibili.com/pgc/web/season/section?season_id={seasonId}" 41 | ) 42 | 43 | html = self.curl(seasonUrl) 44 | json = self.loads(html) 45 | assert json["code"] == 0, "data" 46 | lists = self.column(json["result"]["main_section"]["episodes"], "aid") 47 | 48 | elif "/av" in url: 49 | aid = self.match("\/av(\d+)", url) 50 | pageUrl = ( 51 | f"https://api.bilibili.com/x/player/pagelist?aid={aid}&jsonp=jsonp" 52 | ) 53 | html = self.curl(pageUrl) 54 | json = self.loads(html) 55 | assert json["code"] == 0, "data" 56 | lists = [ 57 | f"https://www.bilibili.com/video/av{aid}?p={i + 1}" 58 | for i in range(len(json["data"])) 59 | ] 60 | elif "/BV" in url: 61 | bvid = self.match("\/(BV\w+)", url) 62 | aid = self.dec(bvid) 63 | pageUrl = ( 64 | f"https://api.bilibili.com/x/player/pagelist?aid={aid}&jsonp=jsonp" 65 | ) 66 | html = self.curl(pageUrl) 67 | json = self.loads(html) 68 | assert json["code"] == 0, "data" 69 | lists = [ 70 | f"https://www.bilibili.com/video/av{aid}?p={i + 1}" 71 | for i in range(len(json["data"])) 72 | ] 73 | else: 74 | html = self.curl(url) 75 | data = self.match("window.__INITIAL_STATE__=(\{.*?\});", html) 76 | json = self.loads(data) 77 | serial = self.haskey(json, "mediaInfo.title") 78 | lists = [ 79 | { 80 | "parse": str(i["cid"]), 81 | "title": "%s %s" % (i["titleFormat"], i["longTitle"]), 82 | } 83 | for i in json["epList"] 84 | ] 85 | 86 | return { 87 | "data": lists, 88 | "category": "video", 89 | "type": "bilibili", 90 | "serial": serial, 91 | } 92 | 93 | def dec(self, x): 94 | # https://www.zhihu.com/question/381784377/answer/1099438784 95 | tr = { 96 | "f": 0, 97 | "Z": 1, 98 | "o": 2, 99 | "d": 3, 100 | "R": 4, 101 | "9": 5, 102 | "X": 6, 103 | "Q": 7, 104 | "D": 8, 105 | "S": 9, 106 | "U": 10, 107 | "m": 11, 108 | "2": 12, 109 | "1": 13, 110 | "y": 14, 111 | "C": 15, 112 | "k": 16, 113 | "r": 17, 114 | "6": 18, 115 | "z": 19, 116 | "B": 20, 117 | "q": 21, 118 | "i": 22, 119 | "v": 23, 120 | "e": 24, 121 | "Y": 25, 122 | "a": 26, 123 | "h": 27, 124 | "8": 28, 125 | "b": 29, 126 | "t": 30, 127 | "4": 31, 128 | "x": 32, 129 | "s": 33, 130 | "W": 34, 131 | "p": 35, 132 | "H": 36, 133 | "n": 37, 134 | "J": 38, 135 | "E": 39, 136 | "7": 40, 137 | "j": 41, 138 | "L": 42, 139 | "5": 43, 140 | "V": 44, 141 | "G": 45, 142 | "3": 46, 143 | "g": 47, 144 | "u": 48, 145 | "M": 49, 146 | "T": 50, 147 | "K": 51, 148 | "N": 52, 149 | "P": 53, 150 | "A": 54, 151 | "w": 55, 152 | "c": 56, 153 | "F": 57, 154 | } 155 | s = [11, 10, 3, 8, 4, 6] 156 | xor = 177451812 157 | add = 8728348608 158 | r = 0 159 | for i in range(6): 160 | r += tr[x[s[i]]] * 58**i 161 | return (r - add) ^ xor 162 | 163 | def enc(self, x): 164 | table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF" 165 | s = [11, 10, 3, 8, 4, 6] 166 | xor = 177451812 167 | add = 8728348608 168 | x = (int(x) ^ xor) + add 169 | r = list("BV1 4 1 7 ") 170 | for i in range(6): 171 | r[s[i]] = table[x // 58**i % 58] 172 | return "".join(r) 173 | -------------------------------------------------------------------------------- /qito/parse/playlist/ixigua.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : ixigua.py 5 | @Time : 2022/10/14 下午6:40 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "西瓜视频剧集列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | cover = self.match("\/(\d+)", params["parse"]) 18 | else: 19 | cover = params["parse"] 20 | json = self.curl( 21 | { 22 | "url": "https://www.ixigua.com/api/albumv2/details", 23 | "params": {"albumId": cover}, 24 | "referer": "https://www.ixigua.com/", 25 | "response": "json", 26 | } 27 | ) 28 | assert json["code"] == 200, "playlist" 29 | 30 | lists = [ 31 | f'https://www.ixigua.com/{i["albumId"]}?id={i["episodeId"]}' 32 | for i in json["data"]["playlist"] 33 | ] 34 | serial = self.haskey(json, "data.albumInfo.title") 35 | return {"data": lists, "category": "video", "type": "ixigua", "serial": serial} 36 | -------------------------------------------------------------------------------- /qito/parse/playlist/le.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : le.py 5 | @Time : 2022/8/28 上午10:51 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "乐视剧集列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | vid = self.match("\/vplay\/(\d+)", params["parse"]) 18 | if not vid: 19 | cover = self.match("\/(\d+)", params["parse"]) 20 | else: 21 | vid = params["parse"] 22 | serial = "" 23 | if vid: 24 | tvUrl = f"http://www.le.com/ptv/vplay/{vid}.html" 25 | tvSource = self.curl( 26 | { 27 | "url": tvUrl, 28 | # 'from':'', 29 | } 30 | ) 31 | cover = self.match("pid\s*:\s*(\d+)", tvSource) 32 | serial = self.match('pTitle\s*:\s*"([^"]+)"', tvSource) 33 | 34 | url = f"http://d.api.m.le.com/detail/episode?pid={cover}&platform=pc&page=1&pagesize=2000&type=1&_=1565045426195" 35 | 36 | html = self.curl(url) 37 | json = self.loads(html) 38 | assert json["code"] == "200", "playlist" 39 | 40 | lists = [i["vid"] for i in json["data"]["list"]] 41 | if len(lists) and not serial: 42 | tvUrl = f"http://www.le.com/ptv/vplay/{lists[0]}.html" 43 | tvSource = self.curl( 44 | { 45 | "url": tvUrl, 46 | # 'from':'', 47 | } 48 | ) 49 | serial = self.match('pTitle\s*:\s*"([^"]+)"', tvSource) 50 | 51 | return {"data": lists, "category": "video", "type": "le", "serial": serial} 52 | -------------------------------------------------------------------------------- /qito/parse/playlist/miguvideo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : miguvideo.py 5 | @Time : 2022/11/28 下午2:05 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "咪咕视频列表" 14 | 15 | def videoList(self, params): 16 | if params["parse"].startswith("http"): 17 | cover = self.match("=(\d+)", params["parse"]) 18 | else: 19 | cover = params["parse"] 20 | url = f"https://program-sc.miguvideo.com/program/v3/cont/content-info/{cover}/1" 21 | html = self.curl(url) 22 | json = self.loads(html) 23 | 24 | lists = [ 25 | {"title": i["name"], "parse": i["pID"]} 26 | for i in self.haskey(json, "body.data.datas") 27 | ] 28 | serial = self.haskey(json, "body.data.name") 29 | return { 30 | "data": lists, 31 | "category": "video", 32 | "type": "miguvideo", 33 | "serial": serial, 34 | } 35 | -------------------------------------------------------------------------------- /qito/parse/playlist/qq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : qq.py 5 | @Time : 2022/9/8 下午10:51 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "腾讯剧集列表" 14 | 15 | def videoList(self, params): 16 | cover = "" 17 | if params["parse"].startswith("http"): 18 | cover = self.match("cover\/(\w+)", params["parse"]) 19 | vid = self.match( 20 | ["vid=(\w+)", "/(\w+)$", "cover\/\w+\/(\w+)"], 21 | params["parse"], 22 | ) 23 | html = self.curl(params["parse"]) 24 | if not vid: 25 | vid = self.match( 26 | [ 27 | "&vid=(\w+)", 28 | "vid:\s*[\"'](\w+)", 29 | "vid\s*=\s*[\"']\s*(\w+)", 30 | '"vid":"(\w+)"', 31 | ], 32 | html, 33 | ) 34 | else: 35 | vid = params["parse"] 36 | if vid and not cover: 37 | u = f"https://m.v.qq.com/play.html?cid=&vid={vid}&ptag=v_qq_com%23v.play.adaptor%233" 38 | h = self.curl( 39 | { 40 | "url": u, 41 | } 42 | ) 43 | cover = self.match(["cid%22%3A%22(\w+)", "cid\s*=\s*'(\w+)'"], h) 44 | 45 | s = self.curl( 46 | { 47 | "url": f"https://node.video.qq.com/x/api/float_vinfo2?cid={cover}", 48 | # 'from':'', 49 | } 50 | ) 51 | d = self.jsonParse(s) 52 | serial = d["c"]["title"] 53 | playList = d["c"]["video_ids"] 54 | return {"data": playList, "category": "video", "type": "qq", "serial": serial} 55 | -------------------------------------------------------------------------------- /qito/parse/playlist/sohu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : sohu.py 5 | @Time : 2022/12/6 上午10:58 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "搜狐视频剧集(SOHU)" 14 | 15 | def videoList(self, params): 16 | url = params["parse"] 17 | 18 | if url.startswith("http"): 19 | vid = self.match("v\/([^.]+)", url) 20 | else: 21 | vid = url 22 | url = f"https://tv.sohu.com/v/{vid}.html" 23 | html = self.curl( 24 | { 25 | "url": url, 26 | # 'from':'', 27 | } 28 | ) 29 | 30 | playlistId = self.match(r'playlistId\s*=\s*"(\d+)"', html) 31 | a = f"https://pl.hd.sohu.com/videolist?playlistid={playlistId}&o_playlistId=&pianhua=0&pagenum=1&pagesize=300&order=0&cnt=1&pageRule=2&withPgcVideo=1&withLookPoint=1&ssl=0&preVideoRule=3&_=1670295414041" 32 | b = self.curl( 33 | { 34 | "url": a, 35 | # 'from':'', 36 | } 37 | ) 38 | c = self.jsonParse(b) 39 | playlist = [f"https:{i['pageUrl']}" for i in c["videos"]] 40 | serial = self.haskey(c, "albumName") 41 | return {"data": playlist, "category": "video", "type": "sohu", "serial": serial} 42 | -------------------------------------------------------------------------------- /qito/parse/playlist/youku.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : youku.py 5 | @Time : 2022/9/4 下午5:01 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "优酷剧集列表" 14 | 15 | def videoList(self, params): 16 | url = params["parse"] 17 | if url.startswith("http"): 18 | url = self.curl({"url": url, "response": "location"}) 19 | vid = self.match("v_show/id_([^\.]+)", url) 20 | else: 21 | vid = url 22 | if vid: 23 | ccode = "0524" 24 | try: 25 | utid = self.curl( 26 | { 27 | "url": "http://log.mmstat.com/eg.js", 28 | "headers": { 29 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0", 30 | "Referer": "https://g.alicdn.com/alilog/oneplus/blk.html", 31 | }, 32 | "response": "cookie", 33 | }, 34 | )["cna"] 35 | except: 36 | utid = "qyiUGD8MHWkCARudQQu7gaFM" 37 | params = { 38 | "vid": vid, 39 | "ccode": ccode, 40 | "client_ip": "192.168.1.1", 41 | "utid": utid, 42 | "client_ts": self.timestamp, 43 | "ckey": "115#1dCil11O1TaNcv6zGfND1Cso311GLyAi1/2Wsi/gdl1it86z19WZy56NE8P1IvvCtU/8yPZQi/WJ1aU4AWNcaLBfOZPQOSAPetT4yWZQgbvJhEz4vBN4+5yAurrQ/jfyet/4yWZQiQ+ghZz8OWNcaTpjurPdvOoNPKxRLp+2i7lL1FGYdPYMTT9cxCNRlGg0kxHucjHqect+1pQNBWMKLRoQD1ZDrSxQ+F6RY5gk0Bmywzgc8WLIgwd2piFbn1q6EWcfZc1Wg5bS6ancr86G4xLB9BSOV9noSrKFv5r4lzKO8RWkfVdXHmIATO1SsV5RtVQCAcMDKsROmJbQy8ozjXN+EK4p3CZSxTcnDRaAr/TrOZSCIXoXN8k3EkUCh1QV3dpRqjRzsM4qBS36s8JOXl9VM4+QJ0+DLRJUKP3J11CNo0odneVsS52H58CYeEqN", 44 | } 45 | html = self.curl( 46 | { 47 | "url": "https://ups.youku.com/ups/get.json", 48 | "params": params, 49 | } 50 | ) 51 | json = self.jsonParse(html) 52 | 53 | serial = self.haskey(json, "data.show.title") 54 | cover = self.haskey(json, "data.show.encodeid") 55 | if cover: 56 | s = self.curl( 57 | { 58 | "url": f"https://search.youku.com/api/search?appScene=show_episode&showIds={cover}&appCaller=h5" 59 | } 60 | ) 61 | data = self.loads(s) 62 | playlist = self.column(data["serisesList"], "videoId") 63 | return { 64 | "data": playlist, 65 | "category": "video", 66 | "type": "youku", 67 | "serial": serial, 68 | } 69 | -------------------------------------------------------------------------------- /qito/parse/video/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : __init__.py.py 5 | @Time : 2021/10/19 下午5:47 6 | """ 7 | -------------------------------------------------------------------------------- /qito/parse/video/acfun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : acfun.py 5 | @Time : 2022/11/29 下午1:59 6 | """ 7 | import template 8 | 9 | 10 | class Main(template.Template): 11 | def __init__(self): 12 | super().__init__() 13 | self.title = "()" 14 | 15 | def query(self): 16 | p = self.params 17 | if not p["parse"].startswith("http"): 18 | if self.match("aa(\d+)", p["parse"]): 19 | p["parse"] = "https://www.acfun.cn/bangumi/%s" % p["parse"] 20 | elif self.match("ac(\d+)", p["parse"]): 21 | p["parse"] = "https://www.acfun.cn/v/%s" % p["parse"] 22 | 23 | if p["parse"].startswith("http"): 24 | aid = self.match(["ac(\d+)", "ac=(\d+)"], p["parse"]) 25 | 26 | if not aid: 27 | otype = "bangumi" 28 | html = self.curl({"url": p["parse"], "encoding": "utf8"}) 29 | 30 | vid = self.match(['"videoId":(\d+),', '"danmakuId":(\d+)'], html) 31 | image = self.match('"image":"([^"]+)",', html) 32 | title = self.match('"episodeName":"(.*?)"', html) 33 | 34 | else: 35 | otype = "video" 36 | html = self.curl({"url": p["parse"], "encoding": "utf8"}) 37 | vid = self.match('"currentVideoId":(\d+),', html) 38 | title = self.match( 39 | ["\