├── .gitattributes ├── .github └── workflows │ └── update-room-url.yml ├── .gitignore ├── 173.py ├── 17live.py ├── 2cq.py ├── 51lm.py ├── 95xiu.py ├── 9xiu.py ├── LICENSE ├── README.md ├── acfun.py ├── bigo.py ├── bilibili.py ├── cc.py ├── changyou.py ├── danmu ├── danmaku │ ├── __init__.py │ ├── acfun.proto │ ├── acfun.py │ ├── acfun_pb2.py │ ├── bilibili.py │ ├── cc.py │ ├── douyu.py │ ├── egame.py │ ├── huajiao.proto │ ├── huajiao.py │ ├── huajiao_pb2.py │ ├── huomao.py │ ├── huya.py │ ├── inke.py │ ├── kuaishou.proto │ ├── kuaishou.py │ ├── kuaishou_pb2.py │ ├── kugou.proto │ ├── kugou.py │ ├── kugou_pb2.py │ ├── laifeng.py │ ├── longzhu.py │ ├── look.py │ ├── pps.py │ ├── qf.py │ ├── tars │ │ ├── EndpointF.py │ │ ├── QueryF.py │ │ ├── __TimeoutQueue.py │ │ ├── __adapterproxy.py │ │ ├── __async.py │ │ ├── __init__.py │ │ ├── __logger.py │ │ ├── __packet.py │ │ ├── __rpc.py │ │ ├── __servantproxy.py │ │ ├── __tars.py │ │ ├── __trans.py │ │ ├── __tup.py │ │ ├── __util.py │ │ ├── core.py │ │ ├── exception.py │ │ └── tars │ │ │ ├── EndpointF.tars │ │ │ ├── QueryF.tars │ │ │ └── __init__.py │ ├── yqs.proto │ ├── yqs.py │ ├── yqs_pb2.py │ └── zhanqi.py └── main.py ├── data ├── .gitkeep ├── bilibili.json ├── bilibili.m3u ├── douyu.json ├── douyu.m3u ├── huya.json └── huya.m3u ├── douyin.py ├── douyu.py ├── egame.py ├── fengbolive.py ├── hongle.py ├── huajiao.py ├── huomao.py ├── huya-bak.py ├── huya.py ├── imifun.py ├── immomo.py ├── inke.py ├── iqiyi.js ├── iqiyi.py ├── ixigua.py ├── jd.py ├── kbs.py ├── kk.py ├── kuaishou.py ├── kugou.py ├── kuwo.py ├── laifeng.py ├── lehai.py ├── liveu.py ├── longzhu.py ├── look.py ├── maoer.py ├── migu.py ├── nodejs ├── .npmrc ├── all-m3u.js ├── bilibili-batch.js ├── danmu │ ├── bilibli.js │ ├── douyu │ │ ├── client.js │ │ ├── clientEvent.js │ │ ├── config.js │ │ ├── index.js │ │ ├── messageEvent.js │ │ ├── packet.js │ │ └── stt.js │ └── huya │ │ ├── client.js │ │ ├── index.js │ │ └── lib.js ├── douyu-batch.js ├── huya-batch.js ├── package.json ├── pnpm-lock.yaml ├── run-all.bat ├── run-all.sh └── utils │ └── utils.js ├── now.py ├── pps.py ├── ppsport.py ├── qf.py ├── qie.py ├── renren.py ├── requirements.txt ├── showself.py ├── sports_iqiyi.py ├── tiktok.py ├── tuho.py ├── twitch.py ├── v6cn.py ├── wali.py ├── woxiu.py ├── xunlei.py ├── yangshipin.py ├── yizhibo.py ├── youku.py ├── yuanbobo.py ├── yy.py ├── zhanqi.py └── zhibotv.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=python 2 | -------------------------------------------------------------------------------- /.github/workflows/update-room-url.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: update-room-url 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | #push: 9 | # branches: [ master ] 10 | #schedule: 11 | #- cron: '0 12 * * *' 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | python-version: ["3.10"] 27 | node-version: [ 16.x ] 28 | # Steps represent a sequence of tasks that will be executed as part of the job 29 | steps: 30 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 31 | - uses: actions/checkout@v3 32 | 33 | - name: Set up Python ${{ matrix.python-version }} 34 | uses: actions/setup-python@v3 35 | with: 36 | python-version: ${{ matrix.python-version }} 37 | 38 | - name: Use Node.js ${{ matrix.node-version }} 39 | uses: actions/setup-node@v2 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | registry-url: https://registry.npmjs.org 43 | #cache: 'npm' 44 | 45 | - name: setup and install python packages 46 | run: | 47 | pip install -r requirements.txt 48 | 49 | - name: install pnpm 50 | run: npm i pnpm -g 51 | 52 | - name: install nodejs dependences 53 | working-directory: ./nodejs 54 | run: | 55 | pnpm i --no-frozen-lockfile 56 | 57 | - name: update bilibili 58 | working-directory: ./nodejs 59 | run: node bilibili-batch.js 60 | 61 | - name: update douyu 62 | working-directory: ./nodejs 63 | run: node douyu-batch.js 64 | 65 | #- name: update huya 66 | # working-directory: ./nodejs 67 | # run: node huya-batch.js 68 | 69 | - name: merge m3u files 70 | working-directory: ./nodejs 71 | run: node all-m3u.js 72 | 73 | - name: Archive production artifacts 74 | uses: actions/upload-artifact@v3 75 | with: 76 | name: m3u data 77 | path: | 78 | data 79 | 80 | 81 | - name: pubish release 82 | uses: softprops/action-gh-release@v1 83 | if: startsWith(github.ref, 'refs/tags/') 84 | with: 85 | files: "data/**" 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GIT_PAT_TOKEN }} 88 | 89 | - name: Deploy to Vercel 90 | run: npx vercel --token ${VERCEL_TOKEN} --prod 91 | working-directory: ./data 92 | env: 93 | VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} 94 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 95 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 96 | 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | .idea/* 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | *.local.* 127 | node_modules 128 | 129 | .vscode 130 | .idea 131 | easyplayer 132 | vercel-deploy 133 | vercel-deploy-gh 134 | .vercel 135 | data/* 136 | 137 | local 138 | -------------------------------------------------------------------------------- /173.py: -------------------------------------------------------------------------------- 1 | # 艺气山直播:http://www.173.com/room/category?categoryId=11 2 | 3 | import requests 4 | 5 | 6 | class YQS: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | params = f'roomId={self.rid}' 13 | with requests.Session() as s: 14 | res = s.post('https://www.173.com/room/getVieoUrl', params=params).json() 15 | data = res['data'] 16 | if data: 17 | status = data['status'] 18 | if status == 2: 19 | return data['url'] 20 | else: 21 | raise Exception('未开播') 22 | else: 23 | raise Exception('直播间不存在') 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | yqs = YQS(rid) 29 | return yqs.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('输入艺气山直播房间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /17live.py: -------------------------------------------------------------------------------- 1 | # 获取17直播的真实流媒体地址,可能需要挂国外代理才行。 2 | # 17直播间链接形式:https://17.live/live/276480 3 | 4 | import requests 5 | 6 | 7 | class Live17: 8 | 9 | def __init__(self, rid): 10 | """ 11 | # 可能需要挂代理。 12 | # self.proxies = { 13 | # "http": "http://xxxx:1080", 14 | # "https": "http://xxxx:1080", 15 | # } 16 | Args: 17 | rid: 18 | """ 19 | self.rid = rid 20 | self.BASE_URL = 'https://api-dsa.17app.co/api/v1/lives/' 21 | 22 | def get_real_url(self): 23 | try: 24 | # res = requests.get(f'{self.BASE_URL}{self.rid}', proxies=self.proxies).json() 25 | res = requests.get(f'{self.BASE_URL}{self.rid}').json() 26 | real_url_default = res.get('rtmpUrls')[0].get('url') 27 | real_url_modify = real_url_default.replace('global-pull-rtmp.17app.co', 'china-pull-rtmp-17.tigafocus.com') 28 | real_url = [real_url_modify, real_url_default] 29 | except Exception: 30 | raise Exception('直播间不存在或未开播') 31 | return real_url 32 | 33 | 34 | def get_real_url(rid): 35 | try: 36 | live17 = Live17(rid) 37 | return live17.get_real_url() 38 | except Exception as e: 39 | print('Exception:', e) 40 | return False 41 | 42 | 43 | if __name__ == '__main__': 44 | r = input('请输入17直播房间号:\n') 45 | print(get_real_url(r)) 46 | -------------------------------------------------------------------------------- /2cq.py: -------------------------------------------------------------------------------- 1 | # 棉花糖直播:https://www.2cq.com/rank 2 | 3 | import requests 4 | 5 | 6 | class MHT: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | with requests.Session() as s: 13 | res = s.get(f'https://www.2cq.com/proxy/room/room/info?roomId={self.rid}&appId=1004') 14 | res = res.json() 15 | if res['status'] == 1: 16 | result = res['result'] 17 | if result['liveState'] == 1: 18 | real_url = result['pullUrl'] 19 | return real_url 20 | else: 21 | raise Exception('未开播') 22 | else: 23 | raise Exception('直播间可能不存在') 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | mht = MHT(rid) 29 | return mht.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('输入棉花糖直播房间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /51lm.py: -------------------------------------------------------------------------------- 1 | # 羚萌直播:https://live.51lm.tv/programs/Hot 2 | 3 | from urllib.parse import urlencode 4 | import requests 5 | import time 6 | import hashlib 7 | 8 | 9 | class LM: 10 | 11 | def __init__(self, rid): 12 | self.rid = rid 13 | self.BASE_URL = 'https://www.51lm.tv/live/room/info/basic' 14 | 15 | def get_real_url(self): 16 | roominfo = {'programId': self.rid} 17 | 18 | def g(d): 19 | return hashlib.md5(f'{d}#{urlencode(roominfo)}#Ogvbm2ZiKE'.encode('utf-8')).hexdigest() 20 | 21 | lminfo = { 22 | 'h': int(time.time()) * 1000, 23 | 'i': -246397986, 24 | 'o': 'iphone', 25 | 's': 'G_c17a64eff3f144a1a48d9f02e8d981c2', 26 | 't': 'H', 27 | 'v': '4.20.43', 28 | 'w': 'a710244508d3cc14f50d24e9fecc496a' 29 | } 30 | u = g(urlencode(lminfo)) 31 | lminfo = f'G={u}&{urlencode(lminfo)}' 32 | with requests.Session() as s: 33 | res = s.post(self.BASE_URL, json=roominfo, headers={'lminfo': lminfo}).json() 34 | code = res['code'] 35 | if code == 200: 36 | status = res['data']['isLiving'] 37 | if status == 'True': 38 | real_url = res['data']['playUrl'] 39 | return real_url 40 | else: 41 | raise Exception('未开播') 42 | elif code == -1: 43 | raise Exception('输入错误') 44 | elif code == 1201: 45 | raise Exception('直播间不存在') 46 | 47 | 48 | def get_real_url(rid): 49 | try: 50 | lm = LM(rid) 51 | return lm.get_real_url() 52 | except Exception as e: 53 | print('Exception:', e) 54 | return False 55 | 56 | 57 | if __name__ == '__main__': 58 | r = input('输入羚萌直播房间号:\n') 59 | print(get_real_url(r)) 60 | -------------------------------------------------------------------------------- /95xiu.py: -------------------------------------------------------------------------------- 1 | # 95秀:http://www.95.cn/ 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class JWXiu: 8 | 9 | def __init__(self, rid): 10 | self.rid = rid 11 | 12 | def get_real_url(self): 13 | with requests.Session() as s: 14 | res = s.get(f'https://www.95.cn/{self.rid}.html').text 15 | try: 16 | uid = re.search(r'"uid":(\d+),', res).group(1) 17 | status = re.search(r'"is_offline":"(\d)"', res).group(1) 18 | except AttributeError: 19 | raise Exception('没有找到直播间') 20 | if status == '0': 21 | real_url = f'https://play1.95xiu.com/app/{uid}.flv' 22 | return real_url 23 | else: 24 | raise Exception('未开播') 25 | 26 | 27 | def get_real_url(rid): 28 | try: 29 | jwx = JWXiu(rid) 30 | return jwx.get_real_url() 31 | except Exception as e: 32 | print('Exception:', e) 33 | return False 34 | 35 | 36 | if __name__ == '__main__': 37 | r = input('输入95秀房间号:\n') 38 | print(get_real_url(r)) 39 | -------------------------------------------------------------------------------- /9xiu.py: -------------------------------------------------------------------------------- 1 | # 九秀直播:https://www.9xiu.com/other/classify?tag=all&index=all 2 | 3 | import requests 4 | 5 | 6 | class JXiu: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | with requests.Session() as s: 13 | url = f'https://h5.9xiu.com/room/live/enterRoom?rid={self.rid}' 14 | headers = { 15 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) ' 16 | 'AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' 17 | } 18 | res = s.get(url, headers=headers).json() 19 | if res['code'] == 200: 20 | status = res['data']['status'] 21 | if status == 0: 22 | raise Exception('未开播') 23 | elif status == 1: 24 | live_url = res['data']['live_url'] 25 | return live_url 26 | else: 27 | raise Exception('直播间可能不存在') 28 | 29 | 30 | def get_real_url(rid): 31 | try: 32 | jx = JXiu(rid) 33 | return jx.get_real_url() 34 | except Exception as e: 35 | print('Exception:', e) 36 | return False 37 | 38 | 39 | if __name__ == '__main__': 40 | r = input('输入九秀直播房间号:\n') 41 | print(get_real_url(r)) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 自用自改 2 | 3 | - ~~使用`Node.js`动态获取分区房间列表: douyu、bilibili、huya~~ 该仓库的js版不再更新,请查看[ql-scripts](https://github.com/moxun33/ql-scripts)更新 4 | 5 | - 批量获取直播URL,添加房间名称、主播名称 6 | - 生成m3u文件 7 | - 修改部分`python`脚本 8 | - ~~在`Node.js`调用`python`脚本~~ 9 | - ~~`douyu、bilibili、huya`使用`js`解析~~ 10 | - 欢迎使用[vvibe](https://github.com/moxun33/vvibe)直播播放器 11 | 12 | # 免责声明:本仓库的m3u文件和js代码仅供学习交流使用,不得用于任何商业用途,数据来源于互联网公开内容,没有获取任何私有和有权限的信息(个人信息等)。由此引发的任何法律纠纷与本人无关 13 | 14 | --- 15 | 16 | # Real-Url 17 | 18 | ## 说明 19 | 20 | 没想到还有这么多朋友发 issue 和邮件咨询问题,感谢大家的支持🎈!因为有时很忙,回复和提交代码的周期会有点长,抱歉哦😋 21 | 22 | 这个仓库存放的是:获取一些直播平台真实流媒体地址(直播源)和弹幕的 Python 代码实现。获取的地址经测试,均可在 PotPlayer、VLC、DPlayer(flv.js + hls.js)等播放器中播放。 23 | 24 | > 🤘👌🤙🙏🐉👉 :如果该项目能帮助到您,欢迎 star 和 pr;或在您的项目中标注 Real-Url 为参考来源。 25 | 26 | 目前已实现: 27 | 28 | **59** 个直播平台的直播源获取:斗鱼直播、虎牙直播、哔哩哔哩直播、战旗直播、网易 CC 直播、火猫直播、企鹅电竞、YY 直播、一直播、快手直播、花椒直播、映客直播、西瓜直播、触手直播(已倒闭)、NOW 直播、抖音直播,爱奇艺直播、酷狗直播、龙珠直播、PPS 奇秀直播、六间房、17 直播、来疯直播、优酷轮播台、网易 LOOK 直播、千帆直播、陌陌直播、小米直播、迅雷直播、京东直播、企鹅体育、人人直播、棉花糖直播、九秀直播、羚萌直播、95秀、新浪疯播、红人直播、艾米直播、KK直播、酷我聚星、乐嗨直播、秀色直播、星光直播、我秀直播、热猫直播、艺气山直播、AcFun 直播、猫耳FM、畅秀阁、Twitch、TikTok、央视频、PP体育、zhibotv、腾讯体育直播、爱奇艺体育直播、liveU、bigolive、咪咕视频体育。 29 | 30 | **18** 个直播平台的弹幕获取:斗鱼直播、虎牙直播、哔哩哔哩直播、快手直播、火猫直播、企鹅电竞、花椒直播、映客直播、网易 CC 直播、酷狗直播、龙珠直播、PPS 奇秀、搜狐千帆、战旗直播、来疯直播、网易 LOOK 直播、AcFun 直播、艺气山直播。 31 | 32 | ## 运行 33 | 34 | 1. 项目使用了很简单的 Python 代码,仅在 Python 3 环境运行测试。 35 | 2. 具体所需模块请查看 requirements.txt 36 | 3. 获取斗鱼和爱奇艺的直播源,需 JavaScript 环境,可使用 node.js。爱奇艺直播里有个参数是加盐的 MD5,由仓库中的 iqiyi.js 生成。 37 | 4. 每个平台的直播源和弹幕获取功能相互独立,以后再整合。弹幕食用:python main.py 38 | 39 | ## 反馈 40 | 41 | 有直播平台失效或新增其他平台解析的,可发 [issue](https://github.com/wbt5/real-url/issues/new)。 42 | 43 | ## 更新 44 | 45 | 2021.11.7::sparkles:新增咪咕体育。 46 | 47 | 2021.8.15::sparkles:新增 liveU、bigolive。 48 | 49 | 2021.7.4::art:更新哔哩哔哩直播源;:bug:修复Acfun直播弹幕;:bug:修复企鹅电竞弹幕。 50 | 51 | 2021.6.20::sparkles:新增爱奇艺体育直播。 52 | 53 | 2021.6.13::bug:修复腾讯体育。 54 | 55 | 2021.6.12::bug:修复斗鱼直播。 56 | 57 | 2021.05.22::sparkles:新增腾讯体育直播。 58 | 59 | 2021.05.15::art:更新爱奇艺、:bug:修复战旗直播。 60 | 61 | 2021.05.13: :sparkles:新增 zhibotv。 62 | 63 | 2021.05.05::sparkles:新增 PP体育。 64 | 65 | 2021.05.03::sparkles:新增 央视频。 66 | 67 | 2021.05.02::sparkles:新增 Twitch、TikTok。 68 | 69 | 2021.05.01::sparkles:新增畅秀阁、猫耳FM。 70 | 71 | 2020.12.20:修复直播源:抖音、艺气山、花椒、快手、来疯、龙珠、PPS、人人直播、17live 可能需要挂代理。 72 | 73 | 2020.10.17:修复:西瓜直播、YY直播。 74 | 75 | 2020.09.26:更新:虎牙直播源;注释掉未完成的 YY 直播弹幕功能。 76 | 77 | 2020.09.12:新增:斗鱼添加一个从PC网页端获取直播源的方法,可选线路和清晰度;新增requirements.txt文件;更新代码。 78 | 79 | 2020.08.18:更新快手直播源,现在播放链接需要带参数;更新快手直播弹幕,直接用 protobuf 序列化;新增 AcFun、艺气山两个平台的弹幕功能。 80 | 81 | 2020.08.08:新增 AcFun 直播、艺气山直播;更新:哔哩哔哩直播、虎牙直播、红人直播;优化:斗鱼直播。 82 | 83 | 2020.07.31:新增 19 个直播平台,详见上面说明;更新YY直播,现在可以获取最高画质;优化战旗直播、优酷直播代码; 84 | 85 | 2020.07.25:新增网易 LOOK 直播弹幕获取;修复斗鱼直播源;新增陌陌直播源。 86 | 87 | 2020.07.19:新增来疯直播弹幕获取 88 | 89 | 2020.07.18:新增酷狗、龙珠、PPS奇秀、搜狐千帆、战旗直播等5个平台的弹幕获取 90 | 91 | 2020.07.11:新增网易CC直播弹幕获取 92 | 93 | 2020.07.05:新增花椒直播、映客直播弹幕获取;更新虎牙直播源 94 | 95 | 2020.06.25:新增🐧企鹅电竞弹幕获取 96 | 97 | 2020.06.19:新增火猫直播弹幕获取 98 | 99 | 2020.06.18:新增弹幕功能 100 | 101 | - 添加斗鱼、虎牙、哔哩哔哩和快手 4 个平台的弹幕获取。后续添加其他平台。 102 | - 实现弹幕功能的代码和思路主要来自:[danmaku](https://github.com/IsoaSFlus/danmaku) 和 [ks_barrage](https://github.com/py-wuhao/ks_barrage),感谢两位大佬! 103 | 104 | 2020.05.30:更新虎牙直播。 105 | 106 | 2020.05.25:更新哔哩哔哩直播。 107 | 108 | - 默认获取最高画质,不同清晰度取决于请求参数中的 qn。 109 | - 增加 .m3u8 格式播放链接的获取方法。 110 | 111 | 2020.05.23:更新17直播、虎牙直播 112 | 113 | 2020.05.19:更新火猫、快手、酷狗、PPS 114 | 115 | 2020.05.08:新增优酷轮播台、look 直播、千帆直播; 116 | 117 | - 新增优酷轮播台:优酷轮播台是优酷直播下的一个子栏目,轮播一些经典电影电视剧,个人感觉要比其他直播平台影视区的画质要好,而且没有平台水印和主播自己贴的乱七八糟的字幕遮挡。 118 | - 新增 LOOK 直播:LOOK 直播是网易云音乐旗下的直播平台。 119 | - 新增千帆直播:千帆直播是搜狐旗下的直播平台。 120 | 121 | 2020.05.01:新增优酷的来疯直播。 122 | 123 | 2020.04.30:新增17直播。 124 | 125 | 2020.04.24:修复虎牙、哔哩哔哩、快手、爱奇艺。 126 | 127 | 2020.02.26:更新一直播。 128 | 129 | 2020.01.18:更新抖音直播。 130 | 131 | 2020.01.10:新增酷狗直播、龙珠直播、PPS奇秀直播、六间房。 132 | 133 | 2020.01.09:新增爱奇艺直播。 134 | 135 | 2020.01.07:新增抖音直播;删除一个直播平台。 136 | 137 | 2020.01.03:修复快手直播,请求移动网页版。 138 | 139 | 2019.12.31:修复快手直播。 140 | 141 | 2019.12.07:修复哔哩哔哩直播。 142 | 143 | 2019.12.04:更新斗鱼直播,新增一种获取方式。 144 | 145 | 2019.11.24:新增收米直播。 146 | 147 | 2019.11.18:新增西瓜直播;触手直播;NOW直播。 148 | 149 | 2019.11.18:新增一直播;快手直播;花椒直播;映客直播。 150 | 151 | 2019.11.17:新增火猫直播;新增企鹅电竞;新增YY直播。 152 | 153 | 2019.11.16:新增战旗tv直播源;新增网易CC直播。 154 | 155 | 2019.11.09:新增哔哩哔哩直播源。 156 | 157 | 2019.11.03:新增虎牙直播源。 158 | 159 | 2019.11.02:修复斗鱼预览地址获取的方法;新增未开播房间的判断。 160 | 161 | ## 鸣谢 162 | 163 | 感谢 [JetBrains](https://www.jetbrains.com/?from=real-url) 提供的 free JetBrains Open Source license 164 | 165 | [![JetBrains-logo](https://i.loli.net/2020/10/03/E4h5FZmSfnGIgap.png)](https://www.jetbrains.com/?from=real-url) 166 | -------------------------------------------------------------------------------- /acfun.py: -------------------------------------------------------------------------------- 1 | # AcFun直播:https://live.acfun.cn/ 2 | # 默认最高画质 3 | 4 | import requests 5 | import json 6 | 7 | 8 | class AcFun: 9 | 10 | def __init__(self, rid): 11 | self.rid = rid 12 | 13 | def get_real_url(self): 14 | headers = { 15 | 'content-type': 'application/x-www-form-urlencoded', 16 | 'cookie': '_did=H5_', 17 | 'referer': 'https://m.acfun.cn/' 18 | } 19 | url = 'https://id.app.acfun.cn/rest/app/visitor/login' 20 | data = 'sid=acfun.api.visitor' 21 | with requests.Session() as s: 22 | res = s.post(url, data=data, headers=headers).json() 23 | userid = res['userId'] 24 | visitor_st = res['acfun.api.visitor_st'] 25 | 26 | url = 'https://api.kuaishouzt.com/rest/zt/live/web/startPlay' 27 | params = { 28 | 'subBiz': 'mainApp', 29 | 'kpn': 'ACFUN_APP', 30 | 'kpf': 'PC_WEB', 31 | 'userId': userid, 32 | 'did': 'H5_', 33 | 'acfun.api.visitor_st': visitor_st 34 | } 35 | data = f'authorId={self.rid}&pullStreamType=FLV' 36 | res = s.post(url, params=params, data=data, headers=headers).json() 37 | if res['result'] == 1: 38 | data = res['data'] 39 | videoplayres = json.loads(data['videoPlayRes']) 40 | liveadaptivemanifest, = videoplayres['liveAdaptiveManifest'] 41 | adaptationset = liveadaptivemanifest['adaptationSet'] 42 | representation = adaptationset['representation'][-1] 43 | real_url = representation['url'] 44 | return real_url 45 | else: 46 | raise Exception('直播已关闭') 47 | 48 | 49 | def get_real_url(rid): 50 | try: 51 | acfun = AcFun(rid) 52 | return acfun.get_real_url() 53 | except Exception as e: 54 | print('Exception:', e) 55 | return False 56 | 57 | 58 | if __name__ == '__main__': 59 | r = input('请输入AcFun直播房间号:\n') 60 | print(get_real_url(r)) 61 | -------------------------------------------------------------------------------- /bigo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/8/15 16:00 3 | # @Project: my-spiders 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | import requests 8 | 9 | 10 | class bigo: 11 | 12 | def __init__(self, rid): 13 | self.rid = rid 14 | 15 | def get_real_url(self): 16 | with requests.Session() as s: 17 | url = f'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo' 18 | res = s.post(url, data={'siteId': self.rid}).json() 19 | hls_src = res['data']['hls_src'] 20 | play_url = hls_src if hls_src else '不存在或未开播' 21 | return play_url 22 | 23 | 24 | def get_real_url(rid): 25 | try: 26 | url = bigo(rid) 27 | return url.get_real_url() 28 | except Exception as e: 29 | print('Exception:', e) 30 | return False 31 | 32 | 33 | if __name__ == '__main__': 34 | r = input('输入bigo直播房间号:\n') 35 | print(get_real_url(r)) 36 | -------------------------------------------------------------------------------- /bilibili.py: -------------------------------------------------------------------------------- 1 | # 获取哔哩哔哩直播的真实流媒体地址,默认获取直播间提供的最高画质 2 | # qn=150高清 3 | # qn=250超清 4 | # qn=400蓝光 5 | # qn=10000原画 6 | import requests 7 | import sys 8 | 9 | class BiliBili: 10 | 11 | def __init__(self, rid): 12 | """ 13 | 有些地址无法在PotPlayer播放,建议换个播放器试试 14 | Args: 15 | rid: 16 | """ 17 | rid = rid 18 | self.header = { 19 | 'User-Agent': 'Mozilla/5.0 (iPod; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 20 | 'like Gecko) CriOS/87.0.4280.163 Mobile/15E148 Safari/604.1', 21 | } 22 | # 先获取直播状态和真实房间号 23 | r_url = 'https://api.live.bilibili.com/room/v1/Room/room_init' 24 | param = { 25 | 'id': rid 26 | } 27 | with requests.Session() as self.s: 28 | res = self.s.get(r_url, headers=self.header, params=param).json() 29 | if res['msg'] == '直播间不存在': 30 | raise Exception(f'bilibili {rid} {res["msg"]}') 31 | live_status = res['data']['live_status'] 32 | if live_status != 1: 33 | raise Exception(f'bilibili {rid} 未开播') 34 | self.real_room_id = res['data']['room_id'] 35 | 36 | def get_real_url(self, current_qn: int = 10000) -> dict: 37 | url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo' 38 | param = { 39 | 'room_id': self.real_room_id, 40 | 'protocol': '0,1', 41 | 'format': '0,1,2', 42 | 'codec': '0,1', 43 | 'qn': current_qn, 44 | 'platform': 'web', 45 | 'ptype': 8, 46 | } 47 | res = self.s.get(url, headers=self.header, params=param).json() 48 | 49 | stream_info = res['data']['playurl_info']['playurl']['stream'] 50 | qn_max = 0 51 | 52 | for data in stream_info: 53 | accept_qn = data['format'][0]['codec'][0]['accept_qn'] 54 | for qn in accept_qn: 55 | qn_max = qn if qn > qn_max else qn_max 56 | if qn_max != current_qn: 57 | param['qn'] = qn_max 58 | res = self.s.get(url, headers=self.header, params=param).json() 59 | stream_info = res['data']['playurl_info']['playurl']['stream'] 60 | 61 | stream_urls = {} 62 | # flv流无法播放,暂修改成获取hls格式的流, 63 | for data in stream_info: 64 | format_name = data['format'][0]['format_name'] 65 | if format_name == 'ts': 66 | #print(data['format'][-1]) 67 | base_url = data['format'][-1]['codec'][0]['base_url'] 68 | url_info = data['format'][-1]['codec'][0]['url_info'] 69 | for i, info in enumerate(url_info): 70 | host = info['host'] 71 | extra = info['extra'] 72 | stream_urls[f'url{i + 1}'] = f'{host}{base_url}{extra}' 73 | break 74 | stream_urls['uid']=res['data']['uid'] 75 | return stream_urls 76 | 77 | 78 | def get_real_url(rid): 79 | try: 80 | bilibili = BiliBili(rid) 81 | return bilibili.get_real_url() 82 | except Exception as e: 83 | print('Exception:', e) 84 | return False 85 | 86 | 87 | if __name__ == '__main__': 88 | try: 89 | r=sys.argv[1] 90 | except: 91 | r = input('请输入bilibili直播房间号:\n') 92 | print(get_real_url(r)) 93 | -------------------------------------------------------------------------------- /cc.py: -------------------------------------------------------------------------------- 1 | # 获取网易CC的真实流媒体地址。 2 | # 默认为最高画质 3 | 4 | import requests 5 | 6 | 7 | class CC: 8 | 9 | def __init__(self, rid): 10 | self.rid = rid 11 | 12 | def get_real_url(self): 13 | room_url = f'https://api.cc.163.com/v1/activitylives/anchor/lives?anchor_ccid={self.rid}' 14 | response = requests.get(url=room_url).json() 15 | data = response.get('data', 0) 16 | if data: 17 | channel_id = data.get(f'{self.rid}').get('channel_id', 0) 18 | if channel_id: 19 | response = requests.get(f'https://cc.163.com/live/channel/?channelids={channel_id}').json() 20 | real_url = response.get('data')[0].get('sharefile') 21 | else: 22 | raise Exception('直播间不存在') 23 | else: 24 | raise Exception('输入错误') 25 | return real_url 26 | 27 | 28 | def get_real_url(rid): 29 | try: 30 | cc = CC(rid) 31 | return cc.get_real_url() 32 | except Exception as e: 33 | print('Exception:', e) 34 | return False 35 | 36 | 37 | if __name__ == '__main__': 38 | r = input('请输入网易CC直播房间号:\n') 39 | print(get_real_url(r)) 40 | -------------------------------------------------------------------------------- /changyou.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/5/1 17:40 3 | # @Project: real-url 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | import json 8 | 9 | import requests 10 | 11 | 12 | class ChangYou: 13 | 14 | def __init__(self, rid): 15 | self.rid = rid 16 | 17 | def get_real_url(self): 18 | headers = { 19 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 20 | 'like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 ' 21 | } 22 | url = 'http://cxg.changyou.com/landingpage/getstreamname.action?roomid={}'.format(self.rid) 23 | with requests.Session() as s: 24 | res = s.get(url, headers=headers).json() 25 | try: 26 | code = res['code'] 27 | if code == 'error': 28 | return res['msg'] 29 | else: 30 | stream = res['obj']['stream'] 31 | url = 'http://pull.wscdn.cxg.changyou.com/show/{}.flv'.format(stream) 32 | return url 33 | except json.decoder.JSONDecodeError: 34 | return '输入错误' 35 | 36 | 37 | def get_real_url(rid): 38 | try: 39 | cxg = ChangYou(rid) 40 | return cxg.get_real_url() 41 | except Exception as e: 42 | print('Exception:', e) 43 | return False 44 | 45 | 46 | if __name__ == '__main__': 47 | r = input('请输入畅秀阁roomid:\n') 48 | print(get_real_url(r)) 49 | -------------------------------------------------------------------------------- /danmu/danmaku/bilibili.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | from struct import pack, unpack 4 | import aiohttp 5 | import zlib 6 | 7 | 8 | class Bilibili: 9 | wss_url = 'wss://broadcastlv.chat.bilibili.com/sub' 10 | heartbeat = b'\x00\x00\x00\x1f\x00\x10\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x5b\x6f\x62\x6a\x65\x63\x74\x20' \ 11 | b'\x4f\x62\x6a\x65\x63\x74\x5d ' 12 | heartbeatInterval = 60 13 | 14 | @staticmethod 15 | async def get_ws_info(url): 16 | url = 'https://api.live.bilibili.com/room/v1/Room/room_init?id=' + url.split('/')[-1] 17 | reg_datas = [] 18 | async with aiohttp.ClientSession() as session: 19 | async with session.get(url) as resp: 20 | room_json = json.loads(await resp.text()) 21 | room_id = room_json['data']['room_id'] 22 | data = json.dumps({ 23 | 'roomid': room_id, 24 | 'uid': int(1e14 + 2e14 * random.random()), 25 | 'protover': 1 26 | }, separators=(',', ':')).encode('ascii') 27 | data = (pack('>i', len(data) + 16) + b'\x00\x10\x00\x01' + 28 | pack('>i', 7) + pack('>i', 1) + data) 29 | reg_datas.append(data) 30 | 31 | return Bilibili.wss_url, reg_datas 32 | 33 | @staticmethod 34 | def decode_msg(data): 35 | dm_list_compressed = [] 36 | dm_list = [] 37 | ops = [] 38 | msgs = [] 39 | while True: 40 | try: 41 | packetLen, headerLen, ver, op, seq = unpack('!IHHII', data[0:16]) 42 | except Exception as e: 43 | break 44 | if len(data) < packetLen: 45 | break 46 | if ver == 1 or ver == 0: 47 | ops.append(op) 48 | dm_list.append(data[16:packetLen]) 49 | elif ver == 2: 50 | dm_list_compressed.append(data[16:packetLen]) 51 | if len(data) == packetLen: 52 | data = b'' 53 | break 54 | else: 55 | data = data[packetLen:] 56 | 57 | for dm in dm_list_compressed: 58 | d = zlib.decompress(dm) 59 | while True: 60 | try: 61 | packetLen, headerLen, ver, op, seq = unpack('!IHHII', d[0:16]) 62 | except Exception as e: 63 | break 64 | if len(d) < packetLen: 65 | break 66 | ops.append(op) 67 | dm_list.append(d[16:packetLen]) 68 | if len(d) == packetLen: 69 | d = b'' 70 | break 71 | else: 72 | d = d[packetLen:] 73 | 74 | for i, d in enumerate(dm_list): 75 | try: 76 | msg = {} 77 | if ops[i] == 5: 78 | j = json.loads(d) 79 | msg['msg_type'] = { 80 | 'SEND_GIFT': 'gift', 81 | 'DANMU_MSG': 'danmaku', 82 | 'WELCOME': 'enter', 83 | 'NOTICE_MSG': 'broadcast', 84 | 'LIVE_INTERACTIVE_GAME': 'interactive_danmaku' # 新增互动弹幕,经测试与弹幕内容一致 85 | }.get(j.get('cmd'), 'other') 86 | 87 | # 2021-06-03 bilibili 字段更新, 形如 DANMU_MSG:4:0:2:2:2:0 88 | if msg.get('msg_type', 'UNKNOWN').startswith('DANMU_MSG'): 89 | msg['msg_type'] = 'danmaku' 90 | 91 | if msg['msg_type'] == 'danmaku': 92 | msg['name'] = (j.get('info', ['', '', ['', '']])[2][1] 93 | or j.get('data', {}).get('uname', '')) 94 | msg['content'] = j.get('info', ['', ''])[1] 95 | elif msg['msg_type'] == 'interactive_danmaku': 96 | msg['name'] = j.get('data', {}).get('uname', '') 97 | msg['content'] = j.get('data', {}).get('msg', '') 98 | elif msg['msg_type'] == 'broadcast': 99 | msg['type'] = j.get('msg_type', 0) 100 | msg['roomid'] = j.get('real_roomid', 0) 101 | msg['content'] = j.get('msg_common', 'none') 102 | msg['raw'] = j 103 | else: 104 | msg['content'] = j 105 | else: 106 | msg = {'name': '', 'content': d, 'msg_type': 'other'} 107 | msgs.append(msg) 108 | except Exception as e: 109 | pass 110 | 111 | return msgs 112 | -------------------------------------------------------------------------------- /danmu/danmaku/douyu.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import aiohttp 4 | from struct import pack 5 | 6 | 7 | class Douyu: 8 | wss_url = 'wss://danmuproxy.douyu.com:8503/' 9 | heartbeat = b'\x14\x00\x00\x00\x14\x00\x00\x00\xb1\x02\x00\x00\x74\x79\x70\x65\x40\x3d\x6d\x72\x6b\x6c' \ 10 | b'\x2f\x00 ' 11 | heartbeatInterval = 60 12 | 13 | @staticmethod 14 | async def get_ws_info(url): 15 | room_id = url.split('/')[-1] 16 | async with aiohttp.ClientSession() as session: 17 | async with session.get('https://m.douyu.com/' + str(room_id)) as resp: 18 | room_page = await resp.text() 19 | room_id = re.findall(r'"rid":(\d{1,8})', room_page)[0] 20 | reg_datas = [] 21 | data = f'type@=loginreq/roomid@={room_id}/' 22 | s = pack('i', 9 + len(data)) * 2 23 | s += b'\xb1\x02\x00\x00' # 689 24 | s += data.encode('ascii') + b'\x00' 25 | reg_datas.append(s) 26 | data = f'type@=joingroup/rid@={room_id}/gid@=-9999/' 27 | s = pack('i', 9 + len(data)) * 2 28 | s += b'\xb1\x02\x00\x00' # 689 29 | s += data.encode('ascii') + b'\x00' 30 | reg_datas.append(s) 31 | return Douyu.wss_url, reg_datas 32 | 33 | @staticmethod 34 | def decode_msg(data): 35 | msgs = [] 36 | for msg in re.findall(b'(type@=.*?)\x00', data): 37 | try: 38 | msg = msg.replace(b'@=', b'":"').replace(b'/', b'","') 39 | msg = msg.replace(b'@A', b'@').replace(b'@S', b'/') 40 | msg = json.loads((b'{"' + msg[:-2] + b'}').decode('utf8', 'ignore')) 41 | msg['name'] = msg.get('nn', '') 42 | msg['content'] = msg.get('txt', '') 43 | msg['msg_type'] = {'dgb': 'gift', 'chatmsg': 'danmaku', 44 | 'uenter': 'enter'}.get(msg['type'], 'other') 45 | msgs.append(msg) 46 | except Exception as e: 47 | pass 48 | return msgs 49 | -------------------------------------------------------------------------------- /danmu/danmaku/huajiao.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package HuaJiaoPack; 3 | 4 | message Message { 5 | required uint32 msgid = 1; 6 | required uint64 sn = 2; 7 | optional string sender = 3; 8 | optional string receiver = 4; 9 | optional string receiver_type = 5; 10 | optional Request req = 6; 11 | optional Response resp = 7; 12 | optional Notify notify = 8; 13 | optional string sender_type = 12; 14 | 15 | message Request { 16 | optional LoginReq login = 2; 17 | optional InitLoginReq init_login_req = 9; 18 | optional Service_Req service_req = 11; 19 | 20 | message LoginReq { 21 | required string mobile_type = 1; 22 | required uint32 net_type = 2; 23 | required string server_ram = 3; 24 | optional bytes secret_ram = 4; 25 | optional uint32 app_id = 5[default = 2000]; 26 | optional string platform = 8; 27 | optional string verf_code = 9; 28 | optional bool not_encrypt = 10; 29 | } 30 | 31 | message InitLoginReq { 32 | required string client_ram = 1; 33 | optional string sig = 2; 34 | } 35 | 36 | message Service_Req { 37 | required uint32 service_id = 1; 38 | required bytes request = 2; 39 | } 40 | } 41 | 42 | message Response { 43 | optional LoginResp login = 3; 44 | optional ChatResp chat = 4; 45 | optional InitLoginResp init_login_resp = 10; 46 | optional Service_Resp service_resp = 12; 47 | 48 | message LoginResp { 49 | required uint32 timestamp = 1; 50 | required string session_id = 2; 51 | required string session_key = 3; 52 | optional string client_login_ip = 4; 53 | optional string serverip = 5; 54 | } 55 | 56 | message InitLoginResp { 57 | required string client_ram = 1; 58 | required string server_ram = 2; 59 | } 60 | 61 | message Service_Resp { 62 | required uint32 service_id = 1; 63 | required bytes response = 2; 64 | } 65 | 66 | message ChatResp { 67 | required uint32 result = 1; 68 | optional uint32 body_id = 2; 69 | } 70 | } 71 | 72 | message Notify { 73 | optional NewMessageNotify newinfo_ntf = 1; 74 | 75 | message NewMessageNotify { 76 | required string info_type = 1; 77 | optional bytes info_content = 2; 78 | optional int64 info_id = 3; 79 | optional uint32 query_after_seconds = 4; 80 | } 81 | 82 | } 83 | 84 | } 85 | 86 | message ChatRoomPacket { 87 | required bytes roomid = 1; 88 | optional ChatRoomUpToServer to_server_data = 2; 89 | optional ChatRoomDownToUser to_user_data = 3; 90 | optional string uuid = 4; 91 | optional uint64 client_sn = 5; 92 | optional uint32 appid = 6; 93 | 94 | message ChatRoomUpToServer { 95 | required uint32 payloadtype = 1; 96 | optional ApplyJoinChatRoomRequest applyjoinchatroomreq = 4; 97 | 98 | message ApplyJoinChatRoomRequest { 99 | required bytes roomid = 1; 100 | optional ChatRoom room = 2; 101 | optional int32 userid_type = 3; 102 | } 103 | } 104 | 105 | message ChatRoomDownToUser { 106 | required int32 result = 1; 107 | required uint32 payloadtype = 2; 108 | optional CreateChatRoomResponse createchatroomresp = 3; 109 | optional ApplyJoinChatRoomResponse applyjoinchatroomresp = 5; 110 | optional QuitChatRoomResponse quitchatroomresp = 6; 111 | optional ChatRoomNewMsg newmsgnotify = 13; 112 | optional MemberJoinChatRoomNotify memberjoinnotify = 16; 113 | optional MemberQuitChatRoomNotify memberquitnotify = 17; 114 | repeated ChatRoomMNotify multinotify = 200; 115 | 116 | 117 | message CreateChatRoomResponse { 118 | optional ChatRoom room = 1; 119 | } 120 | 121 | message ApplyJoinChatRoomResponse { 122 | optional ChatRoom room = 1; 123 | } 124 | 125 | message QuitChatRoomResponse { 126 | optional ChatRoom room = 1; 127 | } 128 | 129 | message ChatRoomNewMsg { 130 | required bytes roomid = 1; 131 | optional CRUser sender = 2; 132 | optional int32 msgtype = 3; 133 | optional bytes msgcontent = 4; 134 | optional int32 regmemcount = 5; 135 | optional int32 memcount = 6; 136 | optional uint32 msgid = 7; 137 | optional uint32 maxid = 8; 138 | optional uint64 timestamp = 9; 139 | } 140 | 141 | message MemberJoinChatRoomNotify { 142 | required ChatRoom room = 1; 143 | } 144 | 145 | message MemberQuitChatRoomNotify { 146 | required ChatRoom room = 1; 147 | } 148 | 149 | message ChatRoomMNotify { 150 | required int32 type = 1; 151 | required bytes data = 2; 152 | optional int32 regmemcount = 3; 153 | optional int32 memcount = 4; 154 | } 155 | } 156 | } 157 | 158 | message ChatRoom { 159 | required bytes roomid = 1; 160 | repeated CRPair properties = 8; 161 | repeated CRUser members = 9; 162 | optional bytes partnerdata = 13; 163 | } 164 | 165 | message CRUser { 166 | optional bytes userid = 1; 167 | optional string name = 2; 168 | optional bytes userdata = 6; 169 | } 170 | 171 | message CRPair { 172 | required string key = 1; 173 | optional bytes value = 2; 174 | } -------------------------------------------------------------------------------- /danmu/danmaku/huomao.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import aiohttp 3 | import json 4 | 5 | 6 | class HuoMao: 7 | heartbeat = b'\x00\x00\x00\x10\x00\x10\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01' 8 | # heartbeat = struct.pack('!ihhii', 16,16,1,2,1) 9 | heartbeatInterval = 30 10 | 11 | @staticmethod 12 | async def get_ws_info(url): 13 | goim = 'http://www.huomao.com/ajax/goimConf?type=h5' 14 | headers = { 15 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, ' 16 | 'like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'} 17 | async with aiohttp.ClientSession() as session: 18 | async with session.get(goim, headers=headers) as resp: 19 | info = json.loads(await resp.text()) 20 | webSocketUrls = info.get('host_wss', 0) 21 | rid = int(url.split('/')[-1]) 22 | reg_datas = [] 23 | tokenBody = json.dumps({"Uid": 0, "Rid": rid}, separators=(',', ':')) 24 | bodyBuf = tokenBody.encode('ascii') 25 | headerBuf = struct.pack('!ihhii', (16 + len(bodyBuf)), 16, 1, 7, 1) 26 | data = headerBuf + bodyBuf 27 | reg_datas.append(data) 28 | return webSocketUrls, reg_datas 29 | 30 | @staticmethod 31 | def decode_msg(data): 32 | packetLen, headerLen, ver, op, seq = struct.unpack('!ihhii', data[0:16]) 33 | msgs = [] 34 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 35 | if op == 5: 36 | offset = 0 37 | while offset < len(data): 38 | packetLen, headerLen, ver = struct.unpack('!ihh', data[offset:(offset + 8)]) 39 | msgBody = data[offset + headerLen:offset + packetLen] 40 | offset += packetLen 41 | body = json.loads(msgBody.decode('utf8')) 42 | if body.get('code', 0) == '100001': 43 | msg['name'] = body['speak']['user']['name'] 44 | msg['content'] = body['speak']['barrage']['msg'] 45 | msg['msg_type'] = 'danmaku' 46 | msgs.append(msg.copy()) 47 | return msgs 48 | msgs.append(msg) 49 | return msgs 50 | -------------------------------------------------------------------------------- /danmu/danmaku/huya.py: -------------------------------------------------------------------------------- 1 | import re 2 | import aiohttp 3 | from .tars import tarscore 4 | 5 | 6 | class Huya: 7 | wss_url = 'wss://cdnws.api.huya.com/' 8 | heartbeat = b'\x00\x03\x1d\x00\x00\x69\x00\x00\x00\x69\x10\x03\x2c\x3c\x4c\x56\x08\x6f\x6e\x6c\x69\x6e\x65\x75' \ 9 | b'\x69\x66\x0f\x4f\x6e\x55\x73\x65\x72\x48\x65\x61\x72\x74\x42\x65\x61\x74\x7d\x00\x00\x3c\x08\x00' \ 10 | b'\x01\x06\x04\x74\x52\x65\x71\x1d\x00\x00\x2f\x0a\x0a\x0c\x16\x00\x26\x00\x36\x07\x61\x64\x72\x5f' \ 11 | b'\x77\x61\x70\x46\x00\x0b\x12\x03\xae\xf0\x0f\x22\x03\xae\xf0\x0f\x3c\x42\x6d\x52\x02\x60\x5c\x60' \ 12 | b'\x01\x7c\x82\x00\x0b\xb0\x1f\x9c\xac\x0b\x8c\x98\x0c\xa8\x0c ' 13 | heartbeatInterval = 60 14 | 15 | @staticmethod 16 | async def get_ws_info(url): 17 | reg_datas = [] 18 | url = 'https://m.huya.com/' + url.split('/')[-1] 19 | headers = { 20 | 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, ' 21 | 'like Gecko) Chrome/79.0.3945.88 Mobile Safari/537.36'} 22 | async with aiohttp.ClientSession() as session: 23 | async with session.get(url, headers=headers) as resp: 24 | room_page = await resp.text() 25 | m = re.search(r"lYyid\":([0-9]+)", room_page, re.MULTILINE) 26 | ayyuid = m.group(1) 27 | m = re.search(r"lChannelId\":([0-9]+)", room_page, re.MULTILINE) 28 | tid = m.group(1) 29 | m = re.search(r"lSubChannelId\":([0-9]+)", room_page, re.MULTILINE) 30 | sid = m.group(1) 31 | 32 | oos = tarscore.TarsOutputStream() 33 | oos.write(tarscore.int64, 0, int(ayyuid)) 34 | oos.write(tarscore.boolean, 1, True) # Anonymous 35 | oos.write(tarscore.string, 2, "") # sGuid 36 | oos.write(tarscore.string, 3, "") 37 | oos.write(tarscore.int64, 4, int(tid)) 38 | oos.write(tarscore.int64, 5, int(sid)) 39 | oos.write(tarscore.int64, 6, 0) 40 | oos.write(tarscore.int64, 7, 0) 41 | 42 | wscmd = tarscore.TarsOutputStream() 43 | wscmd.write(tarscore.int32, 0, 1) 44 | wscmd.write(tarscore.bytes, 1, oos.getBuffer()) 45 | 46 | reg_datas.append(wscmd.getBuffer()) 47 | return Huya.wss_url, reg_datas 48 | 49 | @staticmethod 50 | def decode_msg(data): 51 | class user(tarscore.struct): 52 | def readFrom(ios): 53 | return ios.read(tarscore.string, 2, False).decode('utf8') 54 | 55 | name = '' 56 | content = '' 57 | msgs = [] 58 | ios = tarscore.TarsInputStream(data) 59 | 60 | if ios.read(tarscore.int32, 0, False) == 7: 61 | ios = tarscore.TarsInputStream(ios.read(tarscore.bytes, 1, False)) 62 | if ios.read(tarscore.int64, 1, False) == 1400: 63 | ios = tarscore.TarsInputStream(ios.read(tarscore.bytes, 2, False)) 64 | name = ios.read(user, 0, False) # username 65 | content = ios.read(tarscore.string, 3, False).decode('utf8') # content 66 | 67 | if name != '': 68 | msg = {'name': name, 'content': content, 'msg_type': 'danmaku'} 69 | else: 70 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 71 | msgs.append(msg) 72 | return msgs 73 | -------------------------------------------------------------------------------- /danmu/danmaku/inke.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import re 3 | import time 4 | import json 5 | 6 | 7 | class Inke: 8 | heartbeat = None 9 | 10 | @staticmethod 11 | async def get_ws_info(url): 12 | uid = re.search(r'uid=(\d+)', url).group(1) 13 | roomid = id = re.search(r'&id=(\d+)', url).group(1) 14 | t = int(time.time() * 1e3) 15 | cr = 'https://chatroom.inke.cn/url?roomid={}&uid={}&id={}&access_from=pc_web&_t={}'.format(roomid, uid, id, t) 16 | async with aiohttp.ClientSession() as session: 17 | async with session.get(cr) as resp: 18 | res = await resp.text() 19 | wss_url = json.loads(res).get('url') 20 | return wss_url, None 21 | 22 | @staticmethod 23 | def decode_msg(data): 24 | msgs = [] 25 | name = content = '' 26 | msg_type = 'other' 27 | message = json.loads(data) 28 | ms = message['ms'] 29 | c = ms[-1].get('c', 0) 30 | if c: 31 | tp = ms[-1].get('tp', 0) 32 | if tp == 'pub' or tp == 'color': 33 | name = ms[0].get('from').get('nic', '') 34 | elif tp == 'user_join_tip': 35 | name = ms[0].get('u').get('nic', '') 36 | else: 37 | name = 'sys' 38 | content = c 39 | msg_type = 'danmaku' 40 | msg = {'name': name, 'content': content, 'msg_type': msg_type} 41 | msgs.append(msg) 42 | return msgs 43 | -------------------------------------------------------------------------------- /danmu/danmaku/kuaishou.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package KuaiShouPack; 3 | 4 | message CSWebHeartbeat { 5 | optional uint64 timestamp = 1; 6 | } 7 | 8 | message SocketMessage { 9 | optional PayloadType payloadType = 1; 10 | optional CompressionType compressionType = 2; 11 | optional bytes payload = 3; 12 | 13 | enum CompressionType { 14 | UNKNOWN = 0; 15 | NONE = 1; 16 | GZIP = 2; 17 | AES = 3; 18 | } 19 | } 20 | 21 | enum PayloadType { 22 | UNKNOWN = 0; 23 | CS_HEARTBEAT = 1; 24 | CS_ERROR = 3; 25 | CS_PING = 4; 26 | PS_HOST_INFO = 51; 27 | SC_HEARTBEAT_ACK = 101; 28 | SC_ECHO = 102; 29 | SC_ERROR = 103; 30 | SC_PING_ACK = 104; 31 | SC_INFO = 105; 32 | CS_ENTER_ROOM = 200; 33 | CS_USER_PAUSE = 201; 34 | CS_USER_EXIT = 202; 35 | CS_AUTHOR_PUSH_TRAFFIC_ZERO = 203; 36 | CS_HORSE_RACING = 204; 37 | CS_RACE_LOSE = 205; 38 | CS_VOIP_SIGNAL = 206; 39 | SC_ENTER_ROOM_ACK = 300; 40 | SC_AUTHOR_PAUSE = 301; 41 | SC_AUTHOR_RESUME = 302; 42 | SC_AUTHOR_PUSH_TRAFFIC_ZERO = 303; 43 | SC_AUTHOR_HEARTBEAT_MISS = 304; 44 | SC_PIP_STARTED = 305; 45 | SC_PIP_ENDED = 306; 46 | SC_HORSE_RACING_ACK = 307; 47 | SC_VOIP_SIGNAL = 308; 48 | SC_FEED_PUSH = 310; 49 | SC_ASSISTANT_STATUS = 311; 50 | SC_REFRESH_WALLET = 312; 51 | SC_LIVE_CHAT_CALL = 320; 52 | SC_LIVE_CHAT_CALL_ACCEPTED = 321; 53 | SC_LIVE_CHAT_CALL_REJECTED = 322; 54 | SC_LIVE_CHAT_READY = 323; 55 | SC_LIVE_CHAT_GUEST_END = 324; 56 | SC_LIVE_CHAT_ENDED = 325; 57 | SC_RENDERING_MAGIC_FACE_DISABLE = 326; 58 | SC_RENDERING_MAGIC_FACE_ENABLE = 327; 59 | SC_RED_PACK_FEED = 330; 60 | SC_LIVE_WATCHING_LIST = 340; 61 | SC_LIVE_QUIZ_QUESTION_ASKED = 350; 62 | SC_LIVE_QUIZ_QUESTION_REVIEWED = 351; 63 | SC_LIVE_QUIZ_SYNC = 352; 64 | SC_LIVE_QUIZ_ENDED = 353; 65 | SC_LIVE_QUIZ_WINNERS = 354; 66 | SC_SUSPECTED_VIOLATION = 355; 67 | SC_SHOP_OPENED = 360; 68 | SC_SHOP_CLOSED = 361; 69 | SC_GUESS_OPENED = 370; 70 | SC_GUESS_CLOSED = 371; 71 | SC_PK_INVITATION = 380; 72 | SC_PK_STATISTIC = 381; 73 | SC_RIDDLE_OPENED = 390; 74 | SC_RIDDLE_CLOESED = 391; 75 | SC_RIDE_CHANGED = 412; 76 | SC_BET_CHANGED = 441; 77 | SC_BET_CLOSED = 442; 78 | SC_LIVE_SPECIAL_ACCOUNT_CONFIG_STATE = 645; 79 | } 80 | 81 | message CSWebEnterRoom { 82 | optional string token = 1; 83 | optional string liveStreamId = 2; 84 | optional uint32 reconnectCount = 3; 85 | optional uint32 lastErrorCode = 4; 86 | optional string expTag = 5; 87 | optional string attach = 6; 88 | optional string pageId = 7; 89 | } 90 | 91 | message SCWebFeedPush { 92 | optional string displayWatchingCount = 1; 93 | optional string displayLikeCount = 2; 94 | optional uint64 pendingLikeCount = 3; 95 | optional uint64 pushInterval = 4; 96 | repeated WebCommentFeed commentFeeds = 5; 97 | optional string commentCursor = 6; 98 | repeated WebComboCommentFeed comboCommentFeed = 7; 99 | repeated WebLikeFeed likeFeeds = 8; 100 | repeated WebGiftFeed giftFeeds = 9; 101 | optional string giftCursor = 10; 102 | repeated WebSystemNoticeFeed systemNoticeFeeds = 11; 103 | repeated WebShareFeed shareFeeds = 12; 104 | 105 | } 106 | 107 | message WebCommentFeed { 108 | optional string id = 1; 109 | optional SimpleUserInfo user = 2; 110 | optional string content = 3; 111 | optional string deviceHash = 4; 112 | optional uint64 sortRank = 5; 113 | optional string color = 6; 114 | optional WebCommentFeedShowType showType = 7; 115 | } 116 | 117 | message SimpleUserInfo { 118 | optional string principalId = 1; 119 | optional string userName = 2; 120 | optional string headUrl = 3; 121 | } 122 | 123 | enum WebCommentFeedShowType { 124 | FEED_SHOW_UNKNOWN = 0; 125 | FEED_SHOW_NORMAL = 1; 126 | FEED_HIDDEN = 2; 127 | } 128 | 129 | message WebComboCommentFeed { 130 | optional string id = 1; 131 | optional string content = 2; 132 | optional uint32 comboCount = 3; 133 | } 134 | 135 | message WebLikeFeed { 136 | optional string id = 1; 137 | optional SimpleUserInfo user = 2; 138 | optional uint64 sortRank = 3; 139 | optional string deviceHash = 4; 140 | } 141 | 142 | message WebGiftFeed { 143 | optional string id = 1; 144 | optional SimpleUserInfo user = 2; 145 | optional uint64 time = 3; 146 | optional uint32 giftId = 4; 147 | optional uint64 sortRank = 5; 148 | optional string mergeKey = 6; 149 | optional uint32 batchSize = 7; 150 | optional uint32 comboCount = 8; 151 | optional uint32 rank = 9; 152 | optional uint64 expireDuration = 10; 153 | optional uint64 clientTimestamp = 11; 154 | optional uint64 slotDisplayDuration = 12; 155 | optional uint32 starLevel = 13; 156 | optional StyleType styleType = 14; 157 | optional WebLiveAssistantType liveAssistantType = 15; 158 | optional string deviceHash = 16; 159 | optional bool danmakuDisplay = 17; 160 | 161 | enum StyleType { 162 | UNKNOWN_STYLE = 0; 163 | BATCH_STAR_0 = 1; 164 | BATCH_STAR_1 = 2; 165 | BATCH_STAR_2 = 3; 166 | BATCH_STAR_3 = 4; 167 | BATCH_STAR_4 = 5; 168 | BATCH_STAR_5 = 6; 169 | BATCH_STAR_6 = 7; 170 | } 171 | } 172 | 173 | enum WebLiveAssistantType { 174 | UNKNOWN_ASSISTANT_TYPE = 0; 175 | SUPER = 1; 176 | JUNIOR = 2; 177 | } 178 | 179 | message WebSystemNoticeFeed { 180 | optional string id = 1; 181 | optional SimpleUserInfo user = 2; 182 | optional uint64 time = 3; 183 | optional string content = 4; 184 | optional uint64 displayDuration = 5; 185 | optional uint64 sortRank = 6; 186 | optional DisplayType displayType = 7; 187 | 188 | enum DisplayType { 189 | UNKNOWN_DISPLAY_TYPE = 0; 190 | COMMENT = 1; 191 | ALERT = 2; 192 | TOAST = 3; 193 | } 194 | } 195 | 196 | message WebShareFeed { 197 | optional string id = 1; 198 | optional SimpleUserInfo user = 2; 199 | optional uint64 time = 3; 200 | optional uint32 thirdPartyPlatform = 4; 201 | optional uint64 sortRank = 5; 202 | optional WebLiveAssistantType liveAssistantType = 6; 203 | optional string deviceHash = 7; 204 | } 205 | -------------------------------------------------------------------------------- /danmu/danmaku/kuaishou.py: -------------------------------------------------------------------------------- 1 | from . import kuaishou_pb2 as pb 2 | import aiohttp 3 | import re 4 | import json 5 | import time 6 | import random 7 | 8 | 9 | class KuaiShou: 10 | heartbeatInterval = 20 11 | 12 | @staticmethod 13 | async def get_ws_info(url): 14 | """获取wss连接信息 15 | Args: 16 | 直播间完整地址 17 | Returns: 18 | webSocketUrls:wss地址 19 | data:第一次send数据 20 | liveStreamId: 21 | token: 22 | page_id: 23 | :param url: 24 | """ 25 | rid = url.split('/')[-1] 26 | url = 'https://m.gifshow.com/fw/live/' + str(rid) # 移动版直播间地址 27 | headers = { 28 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, ' 29 | 'like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', 30 | 'Cookie': 'did=web_d563dca728d28b00336877723e0359ed'} # 请求失败则更换cookie中的did字段 31 | async with aiohttp.ClientSession() as session: 32 | async with session.get(url, headers=headers) as resp: 33 | res = await resp.text() 34 | 35 | wsfeedinfo = re.search(r'wsFeedInfo":(.*),"liveExist', res) 36 | if wsfeedinfo: 37 | wsfeedinfo = json.loads(wsfeedinfo.group(1)) 38 | else: 39 | raise Exception('找不到 wsFeedInfo,可能链接错误或 Cookie 过期') 40 | 41 | livestreamid, [websocketurls], token = wsfeedinfo.values() 42 | page_id = KuaiShou.get_page_id() 43 | 44 | p, s = pb.SocketMessage(), pb.CSWebEnterRoom() 45 | s.liveStreamId, s.pageId, s.token = livestreamid, page_id, token 46 | p.payload = s.SerializeToString() 47 | p.payloadType = 200 48 | reg_data = p.SerializeToString() 49 | 50 | t = pb.CSWebHeartbeat() 51 | t.timestamp = int(time.time() * 1000) 52 | p.payload = t.SerializeToString() 53 | p.payloadType = 1 54 | KuaiShou.heartbeat = p.SerializeToString() # 心跳可固定 55 | 56 | return websocketurls, [reg_data] 57 | 58 | @staticmethod 59 | def get_page_id(): 60 | charset = "bjectSymhasOwnProp-0123456789ABCDEFGHIJKLMNQRTUVWXYZ_dfgiklquvxz" 61 | page_id = '' 62 | for _ in range(0, 16): 63 | page_id += random.choice(charset) 64 | page_id += "_" 65 | page_id += str(int(time.time() * 1000)) 66 | return page_id 67 | 68 | @staticmethod 69 | def decode_msg(message): 70 | msgs = [{'name': '', 'content': '', 'msg_type': 'other'}] 71 | 72 | p, s = pb.SocketMessage(), pb.SCWebFeedPush() 73 | p.ParseFromString(message) 74 | if p.payloadType == 310: 75 | s.ParseFromString(p.payload) 76 | 77 | def f(*feeds): 78 | gift = { 79 | 1: '荧光棒', 2: '棒棒糖', 3: '荧光棒', 4: 'PCL加油', 7: '么么哒', 9: '啤酒', 10: '甜甜圈', 80 | 14: '钻戒', 16: '皇冠', 25: '凤冠', 33: '烟花', 41: '跑车', 56: '稳', 113: '火箭', 81 | 114: '玫瑰', 132: '绷带', 133: '平底锅', 135: '红爸爸', 136: '蓝爸爸', 137: '铭文碎片', 82 | 143: '太阳女神', 147: '赞', 149: '血瓶', 150: 'carry全场', 152: '大红灯笼', 156: '穿云箭', 83 | 159: '膨胀了', 160: '秀你一脸', 161: 'MVP', 163: '加油', 164: '猫粮', 165: '小可爱', 84 | 169: '男神', 172: '联盟金猪', 173: '有钱花', 193: '蛋糕', 197: '棒棒糖', 198: '瓜', 85 | 199: '小可爱', 201: '赞', 207: '快手卡', 208: '灵狐姐', 216: 'LPL加油', 218: '烟花', 86 | 219: '告白气球', 220: '大红灯笼', 221: '怦然心动', 222: '凤冠', 223: '火箭', 224: '跑车', 87 | 225: '穿云箭', 226: '金话筒', 227: 'IG冲鸭', 228: 'GRF冲鸭', 229: 'FPX冲鸭', 230: 'FNC冲鸭', 88 | 231: 'SKT冲鸭', 232: 'SPY冲鸭', 233: 'DWG冲鸭', 234: 'G2冲鸭', 235: '爆单', 236: '入团券', 89 | 237: '陪着你540', 238: '支持牌', 239: '陪着你', 242: '金龙', 243: '豪车幻影', 244: '超级6', 90 | 245: '水晶', 246: '金莲', 247: '福袋', 248: '铃铛', 249: '巧克力', 250: '感恩的心', 91 | 254: '武汉加油', 256: '金龙', 257: '财神', 258: '金龙', 259: '天鹅湖', 260: '珍珠', 92 | 261: '金莲', 262: '招财猫', 263: '铃铛', 264: '巧克力', 266: '幸运魔盒', 267: '吻你', 93 | 268: '梦幻城堡', 269: '游乐园', 271: '萌宠', 272: '小雪豹', 275: '喜欢你', 276: '三级头', 94 | 277: '喜欢你', 278: '财神', 279: '锦鲤', 281: '廉颇', 282: '开黑卡', 283: '付费直播门票(不下线)', 95 | 285: '喜欢你呀', 286: '629', 287: '真爱大炮', 289: '玫瑰花园', 290: '珠峰', 292: '鹿角', 96 | 296: '666', 297: '超跑车队', 298: '奥利给', 302: '互粉', 303: '冰棒', 304: '龙之谷', 97 | 306: '浪漫游轮', 307: '壁咚', 308: '壁咚', 309: '鹿角', 310: '么么哒', 311: '私人飞机', 98 | 312: '巅峰票', 313: '巅峰王者', 315: '莫吉托', 316: '地表最强', 318: '阳光海滩', 319: '12号唱片' 99 | } 100 | infos = [{'name': '', 'content': '', 'msg_type': 'other'}] 101 | for feed in feeds: 102 | if feed: 103 | for i in feed: 104 | name = i.user.userName 105 | content = i.content if hasattr(i, 'content') else '送 ' + gift.get(i.giftId, '') \ 106 | if hasattr(i, 'giftId') else '点亮了 ❤' 107 | info = {'name': name, 'content': content, 'msg_type': 'danmaku'} 108 | infos.append(info.copy()) 109 | return infos 110 | 111 | msgs = f(s.commentFeeds, s.giftFeeds, s.likeFeeds) 112 | 113 | return msgs 114 | -------------------------------------------------------------------------------- /danmu/danmaku/laifeng.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import json 3 | import time 4 | 5 | 6 | class LaiFeng: 7 | heartbeat = '2::' 8 | heartbeatInterval = 30 9 | 10 | @staticmethod 11 | async def get_ws_info(url): 12 | rid = url.split('/')[-1] 13 | async with aiohttp.ClientSession() as session: 14 | async with session.get('http://v.laifeng.com/') as resp: 15 | imk = dict(resp.cookies)['imk'].value 16 | args = { 17 | 'name': 'enter', 18 | 'args': [{ 19 | 'token': imk.replace('%3D', '='), 20 | 'yktk': '', 21 | 'uid': '2082628924', 22 | 'isPushHis': '1', 23 | 'roomid': rid, 24 | 'endpointtype': 'ct_,dt_1_1003|0|_{}|CTaXF+oKpB4CAatxtZHBQchJ'.format(time.time() * 1e3) 25 | }] 26 | } 27 | reg_data = '5:::' + json.dumps(args) 28 | return 'ws://normal01.chatroom.laifeng.com/socket.io/1/websocket/', [reg_data] 29 | 30 | @staticmethod 31 | def decode_msg(message): 32 | type_ = message[0] 33 | msgs = [] 34 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 35 | if type_ == '5': 36 | data = json.loads(message[4:]) 37 | name = data.get('name', 0) 38 | args = data['args'] 39 | for arg in args: 40 | if name == 'enterMessage': # 入场信息 41 | msg['name'] = 'SYS' 42 | msg['content'] = arg['body']['n'] + ' 进入频道' 43 | msg['msg_type'] = 'danmaku' 44 | elif name == 'globalHornMessage': # 系统消息 45 | msg['name'] = 'SYS' 46 | msg['content'] = arg['body']['m'] 47 | msg['msg_type'] = 'danmaku' 48 | elif name == 'chatMessage': # 弹幕 49 | msg['name'] = arg['body']['n'] 50 | msg['content'] = arg['body']['m'] 51 | msg['msg_type'] = 'danmaku' 52 | msgs.append(msg.copy()) 53 | return msgs 54 | -------------------------------------------------------------------------------- /danmu/danmaku/longzhu.py: -------------------------------------------------------------------------------- 1 | import json 2 | import aiohttp 3 | import re 4 | 5 | 6 | class LongZhu: 7 | heartbeat = None 8 | 9 | @staticmethod 10 | async def get_ws_info(url): 11 | rid = url.split('/')[-1] 12 | async with aiohttp.ClientSession() as session: 13 | async with session.get('http://m.longzhu.com/' + rid) as resp: 14 | res1 = await resp.text() 15 | roomid = re.search(r'var roomId = (\d+);', res1).group(1) 16 | async with session.get('http://idc-gw.longzhu.com/mbidc?roomId=' + roomid) as resp2: 17 | res2 = json.loads(await resp2.text()) 18 | ws_url = res2['data']['redirect_to'] + '?room_id=' + roomid 19 | return ws_url, None 20 | 21 | @staticmethod 22 | def decode_msg(message): 23 | msgs = [] 24 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 25 | message = json.loads(message) 26 | type_ = message['type'] 27 | # type_ == 'gift' 礼物 28 | if type_ == 'chat': 29 | msg['name'] = message['msg']['user']['username'] 30 | msg['content'] = (message['msg']['content']).strip() 31 | msg['msg_type'] = 'danmaku' 32 | elif type_ == 'commonjoin': 33 | msg['name'] = message['msg']['user']['username'] 34 | msg['content'] = message['msg']['userMessage'] 35 | msg['msg_type'] = 'danmaku' 36 | msgs.append(msg.copy()) 37 | return msgs 38 | -------------------------------------------------------------------------------- /danmu/danmaku/look.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from Crypto.Util.Padding import pad 3 | import aiohttp 4 | import json 5 | import base64 6 | 7 | 8 | class Look: 9 | heartbeat = '2::' 10 | heartbeatInterval = 30 11 | 12 | @staticmethod 13 | def aes_(t, key): 14 | t = t.encode('utf-8') 15 | t = pad(t, AES.block_size) 16 | key = key.encode() 17 | iv = b'0102030405060708' 18 | mode = AES.MODE_CBC 19 | c = AES.new(key, mode, iv) 20 | res = c.encrypt(t) 21 | return base64.b64encode(res).decode('utf-8') 22 | 23 | @staticmethod 24 | async def get_ws_info(url): 25 | rid = url.split('=')[-1] 26 | async with aiohttp.ClientSession() as session: 27 | async with session.get('https://weblink10.netease.im/socket.io/1/') as resp: 28 | res = await resp.text() 29 | sessid = res.split(':')[0] 30 | ws_url = 'wss://weblink10.netease.im/socket.io/1/websocket/' + sessid 31 | room = { 32 | 'liveRoomNo': rid 33 | } 34 | c = json.dumps(room, separators=(',', ':')) 35 | data = { 36 | 'params': Look.aes_(Look.aes_(c, '0CoJUm6Qyw8W8jud'), 'dV00kZnm4Au69cp2'), 37 | 'encSecKey': 'e08bda29630b9a9bbf9552a1e5f889972aedfe6bc4e695b60d566294043431c60e42487153c6e0df42df0aa9d40c739552d8d8ee58d9acbcab8f4ae0df997a787eefcc56bdcd12fd2f1e41bdb5f9db240b3e10b6bd762fd207853af4c78dddf8254cf6ff83599120bd041c3e7dfb3faea1cd2886bd2c40de0981a11ae2af2a33 ' 38 | } 39 | async with session.post('https://api.look.163.com/weapi/livestream/room/get/v3', data=data) as resp: 40 | res = await resp.json() 41 | roomid = res['data']['roomInfo']['roomId'] 42 | 43 | args = { 44 | 'SID': 13, 45 | 'CID': 2, 46 | 'SER': 1, 47 | 'Q': [{ 48 | 't': 'byte', 49 | 'v': 1 50 | }, { 51 | 't': 'Property', 52 | 'v': { 53 | '1': '3a6a3e48f6854dfa4e4464f3bdaec3b4', 54 | '2': '', 55 | '3': '1713a7e3e1e4d7b99fe5bcff2fe7e178', 56 | '5': roomid, 57 | '8': 0, 58 | '20': '', 59 | '21': ' ', 60 | '26': '', 61 | '38': 1 62 | } 63 | }, { 64 | 't': 'Property', 65 | 'v': { 66 | '4': '', 67 | '6': '47', 68 | '8': 1, 69 | '9': 1, 70 | '13': '1713a7e3e1e4d7b99fe5bcff2fe7e178', 71 | '18': '3a6a3e48f6854dfa4e4464f3bdaec3b4', 72 | '19': '', 73 | '24': '', 74 | '26': '', 75 | '1000': '' 76 | } 77 | } 78 | ] 79 | } 80 | reg_data = '3:::' + json.dumps(args, separators=(',', ':')) 81 | 82 | return ws_url, [reg_data] 83 | 84 | @staticmethod 85 | def decode_msg(message): 86 | type_ = message[0] 87 | msgs = [] 88 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 89 | if type_ == '3': 90 | data = json.loads(message[4:]) 91 | if data['cid'] == 10: 92 | body = data['r'][1]['body'] 93 | body = body[0] 94 | if body['2'] == '100': 95 | info = json.loads(body['4']) 96 | if info['type'] == 114: # 入场信息 97 | msg['name'] = info['content']['user']['nickName'] 98 | msg['content'] = ' 进入了直播间' 99 | msg['msg_type'] = 'danmaku' 100 | elif info['type'] == 102: # 礼物 101 | msg['name'] = info['content']['user']['nickName'] 102 | number = info['content']['number'] 103 | giftname = info['content']['giftName'] 104 | msg['content'] = ' 送了{}{}个'.format(giftname, number) 105 | msg['msg_type'] = 'danmaku' 106 | elif body['2'] == '0': # 发言 107 | info = json.loads(body['4']) 108 | msg['name'] = info['content']['user']['nickname'] 109 | msg['content'] = body['3'] 110 | msg['msg_type'] = 'danmaku' 111 | msgs.append(msg.copy()) 112 | return msgs 113 | -------------------------------------------------------------------------------- /danmu/danmaku/pps.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import urllib.parse 3 | import json 4 | 5 | 6 | class QiXiu: 7 | heartbeat = None 8 | 9 | @staticmethod 10 | async def get_ws_info(url): 11 | rid = url.split('/')[-1] 12 | s = bytes([57, 77, 83, 73, 53, 86, 85, 71, 50, 81, 74, 80, 66, 52, 78, 54, 68, 48, 81, 13 | 83, 89, 87, 69, 72, 67, 90, 83, 75, 84, 49, 77, 50, 84, 65, 75, 88]).decode('utf-8') 14 | # ua = 'User-Agent' 15 | # ak = deviceid = md5(str(int(time.time() * 1e3)) + ua + '0000') 16 | ak = deviceid = '118d2ae703e62992263e6741afbb5627' 17 | e = { 18 | 'ag': 1, 19 | 'ak': ak, 20 | 'at': 3, 21 | 'd': deviceid, 22 | 'n': 1, 23 | 'p': 1, 24 | 'r': rid, 25 | 'v': '1.01.0801' 26 | } 27 | i = '' 28 | for k, v in e.items(): 29 | i += '{}={}|'.format(k, str(v)) 30 | e['sg'] = hashlib.md5((i + s).encode('utf-8')).hexdigest() 31 | ws_url = 'ws://qx-ws.iqiyi.com/ws?' + urllib.parse.urlencode(e) 32 | return ws_url, None 33 | 34 | @staticmethod 35 | def decode_msg(data): 36 | message = json.loads(data) 37 | msgs = [] 38 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 39 | for ms in message: 40 | m = ms['ct'] 41 | type_ = ms['t'] 42 | # 200001:进场消息 43 | # 300001:聊天信息 44 | # 102001:礼物 45 | # 1100002:礼物 46 | # 400001:人气值 47 | # 5000010:升级 48 | # 700095:live_score 49 | # 700091:排名 50 | # 其他:系统消息 51 | if type_ == 300001: 52 | msg['name'] = m['op_userInfo']['nick_name'] 53 | msg['content'] = m['msg'] 54 | msg['msg_type'] = 'danmaku' 55 | elif type_ == 102001: 56 | msg['name'] = m['op_userInfo']['nick_name'] 57 | num = m['op_info']['num'] 58 | gift = m['op_info']['name'] 59 | msg['content'] = '送出{}个{}'.format(num, gift) 60 | msg['msg_type'] = 'danmaku' 61 | elif type_ in [200001, 1100002, 110001, 3019, 3022, 3002, 3024]: 62 | msg['name'] = 'SYS' 63 | info = m['op_info'].get('public_chat_msg', 0) 64 | if not info: 65 | info = m['op_info']['roll_chat_msg'] 66 | content = '' 67 | items = info['items'] 68 | for item in items: 69 | content += item.get('content', '') 70 | msg['content'] = content 71 | msg['msg_type'] = 'danmaku' 72 | msgs.append(msg.copy()) 73 | return msgs 74 | -------------------------------------------------------------------------------- /danmu/danmaku/qf.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import json 3 | import time 4 | 5 | 6 | class QF: 7 | heartbeat = '2::' 8 | heartbeatInterval = 30 9 | 10 | @staticmethod 11 | async def get_ws_info(url): 12 | rid = url.split('/')[-1] 13 | async with aiohttp.ClientSession() as session: 14 | async with session.get('https://conn-chat.qf.56.com/socket.io/1/') as resp: 15 | res = await resp.text() 16 | sessid = res.split(':')[0] 17 | ws_url = 'wss://conn-chat.qf.56.com/socket.io/1/websocket/' + sessid 18 | # e = 2 19 | t = 'connector-sio.entryHandler.enter' 20 | s = { 21 | 'userId': '', 22 | 'aq': 0, 23 | 'roomId': rid, 24 | 'token': '', 25 | 'ip': '', 26 | 'recet': 0, 27 | 'params': { 28 | 'referFrom': '0' 29 | }, 30 | 'apType': 0, 31 | 'timestamp': int(time.time() * 1e3) 32 | } 33 | r = json.dumps(s, separators=(',', ':')) 34 | if len(t) > 255: 35 | raise Exception('route maxlength is overflow') 36 | reg_data = '3:::' + '\x00\x00\x00\x02 ' + t + r 37 | return ws_url, [reg_data] 38 | 39 | @staticmethod 40 | def decode_msg(message): 41 | msgs = [] 42 | msg = {'name': '', 'content': '', 'msg_type': 'other'} 43 | type_ = message[0] 44 | if type_ == '3': 45 | data = json.loads(message[4:]) 46 | route = data.get('route', 0) 47 | body = data['body'] 48 | if route == 'onUserLog': # 入场信息 49 | msg['name'] = 'SYS' 50 | msg['content'] = body['userName'] + ' 来了' 51 | msg['msg_type'] = 'danmaku' 52 | elif route == 'onChat': # 弹幕 53 | msg['name'] = body['userName'] 54 | msg['content'] = body['content'] 55 | msg['msg_type'] = 'danmaku' 56 | elif route == 'onGift': # 弹幕 57 | msg['name'] = 'SYS' 58 | msg['content'] = body['userName'] + ' 送礼物 ' + body['giftName'] 59 | msg['msg_type'] = 'danmaku' 60 | elif route == 'onBc': # 弹幕 61 | msg['name'] = 'SYS' 62 | msg['content'] = body['userName'] + ':' + body['msg'] 63 | msg['msg_type'] = 'danmaku' 64 | msgs.append(msg.copy()) 65 | return msgs 66 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/EndpointF.py: -------------------------------------------------------------------------------- 1 | # Tencent is pleased to support the open source community by making Tars available. 2 | # 3 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 4 | # 5 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | # in compliance with the License. You may obtain a copy of the License at 7 | # 8 | # https://opensource.org/licenses/BSD-3-Clause 9 | # 10 | # Unless required by applicable law or agreed to in writing, software distributed 11 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | from core import tarscore 17 | 18 | 19 | class EndpointF(tarscore.struct): 20 | __tars_class__ = "register.EndpointF" 21 | 22 | def __init__(self): 23 | self.host = "" 24 | self.port = 0 25 | self.timeout = 0 26 | self.istcp = 0 27 | self.grid = 0 28 | self.groupworkid = 0 29 | self.grouprealid = 0 30 | self.setId = "" 31 | self.qos = 0 32 | self.bakFlag = 0 33 | self.weight = 0 34 | self.weightType = 0 35 | 36 | @staticmethod 37 | def writeTo(oos, value): 38 | oos.write(tarscore.string, 0, value.host) 39 | oos.write(tarscore.int32, 1, value.port) 40 | oos.write(tarscore.int32, 2, value.timeout) 41 | oos.write(tarscore.int32, 3, value.istcp) 42 | oos.write(tarscore.int32, 4, value.grid) 43 | oos.write(tarscore.int32, 5, value.groupworkid) 44 | oos.write(tarscore.int32, 6, value.grouprealid) 45 | oos.write(tarscore.string, 7, value.setId) 46 | oos.write(tarscore.int32, 8, value.qos) 47 | oos.write(tarscore.int32, 9, value.bakFlag) 48 | oos.write(tarscore.int32, 11, value.weight) 49 | oos.write(tarscore.int32, 12, value.weightType) 50 | 51 | @staticmethod 52 | def readFrom(ios): 53 | value = EndpointF() 54 | value.host = ios.read(tarscore.string, 0, True, value.host) 55 | value.port = ios.read(tarscore.int32, 1, True, value.port) 56 | value.timeout = ios.read(tarscore.int32, 2, True, value.timeout) 57 | value.istcp = ios.read(tarscore.int32, 3, True, value.istcp) 58 | value.grid = ios.read(tarscore.int32, 4, True, value.grid) 59 | value.groupworkid = ios.read( 60 | tarscore.int32, 5, False, value.groupworkid) 61 | value.grouprealid = ios.read( 62 | tarscore.int32, 6, False, value.grouprealid) 63 | value.setId = ios.read(tarscore.string, 7, False, value.setId) 64 | value.qos = ios.read(tarscore.int32, 8, False, value.qos) 65 | value.bakFlag = ios.read(tarscore.int32, 9, False, value.bakFlag) 66 | value.weight = ios.read(tarscore.int32, 11, False, value.weight) 67 | value.weightType = ios.read( 68 | tarscore.int32, 12, False, value.weightType) 69 | return value 70 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | # Tencent is pleased to support the open source community by making Tars available. 6 | # 7 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 8 | # 9 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 10 | # in compliance with the License. You may obtain a copy of the License at 11 | # 12 | # https://opensource.org/licenses/BSD-3-Clause 13 | # 14 | # Unless required by applicable law or agreed to in writing, software distributed 15 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 16 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations under the License. 18 | # 19 | 20 | __version__ = "0.0.1" 21 | 22 | from .__util import util 23 | from .__tars import TarsInputStream 24 | from .__tars import TarsOutputStream 25 | from .__tup import TarsUniPacket 26 | 27 | 28 | class tarscore: 29 | class TarsInputStream(TarsInputStream): 30 | pass 31 | 32 | class TarsOutputStream(TarsOutputStream): 33 | pass 34 | 35 | class TarsUniPacket(TarsUniPacket): 36 | pass 37 | 38 | class boolean(util.boolean): 39 | pass 40 | 41 | class int8(util.int8): 42 | pass 43 | 44 | class uint8(util.uint8): 45 | pass 46 | 47 | class int16(util.int16): 48 | pass 49 | 50 | class uint16(util.uint16): 51 | pass 52 | 53 | class int32(util.int32): 54 | pass 55 | 56 | class uint32(util.uint32): 57 | pass 58 | 59 | class int64(util.int64): 60 | pass 61 | 62 | class float(util.float): 63 | pass 64 | 65 | class double(util.double): 66 | pass 67 | 68 | class bytes(util.bytes): 69 | pass 70 | 71 | class string(util.string): 72 | pass 73 | 74 | class struct(util.struct): 75 | pass 76 | 77 | @staticmethod 78 | def mapclass(ktype, vtype): return util.mapclass(ktype, vtype) 79 | 80 | @staticmethod 81 | def vctclass(vtype): return util.vectorclass(vtype) 82 | 83 | @staticmethod 84 | def printHex(buff): util.printHex(buff) 85 | 86 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/__logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # filename: __logger.py 5 | 6 | # Tencent is pleased to support the open source community by making Tars available. 7 | # 8 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 9 | # 10 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 11 | # in compliance with the License. You may obtain a copy of the License at 12 | # 13 | # https://opensource.org/licenses/BSD-3-Clause 14 | # 15 | # Unless required by applicable law or agreed to in writing, software distributed 16 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 17 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations under the License. 19 | # 20 | 21 | ''' 22 | @version: 0.01 23 | @brief: 日志模块 24 | ''' 25 | 26 | # 仅用于调试 27 | 28 | import logging 29 | from logging.handlers import RotatingFileHandler 30 | import os 31 | import re 32 | 33 | tarsLogger = logging.getLogger('TARS client') 34 | strToLoggingLevel = { 35 | "critical": logging.CRITICAL, 36 | "error": logging.ERROR, 37 | "warn": logging.WARNING, 38 | "info": logging.INFO, 39 | "debug": logging.DEBUG, 40 | "none": logging.NOTSET 41 | } 42 | #console = logging.StreamHandler() 43 | # console.setLevel(logging.DEBUG) 44 | #filelog = logging.FileHandler('tars.log') 45 | # filelog.setLevel(logging.DEBUG) 46 | #formatter = logging.Formatter('%(asctime)s | %(levelname)8s | [%(name)s] %(message)s', '%Y-%m-%d %H:%M:%S') 47 | # console.setFormatter(formatter) 48 | # filelog.setFormatter(formatter) 49 | # tarsLogger.addHandler(console) 50 | # tarsLogger.addHandler(filelog) 51 | # tarsLogger.setLevel(logging.DEBUG) 52 | # tarsLogger.setLevel(logging.INFO) 53 | # tarsLogger.setLevel(logging.ERROR) 54 | 55 | 56 | def createLogFile(filename): 57 | if filename.endswith('/'): 58 | raise ValueError("The logfile is a dir not a file") 59 | if os.path.exists(filename) and os.path.isfile(filename): 60 | pass 61 | else: 62 | fileComposition = str.split(filename, '/') 63 | print(fileComposition) 64 | currentFile = '' 65 | for item in fileComposition: 66 | if item == fileComposition[-1]: 67 | currentFile += item 68 | if not os.path.exists(currentFile) or not os.path.isfile(currentFile): 69 | while True: 70 | try: 71 | os.mknod(currentFile) 72 | break 73 | except OSError as msg: 74 | errno = re.findall(r"\d+", str(msg)) 75 | if len(errno) > 0 and errno[0] == '17': 76 | currentFile += '.log' 77 | continue 78 | break 79 | currentFile += (item + '/') 80 | if not os.path.exists(currentFile): 81 | os.mkdir(currentFile) 82 | 83 | 84 | def initLog(logpath, logsize, lognum, loglevel): 85 | createLogFile(logpath) 86 | handler = RotatingFileHandler(filename=logpath, maxBytes=logsize, 87 | backupCount=lognum) 88 | formatter = logging.Formatter( 89 | '%(asctime)s | %(levelname)6s | [%(filename)18s:%(lineno)4d] | [%(thread)d] %(message)s', '%Y-%m-%d %H:%M:%S') 90 | handler.setFormatter(formatter) 91 | tarsLogger.addHandler(handler) 92 | if loglevel in strToLoggingLevel: 93 | tarsLogger.setLevel(strToLoggingLevel[loglevel]) 94 | else: 95 | tarsLogger.setLevel(strToLoggingLevel["error"]) 96 | 97 | 98 | if __name__ == '__main__': 99 | tarsLogger.debug('debug log') 100 | tarsLogger.info('info log') 101 | tarsLogger.warning('warning log') 102 | tarsLogger.error('error log') 103 | tarsLogger.critical('critical log') 104 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/__packet.py: -------------------------------------------------------------------------------- 1 | # Tencent is pleased to support the open source community by making Tars available. 2 | # 3 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 4 | # 5 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | # in compliance with the License. You may obtain a copy of the License at 7 | # 8 | # https://opensource.org/licenses/BSD-3-Clause 9 | # 10 | # Unless required by applicable law or agreed to in writing, software distributed 11 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | 17 | from .__util import util 18 | 19 | 20 | class RequestPacket(util.struct): 21 | mapcls_context = util.mapclass(util.string, util.string) 22 | mapcls_status = util.mapclass(util.string, util.string) 23 | 24 | def __init__(self): 25 | self.iVersion = 0 26 | self.cPacketType = 0 27 | self.iMessageType = 0 28 | self.iRequestId = 0 29 | self.sServantName = '' 30 | self.sFuncName = '' 31 | self.sBuffer = bytes() 32 | self.iTimeout = 0 33 | self.context = RequestPacket.mapcls_context() 34 | self.status = RequestPacket.mapcls_status() 35 | 36 | @staticmethod 37 | def writeTo(oos, value): 38 | oos.write(util.int16, 1, value.iVersion) 39 | oos.write(util.int8, 2, value.cPacketType) 40 | oos.write(util.int32, 3, value.iMessageType) 41 | oos.write(util.int32, 4, value.iRequestId) 42 | oos.write(util.string, 5, value.sServantName) 43 | oos.write(util.string, 6, value.sFuncName) 44 | oos.write(util.bytes, 7, value.sBuffer) 45 | oos.write(util.int32, 8, value.iTimeout) 46 | oos.write(RequestPacket.mapcls_context, 9, value.context) 47 | oos.write(RequestPacket.mapcls_status, 10, value.status) 48 | 49 | @staticmethod 50 | def readFrom(ios): 51 | value = RequestPacket() 52 | value.iVersion = ios.read(util.int16, 1, True, 0) 53 | print(("iVersion = %d" % value.iVersion)) 54 | value.cPacketType = ios.read(util.int8, 2, True, 0) 55 | print(("cPackerType = %d" % value.cPacketType)) 56 | value.iMessageType = ios.read(util.int32, 3, True, 0) 57 | print(("iMessageType = %d" % value.iMessageType)) 58 | value.iRequestId = ios.read(util.int32, 4, True, 0) 59 | print(("iRequestId = %d" % value.iRequestId)) 60 | value.sServantName = ios.read(util.string, 5, True, '22222222') 61 | value.sFuncName = ios.read(util.string, 6, True, '') 62 | value.sBuffer = ios.read(util.bytes, 7, True, value.sBuffer) 63 | value.iTimeout = ios.read(util.int32, 8, True, 0) 64 | value.context = ios.read( 65 | RequestPacket.mapcls_context, 9, True, value.context) 66 | value.status = ios.read( 67 | RequestPacket.mapcls_status, 10, True, value.status) 68 | return value 69 | 70 | 71 | class ResponsePacket(util.struct): 72 | __tars_class__ = "tars.RpcMessage.ResponsePacket" 73 | mapcls_status = util.mapclass(util.string, util.string) 74 | 75 | def __init__(self): 76 | self.iVersion = 0 77 | self.cPacketType = 0 78 | self.iRequestId = 0 79 | self.iMessageType = 0 80 | self.iRet = 0 81 | self.sBuffer = bytes() 82 | self.status = RequestPacket.mapcls_status() 83 | 84 | @staticmethod 85 | def writeTo(oos, value): 86 | oos.write(util.int16, 1, value.iVersion) 87 | oos.write(util.int8, 2, value.cPacketType) 88 | oos.write(util.int32, 3, value.iRequestId) 89 | oos.write(util.int32, 4, value.iMessageType) 90 | oos.write(util.int32, 5, value.iRet) 91 | oos.write(util.bytes, 6, value.sBuffer) 92 | oos.write(value.mapcls_status, 7, value.status) 93 | 94 | @staticmethod 95 | def readFrom(ios): 96 | value = ResponsePacket() 97 | value.iVersion = ios.read(util.int16, 1, True) 98 | value.cPacketType = ios.read(util.int8, 2, True) 99 | value.iRequestId = ios.read(util.int32, 3, True) 100 | value.iMessageType = ios.read(util.int32, 4, True) 101 | value.iRet = ios.read(util.int32, 5, True) 102 | value.sBuffer = ios.read(util.bytes, 6, True) 103 | value.status = ios.read(value.mapcls_status, 7, True) 104 | return value 105 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/__tup.py: -------------------------------------------------------------------------------- 1 | # Tencent is pleased to support the open source community by making Tars available. 2 | # 3 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 4 | # 5 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | # in compliance with the License. You may obtain a copy of the License at 7 | # 8 | # https://opensource.org/licenses/BSD-3-Clause 9 | # 10 | # Unless required by applicable law or agreed to in writing, software distributed 11 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | import struct 17 | import string 18 | from .__util import util 19 | from .__tars import TarsOutputStream 20 | from .__tars import TarsInputStream 21 | from .__packet import RequestPacket 22 | 23 | 24 | class TarsUniPacket(object): 25 | def __init__(self): 26 | self.__mapa = util.mapclass(util.string, util.bytes) 27 | self.__mapv = util.mapclass(util.string, self.__mapa) 28 | self.__buffer = self.__mapv() 29 | self.__code = RequestPacket() 30 | 31 | # @property 32 | # def version(self): 33 | # return self.__code.iVersion 34 | 35 | # @version.setter 36 | # def version(self, value): 37 | # self.__code.iVersion = value 38 | 39 | @property 40 | def servant(self): 41 | return self.__code.sServantName 42 | 43 | @servant.setter 44 | def servant(self, value): 45 | self.__code.sServantName = value 46 | 47 | @property 48 | def func(self): 49 | return self.__code.sFuncName 50 | 51 | @func.setter 52 | def func(self, value): 53 | self.__code.sFuncName = value 54 | 55 | @property 56 | def requestid(self): 57 | return self.__code.iRequestId 58 | 59 | @requestid.setter 60 | def requestid(self, value): 61 | self.__code.iRequestId = value 62 | 63 | @property 64 | def result_code(self): 65 | if ("STATUS_RESULT_CODE" in self.__code.status) == False: 66 | return 0 67 | 68 | return string.atoi(self.__code.status["STATUS_RESULT_CODE"]) 69 | 70 | @property 71 | def result_desc(self): 72 | if ("STATUS_RESULT_DESC" in self.__code.status) == False: 73 | return '' 74 | 75 | return self.__code.status["STATUS_RESULT_DESC"] 76 | 77 | def put(self, vtype, name, value): 78 | oos = TarsOutputStream() 79 | oos.write(vtype, 0, value) 80 | self.__buffer[name] = {vtype.__tars_class__: oos.getBuffer()} 81 | 82 | def get(self, vtype, name): 83 | if (name in self.__buffer) == False: 84 | raise Exception("UniAttribute not found key:%s,type:%s" % 85 | (name, vtype.__tars_class__)) 86 | 87 | t = self.__buffer[name] 88 | if (vtype.__tars_class__ in t) == False: 89 | raise Exception("UniAttribute not found type:" + 90 | vtype.__tars_class__) 91 | 92 | o = TarsInputStream(t[vtype.__tars_class__]) 93 | return o.read(vtype, 0, True) 94 | 95 | def encode(self): 96 | oos = TarsOutputStream() 97 | oos.write(self.__mapv, 0, self.__buffer) 98 | 99 | self.__code.iVersion = 2 100 | self.__code.sBuffer = oos.getBuffer() 101 | 102 | sos = TarsOutputStream() 103 | RequestPacket.writeTo(sos, self.__code) 104 | 105 | return struct.pack('!i', 4 + len(sos.getBuffer())) + sos.getBuffer() 106 | 107 | def decode(self, buf): 108 | ois = TarsInputStream(buf[4:]) 109 | self.__code = RequestPacket.readFrom(ois) 110 | 111 | sis = TarsInputStream(self.__code.sBuffer) 112 | self.__buffer = sis.read(self.__mapv, 0, True) 113 | 114 | def clear(self): 115 | self.__code.__init__() 116 | 117 | def haskey(self, name): 118 | return name in self.__buffer 119 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/core.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | # Tencent is pleased to support the open source community by making Tars available. 6 | # 7 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 8 | # 9 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 10 | # in compliance with the License. You may obtain a copy of the License at 11 | # 12 | # https://opensource.org/licenses/BSD-3-Clause 13 | # 14 | # Unless required by applicable law or agreed to in writing, software distributed 15 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 16 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations under the License. 18 | # 19 | 20 | __version__ = "0.0.1" 21 | 22 | from __util import util 23 | from __tars import TarsInputStream 24 | from __tars import TarsOutputStream 25 | from __tup import TarsUniPacket 26 | 27 | 28 | class tarscore: 29 | class TarsInputStream(TarsInputStream): 30 | pass 31 | 32 | class TarsOutputStream(TarsOutputStream): 33 | pass 34 | 35 | class TarsUniPacket(TarsUniPacket): 36 | pass 37 | 38 | class boolean(util.boolean): 39 | pass 40 | 41 | class int8(util.int8): 42 | pass 43 | 44 | class uint8(util.uint8): 45 | pass 46 | 47 | class int16(util.int16): 48 | pass 49 | 50 | class uint16(util.uint16): 51 | pass 52 | 53 | class int32(util.int32): 54 | pass 55 | 56 | class uint32(util.uint32): 57 | pass 58 | 59 | class int64(util.int64): 60 | pass 61 | 62 | class float(util.float): 63 | pass 64 | 65 | class double(util.double): 66 | pass 67 | 68 | class bytes(util.bytes): 69 | pass 70 | 71 | class string(util.string): 72 | pass 73 | 74 | class struct(util.struct): 75 | pass 76 | 77 | @staticmethod 78 | def mapclass(ktype, vtype): return util.mapclass(ktype, vtype) 79 | 80 | @staticmethod 81 | def vctclass(vtype): return util.vectorclass(vtype) 82 | 83 | @staticmethod 84 | def printHex(buff): util.printHex(buff) 85 | 86 | 87 | # 被用户引用 88 | from __util import configParse 89 | from __rpc import Communicator 90 | from exception import * 91 | from __logger import tarsLogger 92 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/exception.py: -------------------------------------------------------------------------------- 1 | # Tencent is pleased to support the open source community by making Tars available. 2 | # 3 | # Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved. 4 | # 5 | # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 6 | # in compliance with the License. You may obtain a copy of the License at 7 | # 8 | # https://opensource.org/licenses/BSD-3-Clause 9 | # 10 | # Unless required by applicable law or agreed to in writing, software distributed 11 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | class TarsException(Exception): pass 17 | 18 | class TarsTarsDecodeRequireNotExist(TarsException): pass 19 | class TarsTarsDecodeMismatch(TarsException): pass 20 | class TarsTarsDecodeInvalidValue(TarsException): pass 21 | class TarsTarsUnsupportType(TarsException): pass 22 | 23 | class TarsNetConnectException(TarsException): pass 24 | class TarsNetConnectLostException(TarsException): pass 25 | class TarsNetSocketException(TarsException): pass 26 | class TarsProxyDecodeException(TarsException): pass 27 | class TarsProxyEncodeException(TarsException): pass 28 | class TarsServerEncodeException(TarsException): pass 29 | class TarsServerDecodeException(TarsException): pass 30 | class TarsServerNoFuncException(TarsException): pass 31 | class TarsServerNoServantException(TarsException): pass 32 | class TarsServerQueueTimeoutException(TarsException): pass 33 | class TarsServerUnknownException(TarsException): pass 34 | class TarsSyncCallTimeoutException(TarsException): pass 35 | class TarsRegistryException(TarsException): pass 36 | class TarsServerResetGridException(TarsException): pass 37 | -------------------------------------------------------------------------------- /danmu/danmaku/tars/tars/EndpointF.tars: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxun33/real-url/2748371704066503abfe186ae5b731b034f72c3a/danmu/danmaku/tars/tars/EndpointF.tars -------------------------------------------------------------------------------- /danmu/danmaku/tars/tars/QueryF.tars: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxun33/real-url/2748371704066503abfe186ae5b731b034f72c3a/danmu/danmaku/tars/tars/QueryF.tars -------------------------------------------------------------------------------- /danmu/danmaku/tars/tars/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxun33/real-url/2748371704066503abfe186ae5b731b034f72c3a/danmu/danmaku/tars/tars/__init__.py -------------------------------------------------------------------------------- /danmu/danmaku/yqs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package YiQishanPack; 3 | 4 | message CSHead { 5 | optional uint32 command = 1; 6 | optional uint32 subcmd = 2; 7 | optional uint32 seq = 3; 8 | optional bytes uuid = 4; 9 | optional uint32 clientType = 5; 10 | optional uint32 headFlag = 6; 11 | optional uint32 clientVer = 7; 12 | optional bytes signature = 8; 13 | optional uint32 routeKey = 9; 14 | } 15 | 16 | message TCPAccessReq { 17 | optional bytes AccessToken = 1; 18 | optional bytes MachineCode = 2; 19 | } 20 | 21 | message TcpHelloReq { 22 | optional string uuid = 1; 23 | } 24 | 25 | message EnterRoomReq { 26 | optional bytes uuid = 1; 27 | optional bytes roomid = 2; 28 | optional uint32 neednum = 3; 29 | optional bool isfake = 4; 30 | optional bool needbroadcast = 5; 31 | optional bytes nick = 6; 32 | optional bytes clientip = 7; 33 | optional bytes subroomid = 8; 34 | optional uint32 gameid = 10; 35 | } 36 | 37 | message RoomHelloReq { 38 | optional bytes uuid = 1; 39 | optional bytes roomid = 2; 40 | optional bytes roomsig = 3; 41 | optional uint32 connsvrip = 4; 42 | optional bool isinternal = 5; 43 | optional bytes subroomid = 6; 44 | } 45 | 46 | message Token { 47 | optional string uuid = 1; 48 | optional bytes gtkey = 2; 49 | optional uint32 ip = 3; 50 | optional uint32 expiresstime = 4; 51 | optional uint32 gentime = 5; 52 | } 53 | 54 | message PublicChatNotify { 55 | optional bytes roomid = 1; 56 | optional bytes uuid = 2; 57 | optional bytes nick = 3; 58 | optional ChatInfo info = 4; 59 | optional bytes touuid = 5; 60 | optional bytes tonick = 6; 61 | optional uint32 privilege = 7; 62 | optional uint32 rank = 8; 63 | optional uint32 fromgame = 9; 64 | optional bytes gameid = 10; 65 | repeated BadgeType badges = 11; 66 | optional RoomUserInfo userinfo = 12; 67 | optional bool isnoble = 13; 68 | optional uint32 noblelevelid = 14; 69 | optional string noblelevelname = 15; 70 | optional bool isnoblemessage = 16; 71 | } 72 | 73 | enum BadgeType { 74 | NOBARRAGE = 0; 75 | FIRST_CHARGE_BADGE = 1; 76 | FIRST_CHARGE_COPPER = 2; 77 | FIRST_CHARGE_SLIVER = 3; 78 | FIRST_CHARGE_GOLD = 4; 79 | } 80 | 81 | message ChatInfo { 82 | optional uint32 chattype = 1; 83 | optional bytes textmsg = 2; 84 | } 85 | 86 | message RoomUserInfo { 87 | optional bytes uuid = 1; 88 | optional bytes nick = 2; 89 | optional uint32 weekartistconsume = 3; 90 | optional uint32 artisttotalconsume = 4; 91 | optional uint32 totalconsume = 5; 92 | optional uint32 guardendtime = 6; 93 | optional uint32 peerageid = 7; 94 | } 95 | 96 | message GiftNotyInfo { 97 | optional bytes roomid = 1; 98 | optional bytes giftid = 2; 99 | optional uint32 giftcnt = 3; 100 | optional bytes fromuuid = 4; 101 | optional bytes fromnick = 5; 102 | optional bytes touuid = 6; 103 | optional bytes tonick = 7; 104 | optional uint32 consume = 8; 105 | optional bytes sessid = 9; 106 | optional uint32 hits = 10; 107 | optional uint32 hitsall = 11; 108 | optional uint32 flag = 12; 109 | optional uint32 fromviplevel = 13; 110 | optional uint32 fanslevel = 14; 111 | optional bool fromisnoble = 15; 112 | optional uint32 fromnoblelevelid = 16; 113 | } 114 | 115 | message NotifyFreeGift { 116 | optional bytes uuid = 1; 117 | optional bytes fromnick = 2; 118 | optional bytes touuid = 3; 119 | optional bytes tonick = 4; 120 | optional bytes roomid = 5; 121 | optional uint32 giftid = 6; 122 | optional uint32 giftcnt = 7; 123 | optional uint32 fromviplevel = 8; 124 | optional uint32 fanslevel = 9; 125 | optional bool fromisnoble = 11; 126 | optional uint32 fromnoblelevelid = 12; 127 | } 128 | 129 | message SendBroadcastPkg { 130 | optional bytes uuid = 1; 131 | repeated BroadcastMsg broadcastmsg = 2; 132 | 133 | message BroadcastMsg { 134 | optional uint32 businesstype = 1; 135 | optional bytes title = 2; 136 | optional bytes content = 3; 137 | optional uint32 msgseq = 4; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /danmu/danmaku/yqs.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import struct 3 | 4 | import requests 5 | from Crypto.Cipher import DES 6 | from Crypto.Util.Padding import pad 7 | 8 | from . import yqs_pb2 as pb 9 | 10 | 11 | class YiQiShan: 12 | ws_url = 'wss://websocket.173.com/' 13 | 14 | def __init__(self, rid): 15 | self.rid = str(rid) 16 | self.key = b'e#>&*m16' 17 | with requests.Session() as se: 18 | res = se.get('http://www.173.com/{}'.format(rid)) 19 | try: 20 | self.uuid, _, token, _ = res.cookies.values() 21 | except ValueError: 22 | raise Exception('房间不存在') 23 | self.accesstoken = binascii.a2b_hex(token) 24 | s = YiQiShan.des_decode(self.accesstoken, self.key) 25 | p = pb.Token() 26 | p.ParseFromString(s) 27 | self.gtkey = p.gtkey[:8] 28 | 29 | @staticmethod 30 | def des_encode(t, key): 31 | t = pad(t, DES.block_size) 32 | c = DES.new(key, DES.MODE_ECB) 33 | res = c.encrypt(t) 34 | return res 35 | 36 | @staticmethod 37 | def des_decode(t, key): 38 | c = DES.new(key, DES.MODE_ECB) 39 | res = c.decrypt(t) 40 | length = len(res) 41 | padding = res[length - 1] 42 | res = res[0:length - padding] 43 | return res 44 | 45 | def startup(self): 46 | p = pb.TCPAccessReq() 47 | p.AccessToken = self.accesstoken 48 | return p.SerializeToString() 49 | 50 | def tcphelloreq(self): 51 | p = pb.TcpHelloReq() 52 | p.uuid = self.uuid 53 | return p.SerializeToString() 54 | 55 | def enterroomreq(self): 56 | p = pb.EnterRoomReq() 57 | p.uuid = self.uuid.encode() 58 | p.roomid = self.rid.encode() 59 | return p.SerializeToString() 60 | 61 | def roomhelloreq(self): 62 | p = pb.RoomHelloReq() 63 | p.uuid = self.uuid.encode() 64 | p.roomid = self.rid.encode() 65 | return p.SerializeToString() 66 | 67 | def pack(self, paylod_type): 68 | command = { 69 | 'startup': 123, 70 | 'tcphelloreq': 122, 71 | 'enterroomreq': 601, 72 | 'roomhelloreq': 600 73 | } 74 | subcmd = { 75 | 'startup': 0, 76 | 'tcphelloreq': 0, 77 | 'enterroomreq': 1, 78 | 'roomhelloreq': 1 79 | } 80 | p = pb.CSHead() 81 | p.command = command[paylod_type] 82 | p.subcmd = subcmd[paylod_type] 83 | p.uuid = self.uuid.encode() 84 | p.clientType = 4 85 | p.routeKey = int(self.rid) 86 | n = p.SerializeToString() 87 | 88 | key = self.key if paylod_type == 'startup' else self.gtkey 89 | payload = getattr(self, paylod_type)() 90 | s = YiQiShan.des_encode(payload, key) 91 | 92 | buf = struct.pack('!HcH', len(n) + len(s) + 8, b'W', len(n)) 93 | buf += n 94 | buf += struct.pack('!H', len(s)) 95 | buf += s + b'M' 96 | return buf 97 | 98 | def unpack(self, data): 99 | msgs = [{'name': '', 'content': '', 'msg_type': 'other'}] 100 | 101 | s, = struct.unpack_from('!h', data, 3) 102 | p, = struct.unpack_from('!h', data, 5 + s) 103 | u = data[7 + s:7 + s + p] 104 | 105 | a = pb.CSHead() 106 | a.ParseFromString(data[5:5 + s]) 107 | cmd = a.command 108 | key = self.key if cmd == 123 else self.gtkey 109 | t = u if cmd == 102 else YiQiShan.des_decode(u, key) 110 | 111 | o = cmd 112 | # r = a.subcmd 113 | if o == 102: 114 | p = pb.SendBroadcastPkg() 115 | p.ParseFromString(t) 116 | for i in p.broadcastmsg: 117 | # PublicChatNotify = 1 118 | # BUSINESS_TYPE_FREE_GIFT = 2 119 | # BUSINESS_TYPE_PAY_GIFT = 3 120 | if i.businesstype == 1: # 发言 121 | q = pb.PublicChatNotify() 122 | q.ParseFromString(i.content) 123 | user = q.nick.decode() 124 | content = q.info.textmsg.decode() 125 | # elif i.businesstype == 2: # 免费礼物 126 | # print(i.businesstype) 127 | # q = pb.NotifyFreeGift() 128 | # q.ParseFromString(i.content) 129 | # elif i.businesstype == 3: # 收费礼物 130 | # print(i.businesstype) 131 | # q = pb.GiftNotyInfo() 132 | # q.ParseFromString(i.content) 133 | # else: 134 | # pass 135 | msg = {'name': user, 'content': content, 'msg_type': 'danmaku'} 136 | msgs.append(msg.copy()) 137 | return msgs 138 | -------------------------------------------------------------------------------- /danmu/danmaku/zhanqi.py: -------------------------------------------------------------------------------- 1 | import json 2 | import struct 3 | import aiohttp 4 | 5 | 6 | class ZhanQi: 7 | heartbeat = b'\xbb\xcc\x00\x00\x00\x00\x15\x00\x00\x00\x10\'{"cmdid": "keeplive"}' 8 | wss_url = 'wss://gw.zhanqi.tv/' 9 | heartbeatInterval = 30 10 | 11 | @staticmethod 12 | async def get_ws_info(url): 13 | reg_datas = [] 14 | rid = url.split('/')[-1] 15 | async with aiohttp.ClientSession() as session: 16 | async with session.get('https://m.zhanqi.tv/api/static/v2.1/room/domain/{}.json'.format(rid)) as resp: 17 | info = json.loads(await resp.text()) 18 | roomid = info['data']['id'] 19 | async with session.get('https://m.zhanqi.tv/api/public/room.viewer') as resp2: 20 | res = json.loads(await resp2.text()) 21 | gid = res['data']['gid'] 22 | sid = res['data']['sid'] 23 | timestamp = res['data']['timestamp'] 24 | 25 | login = { 26 | 'cmdid': 'loginreq', 27 | 'roomid': int(roomid), 28 | 'chatroomid': 0, 29 | 'gid': gid, 30 | 'sid': sid, 31 | 't': 0, 32 | 'r': 0, 33 | 'device': 1, 34 | 'fhost': 'mzhanqi', 35 | 'uid': 0, 36 | 'timestamp': timestamp 37 | } 38 | body = json.dumps(login, separators=(',', ':')) 39 | head = struct.pack(' { 28 | this._ws = new WebSocket(url); 29 | this._ws.on('open', this.clientEvent.connect.bind(this)); 30 | this._ws.on('error', this.clientEvent.error.bind(this)); 31 | this._ws.on('close', this.clientEvent.disconnect.bind(this)); 32 | this._ws.on('message', this.messageHandle.bind(this)); 33 | } 34 | 35 | send(message) { 36 | this._ws.send(Packet.Encode(STT.serialize(message))); 37 | } 38 | 39 | login = () => { 40 | this.send({ 41 | type: 'loginreq', 42 | roomid: this.roomId, 43 | }); 44 | } 45 | 46 | joinGroup = () => { 47 | this.send({ 48 | type: 'joingroup', 49 | rid: this.roomId, 50 | gid: -9999, 51 | }); 52 | } 53 | 54 | heartbeat = () => { 55 | const delay = config.HEARBEAT_INTERVAL * 1000; 56 | this._heartbeatTask = setInterval(() => this.send({ type: 'mrkl' }), delay); 57 | } 58 | 59 | logout = () => { 60 | this.send({ type: 'logout' }); 61 | clearInterval(this._heartbeatTask); 62 | } 63 | 64 | run = url => { 65 | //目前已知的弹幕服务器 66 | const port = 8500 + ((min, max) => Math.floor(Math.random() * (max - min + 1) + min))(1, 6); 67 | this._initSocket(url || `wss://danmuproxy.douyu.com:${port}/`); 68 | } 69 | 70 | messageHandle(data) { 71 | Packet.Decode(data, m => { 72 | const r = STT.deserialize(m); 73 | 74 | if (this.debug) { 75 | // this.logger.write(r.type, m); 76 | } 77 | 78 | const isExist = Object.keys(this.messageEvent) 79 | .filter(eventName => !this.ignore?.includes(eventName)) 80 | .includes(r.type); 81 | if (isExist) { 82 | this.messageEvent[r.type](r); 83 | } 84 | }); 85 | } 86 | 87 | on(method, callback) { 88 | method = method.toLocaleLowerCase(); 89 | 90 | if (this.clientEvent.hasOwnProperty(method)) { 91 | const clientEvent = this.clientEvent[method]; 92 | const listener = callback; 93 | callback = function (e) { 94 | if (['connect', 'disconnect'].includes(method)) { 95 | clientEvent.bind(this)(); 96 | } 97 | listener.bind(this)(e); 98 | } 99 | 100 | this.clientEvent[method] = callback; 101 | } 102 | 103 | if (this.messageEvent.hasOwnProperty(method)) { 104 | this.messageEvent[method] = callback.bind(this); 105 | } 106 | } 107 | } 108 | 109 | module.exports = Client 110 | -------------------------------------------------------------------------------- /nodejs/danmu/douyu/clientEvent.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | connect: function () { 3 | this.login(); //登入 4 | this.joinGroup(); //加入组 5 | this.heartbeat(); //发送心跳,强制45秒 6 | }, 7 | error: function (err) { 8 | console.error(err); 9 | }, 10 | disconnect: function () { 11 | this.logout(); 12 | }, 13 | } -------------------------------------------------------------------------------- /nodejs/danmu/douyu/config.js: -------------------------------------------------------------------------------- 1 | const HEARBEAT_INTERVAL = 45; 2 | 3 | const MSG_LIVE_ON = '主播正在直播'; 4 | const MSG_LIVE_OFF = '主播没有直播'; 5 | const MSG_ROOM_RSS = '房间开播提醒'; 6 | const MSG_BC_BUY_DESERVE = '赠送酬勤通知'; 7 | const MSG_SSD = '超级弹幕'; 8 | const MSG_ROOM_SPBC = '房间内礼物广播'; 9 | 10 | module.exports = { 11 | URL, 12 | HEARBEAT_INTERVAL, 13 | MSG_LIVE_ON, 14 | MSG_LIVE_OFF, 15 | MSG_ROOM_RSS, 16 | MSG_BC_BUY_DESERVE, 17 | MSG_SSD, 18 | MSG_ROOM_SPBC, 19 | } -------------------------------------------------------------------------------- /nodejs/danmu/douyu/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: moxun33 3 | * @Date: 2022-09-08 21:20:23 4 | * @LastEditors: moxun33 5 | * @LastEditTime: 2022-09-14 21:03:02 6 | * @FilePath: \real-url\nodejs\danmu\douyu\index.js 7 | * @Description: 8 | * @qmj 9 | */ 10 | /** 11 | * Created by xun on 2022/5/7 9:33. 12 | * description: index 13 | */ 14 | const client = require("./client"); 15 | 16 | client.STT = require("./stt"); 17 | client.Packet = require("./packet"); 18 | 19 | //设置房间号,初始化 20 | const roomId = 4549169; 21 | const opts = { 22 | debug: true, // 默认关闭 false 23 | }; 24 | const room = new client(roomId, opts); 25 | 26 | //系统事件 27 | room.on("connect", function () { 28 | console.log("[connect] roomId=%s", this.roomId); 29 | }); 30 | room.on("disconnect", function () { 31 | console.log("[disconnect] roomId=%s", this.roomId); 32 | }); 33 | room.on("error", function (err) { 34 | console.log("[error] roomId=%s", this.roomId); 35 | }); 36 | 37 | //消息事件 38 | room.on("chatmsg", function (res) { 39 | console.log("[ res ] >", JSON.stringify(res)); 40 | console.log("[chatmsg]", ` [${res.nn}] ${res.txt}`); 41 | }); 42 | room.on("loginres", function (res) { 43 | console.log("[loginres]", "登录成功"); 44 | }); 45 | room.on("uenter", function (res) { 46 | // console.log('[uenter]', `${res.nn}进入房间`) 47 | }); 48 | 49 | //开始监听 50 | room.run(); 51 | -------------------------------------------------------------------------------- /nodejs/danmu/douyu/packet.js: -------------------------------------------------------------------------------- 1 | require('fast-text-encoding'); 2 | 3 | class Packet { 4 | static HEADER_LEN_SIZE = 4; 5 | static HEADER_LEN_TYPECODE = 2; 6 | static HEADER_LEN_ENCRYPT = 1; 7 | static HEADER_LEN_PLACEHOLDER = 1; 8 | static HEADER_LEN_TOTAL = Packet.HEADER_LEN_SIZE * 2 + 9 | Packet.HEADER_LEN_TYPECODE + 10 | Packet.HEADER_LEN_ENCRYPT + 11 | Packet.HEADER_LEN_PLACEHOLDER; 12 | 13 | static concat() { 14 | const arr = []; 15 | for (let n = 0; n < arguments.length; n++) arr[n] = arguments[n]; 16 | 17 | return arr.reduce(function (arr, buf) { 18 | const message = buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf; 19 | const t = new Uint8Array(arr.length + message.length); 20 | t.set(arr, 0); 21 | t.set(message, arr.length); 22 | return t; 23 | }, new Uint8Array(0)); 24 | } 25 | 26 | static Encode(data) { 27 | const encoder = new TextEncoder(); 28 | const body = Packet.concat(encoder.encode(data), Uint8Array.of(0)); 29 | const messageLength = body.length + Packet.HEADER_LEN_SIZE * 2; 30 | const r = new DataView(new ArrayBuffer(body.length + Packet.HEADER_LEN_TOTAL)); 31 | 32 | r.setUint32(0, messageLength, true); 33 | r.setUint32(4, messageLength, true); 34 | r.setInt16(8, 689, true); 35 | r.setInt16(10, 0, true); 36 | 37 | return new Uint8Array(r.buffer).set(body, Packet.HEADER_LEN_TOTAL), r.buffer; 38 | } 39 | 40 | static Decode(buf, callback) { 41 | const decoder = new TextDecoder(); 42 | let readLength = 0; 43 | let buffer = Packet.concat(new ArrayBuffer(0), buf).buffer; 44 | while (buffer.byteLength > 0) { 45 | if (0 === readLength) { 46 | if (buffer.byteLength < 4) return; 47 | 48 | readLength = new DataView(buffer).getUint32(0, true); 49 | buffer = buffer.slice(4); 50 | } 51 | 52 | if (buffer.byteLength < readLength) return; 53 | 54 | const message = decoder.decode(buffer.slice(8, readLength - 1)); 55 | buffer = buffer.slice(readLength); 56 | readLength = 0; 57 | callback(message); 58 | } 59 | } 60 | } 61 | 62 | module.exports = Packet; -------------------------------------------------------------------------------- /nodejs/danmu/douyu/stt.js: -------------------------------------------------------------------------------- 1 | class STT { 2 | static escape(v) { 3 | return v.toString().replace(/@/g, "@A").replace(/\//g, "@S"); 4 | } 5 | 6 | static unescape(v) { 7 | return v.toString().replace(/@S/g, "/").replace(/@A/g, "@"); 8 | } 9 | 10 | static serialize(raw) { 11 | if (Object.prototype.toString.call(raw).slice(8, -1) === "Object") { 12 | return Object.entries(raw) 13 | .map(([k, v]) => `${k}@=${STT.serialize(v)}`) 14 | .join(""); 15 | } else if (Array.isArray(raw)) { 16 | return raw.map((v) => `${STT.serialize(v)}`).join(""); 17 | } else { 18 | return STT.escape(raw.toString()) + "/"; 19 | } 20 | } 21 | 22 | static deserialize(raw) { 23 | if (raw.includes("//")) { 24 | return raw 25 | .split("//") 26 | .filter((e) => e !== "") 27 | .map((item) => STT.deserialize(item)); 28 | } 29 | // raw.includes("col") && console.log(raw); 30 | 31 | if (raw.includes("@=")) { 32 | return raw 33 | .split("/") 34 | .filter((e) => e !== "") 35 | .reduce((o, s) => { 36 | const [k, v] = s.split("@="); 37 | return (o[k] = v ? STT.deserialize(v) : ""), o; 38 | }, {}); 39 | } else { 40 | return STT.unescape(raw); 41 | } 42 | } 43 | } 44 | 45 | module.exports = STT; 46 | -------------------------------------------------------------------------------- /nodejs/danmu/huya/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xun on 2022/5/7 10:09. 3 | * description: index 4 | */ 5 | const huya_danmu = require("./client"); 6 | const roomid = "11352944"; 7 | const client = new huya_danmu(roomid); 8 | 9 | client.on("connect", () => { 10 | console.log(`已连接huya ${roomid}房间弹幕~`); 11 | }); 12 | 13 | client.on("message", (msg) => { 14 | switch (msg.type) { 15 | case "chat": 16 | console.log(`[${msg.from.name}]:${msg.content}`); 17 | break; 18 | case "gift": 19 | console.log(`[${msg.from.name}]->赠送${msg.count}个${msg.name}`); 20 | break; 21 | case "online": 22 | console.log(`[当前人气]:${msg.count}`); 23 | break; 24 | } 25 | }); 26 | 27 | client.on("error", (e) => { 28 | console.log(e); 29 | }); 30 | 31 | client.on("close", () => { 32 | console.log("close"); 33 | }); 34 | 35 | client.start(); 36 | -------------------------------------------------------------------------------- /nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "crypto-js": "^4.1.1", 4 | "fast-text-encoding": "^1.0.3", 5 | "https-proxy-agent": "^5.0.1", 6 | "iconv-lite": "^0.6.3", 7 | "md5": "^2.3.0", 8 | "node-fetch": "2.x", 9 | "pako": "^2.0.4", 10 | "qs": "^6.11.0", 11 | "to-arraybuffer": "^1.0.1", 12 | "vm2": "^3.9.9", 13 | "ws": "^8.6.0" 14 | }, 15 | "name": "real-url", 16 | "version": "1.0.0", 17 | "main": "index.js", 18 | "repository": "git@github.com:moxun33/real-url.git", 19 | "author": "moxun33 ", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /nodejs/run-all.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | node douyu-batch.js & node huya-batch.js & node bilibili-batch.js & node all-m3u.js 3 | pause -------------------------------------------------------------------------------- /nodejs/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node douyu-batch.js 3 | node huya-batch.js 4 | node bilibili-batch.js 5 | node all-m3u.js -------------------------------------------------------------------------------- /nodejs/utils/utils.js: -------------------------------------------------------------------------------- 1 | const {createHash} = require("crypto"); 2 | const fetch = require("node-fetch"); 3 | //const HttpsProxyAgent = require('https-proxy-agent'); 4 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 5 | 6 | const COMM_CONF = { 7 | PROXY_URL: 'http://127.0.0.1:18888', 8 | URLENCODED_FORM_TYPE: 'application/x-www-form-urlencoded;charset=utf-8;', 9 | MOBILE_USER_AGENT: 10 | "Mozilla/5.0 (Linux; U; Android 4.0.3; zh-CN; vivo X9i Build/N2G47H) AppleWebKit/537.36 (KHTML,like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.9.3.973 Mobile Safari/537.36", 11 | }; 12 | 13 | //统一请求发送 14 | const fireFetch = async (url, opts = {}, isJson = false) => { 15 | try { 16 | 17 | const heads = opts.headers||opts.Headers|| {}; 18 | const res = await fetch(url, { 19 | // agent: HttpsProxyAgent(COMM_CONF.PROXY_URL), 20 | ...opts, mode: "same-origin", 21 | credentials: "same-origin", 22 | headers: { 23 | "User-Agent": COMM_CONF.MOBILE_USER_AGENT, 24 | 'Content-Type': COMM_CONF.URLENCODED_FORM_TYPE, 25 | ...heads, 26 | }, 27 | }).then((res) => { 28 | 29 | return (isJson ? res.json() : res.text()) 30 | }); 31 | // console.log(res); 32 | return res; 33 | } catch (e) { 34 | console.error(e, 'fetch error'); 35 | return isJson ? {} : ""; 36 | } 37 | }; 38 | 39 | //随机整数 40 | function getRandomInt(min = 1, max = 100000000) { 41 | min = Math.ceil(min); 42 | max = Math.floor(max); 43 | return Math.floor(Math.random() * (max - min)) + min; //不含最大值,含最小值 44 | } 45 | 46 | //判断json是否有效 47 | const isJSONValid = (str) => { 48 | try { 49 | JSON.parse(str); 50 | return true; 51 | } catch (e) { 52 | return false; 53 | } 54 | }; 55 | 56 | //提取html文本的目标字符串 57 | const matchHtmlText = (html, reg, defData = "") => { 58 | if (!reg) { 59 | return defData; 60 | } 61 | const matches = html.match(reg) || []; 62 | 63 | if (matches.length < 1) { 64 | return defData; 65 | } 66 | return matches[0]; 67 | }; 68 | 69 | /** 70 | * @desc: 加密 71 | * @param {string} algorithm 72 | * @param {any} content 73 | * @return {string} 74 | */ 75 | const encrypt = (content, algorithm = "md5") => { 76 | let hash = createHash(algorithm); 77 | hash.update(content); 78 | return hash.digest("hex"); 79 | }; 80 | 81 | /** 82 | * 解析 url 的 search 参数 83 | * @param qs {string} url search 84 | * @return object 85 | * {key:value} 86 | * */ 87 | const parseUrlSearch = (qs) => { 88 | if (qs && qs.indexOf("?") > -1) { 89 | const newQS = qs.substring(qs.indexOf("?")).replace("?", ""); 90 | const tmpArr = newQS.split("&"); 91 | let finalObj = {}; 92 | tmpArr.forEach((item) => { 93 | const itemSplit = item.split("="); 94 | if (itemSplit.length === 2) { 95 | finalObj[decodeURIComponent(itemSplit[0])] = decodeURIComponent( 96 | decodeURIComponent(itemSplit[1]) 97 | ); 98 | } 99 | }); 100 | return finalObj; 101 | } 102 | return {}; 103 | }; 104 | 105 | /** 106 | * 把对象拼接成 url search 参数 107 | * @param obj {Object} 108 | * @param noPrefix {boolean} 第一位不需要 问号 109 | * @return string 110 | * 111 | * */ 112 | const genUrlSearch = (obj, noPrefix = false) => { 113 | let urlQs = ""; 114 | const keys = Object.keys(obj); 115 | if (obj instanceof Object && keys.length > 0) { 116 | keys.forEach((s, i) => { 117 | if (s && obj[s]) { 118 | const value = encodeURIComponent(obj[s]); 119 | const key = encodeURIComponent(s); 120 | urlQs += `${urlQs.length === 0 ? noPrefix ? '' : "?" : "&"}${key}=${value}`; 121 | } 122 | }); 123 | } 124 | 125 | return urlQs; 126 | }; 127 | 128 | module.exports = { 129 | COMM_CONF, 130 | getRandomInt, 131 | encrypt, 132 | isJSONValid, 133 | parseUrlSearch, 134 | genUrlSearch, 135 | fireFetch, 136 | matchHtmlText 137 | }; 138 | -------------------------------------------------------------------------------- /now.py: -------------------------------------------------------------------------------- 1 | # 获取NOW直播的真实流媒体地址。 2 | 3 | import requests 4 | 5 | 6 | class Now: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | try: 13 | room_url = f'https://now.qq.com/cgi-bin/now/web/room/get_live_room_url?room_id={self.rid}&platform=8' 14 | response = requests.get(url=room_url).json() 15 | result = response.get('result') 16 | real_url = { 17 | 'raw_hls_url': result.get('raw_hls_url', 0), 18 | 'raw_rtmp_url': result.get('raw_rtmp_url', 0), 19 | 'raw_flv_url': result.get('raw_flv_url', 0) 20 | } 21 | except Exception: 22 | raise Exception('直播间不存在或未开播') 23 | return real_url 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | now = Now(rid) 29 | return now.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('请输入NOW直播间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /pps.py: -------------------------------------------------------------------------------- 1 | # 获取PPS奇秀直播的真实流媒体地址。 2 | 3 | import requests 4 | import re 5 | import time 6 | 7 | 8 | class PPS: 9 | 10 | def __init__(self, rid): 11 | self.rid = rid 12 | self.BASE_URL = 'https://m-x.pps.tv/api/stream/getH5' 13 | self.s = requests.Session() 14 | 15 | def get_real_url(self): 16 | headers = { 17 | 'Content-Type': 'application/x-www-form-urlencoded', 18 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 19 | 'like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', 20 | 'Referer': 'https://m-x.pps.tv/' 21 | } 22 | tt = int(time.time() * 1000) 23 | try: 24 | res = self.s.get(f'https://m-x.pps.tv/room/{self.rid}', headers=headers).text 25 | anchor_id = re.findall(r'anchor_id":"(\d*)', res)[0] 26 | params = { 27 | 'qd_tm': tt, 28 | 'typeId': 1, 29 | 'platform': 7, 30 | 'vid': 0, 31 | 'qd_vip': 0, 32 | 'qd_uid': anchor_id, 33 | 'qd_ip': '114.114.114.114', 34 | 'qd_vipres': 0, 35 | 'qd_src': 'h5_xiu', 36 | 'qd_tvid': 0, 37 | 'callback': '', 38 | } 39 | res = self.s.get(self.BASE_URL, headers=headers, params=params).text 40 | real_url = re.findall(r'"hls":"(.*)","rate_list', res)[0] 41 | except Exception: 42 | raise Exception('直播间不存在或未开播') 43 | return real_url 44 | 45 | 46 | def get_real_url(rid): 47 | try: 48 | pps = PPS(rid) 49 | return pps.get_real_url() 50 | except Exception as e: 51 | print('Exception:', e) 52 | return False 53 | 54 | 55 | if __name__ == '__main__': 56 | r = input('请输入奇秀直播房间号:\n') 57 | print(get_real_url(r)) 58 | -------------------------------------------------------------------------------- /qf.py: -------------------------------------------------------------------------------- 1 | # 获取56千帆直播的真实流媒体地址。 2 | # 千帆直播直播间链接形式:https://qf.56.com/520686 3 | 4 | import requests 5 | import re 6 | 7 | 8 | class QF: 9 | 10 | def __init__(self, rid): 11 | """ 12 | 搜狐千帆直播可以直接在网页源码里找到播放地址 13 | Args: 14 | rid: 数字直播间号 15 | """ 16 | self.rid = rid 17 | self.s = requests.Session() 18 | 19 | def get_real_url(self): 20 | try: 21 | res = self.s.get(f'https://qf.56.com/{self.rid}').text 22 | flvurl = re.search(r"flvUrl:'(.*)?'", res).group(1) 23 | if 'flv' in flvurl: 24 | real_url = flvurl 25 | else: 26 | res = self.s.get(flvurl).json() 27 | real_url = res['url'] 28 | except Exception: 29 | raise Exception('直播间不存在或未开播') 30 | return real_url 31 | 32 | 33 | def get_real_url(rid): 34 | try: 35 | qf = QF(rid) 36 | return qf.get_real_url() 37 | except Exception as e: 38 | print('Exception:', e) 39 | return False 40 | 41 | 42 | if __name__ == '__main__': 43 | r = input('请输入千帆直播房间号:\n') 44 | print(get_real_url(r)) 45 | -------------------------------------------------------------------------------- /qie.py: -------------------------------------------------------------------------------- 1 | # 企鹅体育:https://live.qq.com/directory/all 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class ESport: 8 | 9 | def __init__(self, rid): 10 | self.rid = rid 11 | 12 | def get_real_url(self): 13 | with requests.Session() as s: 14 | res = s.get(f'https://m.live.qq.com/{self.rid}') 15 | show_status = re.search(r'"show_status":"(\d)"', res.text) 16 | if show_status: 17 | if show_status.group(1) == '1': 18 | hls_url = re.search(r'"hls_url":"(.*)","use_p2p"', res.text).group(1) 19 | return hls_url 20 | else: 21 | raise Exception('未开播') 22 | else: 23 | raise Exception('直播间不存在') 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | es = ESport(rid) 29 | return es.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('请输入企鹅体育直播房间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /renren.py: -------------------------------------------------------------------------------- 1 | # 人人直播:http://zhibo.renren.com/ 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class RenRen: 8 | 9 | def __init__(self, rid): 10 | """ 11 | 直播间地址形式:http://activity.renren.com/live/liveroom/970302934_21348 12 | rid即970302934_21348 13 | Args: 14 | rid:房间号 15 | """ 16 | self.rid = rid 17 | self.s = requests.Session() 18 | 19 | def get_real_url(self): 20 | res = self.s.get(f'http://activity.renren.com/live/liveroom/{self.rid}').text 21 | try: 22 | s = re.search(r'playUrl":"(.*?)"', res) 23 | play_url = s.group(1) 24 | return play_url 25 | except Exception: 26 | raise Exception('解析错误') 27 | 28 | 29 | def get_real_url(rid): 30 | try: 31 | rr = RenRen(rid) 32 | return rr.get_real_url() 33 | except Exception as e: 34 | print('Exception:', e) 35 | return False 36 | 37 | 38 | if __name__ == '__main__': 39 | r = input('请输入人人直播房间号:\n') 40 | print(get_real_url(r)) 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | charset-normalizer==2.0.12 3 | idna==3.3 4 | PyExecJS==1.5.1 5 | requests==2.27.1 6 | six==1.16.0 7 | urllib3==1.26.8 8 | aiohttp 9 | Crypto -------------------------------------------------------------------------------- /showself.py: -------------------------------------------------------------------------------- 1 | # 秀色直播:https://www.showself.com/ 2 | 3 | from urllib.parse import urlencode 4 | import requests 5 | import time 6 | import hashlib 7 | 8 | 9 | class ShowSelf: 10 | 11 | def __init__(self, rid): 12 | self.rid = rid 13 | self.headers = { 14 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 15 | 'Chrome/95.0.4638.69 Safari/537.36 ' 16 | } 17 | self.s = requests.Session() 18 | 19 | def get_real_url(self): 20 | res = self.s.get('https://service.showself.com/v2/custuser/visitor', headers=self.headers).json() 21 | uid = res['data']['uid'] 22 | accesstoken = sessionid = res['data']['sessionid'] 23 | params = { 24 | 'accessToken': accesstoken, 25 | 'tku': uid, 26 | '_st1': int(time.time() * 1000) 27 | } 28 | payload = { 29 | 'groupid': '999', 30 | 'roomid': self.rid, 31 | 'sessionid': sessionid, 32 | 'sessionId': sessionid 33 | } 34 | # 合并两个字典 35 | data = dict(params, **payload) 36 | data = f'{urlencode(sorted(data.items(), key=lambda d: d[0]))}sh0wselfh5' 37 | _ajaxData1 = hashlib.md5(data.encode('utf-8')).hexdigest() 38 | payload['_ajaxData1'] = _ajaxData1 39 | url = f'https://service.showself.com/v2/rooms/{self.rid}/members?{urlencode(params)}' 40 | res = self.s.post(url, json=payload, headers=self.headers) 41 | if res.status_code == 200: 42 | res = res.json() 43 | statuscode = res['status']['statuscode'] 44 | if statuscode == '0': 45 | if res['data']['roomInfo']['live_status'] == '1': 46 | anchor, = res['data']['roomInfo']['anchor'] 47 | real_url = anchor['media_url'] 48 | return real_url 49 | else: 50 | raise Exception('未开播') 51 | else: 52 | raise Exception('房间不存在') 53 | else: 54 | raise Exception('参数错误') 55 | 56 | 57 | def get_real_url(rid): 58 | try: 59 | ss = ShowSelf(rid) 60 | return ss.get_real_url() 61 | except Exception as e: 62 | print('Exception:', e) 63 | return False 64 | 65 | 66 | if __name__ == '__main__': 67 | r = input('输入秀色直播房间号:\n') 68 | print(get_real_url(r)) 69 | -------------------------------------------------------------------------------- /sports_iqiyi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/6/19 20:39 3 | # @Project: my-spiders 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | 8 | import binascii 9 | import hashlib 10 | import json 11 | import re 12 | import time 13 | from urllib.parse import urlencode 14 | 15 | import execjs 16 | import requests 17 | 18 | 19 | class sIQiYi: 20 | 21 | def __init__(self, rid): 22 | """ 23 | 收费直播间、未开播直播间、已结束直播间获取到的地址均无法播放; 24 | Args: 25 | rid: 这里传入完整的直播间地址 26 | """ 27 | url = rid 28 | self.rid = url.split('/')[-1] 29 | self.s = requests.Session() 30 | 31 | def decodeurl(self): 32 | """ 33 | 传入url地址,截取url中的直播间id 34 | 字符串lgqipu倒序后转为十进制数,作为qpid解码的传参 35 | Returns: 36 | qpid 37 | """ 38 | o = 'lgqipu' 39 | o = int(binascii.hexlify(o[::-1].encode()), 16) 40 | 41 | s = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 42 | a = 0 43 | rr = enumerate(self.rid) 44 | for i, _ in rr: 45 | a += s.index(_) * pow(36, len(self.rid) - (i + 1)) 46 | 47 | a = f'{a:b}' 48 | n = f'{o:b}' 49 | x = len(a) 50 | y = len(n) 51 | if x > y: 52 | i = a[:x - y] 53 | a = a[x - y:] 54 | else: 55 | i = n[:y - x] 56 | n = n[y - x:] 57 | 58 | for rs, ele in enumerate(a): 59 | if ele == n[rs]: 60 | i += '0' 61 | else: 62 | i += '1' 63 | qpid = int(i, 2) 64 | return qpid 65 | 66 | def get_real_url(self): 67 | """ 68 | 里面iqiyi.js是个加盐的md5,execjs执行后获取cmd5x的返回值 69 | Returns: 70 | m3u8格式播放地址 71 | Raises: 72 | Could not find an available JavaScript runtime: 是否安装了js环境 73 | """ 74 | qpid = self.decodeurl() 75 | uid = 'ba4fe551bd889d73f3d321d2fadc6130' 76 | ve = hashlib.md5(f'{qpid}function getTime() {{ [native code] }}{uid}'.encode('utf-8')).hexdigest() 77 | v = { 78 | 'lp': qpid, 79 | 'src': '01014351010000000000', 80 | 'ptid': '02037251010000000000', 81 | 'uid': '', 82 | 'rateVers': 'H5_QIYI', 83 | 'k_uid': uid, 84 | 'qdx': 'n', 85 | 'qdv': 3, 86 | 'dfp': '', 87 | 've': ve, 88 | 'v': 1, 89 | 'k_err_retries': 0, 90 | 'tm': int(time.time()), 91 | 'k_ft4': 17179869185, 92 | 'k_ft1': 141287244169216, 93 | 'k_ft5': 1, 94 | 'qd_v': 1, 95 | 'qdy': 'a', 96 | 'qds': 0, 97 | # 'callback': 'Q3d080ff19d8f233acb05683bf38e3a15', 98 | # 'vf': 'f0b986f100ae81fff8e8f8f96053e815', 99 | } 100 | k = '/jp/live?' + urlencode(v) 101 | cb = hashlib.md5(k.encode('utf-8')).hexdigest() 102 | k = f'{k}&callback=Q{cb}' 103 | 104 | # 生成vf 105 | with open('iqiyi.js', 'r') as f: 106 | content = f.read() 107 | try: 108 | cmd5x = execjs.compile(content) 109 | vf = cmd5x.call('cmd5x', k) 110 | except RuntimeError: 111 | raise Exception('Could not find an available JavaScript runtime.') 112 | 113 | # 请求url 114 | url = f'https://live.video.iqiyi.com{k}&vf={vf}' 115 | res = self.s.get(url).text 116 | data = re.search(r'try{\w{33}\(([\w\W]+)\s\);}catch\(e\){};', res).group(1) 117 | data = json.loads(data) 118 | if data['code'] == 'A00004': 119 | raise Exception('直播间地址错误!') 120 | elif data['code'] == 'A00000': 121 | try: 122 | url = data['data']['streams'][-1]['url'] 123 | except IndexError: 124 | raise Exception('可能直播未开始直播或为付费直播!') 125 | else: 126 | raise Exception('无法定位错误原因,可提交issue!') 127 | return url 128 | 129 | 130 | def get_real_url(rid): 131 | try: 132 | siqiyi = sIQiYi(rid) 133 | return siqiyi.get_real_url() 134 | except Exception as e: 135 | print('Exception:', e) 136 | return False 137 | 138 | 139 | if __name__ == '__main__': 140 | r = input('请输入爱奇艺体育直播间完整地址地址,注意只能获取免费直播:\n') 141 | # https://sports.iqiyi.com/resource/pcw/live/gwbgbfbgc3 142 | print(get_real_url(r)) 143 | -------------------------------------------------------------------------------- /tiktok.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/5/2 23:23 3 | # @Project: real-url 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | import re 8 | 9 | import requests 10 | 11 | 12 | class TikTok: 13 | 14 | def __init__(self, rid): 15 | self.rid = rid 16 | 17 | def get_real_url(self): 18 | headers = { 19 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 20 | 'like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', 21 | } 22 | res = requests.get(self.rid, headers=headers).text 23 | url = re.search(r'"LiveUrl":"(.*?m3u8)",', res) 24 | 25 | if url: 26 | return url.group(1) 27 | else: 28 | raise Exception('link invalid') 29 | 30 | 31 | def get_real_url(rid): 32 | try: 33 | tt = TikTok(rid) 34 | return tt.get_real_url() 35 | except Exception as e: 36 | print('Exception:', e) 37 | return False 38 | 39 | 40 | if __name__ == '__main__': 41 | # https://vm.tiktok.com/ZMe45tomE 42 | r = input('请输入 TikTok 分享链接:\n') 43 | print(get_real_url(r)) 44 | -------------------------------------------------------------------------------- /tuho.py: -------------------------------------------------------------------------------- 1 | # 星光直播:https://www.tuho.tv/28545037 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class TuHo: 8 | 9 | def __init__(self, rid): 10 | self.rid = rid 11 | 12 | def get_real_url(self): 13 | with requests.Session() as s: 14 | res = s.get(f'https://www.tuho.tv/{self.rid}').text 15 | flv = re.search(r'videoPlayFlv":"(https[\s\S]+?flv)', res) 16 | if flv: 17 | status = re.search(r'isPlaying\s:\s(\w+),', res).group(1) 18 | if status == 'true': 19 | real_url = flv.group(1).replace('\\', '') 20 | return real_url 21 | else: 22 | raise Exception('未开播') 23 | else: 24 | raise Exception('直播间不存在') 25 | 26 | 27 | def get_real_url(rid): 28 | try: 29 | th = TuHo(rid) 30 | return th.get_real_url() 31 | except Exception as e: 32 | print('Exception:', e) 33 | return False 34 | 35 | 36 | if __name__ == '__main__': 37 | r = input('输入星光直播房间号:\n') 38 | print(get_real_url(r)) 39 | -------------------------------------------------------------------------------- /twitch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/5/2 16:20 3 | # @Project: real-url 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | import json 8 | import re 9 | from urllib.parse import urlencode 10 | 11 | # twitch 直播需要科学上网 12 | import requests 13 | 14 | 15 | class Twitch: 16 | 17 | def __init__(self, rid): 18 | # rid = channel_name 19 | self.rid = rid 20 | with requests.Session() as self.s: 21 | pass 22 | 23 | def get_client_id(self): 24 | try: 25 | res = self.s.get(f'https://www.twitch.tv/{self.rid}').text 26 | client_id = re.search(r'"Client-ID":"(.*?)"', res).group(1) 27 | return client_id 28 | except requests.exceptions.ConnectionError: 29 | raise Exception('ConnectionError') 30 | 31 | def get_sig_token(self): 32 | data = { 33 | "operationName": "PlaybackAccessToken_Template", 34 | "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, " 35 | "$isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, " 36 | "params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(" 37 | "if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, " 38 | "params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(" 39 | "if: $isVod) { value signature __typename }}", 40 | "variables": { 41 | "isLive": True, 42 | "login": self.rid, 43 | "isVod": False, 44 | "vodID": "", 45 | "playerType": "site" 46 | } 47 | } 48 | 49 | headers = { 50 | 'Client-ID': self.get_client_id(), 51 | 'Referer': 'https://www.twitch.tv/', 52 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 53 | 'Chrome/90.0.4430.93 Safari/537.36', 54 | } 55 | res = self.s.post('https://gql.twitch.tv/gql', headers=headers, data=json.dumps(data)).json() 56 | try: 57 | token, signature, _ = res['data']['streamPlaybackAccessToken'].values() 58 | except AttributeError: 59 | raise Exception("Channel does not exist") 60 | 61 | return signature, token 62 | 63 | def get_real_url(self): 64 | signature, token = self.get_sig_token() 65 | params = { 66 | 'allow_source': 'true', 67 | 'dt': 2, 68 | 'fast_bread': 'true', 69 | 'player_backend': 'mediaplayer', 70 | 'playlist_include_framerate': 'true', 71 | 'reassignments_supported': 'true', 72 | 'sig': signature, 73 | 'supported_codecs': 'vp09,avc1', 74 | 'token': token, 75 | 'cdm': 'wv', 76 | 'player_version': '1.4.0', 77 | } 78 | url = f'https://usher.ttvnw.net/api/channel/hls/{self.rid}.m3u8?{urlencode(params)}' 79 | return url 80 | 81 | 82 | def get_real_url(rid): 83 | try: 84 | tw = Twitch(rid) 85 | return tw.get_real_url() 86 | except Exception as e: 87 | print('Exception:', e) 88 | return False 89 | 90 | 91 | if __name__ == '__main__': 92 | r = input('请输入 twitch 房间名:\n') 93 | print(get_real_url(r)) 94 | -------------------------------------------------------------------------------- /v6cn.py: -------------------------------------------------------------------------------- 1 | # 获取六间房直播的真实流媒体地址。 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class V6CN: 8 | 9 | def __init__(self, rid): 10 | self.rid = rid 11 | 12 | def get_real_url(self): 13 | try: 14 | response = requests.get(f'https://v.6.cn/{self.rid}').text 15 | result = re.findall(r'"flvtitle":"v(\d*?)-(\d*?)"', response)[0] 16 | uid = result[0] 17 | flvtitle = 'v{}-{}'.format(*result) 18 | response = requests.get(f'https://rio.6rooms.com/live/?s={uid}').text 19 | hip = 'https://' + re.search(r'(.*\.com).*?', response).group(1) 20 | real_url = [f'{hip}/{flvtitle}/palylist.m3u8', f'{hip}/httpflv/{flvtitle}'] 21 | except Exception: 22 | raise Exception('直播间不存在或未开播') 23 | return real_url 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | v6cn = V6CN(rid) 29 | return v6cn.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('请输入六间房直播房间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /wali.py: -------------------------------------------------------------------------------- 1 | # 小米直播:https://live.wali.com/fe 2 | 3 | import requests 4 | 5 | 6 | class WaLi: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | zuid = self.rid.split('_')[0] 13 | with requests.Session() as s: 14 | res = s.get(f'https://s.zb.mi.com/get_liveinfo?lid={self.rid}&zuid={zuid}').json() 15 | status = res['data']['status'] 16 | if status == 1: 17 | flv = res['data']['video']['flv'] 18 | return flv.replace('http', 'https') 19 | else: 20 | raise Exception('直播间不存在或未开播') 21 | 22 | 23 | def get_real_url(rid): 24 | try: 25 | wali = WaLi(rid) 26 | return wali.get_real_url() 27 | except Exception as e: 28 | print('Exception:', e) 29 | return False 30 | 31 | 32 | if __name__ == '__main__': 33 | r = input('请输入小米直播房间号:\n') 34 | print(get_real_url(r)) 35 | -------------------------------------------------------------------------------- /woxiu.py: -------------------------------------------------------------------------------- 1 | # 我秀直播:https://www.woxiu.com/ 2 | 3 | import requests 4 | 5 | 6 | class WoXiu: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | headers = { 13 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 14 | 'like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 ' 15 | } 16 | url = f'https://m.woxiu.com/index.php?action=M/Live&do=LiveInfo&room_id={self.rid}' 17 | with requests.Session() as s: 18 | res = s.get(url, headers=headers) 19 | try: 20 | res = res.json() 21 | except Exception: 22 | raise Exception('直播间不存在') 23 | status = res['online'] 24 | if status: 25 | live_stream = res['live_stream'] 26 | return live_stream 27 | else: 28 | raise Exception('未开播') 29 | 30 | 31 | def get_real_url(rid): 32 | try: 33 | wx = WoXiu(rid) 34 | return wx.get_real_url() 35 | except Exception as e: 36 | print('Exception:', e) 37 | return False 38 | 39 | 40 | if __name__ == '__main__': 41 | r = input('请输入我秀直播房间号:\n') 42 | print(get_real_url(r)) 43 | -------------------------------------------------------------------------------- /xunlei.py: -------------------------------------------------------------------------------- 1 | # 迅雷直播:https://live.xunlei.com/global/index.html?id=0 2 | 3 | import requests 4 | import hashlib 5 | import time 6 | from urllib.parse import urlencode 7 | 8 | 9 | class XunLei: 10 | 11 | def __init__(self, rid): 12 | self.rid = rid 13 | 14 | def get_real_url(self): 15 | url = 'https://biz-live-ssl.xunlei.com//caller' 16 | headers = { 17 | 'cookie': 'appid=1002' 18 | } 19 | _t = int(time.time() * 1000) 20 | u = '1002' 21 | f = '&*%$7987321GKwq' 22 | params = { 23 | '_t': _t, 24 | 'a': 'play', 25 | 'c': 'room', 26 | 'hid': 'h5-e70560ea31cc17099395c15595bdcaa1', 27 | 'uuid': self.rid, 28 | } 29 | data = urlencode(params) 30 | p = hashlib.md5(f'{u}{data}{f}'.encode('utf-8')).hexdigest() 31 | params['sign'] = p 32 | with requests.Session() as s: 33 | res = s.get(url, params=params, headers=headers).json() 34 | if res['result'] == 0: 35 | play_status = res['data']['play_status'] 36 | if play_status == 1: 37 | real_url = res['data']['data']['stream_pull_https'] 38 | return real_url 39 | else: 40 | raise Exception('未开播') 41 | else: 42 | raise Exception('直播间可能不存在') 43 | 44 | 45 | def get_real_url(rid): 46 | try: 47 | xl = XunLei(rid) 48 | return xl.get_real_url() 49 | except Exception as e: 50 | print('Exception:', e) 51 | return False 52 | 53 | 54 | if __name__ == '__main__': 55 | r = input('请输入迅雷直播房间号:\n') 56 | print(get_real_url(r)) 57 | -------------------------------------------------------------------------------- /yangshipin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/5/3 12:28 3 | # @Project: real-url 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | # CCTV-1: https://m.yangshipin.cn/video?type=1&vid=2000210103&pid=600001859 8 | # 需要替换 headers 中登陆的 cookie 9 | # 目前网页版链接有效时间很短,且每个IP每天的请求数量有限制 10 | # APP版本的链接放时间更长,但需要反编译获取ckey生成方式,以后再更新。 11 | 12 | import binascii 13 | import ctypes 14 | import time 15 | import uuid 16 | from urllib.parse import parse_qs 17 | 18 | import requests 19 | from Crypto.Cipher import AES 20 | 21 | 22 | def aes_encrypt(text): 23 | """ 24 | AES加密 25 | """ 26 | key = binascii.a2b_hex('4E2918885FD98109869D14E0231A0BF4') 27 | iv = binascii.a2b_hex('16B17E519DDD0CE5B79D7A63A4DD801C') 28 | pad = 16 - len(text) % 16 29 | text = text + pad * chr(pad) 30 | text = text.encode() 31 | cipher = AES.new(key, AES.MODE_CBC, iv) 32 | encrypt_bytes = cipher.encrypt(text) 33 | return binascii.b2a_hex(encrypt_bytes).decode() 34 | 35 | 36 | class YangShiPin: 37 | 38 | def __init__(self, rid): 39 | var = parse_qs(rid) 40 | vid, = var['vid'] 41 | pid, = var['pid'] 42 | 43 | platform = 4330701 44 | guid = 'ko7djb70_vbjvrg5gcm' 45 | txvlive_version = '3.0.37' 46 | tt = int(time.time()) 47 | jc = 'mg3c3b04ba' 48 | wu = f'|{vid}|{tt}|{jc}|{txvlive_version}|{guid}|{platform}|https://m.yangshipin.cn/|mozilla/5.0 (iphone; ' \ 49 | f'cpu||Mozilla|Netscape|Win32| ' 50 | 51 | u = 0 52 | for i in wu: 53 | _char = ord(i) 54 | u = (u << 5) - u + _char 55 | u &= u & 0xffffffff 56 | bu = ctypes.c_int32(u).value 57 | 58 | xu = f'|{bu}{wu}' 59 | # ckey是个aes加密,CBC模式,pkcs7填充 60 | ckey = ('--01' + aes_encrypt(xu)).upper() 61 | 62 | self.params = { 63 | 'cmd': 2, 64 | 'cnlid': vid, 65 | 'pla': 0, 66 | 'stream': 2, 67 | 'system': 1, 68 | 'appVer': '3.0.37', 69 | 'encryptVer': '8.1', 70 | 'qq': 0, 71 | 'device': 'PC', 72 | 'guid': 'ko7djb70_vbjvrg5gcm', 73 | 'defn': 'auto', 74 | 'host': 'yangshipin.cn', 75 | 'livepid': pid, 76 | 'logintype': 1, 77 | 'vip_status': 1, 78 | 'livequeue': 1, 79 | 'fntick': tt, 80 | 'tm': tt, 81 | 'sdtfrom': 113, 82 | 'platform': platform, 83 | 'cKey': ckey, 84 | 'queueStatus': 0, 85 | 'uhd_flag': 4, 86 | 'flowid': uuid.uuid4().hex, 87 | 'sphttps': 1, 88 | # 'callback': 'txvlive_videoinfoget_9046016361', 89 | } 90 | 91 | def get_real_url(self): 92 | headers = { 93 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' 94 | 'like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', 95 | 'referer': 'https://m.yangshipin.cn/', 96 | 'cookie': '' 97 | } 98 | res = requests.get('https://liveinfo.yangshipin.cn/', headers=headers, params=self.params).json() 99 | url = res.get('playurl', 0) 100 | if url: 101 | return url 102 | else: 103 | return res 104 | 105 | 106 | def get_real_url(rid): 107 | try: 108 | ysp = YangShiPin(rid) 109 | return ysp.get_real_url() 110 | except Exception as e: 111 | print('Exception:', e) 112 | return False 113 | 114 | 115 | if __name__ == '__main__': 116 | r = input('显示“无登录信息”,则需要填充cookie。请输入央视频地址:\n') 117 | print(get_real_url(r)) 118 | -------------------------------------------------------------------------------- /yizhibo.py: -------------------------------------------------------------------------------- 1 | # 获取一直播的真实流媒体地址。 2 | 3 | import requests 4 | import re 5 | 6 | 7 | class YiZhiBo: 8 | 9 | def __init__(self, rid): 10 | """ 11 | 一直播需要传入直播间的完整地址 12 | Args: 13 | rid:完整地址 14 | """ 15 | self.rid = rid 16 | self.s = requests.Session() 17 | 18 | def get_real_url(self): 19 | try: 20 | res = self.s.get(self.rid).text 21 | play_url, status_code = re.findall(r'play_url:"(.*?)"[\s\S]*status:(\d+),', res)[0] 22 | if status_code == '10': 23 | return play_url 24 | else: 25 | raise Exception('未开播') 26 | except Exception: 27 | raise Exception('获取错误') 28 | 29 | 30 | def get_real_url(rid): 31 | try: 32 | yzb = YiZhiBo(rid) 33 | return yzb.get_real_url() 34 | except Exception as e: 35 | print('Exception:', e) 36 | return False 37 | 38 | 39 | if __name__ == '__main__': 40 | r = input('请输入一直播房间地址:\n') 41 | print(get_real_url(r)) 42 | -------------------------------------------------------------------------------- /youku.py: -------------------------------------------------------------------------------- 1 | # 获取@优酷轮播台@的真实流媒体地址。 2 | # 优酷轮播台是优酷直播live.youku.com下的一个子栏目,轮播一些经典电影电视剧,个人感觉要比其他直播平台影视区的画质要好, 3 | # 而且没有平台水印和主播自己贴的乱七八糟的字幕遮挡。 4 | # liveId 是如下形式直播间链接: 5 | # “https://vku.youku.com/live/ilproom?spm=a2hcb.20025885.m_16249_c_59932.d_11&id=8019610&scm=20140670.rcmd.16249.live_8019610”中的8019610字段。 6 | 7 | import requests 8 | import time 9 | import hashlib 10 | import json 11 | 12 | 13 | class YouKu: 14 | 15 | def __init__(self, rid): 16 | """ 17 | 获取优酷轮播台的流媒体地址 18 | Args: 19 | rid: 直播间url中id=8019610,其中id即为房间号 20 | """ 21 | self.rid = rid 22 | self.s = requests.Session() 23 | 24 | def get_real_url(self): 25 | try: 26 | tt = str(int(time.time() * 1000)) 27 | data = json.dumps({'liveId': self.rid, 'app': 'Pc'}, separators=(',', ':')) 28 | url = 'https://acs.youku.com/h5/mtop.youku.live.com.livefullinfo/1.0/?appKey=24679788' 29 | cookies = self.s.get(url).cookies 30 | token = cookies.get_dict().get('_m_h5_tk')[0:32] 31 | sign = hashlib.md5(f'{token}&{tt}&24679788&{data}'.encode('utf-8')).hexdigest() 32 | params = { 33 | 't': tt, 34 | 'sign': sign, 35 | 'data': data 36 | } 37 | response = self.s.get(url, params=params).json() 38 | streamname = response.get('data').get('data').get('stream')[0].get('streamName') 39 | real_url = f'https://lvo-live.youku.com/vod2live/{streamname}_mp4hd2v3.m3u8?&expire=21600&psid=1&ups_ts=' \ 40 | f'{int(time.time())}&vkey= ' 41 | except Exception: 42 | raise Exception('请求错误') 43 | return real_url 44 | 45 | 46 | def get_real_url(rid): 47 | try: 48 | yk = YouKu(rid) 49 | return yk.get_real_url() 50 | except Exception as e: 51 | print('Exception:', e) 52 | return False 53 | 54 | 55 | if __name__ == '__main__': 56 | r = input('请输入优酷轮播台房间号:\n') 57 | print(get_real_url(r)) 58 | -------------------------------------------------------------------------------- /yuanbobo.py: -------------------------------------------------------------------------------- 1 | # 热猫直播:https://zhibo.yuanbobo.com/ 2 | import requests 3 | import re 4 | 5 | 6 | class YuanBoBo: 7 | 8 | def __init__(self, rid): 9 | self.rid = rid 10 | 11 | def get_real_url(self): 12 | with requests.Session() as s: 13 | res = s.get(f'https://zhibo.yuanbobo.com/{self.rid}').text 14 | stream_id = re.search(r"stream_id:\s+'(\d+)'", res) 15 | if stream_id: 16 | status = re.search(r"status:\s+'(\d)'", res).group(1) 17 | if status == '1': 18 | real_url = f'https://tliveplay.yuanbobo.com/live/{stream_id.group(1)}.m3u8' 19 | return real_url 20 | else: 21 | raise Exception('未开播') 22 | else: 23 | raise Exception('直播间不存在') 24 | 25 | 26 | def get_real_url(rid): 27 | try: 28 | th = YuanBoBo(rid) 29 | return th.get_real_url() 30 | except Exception as e: 31 | print('Exception:', e) 32 | return False 33 | 34 | 35 | if __name__ == '__main__': 36 | r = input('输入热猫直播房间号:\n') 37 | print(get_real_url(r)) 38 | -------------------------------------------------------------------------------- /yy.py: -------------------------------------------------------------------------------- 1 | # 获取YY直播的真实流媒体地址。https://www.yy.com/1349606469 2 | # 默认获取最高画质 3 | 4 | import requests 5 | import re 6 | import json 7 | 8 | 9 | class YY: 10 | 11 | def __init__(self, rid): 12 | self.rid = rid 13 | 14 | def get_real_url(self): 15 | headers = { 16 | 'referer': f'https://wap.yy.com/mobileweb/{self.rid}', 17 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 18 | 'Chrome/95.0.4638.69 Safari/537.36 ' 19 | } 20 | room_url = f'https://interface.yy.com/hls/new/get/{self.rid}/{self.rid}/1200?source=wapyy&callback=' 21 | with requests.Session() as s: 22 | res = s.get(room_url, headers=headers) 23 | if res.status_code == 200: 24 | data = json.loads(res.text[1:-1]) 25 | if data.get('hls', 0): 26 | xa = data['audio'] 27 | xv = data['video'] 28 | xv = re.sub(r'_0_\d+_0', '_0_0_0', xv) 29 | url = f'https://interface.yy.com/hls/get/stream/15013/{xv}/15013/{xa}?source=h5player&type=m3u8' 30 | res = s.get(url).json() 31 | real_url = res['hls'] 32 | return real_url 33 | else: 34 | raise Exception('未开播') 35 | else: 36 | raise Exception('直播间不存在') 37 | 38 | 39 | def get_real_url(rid): 40 | try: 41 | yy = YY(rid) 42 | return yy.get_real_url() 43 | except Exception as e: 44 | print('Exception:', e) 45 | return False 46 | 47 | 48 | if __name__ == '__main__': 49 | r = input('输入YY直播房间号:\n') 50 | print(get_real_url(r)) 51 | -------------------------------------------------------------------------------- /zhanqi.py: -------------------------------------------------------------------------------- 1 | # 获取战旗直播(战旗TV)的真实流媒体地址。https://www.zhanqi.tv/lives 2 | # 默认最高画质 3 | 4 | import json 5 | import re 6 | 7 | import requests 8 | 9 | 10 | class ZhanQi: 11 | 12 | def __init__(self, rid): 13 | """ 14 | 战旗直播间有两种:一种是普通直播间号为数字或字幕;另一种是官方的主题直播间,链接带topic。 15 | 所以先判断一次后从网页源代码里获取数字直播间号 16 | Args: 17 | rid:直播间链接,从网页源码里获取真实数字rid 18 | """ 19 | self.s = requests.Session() 20 | res = self.s.get(rid).text 21 | self.rid = re.search(r'"code":"(\d+)"', res).group(1) 22 | 23 | def get_real_url(self): 24 | res = self.s.get(f'https://m.zhanqi.tv/api/static/v2.1/room/domain/{self.rid}.json') 25 | try: 26 | res = res.json() 27 | videoid = res['data']['videoId'] 28 | status = res['data']['status'] 29 | except (KeyError, json.decoder.JSONDecodeError): 30 | raise Exception('Incorrect rid') 31 | 32 | if status == '4': 33 | # 获取gid 34 | res = self.s.get('https://www.zhanqi.tv/api/public/room.viewer') 35 | try: 36 | res = res.json() 37 | gid = res['data']['gid'] 38 | except KeyError: 39 | raise Exception('Getting gid incorrectly') 40 | 41 | # 获取cdn_host 42 | res = self.s.get('https://umc.danuoyi.alicdn.com/dns_resolve_https?app=zqlive&host_key=alhdl-cdn.zhanqi.tv') 43 | cdn_host, *_ = res.json().get('redirect_domain') 44 | 45 | # 获取chain_key 46 | data = { 47 | 'stream': f'{videoid}.flv', 48 | 'cdnKey': 202, 49 | 'platform': 128, 50 | } 51 | headers = { 52 | 'cookie': f'gid={gid}', 53 | } 54 | res = self.s.post('https://www.zhanqi.tv/api/public/burglar/chain', data=data, headers=headers).json() 55 | chain_key = res['data']['key'] 56 | url = f'https://{cdn_host}/alhdl-cdn.zhanqi.tv/zqlive/{videoid}.flv?{chain_key}&playNum=68072487067' \ 57 | f'&gId={gid}&ipFrom=1&clientIp=&fhost=h5&platform=128' 58 | return url 59 | else: 60 | raise Exception('No streaming') 61 | 62 | 63 | def get_real_url(rid): 64 | try: 65 | zq = ZhanQi(rid) 66 | return zq.get_real_url() 67 | except Exception as e: 68 | print('Exception:', e) 69 | return False 70 | 71 | 72 | if __name__ == '__main__': 73 | # 直播间链接类似:https://www.zhanqi.tv/topic/owl 或 https://www.zhanqi.tv/152600919 74 | r = input('输入战旗直播间的链接:\n') 75 | print(get_real_url(r)) 76 | -------------------------------------------------------------------------------- /zhibotv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time: 2021/5/13 20:27 3 | # @Project: real-url 4 | # @Author: wbt5 5 | # @Blog: https://wbt5.com 6 | 7 | import requests 8 | import sys 9 | 10 | class ZhiBotv: 11 | 12 | def __init__(self, rid): 13 | """ 14 | 中国体育&新传宽频,直播间地址如:https://v.zhibo.tv/10007 15 | Args: 16 | rid:房间号 17 | """ 18 | self.rid = rid 19 | self.params = { 20 | 'token': '', 21 | 'roomId': self.rid, 22 | 'angleId': '', 23 | 'lineId': '', 24 | 'definition': 'hd', 25 | 'statistics': 'pc|web|1.0.0|0|0|0|local|5.0.1', 26 | } 27 | self.BASE_URL = 'https://rest.zhibo.tv/room/get-pull-stream-info-v430' 28 | self.HEADERS = { 29 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 30 | 'Chrome/95.0.4638.69 Safari/537.36 ', 31 | 'Referer': 'https://www.zhibo.tv/live/' 32 | } 33 | 34 | def get_real_url(self): 35 | """ 36 | no streaming 没开播; 37 | non-existent rid 房间号不存在; 38 | :return: url 39 | """ 40 | with requests.Session() as s: 41 | res = s.get(self.BASE_URL, params=self.params, headers=self.HEADERS).json() 42 | if 'hlsHUrl' in res['data']: 43 | url = res['data'].get('hlsHUrl') 44 | if url: 45 | return url 46 | else: 47 | raise Exception('no streaming') 48 | else: 49 | raise Exception('non-existent rid') 50 | 51 | 52 | def get_real_url(rid): 53 | try: 54 | zbtv = ZhiBotv(rid) 55 | return zbtv.get_real_url() 56 | except Exception as e: 57 | print('Exception:', e) 58 | return False 59 | 60 | 61 | if __name__ == '__main__': 62 | try: 63 | r=sys.argv[1] 64 | except: 65 | r = input('请输入中国体育房间号:\n') 66 | print(get_real_url(r)) 67 | --------------------------------------------------------------------------------