解除B站区域限制

通过替换获取视频地址接口的方式, 实现解除B站区域限制; 只对HTML5播放器生效; 只支持bangumi.bilibili.com域名下的番剧视频;

目前為 2017-05-01 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 解除B站区域限制
  3. // @namespace http://tampermonkey.net/
  4. // @version 5.1.0
  5. // @description 通过替换获取视频地址接口的方式, 实现解除B站区域限制; 只对HTML5播放器生效; 只支持bangumi.bilibili.com域名下的番剧视频;
  6. // @author ipcjs
  7. // @require https://static.hdslb.com/js/md5.js
  8. // @include *://www.bilibili.com/video/av*
  9. // @include *://bangumi.bilibili.com/anime/*
  10. // @include *://www.bilibili.com/blackboard/html5player.html*
  11. // @run-at document-start
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16. log('[' + GM_info.script.name + '] run on: ' + window.location.href);
  17.  
  18. var MODE_DEFAULT = 'default'; // 默认模式, 自动判断使用何种模式, 推荐;
  19. var MODE_REPLACE = 'replace'; // 替换模式, 替换有区域限制的视频的接口的返回值; 因为替换的操作是同步的会卡一下界面, 但没有区域限制的视频不会受到影响;
  20. var MODE_REDIRECT = 'redirect'; // 重定向模式, 直接重定向所有番剧视频的接口到代理服务器; 所有番剧视频都通过代理服务器获取视频地址, 如果代理服务器不稳定, 可能加载不出视频;
  21.  
  22. var settings = getCookies();
  23. var proxyServer = settings.balh_server || 'http://biliplus.ipcjsdev.tk'; // 优先从cookie中读取服务器地址
  24. var isBlockedVip = settings.balh_blocked_vip; // "我是一位被永久封号的大会员"(by Google翻译)
  25. var mode = settings.balh_mode || (isBlockedVip ? MODE_REDIRECT : MODE_DEFAULT); // 若账号是被永封的大会员, 默认使用重定向模式
  26.  
  27. log('Mode:', mode, 'isBlockedVip:', isBlockedVip, 'server:', proxyServer, 'readyState:', document.readyState);
  28.  
  29. var bilibiliApis = (function () {
  30. function BilibiliApi(props) {
  31. Object.assign(this, props);
  32. }
  33.  
  34. BilibiliApi.prototype.asyncAjaxByProxy = function (originUrl, success, error) {
  35. var one_api = this;
  36. $.ajax({
  37. url: one_api.transToProxyUrl(originUrl),
  38. async: true,
  39. xhrFields: {withCredentials: true},
  40. success: function (result) {
  41. log('==>', result);
  42. success(one_api.processProxySuccess(result));
  43. // log('success', arguments, this);
  44. },
  45. error: function (e) {
  46. log('error', arguments, this);
  47. error(e);
  48. }
  49. });
  50. };
  51. return {
  52. _get_source: new BilibiliApi({
  53. transToProxyUrl: function (url) {
  54. return proxyServer + '/api/bangumi?season=' + window.season_id;
  55. },
  56. processProxySuccess: function (data) {
  57. var found = null;
  58. for (var i = 0; i < data.result.episodes.length; i++) {
  59. if (data.result.episodes[i].episode_id == window.episode_id) {
  60. found = data.result.episodes[i];
  61. }
  62. }
  63. var returnVal = found !== null ? {
  64. "code": 0,
  65. "message": "success",
  66. "result": {
  67. "aid": found.av_id,
  68. "cid": found.danmaku,
  69. "episode_status": isBlockedVip ? 2 : found.episode_status,
  70. "payment": {"price": "9876547210.33"},
  71. "player": "vupload",
  72. "pre_ad": 0,
  73. "season_status": isBlockedVip ? 2 : data.result.season_status
  74. }
  75. } : {
  76. code: -404,
  77. message: '不存在该剧集'
  78. };
  79. return returnVal;
  80. }
  81. }),
  82. _playurl: new BilibiliApi({
  83. transToProxyUrl: function (url) {
  84. return proxyServer + '/BPplayurl.php?' + url.split('?')[1].replace(/(cid=\d+)/, '$1|' + (url.match(/module=(\w+)/) || ['', 'bangumi'])[1]);
  85. },
  86. processProxySuccess: function (data) {
  87. if (data.code === -403) {
  88. // window.alert('当前使用的服务器(' + proxyServer + ')依然有区域限制');
  89. showNotification(Date.now(), GM_info.script.name, '突破黑洞失败,我们未能穿透敌人的盔甲\n当前代理服务器(' + proxyServer + ')依然有区域限制Σ(  ̄□ ̄||)', '//bangumi.bilibili.com/favicon.ico', 3e3);
  90. } else if (data.code) {
  91. console.error(data);
  92. showNotification(Date.now(), GM_info.script.name, '突破黑洞失败\n' + JSON.stringify(data), '//bangumi.bilibili.com/favicon.ico', 3e3);
  93. } else if (isAreaLimitForPlayUrl(data)) {
  94. console.error('>>aera limit');
  95. showNotification(Date.now(), GM_info.script.name, '突破黑洞失败,需要登录\n点此进行登录', '//bangumi.bilibili.com/favicon.ico', 3e3, showLogin);
  96. // if (window.confirm('试图获取视频地址失败, 请登录代理服务器' +
  97. // '\n注意: 只支持"使用bilibili账户密码进行登录"'
  98. // )) {
  99. // window.top.location = proxyServer + '/login';
  100. // }
  101. } else {
  102. // showNotification(Date.now(), GM_info.script.name, '已突破黑洞,开始加载视频', '//bangumi.bilibili.com/favicon.ico', 2e3);
  103. }
  104. return data;
  105. }
  106. })
  107. };
  108. })();
  109.  
  110. if (!window.jQuery) { // 若还未加载jQuery, 则监听
  111. var jQuery;
  112. Object.defineProperty(window, 'jQuery', {
  113. configurable: true, enumerable: true, set: function (v) {
  114. jQuery = v;
  115. injectDataFilter();// 设置jQuery后, 立即注入
  116. }, get: function () {
  117. return jQuery;
  118. }
  119. });
  120. } else {
  121. injectDataFilter();
  122. }
  123.  
  124. documentReady(function () {
  125. if (window.location.hostname === 'bangumi.bilibili.com') {
  126. checkLoginState();
  127. checkHtml5();
  128. } else if (window.location.href.indexOf('www.bilibili.com/video/av') !== -1) {
  129. tryBangumiRedirect();
  130. }
  131. });
  132.  
  133. // 暴露接口
  134. window.bangumi_aera_limit_hack = {
  135. setCookie: setCookie,
  136. getCookie: getCookie,
  137. login: showLogin,
  138. _clear_local_value: function () {
  139. delete localStorage.balh_notFirst;
  140. delete localStorage.balh_login;
  141. delete localStorage.balh_mainLogin;
  142. delete localStorage.oauthTime;
  143. delete localStorage.balh_h5_not_first;
  144. }
  145. };
  146.  
  147. ////////////////接下来全是函数/////////////////
  148.  
  149. function injectDataFilter() {
  150. window.jQuery.ajaxSetup({
  151. dataFilter: jqueryDataFilter
  152. });
  153. replaceAjax();
  154. }
  155.  
  156. function jqueryDataFilter(data, type) {
  157. var json, group;
  158. // log(arguments, this);
  159. if (this.url.startsWith(window.location.protocol + '//bangumi.bilibili.com/web_api/season_area')) {
  160. // 番剧页面是否要隐藏番剧列表 API
  161. log(data);
  162. json = JSON.parse(data);
  163. // 限制区域时的data为:
  164. // {"code":0,"message":"success","result":{"play":0}}
  165. if (json.code === 0 && json.result && json.result.play === 0) {
  166. mode === MODE_DEFAULT && setAreaLimitSeason(true);
  167. json.result.play = 1; // 改成1就能够显示
  168. data = JSON.stringify(json);
  169. log('==>', data);
  170. showNotification(Date.now(), GM_info.script.name, '检测到区域限制番剧,准备启动黑洞突破程序\n\n刷下存在感(°∀°)ノ', '//bangumi.bilibili.com/favicon.ico', 2e3);
  171. } else {
  172. mode === MODE_DEFAULT && setAreaLimitSeason(false);
  173. }
  174. }
  175. return data;
  176. }
  177.  
  178. /*
  179. {"code":0,"message":"success","result":{"aid":9854952,"cid":16292628,"episode_status":2,"payment":{"price":"0"},"player":"vupload","pre_ad":0,"season_status":2}}
  180. */
  181. function replaceAjax() {
  182. var originalAjax = $.ajax;
  183. $.ajax = function (arg0, arg1) {
  184. // log(arguments);
  185. var param;
  186. if (arg1 === undefined) {
  187. param = arg0;
  188. } else {
  189. arg0 && (arg1.url = arg0);
  190. param = arg1;
  191. }
  192. var oriSuccess = param.success;
  193. var one_api;
  194. if (param.url.match('/web_api/get_source')) {
  195. one_api = bilibiliApis._get_source;
  196. if (mode === MODE_REDIRECT || (mode === MODE_DEFAULT && isAreaLimitSeason())) { // 对应redirect模式
  197. param.url = one_api.transToProxyUrl(param.url);
  198. param.type = 'GET';
  199. delete param.data;
  200. param.success = function (data) {
  201. var returnVal = one_api.processProxySuccess(data);
  202. log('Redirected request: get_source', returnVal);
  203. oriSuccess(returnVal);
  204. };
  205. } else { // 对应replace模式
  206. param.success = function (json) {
  207. log(json);
  208. if (json.code === -40301 // 区域限制
  209. || json.result.pay_user && isBlockedVip) { // 需要付费的视频, 此时B站返回的cid是错了, 故需要使用代理服务器的接口
  210. one_api.asyncAjaxByProxy(param.url, oriSuccess, function (e) {
  211. oriSuccess(json); // 新的请求报错, 也应该返回原来的数据
  212. });
  213. mode === MODE_DEFAULT && setAreaLimitSeason(true); // 只要默认模式才要跟踪是否有区域限制
  214. } else {
  215. mode === MODE_DEFAULT && setAreaLimitSeason(false);
  216. if (isBlockedVip && json.code === 0 && json.result.pre_ad) {
  217. json.result.pre_ad = 0; // 去除前置广告
  218. }
  219. oriSuccess(json); // 保证一定调用了原来的success
  220. }
  221. };
  222. }
  223. } else if (param.url.match('/player/web_api/playurl')) {
  224. one_api = bilibiliApis._playurl;
  225. if (mode === MODE_REDIRECT || (mode === MODE_DEFAULT && isAreaLimitSeason())) {
  226. param.url = one_api.transToProxyUrl(param.url);
  227. param.success = function (data) {
  228. oriSuccess(one_api.processProxySuccess(data));
  229. };
  230. log('Redirected request: bangumi playurl -> ', param.url);
  231. } else {
  232. param.success = function (json) {
  233. // 获取视频地址 API
  234. log(json);
  235. if (isBlockedVip || isAreaLimitForPlayUrl(json)) {
  236. one_api.asyncAjaxByProxy(param.url, oriSuccess, function (e) {
  237. oriSuccess(json);
  238. });
  239. mode === MODE_DEFAULT && setAreaLimitSeason(true);
  240. } else {
  241. mode === MODE_DEFAULT && setAreaLimitSeason(false);
  242. oriSuccess(json);
  243. }
  244. }
  245. }
  246. }
  247. // default
  248. return originalAjax.apply(this, [param]);
  249. }
  250. }
  251.  
  252. function isAreaLimitSeason() {
  253. return getCookie('balh_season_' + getSeasonId());
  254. }
  255. function setAreaLimitSeason(limit) {
  256. var season_id = getSeasonId();
  257. setCookie('balh_season_' + season_id, limit ? '1' : undefined, ''); // 第三个参数为'', 表示时Session类型的cookie
  258. log('setAreaLimitSeason', season_id, limit);
  259. }
  260.  
  261. function getSeasonId() {
  262. try {
  263. return window.season_id || window.top.season_id;
  264. } catch (e) {
  265. console.error(e);
  266. return (window.top.location.pathname.match(/\/anime\/(\d+)/) || ['', '000'])[1];
  267. }
  268. }
  269.  
  270. function isAreaLimitForPlayUrl(json) {
  271. return json.durl && json.durl.length === 1 && json.durl[0].length === 15126 && json.durl[0].size === 124627;
  272. }
  273.  
  274. function getParam(url, key) {
  275. return (url.match(new RegExp('[?|&]' + key + '=(\\w+)')) || ['', ''])[1];
  276. }
  277.  
  278. function getCookies() {
  279. var map = document.cookie.split('; ').reduce(function (obj, item) {
  280. var entry = item.split('=');
  281. obj[entry[0]] = entry[1];
  282. return obj;
  283. }, {});
  284. return map;
  285. }
  286.  
  287. function getCookie(key) {
  288. return getCookies()[key];
  289. }
  290. // document.cookie=`bangumi_aera_limit_hack_server=https://www.biliplus.com; domain=.bilibili.com; path=/; expires=${new Date("2020-01-01").toUTCString()}`;
  291.  
  292. /**
  293. * @key key
  294. * @value 为undefined时, 表示删除cookie
  295. * @options 为undefined时, 表示过期时间为3年
  296. * 为''时, 表示Session cookie
  297. * 为数字时, 表示指定过期时间
  298. * 为{}时, 表示指定所有的属性
  299. * */
  300. function setCookie(key, value, options) {
  301. if (typeof options !== 'object') {
  302. options = {
  303. domain: '.bilibili.com',
  304. path: '/',
  305. 'max-age': value === undefined ? 0 : (options === undefined ? 94608000 : options)
  306. };
  307. }
  308. var c = Object.keys(options).reduce(function (str, key) {
  309. return str + '; ' + key + '=' + options[key];
  310. }, key + '=' + value);
  311. document.cookie = c;
  312. return c;
  313. }
  314.  
  315. function showLogin() {
  316. if (!document.getElementById('balh-style-login')) {
  317. var style = document.createElement('style');
  318. style.id = 'balh-style-login';
  319. document.head.appendChild(style).innerHTML = '@keyframes pop-iframe-in{0%{opacity:0;transform:scale(.7);}100%{opacity:1;transform:scale(1)}}@keyframes pop-iframe-out{0%{opacity:1;transform:scale(1);}100%{opacity:0;transform:scale(.7)}}.GMBiliPlusCloseBox{position:absolute;top:5%;right:8%;font-size:40px;color:#FFF}';
  320. }
  321.  
  322. var loginUrl = proxyServer + '/login',
  323. iframeSrc = 'https://passport.bilibili.com/login?appkey=27eb53fc9058f8c3&api=' + encodeURIComponent(loginUrl) + '&sign=' + hex_md5('api=' + loginUrl + 'c2ed53a74eeefe3cf99fbd01d8c9c375');
  324.  
  325. var div = document.createElement('div');
  326. div.id = 'GMBiliPlusLoginContainer';
  327. div.innerHTML = '<div style="position:fixed;top:0;left:0;z-index:10000;width:100%;height:100%;background:rgba(0,0,0,.5);animation-fill-mode:forwards;animation-name:pop-iframe-in;animation-duration:.5s;cursor:pointer"><iframe src="' + iframeSrc + '" style="background:#e4e7ee;position:absolute;top:10%;left:10%;width:80%;height:80%"></iframe><div class="GMBiliPlusCloseBox">×</div></div>';
  328. div.firstChild.addEventListener('click', function (e) {
  329. if (e.target === this || e.target.className === 'GMBiliPlusCloseBox') {
  330. if (!confirm('确认关闭?')) {
  331. return false;
  332. }
  333. div.firstChild.style.animationName = 'pop-iframe-out';
  334. setTimeout(function () {
  335. div.remove();
  336. window.location.reload();
  337. }, 5e2);
  338. }
  339. });
  340. document.body.appendChild(div);
  341. delete localStorage.balh_login;
  342. }
  343.  
  344. // 逻辑有点乱, 当前在如下情况才会弹一次登录提示框:
  345. // 1. 第一次使用
  346. // 2. 主站+服务器都退出登录后, 再重新登录主站
  347. function checkLoginState() {
  348. if (getCookie("DedeUserID") === undefined) {
  349. //未登录主站,强制指定值
  350. localStorage.balh_notFirst = 1;
  351. localStorage.balh_login = 0;
  352. localStorage.balh_mainLogin = 0
  353. } else if (localStorage.balh_mainLogin !== undefined) {
  354. //主站未登录变为登录,重置显示弹窗
  355. delete localStorage.balh_notFirst;
  356. delete localStorage.balh_login;
  357. delete localStorage.balh_mainLogin;
  358. delete localStorage.oauthTime;
  359. }
  360. if (!localStorage.balh_notFirst) {
  361. //第一次打开,确认是否已登陆;未登录显示确认弹窗
  362. localStorage.balh_notFirst = 1;
  363. checkExpiretime(function () {
  364. if (localStorage.oauthTime === undefined) {
  365. localStorage.balh_login = 0;
  366. if (confirm('看起来你是第一次使用' + GM_info.script.name + '\n要不要考虑进行一下授权?\n\n授权后可以观看区域限定番剧的1080P(如果你是大会员或承包过的话)\n\n你可以随时通过执行 bangumi_aera_limit_hack.login() 来打开授权页面')) {
  367. showLogin();
  368. }
  369. } else {
  370. localStorage.balh_login = 1;
  371. }
  372. });
  373. } else if (localStorage.balh_login === undefined) {
  374. //非第一次打开,登录状态被重置,重新检测
  375. checkExpiretime(function () {
  376. localStorage.balh_login = (localStorage.oauthTime === undefined) ? 0 : 1
  377. });
  378. } else if (localStorage.balh_login == 1 && Date.now() - parseInt(localStorage.oauthTime) > 24 * 60 * 60 * 1000) {
  379. //已登录,每天为周期检测key有效期,过期前五天会自动续期
  380. checkExpiretime();
  381. }
  382.  
  383. function checkExpiretime(loadCallback) {
  384. var script = document.createElement('script');
  385. script.src = proxyServer + '/login?act=expiretime';
  386. loadCallback && script.addEventListener('load', loadCallback);
  387. document.head.appendChild(script);
  388. }
  389. }
  390.  
  391. function checkHtml5() {
  392. if (!localStorage.balh_h5_not_first && localStorage.defaulth5 == 0 && window.GrayManager) {
  393. new MutationObserver(function (mutations, observer) {
  394. observer.disconnect();
  395. localStorage.balh_h5_not_first = 'yes';
  396. if (window.confirm(GM_info.script.name + '只在HTML5播放器下有效,是否切换到HTML5?')) {
  397. window.GrayManager.clickMenu('change_h5');// change_flash, change_h5
  398. }
  399. }).observe(document.querySelector('.player-content'), {
  400. childList: true, // 监听child的增减
  401. attributes: false, // 监听属性的变化
  402. });
  403. }
  404. }
  405.  
  406. // document载入完成后回调, 相当于$(cb);
  407. function documentReady(cb) {
  408. if (document.readyState !== 'loading') {
  409. return setTimeout(cb, 1);
  410. }
  411. var cbWrapper = function () {
  412. document.removeEventListener('DOMContentLoaded', cbWrapper);
  413. cb();
  414. };
  415. document.addEventListener('DOMContentLoaded', cbWrapper);
  416. }
  417.  
  418. function log() {
  419. console.log.apply(console, arguments);
  420. }
  421.  
  422. /**
  423. * 通知模块
  424. * 剽窃自 YAWF 用户脚本
  425. * 硬广:https://tiansh.github.io/yawf/
  426. */
  427. var notify = (function () {
  428. var avaliable = {};
  429. var shown = [];
  430. var use = {
  431. 'hasPermission': function () {
  432. return null;
  433. },
  434. 'requestPermission': function (callback) {
  435. return null;
  436. },
  437. 'hideNotification': function (notify) {
  438. return null;
  439. },
  440. 'showNotification': function (id, title, body, icon, delay, onclick) {
  441. return null;
  442. }
  443. };
  444.  
  445. // 检查一个微博是不是已经被显示过了,如果显示过了不重复显示
  446. var shownFeed = function (id) {
  447. return false;
  448. };
  449.  
  450. // webkitNotifications
  451. // Tab Notifier 扩展实现此接口,但显示的桌面提示最多只能显示前两行
  452. if (typeof webkitNotifications !== 'undefined') avaliable.webkit = {
  453. 'hasPermission': function () {
  454. return [true, null, false][webkitNotifications.checkPermission()];
  455. },
  456. 'requestPermission': function (callback) {
  457. return webkitNotifications.requestPermission(callback);
  458. },
  459. 'hideNotification': function (notify) {
  460. notify.cancel();
  461. afterHideNotification(notify);
  462. },
  463. 'showNotification': function (id, title, body, icon, delay, onclick) {
  464. if (shownFeed(id)) return null;
  465. var notify = webkitNotifications.createNotification(icon, title, body);
  466. if (delay && delay > 0) notify.addEventListener('display', function () {
  467. setTimeout(function () {
  468. hideNotification(notify);
  469. }, delay);
  470. });
  471. if (onclick) notify.addEventListener('click', onclick);
  472. notify.show();
  473. return notify;
  474. },
  475. };
  476.  
  477. // Notification
  478. // Firefox 22+
  479. // 显示4秒会自动关闭 https://bugzil.la/875114
  480. if (typeof Notification !== 'undefined') avaliable.standard = {
  481. 'hasPermission': function () {
  482. return {
  483. 'granted': true,
  484. 'denied': false,
  485. 'default': null,
  486. }[Notification.permission];
  487. },
  488. 'requestPermission': function (callback) {
  489. return Notification.requestPermission(callback);
  490. },
  491. 'hideNotification': function (notify) {
  492. notify.close();
  493. afterHideNotification(notify);
  494. },
  495. 'showNotification': function (id, title, body, icon, delay, onclick) {
  496. if (shownFeed(id)) return null;
  497. var notify = new Notification(title, {'body': body, 'icon': icon, 'requireInteraction': !delay});
  498. if (delay && delay > 0) notify.addEventListener('show', function () {
  499. setTimeout(function () {
  500. hideNotification(notify);
  501. }, delay);
  502. });
  503. if (onclick) notify.addEventListener('click', onclick);
  504. return notify;
  505. },
  506. };
  507.  
  508. // 有哪些接口可用
  509. var avaliableNotification = function () {
  510. return Object.keys(avaliable);
  511. };
  512. // 选择用哪个接口
  513. var choseNotification = function (prefer) {
  514. return (use = prefer && avaliable[prefer] || avaliable.standard);
  515. };
  516. choseNotification();
  517. // 检查权限
  518. var hasPermission = function () {
  519. return use.hasPermission.apply(this, arguments);
  520. };
  521. // 请求权限
  522. var requestPermission = function () {
  523. return use.requestPermission.apply(this, arguments);
  524. };
  525. // 显示消息
  526. var showNotification = function (title, body, icon, delay, onclick) {
  527. var notify = use.showNotification.apply(this, arguments);
  528. shown.push(notify);
  529. return notify;
  530. };
  531. // 隐藏已经显示的消息
  532. var hideNotification = function (notify) {
  533. use.hideNotification.apply(this, arguments);
  534. return notify;
  535. };
  536. var afterHideNotification = function (notify) {
  537. shown = shown.filter(function (x) {
  538. return x !== notify;
  539. });
  540. };
  541.  
  542. document.addEventListener('unload', function () {
  543. shown.forEach(hideNotification);
  544. shown = [];
  545. });
  546.  
  547. return {
  548. 'avaliableNotification': avaliableNotification,
  549. 'choseNotification': choseNotification,
  550. 'hasPermission': hasPermission,
  551. 'requestPermission': requestPermission,
  552. 'showNotification': showNotification,
  553. 'hideNotification': hideNotification
  554. };
  555.  
  556. }());
  557.  
  558. function showNotification() {
  559. switch (notify.hasPermission()) {
  560. case null: // default
  561. var thatArguments = arguments;
  562. notify.requestPermission(function () {
  563. showNotification.apply(null, thatArguments);
  564. });
  565. break;
  566. case true: // granted
  567. notify.showNotification.apply(null, arguments);
  568. break;
  569. case false: // denied
  570. log('Notification permission: denied');
  571. break;
  572. }
  573. }
  574.  
  575. function tryBangumiRedirect() {
  576. var msgBox;
  577. if (!(msgBox = document.querySelector('.b-page-body > .z-msg > .z-msg-box'))) {
  578. return;
  579. }
  580. var msg = document.createElement('a');
  581. msgBox.insertBefore(msg, msgBox.firstChild);
  582. msg.innerText = '获取番剧页Url中...';
  583.  
  584. var season_id, ep_index, av_id = location.pathname.replace(/.*av(\d+).*/, '$1');
  585. ajaxPromise(proxyServer + '/api/view?id=' + av_id)
  586. .then(function (data) {
  587. if (!data.bangumi) {
  588. return Promise.reject('该AV号不属于任何番剧页');//No bangumi in api response
  589. }
  590. var int = parseInt,
  591. old_ep_id = location.pathname.replace(/.*av\d+.(?:index_(\d+).*)?/, '$1');
  592. ep_index = (int(old_ep_id) - 1) ||
  593. (int(data.season_index) - 1) ||
  594. (int(data.description.replace(/\D(\d+)/, '$1')) - 1);
  595. ep_index = (isNaN(ep_index) || ep_index < 1) ? 0 : ep_index;
  596. season_id = data.bangumi.season_id;
  597. return ajaxPromise(proxyServer + '/api/bangumi?season=' + season_id);
  598. })
  599. .then(function (result) {
  600. if (result.code !== 0) {
  601. return Promise.reject(result);
  602. }
  603. var episode_id = result.result.episodes.reverse()[ep_index].episode_id;
  604. var bangumi_url = '//bangumi.bilibili.com/anime/' + season_id + '/play#' + episode_id;
  605. log('Redirect av:', av_id, '==>', bangumi_url);
  606. msg.innerText = '即将跳转到:' + bangumi_url;
  607. location.href = bangumi_url;
  608. })
  609. .catch(function (e) {
  610. log('error:', arguments);
  611. msg.innerText = 'error:' + e;
  612. });
  613. }
  614.  
  615. function ajaxPromise(options) {
  616. return new Promise(function (resolve, reject) {
  617. typeof options !== 'object' && (options = {url: options});
  618.  
  619. options.async === undefined && (options.async = true);
  620. options.xhrFields === undefined && (options.xhrFields = {withCredentials: true});
  621. options.success = function (data) {
  622. resolve(data);
  623. };
  624. options.error = function (err) {
  625. reject(err);
  626. };
  627. $.ajax(options);
  628. })
  629. }