├── CHANGELOG.md ├── LICENSE ├── README.md ├── img ├── logo │ ├── chrome.png │ ├── firefox.png │ ├── opera.png │ ├── safari.png │ └── warning.png └── screenshot │ ├── menu.png │ └── replace.png ├── index.html ├── replace_bilibili_bofqi.meta.js └── replace_bilibili_bofqi.user.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | * [2015-08-03] 2.62 :修复个人页面的兼容性 2 | * [2014-10-24] 2.61 :兼容一些改版 3 | * [2014-09-04] 2.60 :临时修正(应该没用) 4 | * [2014-08-18] 2.59 :新的 html5 接口链接 5 | * [2014-08-17] 2.58 :可选不向链接的锚点添加数据(启用后iqiyi视频必须使用生成页面) 6 | * [2014-08-14] 2.57 :修理视频标签和专题列表部分显示问题 7 | * [2014-08-13] 2.56 : /video/av 自动跳转 /video/av1/index1.html 处理 iqiyi 可以显示顶栏底栏 8 | * [2014-08-12] 2.55 :电脑端不显示的默认“生成页面”,所有链接上加hash以解决自动跳转问题,生成页面处理 /video/av/ ,生成页面可以只拿着aid 生成 (#7) 9 | * [2014-08-11] 2.54 :默认长按菜单出生成页面的链接,彻底解决 (#6);强制显示“生成页面”链接不再需要 #3 中提到的修改 10 | * [2014-08-10] 2.53 :iqiyi使用 secure.bilibili.com/secure, 页面? 11 | * [2014-07-31] 2.52 :先拿miniloader的api骗一下playurl,如果有问题尝试强制替换 12 | * [2014-07-21] 2.51 :修复在非bangumi-two页面读取新番列表的错误 13 | * [2014-07-21] 2.50 :修复404页面或未审核视频生成页面的错误 ([#3](https://github.com/tiansh/rbb/issues/3)) 14 | * [2014-07-14] 2.49 :继续修理GM2兼容性 15 | * [2014-07-01] 2.48 :兼容GM2,修正版权番选择播放器的视频的长按菜单 16 | * [2014-07-01] 2.47 :版权番选择播放器的视频会随机选择一个可以替换的视频做替换 17 | * [2014-07-01] 2.46 :支持版权番选择播放器的视频 18 | * [2014-06-18] 2.45 :完善对 bilibili.com 域名的支持,添加对Fx32+GM1的支持(请手动开启) 19 | * [2014-06-17] 2.44 :支持 bilibili.com 域名,并以其为默认域名 20 | * [2014-06-09] 2.43 :添加一个补档页面使用的接口获取视频信息 21 | * [2014-06-08] 2.42 :使用 bilibili.com 域名替换 interface, api 22 | * [2014-06-07] 2.41 :修复显示隐藏视频时对XML字符的二次转义 23 | * [2014-06-07] 2.40 :修理强制替换框不会立即消失的错误 24 | * [2014-05-27] 2.39 :添加对撞车视频的处理 25 | * [2014-05-26] 2.38 :因为API调用有频率限制,减少API调用,可能时优先考虑使用getPageList 26 | * [2014-05-24] 2.37 :元素属性上区别找到的隐藏视频和原来的视频,搜索相邻视频显示进度 27 | * [2014-05-16] 2.36 :二次元新番列表显示对手机隐藏的视频 28 | * [2014-05-15] 2.35 :修理无法找到不对应aid的视频的问题([#2](https://github.com/tiansh/rbb/issues/2)) 29 | * [2014-05-14] 2.34 :直接外站跳转或404的视频长按菜单也有专题链接了,404上生成的页面支持视频描述和标签等 30 | * [2014-05-12] 2.33 :二次元新番列表显示隐藏的视频 31 | * [2014-05-08] 2.32 :修理对页面广告的兼容性(因为我一直在用ABP所以没发现) 32 | * [2014-05-04] 2.31 :播放页面500也可以播给你看! 33 | * [2014-05-04] 2.30 :少量代码整理,修理下拉菜单为空时边框显示,修理无法加载到信息的视频的菜单项 34 | * [2014-04-29] 2.29 :没有播放器时,长按链接菜单将生成页面排到前面 35 | * [2014-04-28] 2.28 :改善推断视频地址算法,修理获取标题失败问题;鉴于最近的一些情况,在404页面启用脚本 36 | * [2014-04-26] 2.27 :生成页面增加显示番剧信息,修复生成页面长按鼠标菜单的相关问题 37 | * [2014-04-26] 2.26 :调整样式,增强Chrome/Oprea在用户空间页面的兼容性 38 | * [2014-04-19] 2.25 :修理Chrome/Oprea下显示专题链接的问题 39 | * [2014-04-19] 2.24 :长按鼠标菜单显示专题链接 40 | * [2014-04-15] 2.23 :长按选择播放位置的菜单中共享弹幕池的若干分页只显示最后一个分页 41 | * [2014-04-14] 2.22 :将脚本迁移到github 42 | * [2014-04-04] 2.21 :修复Chrome和Opera下不能开启打印调试信息的问题,更新include规则 43 | * [2014-03-28] 2.20 :通过unsafeWindow暴露给其他脚本的一些接口,由于原网站开始使用hash作为参数故停止对hash的修改 44 | * [2014-03-19] 2.19 :支持新的自动换分页 45 | * [2014-03-13] 2.18 :分页信息显示,设置缓存上限,不确定有用没用总之先加上对bilibili.cn域名的页面识别 46 | * [2014-03-09] 2.17 :改进根据相邻视频推断cid的算法,少量代码重构 47 | * [2014-03-04] 2.16 :显示视频源 48 | * [2014-02-24] 2.15 :我才不会说和0.3版本一样的更新信息我又得再写一遍呢,更新2.14把给乐视用的那个选择框给弄坏了 49 | * [2014-02-24] 2.14 :更改消息框位置 50 | * [2014-02-22] 2.13 :修理长按菜单小错误,分页选择更新样式,兼容Chrome/Opera上使用的Stylish,支持专题中的链接,改进无权限视频查找 51 | * [2014-02-17] 2.12 :更新长按鼠标时对多分页视频的显示,长按鼠标菜单添加选分页功能,修复强制替换播放器功能某些情况下会导致Flash被重新加载 52 | * [2014-02-15] 2.11 :更新对直播视频的识别,修复强制替换时不同播放器的显示,允许不替换播放器 53 | * [2014-02-14] 2.10 :修复替换播放器后显示全部分页,修复生成页面的网面全屏和浮动播放器,生成的页面支持多分页,更新长按菜单样式,添加强制替换功能 54 | * [2014-02-11] 2.9 :更新了对分页的处理,重构了部分代码修正一些错误 55 | * [2014-02-10] 2.8 :对于无权限浏览的视频,找不到播放器的时候,也显示评论和标题信息 56 | * [2014-02-08] 2.7 :使用生成页面的方式优化添加视频页面的显示,恢复通过html5视频链接获取cid方法 57 | * [2014-02-07] 2.6 :可以用换分页解决问题的,用换分页来解决,不替换播放器(影响速度,2.9取消) 58 | * [2014-01-30] 2.5 :不替换直播的播放器 59 | * [2014-01-27] 2.4 :在生成的页面上支持收藏、添加标签、添加评论等功能 60 | * [2014-01-27] 2.3 :更新并增加了一些API来源,“尝试”兼容Opera浏览器,尝试通过生成的页面解决直接跳转外站的视频 61 | * [2014-01-23] 2.2 :修理对space.bilibili.tv域名的兼容性问题 62 | * [2014-01-20] 2.1 :修理对bilibili.kankanews.com域名的兼容性问题 63 | * [2014-01-16] 2.0 :重构了代码,对错误信息、通过相邻视频推测视频地址、API和interface的调用等方便进行了改进,增加了本地缓存 64 | * [2014-01-07] 1.13 :支持bilibili.kankanews.com域名 65 | * [2014-01-06] 1.12 :替换后的视频支持新的浮动播放框 66 | * [2014-01-05] 1.11 :支持多分页视频,因为html5播放器链接和弹幕下载链接长期失效,现已禁用 67 | * [2013-11-22] 1.10 :拖拽链接时不出现选框;处理网(hui)站(yuan)抽(de)风(shi)时(jie)界面变回131111前的情况的页面 68 | * [2013-11-14] 1.9 :把链接改为了原来的链接,我也不知道他们为啥又用这个了。再有就是修正了一下提示信息的位置 69 | * [2013-11-12] 1.8 :添加11月11日新上线的客户端的passKey备用。调整获取Cid顺序,降低弹幕下载页面优先级(因为该页面经常崩溃) 70 | * [2013-11-11] 1.7 :B站界面改版,代码随之进行了一些调整,保持风格与网站一致,保持网页全屏功能正常 71 | * [2013-10-30] 1.6 :更新视频播放器的iframe的html为B站当前新的代码,提高兼容性 72 | * [2013-10-16] 1.5 :重写了对网络访问超时的判定,提高对网速过慢情况的兼容性,尤其是一些经过第三方中转的网络访问 73 | * [2013-10-11] 1.4 :添加对获取cid的页面响应超时的处理。最近B站经常会各种卡死…… 74 | * [2013-10-07] 1.3 :更新全屏修正一段的代码,支持新的播放器地址 75 | * [2013-10-06] 1.2 :默认使用新的播放器地址,不对天国的Flash游戏分区的视频进行替换 76 | * [2013-10-05] 1.1 :添加长按菜单无法在B站观看时的出错信息,添加对形如acg.tv/avXXX链接的支持 77 | * [2013-09-27] 1.0 :增加对跳转到外站的视频的支持(详见说明),完成所有基本功能,所以版本号改到1.0~ 78 | * [2013-09-20] 0.4 :稍微改了改通过临近视频的cid推断cid的算法,效果比原来好了一点的样子~ 79 | * [2013-09-19] 0.3 :你们可以无视脑残写错了点东西然后就直接去更新了…… 80 | * [2013-09-19] 0.2 :添加了通过相邻av号查找cid的功能,可用于未审核或仅限会员的视频。识别新的播放器地址。 81 | * [2013-08-31] 0.1 :初始版本 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/README.md -------------------------------------------------------------------------------- /img/logo/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/logo/chrome.png -------------------------------------------------------------------------------- /img/logo/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/logo/firefox.png -------------------------------------------------------------------------------- /img/logo/opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/logo/opera.png -------------------------------------------------------------------------------- /img/logo/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/logo/safari.png -------------------------------------------------------------------------------- /img/logo/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/logo/warning.png -------------------------------------------------------------------------------- /img/screenshot/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/screenshot/menu.png -------------------------------------------------------------------------------- /img/screenshot/replace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansh/rbb/b08a3dcc5f8e5eb1a2f2ccb63bab6aeca0738437/img/screenshot/replace.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Replace bilibili bofqi 7 | 8 | 9 | 10 | 47 | 81 | 82 | 83 |
84 |

Replace bilibili bofqi

85 |
最新版本:2.62
86 |
87 | 93 |
94 | 安装脚本 95 |
96 |
97 | 报告Bug 98 | GitHub页面上报告错误 99 |
100 |
101 | 私信我 102 | 在bilibili上私信作者 103 |
104 |
105 |
106 |
107 |
108 |

安装教程

109 |
110 | 115 | 122 |
123 | Mozilla Firefox 124 |
125 | 使用Mozilla Firefox浏览器的用户,需要安装GreaseMonkey附加组件以运行本脚本。
126 | 如果您已经安装了这个附加组件,点此安装本脚本,在弹出的对话框中确认脚本安装即可。 127 |
128 |
129 |
可以使用Scriptish附加组件吗?
130 |
本脚本基本支持Scriptish附加组件,如果使用Scriptish附加组件,建议到该附加组件的GitHub主页上下载Nightly版本以保证较好的兼容性。推荐使用GreaseMonkey以获得最好的效果。
131 |
132 |
133 |
134 | Google Chrome 135 |
136 | 使用Google Chrome的用户,需要安装TamperMonkey 扩展程序以运行本脚本。
137 | 如果您已经安装了这个扩展程序,点此安装本脚本,在打开的页面中确认脚本安装即可。 138 |
139 |
140 |
141 | Opera 142 |
143 | 使用Opera的用户,需要安装TamperMonkey Beta 扩展以运行本脚本。
144 | 如果您已经安装了这个扩展,点此安装本脚本,在打开的页面中确认脚本安装即可。 145 |
146 |
147 |
可以使用Violent monkey附加组件吗?
148 |
本脚本同样支持最新版的Violent monkey,可能有少量兼容性问题,所以不推荐使用Violent monkey运行本脚本。但这些兼容问题不影响一般使用。
149 |
150 |
151 |
152 | Safari 153 |
154 | 在麦金塔系统上使用Safari的用户,需要安装JavaScript Blocker以运行本脚本。
155 | 如果您已经安装了这个扩展,点此安装本脚本,在打开的页面中确认脚本安装;之后浏览bilibili的网页,在JavaScript Blocker的下拉菜单中允许本脚本执行。 156 |
157 |
158 | 159 |
160 |
161 |

屏幕截图

162 |
163 |
164 | Replace bilibili bofqi 长按鼠标显示的菜单 165 |
Replace bilibili bofqi 长按视频链接可以出现选择播放位置的菜单,适用于直接跳转到外站的视频
166 |
167 |
168 | Replace bilibili bofqi 成功替换播放器 169 |
Replace bilibili bofqi 检测到替换播放器可以正常播放后会自动替换播放器
170 |
171 |
172 |

恢复原生播放器

173 |
174 |
脚本会自动将外站的播放器替换为原生的播放器。
175 |
脚本主要作用于bilibili网站中一些不是原生播放器的视频。脚本会自动查找对应的原生播放器是否可用,若找到且确认可用会自动替换播放器。
176 |
这个过程主要分为查找视频地址(即cid)和检查视频地址是否可以正常播放两个步骤。检查视频是否可用的意义在于避免替换了不支持的视频后出现“非常抱歉”的16秒视频。
177 |
如果检查视频播放花费了太多的时间,脚本会询问是否忽略检查直接替换。如果你发现播放器左下角显示“加载视频地址…[完成]”,那么说明替换播放器已经成功。如果之后出现无限小电视的情况,可能是网络的问题,或者是视频源的问题。
178 |
检查是否可以正常播放时有如下几种常见错误: 179 |
    180 |
  • “视频不允许在您当前所在地区播放”:有时候这类问题可以通过强制替换后反复刷新来解决(强制替换后顶部有刷新用按钮,请勿直接刷新网页)。此外更为稳定的做法,因为这些视频不允许在中国大陆等地区(可能包括日本)播放,所以您可以使用一个在这些地区以外的代理并将http://interface.bilibili.com/playurl?*作为代理规则加入到代理列表中。关于什么是代理、怎么使用代理等问题不在本文的讨论范围内,请自行Google。
  • 181 |
  • “(该视频当前不可用)”或“无法替换**源的视频”:一些视频可能无法替换为原始的播放器,还有一些视频可能需要等待几小时到一天的时间才能替换播放器。如果该视频新出不久,而且您不是急着马上要看到的话,可以考虑过一段时间之后再试试。
  • 182 |
183 |
184 | 185 |
186 |

长按鼠标菜单

187 |
188 |
在视频连接上按住鼠标左键,可以出现一个选择播放位置或选择分页的菜单。
189 |
有些视频点击后自动跳转到其他站点,还有些会显示404,对于这类视频,您可以在视频链接上按住鼠标左键。脚本会弹出一个选择播放位置的菜单。在其中选择“生成页面”即可使用一个伪造的视频页面观看视频。如果脚本检查到视频不能正常播放,会在菜单的顶部给出错误信息进行提示。
190 |
对于多分页的视频,在链接上长按鼠标会弹出一个选择分页的菜单,你可以直接选择想要的分页而不必先打开第一个分页。
191 |
应用举例:av782436: 【10月】KILL LA KILL 01av548141: 【4月】我的妹妹不可能那么可爱 第二季 01(需要将脚本应用到本页面,或在bilibili站点中使用,长按相应链接生效)。
192 |
193 |

部分绕过用户限制

194 |
195 |
脚本可以处理部分没有权限收看的视频。
196 |
对于一些审核不通过的视频,或者在未登录的情况下访问一些仅限会员收看的视频。本脚本会试图通过相邻的视频地址推算当前视频的地址。
197 |
由于技术限制,这个推算很可能是不准确的,可能会有显示了错误的视频或者漏掉了一些分页的情况(一般不会发生多的情况)。效果上没什么保障。而且这一功能一般不太稳定,如果失败可以尝试刷新两次。这一功能仅为不准确地推断视频地址,不推荐长期用本脚本作为越过登录检查的工具。
198 |
如果您需要访问仅限会员收看的视频,建议您注册一个帐号。或者选用去其他通过第三方服务器获取视频地址的脚本。本脚本不会添加对第三方服务器的访问,所以不会对这一功能进行扩展。
199 | 203 |
注意,本功能只是因为上面的功能实现后所附带的功能,建议不要没事去翻那些没过审的视频,有些视频还是很毁三观的。
204 |
205 |

显示新番视频列表隐藏视频

206 |
207 |
由于一些众所周知的原因,一些视频不会显示在新番视频列表中,本脚本会重新加载新番视频列表并将他们显示出来。
208 |
你需要如处理直接跳转外站的视频一样的处理方法处理这类视频,虽然直接打开后可以在404页面上加载出视频,但效果不如生成页面好。
209 |
如果你需要高亮显示脚本添加出来的隐藏视频,你可以考虑安装样式Bilibili Hidden Bangumi Highlight
210 |
211 |

脚本权限及用户隐私声明

212 |
213 |
脚本使用了GreaseMonkey中的GM_xmlhttpRequestGM_getValueGM_setValueGM_deleteValueGM_addStyleunsafeWindow接口。
214 |
215 | 脚本使用GM_xmlhttpRequest用于网络访问,脚本进行的网络访问限于下列域名:
    216 |
  • www.bilibili.com
  • 217 |
  • www.bilibili.tv
  • 218 |
  • bilibili.kankanews.com
  • 219 |
  • secure.bilibili.tv
  • 220 |
  • static-s.bilibili.tv
  • 221 |
  • secure.bilibili.com
  • 222 |
  • api.bilibili.cn
  • 223 |
  • api.bilibili.com
  • 224 |
  • interface.bilibili.cn
  • 225 |
  • interface.bilibili.com
  • 226 |
  • static.hdslb.com
  • 227 |
228 | 这些域名都是bilibili站点的相关域名。
229 | 此外在长按链接弹出菜单时,脚本还会访问对应链接指向的页面。 230 |
231 |
脚本使用了GM_getValue GM_setValueGM_deleteValue用于本地存储,脚本在本地存储了:从网络上获取的cid和aid的对应关系。脚本缓存的数据仅供脚本自己使用,不会发送到网络上,本脚本、其他浏览器插件和用户有权限读取这些信息。
232 |
脚本使用了GM_addStyle用于向页面添加自定义的样式。
233 |
234 | 脚本使用了unsafeWindow用于一些对网页进行直接操作的功能,脚本使用该接口:
    235 |
  • 显示提示信息
  • 236 |
  • 修改页面内容,以仿造视频页面,显示会自动跳转到外站的视频。
  • 237 |
238 |
239 |
240 | 241 |

更新历史

242 |
243 | 262 |
263 | 264 |

关于

265 |
266 |
作者;B站ID:ts9(1378109)
267 |
脚本使用 GNU GPL v3 和 CC BY-SA 3.0 协议发布,你可以选择合适的协议使用和继续发布。
268 |
本脚本最早发布在 http://userscripts.org/scripts/show/176946 ,因为userscripts网站比较不稳定,现在主页搬移到了 https://tiansh.github.io/rbb/ 。
269 |
270 | 271 |

我的其他几个bilibili脚本

272 |
273 | 280 |
281 |
282 | 283 | 284 | -------------------------------------------------------------------------------- /replace_bilibili_bofqi.meta.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Replace bilibili bofqi 3 | // @namespace http://userscripts.org/users/ts 4 | // @description 替换哔哩哔哩弹幕网(bilibili.com, bilibili.tv, bilibili.kankanews.com)播放器为原生播放器,直接外站跳转链接可长按选择播放位置,处理少量未审核或仅限会员的视频。 5 | // @include /^http://([^/]*\.)?bilibili\.com(/.*)?$/ 6 | // @include /^http://([^/]*\.)?bilibili\.tv(/.*)?$/ 7 | // @include /^http://([^/]*\.)?bilibili\.kankanews\.com(/.*)?$/ 8 | // @version 2.62 9 | // @updateURL https://tiansh.github.io/rbb/replace_bilibili_bofqi.meta.js 10 | // @downloadURL https://tiansh.github.io/rbb/replace_bilibili_bofqi.user.js 11 | // @grant GM_xmlhttpRequest 12 | // @grant GM_getValue 13 | // @grant GM_setValue 14 | // @grant GM_deleteValue 15 | // @grant GM_addStyle 16 | // @grant unsafeWindow 17 | // @copyright 2013+, 田生 18 | // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html 19 | // @license CC Attribution-ShareAlike 4.0 International; http://creativecommons.org/licenses/by-sa/4.0/ 20 | // @run-at document-start 21 | // ==/UserScript== 22 | 23 | -------------------------------------------------------------------------------- /replace_bilibili_bofqi.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Replace bilibili bofqi 3 | // @namespace http://userscripts.org/users/ts 4 | // @description 替换哔哩哔哩弹幕网(bilibili.com, bilibili.tv, bilibili.kankanews.com)播放器为原生播放器,直接外站跳转链接可长按选择播放位置,处理少量未审核或仅限会员的视频。 5 | // @include /^http://([^/]*\.)?bilibili\.com(/.*)?$/ 6 | // @include /^http://([^/]*\.)?bilibili\.tv(/.*)?$/ 7 | // @include /^http://([^/]*\.)?bilibili\.kankanews\.com(/.*)?$/ 8 | // @version 2.62 9 | // @updateURL https://tiansh.github.io/rbb/replace_bilibili_bofqi.meta.js 10 | // @downloadURL https://tiansh.github.io/rbb/replace_bilibili_bofqi.user.js 11 | // @grant GM_xmlhttpRequest 12 | // @grant GM_getValue 13 | // @grant GM_setValue 14 | // @grant GM_deleteValue 15 | // @grant GM_addStyle 16 | // @grant unsafeWindow 17 | // @author 田生 18 | // @copyright 2013+, 田生 19 | // @license Mozilla Public License; https://www.mozilla.org/MPL/ 20 | // @run-at document-start 21 | // ==/UserScript== 22 | 23 | /* 24 | * 有!人!接!坑!吗! 25 | */ 26 | 27 | // 如果想要修改程序的相关配置,可以直接在这里修改。 28 | // 这些配置会存储到文件中,因此即便自动升级,也会保留之前的配置,不必禁用自动升级。 29 | // 如果为null则表示这里将使用原来设置的值或默认值。 30 | var config = { 31 | 'debug': null, // 是否打印调试信息(Boolean,默认false) 32 | 'cache_active': null, // 是否使用磁盘缓存(Boolean,默认true) 33 | 'cache_maxsize': null, // 缓存最大条目数 (Number,默认1000) 34 | 'check': null, // 是否检查替换后是否能正常播放(Boolean,默认true) 35 | 'export': null, // 是否向外部暴露接口(Boolean,默认true) 36 | 'netmax': null, // 通过相邻视频推测时最多检查多少个视频(Number,默认50) 37 | 'cmenu_type': null, // 是否显示复杂的菜单项(String,"default"需要时 "complete"总是 "simple"从不) 38 | 'hasharg': null, // 是否向所有链接的锚点中加入 rbb 参数(Boolean,默认true) 39 | 'clean': false, // 这里为true的话会清空所有本地设置,全部使用默认值,上面的所有设置将无效 40 | }; 41 | 42 | /* 43 | 44 | Replace bilibili bofqi 45 | 替换哔哩哔哩弹幕网(bilibili.com, bilibili.kankanews.com)播放器为原生播放器,直接外站跳转链接可长按选择播放位置,处理少量未审核或仅限会员的视频。 46 | 47 | 48 | 项目主页: https://tiansh.github.io/rbb/ 49 | 50 | 51 | 【历史版本】 52 | 53 | * 2.62 :修复个人页面的兼容性 54 | * 2.61 :修正对原站播放器的识别,不替换原站播放器视频 55 | * 2.60 :临时修正(应该没用) 56 | * 2.59 :新的 html5 接口链接 57 | * 2.58 :可选不向链接的锚点添加数据(启用后iqiyi视频必须使用生成页面) 58 | * 2.57 :修理视频标签和专题列表部分显示问题 59 | * 2.56 : /video/av 自动跳转 /video/av1/index1.html 处理 iqiyi 可以显示顶栏底栏 60 | * 2.55 :电脑端不显示的默认“生成页面”,所有链接上加hash以解决自动跳转问题,生成页面处理 /video/av/ ,生成页面可以只拿着aid 生成 (#7) 61 | * 2.54 :默认长按菜单出生成页面的链接,彻底解决 (#6);强制显示“生成页面”链接不再需要 #3 中提到的修改 62 | * 2.53 :iqiyi使用 secure.bilibili.com/secure, 页面? 63 | * 2.52 :先拿miniloader的api骗一下playurl,如果有问题尝试强制替换 64 | * 2.51 :修复在非bangumi-two页面读取新番列表的错误 (#4) 65 | * 2.50 :修复404页面或未审核视频生成页面的错误 (#3) 66 | * 2.49 :继续修理GM2兼容性 67 | * 2.48 :兼容GM2,修正版权番选择播放器的视频的长按菜单 68 | * 2.47 :版权番选择播放器的视频会随机选择一个可以替换的视频做替换 69 | * 之前的版本请到 https://github.com/tiansh/rbb/blob/master/CHANGELOG.md 查看 70 | 71 | 72 | 【关于】 73 | 74 | 脚本使用 GNU GPL v3 或 CC BY-SA 3.0 协议。 75 | 76 | */ 77 | 78 | var preLoaded = (function () { 79 | // 检查是否是生成用的页面,如果是的话则标记并隐藏内容 80 | var fakePage = function () { 81 | var prefix1 = '/video/av1/index_1.html'; 82 | var prefix2 = '/video/av/'; 83 | var match1 = location.pathname.indexOf(prefix1) === 0; 84 | var match2 = location.pathname.indexOf(prefix2) === 0; 85 | if (!match1 && !match2) return false; 86 | if (location.hash.indexOf('rbb=') === 0) return false; 87 | if (match2) location.replace(location.href.replace(prefix2, prefix1)); 88 | else { 89 | GM_addStyle('html { display: none; }'); 90 | document.title = '哔哩哔哩 - ( ゜- ゜)つロ 乾杯~ - bilibili'; 91 | } 92 | return true; 93 | }; 94 | return { 95 | 'fakePage': fakePage() 96 | }; 97 | }()); 98 | 99 | // 替换当前页面的播放器 100 | var cosmos = function () { 101 | 102 | var bilibili = { 103 | 'url': { 104 | 'bilibili': 'bilibili.com', 105 | 'host': [ 106 | 'www.bilibili.com', 107 | 'bilibili.kankanews.com', 108 | 'www.bilibili.tv', // 用户空间仍然在使用该域名 109 | 'www.bilibili.cn', // 保留,网站并未使用 110 | ], 111 | 'av': [ 112 | 'http://www.bilibili.com/video/av', 113 | 'http://bilibili.kankanews.com/video/av', 114 | 'http://acg.tv/av', 115 | 'http://www.bilibili.tv/video/av', // 可能已停用 116 | 'http://www.bilibili.cn/video/av', // 保留,网站并未使用 117 | ], 118 | 'video': 'http://{{host}}/video/av{{aid}}/index_{{pid}}.html', 119 | 'iframe': { 120 | 'secure': 'https://secure.bilibili.com/secure,', 121 | 'ssl': 'https://ssl.bilibili.com/secure,', // 保留,网站并未使用 122 | 'secure0': 'https://secure.bilibili.tv/secure,', // 可能已停用 123 | 'ssl0': 'https://ssl.bilibili.tv/secure,', // 可能已停用 124 | }, 125 | 'bofqi': 'https://secure.bilibili.com/secure,cid={{cid}}&aid={{aid}}', 126 | 'flash': [ 127 | 'https://static-s.bilibili.com/play.swf', 128 | 'https://static-s.bilibili.com/live-play.swf', 129 | // 历史播放器,可能已停用 130 | 'https://static-s.bilibili.tv/play.swf', 131 | 'https://static-s.bilibili.tv/live-play.swf', 132 | 'http://static.hdslb.com/play.swf', 133 | 'http://static.hdslb.com/live-play.swf', 134 | ], 135 | 'bflash': 'https://static-s.bilibili.com/play.swf?cid={{cid}}&aid={{aid}}', 136 | 'sp': { 137 | 'spview': 'http://api.bilibili.com/spview?spid={{spid}}&season_id={{season_id}}&bangumi=1', 138 | 'spid': 'http://api.bilibili.com/sp?spid={{spid}}', 139 | 'page': 'http://{{host}}/sp/{{title}}', 140 | }, 141 | 'view': [ 142 | { // 网页Flash播放器的passkey (batch参数是额外加上去的) 143 | 'url': 'http://api.bilibili.com/view?type=json&id={{aid}}&batch=1&appkey=8e9fc618fbd41e28', 144 | 'ua': navigator.userAgent, 145 | }, 146 | { // 手机客户端的passkey(苹果系统与安卓系统的区别在于platform参数和userAgent) 147 | 'url': 'http://api.bilibili.com/view?type=json&id={{aid}}&batch=1' + 148 | '&platform=ios&appkey=0a99fa1d87fdd38c', 149 | 'ua': 'bilianime/570 CFNetwork/672.0.8 Darwin/14.0.0', 150 | }, 151 | ], 152 | 'playurl': 'http://interface.bilibili.com/playurl?cid={{cid}}&appkey=8e9fc618fbd41e28', 153 | 'player': 'http://interface.bilibili.com/player?id=cid:{{cid}}&appkey=8e9fc618fbd41e28', 154 | 'suggest': 'http://{{host}}/suggest?term=av{{aid}}' + 155 | '&jsoncallback={{callback}}&rnd={{random}}&_={{date}}', 156 | 'html5': 'http://m.acg.tv/m/html5?aid={{aid}}&page={{pid}}', 157 | 'pagelist': 'http://{{host}}/widget/getPageList?aid={{aid}}', 158 | 'arc': 'http://{{host}}/html/arc/{{aid}}.html', 159 | }, 160 | 'text': { 161 | 'fail': { 162 | 'get': '获取cid失败,若刷新对此无效则可能对该视频无效。', 163 | 'getc': '获取cid失败,若刷新对此无效则可能对该视频无效。顺便找到了一些附近的隐藏视频……', 164 | 'check': '无法替换播放器,(网络访问出错或原视频链接已失效);视频源:{{source}}。(cid:{{cid}})', 165 | 'msg': '无法替换播放器,错误信息:{{msg}};视频源:{{source}}。(cid:{{cid}})', 166 | 'unsupport': '无法替换{{source}}源的视频。{{msg}}(cid:{{cid}})', 167 | 'default': '(加载失败)', 168 | 'unexpect': '(无法解析的服务器返回)', 169 | 'server': '(服务器太忙请重试)', 170 | 'network': '(网络访问出错)', 171 | 'notexist': '(该视频当前不可用)', 172 | 'live': '(不支持直播视频)', 173 | }, 174 | 'force': { 175 | 'replace': '强制替换', 176 | 'message': '已经替换为原始播放器,替换的播放器可以完成“加载视频地址…”吗?', 177 | 'reload': '否—刷新重试', 178 | 'rollback': '否—放弃替换', 179 | 'done': '是—完成替换', 180 | }, 181 | 'loading': { 182 | 'near': '正在试图通过相邻视频查找cid,需要一些时间,且可能不准确。', 183 | 'nearc': '正在{{cid}}周围搜索视频,已搜索{{num}}个视频……', 184 | 'check': '已得到cid,检查加载视频地址… (cid:{{cid}})', 185 | 'checks': '加载视频地址…', 186 | 'ignore': '跳过检查', 187 | 'wait': '等待加载', 188 | }, 189 | 'menu': { 190 | 'page': '生成网页', 191 | 'swf': '仅播放器', 192 | 'origen': '原始页面', 193 | 'auto': '自动选择', 194 | 'chose': '选择分页', 195 | 'sp': '专题页', 196 | }, 197 | 'succ': { 198 | 'replace': '已成功替换播放器,若无法正常播放请刷新页面或禁用替换。视频源:{{source}}。(cid:{{cid}})', 199 | 'add': '已成功找到第{{pid}}分页,分页可能有缺少或错误。(cid:{{cid}})', 200 | 'rollback': '禁用替换', 201 | }, 202 | 'title': '{{title}} - 哔哩哔哩 - ( ゜- ゜)つロ 乾杯~ - bilibili', 203 | 'disable': { 204 | 'message': '当前页面已禁用对播放器的替换。', 205 | 'redo': '撤销禁用', 206 | }, 207 | 'split': { 208 | 'pid': '、', 209 | }, 210 | 'source': { 211 | 'site': { 212 | 'iqiyi': '爱奇艺', 213 | 'sina': '新浪', 214 | 'qq': '腾讯', 215 | 'letv': '乐视', 216 | 'mletv': '乐视移动云', 217 | 'youku': '优酷', 218 | 'sohu': '搜狐', 219 | 'pptv': 'PPTV', 220 | 'local': '本地', 221 | }, 222 | 'other': '(其他网站)', 223 | 'unknown': '(未知)', 224 | 'multi': '(多个视频)', 225 | 'unsupport': ['iqiyi'], 226 | 'api': '(声称)', 227 | 'playurl': '(实际)', 228 | }, 229 | }, 230 | 'video': { 231 | 'ignore': [1, 1113, 8219], 232 | }, 233 | 'host': location.host, 234 | 'timeout': { 235 | 'press': 200, 236 | 'network': 1000, 237 | }, 238 | 'html': { 239 | 'button': function (value) { 240 | return ''; 241 | }, 242 | 'bofqi': function (url) { 243 | return [ 244 | '', 248 | '', 249 | '', 250 | ].join(''); 251 | }, 252 | 'msgbox': [ 253 | '
', 254 | '
{{text}}
', 255 | '
', 256 | ].join(''), 257 | 'menu': function (items, sp) { 258 | if (sp) items.unshift({ 259 | 'href': sp.href, 260 | 'title': bilibili.text.menu.sp, 261 | 'sp': true, 262 | }); 263 | return ['
', 264 | '
', 265 | '', 266 | '
', 267 | items.map(function genMenuItem(item, i) { 268 | return ['
', 269 | (item.href ? ['', 270 | (item.sp ? '' : ''), 271 | xmlEscape(item.title), 272 | ''].join('') : 273 | ['', xmlEscape(item.title), ''].join('')), 274 | (item.submenu ? ['
', 275 | item.submenu.map(genMenuItem).join(''), 276 | '
'].join('') : ''), 277 | '
'].join(''); 278 | }).join(''), 279 | '
'].join(''); 280 | }, 281 | 'menu2': function (menu, sp) { 282 | var menuLink = function (href, title, sp) { 283 | if (!sp) sp = ''; 284 | else sp = ''; 285 | return [ 286 | '', 287 | '', sp, xmlEscape(title), '', 288 | '', sp, xmlEscape(title), '', 289 | ''].join(''); 290 | }; 291 | return ['
', 292 | (sp ? (['
', 293 | menuLink(sp.href, sp.title, true), 294 | '
'].join('')) : ''), 295 | '
', menuLink(menu.href, menu.title), '
', 296 | menu.submenu.map(function (item) { 297 | return ['
', 298 | menuLink(item.href, item.title), 299 | '
'].join(''); 300 | }).join(''), 301 | '
'].join(''); 302 | }, 303 | // 简化的视频页面 304 | 'page': [ 305 | '', 307 | '', 308 | '', 309 | '', 310 | '{{title}} - 哔哩哔哩 - ( ゜- ゜)つロ 乾杯~ - bilibili', 311 | '', 312 | '', 314 | '', 315 | '', 316 | '', 317 | '', 318 | '', 319 | '', 320 | '', 321 | '', 322 | '', 323 | '
', 324 | // 关灯用 325 | '
', 326 | // 视频信息(仅标题) 327 | '
', 328 | '
', 329 | '

{{title}}

', 330 | '
 
', 331 | '
', 332 | '
', 333 | '', 334 | '', 340 | '
', 341 | '', 342 | '', 343 | '', 345 | '
', 346 | '
', 347 | '
', 348 | '
', 349 | '
', 350 | // 播放器 351 | '
', 352 | '', 355 | '', 356 | '', 357 | '
', 358 | // 新番专题信息 359 | '
', 360 | '
', 361 | // 标签和描述 362 | '
', 363 | '
', 364 | '
', 365 | '
    ', 366 | '', 367 | '增加TAG', 368 | '', 369 | '
    ', 370 | '
    {{description}}
    ', 371 | '
    ', 372 | '
    ', 373 | '
    ', 374 | // 评论 375 | '
    ', 376 | '
    评论列表
    ', 377 | '
    ', 378 | '', 381 | '
    ', 382 | '
    ', 383 | // 评分(功能不可用,该元素用于定位视频播放器) 384 | '
    评分&推荐
    ', 385 | '
    ', 386 | '
    ', 387 | '
    ', 388 | // 更新页面上的标签信息 389 | '', 397 | '', 398 | '', 399 | ].join('\n'), 400 | 'v_bgm_list': [ 401 | '
    ', 402 | '
    分集列表
    ', 403 | '
    ', 404 | '
    ', 405 | '
    查看更多
    ', 406 | '
    ', 407 | '
    ', 408 | '', 409 | '
    ', 410 | '{{sptitle}}', 411 | '
    {{bangumi}}话 相关视频{{relative}}
    ', 412 | '

    订阅

    ', 413 | '
    ', 414 | '
    ', 415 | ].join(''), 416 | 'dupinfo': ['
    ', 417 | '(本视频已撞车或被版权所有者申述,', 418 | '您可以到av{{aid}}查看对应视频)', 419 | '
    '].join(''), 420 | }, 421 | 'js': { 422 | 'page': 'http://static.hdslb.com/js/page.arc.js', 423 | 'list': [ 424 | 'http://static.hdslb.com/js/jquery.min.js', 425 | 'http://static.hdslb.com/js/jquery-ui.min.js', 426 | 'http://static.hdslb.com/js/base.core.v3.js', 427 | 'http://static.hdslb.com/js/page.arc.js', 428 | 'http://static.hdslb.com/js/video.min.js', 429 | ], 430 | // 接收来自播放器窗口的请求 431 | // 函数中代码来自 http://static.hdslb.com/js/page.arc.js 432 | // 为了兼容性目的添加了 .tv 相关域名 433 | 'post': ['javascript: void(function () {var c;', 434 | 'window.postMessage?(c=function(a){"https://secure.bilibili.com"!=a.origin', 435 | '&&"https://secure.bilibili.tv"!=a.origin&&"https://ssl.bilibili.com"!=a.origin', 436 | '&&"https://ssl.bilibili.tv"!=a.origin||"secJS:"!=a.data.substr(0,6)', 437 | '||eval(a.data.substr(6));', 438 | '"undefined"!=typeof console&&console.log(a.origin+": "+a.data)},', 439 | 'window.addEventListener?window.addEventListener("message",c,!1):', 440 | 'window.attachEvent&&window.attachEvent("onmessage",c)):', 441 | 'setInterval(function(){if(evalCode=__GetCookie("__secureJS"))', 442 | '{__SetCookie("__secureJS",""),eval(evalCode)}},1000);', 443 | '}());'].join(''), 444 | // 拉到下方时显示浮动播放器窗口的代码 445 | // 函数中代码来自 http://static.hdslb.com/js/page.arc.js 446 | 'float': ['javascript: void(function () {', 447 | 'var r=$("#bofqi"),o=r.offset().top+r.height()+100,h=0,j=!1;$(document).', 448 | 'scroll(function(){o==r.offset().top+r.height()+100||r.hasClass("float")||', 449 | '(o=r.offset().top+r.height()+100);$(window).scrollTop()>o?r.hasClass(', 450 | '"float")||(j||0==$(".comm").find("ul").length)||($(\'
    \')', 451 | '.insertBefore(r),$(\'
    \u56de\u5230\u9876\u90e8
    ', 452 | '
    \u70b9\u51fb\u6309\u4f4f\u62d6\u52a8
    ', 453 | '\u5173\u95ed
    \').prependTo(r),0<$(".huodong_bg").length&&$(".huodong_bg")', 454 | '.hide(),r.addClass("float").css({left:$(".rat").offset().left,opacity:0}).stop()', 455 | '.animate({opacity:1},300),730>=$(window).height()&&r.css({top:"inherit",bottom:"5px"}))', 456 | ':(j&&(j=!1),r.hasClass("float")&&(f(),$(".move",r).remove(),$(".dami").remove(),', 457 | 'r.removeClass("float"),r.css({left:"",top:"",bottom:""}),0<$(".huodong_bg").length&&', 458 | '$(".huodong_bg").show()))});r.hover(function(){r.hasClass("float")&&!h&&$(".move",r).show()},', 459 | 'function(){h||$(".move",r).hide()});$(r).delegate(".move","mousedown",function(e){h=1;', 460 | '$("body,#bofqi").addClass("noselect");$(this).addClass("on");$(\'
    \')', 461 | '.appendTo("body");var d=e.pageX-$(this).offset().left,g=e.pageY-$(this).offset().top;', 462 | '$(document).bind("mousemove",function(b){var l=b.clientX-d,c=b.clientY-g<=$(window).height()', 463 | '-r.height()?b.clientY-g:$(window).height()-r.height(),c=b.clientY-g>=$(window).height()-', 464 | 'r.height()-5?$(window).height()-r.height()-5:0>=b.clientY-g?0:b.clientY-g;r', 465 | '.css({left:l,top:c})})});$(r).delegate(".move","mouseup",function(b){f()});$(r)', 466 | '.delegate(".move .close","click",function(b){j=!0;f();$(".move",r).remove();$(".dami")', 467 | '.remove();r.removeClass("float");r.css({left:"",top:"",bottom:""});0<$(".huodong_bg")', 468 | '.length&&$(".huodong_bg").show()});$(r).delegate(".move .gotop","click",function(b){', 469 | '$("html,body").animate({scrollTop:$(".viewbox").offset().top},300)});var f=function(){', 470 | 'h=0;$(".mmask").remove();$(document).unbind("mousemove");$("body,#bofqi")', 471 | '.removeClass("noselect");$(".move",r).removeClass("on")}', 472 | '}());'].join(''), 473 | 'kwtags': ['javascript: void(function () {', 474 | 'aid = String({{aid}});', 475 | 'mid = String({{mid}});', 476 | 'spid = Number({{spid}});', 477 | 'kwtags(({{tag}} || "").split(","), []);', 478 | '}());'].join(''), 479 | 'showsp': ['javascript: void(function () {', 480 | 'var i, o;', 481 | 'document.querySelector(".tag").innerHTML = "";', 482 | 'aid = String({{aid}});', 483 | 'mid = String({{mid}});', 484 | 'spid = Number({{spid}});', 485 | 'try { kwtags({{tag}}, []); } catch (e1) { }', 486 | 'isSpAtt = isSpFav = "0";', 487 | 'if (AttentionList) {', 488 | 'for (i in AttentionList) {', 489 | 'o = AttentionList[i];', 490 | 'if ((o < 0) && (o * -1) == spid)', 491 | 'isSpAtt = isSpFav = "1";', 492 | '}', 493 | '}', 494 | 'try { showSpAdbtn(); } catch (e2) { }', 495 | '}());'].join(''), 496 | }, 497 | }; 498 | if (bilibili.url.host.indexOf(bilibili.host) === -1) 499 | bilibili.host = bilibili.url.host[0]; 500 | 501 | // 以setTimeout调用函数 502 | var call = function (f) { setTimeout(f, 0); }; 503 | 504 | // 刷新配置项 505 | bilibili.config = (function () { 506 | var ret = {}; 507 | // 这里是默认配置,请勿直接修改这里,修改配置请到文件开头 508 | var defaultConfig = { 509 | 'debug': false, 510 | 'cache_active': true, 511 | 'cache_maxsize': 1000, 512 | 'check': true, 513 | 'export': true, 514 | 'netmax': 50, 515 | 'cmenu_type': 'complete', 516 | 'hasharg': true, 517 | }; 518 | var readConfig = function (key) { 519 | if (config[key] === null) config[key] = GM_getValue(key, null); 520 | if (config[key] === null) config[key] = defaultConfig[key]; 521 | config[key] = defaultConfig[key].constructor(config[key]); 522 | if (config[key] !== config[key] || config.clean) config[key] = defaultConfig[key]; 523 | }; 524 | var flushConfig = function (key) { 525 | GM_setValue(key, config[key]); 526 | ret[key] = config[key]; 527 | }; 528 | return (function () { 529 | var keys = Object.keys(defaultConfig); 530 | keys.forEach(readConfig); 531 | keys.forEach(flushConfig); 532 | call(function () { debug('RBB config: %o', ret); }); 533 | return ret; 534 | }()); 535 | }()); 536 | 537 | // 打印调试信息 538 | var debug = (function () { 539 | if (bilibili.config.debug && console && console.log) 540 | try { return console.log.bind(console); } catch (e) { } 541 | return function () { }; 542 | }()); 543 | 544 | // 使用aid, cid等变量替换上述常量URL中{}内的相关内容 545 | var genStr = (function () { 546 | var key = function (x, y) { 547 | try { 548 | return [x].concat(y.split('.')) 549 | .reduce(function (a, b) { return a[b]; }) || null; 550 | } catch (e) { return null; } 551 | }; 552 | var find = function (str, datas) { 553 | return [null].concat(datas).reduce(function (ret, set) { 554 | if (ret !== null) return ret; 555 | return key(set, str) || null; 556 | }); 557 | }; 558 | return function (base, esc, datas) { 559 | base = base.replace(/{{([a-z]*)}}/g, function (o, i) { 560 | var ret = find(i, datas); 561 | if (ret === null) return ''; else return esc(ret); 562 | }); 563 | return base; 564 | }; 565 | }()); 566 | var genURL = function (url) { 567 | var datas = [].slice.call(arguments, 0).slice(1).concat(bilibili); 568 | return genStr(url, encodeURIComponent, datas); 569 | }; 570 | var genXML = function (xml) { 571 | var datas = [{ '<': '{', '>': '}' }] 572 | .concat([].slice.call(arguments, 0).slice(1)).concat(bilibili); 573 | return genStr(xml, function (s) { return s; }, datas); 574 | }; 575 | var genCode = function (url) { 576 | var datas = [].slice.call(arguments, 0).slice(1).concat(bilibili); 577 | return genStr(url, function (s) { return JSON.stringify(s); }, datas); 578 | }; 579 | 580 | // 通过链接获取主机地址 581 | var getHost = function (url) { 582 | var a = document.createElement('a'); 583 | a.href = url; 584 | return a.hostname; 585 | }; 586 | 587 | // XML字符转义 588 | var xmlEscape = function (s) { 589 | return String(s).replace(/./g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); 590 | }; 591 | 592 | // 处理地址中#后面的本地参数 593 | var hashArg = (function () { 594 | var href2A = function (href) { 595 | var a = document.createElement('a'); 596 | a.href = href; 597 | return a; 598 | }; 599 | // 向地址的本地参数中设置参数 600 | var set = function (key, val, a) { 601 | var f = false; 602 | var str = val === null ? '' : key + '=' + encodeURIComponent(val); 603 | if (typeof a === 'string') a = href2A(a); 604 | var hash = ((a || location).hash || '#').slice(1).split('&').map(function (kv) { 605 | var arg = kv.match(/^([^=]*)=(.*)$/); 606 | if (!arg || !arg[2] || arg[1] != key) return kv; 607 | f = true; return str; 608 | }).filter(function (s) { return s.length > 0; }); 609 | if (!f) hash = hash.concat([str]); 610 | (a || location).hash = ''; 611 | (a || location).hash = '#' + hash.join('&'); 612 | return (a || location).href; 613 | }; 614 | // 从地址的本地参数中读取参数 615 | var get = function (key, a) { 616 | var val = null; 617 | if (typeof a === 'string') a = href2A(a); 618 | ((a || location).hash || '#').slice(1).split('&').forEach(function (kv) { 619 | var arg = kv.match(/^([^=]*)=(.*)$/); 620 | if (!arg || !arg[2] || arg[1] != key) return kv; 621 | val = decodeURIComponent(arg[2]); 622 | }); 623 | return val; 624 | }; 625 | return { 'get': get, 'set': set }; 626 | }()); 627 | 628 | // 注册和调用一些函数用的 629 | var callbackEvents = function () { 630 | var funcs = []; 631 | var call = function () { 632 | var self = this, arg = arguments; 633 | funcs.forEach(function (f) { 634 | try { f.apply(self, arg); } catch (e) { } 635 | }); 636 | }; 637 | call.add = function (callback) { funcs.push(callback); }; 638 | return call; 639 | }; 640 | 641 | // 初始化消息框 642 | var initShowMsg = function () { 643 | var msgBox = document.querySelector('#rbb-message'); 644 | if (msgBox) return msgBox; 645 | msgBox = document.createElement('div'); msgBox.id = 'rbb-message'; 646 | document.body.parentNode.appendChild(msgBox); 647 | msgBox.style.top = '80px'; 648 | return msgBox; 649 | }; 650 | 651 | var genMsgBox = function (rel, text, timeout, type, zIndex) { 652 | var mb = document.createElement('div'); 653 | mb.innerHTML = genXML(bilibili.html.msgbox, { 'text': text, 'type': type }); 654 | mb = mb.firstChild; 655 | mb.style.left = '0px'; mb.style.top = '80px'; 656 | if (zIndex) mb.style.zIndex = zIndex; 657 | if (timeout > 0) setTimeout(function () { mb.parentNode.removeChild(mb); }, timeout); 658 | document.body.appendChild(mb); 659 | return mb; 660 | }; 661 | 662 | // 显示消息的提示框 663 | var showMsg = (function () { 664 | var zIndex = 11000; 665 | var showMsg = function (msg, timeout, type, buttons) { 666 | var msgBox = initShowMsg(); 667 | debug('MessageBox: %o - %o', msg, buttons); 668 | if (buttons) debug('MessageBox Buttons: %o', buttons); 669 | var text = xmlEscape(msg); 670 | if (buttons) text += buttons.map(function (button) { 671 | return bilibili.html.button(button.value); 672 | }).join(''); 673 | try { 674 | var msgbox = genMsgBox(msgBox, text, timeout, type, zIndex++); 675 | var buttonList = msgbox.querySelectorAll('button'); 676 | [].slice.call(buttonList, 0).forEach(function (button, i) { 677 | button.addEventListener('click', buttons[i].click); 678 | }); 679 | return msgbox; 680 | } catch (e) { debug('failed to show this message: %o', e); } 681 | }; 682 | showMsg.gotCid = function (cid) { 683 | if (cid) msgBox.setAttribute('cid', cid); 684 | else msgBox.setAttribute('cid', 'N/A'); 685 | }; 686 | return showMsg; 687 | }()); 688 | 689 | // 从URL中截取aid(av), pid号 690 | var videoPage = function (href, nullpage) { 691 | var aid, pid; 692 | if (typeof href !== 'string') return null; 693 | if (!bilibili.url.av.map(function (h) { return href.indexOf(h) === 0; }) 694 | .reduce(function (x, y) { return x || y; })) return null; 695 | if (!(aid = Number(href.replace(/^[^#]*av(\d+).*$/, '$1')))) return null; 696 | pid = Number(hashArg.get('page', href)) || 697 | Number(href.replace(/^[^#]*av\d+\/index_(\d+)\.html(\?.*)?(#.*)?$/, '$1')) || null; 698 | if (!nullpage && pid === null) pid = 1; 699 | try { 700 | if (aid === 1 && pid === 1 && href.match(/#.*rbb=/)) { 701 | var rbb = JSON.parse(hashArg.get('rbb', href)); 702 | debug('rbb=%o', rbb); 703 | aid = rbb.aid || null; pid = rbb.pid || null; 704 | } 705 | } catch (e) { } 706 | debug('page %s - aid: %o, pid: %o', href, aid, pid); 707 | if (!aid) return null; else return { 'aid': aid, 'pid': pid }; 708 | }; 709 | 710 | // 检查是否是原生播放器 711 | var isBilibiliBofqi = function (doc) { 712 | var any = function (arr) { return arr.reduce(function (x, y) { return x || y; }); }; 713 | if (any(bilibili.url.flash.map(function (flash) { 714 | return !!doc.querySelector('#bofqi embed[src="' + flash + '"]'); 715 | }))) return true; 716 | if (any(Object.keys(bilibili.url.iframe).map(function (iframe) { 717 | return !!doc.querySelector('#bofqi iframe[src^="' + bilibili.url.iframe[iframe] + '"]:not([src*="iqiyi"])'); 718 | }))) return true; 719 | if ((function () { 720 | var scr = document.querySelector('#bofqi script'); 721 | return scr && scr.textContent.indexOf('EmbedPlayer') !== -1; 722 | }())) return true; 723 | return false; 724 | }; 725 | 726 | // 检查页面中是否有播放器 727 | var hasBofqi = function (doc) { 728 | return !!doc.querySelector('#bofqi'); 729 | }; 730 | 731 | // 检查是否是选择视频源的版权番 732 | var isCopyrightVideoSelect = function (doc) { 733 | if (doc.querySelector('.player-placeholder')) return true; 734 | var i = doc.querySelector('#alist>*'); 735 | if (i && i.innerHTML.match(/\[\[(.*)\]\]/)) return true; 736 | return false; 737 | }; 738 | 739 | // 检查是否支持此页面 740 | var validPage = function () { 741 | var id = videoPage(location.href); 742 | // 仅在视频页面运行本脚本 743 | if (!id || !id.aid) return null; 744 | // 页面中要有播放器或者错误信息 745 | if (!document.querySelector('.z-msg') && 746 | !document.querySelector('#bofqi') && 747 | !document.querySelector('.errmsg') && 748 | !document.querySelector('.container .notice') && 749 | document.title !== 'bilibili - 提示') return null; 750 | // 忽略已经使用原生播放器的视频 751 | if (isBilibiliBofqi(document)) return null; 752 | // 天国的Flash游戏分区不算外站播放器 753 | if (document.querySelector('.tminfo a.on[href="/video/game-flash-1.html"]')) return null; 754 | // 忽略av1等无视频页面 755 | if (bilibili.video.ignore.indexOf(id.aid) !== -1) return null; 756 | // 没有播放器的页面不计算pid 757 | if (!document.querySelector('#bofqi') && id && id.pid) id.pid = null; 758 | // 强制不替换的 759 | if (hashArg.get('rbb') === 'false') return null; 760 | // 选择版权番播放器的视频pid要-1 761 | if (isCopyrightVideoSelect(document) && !--id.pid) id.pid = null; 762 | if (id) debug('Av%d, page%d', id.aid, id.pid); 763 | return id; 764 | }; 765 | 766 | // 获取id 767 | // regist注册一个函数 768 | // concat与其他的getId连接 769 | var getId = function () { 770 | var funclist = [].slice.call(arguments, 0); 771 | var outer = !!funclist.length; 772 | // 注册一种查找方法 773 | var regist = function (func) { 774 | funclist.push(func); 775 | return func; 776 | }; 777 | // 依次使用几个getId 778 | var concat = function () { 779 | var funcs = [].slice.call(arguments, 0); 780 | if (!outer) { 781 | funcs = [this].concat(funcs); 782 | return getId.apply(this, funcs); 783 | } else { 784 | funclist = funclist.concat(funcs); 785 | return this; 786 | } 787 | }; 788 | // 有超时的调用某个查找id的函数 789 | // 调用func(iid, onsucc, onerror) 790 | // 当超时或func回调时调用next,func回调时调用onsucc或onerror 791 | var callTimeout = function (func, iid, onsucc, onerror, next) { 792 | var timer = null, called = false; 793 | var done = function (timeout) { 794 | if (!timeout && timer) clearTimeout(timer); 795 | if (timer !== null) { timer = null; call(next); } 796 | }; 797 | var resp = function (cb) { 798 | return function () { 799 | if (cb && !called) { 800 | cb.apply(outer ? this : func, arguments); 801 | called = true; 802 | } 803 | done(!cb); 804 | }; 805 | }; 806 | if (!outer) timer = setTimeout(resp(), bilibili.timeout.network); 807 | else timer = false; 808 | try { func(iid, resp(onsucc), resp(onerror)); } 809 | catch (e) { 810 | debug('get id failed to call function %o. Error msg: %o', func, e); 811 | } 812 | }; 813 | // 逐个调用查找id的函数 814 | var run = function (iid, onsucc, onerror) { 815 | // 获取注册的函数列表 816 | var ways = funclist; 817 | if (ways.length === 0) { call(onerror); return; } 818 | var processing = 0, done = false, remained = ways.length; 819 | // 当一个函数返回失败时 820 | var err = function () { 821 | var self = this, arg = arguments; 822 | if (!done && --processing <= 0 && remained <= 0) call(function () { onerror.apply(self, arg); }); 823 | debug('done = %s, processing = %d, remained = %d', done, processing, remained); 824 | }; 825 | // 当一个函数返回成功时 826 | var succ = function () { 827 | if (done) return; done = true; 828 | var self = this, arg = arguments; 829 | call(function () { onsucc.apply(self, arg); }); 830 | }; 831 | // 开始调用一个函数 832 | var act = function (f, next) { 833 | if (done) return; 834 | remained--; processing++; 835 | call(function () { callTimeout(f, iid, succ, err, next); }); 836 | }; 837 | // 依次调用注册的函数 838 | (function tryGetId(i) { 839 | if (done || i === ways.length) return; 840 | debug('Get id %d: %o, with iid = %o', i, ways[i], iid); 841 | act(ways[i], function () { tryGetId(i + 1); }); 842 | }(0)); 843 | }; 844 | if (!outer) run.regist = regist; 845 | run.concat = concat; 846 | return run; 847 | }; 848 | 849 | // 内存信息缓存 850 | var infoCache = function () { 851 | var data = {}; 852 | var put = function (key, value) { 853 | if (!value) return null; 854 | return (data[key] = value); 855 | }; 856 | var get = function (key, defaultValue) { 857 | var value = data[key]; 858 | if (typeof value === 'undefined') return defaultValue; 859 | return data[key]; 860 | }; 861 | var del = function (key) { delete data[key]; }; 862 | var clear = function () { data = {}; }; 863 | return { 'get': get, 'put': put, 'del': del, 'clear': clear }; 864 | }; 865 | var videoInfo = infoCache(), playurlInfo = infoCache(); 866 | var pageInfo = infoCache(), videoSpInfo = infoCache(); 867 | var candidateCidInfo = infoCache(); 868 | 869 | // 文件信息缓存 870 | // 将网络访问getId的结果缓存起来 871 | // 不过实际上是通过注册到getId中的函数直接调用实现的读写 872 | var getIdCache = (function () { 873 | var caches = {}; 874 | var cache = function (cacheKey) { 875 | // get/putData读写GM提供的接口存储数据 876 | var getData = function () { 877 | var data; 878 | try { data = JSON.parse(GM_getValue(cacheKey, '{}')) || {}; } 879 | catch (e) { data = {}; } 880 | return data; 881 | }; 882 | var putData = function (data) { 883 | GM_setValue(cacheKey, JSON.stringify(data)); 884 | }; 885 | var clearData = function (data) { 886 | GM_deleteValue(cacheKey); 887 | }; 888 | var get = function (key, defaultValue) { 889 | var value = getData()[key]; 890 | if (typeof value === 'undefined') return defaultValue; 891 | return value; 892 | }; 893 | var put = function (key, value) { 894 | var data = getData(); 895 | data[key] = value; 896 | putData(data); 897 | return value; 898 | }; 899 | var del = function (key) { 900 | var data = getData(); 901 | delete data[key]; 902 | putData(data); 903 | }; 904 | var clear = function () { 905 | clearData(); 906 | }; 907 | if (Object.keys(getData()).length > bilibili.config.cache_maxsize) clearData(); 908 | return { 'get': get, 'put': put, 'del': del, 'clear': clear }; 909 | }; 910 | if (bilibili.config.cache_active) { 911 | // 开启了缓存 912 | return function (cacheKey) { 913 | return (caches[cacheKey] = caches[cacheKey] || cache(cacheKey)); 914 | }; 915 | } else { 916 | return function (cacheKey) { 917 | // 没开启文件缓存的话转而用内存的缓存 918 | cache(cacheKey).clear(); 919 | return infoCache(); 920 | }; 921 | } 922 | }()); 923 | 924 | // getCid分为三种以方便之后根据不同需要决定优先关系 925 | var getCidDirect = getId(), getCidAPI = getId(); 926 | var getCidUndirect = getId(), getCidCached = getId(); 927 | var getAid = getId(); 928 | var getTitle = getId(); 929 | // 对getCid和getAid的结果分别进行缓存 930 | var getCidCache = getIdCache('chatid'), getAidCache = getIdCache('av'); 931 | 932 | // 获取视频源名称 933 | var getVideoSource = function (id, cid) { 934 | if (id.pid === null && typeof cid !== 'number' && Object.keys(cid).length === 1) 935 | id.pid = Object.keys(cid)[0]; 936 | if (id.pid === null) return { 'name': bilibili.text.source.multi }; 937 | var source = bilibili.text.source; 938 | var name = function (type) { return source.site[type] || source.other; }; 939 | var list, playurl, ret = null; 940 | try { 941 | try { 942 | (function () { 943 | var l = videoInfo.get(id.aid).list; 944 | Object.keys(l).forEach(function (x) { 945 | if (l[x].page === id.pid) list = l[x].type; 946 | }); 947 | }()); 948 | } catch (e1) { list = null; } 949 | try { playurl = playurlInfo.get(cid).querySelector('from').textContent; } 950 | catch (e2) { playurl = null; } 951 | if (!playurl && !list) return { 'name': source.unknown }; 952 | if (playurl === list) return { 'name': name(playurl) }; 953 | if (list && !playurl && source.unsupport.indexOf(list) !== -1) 954 | return { 'unsupport': true, 'name': name(list) }; 955 | if (!list || !playurl || list === playurl) 956 | return { 'name': name(list || playurl) }; 957 | return { 958 | 'name': [ 959 | name(list), source.api, name(playurl), source.playurl].join('') 960 | }; 961 | } catch (e) { } 962 | if (ret === null) return source.unknown; 963 | else return ret; 964 | }; 965 | 966 | // 获取缓存的cid信息 967 | // 用于通过相邻cid推测时等不需要完全准确时使用 968 | // 或其他方法失效时使用 969 | var getCidByCache = (function (getCid, getCidCache, getAidCache) { 970 | return getCid.regist(function (id, onsucc, onerror) { 971 | var cids = getCidCache.get(id.aid); 972 | debug('Reading Cache, aid = %d -> cid = %s', id.aid, JSON.stringify(cids)); 973 | if (cids) { 974 | if (id.pid) { 975 | if (cids[id.pid]) onsucc(cids[id.pid]); 976 | else onerror(); 977 | } else onsucc(cids); 978 | } else onerror(); 979 | }); 980 | }(getCidCached, getCidCache, getAidCache)); 981 | 982 | // 通过API获取Cid 983 | var getCidByApi = (function (getCid, getCidCache, getAidCache) { 984 | var registGetCidByApi = function (view, i) { 985 | return getCid.regist(function (id, onsucc, onerror) { 986 | GM_xmlhttpRequest({ 987 | 'method': 'GET', 988 | 'url': genURL(view.url, { 'aid': id.aid }), 989 | 'headers': { 990 | 'User-Agent': view.ua, 991 | 'Cookie': document.cookies, 992 | }, 993 | 'onload': function (resp) { 994 | var data, cids = {}, cid; 995 | try { 996 | // 解析返回结果 997 | data = JSON.parse(resp.responseText); 998 | debug('view api response: %o', data); 999 | Object.keys(data.list).forEach(function (k) { cids[data.list[k].page] = data.list[k].cid; }); 1000 | // 缓存数据 1001 | if (cids && Object.keys(cids).length) getCidCache.put(String(id.aid), cids); 1002 | Object.keys(cids).forEach(function (pid) { 1003 | getAidCache.put(String(cids[pid]), { 'aid': id.aid, 'pid': Number(pid) }); 1004 | }); 1005 | videoInfo.put(id.aid, data); 1006 | debug('Get Cid Cached: aid = %d -> cid = %s', id.aid, JSON.stringify(cids)); 1007 | // 得到需要的cid 1008 | if (id.pid) cid = cids[id.pid]; else cid = cids; 1009 | } catch (e) { 1010 | debug('Error while getting cid by using API[%d]: %s', i, String(e)); 1011 | debug('Server response: %o', resp.responseText); 1012 | cid = null; 1013 | } 1014 | if (!cid) call(onerror); else call(function () { onsucc(cid); }); 1015 | debug('Got cid = %o by using API[%d]', cid, i); 1016 | }, 1017 | 'onerror': function () { 1018 | call(onerror); 1019 | debug('Failed to get cid via API'); 1020 | } 1021 | }); 1022 | }); 1023 | }; 1024 | return bilibili.url.view.map(registGetCidByApi); 1025 | }(getCidAPI, getCidCache, getAidCache)); 1026 | 1027 | // 通过提供给播放器以自动下一分页的接口获取cid 1028 | var getCidByPageList = (function (getCid, getCidCache, getAidCache) { 1029 | getCid.regist(function (id, onsucc, onerror) { 1030 | GM_xmlhttpRequest({ 1031 | 'method': 'GET', 1032 | 'url': genURL(bilibili.url.pagelist, { 'aid': id.aid }), 1033 | 'onload': function (resp) { 1034 | var data, cid = null, cids = {}; 1035 | try { 1036 | data = JSON.parse(resp.responseText); 1037 | data.map(function (p) { 1038 | cids[p.page] = p.cid; 1039 | getAidCache.put(String(p.cid), { 'aid': id.aid, 'pid': Number(p.page) }); 1040 | }); 1041 | getCidCache.put(String(id.aid), cids); 1042 | if (id.pid) cid = cids[id.pid]; else cid = cids; 1043 | } catch (e) { data = {}; } 1044 | if (cid) call(function () { onsucc(cid); }); 1045 | else call(onerror); 1046 | debug('Got cid = %s by using pagelist', JSON.stringify(cid)); 1047 | }, 1048 | 'onerror': function () { call(onerror); } 1049 | }); 1050 | }); 1051 | }(getCidDirect, getCidCache, getAidCache)); 1052 | 1053 | // 通过为iOS设备提供的获取弹幕和MP4视频的接口得到cid 1054 | // 由于历史上的不稳定,优先级低于API获取方式 1055 | // 另外,该方式仅限单一分页 1056 | var getCidByMHtml5 = (function (getCid, getCidCache, getAidCache) { 1057 | getCid.regist(function (id, onsucc, onerror) { 1058 | if (!id.pid) onerror(); 1059 | else GM_xmlhttpRequest({ 1060 | 'method': 'GET', 1061 | 'url': genURL(bilibili.url.html5, { 'aid': id.aid, 'pid': id.pid }), 1062 | 'onload': function (resp) { 1063 | var data; 1064 | try { data = JSON.parse(resp.responseText); } catch (e) { data = {}; } 1065 | var cid = Number((data.cid || '').replace(/^[^\d]*(\d+)[^\d]*$/, '$1')); 1066 | if (cid) { 1067 | getAidCache.put(String(cid), { 'aid': id.aid, 'pid': id.pid }); 1068 | call(function () { onsucc(cid); }); 1069 | } else { 1070 | call(onerror); 1071 | } 1072 | debug('Got cid = %s by using html5', JSON.stringify(cid)); 1073 | }, 1074 | 'onerror': function () { call(onerror); } 1075 | }); 1076 | }); 1077 | }(getCidDirect, getCidCache, getAidCache)); 1078 | 1079 | // 通过Flash播放器中的参数获取cid 1080 | var getCidByFlashVar = (function (getCid, getCidCache, getAidCache) { 1081 | return getCid.regist(function (id, onsucc, onerror) { 1082 | // 只有这个id对应当前页面的时候检查Flash参数才有意义 1083 | var currentId = videoPage(location.href); 1084 | if (!currentId || currentId.aid !== id.aid || currentId.pid !== id.pid) { call(onerror); return; } 1085 | var bofqi = document.querySelector('#bofqi'), arg = null, extmatch = false, cid; 1086 | // 获取flash参数 1087 | try { arg = bofqi.querySelector('object param[name="flashvars"]').getAttribute('value'); } 1088 | catch (e) { 1089 | try { arg = bofqi.querySelector('embed[flashvars]').getAttribute('flashvars'); } 1090 | catch (e2) { } 1091 | } 1092 | if (typeof arg !== 'string') { call(onerror); return; } 1093 | // 从参数中找包含cid关键字且值是数字的键值对,认为其值为cid 1094 | arg.split('&').forEach(function (s) { 1095 | var t = s.split('='); if (t.length !== 2) return; 1096 | if (isNaN(Number(t[1])) || (t[0].indexOf('cid') === -1)) return; 1097 | if (!extmatch) { cid = Number(t[1]); extmatch = t[0] === 'cid'; } 1098 | }); 1099 | if (!cid) call(onerror); else call(function () { onsucc(cid); }); 1100 | debug('Got cid = %d by checking Flash arguments', cid); 1101 | }); 1102 | }(getCidDirect, getCidCache, getAidCache)); 1103 | 1104 | // 获取缓存的Aid 1105 | var getAidByCache = (function (getAid, getCidCache, getAidCache) { 1106 | return getAid.regist(function (cid, onsucc, onerror) { 1107 | var id = getAidCache.get(cid); 1108 | debug('Reading Cache, cid = %d -> aid = %o', cid, id); 1109 | if (id && id.aid && id.pid) onsucc(id); 1110 | else onerror(); 1111 | }); 1112 | }(getAid, getCidCache, getAidCache)); 1113 | 1114 | // 通过Interface使用cid查aid 1115 | var getAidByInterface = (function (getAid, getCidCache, getAidCache) { 1116 | return getAid.regist(function (cid, onsucc, onerror) { 1117 | GM_xmlhttpRequest({ 1118 | 'method': 'GET', 1119 | 'url': genURL(bilibili.url.player, { 'cid': cid }), 1120 | 'onload': function (resp) { 1121 | // 解析服务器返回 1122 | var lines = resp.responseText.split('\n'); 1123 | var aid = null, pid = null; 1124 | lines.forEach(function (line) { 1125 | aid = aid || Number(line.replace(/^([0-9]*)<\/aid>*$/, '$1')) || null; 1126 | pid = pid || Number(line.replace(/^([0-9]*)<\/pid>*$/, '$1')) || null; 1127 | }); 1128 | if (aid && pid) { 1129 | getAidCache.put(String(cid), { 'aid': aid, 'pid': pid }); 1130 | call(function () { onsucc({ 'aid': aid, 'pid': pid }); }); 1131 | debug('Got aid = %d, pid = %d where cid = %d, via Interface', aid, pid, cid); 1132 | } else { 1133 | call(onerror); 1134 | debug('Get Aid by Interface failed, cid = %d', cid); 1135 | } 1136 | }, 1137 | 'onerror': function () { 1138 | call(onerror); 1139 | debug('Failed to get aid related to cid'); 1140 | } 1141 | }); 1142 | }); 1143 | }(getAid, getCidCache, getAidCache)); 1144 | 1145 | // 通过相邻视频查找cid 1146 | var getCidNearby = (function (getCid, getCidCache, getAidCache) { 1147 | return getCid.regist(function (id, oriOnsucc, oriOnerror) { 1148 | debug('Try to get cid using nearby videos...'); 1149 | var msgbox = showMsg(bilibili.text.loading.near, 1e9, 'warning'); 1150 | var hidemsg = function () { msgbox.parentNode.removeChild(msgbox); }; 1151 | var updateMsg = function (ccid, num) { 1152 | var oldMsgbox = msgbox; 1153 | msgbox = showMsg( 1154 | bilibili.text.loading.nearc 1155 | .replace('{{cid}}', Math.round(ccid)) 1156 | .replace('{{num}}', num), 1157 | 1e9, 'warning'); 1158 | setTimeout(function () { oldMsgbox.parentNode.removeChild(oldMsgbox); }, 1000); 1159 | }; 1160 | var onsucc = function () { hidemsg(); oriOnsucc.apply(this, arguments); }; 1161 | var onerror = function () { hidemsg(); oriOnerror.apply(this, arguments); }; 1162 | var findCidInRange, candidateCid = [], candidateCidLoading = 0, errorDone = null; 1163 | // 如果找到某个相邻的视频的cid则记录下来 1164 | var found = (function (onsucc, onerror) { 1165 | var lastCid = [], nextCid = []; 1166 | var checkDone = (function () { 1167 | var count = 0; 1168 | var sort = function (a) { return a.sort(function (x, y) { return x - y; }); }; 1169 | var medians = function (a) { return sort(a)[a.length >> 1]; }; 1170 | var filterGL = function (a, g, l) { return a.filter(function (x) { return x > g && x < l; }); }; 1171 | return function () { 1172 | // 检查是否最小和最大的两边都完成了 1173 | debug('check done %d', count); 1174 | if (++count < 2) return; 1175 | // 打印调试信息 1176 | debug('lastCid = %o', lastCid); 1177 | debug('nextCid = %o', nextCid); 1178 | // 如果两边没有找到任何的cid则失败 1179 | if (!nextCid.length && !lastCid.length) { call(onerror); return; } 1180 | // 以下的算法纯属个人YY,因为我觉得这样效果会好些 1181 | // 如果一边没找到任何cid,则用另一侧最近的视频的来充数 1182 | if (!nextCid.length) nextCid = [lastCid[0] + 2]; 1183 | if (!lastCid.length) lastCid = [nextCid[0] - 2]; 1184 | // 基于假设:cid和aid正相关,且大多数情况下有 cid[i] > cid[j] iff aid[i] > aid[j] 1185 | var nm, lm; 1186 | // 去掉右侧比左侧所有数字都小的,和左侧比右侧所有数字都大的 1187 | nm = Math.max.apply(Math, nextCid); lm = Math.min.apply(Math, lastCid); 1188 | nextCid = filterGL(nextCid, lm, Infinity); lastCid = filterGL(lastCid, -Infinity, nm); 1189 | // 找到两边的中位数,把另一边在这个中位数以外的部分砍掉 1190 | nextCid.push(Infinity); lastCid.push(-Infinity); 1191 | nm = medians(nextCid); lm = medians(lastCid); 1192 | nextCid = filterGL(nextCid, lm, Infinity); lastCid = filterGL(lastCid, -Infinity, nm); 1193 | // 再找两边最靠里的数,把另一边在这里面的部分砍掉 1194 | nm = Math.min.apply(Math, nextCid); lm = Math.max.apply(Math, lastCid); 1195 | nextCid = filterGL(nextCid, lm, Infinity); lastCid = filterGL(lastCid, -Infinity, nm); 1196 | // 最后再找两边最靠里的数,认为我们要的cid在这个范围内 1197 | lastCid = sort(lastCid.concat([-Infinity])).reverse(); 1198 | nextCid = sort(nextCid.concat([Infinity])); 1199 | onsucc(lastCid, nextCid); 1200 | }; 1201 | }()); 1202 | // 记录获取到的新cid 1203 | var pushCid = function (group) { 1204 | return function (cid) { 1205 | if (cid.constructor === Object) { 1206 | debug('Nearby cid %o found', cid); 1207 | if (Object.keys(cid).length === 1 && cid[1]) { 1208 | group.push(cid[1]); 1209 | return true; 1210 | } 1211 | } else if (cid === true) { 1212 | checkDone(); 1213 | return null; 1214 | } 1215 | return false; 1216 | }; 1217 | }; 1218 | return { 'last': pushCid(lastCid), 'next': pushCid(nextCid) }; 1219 | }(function (lastCid, nextCid) { 1220 | findCidInRange(lastCid, nextCid); 1221 | }, onerror)); 1222 | // 查找相邻视频的cid编号 1223 | var getNearCid = function (aidF, foundF) { 1224 | var i = 0, succ = 0; 1225 | (function tryGetNearCid() { 1226 | debug('Get cid with aid = %d', aidF(id.aid, i + 1)); 1227 | if ((++i >= 8 && succ >= 3) || (i >= 12) || succ == 6) foundF(true); 1228 | else getCidCached.concat(getCidDirect).concat(getCidAPI)( 1229 | { 'aid': aidF(id.aid, i), 'pid': null }, 1230 | function (cid) { 1231 | if (cid && foundF(cid)) succ++; 1232 | tryGetNearCid(); 1233 | }, 1234 | tryGetNearCid 1235 | ); 1236 | }()); 1237 | }; 1238 | getNearCid(function (aid, i) { return Math.max(aid - i, 1); }, found.last); 1239 | getNearCid(function (aid, i) { return aid + i; }, found.next); 1240 | // 没有找到的时候处理一下可能的情况 1241 | var notFound = function () { 1242 | errorDone = true; 1243 | if (!candidateCidLoading) call(onerror); 1244 | }; 1245 | var candidateLoadDone = function () { 1246 | candidateCidInfo.put(id.aid, candidateCid); 1247 | if (errorDone && !candidateCidLoading) notFound(); 1248 | }; 1249 | // 在某个已知的范围内逐个检查是否有对应该aid的cid 1250 | findCidInRange = function (lastCid, nextCid) { 1251 | debug('find in range %s - %s', JSON.stringify(lastCid), JSON.stringify(nextCid)); 1252 | nextCid.pop(); lastCid.pop(); 1253 | var m = (nextCid[0] + lastCid[0]) / 2; 1254 | if (isNaN(m)) call(notFound); 1255 | while (nextCid.length < lastCid.length) nextCid.push(nextCid[nextCid.length - 1]); 1256 | while (nextCid.length > lastCid.length) lastCid.push(lastCid[lastCid.length - 1]); 1257 | var failCount = 0, cid = {}; 1258 | var cids = nextCid.map(function (n, i) { 1259 | return [].slice.call(Array(nextCid[i] - lastCid[i] + 1), 0) 1260 | .map(function (x, y) { return y + lastCid[i]; }) 1261 | .filter(function (x) { return i === 0 || x > nextCid[i - 1] || x < lastCid[i - 1]; }) 1262 | .sort(function (x, y) { return Math.abs(x - m) - Math.abs(y - m); }); 1263 | }).reduce(function (x, y) { return x.concat(y); }); 1264 | // 从中间到两侧依次查找 1265 | var done = function () { 1266 | var ret = cid; 1267 | if (id.pid) if (cid[id.pid]) ret = cid[id.pid]; else ret = undefined; 1268 | else if (!Object.keys(cid).length) ret = undefined; 1269 | if (ret) call(function () { onsucc(cid); }); 1270 | else call(notFound); 1271 | }; 1272 | debug('Searching in lastCids: %o, nextCids: %o', lastCid, nextCid); 1273 | debug('Searching in cid list: %o', cids); 1274 | var networkCounter = 0; 1275 | // 对每个范围内的cid进行搜索 1276 | (function tryFindCid(i) { 1277 | if (Math.random() < 1 / 4) updateMsg(m, i); 1278 | var currentCid, re = i - cids.length; 1279 | if (i < cids.length) currentCid = cids[i]; 1280 | else { 1281 | if (re & 1) currentCid = lastCid[lastCid.length - 1] - (re >> 1) - 1; 1282 | else currentCid = nextCid[nextCid.length - 1] + (re >> 1) + 1; 1283 | } 1284 | if (currentCid <= 0) return tryFindCid(i + 1); 1285 | // 找到对应的aid 1286 | var onsucc = function (rid) { 1287 | if (this === getAidByInterface) networkCounter++; 1288 | if (rid.aid === id.aid) { cid[rid.pid] = currentCid; tryFindCid(i + 1); } 1289 | else if (rid.aid === 0) { candidateCid.push(currentCid); } 1290 | else { 1291 | if (Object.keys(cid).length === Math.max.apply(Math, Object.keys(cid))) failCount++; 1292 | if (failCount > Object.keys(cid).length * 4 + 6) done(); 1293 | else tryFindCid(i + 1); 1294 | } 1295 | }; 1296 | // 没找到对应的aid 1297 | var onerror = function () { 1298 | candidateCidLoading++; 1299 | if (Object.keys(cid).length) failCount++; 1300 | call(function () { tryFindCid(i + 1); }); 1301 | checkCid(currentCid, function () { 1302 | // 如果一个cid不对应aid,但是对应了视频,那就说明这个cid曾经是有效的 1303 | candidateCidLoading--; 1304 | candidateCid.push(currentCid); 1305 | getAidCache.put(currentCid, { 'aid': 0, 'pid': 0 }); 1306 | candidateLoadDone(); 1307 | }, function () { 1308 | // 如果一个cid既不对应aid,又不对应视频,那么就是说这个cid是无效的 1309 | candidateCidLoading--; 1310 | getAidCache.put(currentCid, { 'aid': null, 'pid': null }); 1311 | candidateLoadDone(); 1312 | }); 1313 | }; 1314 | if (networkCounter > bilibili.config.netmax) done(); 1315 | else if (currentCid <= 0) call(function () { tryFindCid(i + 1); }); 1316 | else getAid(currentCid, onsucc, onerror); 1317 | }(0)); 1318 | }; 1319 | }); 1320 | }(getCidUndirect, getCidCache, getAidCache)); 1321 | 1322 | // 检查该cid是否可用 1323 | // 若检查成功则可以替换播放器;否则即便替换播放器也只会看到无限小电视或16秒“非常抱歉”错误提示 1324 | var checkCid = function (cid, onsucc, onerror) { 1325 | if (!bilibili.config.check) { onsucc(cid); return; } 1326 | GM_xmlhttpRequest({ 1327 | 'method': 'GET', 1328 | 'url': genURL(bilibili.url.playurl, { 'cid': cid }), 1329 | 'onload': function (resp) { 1330 | var data, result, message, length = 0; 1331 | try { 1332 | data = (new DOMParser()).parseFromString(resp.responseText, 'text/xml'); 1333 | playurlInfo.put(cid, data); 1334 | result = (data.querySelector('result') || {}).textContent; 1335 | message = (data.querySelector('message') || data.querySelector('error_text') || {}).textContent; 1336 | [].slice.call(data.querySelectorAll('length'), 0).map(function (o) { 1337 | var len = Number((o || {}).textContent); 1338 | if (!isNaN(len)) length += len; 1339 | }); 1340 | } catch (e) { data = {}; } 1341 | if (result) debug('check cid %d result: %s', cid, result); 1342 | else debug('check cid %d response: %o', cid, resp.responseText); 1343 | // 我也不知道他们是怎么做到一会儿用suee表示正常一会儿用succ表示正常的 1344 | result = result === 'suee' || result === 'succ'; 1345 | if (result && length !== 0) onsucc(data); 1346 | else call(function () { 1347 | if (result && length === 0) message = bilibili.text.fail.live; 1348 | if (resp.responseText.indexOf('"error_code":"E107"') !== -1) message = bilibili.text.fail.notexist; 1349 | if (resp.responseText.indexOf('"error_code":"E108"') !== -1) message = bilibili.text.fail.notexist; 1350 | if (resp.responseText.indexOf('504 Gateway Time-out') !== -1) 1351 | message = bilibili.text.fail.server; 1352 | onerror(message || bilibili.text.fail.unexpect, data || null); 1353 | }); 1354 | }, 1355 | 'onerror': function () { 1356 | debug('Network failed while verifying cid'); 1357 | onerror(bilibili.text.fail.network); 1358 | } 1359 | }); 1360 | }; 1361 | 1362 | // 修复添加/修改的视频播放器的页面全屏等功能 1363 | var fixFullWindow = function () { 1364 | var findStyle = function (s) { 1365 | return document.querySelector('script[src="' + s + '"]'); 1366 | }; 1367 | var addScript = function (s) { 1368 | var st = document.createElement('script'); st.src = s; 1369 | document.querySelector('head').appendChild(st); 1370 | }; 1371 | var fixFunction = [ 1372 | function () { }, 1373 | function () { location.href = bilibili.js.post; }, 1374 | function () { location.href = bilibili.js.float; }, 1375 | ].concat(bilibili.js.list.map(function (s) { 1376 | return function () { if (!findStyle(s)) addScript(s); }; 1377 | })); 1378 | (function doFix(i) { 1379 | if (i >= fixFunction.length) return; 1380 | fixFunction[i](); 1381 | call(function () { doFix(i + 1); }); 1382 | }(0)); 1383 | fixFullWindow = function () { }; 1384 | }; 1385 | 1386 | // 修理版权番选择页面 1387 | var fixCopyrightVideoPage = function () { 1388 | var bofqi = document.querySelector('#bofqi'); 1389 | if (bofqi.getAttribute('fake')) return; 1390 | var newBofqi = createNewBofqi(0, 0); 1391 | bofqi.parentNode.insertBefore(newBofqi, bofqi.nextSibling); 1392 | document.querySelector('#bofqi .player').style.display = 'none'; 1393 | }; 1394 | 1395 | // 通过搜索建议获取视频标题 1396 | var getTitleBySearchSuggestion = (function (getTitle) { 1397 | return getTitle.regist(function (aid, onsucc, onerror) { 1398 | // 他们改了逻辑,现在一定要指定回调函数了,不然不给数据,所以假冒一个…… 1399 | // 他们是用jQuery默认生成的JSONP回调函数的,所以这里模仿jQuery的生成方式 1400 | // 格式为 "jQuery" + 版本号 + 随机数字 + "_" + Unix时间戳 1401 | var fakeCallback = ''; 1402 | var version = null; 1403 | try { version = unsafeWindow.jQuery.fn.jquery; } catch (e) { version = '1.7.2'; } 1404 | fakeCallback = "jQuery" + (version + Math.random()).replace(/\D/g, "") + "_" + Number(new Date()); 1405 | GM_xmlhttpRequest({ 1406 | 'method': 'GET', 1407 | 'url': genURL(bilibili.url.suggest, { 1408 | 'aid': aid, 1409 | 'callback': fakeCallback, 1410 | 'rnd': Math.random(), 1411 | '_': Number(new Date()), 1412 | }), 1413 | 'onload': function (resp) { 1414 | try { 1415 | var data = String(resp.responseText); 1416 | data = data.slice(data.indexOf('['), data.lastIndexOf(']') + 1); 1417 | onsucc(JSON.parse(data)[0].desc); 1418 | } catch (e) { onerror(); } 1419 | }, 1420 | 'onerror': function () { onerror(); } 1421 | }); 1422 | }); 1423 | }(getTitle)); 1424 | 1425 | // 返回当前的cid(不支持视频内换分页) 1426 | var getCurrentCid = (function () { 1427 | var currentCid = null; 1428 | var ret = function () { 1429 | if (currentCid) return currentCid; 1430 | if (!isBilibiliBofqi(document)) return null; 1431 | try { 1432 | var player = document.querySelector('#bofqi .player'); 1433 | return Number(player.src.match(/cid=(\d+)/)[1]); 1434 | } catch (e) { 1435 | return null; 1436 | } 1437 | }; 1438 | ret.handler = function () { }; 1439 | ret.handler.update = function () { }; 1440 | ret.gotCid = function (cid) { 1441 | if (typeof cid !== 'number') return; 1442 | currentCid = cid; 1443 | ret.handler.update(); 1444 | }; 1445 | return ret; 1446 | }()); 1447 | 1448 | // 添加标题、评论 1449 | var addContent = function (aid, title, scriptLoaded) { 1450 | debug('add content av%d, title: %s', aid, title); 1451 | var z = document.querySelector('.z'); 1452 | var z_msg = document.querySelector('.z-msg'); 1453 | var doc = (new DOMParser()).parseFromString( 1454 | genXML(bilibili.html.page, { 'aid': aid, 'title': title }), 'text/html'); 1455 | if (z && z_msg) { 1456 | z_msg.style.display = 'none'; 1457 | doc.querySelector('.player').src = 'about:blank'; 1458 | ['#heimu', '.viewbox', '#bofqi', '.s_center', doc.querySelector('.common').parentNode].map(function (qs) { 1459 | var o = qs; 1460 | if (o.constructor === String) { 1461 | if (document.querySelector(qs)) return document.querySelector(qs); 1462 | o = doc.querySelector(qs); 1463 | } 1464 | o.parentNode.removeChild(o); 1465 | z.appendChild(o); 1466 | return o; 1467 | }); 1468 | document.title = genStr(bilibili.text.title, function (s) { return s; }, { 'title': title }); 1469 | fixFullWindow(); 1470 | return false; 1471 | } else { 1472 | replaceDocument(doc, scriptLoaded); 1473 | return true; 1474 | } 1475 | }; 1476 | 1477 | // 找到第一个能放的视频页播放 1478 | var findValidPage = function (aid, pids, cids, callback) { 1479 | pids = JSON.parse(JSON.stringify(pids)); 1480 | pids = pids.map(function (x) { return [Math.random(), x]; }) 1481 | .sort(function (x, y) { return x[0] - y[0]; }) 1482 | .map(function (x) { return x[1]; }); 1483 | (function checkPage(i) { 1484 | debug('find first valid page: %d', i); 1485 | if (i === pids.length) return; 1486 | var pid = pids[i], cid = cids[pid]; 1487 | checkCid(cid, function () { callback(pid); }, function (msg) { 1488 | checkCidFail({'aid': aid, 'pid': pid}, cid, msg); 1489 | checkPage(i + 1); 1490 | }); 1491 | }(0)); 1492 | }; 1493 | 1494 | // 添加分页 1495 | var addPages = function (aid, cids, pages, pid) { 1496 | debug("add pages cid: %o", cids); 1497 | debug("add pages pages: %o", pages); 1498 | var guess = cids.constructor === Array; 1499 | var alist = document.querySelector('#rbb_alist'); 1500 | if (!alist) { 1501 | alist = document.createElement('div'); 1502 | alist.id = 'rbb_alist'; 1503 | document.querySelector('.alist').appendChild(alist); 1504 | } 1505 | var pa = {}; 1506 | var pids = Object.keys(cids).map(Number).sort(function (x, y) { return x - y; }); 1507 | pids.forEach(function (pid) { 1508 | var a = alist.querySelector('a[cid="' + cids[pid] + '"]'); 1509 | if (!a) { 1510 | a = document.createElement('a'); a.setAttribute('cid', cids[pid]); 1511 | alist.appendChild(a); 1512 | } 1513 | if (pages && pages[pid]) a.innerHTML = xmlEscape(String(pid) + bilibili.text.split.pid + pages[pid]); 1514 | else a.innerHTML = xmlEscape((guess ? ' ? ' : String(pid)) + bilibili.text.split.pid + '(cid=' + cids[pid] + ')'); 1515 | a.href = genURL(bilibili.url.video, { 'host': bilibili.host, 'aid': aid, 'pid': pid }); 1516 | return (pa[pid] = a); 1517 | }); 1518 | var showPage = function (pid) { 1519 | var cid = cids[pid]; 1520 | if (!cid) return; 1521 | var player = document.querySelector('#bofqi .player'); 1522 | var url = genURL(bilibili.url.bofqi, { 'aid': aid, 'cid': cid }); 1523 | player.src = url; player.style.display = 'block'; 1524 | Object.keys(pa).map(function (pid) { pa[pid].className = ''; }); 1525 | pa[pid].className = 'curPage'; 1526 | getCurrentCid.gotCid(cid); 1527 | replacedBofqi(cid); 1528 | hashArg.set('page', pid); 1529 | }; 1530 | Object.keys(pa).map(function (pid) { 1531 | pa[pid].addEventListener('click', function (event) { 1532 | showPage(pid); 1533 | event.preventDefault(); 1534 | }); 1535 | }); 1536 | if (pid && pid !== true && pids.indexOf(pid) !== -1) showPage(pid); 1537 | else if (pid === true) showPage(pids[0]); 1538 | else if (pid) findValidPage(aid, pids, cids, showPage) 1539 | fixFullWindow(); 1540 | return pids; 1541 | }; 1542 | 1543 | // 补充生成的页面的番组信息 1544 | var fixBgmInfo = function (id) { 1545 | var bgm_list = document.querySelector('.v_bgm_list'); 1546 | if (!bgm_list) return; 1547 | if (bgm_list.querySelector('.info .detail .t')) return; 1548 | var video = videoInfo.get(id.aid); 1549 | if (!video) return; 1550 | showBgmInfo({ 1551 | 'spid': video.spid, 1552 | 'season_id': video.season_id, 1553 | 'aid': id.aid, 1554 | }, true); 1555 | }; 1556 | 1557 | // 利用补档页面的接口获取更多可用的描述 1558 | var fixVideoInfos2 = function (id, callback) { 1559 | GM_xmlhttpRequest({ 1560 | 'method': 'GET', 1561 | 'url': genURL(bilibili.url.arc, { 'aid': id.aid }), 1562 | 'onload': function (resp) { 1563 | var data, title, description; 1564 | try { 1565 | data = resp.responseText.split(/\r\n|\r|\n/); 1566 | title = data[0].match(/^(.*)<\/strong>
    $/)[1]; 1567 | description = data[4]; 1568 | } catch (e) { } 1569 | if (title) { 1570 | document.querySelector('.viewbox h2').setAttribute('title', title); 1571 | document.querySelector('.viewbox h2').textContent = title; 1572 | } 1573 | if (description) 1574 | document.querySelector('.s_center .s_div .intro').textContent = description; 1575 | callback(); 1576 | }, 1577 | }); 1578 | }; 1579 | 1580 | // 利用API获得的数据添加更多可用的描述 1581 | var fixVideoInfos = function (id) { 1582 | var info = videoInfo.get(id.aid); 1583 | if (!info) return null; 1584 | if (info.description) 1585 | document.querySelector('.s_center .s_div .intro').textContent = info.description; 1586 | if (info.title) { 1587 | document.querySelector('.viewbox h2').setAttribute('title', info.title); 1588 | document.querySelector('.viewbox h2').textContent = info.title; 1589 | } 1590 | if (id.aid && info.mid && info.spid && info.tag) { 1591 | location.href = genCode(bilibili.js.kwtags, { 1592 | 'aid': id.aid, 1593 | 'mid': info.mid, 1594 | 'spid': info.spid, 1595 | 'tag': info.tag, 1596 | }); 1597 | } 1598 | return info; 1599 | }; 1600 | 1601 | // 如果没有描述,那就把描述藏起来 1602 | var hideDescription = function () { 1603 | var description = '', s_center; 1604 | try { 1605 | description = document.querySelector('.s_center .s_div .intro').textContent; 1606 | } catch (e) { } 1607 | var tags = document.querySelector('.tag li'); 1608 | if (!description.length && !tags) { 1609 | s_center = document.querySelector('.s_center'); 1610 | s_center.parentNode.removeChild(s_center); 1611 | } 1612 | }; 1613 | 1614 | // 添加的页面中完善相关信息 1615 | var fixAddedPage = function (id) { 1616 | debug('Try to show more details for av%d', id.aid); 1617 | fixBgmInfo(id); 1618 | if (!fixVideoInfos(id)) fixVideoInfos2(id, hideDescription); 1619 | else hideDescription(); 1620 | }; 1621 | 1622 | var addedBofqi = callbackEvents(); 1623 | // 添加播放器标题等页面相关元素 1624 | var addBofqi = function (aid, callback) { 1625 | debug('add bofqi av%d', aid); 1626 | var cb = function (title) { 1627 | var isNew = addContent(aid, title, callback); 1628 | fixFullWindow(); 1629 | if (!isNew) call(callback); 1630 | call(function () { addedBofqi(title); }); 1631 | }; 1632 | getTitle(aid, cb, function () { cb(''); }); 1633 | }; 1634 | 1635 | // 新建一个播放器 1636 | var createNewBofqi = function (aid, cid) { 1637 | var bofqi = document.createElement('div'); 1638 | bofqi.innerHTML = bilibili.html.bofqi(genURL(bilibili.url.bofqi, { 'aid': aid, 'cid': cid })); 1639 | bofqi.className = 'scontent'; 1640 | bofqi.id = 'bofqi'; 1641 | return bofqi; 1642 | }; 1643 | 1644 | // 禁用替换 1645 | var notReplaceBofqi = (function () { 1646 | var setHash = function (hash) { 1647 | location.hash = hash; 1648 | location.reload(); 1649 | }; 1650 | if (hashArg.get('rbb') === 'false') showMsg(bilibili.text.disable.message, 12000, 'warning', 1651 | [{ 'value': bilibili.text.disable.redo, 'click': function () { setHash('#'); } }]); 1652 | return function () { setHash('#rbb=false'); }; 1653 | }()); 1654 | 1655 | var replacedBofqi = callbackEvents(); 1656 | // 添加多个播放器 1657 | var replaceBofqiAll = function (id, cids0) { 1658 | var cids, pages = {}, info = videoInfo.get(id.aid); 1659 | if (isCopyrightVideoSelect(document)) { 1660 | cids = {}; 1661 | Object.keys(cids0).map(function (i) { cids[Number(i) + 1] = cids0[i]; }); 1662 | fixCopyrightVideoPage(); 1663 | if (info && info.list) Object.keys(info.list).map(function (i) { 1664 | pages[Number(info.list[i].page) + 1] = info.list[i].part; 1665 | }); 1666 | } else cids = cids0; 1667 | var pids = addPages(id.aid, cids, pages, videoPage(location.href).pid || true); 1668 | showMsg(bilibili.text.succ.add 1669 | .replace('{{cid}}', pids.map(function (pid) { return cids[pid]; }).join(', ')) 1670 | .replace('{{pid}}', pids.join(', ')), 12000, 'info'); 1671 | return; 1672 | }; 1673 | 1674 | // 替换播放器 1675 | var replaceBofqi = function (id, cid, keepold) { 1676 | if (typeof cid !== 'number') return replaceBofqiAll(id, cid); 1677 | var oldBofqi = document.querySelector('#bofqi'); 1678 | var newBofqi = createNewBofqi(id.aid, cid); 1679 | oldBofqi.parentNode.insertBefore(newBofqi, oldBofqi); 1680 | if (keepold) { 1681 | oldBofqi.id = 'old-bofqi'; 1682 | } else { 1683 | oldBofqi.parentNode.removeChild(oldBofqi); 1684 | } 1685 | fixFullWindow(); 1686 | if (!keepold) 1687 | showMsg(bilibili.text.succ.replace 1688 | .replace('{{source}}', getVideoSource(id, cid).name) 1689 | .replace('{{cid}}', JSON.stringify(cid)), 1690 | 12000, 'info', 1691 | [{ 'value': bilibili.text.succ.rollback, 'click': notReplaceBofqi }]); 1692 | replacedBofqi(cid); 1693 | return oldBofqi; 1694 | }; 1695 | 1696 | // 在不确定是否可以正常替换的情况下替换了播放器显示该菜单 1697 | var replacedMenu = function (oldBofqi) { 1698 | var player = document.querySelector('#bofqi .player'); 1699 | // 重新加载 1700 | var reloadButton = { 1701 | 'value': bilibili.text.force.reload, 1702 | 'click': function () { 1703 | var newPlayer = player.cloneNode(true); 1704 | player.parentNode.insertBefore(newPlayer, player); 1705 | player.parentNode.removeChild(player); 1706 | player = newPlayer; 1707 | } 1708 | }; 1709 | // 退回外站播放器 1710 | var rollbackButton = { 1711 | 'value': bilibili.text.force.rollback, 1712 | 'click': function () { 1713 | var bofqi = document.querySelector('#bofqi'); 1714 | bofqi.parentNode.removeChild(bofqi); 1715 | oldBofqi.id = 'bofqi'; 1716 | msg.parentNode.removeChild(msg); 1717 | } 1718 | }; 1719 | // 成功替换 1720 | var doneButton = { 1721 | 'value': bilibili.text.force.done, 1722 | 'click': function () { 1723 | msg.parentNode.removeChild(msg); 1724 | if (oldBofqi) oldBofqi.parentNode.removeChild(oldBofqi); 1725 | } 1726 | }; 1727 | var buttons = [reloadButton]; 1728 | if (oldBofqi) buttons = buttons.concat([rollbackButton]); 1729 | buttons = buttons.concat([doneButton]); 1730 | var msg = showMsg(bilibili.text.force.message, 1e9, 'warning', buttons); 1731 | }; 1732 | 1733 | // 处理检查cid是否可用出错时的情况 1734 | var checkCidFail = function (id, cid, msg, force) { 1735 | var videoSource = getVideoSource(id, cid); 1736 | var addField = function (s) { 1737 | return s.replace('{{source}}', videoSource.name) 1738 | .replace('{{cid}}', JSON.stringify(cid)).replace('{{msg}}', msg || ''); 1739 | }; 1740 | if (videoSource.unsupport) msg = addField(bilibili.text.fail.unsupport); 1741 | else if (!msg) msg = addField(bilibili.text.fail.check); 1742 | else msg = addField(bilibili.text.fail.msg); 1743 | var buttons = []; 1744 | if (force) buttons = [{ 1745 | 'value': bilibili.text.force.replace.replace('{{source}}', videoSource), 1746 | 'click': force, 1747 | }]; 1748 | var msgbox = showMsg(msg, 50000, 'error', buttons); 1749 | debug('Failed on checking cid with msg %s', msg); 1750 | return msgbox; 1751 | }; 1752 | 1753 | // 获取cid并判断是否可以正常显示视频 1754 | var getValidCid = function (getCid, id, done, cidReady) { 1755 | getCid(id, 1756 | function (cid) { 1757 | var timer = null, cbd = false; 1758 | var ccid = null; 1759 | if (cid.constructor === Number) ccid = cid; 1760 | else { 1761 | Object.keys(cid).map(function (pid) { 1762 | if (ccid === null) ccid = cid[pid]; 1763 | else if (ccid !== cid[pid]) ccid = false; 1764 | }); 1765 | } 1766 | var cb = function (cid, msg) { 1767 | if (cbd) return; cbd = true; 1768 | if (timer) clearTimeout(timer); timer = null; 1769 | done(cid, msg); 1770 | }; 1771 | if (ccid) { 1772 | if (cidReady) timer = setTimeout(function () { 1773 | cidReady(cid, function () { cb(cid); }); 1774 | }, bilibili.timeout.network); 1775 | checkCid(ccid, 1776 | function () { cb(cid); }, 1777 | function (msg) { cb(cid, msg || ''); }); 1778 | } else { 1779 | // 如果对应了多个视频则不进行检查 1780 | done(cid); 1781 | } 1782 | }, 1783 | function () { done(); } 1784 | ); 1785 | }; 1786 | 1787 | // 检查某个页面是否使用原生的播放器 1788 | var checkBilibiliBofqi = function (id, onsucc, onerror) { 1789 | (function checkBofqi(i) { 1790 | if (i === id.pids.length) call(onerror); 1791 | else { 1792 | GM_xmlhttpRequest({ 1793 | 'method': 'GET', 1794 | 'url': genURL(bilibili.url.video, { 'aid': id.aid, 'pid': id.pids[i], 'host': bilibili.host }), 1795 | 'onload': function (resp) { 1796 | var doc = new DOMParser().parseFromString(resp.responseText, 'text/html'); 1797 | if (isBilibiliBofqi(doc)) onsucc(id.pids[i]); 1798 | else checkBofqi(i + 1); 1799 | }, 1800 | 'onerror': function () { 1801 | checkBofqi(i + 1); 1802 | } 1803 | }); 1804 | } 1805 | }(0)); 1806 | }; 1807 | 1808 | // 检查是不是撞车视频 1809 | var isDuplicate = function () { 1810 | if (!unsafeWindow.JumpUrl || !(unsafeWindow.pgo + 1)) return false; 1811 | var id = unsafeWindow.JumpUrl.toString().match(/av(\d+)/); 1812 | if (!id || !Number(id[1])) return false; 1813 | return Number(id[1]); 1814 | }; 1815 | 1816 | (function () { 1817 | var duplicate = isDuplicate(); 1818 | if (!duplicate) return; 1819 | debug('found duplicate video -> av%d', duplicate); 1820 | location.href = 'javascript:void(pgo=1,JumpUrl=function(){});'; 1821 | call(function addDuplicateInfo() { 1822 | var z = document.querySelector('.z'); 1823 | if (!z) return call(addDuplicateInfo); 1824 | var info = document.createElement('div'); 1825 | info.innerHTML = genXML(bilibili.html.dupinfo, { 'aid': duplicate }); 1826 | z.insertBefore(info.firstChild, z.firstChild); 1827 | }); 1828 | }()); 1829 | 1830 | // 主程序在这里 1831 | (function mina() { 1832 | var id = validPage(); if (!id) return; 1833 | var isAdded = !hasBofqi(document); 1834 | var act = function () { 1835 | var msgbox = null, ignore = false; 1836 | var doReplace = function (cid) { 1837 | var oldBofqi = replaceBofqi(id, cid, ignore); 1838 | if (ignore) replacedMenu(oldBofqi); 1839 | if (isAdded) fixAddedPage(id); 1840 | }; 1841 | getValidCid(getCidAPI.concat(getCidDirect).concat(getCidCached).concat(getCidUndirect), 1842 | id, function (cid, errormsg) { 1843 | debug('mina got cid=%o, with message: %s', cid, errormsg); 1844 | var ccid = []; 1845 | getCurrentCid.gotCid(cid); 1846 | if (msgbox) { msgbox.parentNode.removeChild(msgbox); msgbox = null; } 1847 | if (!cid) { 1848 | // 如果没有获取到cid 1849 | ccid = candidateCidInfo.get(id.aid); 1850 | if (ccid && ccid.length) { 1851 | // 但是找到了一些不对应aid的视频 1852 | addPages(id.aid, ccid, null, null); 1853 | showMsg(bilibili.text.fail.getc, 12000, 'warning'); 1854 | } else { 1855 | // 获取cid失败 1856 | showMsg(bilibili.text.fail.get, 12000, 'error'); 1857 | } 1858 | } else if (typeof errormsg !== 'undefined') { 1859 | // 如果有报错信息,则说明是cid检查不通过 1860 | msgbox = checkCidFail(id, cid, errormsg, function () { 1861 | doReplace(cid); 1862 | msgbox.parentNode.removeChild(msgbox); 1863 | }); 1864 | } else { 1865 | doReplace(cid); 1866 | } 1867 | }, function (cid, ignoreCheck) { 1868 | getCurrentCid.gotCid(cid); 1869 | msgbox = showMsg(bilibili.text.loading.check.replace('{{cid}}', JSON.stringify(cid)), 1870 | 1e9, 'warning', [{ 1871 | 'value': bilibili.text.loading.ignore, 1872 | 'click': function () { 1873 | ignore = true; ignoreCheck(); 1874 | } 1875 | }, { 1876 | 'value': bilibili.text.loading.wait, 1877 | 'click': function () { 1878 | if (!msgbox) return; 1879 | msgbox.parentNode.removeChild(msgbox); 1880 | } 1881 | } 1882 | ]); 1883 | }); 1884 | }; 1885 | // 如果需要添加播放框添加播放器 1886 | if (isAdded) addBofqi(id.aid, act); 1887 | else act(); 1888 | }()); 1889 | 1890 | debug('Replace bilibili bofqi LOADED.'); 1891 | 1892 | 1893 | // 从URL中截取aid(av), pid号 1894 | var fixLinks = function () { 1895 | var links = [].slice.call(document.querySelectorAll('a[href*="video/av"]:not([href*="rbb="])'), 0); 1896 | links.forEach(function (link) { 1897 | var vp = videoPage(link.href); if (!vp) return; 1898 | var rbb = 'rbb=' + encodeURIComponent(JSON.stringify(vp)); 1899 | if (links.indexOf('#') === -1) link.href += '#' + rbb; 1900 | else link.href += '&' + rbb; 1901 | }); 1902 | }; 1903 | 1904 | var activeFixLinks = function () { 1905 | if (config.hasharg === false) return; 1906 | (new MutationObserver(function (mutations) { 1907 | fixLinks(); 1908 | })).observe(document.body, { 'childList': true, 'subtree': true }); 1909 | fixLinks(); 1910 | }; 1911 | activeFixLinks(); 1912 | 1913 | // 添加hash的内容到此结束 1914 | 1915 | var menuContainer = (function () { 1916 | var container = null; 1917 | return function () { 1918 | if (!container) { 1919 | container = document.createElement('div'); container.id = 'rbb-menu-container'; 1920 | document.body.parentNode.appendChild(container); 1921 | } 1922 | return container; 1923 | }; 1924 | }()); 1925 | 1926 | var initMenuDom = function (menu) { 1927 | var items = menu.querySelectorAll('.bsp-menu-item'); 1928 | menu.style.maxHeight = 32 * (items.length + 1) + 'px'; 1929 | if (items.length > 8) menu.style.minHeight = 32 * (8 + 1) + 'px'; 1930 | else menu.style.minHeight = 32 * (items.length + 1) + 'px'; 1931 | if (items.length !== 0) menu.style.resize = 'both'; 1932 | }; 1933 | 1934 | // 选项菜单 1935 | var choseMenu = (function () { 1936 | // 显示菜单 1937 | var show = function (menu, position, sp) { 1938 | menuContainer().appendChild(menu); 1939 | var isRbb = menu.className.split(' ').indexOf('rbb-menu') !== -1; 1940 | var dx = 0, dy = 0; 1941 | if (isRbb && sp) dx = -128; 1942 | if (!isRbb && sp) dy = -32; 1943 | if (menu.clientWidth + position.x > document.body.clientWidth) { 1944 | menu.style.right = (document.body.clientWidth - position.x - 8 + dx) + 'px'; 1945 | menu.className += ' rbb-float-right'; 1946 | } else menu.style.left = (position.x - 8 + dx) + 'px'; 1947 | menu.style.top = (position.y - 6 + dy) + 'px'; 1948 | return menu; 1949 | }; 1950 | // 隐藏菜单 1951 | var hide = function (menu) { 1952 | menu.parentNode.removeChild(menu); 1953 | }; 1954 | // 检查某个元素是否在菜单内 1955 | var contains = function (menu, obj) { 1956 | for (; obj; obj = obj.parentNode) if (obj === menu) return true; 1957 | return false; 1958 | }; 1959 | // 创建菜单 1960 | var create = function (items, sp) { 1961 | var menu = document.createElement('div'); 1962 | var bsp = items.length === 1; 1963 | if (bsp) menu.innerHTML = bilibili.html.menu2(items[0], sp); 1964 | else menu.innerHTML = bilibili.html.menu(items, sp); 1965 | menu = menu.firstChild; 1966 | if (bsp) initMenuDom(menu); 1967 | return menu; 1968 | }; 1969 | return function (items, sp, position) { 1970 | debug('New menu displayed: %o, %o', items, sp); 1971 | var displayTime = new Date(); 1972 | var menu = create(items, sp); 1973 | show(menu, position, sp); 1974 | var menuHidden = function (event) { 1975 | // 如果点击事件发生在菜单内则不隐藏菜单 1976 | if (contains(menu, event.target)) return; 1977 | // 如果菜单刚刚被显示不超过半秒则不隐藏菜单 1978 | else if (new Date() - displayTime < 500) return; 1979 | else { 1980 | hide(menu); 1981 | document.body.removeEventListener('mousedown', menuHidden); 1982 | } 1983 | }; 1984 | document.body.addEventListener('mousedown', menuHidden); 1985 | return (function () { 1986 | var message = menu.querySelector('.rbb-menu-message'); 1987 | var setMessage = function (msg) { 1988 | if (!message) return; 1989 | message.innerHTML = xmlEscape(msg || ''); 1990 | if (msg) menu.setAttribute('message', 'message'); 1991 | else menu.removeAttribute('message'); 1992 | }; 1993 | var hideMenu = function () { hide(menu); }; 1994 | return { 'set': setMessage, 'hide': hideMenu }; 1995 | }()); 1996 | }; 1997 | }()); 1998 | 1999 | // 从页面中获取分页名称等相关信息 2000 | var getPageInfo = function (aid, html) { 2001 | var doc; 2002 | try { 2003 | doc = new DOMParser().parseFromString(html, 'text/html'); 2004 | var pages = doc.querySelectorAll('#dedepagetitles option'); 2005 | var nodedata = [].slice.call(pages, 0).map(function (opt) { 2006 | return [opt.innerHTML, opt.value]; 2007 | }); 2008 | var title = doc.querySelector('.viewbox h2').innerHTML; 2009 | if (title && nodedata) { 2010 | pageInfo.put(aid, { 2011 | 'title': title, 2012 | 'nodedata': nodedata, 2013 | }); 2014 | } 2015 | } catch (e1) { } 2016 | try { 2017 | var spo = doc.querySelector('.v_bgm_list .info .detail a'); 2018 | if (spo) { 2019 | var sp = { 2020 | 'title': spo.textContent, 2021 | 'href': spo.getAttribute('href'), 2022 | }; 2023 | videoSpInfo.put(aid, sp); 2024 | } 2025 | } catch (e2) { } 2026 | }; 2027 | 2028 | // 生成那个rbb对象 2029 | var genRbb = function (id, wholeMenu, a, cids) { 2030 | var info = videoInfo.get(id.aid) || {}; 2031 | var pinfo = pageInfo.get(id.aid) || {}; 2032 | var copyrightSelect = false; 2033 | try { 2034 | if (pinfo.nodedata.length === Object.keys(info.list).length + 1) 2035 | if (pinfo.nodedata[0][0].match(/\[\[(.*)\]\]/)) 2036 | copyrightSelect = true; 2037 | } catch (e) { } 2038 | a = a || document.createElement('a'); 2039 | var rbb = { 2040 | 'aid': id.aid, 2041 | 'cids': {}, 2042 | 'pages': {}, 2043 | 'description': info.description || a.getAttribute('txt') || '', 2044 | 'mid': info.mid || 0, 2045 | 'author': info.author || '', 2046 | 'tag': (info.tag || '').split(','), 2047 | 'title': info.title || pinfo.title || a.textContent.trim() || '', 2048 | 'spid': info.spid || 0, 2049 | 'sp_title': info.sp_title || null, 2050 | 'season_id': info.season_id || 0, 2051 | 'copyright_select': copyrightSelect, 2052 | }; 2053 | if (info.list) { 2054 | Object.keys(info.list).map(function (i) { 2055 | var page = info.list[i].page; 2056 | if (copyrightSelect) page++; 2057 | rbb.cids[page] = info.list[i].cid; 2058 | rbb.pages[page] = info.list[i].part; 2059 | }); 2060 | } else if (pinfo.nodedata && !wholeMenu) { 2061 | if (pinfo.nodedata.length === 0) { 2062 | rbb.pages[1] = ''; 2063 | rbb.cids[1] = null; 2064 | } else pinfo.nodedata.map(function (nodedata, i) { 2065 | rbb.pages[i + 1] = nodedata[0]; 2066 | rbb.cids[i + 1] = null; 2067 | }); 2068 | } else if (cids) { 2069 | Object.keys(cids).map(function (pid) { 2070 | rbb.pages[pid] = ((pinfo.nodedata || [])[pid - 1] || {})[0] || ''; 2071 | rbb.cids[pid] = cids[pid]; 2072 | }); 2073 | } else { 2074 | return null; 2075 | } 2076 | return rbb; 2077 | }; 2078 | 2079 | // 对某个视频显示选项菜单 2080 | var showMenu = function (a, id, position) { 2081 | var oldOnclick = a.onclick; 2082 | a.onclick = function () { a.onclick = oldOnclick; return false; }; 2083 | a.style.cursor = 'progress'; 2084 | var menu = null; 2085 | var initMenu = function (cids, inSite, isOrigen) { 2086 | call(function () { a.onclick = oldOnclick; a.style.removeProperty('cursor'); }); 2087 | var configMenu = bilibili.config.cmenu_type; 2088 | var wholeMenu = cids && inSite === false || !isOrigen; 2089 | if (configMenu === 'complete') wholeMenu = true; 2090 | var rbb = genRbb(id, wholeMenu, a, cids); 2091 | // code copy genRbb 2092 | var info = videoInfo.get(id.aid) || {}; 2093 | var pinfo = pageInfo.get(id.aid) || {}; 2094 | var copyrightSelect = false; 2095 | try { 2096 | if (pinfo.nodedata.length === Object.keys(info.list).length + 1) 2097 | if (pinfo.nodedata[0][0].match(/\[\[(.*)\]\]/)) 2098 | copyrightSelect = true; 2099 | } catch (e) { } 2100 | // code copy end 2101 | if (!rbb) return (menu = choseMenu([{ 2102 | 'title': bilibili.text.fail.default, 2103 | 'href': a.href, 2104 | 'submenu': [], 2105 | }], null, position)); 2106 | var pids = Object.keys(rbb.cids).map(Number) 2107 | .sort(function (x, y) { return x - y; }); 2108 | if (cids) pids = pids.filter(function (pid, i) { 2109 | for (i++; i < pids.length; i++) 2110 | if (cids[pid] === cids[pids[i]]) return false; 2111 | return true; 2112 | }); 2113 | debug('Menu with rbb = %o', rbb); 2114 | var menuItem = function (title, href_p) { 2115 | if (pids.length > 1) return { 2116 | 'title': title, 2117 | 'href': href_p((videoPage(a.href) || {}).pid || pids[0]), 2118 | 'submenu': pids.map(function (i) { 2119 | var part = rbb.pages[i] || ''; 2120 | return { 2121 | 'title': part, 2122 | 'href': href_p(i), 2123 | }; 2124 | }), 2125 | }; else return { 2126 | 'title': title, 2127 | 'href': href_p(pids[0]), 2128 | 'submenu': [], 2129 | }; 2130 | }; 2131 | // 生成页面 2132 | var pageLink = function (pid) { 2133 | var base = 'http://' + bilibili.host + '/video/av1/index_1.html'; 2134 | var rbbr = JSON.parse(JSON.stringify(rbb)); 2135 | if (pid) rbbr.pid = pid; 2136 | return hashArg.set('rbb', JSON.stringify(rbbr), base); 2137 | }; 2138 | // 仅播放器 2139 | var iframeLink = function (pid) { 2140 | return genURL(bilibili.url.bflash, { 'cid': rbb.cids[pid], 'aid': id.aid }); 2141 | }; 2142 | // 原始页面 2143 | var origenLink = function (pid) { 2144 | var id = videoPage(a.href); 2145 | return genURL(bilibili.url.video, { 'aid': id.aid, 'pid': pid }) + a.search + a.hash; 2146 | }; 2147 | // 三种页面 2148 | var page, iframe, origen, menuItems; 2149 | origen = menuItem(copyrightSelect ? bilibili.text.menu.auto : bilibili.text.menu.origen, origenLink); 2150 | if (wholeMenu) { 2151 | page = menuItem(bilibili.text.menu.page, pageLink); 2152 | iframe = menuItem(bilibili.text.menu.swf, iframeLink); 2153 | if (inSite !== false) menuItems = [origen, page, iframe]; 2154 | else menuItems = [page, iframe, origen]; 2155 | if (configMenu === 'simple') menuItems.length = 1; 2156 | } else menuItems = [origen]; 2157 | if (menuItems.length === 1) { 2158 | menuItems[0].title = rbb.title || 2159 | (copyrightSelect ? bilibili.text.menu.auto : 2160 | (menuItems[0].submenu.length ? 2161 | bilibili.text.menu.chose : bilibili.text.menu.origen)); 2162 | } 2163 | // 专题 2164 | var sp = videoSpInfo.get(id.aid) || undefined; 2165 | if (!sp && rbb.spid && rbb.sp_title) sp = { 2166 | 'title': rbb.sp_title, 2167 | 'href': genURL(bilibili.url.sp.page, { 'title': rbb.sp_title }), 2168 | }; 2169 | return (menu = choseMenu(menuItems, sp, position)); 2170 | }; 2171 | var updateMenu = function (cids, errormsg, inSite, isOrigen) { 2172 | if (!menu) initMenu(cids, inSite, isOrigen); 2173 | debug('Set menu message: %s', errormsg || ''); 2174 | menu.set(errormsg); 2175 | }; 2176 | var checkHost = function (inSite, isOrigen) { 2177 | debug('inSite: %o, isOrigen: %o', inSite, isOrigen); 2178 | if (inSite && isOrigen) { 2179 | // 如果本来已经是B站的则不需要继续处理 2180 | if ((pageInfo.get(id.aid) || {}).title && bilibili.config.cmenu_type !== 'complete') 2181 | updateMenu(undefined, undefined, inSite, isOrigen); 2182 | else getCidAPI.concat(getCidDirect)(id, function (cids) { 2183 | updateMenu(cids, undefined, inSite, isOrigen); 2184 | }, function () { 2185 | updateMenu(); 2186 | }); 2187 | } else { 2188 | // 如果发现会自动跳转到其他网站或无法获取host信息,则查找cid,试图在B站内播放 2189 | getValidCid(getCidAPI.concat(getCidDirect).concat(getCidCached), id, function (cids, message) { 2190 | updateMenu(cids, message, inSite, isOrigen); 2191 | }, function (cids, ignoreCheck) { 2192 | updateMenu(cids, bilibili.text.loading.checks, inSite, isOrigen); 2193 | }); 2194 | } 2195 | }; 2196 | // 检查是否跳转到外站 2197 | GM_xmlhttpRequest({ 2198 | 'method': 'GET', 2199 | 'url': a.href, 2200 | 'onload': function (resp) { 2201 | getPageInfo(id.aid, resp.responseText); 2202 | var doc; 2203 | // 检查视频跳转 2204 | try { 2205 | if (typeof resp.finalUrl === 'undefined') { 2206 | doc = new DOMParser().parseFromString(resp.responseText, 'text/html'); 2207 | checkHost(undefined, isBilibiliBofqi(doc)); 2208 | } else { 2209 | var host = getHost(resp.finalUrl); 2210 | var inSite = host && bilibili.url.host.indexOf(host) !== -1; 2211 | doc = new DOMParser().parseFromString(resp.responseText, 'text/html'); 2212 | if (!hasBofqi(doc)) inSite = false; 2213 | checkHost(inSite, isBilibiliBofqi(doc)); 2214 | } 2215 | } catch (e) { checkHost(); } 2216 | }, 2217 | 'onerror': function () { checkHost(); } 2218 | }); 2219 | }; 2220 | 2221 | // 按住鼠标时进行处理 2222 | var holdMouse = function () { 2223 | if (!document.body) return; 2224 | var lastPosition; 2225 | document.body.addEventListener('mousedown', function (event) { 2226 | var a = event.target, id; 2227 | while (a && a.tagName && ['A', 'AREA'].indexOf(a.tagName.toUpperCase()) === -1) a = a.parentNode; 2228 | if (a && a.href) debug('Pressed ', a.href); 2229 | // 检查链接是视频页面 2230 | if (!a || !a.href || !(id = videoPage(a.href, true)) || 2231 | bilibili.video.ignore.indexOf(id.aid) !== -1) return; 2232 | if (!(function () { 2233 | for (var p = a; p && typeof p.className === 'string'; p = p.parentNode) { 2234 | // 检查链接不在菜单内 2235 | if (p.className.split(' ').indexOf('rbb-menu-link') !== -1) return false; 2236 | if (p.className.split(' ').indexOf('bsp-menu-link') !== -1) return false; 2237 | // 检查链接不是分页的选项链接 2238 | if (p.className.split(' ').indexOf('alist') !== -1) return false; 2239 | } 2240 | return true; 2241 | }())) return; 2242 | // 仅限左键点击 2243 | if (event.which !== 1) return; 2244 | // 如果用户按到了一个指向视频的链接,则开始工作 2245 | lastPosition = { 2246 | 'x': event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft), 2247 | 'y': event.clientY + (document.body.scrollTop || document.documentElement.scrollTop) 2248 | }; 2249 | var timeout; 2250 | var actions = ['mouseup', 'mouseleave', 'mouseout', 'drag']; 2251 | var mh = function () { 2252 | clearTimeout(timeout); 2253 | actions.forEach(function (e) { a.removeEventListener(e, mh); }); 2254 | }; 2255 | actions.forEach(function (e) { a.addEventListener(e, mh); }); 2256 | // 只有按住足够长时间才执行这段 2257 | timeout = setTimeout(function () { 2258 | showMenu(a, id, lastPosition); 2259 | actions.forEach(function (e) { a.removeEventListener(e, mh); }); 2260 | }, bilibili.timeout.press); 2261 | }); 2262 | }; 2263 | holdMouse(); 2264 | 2265 | // 替换整个文档树 2266 | var replaceDocument = function (doc, scriptLoaded) { 2267 | var stylish = document.querySelectorAll('head style.stylish'); 2268 | [].slice.call(stylish, 0).forEach(function (s) { doc.querySelector('head').appendChild(s); }); 2269 | var scripts = [].slice.call(doc.querySelectorAll('head script[src]'), 0); 2270 | scripts.map(function (s) { return s.parentNode.removeChild(s); }); 2271 | var ret = document.replaceChild(doc.documentElement, document.documentElement); 2272 | var count = scripts.length; 2273 | scripts.map(function (script) { 2274 | var s = document.createElement('script'); 2275 | s.src = script.src; s.async = false; 2276 | var done = false; 2277 | var checkLoad = function () { 2278 | var state = s.readyState; 2279 | if (!done && (!state || /loaded|complete/.test(state))) { 2280 | done = true; if (--count === 0 && scriptLoaded) call(scriptLoaded); 2281 | } 2282 | }; 2283 | s.addEventListener('readystatechange', checkLoad); 2284 | s.addEventListener('load', checkLoad); 2285 | document.querySelector('head').appendChild(s); 2286 | }); 2287 | holdMouse(); 2288 | addStyle(); 2289 | initShowMsg(); 2290 | activeFixLinks(); 2291 | return ret; 2292 | }; 2293 | 2294 | // 显示新番相关信息(用于生成的页面) 2295 | var showBgmInfo = function (id, pre_leaded) { 2296 | var done = 0, spInfo = null, bgmInfo = null; 2297 | var doneCallback = null, errorCallback; 2298 | var sp = { 'spid': id.spid, 'season_id': id.season_id }; 2299 | 2300 | var error = function () { 2301 | if (done < 0) return; done = -1; 2302 | errorCallback(); 2303 | }; 2304 | 2305 | var parse = function (resp) { 2306 | var data; 2307 | try { 2308 | data = JSON.parse(resp.responseText); 2309 | if (!data.count) throw ''; 2310 | if (done >= 0) done++; 2311 | return data; 2312 | } catch (e) { error(); } 2313 | return null; 2314 | }; 2315 | 2316 | var load = function () { 2317 | if (done === 3) doneCallback(); 2318 | }; 2319 | 2320 | var getSpInfo = function () { 2321 | GM_xmlhttpRequest({ 2322 | 'method': 'GET', 2323 | 'url': genURL(bilibili.url.sp.spid, sp), 2324 | 'onload': function (resp) { load(spInfo = parse(resp)); }, 2325 | 'onerror': error, 2326 | }); 2327 | }; 2328 | 2329 | var getBgmInfo = function () { 2330 | GM_xmlhttpRequest({ 2331 | 'method': 'GET', 2332 | 'url': genURL(bilibili.url.sp.spview, sp), 2333 | 'onload': function (resp) { load(bgmInfo = parse(resp)); }, 2334 | 'onerror': error, 2335 | }); 2336 | }; 2337 | 2338 | doneCallback = function () { 2339 | var v_bgm_list = document.querySelector('.v_bgm_list'); 2340 | v_bgm_list.innerHTML = genXML(bilibili.html.v_bgm_list, { 2341 | 'spcover': spInfo.cover, 2342 | 'sptitle': spInfo.title, 2343 | 'spurl': '/sp/' + encodeURIComponent(spInfo.title), 2344 | 'bangumi': bgmInfo.count, 2345 | 'relative': spInfo.count - bgmInfo.count, 2346 | }); 2347 | try { 2348 | new unsafeWindow.bbBangumiSp(id.aid, sp.spid, 0, encodeURIComponent(spInfo.title)); 2349 | } catch (e) { 2350 | debug('Error while adding Bgm info: %o', e); 2351 | } 2352 | }; 2353 | 2354 | errorCallback = function () { }; 2355 | 2356 | getBgmInfo(); 2357 | getSpInfo(); 2358 | 2359 | var scriptLoaded = pre_leaded || false; 2360 | if (!scriptLoaded) return function () { 2361 | if (!scriptLoaded && done >= 0) load(++done); 2362 | scriptLoaded = true; 2363 | }; else ++done; 2364 | 2365 | }; 2366 | 2367 | // 如果打开页面时rbb数组里面没什么东西,那么就先试图加载来东西 2368 | var loadCidFirst = function (id) { 2369 | getCidAPI.concat(getCidDirect)(id, function (cids) { 2370 | var rbb = genRbb(id, true); 2371 | if (!rbb.cids || !rbb.pages) showMsg(bilibili.text.fail.network, 1e5, 'warning'); 2372 | else genFakePage(rbb); 2373 | }, function () { 2374 | showMsg(bilibili.text.fail.network, 1e5, 'warning'); 2375 | }); 2376 | }; 2377 | 2378 | // 使用av1/index_1.html作为显示生成的页面的临时页面 2379 | var genFakePage = function (rbb) { 2380 | rbb = rbb || JSON.parse(hashArg.get('rbb')); 2381 | try { 2382 | debug('rbb: %o', rbb); 2383 | if (!rbb.cids) return loadCidFirst(rbb); 2384 | var content = genXML(bilibili.html.page, { 2385 | 'aid': rbb.aid, 2386 | 'mid': rbb.mid, 2387 | 'title': xmlEscape(rbb.title), 2388 | 'description': xmlEscape(rbb.description), 2389 | 'kwtags': JSON.stringify(rbb.tag), 2390 | 'spid': rbb.spid, 2391 | }); 2392 | var callback = function () { }; 2393 | var doc = new DOMParser().parseFromString(content, 'text/html'); 2394 | var old = replaceDocument(doc, function () { callback(); }); 2395 | var getNode = function (qs) { 2396 | var obj = old.querySelector(qs); 2397 | return obj.parentNode.removeChild(obj); 2398 | }; 2399 | try { 2400 | var z = document.querySelector('.z'); 2401 | ['.z_top', '.header'].map(getNode).forEach(function (obj) { 2402 | z.parentNode.insertBefore(obj, z); 2403 | }); 2404 | ['.footer'].map(getNode).forEach(function (obj) { 2405 | z.parentNode.appendChild(obj); 2406 | }); 2407 | } catch (e) { 2408 | debug('Error while add elements: %o', e); 2409 | } 2410 | window.addEventListener('load', function () { 2411 | location.href = genCode(bilibili.js.showsp, rbb); 2412 | }); 2413 | addPages(rbb.aid, rbb.cids, rbb.pages, rbb.pid || Object.keys(rbb.pages)[0]); 2414 | if (rbb.spid) callback = showBgmInfo(rbb); 2415 | } catch (e) { 2416 | debug('Error while replacing page: %o', e); 2417 | } 2418 | activeFixLinks(); 2419 | GM_addStyle('html { display: block !important; }'); 2420 | } 2421 | if (preLoaded.fakePage) genFakePage(); 2422 | 2423 | // 向unsafeWindow暴露一系列接口以供其他函数调用 2424 | var exportHandler = (function () { 2425 | var funcs = {}; 2426 | var handler = function (args) { 2427 | var funcName = args[0]; args = args.slice(1); 2428 | try { 2429 | funcs[funcName].apply(null, args); 2430 | } catch (e) { 2431 | debug("Failed to call handler %s with arguments %o", funcName, args); 2432 | debug("Error message: %s", e); 2433 | } 2434 | }; 2435 | handler.regist = function (funcName, func) { return (funcs[funcName] = func); }; 2436 | return handler; 2437 | }()); 2438 | 2439 | // ping 检查本脚本是否被加载,或注册本脚本被加载时的回调函数 2440 | // 参数:callback 回调函数(Function) 2441 | // 参数: (无) 2442 | var pingHandler = exportHandler.regist('ping', function (callback) { 2443 | if (typeof callback !== 'function') return; 2444 | call(callback); 2445 | }); 2446 | 2447 | // getAid 通过cid获得aid和pid信息 2448 | // 参数:cid 视频的chatid,如529622(Number) 2449 | // onsucc 成功获取时的回调函数(Function) 2450 | // 参数:id 一个包括aid和pid的对象,如{"aid":314,"pid":1}。 2451 | // onerror 获取失败的回调函数(Function) 2452 | // 参数:(无) 2453 | var getAidHandler = exportHandler.regist('getAid', function (cid, onsucc, onerror) { 2454 | if (typeof cid !== 'number') return; 2455 | if (typeof onsucc !== 'function') return; 2456 | if (typeof onerror !== 'function' && typeof onerror !== 'undefined') return; 2457 | getAid(cid, onsucc, onerror); 2458 | }); 2459 | 2460 | // cid 获取当前视频的cid 2461 | // 参数:callback 返回cid的回调函数(Function) 2462 | // 参数:null 当前无法获取或不是视频页面(可忽略,获取后会在重新调用回调函数) 2463 | // 参数:cid 视频的cid(Number) 2464 | // 在一些情况导致替换了当前页面的播放器后,可能会反复调用该回调函数 2465 | getCurrentCid.handler = (function () { 2466 | var waiting = []; 2467 | var handler = exportHandler.regist('cid', function (callback) { 2468 | if (typeof callback !== 'function') return; 2469 | var cid = getCurrentCid() || null; 2470 | call(function () { callback(cid); }); 2471 | if (cid === null) waiting.push(callback); 2472 | }); 2473 | handler.update = function () { 2474 | var p = waiting; waiting = []; 2475 | p.map(handler); 2476 | }; 2477 | return handler; 2478 | }()); 2479 | 2480 | // getCid 通过aid和pid获取cid 2481 | // 参数:id 一个包括aid(articlecid,av号)和pid(pageid)的对象, 2482 | // 如{"aid":314,"pid":1};或{"aid":314,"pid":null}。(Object) 2483 | // onsucc 成功获取时的回调函数(Function) 2484 | // 参数:cid(Number),如529622(对应第一种参数); 2485 | // 或一个pid到cid的键值对组(Object),如{"1":529622}(对应第二种参数) 2486 | // onerror 获取失败时的回调函数(Function) 2487 | // 参数:(无) 2488 | // methods 获取cid使用哪些方法和这些方法的顺序(String构成的Array) 2489 | // 可能的取值:"direct" 直接获取(可能包括API、HTML5、AssDown、PlayList等) 2490 | // "undirect" 间接获取(通过相邻视频的cid猜测) 2491 | // "cached" 读取缓存(如果之前获取过且用户没有禁用缓存) 2492 | // 说明:如果只需要一个分页请勿将pid留空,可以有更大的几率获取到cid。 2493 | // 如果只需要当前视频的cid,请使用cid接口。 2494 | var getCidHandler = exportHandler.regist('getCid', function (id, onsucc, onerror, methods) { 2495 | if (typeof id === 'number') id = { 'aid': id }; 2496 | if (typeof id !== 'object') return; 2497 | if (typeof id.aid !== 'number') return; 2498 | if (typeof id.pid === 'undefined') id.pid = null; 2499 | if (typeof id.pid !== 'number' && id.pid !== null) return; 2500 | if (typeof onsucc !== 'function') return; 2501 | if (typeof onerror !== 'function' && typeof onerror !== 'undefined') return; 2502 | var getCids = { 2503 | 'api': getCidAPI, 2504 | 'direct': getCidDirect, 2505 | 'cached': getCidCached, 2506 | 'undirect': getCidUndirect, 2507 | }; 2508 | methods = [].slice.call(methods, 0); 2509 | var getCid = []; 2510 | (methods || []).forEach(function (method) { 2511 | if (getCids[method]) getCid.push(getCids[method]); 2512 | }); 2513 | if (!getCid.length) getCid = [getCidAPI, getCidDirect, getCidCached]; 2514 | getCid = getCid.reduce(function (x, y) { 2515 | if (!x || !y) return x || y; return x.concat(y); 2516 | }); 2517 | getCid(id, onsucc, onerror); 2518 | }); 2519 | 2520 | // replaced 注册发生替换播放器事件时的回调函数 2521 | // 参数:callback 替换播放器时回调(Function) 2522 | // 参数:true 表示事件已经被注册 2523 | // 参数:cid 播放器被替换时调用,参数是视频的cid(Number) 2524 | var onReplacedBofqiHandler = exportHandler.regist('replaced', function (callback) { 2525 | if (typeof callback !== 'function') return; 2526 | replacedBofqi.add(callback); 2527 | callback(true); 2528 | }); 2529 | 2530 | // added 注册添加评论等相关信息时的回调函数 2531 | // 参数:callback 替换播放器时回调(Function) 2532 | // 参数:(无) 2533 | var onAddedBofqiHandler = exportHandler.regist('added', function (callback) { 2534 | if (typeof callback !== 'function') return; 2535 | addedBofqi.add(callback); 2536 | }); 2537 | 2538 | // 向unsafeWindow暴露这些接口 2539 | unsafeWindow.replaceBilibiliBofqi = (function () { 2540 | if (!bilibili.config.export) return; 2541 | var x = {}; 2542 | x.push = function () { 2543 | debug("replaceBilibiliBofqi export, arguments: %o", arguments); 2544 | [].slice.call(arguments, 0).forEach(exportHandler); 2545 | }; 2546 | if (unsafeWindow.replaceBilibiliBofqi && 2547 | unsafeWindow.replaceBilibiliBofqi.constructor.name === 'Array') 2548 | unsafeWindow.replaceBilibiliBofqi.forEach(exportHandler); 2549 | return x; 2550 | }()); 2551 | 2552 | 2553 | addStyle(); 2554 | 2555 | }; 2556 | 2557 | if (!document.body) document.addEventListener('DOMContentLoaded', cosmos); 2558 | else setTimeout(cosmos, 0); 2559 | 2560 | // 修正新番列表中部分视频不显示的问题 2561 | // Bilibili Show Hidden Bangumi 2562 | (function fixBangumiTwoList() { 2563 | 2564 | var page = (function () { 2565 | var r = location.href.match(/http:\/\/[^\/]*\/video\/bangumi-two-(\d+).html/); 2566 | return r && r[1] && Number(r[1]); 2567 | }()); 2568 | if (!page) return; 2569 | 2570 | var bilibili = { 2571 | 'url': { 2572 | 'host': [ 2573 | 'www.bilibili.com', 2574 | 'bilibili.kankanews.com', 2575 | 'www.bilibili.tv', 2576 | 'www.bilibili.cn', 2577 | ], 2578 | }, 2579 | 'host': location.host, 2580 | }; 2581 | if (bilibili.url.host.indexOf(bilibili.host) === -1) 2582 | bilibili.host = bilibili.url.host[0]; 2583 | 2584 | var loaded = !!document.body, data = null; 2585 | 2586 | // 将数字转换成以万为单位计数的形式 2587 | // http://static.hdslb.com/js/base.core.v2.js 2588 | var formatFriendlyNumber = function (b) { 2589 | if ('number' === typeof b) b = String(b); 2590 | if (!(0 <= b.indexOf("\u4e07") || 0 <= b.indexOf(","))) { 2591 | return (b = parseInt(b)) ? 10000 <= b && (b = (b / 10000).toFixed(1) + "\u4e07") : b = "--", b 2592 | } 2593 | }; 2594 | // 转义XML字符 2595 | var xmlEscape = function (s) { 2596 | return String(s).replace(/./g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); 2597 | }; 2598 | var xmlUnescape = function (s) { 2599 | var d = document.createElement('div'); 2600 | d.innerHTML = s; 2601 | return d.textContent; 2602 | }; 2603 | 2604 | // 显示新番列表 2605 | var showList = function () { 2606 | GM_addStyle('.video_list ul.vd_list { visibility: visible; }') 2607 | }; 2608 | 2609 | // 将获取的数据添加到网页上 2610 | var addList = function () { 2611 | var i; 2612 | var ul = document.createElement('ul'); 2613 | ul.className = 'vd_list'; 2614 | var listtype = document.querySelector('.vd_list li').className; 2615 | data.forEach(function (video) { 2616 | var c = document.createElement('ul'); 2617 | var url = '/video/av' + (video.visible === 'mobile' ? '1/index_1.html#rbb={%22aid%22:' + video.aid + '}' : video.aid + '/'); 2618 | c.innerHTML = [ 2619 | '
  • ', 2620 | '', 2621 | '', 2622 | '', 2623 | '', xmlEscape(video.title), '', // modified 2624 | '
    ', 2625 | '', formatFriendlyNumber(video.play), '', 2626 | '', formatFriendlyNumber(video.favorites), '', 2627 | '', formatFriendlyNumber(video.video_review), '', 2628 | '', xmlEscape(video.create), '', 2629 | '
    ', 2630 | '
    ', xmlEscape(video.description), '
    ', 2631 | '', xmlEscape(video.author), '', 2632 | '
  • ', 2633 | ].join(''); 2634 | ul.appendChild(c.firstChild); 2635 | }); 2636 | var cnt = document.querySelector('.video_list .vd_list_cnt'); 2637 | cnt.removeChild(cnt.firstChild); 2638 | cnt.insertBefore(ul, cnt.firstChild); 2639 | }; 2640 | 2641 | var hideNextPage = function () { 2642 | GM_xmlhttpRequest({ 2643 | 'method': 'GET', 2644 | 'url': 'http://' + bilibili.host + '/video/bangumi-two-' + (page + 1) + '.html', 2645 | 'onload': function (resp) { 2646 | var doc = (new DOMParser()).parseFromString(resp.responseText, 'text/html'); 2647 | dataFromDocument(doc).map(function (video) { 2648 | var cnt = [].slice.call(document.querySelectorAll('.vd_list li'), 0); 2649 | cnt.map(function (li) { 2650 | if (~li.querySelector('.title').href.match(/\/av(\d+)/)[1] === ~video.aid) 2651 | li.parentNode.removeChild(li); 2652 | }); 2653 | }); 2654 | }, 2655 | }); 2656 | }; 2657 | 2658 | var active = function () { 2659 | if (!loaded || !data) return; 2660 | data = mergeData(data); 2661 | try { addList(); } catch (e) { } 2662 | showList(); 2663 | hideNextPage(); 2664 | }; 2665 | 2666 | var dataFromDocument = function (doc) { 2667 | var cnt = [].slice.call(doc.querySelectorAll('.vd_list li'), 0); 2668 | return cnt.map(function (li) { 2669 | try { 2670 | var qs = li.querySelector.bind(li); 2671 | return { 2672 | 'aid': qs('.title').href.match(/\/av(\d+)/)[1], 2673 | 'pic': qs('.preview img').src, 2674 | 'title': qs('.title').textContent, 2675 | 'play': qs('.gk').textContent, 2676 | 'favorites': qs('.sc').textContent, 2677 | 'video_review': qs('.dm').textContent, 2678 | 'create': qs('.date').textContent, 2679 | 'description': qs('.info').textContent, 2680 | 'mid': qs('.up').href.match(/\/(\d+)/)[1], 2681 | 'author': qs('.up').textContent, 2682 | 'visible': 'web', 2683 | }; 2684 | } catch (e) { } 2685 | }).filter(function (x) { return x; }); 2686 | }; 2687 | 2688 | // 将返回的结果和页面上已有的视频拼合,显示尽可能多的视频 2689 | var mergeData = function (data) { 2690 | var add2Data = function (video) { 2691 | var found = -1; 2692 | data.forEach(function (v, i) { 2693 | if (Number(v.aid) == Number(video.aid)) found = i; 2694 | }); 2695 | if (found === -1) data.push(video); 2696 | else if (data[found].visible !== video.visible) 2697 | data[found].visible = 'all'; 2698 | }; 2699 | dataFromDocument(document).forEach(add2Data); 2700 | data.sort(function (x, y) { return Number(y.aid) - Number(x.aid); }) 2701 | return data; 2702 | }; 2703 | 2704 | // 使用手机的API获取数据 2705 | var getData = function () { 2706 | GM_xmlhttpRequest({ 2707 | 'method': 'GET', 2708 | 'url': 'http://api.bilibili.com/list?pagesize=24&type=json&page=' + page + 2709 | '&ios=0&order=default&appkey=0a99fa1d87fdd38c&platform=ios&tid=33', 2710 | 'headers': { 'User-Agent': 'bilianime/570 CFNetwork/672.0.8 Darwin/14.0.0' }, 2711 | 'onload': function (resp) { 2712 | var respData, i; 2713 | try { 2714 | respData = JSON.parse(resp.responseText).list; 2715 | for (data = [], i = 0; i < 24; i++) { 2716 | data[i] = respData[i]; 2717 | data[i].title = xmlUnescape(data[i].title); 2718 | data[i].visible = 'mobile' 2719 | } 2720 | active(); 2721 | } catch (e) { showList(); } 2722 | }, 2723 | 'onerror': showList, 2724 | 'timeout': 3000, 2725 | 'ontimeout': showList, 2726 | }); 2727 | }; 2728 | 2729 | (function () { 2730 | // 先隐藏已有的新番列表 2731 | GM_addStyle('.video_list ul.vd_list { visibility: hidden; }') 2732 | // 检查文档树是否已经被解析出 2733 | if (!loaded) document.addEventListener('DOMContentLoaded', function () { 2734 | loaded = true; 2735 | active(); 2736 | }); 2737 | getData(); 2738 | }()); 2739 | 2740 | 2741 | }()); 2742 | 2743 | var addStyle = function () { 2744 | GM_addStyle([ 2745 | // rbb-menu 2746 | '#rbb-menu-container, #rbb-menu-container * { all: unset; margin: 0; padding: 0; top: auto; left: auto; right: auto; bottom: auto; position: static; }', 2747 | '#rbb-menu-container .rbb-menu { ', 2748 | 'font-size: 16px; text-align: center; ', 2749 | 'border: 4px solid rgba(204, 204, 204, 0.5); border-radius: 4px; ', 2750 | 'position: absolute; background: #fff; ', 2751 | 'z-index: 10000; ', 2752 | 'background-clip: padding-box;', 2753 | '}', 2754 | '#rbb-menu-container .rbb-menu[message] { margin-top: -32px; }', 2755 | '#rbb-menu-container .rbb-menu .rbb-menu-title { display: none; }', 2756 | '#rbb-menu-container .rbb-menu[message] .rbb-menu-title { display:block; }', 2757 | '#rbb-menu-container .rbb-menu, #rbb-menu-container .rbb-menu-item { overflow: visible; }', 2758 | '#rbb-menu-container .rbb-menu .rbb-menu-message { color: #000; padding: 4px; }', 2759 | '#rbb-menu-container .rbb-menu a, #rbb-menu-container .rbb-menu span { color: #00a1d6; }', 2760 | '#rbb-menu-container .rbb-menu a { cursor: pointer; }', 2761 | '#rbb-menu-container .rbb-menu a:hover { color: #f25d8e; }', 2762 | '#rbb-menu-container .rbb-menu .rbb-menu-title { height: 32px; width: 100%; }', 2763 | '#rbb-menu-container .rbb-menu .rbb-menu-item { height: 32px; width: 120px; float: left; }', 2764 | '#rbb-menu-container .rbb-menu.rbb-float-right .rbb-menu-item { float: right; }', 2765 | '#rbb-menu-container .rbb-menu .rbb-submenu .rbb-menu-item { float: none; }', 2766 | '#rbb-menu-container .rbb-menu .rbb-submenu:empty { display: none; }', 2767 | '#rbb-menu-container .rbb-menu .rbb-menu-item .rbb-submenu { ', 2768 | 'max-height: 0px; background: #fff; float: left; min-width: 112px; ', 2769 | 'background-clip: padding-box; margin-left: -4px;', 2770 | 'padding: 0 16px; overflow-y: auto; text-align: left;', 2771 | 'transition: max-height 0.25s 0.25s, border 0s 0.5s;', 2772 | 'position: relative; z-index: 10000; ', 2773 | 'border: 0 solid rgba(204, 204, 204, 0.5); border-top: none; ', 2774 | '}', 2775 | '#rbb-menu-container .rbb-menu .rbb-menu-item:hover .rbb-submenu { ', 2776 | 'transition: max-height 0.25s 0s, border 0s 0s; ', 2777 | 'max-height: 256px; z-index: 10001; ', 2778 | 'border: 4px solid rgba(204, 204, 204, 0.5); border-radius: 4px; border-top: none; ', 2779 | '}', 2780 | '#rbb-menu-container .rbb-menu .rbb-menu-item:hover .rbb-submenu, ', 2781 | '#rbb-menu-container .rbb-menu .rbb-menu-item:hover { background: #efefef; background-clip: padding-box; }', 2782 | '#rbb-menu-container .rbb-menu .rbb-menu-item span { cursor: default; }', 2783 | '#rbb-menu-container .rbb-menu .rbb-suggest span, ', 2784 | '#rbb-menu-container .rbb-menu .rbb-suggest a { font-weight: bold; }', 2785 | '#rbb-menu-container .rbb-menu .rbb-menu-item span, #rbb-menu-container .rbb-menu .rbb-menu-item a {', 2786 | 'font-size: 16px; line-height: 24px; padding: 4px 16px; display: block;', 2787 | '}', 2788 | '#rbb-menu-container .rbb-menu .rbb-submenu .rbb-menu-item { white-space: nowrap; width: 100%; }', 2789 | '#rbb-menu-container .rbb-menu .rbb-menu-item.rbb-menu-suggest { float: none; width: 100%; }', 2790 | '#rbb-menu-container .rbb-menu .rbb-menu-link .rbb-menu-sp-logo {', 2791 | 'background-image: url("http://static.hdslb.com/images/v2images/icons_home.png");', 2792 | 'background-position: 5px -627px;', 2793 | 'display: inline-block;', 2794 | 'height: 16px;', 2795 | 'margin: -6px 0;', 2796 | 'padding: 4px;', 2797 | 'width: 16px;', 2798 | '}', 2799 | // bsp-menu 2800 | '#bsp-menu-container { display: none !important; }', 2801 | '#rbb-menu-container .bsp-menu {', 2802 | 'font-size: 16px;', 2803 | 'border: 4px solid rgba(204, 204, 204, 0.5); border-radius: 4px;', 2804 | 'position: absolute; z-index: 10000;', 2805 | 'background: #fff;', 2806 | 'background-clip: padding-box;', 2807 | 'overflow-x: hidden; overflow-y: auto;', 2808 | 'min-width: 256px;', 2809 | 'text-align: left;', 2810 | '}', 2811 | '#rbb-menu-container .bsp-menu .bsp-menu-item {', 2812 | 'overflow: hidden;', 2813 | '}', 2814 | '#rbb-menu-container .bsp-menu .bsp-menu-item:last-child {', 2815 | 'float: left; width: calc(100% - 16px);', 2816 | '}', 2817 | '#rbb-menu-container .bsp-menu .bsp-menu-link {', 2818 | 'height: 32px;', 2819 | 'width: 100%;', 2820 | 'overflow: hidden;', 2821 | 'display: block;', 2822 | '}', 2823 | '#rbb-menu-container .bsp-menu .bsp-menu-item:last-child .bsp-menu-link {', 2824 | 'width: calc(100% + 16px);', 2825 | '}', 2826 | '#rbb-menu-container .bsp-menu .bsp-menu-link span {', 2827 | 'height: 24px; line-height: 24px;', 2828 | 'display: block;', 2829 | 'position: relative;', 2830 | 'padding: 4px 16px;', 2831 | 'margin: 0;', 2832 | 'white-space: nowrap;', 2833 | 'cursor: pointer;', 2834 | '}', 2835 | '#rbb-menu-container .bsp-menu .bsp-menu-title {', 2836 | 'text-align: center;', 2837 | '}', 2838 | '#rbb-menu-container .bsp-menu .bsp-menu-link span.bsp-menu-bg,', 2839 | '#rbb-menu-container .bsp-menu .bsp-menu-link span.bsp-menu-fg {', 2840 | 'position: relative;', 2841 | '}', 2842 | '#rbb-menu-container .bsp-menu .bsp-menu-link span.bsp-menu-bg { margin-bottom: -32px; z-index: 10000; }', 2843 | '#rbb-menu-container .bsp-menu .bsp-menu-link span.bsp-menu-fg { color: transparent; z-index: 10001;', 2844 | 'background-image: linear-gradient(to right,', 2845 | 'rgba(255, 255, 255, 0) 0,', 2846 | 'rgba(255, 255, 255, 0) calc(100% - 64px),', 2847 | '#fff calc(100% - 16px),', 2848 | '#fff 100%);', 2849 | '}', 2850 | '#rbb-menu-container .bsp-menu .bsp-menu-link .bsp-menu-bg { color: #00a1d6; }', 2851 | '#rbb-menu-container .bsp-menu .bsp-menu-link:hover .bsp-menu-bg { color: #f25d8e; }', 2852 | '#rbb-menu-container .bsp-menu .bsp-menu-link span.bsp-menu-fg, ', 2853 | '#rbb-menu-container .bsp-menu .bsp-menu-link a.bsp-menu-fg:hover { color: transparent; }', 2854 | '#rbb-menu-container .bsp-menu.bsp-menu-with-sp { padding-top: 32px;}', 2855 | '#rbb-menu-container .bsp-menu .bsp-menu-sp { position: absolute; top: 0; text-align: center; width: 100%; }', 2856 | '#rbb-menu-container .bsp-menu .bsp-menu-link .bsp-menu-sp-logo {', 2857 | 'background-image: url("http://static.hdslb.com/images/v2images/icons_home.png");', 2858 | 'background-position: 5px -627px;', 2859 | 'display: inline-block;', 2860 | 'height: 16px;', 2861 | 'margin: -6px 0;', 2862 | 'padding: 4px;', 2863 | 'width: 16px;', 2864 | '}', 2865 | // rbb-message 2866 | '#rbb-message { position: absolute; left: calc((100% - 980px) / 2); }', 2867 | '.widescreen~#rbb-message { left: calc((100% - 1170px) / 2); }', 2868 | // old-bofqi 2869 | '.widescreen #old-bofqi { width: 1160px; }', 2870 | '#old-bofqi { margin: 5px 5px 25px 5px; position: relative; }', 2871 | '#old-bofqi .player { height: 536px; width: 970px; }', 2872 | '.widescreen #old-bofqi .player { height: 666px; width: 1160px; }', 2873 | // curPage 2874 | '.viewbox .alist a.curPage:hover { ', 2875 | 'background: url("../images/v2images/icons_home.png") no-repeat scroll 8px -1856px #00A1D6;', 2876 | '}', 2877 | // rbb_alist 2878 | '#rbb_alist { float:left; width: 100%; max-height: 70px; overflow-y: auto; }', 2879 | // copyright player selector 2880 | '.player-placeholder { display: none; }', 2881 | ].join('')); 2882 | }; 2883 | addStyle(); 2884 | 2885 | --------------------------------------------------------------------------------