haijiao-vip: 海角社区 解锁收费视频,VIP,去广告

解锁 海角社区(haijiao.com) 收费视频,VIP,并去除网站广告, TG频道:@svip_nav.本插件完全免费,请注意甄别,避免上当受骗.

Från och med 2024-05-22. Se den senaste versionen.

  1. // ==UserScript==
  2. // @name haijiao-vip: 海角社区 解锁收费视频,VIP,去广告
  3. // @namespace https://github.com/sex4096/haijiao_vip
  4. // @version 1.0.5
  5. // @author forgetme8
  6. // @description 解锁 海角社区(haijiao.com) 收费视频,VIP,并去除网站广告, TG频道:@svip_nav.本插件完全免费,请注意甄别,避免上当受骗.
  7. // @homepage https://github.com/sex4096/haijiao_vip#readme
  8. // @supportURL https://github.com/sex4096/haijiao_vip/issue
  9. // @run-at document-idle
  10. // @match https://www.haijiao.com/*
  11. // @match https://haijiao.com/*
  12. // @license MIT
  13. // @connect cdn.jsdelivr.net
  14. // @require https://cdn.jsdelivr.net/npm/react@18.3.0/umd/react.production.min.js
  15. // @require https://cdn.jsdelivr.net/npm/react-dom@18.3.0/umd/react-dom.production.min.js
  16. // @require https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js
  17. // @require https://cdn.jsdelivr.net/npm/antd@5.16.4/dist/antd.min.js
  18. // @require https://cdn.jsdelivr.net/npm/@ant-design/icons@5.3.6/dist/index.umd.min.js
  19. // ==/UserScript==
  20. (function (React$1, ReactDOM, antd, icons) {
  21. "use strict";
  22.  
  23. function _interopDefaultLegacy(e) {
  24. return e && typeof e === "object" && "default" in e ? e : { default: e };
  25. }
  26.  
  27. var React__default = /*#__PURE__*/ _interopDefaultLegacy(React$1);
  28. var ReactDOM__default = /*#__PURE__*/ _interopDefaultLegacy(ReactDOM);
  29.  
  30. var __webpack_require__ = undefined;
  31. var VUE = undefined;
  32. var STORE = undefined;
  33. var AXIOS = undefined;
  34. var callback = undefined;
  35. function initHookWebpack(initialed) {
  36. callback = initialed;
  37. let originCall = Function.prototype.call;
  38. Function.prototype.call = function () {
  39. for (
  40. var _len = arguments.length, args = new Array(_len), _key = 0;
  41. _key < _len;
  42. _key++
  43. ) {
  44. args[_key] = arguments[_key];
  45. }
  46. const result = originCall.apply(this, args);
  47. if (
  48. args.length === 4 &&
  49. args[1] &&
  50. args[1].exports &&
  51. args[0] === args[2] &&
  52. __webpack_require__ === undefined
  53. ) {
  54. __webpack_require__ = args[3];
  55. Function.prototype.call = originCall;
  56. importModules();
  57. }
  58. return result;
  59. };
  60. }
  61. function importModules() {
  62. VUE = __webpack_require__("2b0e").default;
  63. STORE = __webpack_require__("4360").a;
  64. AXIOS = __webpack_require__("bc3a");
  65. AXIOS = getObject(AXIOS);
  66. AXIOS = AXIOS.a;
  67. callback(VUE, STORE, AXIOS);
  68. }
  69. function getObject(module) {
  70. const t =
  71. module && module.__esModule
  72. ? function () {
  73. return module.default;
  74. }
  75. : function () {
  76. return module;
  77. };
  78. defineObject(t, "a", t);
  79. return t;
  80. }
  81. function defineObject(module, key, value) {
  82. Object.prototype.hasOwnProperty.call(module, key) ||
  83. Object.defineProperty(module, key, {
  84. enumerable: true,
  85. get: value,
  86. });
  87. }
  88.  
  89. /**
  90. * 加载模块
  91. * @param module
  92. */
  93. function getModule(module) {
  94. if (!__webpack_require__) return null;
  95. return __webpack_require__(module);
  96. }
  97.  
  98. const Settings = (_ref) => {
  99. let { initialSettings, onFormInstanceReady } = _ref;
  100. const [form] = antd.Form.useForm();
  101. React$1.useEffect(() => {
  102. onFormInstanceReady(form);
  103. }, []);
  104. return /*#__PURE__*/ React.createElement(
  105. React.Fragment,
  106. null,
  107. /*#__PURE__*/ React.createElement(
  108. antd.Form,
  109. {
  110. form: form,
  111. name: "settings",
  112. labelAlign: "right",
  113. labelCol: {
  114. span: 8,
  115. },
  116. wrapperCol: {
  117. span: 16,
  118. },
  119. initialValues: initialSettings,
  120. },
  121. /*#__PURE__*/ React.createElement(
  122. antd.Form.Item,
  123. {
  124. label: "\u53BB\u5E7F\u544A",
  125. },
  126. /*#__PURE__*/ React.createElement(
  127. antd.Form.Item,
  128. {
  129. name: "removeAds",
  130. noStyle: true,
  131. },
  132. /*#__PURE__*/ React.createElement(antd.Switch, null)
  133. ),
  134. /*#__PURE__*/ React.createElement(
  135. "span",
  136. {
  137. style: {
  138. marginLeft: 10,
  139. },
  140. },
  141. "\u53BB\u9664\u7F51\u7AD9\u5E7F\u544A"
  142. )
  143. ),
  144. /*#__PURE__*/ React.createElement(
  145. antd.Form.Item,
  146. {
  147. label: "\u5C4F\u853D\u7F6E\u9876",
  148. },
  149. /*#__PURE__*/ React.createElement(
  150. antd.Form.Item,
  151. {
  152. name: "removeTops",
  153. noStyle: true,
  154. },
  155. /*#__PURE__*/ React.createElement(antd.Switch, null)
  156. ),
  157. /*#__PURE__*/ React.createElement(
  158. "span",
  159. {
  160. style: {
  161. marginLeft: 10,
  162. },
  163. },
  164. "\u5C4F\u853D\u5168\u5C40\u7F6E\u9876\u5E16"
  165. )
  166. ),
  167. /*#__PURE__*/ React.createElement(
  168. antd.Form.Item,
  169. {
  170. label: "\u67E5\u770B\u5C01\u7981\u7528\u6237",
  171. },
  172. /*#__PURE__*/ React.createElement(
  173. antd.Form.Item,
  174. {
  175. name: "viewBanUser",
  176. noStyle: true,
  177. },
  178. /*#__PURE__*/ React.createElement(antd.Switch, null)
  179. ),
  180. /*#__PURE__*/ React.createElement(
  181. "span",
  182. {
  183. style: {
  184. marginLeft: 10,
  185. },
  186. },
  187. "\u67E5\u770B\u88AB\u5C01\u7981\u7684\u7528\u6237\u4FE1\u606F"
  188. )
  189. ),
  190. /*#__PURE__*/ React.createElement(
  191. antd.Form.Item,
  192. {
  193. label: "\u89E3\u9501VIP",
  194. },
  195. /*#__PURE__*/ React.createElement(
  196. antd.Form.Item,
  197. {
  198. name: "unlockVip",
  199. noStyle: true,
  200. },
  201. /*#__PURE__*/ React.createElement(antd.Switch, null)
  202. ),
  203. /*#__PURE__*/ React.createElement(
  204. "span",
  205. {
  206. style: {
  207. marginLeft: 10,
  208. },
  209. },
  210. "\u53EF\u89C2\u770Bvip\u533A\u7684\u5E16\u5B50\u53CA\u89C6\u9891"
  211. )
  212. ),
  213. /*#__PURE__*/ React.createElement(
  214. antd.Form.Item,
  215. {
  216. label: "\u89E3\u9501\u6536\u8D39\u89C6\u9891",
  217. },
  218. /*#__PURE__*/ React.createElement(
  219. antd.Form.Item,
  220. {
  221. name: "unlockBuy",
  222. noStyle: true,
  223. },
  224. /*#__PURE__*/ React.createElement(antd.Switch, null)
  225. ),
  226. /*#__PURE__*/ React.createElement(
  227. "span",
  228. {
  229. style: {
  230. marginLeft: 10,
  231. },
  232. },
  233. "\u53EF\u89C2\u770B\u9700\u8981\u8D2D\u4E70\u7684\u89C6\u9891"
  234. )
  235. ),
  236. /*#__PURE__*/ React.createElement(
  237. antd.Form.Item,
  238. {
  239. label: "\u670D\u52A1\u5730\u5740",
  240. },
  241. /*#__PURE__*/ React.createElement(
  242. antd.Form.Item,
  243. {
  244. name: "host",
  245. noStyle: true,
  246. rules: [
  247. {
  248. required: true,
  249. message: "请输入服务器地址",
  250. },
  251. ],
  252. },
  253. /*#__PURE__*/ React.createElement(antd.Input, null)
  254. ),
  255. /*#__PURE__*/ React.createElement(
  256. "div",
  257. {
  258. style: {
  259. color: "red",
  260. marginTop: 5,
  261. },
  262. },
  263. "\u670D\u52A1\u5668\u5730\u5740\u4E0D\u5B9A\u65F6\u66F4\u6362"
  264. ),
  265. /*#__PURE__*/ React.createElement(
  266. "div",
  267. null,
  268. "\u8BF7\u8BA2\u9605TG\u9891\u9053:@svip_nav\u83B7\u53D6\u6700\u65B0\u5730\u5740"
  269. )
  270. )
  271. )
  272. );
  273. };
  274.  
  275. class PluginStore {
  276. static get(key, defaultValue) {
  277. const value = localStorage.getItem(key);
  278. if (value === null) {
  279. return defaultValue;
  280. }
  281. if (typeof defaultValue === "number") {
  282. return parseInt(value);
  283. }
  284. if (typeof defaultValue === "boolean") {
  285. return value === "true";
  286. }
  287. return value;
  288. }
  289. static set(key, value) {
  290. localStorage.setItem(key, value.toString());
  291. }
  292. }
  293.  
  294. const App = () => {
  295. const [isModalOpen, setIsModalOpen] = React$1.useState(false);
  296. const [formInstance, setFormInstance] = React$1.useState();
  297. const showModal = () => {
  298. setIsModalOpen(true);
  299. };
  300. const handleOk = async () => {
  301. const values = await formInstance?.validateFields();
  302. onCreate(values);
  303. setIsModalOpen(false);
  304. };
  305. const handleCancel = () => {
  306. setIsModalOpen(false);
  307. };
  308. const onCreate = (values) => {
  309. console.log("Received values of form: ", values);
  310. PluginStore.set("removeAds", values.removeAds);
  311. PluginStore.set("removeTops", values.removeTops);
  312. PluginStore.set("unlockVip", values.unlockVip);
  313. PluginStore.set("unlockBuy", values.unlockBuy);
  314. PluginStore.set("viewBanUser", values.viewBanUser);
  315. PluginStore.set("host", values.host);
  316. };
  317. return /*#__PURE__*/ React__default["default"].createElement(
  318. React__default["default"].Fragment,
  319. null,
  320. /*#__PURE__*/ React__default["default"].createElement(antd.FloatButton, {
  321. type: "primary",
  322. tooltip: "\u6D77\u89D2VIP\u8BBE\u7F6E",
  323. style: {
  324. left: 16,
  325. },
  326. icon: /*#__PURE__*/ React__default["default"].createElement(
  327. icons.SettingOutlined,
  328. null
  329. ),
  330. onClick: showModal,
  331. }),
  332. /*#__PURE__*/ React__default["default"].createElement(
  333. antd.Modal,
  334. {
  335. title: "\u8BBE\u7F6E",
  336. open: isModalOpen,
  337. onOk: handleOk,
  338. onCancel: handleCancel,
  339. destroyOnClose: true,
  340. okButtonProps: {
  341. autoFocus: true,
  342. },
  343. },
  344. /*#__PURE__*/ React__default["default"].createElement(Settings, {
  345. initialSettings: {
  346. removeAds: PluginStore.get("removeAds", true),
  347. removeTops: PluginStore.get("removeTops", true),
  348. viewBanUser: PluginStore.get("viewBanUser", true),
  349. unlockVip: PluginStore.get("unlockVip", true),
  350. unlockBuy: PluginStore.get("unlockBuy", true),
  351. host: PluginStore.get("host", "https://haijiao.ku-cloud.com"),
  352. },
  353. onFormInstanceReady: (instance) => {
  354. setFormInstance(instance);
  355. },
  356. })
  357. )
  358. );
  359. };
  360.  
  361. /**
  362. * 自定义拦截器
  363. */
  364. class Interceptor {
  365. // axios模块
  366.  
  367. constructor(axios) {
  368. this.axios = axios;
  369. }
  370.  
  371. /**
  372. * 初始化请求拦截器
  373. */
  374. initRequestInterceptor() {
  375. this.axios.interceptors.request.use(this.requestInterceptor.bind(this));
  376. }
  377.  
  378. /**
  379. * 初始化返回请求拦截器
  380. */
  381. initResponseInterceptor() {
  382. if (this.axios.interceptors.response.handlers.length != 1) {
  383. return;
  384. }
  385. // 因为返回处理会处理掉config,而我们需要config中的url,所以需要在返回处理之前处理
  386. if (this.axios.interceptors.response.handlers?.[0].fulfilled) {
  387. const origin = this.axios.interceptors.response.handlers?.[0].fulfilled;
  388. this.axios.interceptors.response.handlers[0].fulfilled = async (
  389. response
  390. ) => {
  391. const data = await origin(response);
  392. response = {
  393. data: data,
  394. config: response.config,
  395. };
  396. return response;
  397. };
  398. }
  399. this.axios.interceptors.response.use(
  400. this.responseDecodeInterceptor.bind(this)
  401. );
  402. this.axios.interceptors.response.use(this.responseInterceptor.bind(this));
  403. this.axios.interceptors.response.use(
  404. this.responseEncodeInterceptor.bind(this)
  405. );
  406. }
  407.  
  408. /**
  409. * 请求拦截器
  410. * @param request
  411. * @returns
  412. */
  413. async requestInterceptor(request) {
  414. request = await this.requestUnlockBuyInterceptor(request);
  415. request = await this.requestUnlockBanUserInterceptor(request);
  416. request = await this.requestSearchInterceptor(request);
  417. return request;
  418. }
  419.  
  420. /**
  421. * 解锁购买
  422. * @param request
  423. * @returns
  424. */
  425. async requestUnlockBuyInterceptor(request) {
  426. if (
  427. PluginStore.get("unlockBuy", false) === true &&
  428. PluginStore.get("host", "").length > 0
  429. ) {
  430. if (
  431. /\/api\/attachment/g.test(request.url) ||
  432. /topic\/\d+/g.test(request.url)
  433. ) {
  434. console.log("转发请求", request.url, request);
  435. var host = PluginStore.get("host", "");
  436. request.baseURL = host;
  437. request.crossDomain = true;
  438. }
  439. }
  440. return request;
  441. }
  442.  
  443. /**
  444. * 查看被ban的用户信息
  445. * @param request
  446. */
  447. async requestUnlockBanUserInterceptor(request) {
  448. if (
  449. PluginStore.get("unlockBanUser", true) === true &&
  450. PluginStore.get("host", "").length > 0
  451. ) {
  452. if (
  453. /\/api\/user\/info\/\d+/g.test(request.url) ||
  454. /\/api\/user\/news\/other_news_list/g.test(request.url) ||
  455. (/\/api\/topic\/node\/topics/g.test(request.url) &&
  456. request.url.includes("userId"))
  457. ) {
  458. console.log("查看被ban的用户信息", request.url);
  459. var host = PluginStore.get("host", "");
  460. request.baseURL = host;
  461. request.crossDomain = true;
  462. }
  463. }
  464. return request;
  465. }
  466.  
  467. /**
  468. * 解锁搜索功能
  469. * @param request
  470. */
  471. async requestSearchInterceptor(request) {
  472. if (
  473. PluginStore.get("unlockSearch", true) === true &&
  474. PluginStore.get("host", "").length > 0
  475. ) {
  476. if (/\/api\/user\/search/g.test(request.url)) {
  477. console.log("搜索", request.url);
  478. var host = PluginStore.get("host", "");
  479. request.baseURL = host;
  480. request.crossDomain = true;
  481. }
  482. }
  483. return request;
  484. }
  485.  
  486. /**
  487. * 对返回数据进行解码
  488. * @param response
  489. */
  490. async responseDecodeInterceptor(response) {
  491. if (response.data.status === 200) {
  492. const origin_response = JSON.parse(JSON.stringify(response.data.data));
  493. var enc_data = response.data.data.data;
  494. if (enc_data && typeof enc_data === "string" && enc_data.length > 0) {
  495. const Base64 = getModule("e762").a;
  496. enc_data = JSON.parse(
  497. Base64.decode(Base64.decode(Base64.decode(enc_data)))
  498. );
  499. }
  500. response = {
  501. item: enc_data,
  502. url: response.config.url,
  503. mobile: true,
  504. origin_response: origin_response,
  505. };
  506. } else {
  507. // 克隆一个原始请求
  508. const origin_response = JSON.parse(JSON.stringify(response.data));
  509. const item = JSON.parse(JSON.stringify(response.data.data));
  510. response = {
  511. item: item,
  512. url: response.config.url,
  513. mobile: false,
  514. origin_response: origin_response,
  515. };
  516. }
  517. return response;
  518. }
  519.  
  520. /**
  521. * 对reponse重新编码
  522. * @param response
  523. * @returns
  524. */
  525. async responseEncodeInterceptor(response) {
  526. if (response.mobile === true) {
  527. var dec = response.item;
  528. if (response.origin_response.isEncrypted === true) {
  529. const Base64 = getModule("e762").a;
  530. dec = Base64.encode(
  531. Base64.encode(Base64.encode(JSON.stringify(response.item)))
  532. );
  533. }
  534. return {
  535. data: {
  536. ...response.origin_response,
  537. data: dec,
  538. },
  539. };
  540. } else {
  541. return {
  542. ...response.origin_response,
  543. data: response.item,
  544. };
  545. }
  546. }
  547. async responseInterceptor(response) {
  548. const url = response.url.toLowerCase();
  549. var item = response.item;
  550. console.log("拦截器返回数据", url, item);
  551. if (
  552. /^\/api\/topic\/\d+/g.test(url) &&
  553. PluginStore.get("unlockVip", true) === true
  554. ) {
  555. item = await this.fixTopic(item);
  556. }
  557. // 去广告
  558. else if (
  559. /banner\/banner_list/g.test(url) &&
  560. PluginStore.get("removeAds", true) === true
  561. ) {
  562. item = await this.fixAds(item);
  563. }
  564. // 屏蔽置顶帖
  565. else if (
  566. /^\/api\/topic\/global\/topics/g.test(url) &&
  567. PluginStore.get("removeTops", true) === true
  568. ) {
  569. item = await this.fixTops(item);
  570. }
  571. response.item = item;
  572. return response;
  573. }
  574. /**
  575. * 修复帖子内容
  576. * @param {*} data
  577. * @returns
  578. */
  579. async fixTopic(data) {
  580. console.log("修复帖子内容", data);
  581. if (data.node?.vipLimit > 0) {
  582. data.node.vipLimit = 0;
  583. }
  584. var content = data.content;
  585. if (content && !content.startsWith("<html><head></head><body>")) {
  586. // 删除掉[]标签
  587. content = content.replace(/\[视频\]/g, "");
  588. content = content.replace(/\[图片\]/g, "");
  589. content = content.replace(/此处内容售价\d+金币.*请购买后查看./g, "");
  590. content = content.replace(/\[sell.*\/sell]/g, "");
  591. data.attachments?.forEach((attachment) => {
  592. var hasImage,
  593. hasVideo = false;
  594. // 处理图片
  595. if (attachment.category === "images" && attachment.remoteUrl) {
  596. content =
  597. content += `<p><img src="${attachment.remoteUrl}" data-id="${attachment.id}"/>`;
  598. hasImage = true;
  599. }
  600. if (hasImage === true) {
  601. content = `<p>${content}</p>`;
  602. }
  603. if (attachment.category === "video") {
  604. // if (attachment.remoteUrl) {
  605. hasVideo = true;
  606. content += `<p><video src="${attachment.remoteUrl}" data-id="${attachment.id}"></video></p>`;
  607. // } else {
  608. // console.log("视频链接为空", attachment);
  609. // content += `<p><div style="color:red;text-decoration:line-through;">${attachment.error}</div></p>`;
  610. // }
  611. }
  612. if (hasVideo === true) {
  613. content = `<p>${content}</p>`;
  614. }
  615. });
  616. content = `<html><head></head><body>${content}</body></html>`;
  617. }
  618. data.content = content;
  619. return data;
  620. }
  621.  
  622. /**
  623. * 去广告
  624. * @param data
  625. */
  626. async fixAds(data) {
  627. return null;
  628. }
  629. async fixTops(data) {
  630. return [];
  631. }
  632. }
  633.  
  634. function addStyle() {
  635. let script = document.createElement("link");
  636. script.setAttribute("rel", "stylesheet");
  637. script.setAttribute("type", "text/css");
  638. script.href = "https://cdn.jsdelivr.net/npm/antd@5.16.4/dist/reset.min.css";
  639. document.documentElement.appendChild(script);
  640. }
  641. function addAnalytics() {
  642. const script = document.createElement("script");
  643. script.src = "https://www.googletagmanager.com/gtag/js?id=G-NQ08DH5N3T";
  644. script.async = true;
  645. document.head.appendChild(script);
  646. const script2 = document.createElement("script");
  647. script2.innerHTML = `
  648. window.dataLayer = window.dataLayer || [];
  649. function gtag(){dataLayer.push(arguments);}
  650. gtag('js', new Date());
  651. gtag('config', 'G-NQ08DH5N3T');
  652. `;
  653. document.head.appendChild(script2);
  654. }
  655.  
  656. function initialed(vue, store, axios) {
  657. const interceptor = new Interceptor(axios);
  658. interceptor.initRequestInterceptor();
  659. interceptor.initResponseInterceptor();
  660. }
  661. function initSetting() {
  662. const myButton = /*#__PURE__*/ React__default["default"].createElement(
  663. App,
  664. null
  665. );
  666. const pluginDiv = document.createElement("div");
  667. pluginDiv.id = "haijiao-vip-plugin";
  668. document.body.appendChild(pluginDiv);
  669. ReactDOM__default["default"].render(myButton, pluginDiv);
  670. }
  671. sessionStorage.setItem("pageOpen", "1");
  672. addAnalytics();
  673. addStyle();
  674. initSetting();
  675. initHookWebpack(initialed);
  676. })(React, ReactDOM, antd, icons);