百合会漫画

百合会漫画阅读与下载

  1. // ==UserScript==
  2. // @name 百合会漫画
  3. // @namespace https://bbs.yamibo.com/
  4. // @version 0.9.1
  5. // @description 百合会漫画阅读与下载
  6. // @author mooth
  7. // @match https://bbs.yamibo.com/forum-30*
  8. // @match https://bbs.yamibo.com/forum.php?mod=forumdisplay&fid=30*
  9. // @match https://bbs.yamibo.com/thread*
  10. // @match https://bbs.yamibo.com/forum.php?mod=viewthread&tid*
  11.  
  12. // @icon https://www.google.com/s2/favicons?domain=yamibo.com
  13.  
  14. // @require https://update.greasyfork.org/scripts/471654/1225059/vue3js.js
  15. // @require https://unpkg.com/fflate@0.8.2/umd/index.js
  16. // @require https://unpkg.com/axios@1.7.2/dist/axios.min.js
  17.  
  18. // @grant GM_addStyle
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @grant GM_xmlhttpRequest
  22. // @grant GM_addElement
  23.  
  24. // @noframes
  25. // ==/UserScript==
  26.  
  27.  
  28. (function () {
  29. GM_addElement('link', {
  30. href: 'https://unpkg.com/element-plus/dist/index.css',
  31. rel: 'stylesheet'
  32. });
  33. GM_addElement('script', {
  34. src: 'https://unpkg.com/element-plus',
  35. type: 'text/javascript',
  36. id: "element"
  37. });
  38.  
  39. const { createApp, computed, ref, reactive, onMounted, watch } = Vue;
  40.  
  41. // 公用方法 ---------------------------------------------------------------------------
  42. const PubFun = {
  43. // 创建碎片
  44. Fragment: (id) => {
  45. const f = document.createDocumentFragment();
  46. f.appendChild(document.querySelector(id));
  47. return f;
  48. },
  49.  
  50. // 解析地址 html_str 返回页面中解析的地址数组
  51. getUrls: (html) => {
  52. try {
  53. // 定义三个数组,分别存储三种不同目的的图片地址
  54. let urlCandidates = [new Array(), new Array(), new Array()];
  55.  
  56. // 定义一个数组,用于存储最终解析出的图片地址
  57. let urls = [];
  58.  
  59. // 使用正则表达式匹配三种不同格式的图片地址
  60. if (html.match(/zoomfile="(.*?)"/g)) {
  61. urlCandidates[2] = html.match(/zoomfile="(.*?)"/g);
  62. }
  63. if (html.match(/https:\/\/bbs\.yamibo\.com\/data.*?"/g)) {
  64. urlCandidates[1] = html.match(/https:\/\/bbs\.yamibo\.com\/data.*?"/g);
  65. }
  66. if (html.match(/<img.*?class="zoom".*?file="(.*?)"/g)) {
  67. urlCandidates[0] = html.match(/<img.*?class="zoom".*?file="(.*?)"/g);
  68. }
  69.  
  70. // 将三个数组按照长度排序,优先处理匹配数量最多的情况
  71. urlCandidates.sort((a, b) => b.length - a.length);
  72.  
  73. // 遍历三个数组,将匹配到的图片地址进行格式化后存储到 urls 数组中
  74. urlCandidates.forEach(list => {
  75. list.forEach(str => {
  76. if (str.match("bbs.yamibo.com")) {
  77. urls.push(str.match(/(https.*?)"/)[1]);
  78. // "zoomfile=\"data/attachment/forum/202406/06/120022lu22z92dtq3hedq9.jpg\""
  79. } else if (str.match(/zoomfile="(.*?)"/)) {
  80. urls.push("https:\/\/bbs.yamibo.com/" + str.match(/zoomfile="(.*?)"/)[1]);
  81. } else {
  82. urls.push(
  83. str.match(/<img.*?class="zoom".*?file="(.*?)"/)[1]
  84. .replace("http", "https")
  85. );
  86. }
  87. });
  88. });
  89.  
  90. // 过滤不需要的图片 和错误匹配
  91. urls = [...new Set(urls.filter(url => {
  92. if (!url.endsWith('.gif') && !url.match('static')) {
  93. return url
  94. }
  95. }))];
  96.  
  97. // 返回解析出的图片地址数组
  98. return urls;
  99. } catch (error) {
  100. console.log("html地址解析", error);
  101. }
  102. },
  103.  
  104. // 获取图片生成本地地址 解决跨域 设置同源
  105. cross: async (url) => {
  106. return new Promise((resolve, reject) => {
  107. try {
  108. // 将 httpss 替换为 https
  109. url = url.replace("httpss", "https");
  110. // 定义一个变量,用于存储 blob URL
  111. let blobUrl = "";
  112. GM_xmlhttpRequest({
  113. method: "get",
  114. url: url,
  115. responseType: "blob",
  116. headers: {
  117. Referer: "https://bbs.yamibo.com/forum.php?"
  118. },
  119. onload: (res) => {
  120. try {
  121. // 将响应数据转换为 blob URL
  122. blobUrl = window.URL.createObjectURL(res.response);
  123. } catch (error) {
  124. reject(`PubFun.cross图片转本地地址 \n${url}\n ${error}`)
  125. }
  126.  
  127. resolve(blobUrl);
  128. },
  129. onerror: () => {
  130. console.error("无法链接到该页面");
  131. reject("无法链接到该页面");
  132. }
  133. });
  134. } catch (error) {
  135. reject(`PubFun.cross\n${error}\n${url}`);
  136. }
  137.  
  138. });
  139. },
  140.  
  141. // 文件储存
  142. saveAs: (data, name, type) => {
  143. // 创建一个 a 标签
  144. const link = document.createElement("a");
  145. // 创建一个 blob URL
  146. const url = window.URL.createObjectURL(data);
  147. // 设置 a 标签的 href 属性为 blob URL
  148. link.href = url;
  149. // 设置 a 标签的 download 属性为文件名
  150. link.download = name + "." + type;
  151. // 模拟点击 a 标签,触发下载
  152. link.click();
  153. },
  154.  
  155. // fflate打包
  156. fflate_zip: async (name, urlList, state) => {
  157. let num = 0;
  158. let file_b_list = {}
  159. await new Promise(async resolve => {
  160. for (let i = 0; i < urlList.length; i++) {
  161. const url = urlList[i];
  162. try {
  163. const res = await axios.get(url, { responseType: 'blob' });
  164. num++;
  165.  
  166. if (state) {
  167. state.done = num;
  168. state.allTasks = urlList.length;
  169. }
  170.  
  171. const file_blob = res.data;
  172. const file_type = file_blob.type.match(/\/(.*)/)?.[1];
  173. const file = new Uint8Array(await file_blob.arrayBuffer());
  174.  
  175. file_b_list[`${i + 1}.${file_type}`] = file;
  176. } catch (error) {
  177. num++;
  178. if (state) {
  179. state.error++;
  180. }
  181. console.error("下载失败:", url, error);
  182. }
  183.  
  184. // 完成
  185. if(i === urlList.length-1){
  186. resolve()
  187. }
  188. }
  189. });
  190.  
  191. // 更新下载状态
  192. if (state) { state.pack = 1; }
  193. console.log(file_b_list);
  194. fflate.zip(file_b_list, { level: 0 }, (err, content) => {
  195. console.log(content);
  196. // 生成 zip 包并下载
  197. try {
  198. PubFun.saveAs(new Blob([content], { type: "application/zip" }), name, "zip")
  199. // 更新下载状态
  200. if (state) {
  201. state.pack = 2;
  202. }
  203. } catch (e) {
  204. // 更新下载状态
  205. if (state) {
  206. state.pack = 3;
  207. }
  208. // 打印错误信息
  209. console.error("打包失败:", e);
  210. }
  211. })
  212.  
  213. },
  214.  
  215. // 任务队列 a = new createTaskQueue(10), a.addTask(()=>fun())
  216. createTaskQueue: (concurrency = 10) => {
  217. const queue = [];
  218. let running = 0;
  219.  
  220. const run = () => {
  221. while (running < concurrency && queue.length > 0) {
  222. const task = queue.shift();
  223. running++;
  224. task()
  225. .then(() => {
  226. running--;
  227. run();
  228. })
  229. .catch(() => {
  230. running--;
  231. run();
  232. });
  233. }
  234. };
  235.  
  236. const addTask = (task) => {
  237. queue.push(task);
  238. run();
  239. };
  240.  
  241. return {
  242. addTask
  243. }
  244. }
  245. };
  246.  
  247. // 漫画列表组件 ---------------------------------------------------------------------------
  248. class MangaListComponent {
  249. static f = []
  250. static columns = 2
  251.  
  252. constructor() {
  253. // 初始化 列数
  254. if (!GM_getValue("columns")) {
  255. GM_setValue("columns", 2)
  256. MangaListComponent.columns = 2
  257. } else {
  258. MangaListComponent.columns = GM_getValue("columns")
  259. }
  260.  
  261. // 获取表格节点
  262. MangaListComponent.f = PubFun.Fragment("#threadlisttableid")
  263.  
  264. // 模板样式
  265. MangaListComponent.style = `
  266. .columns_container{
  267. display: flex;
  268. gap: 10px;
  269. align-items: center;
  270. svg{
  271. /* height: 40px; */
  272. fill: burlywood;
  273. cursor: pointer;
  274. }
  275. }
  276. .columns_selected{
  277. fill: #551201 !important;
  278. }
  279. .mt_manga_message_container{
  280. display: grid;
  281. }
  282. .mt_manga_message {
  283. display: flex;
  284. height: 150px;
  285. padding: 10px 0;
  286. /* width: 50%; */
  287. float: left;
  288. overflow: auto;
  289. }
  290.  
  291. .mt_manga_message::-webkit-scrollbar {
  292. /* 整体*/
  293. width: 5px;
  294. }
  295.  
  296. .mt_manga_face {
  297. height: 100%;
  298. img{
  299. height: 100%;
  300. }
  301. }
  302.  
  303. .mt_manga_info {
  304. padding: 0 10px;
  305. }
  306. `
  307. GM_addStyle(MangaListComponent.style)
  308.  
  309. // 模板
  310. MangaListComponent.template = `
  311. <div class="columns_container">
  312. <svg @click="columns=1" :class="{'columns_selected':columns==1}" height="40px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1467" ><path d="M868.352 568.32q32.768 0 53.248 19.456t20.48 52.224l0 221.184q0 35.84-19.968 54.784t-52.736 18.944l-706.56 0q-33.792 0-56.832-22.528t-23.04-55.296l0-212.992q0-35.84 19.968-55.808t54.784-19.968l710.656 0zM868.352 90.112q32.768 0 53.248 18.944t20.48 52.736l0 220.16q0 35.84-19.968 54.784t-52.736 18.944l-706.56 0q-33.792 0-56.832-22.528t-23.04-55.296l0-211.968q0-35.84 19.968-55.808t54.784-19.968l710.656 0z" p-id="1468"></path></svg>
  313. <svg @click="columns=2" :class="{'columns_selected':columns==2}" height="47px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1618" ><path d="M433.1 480 174.9 480c-25.9 0-46.9-21-46.9-46.9L128 174.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C480 459 459 480 433.1 480z" p-id="1619"></path><path d="M433.1 896 174.9 896c-25.9 0-46.9-21-46.9-46.9L128 590.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C480 875 459 896 433.1 896z" p-id="1620"></path><path d="M849.1 480 590.9 480c-25.9 0-46.9-21-46.9-46.9L544 174.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C896 459 875 480 849.1 480z" p-id="1621"></path><path d="M849.1 896 590.9 896c-25.9 0-46.9-21-46.9-46.9L544 590.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C896 875 875 896 849.1 896z" p-id="1622"></path></svg>
  314. <svg @click="columns=3" :class="{'columns_selected':columns==3}" height="40px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1768" ><path d="M248.832 63.488q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-116.736 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l116.736 0zM572.416 63.488q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0zM891.904 63.488q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0zM248.832 385.024q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-116.736 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l116.736 0zM572.416 385.024q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0zM891.904 385.024q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0zM248.832 706.56q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-116.736 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l116.736 0zM572.416 706.56q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0zM891.904 706.56q28.672 0 48.64 19.968t19.968 48.64l0 116.736q0 28.672-19.968 48.64t-48.64 19.968l-118.784 0q-28.672 0-48.64-19.968t-19.968-48.64l0-116.736q0-28.672 19.968-48.64t48.64-19.968l118.784 0z" p-id="1769"></path></svg>
  315. </div>
  316.  
  317. <div class="mt_manga_message_container">
  318. <div v-for="(item, index) in comicList" :key="item.id" class="mt_manga_message" >
  319. <div class="mt_manga_face">
  320. <img :src="item.face" class="face" alt="封面">
  321. </div>
  322. <div v-html="item.innerHTML" class="mt_manga_info"></div>
  323. </div>
  324. </div>
  325.  
  326. `
  327. // 创建
  328. createApp(
  329. {
  330. template: MangaListComponent.template,
  331. setup() {
  332. // 获取主题列表
  333. const fragment = MangaListComponent.f;
  334. // 定义主题链接
  335. const url = "https://bbs.yamibo.com/forum.php?mod=viewthread&tid=";
  336. // 列数
  337. const columns = ref(MangaListComponent.columns)
  338. // 定义漫画列表数据
  339. const comicList = ref([]);
  340. // 计算属性,为每个漫画项添加默认封面
  341. const computedComicList = computed(() => comicList.value.map((item) => ({
  342. ...item,
  343. face: item.face || "https://bbs.yamibo.com/data/attachment/forum/201504/01/110518de9s4qsd6qtzbn9m.jpg"
  344. })));
  345.  
  346. ////////////////////////////////////////////////////////////////////////////////////
  347. // 获取漫画封面
  348. const getComicCover = async (url_0, index) => {
  349. return new Promise(async (resolve, reject) => {
  350. let url = url_0
  351.  
  352. // 判断是否需要重定向
  353. let res = await axios.get(url)
  354. // 不需要,直接解析html
  355. if (res.data.match('html')) {
  356. console.log(`直接解析,${url}`);
  357. // 解析HTML获取图片地址
  358. const urls = PubFun.getUrls(res.data);
  359. // 将解析到的第一个图片地址设置为封面
  360. comicList.value[index].face = await PubFun.cross(urls[0]);
  361. resolve()
  362. return
  363. }
  364.  
  365. // 需要重定向
  366. try {
  367. // 获取重定向地址
  368. url = await get_redirect_url_eval(url_0)
  369. // 通过iframe 获取重定向地址
  370. if (url == url_0) {
  371. url = await get_redirect_url_iframe(url_0)
  372. }
  373. } catch (error) {
  374. // 通过iframe 获取重定向地址
  375. url = await get_redirect_url_iframe(url_0)
  376. }
  377.  
  378. // 发送请求获取对应漫画页面的HTML
  379. try {
  380. axios.get(url)
  381. .then(async (res) => {
  382.  
  383. const data = res.data;
  384.  
  385. // 是否重新加载
  386. if (!data.match('https')) {
  387. console.log("二次加载封面");
  388. getComicCover(url, index)
  389. return
  390. }
  391.  
  392. let urls = []
  393. try {
  394. // 解析HTML获取图片地址
  395. urls = PubFun.getUrls(data);
  396. // 将解析到的第一个图片地址设置为封面
  397. comicList.value[index].face = await PubFun.cross(urls[0]);
  398.  
  399. resolve()
  400. } catch (e) {
  401. console.error({
  402. "漫画页地址": url_0,
  403. "地址重定向转换": url,
  404. "封面地址": urls,
  405. "请求响应": res,
  406. e
  407. });
  408. resolve()
  409. }
  410. })
  411. .catch((error) => {
  412. // 打印错误信息
  413. console.error(`请求失败${url_0}:\n ${error}`);
  414. resolve()
  415. });
  416. } catch (error) {
  417. resolve()
  418. }
  419.  
  420.  
  421. })
  422. };
  423.  
  424. // 获取重定向地址 通过eval直接执行地址重定向代码
  425. const get_redirect_url_eval = (url) => {
  426. return new Promise((resolve, reject) => {
  427. try {
  428. // 定义自定义 window 对象
  429. const myWindow = {
  430. window: {
  431. location: {
  432. og: url,
  433. href: url,
  434. assign: (newUrl) => { myWindow.window.location.href = newUrl },
  435. }
  436. },
  437. location: {
  438. href: url,
  439. assign: (newUrl) => { myWindow.location.href = newUrl },
  440. replace: (newUrl) => { myWindow.location.href = newUrl }
  441. },
  442. };
  443.  
  444. // 定义eval上下文
  445. function evalWithContext(code, context) {
  446. return (function () {
  447. // 通过参数解构来获取上下文中的变量和函数
  448. const contextKeys = Object.keys(context);
  449. const contextValues = contextKeys.map(key => context[key]);
  450. // 创建一个新的函数,该函数的参数是上下文中的变量名,并在函数体中执行代码
  451. const func = new Function(...contextKeys, code);
  452. // 调用函数,并将上下文中的变量值作为参数传递给函数
  453. return func(...contextValues);
  454. })();
  455. }
  456.  
  457. axios.get(url)
  458. .then(response => {
  459. // 获取跳转代码
  460. const scriptRegex = /<script\b[^>]*>([\s\S]*?)<\/script>/i;
  461. const match = response.data.match(scriptRegex);
  462.  
  463. if (match && match[1]) {
  464. const scriptContent = match[1];
  465.  
  466. // 执行跳转代码
  467. evalWithContext(scriptContent, myWindow);
  468.  
  469. console.log(myWindow);
  470.  
  471. if (myWindow.location.href.match("https")) {
  472. resolve(myWindow.location.href);
  473. } else {
  474. resolve("https://bbs.yamibo.com" + myWindow.location.href);
  475. }
  476. } else {
  477. reject(url);
  478. }
  479. })
  480. .catch(error => {
  481. console.error('get_redirect_url_eval错误:', myWindow, error);
  482. reject(url);
  483. });
  484. } catch (error) {
  485. reject(url)
  486. }
  487.  
  488. });
  489. };
  490.  
  491. // 在eval失败后 通过iframe直接执行地址重定向代码
  492. const get_redirect_url_iframe = (url) => {
  493. return new Promise((resolve, reject) => {
  494.  
  495. // 创建一个 iframe 元素并设置其 src 属性
  496. const iframe = document.createElement('iframe');
  497. iframe.src = "";
  498. // iframe.sandbox = "allow-same-origin allow-scripts"
  499. iframe.style.display = "none"
  500. document.body.appendChild(iframe)
  501.  
  502. iframe.onload = () => {
  503. console.log("get_redirect_url_iframe", iframe.contentDocument.location.href);
  504. resolve(iframe.contentDocument.location.href)
  505. document.body.removeChild(iframe)
  506. }
  507.  
  508. iframe.contentDocument.location.href = url
  509. })
  510. }
  511.  
  512. // 渐进式加载漫画封面
  513. const progressiveLoad = () => {
  514. // 10个10个加载
  515. const queue = PubFun.createTaskQueue(10);
  516. comicList.value.forEach((item, index) => {
  517. queue.addTask(() => getComicCover(item.url, index))
  518. })
  519. };
  520.  
  521.  
  522. ///////////////////////////////////////////////////////////////////////////////
  523. // 通过原始网页数据初始化漫画列表数据 获取对应漫画地址
  524. const inti_manga_data = () => {
  525. for (let i in fragment.children[0].children) {
  526. const el = fragment.children[0].children[i];
  527. if (typeof el === "object" && el.id && el.id.match("normalthread")) {
  528. comicList.value.push({
  529. id: el.id,
  530. innerHTML: el.innerHTML,
  531. children: el.children,
  532. url: url + el.id.match("normalthread_(.*)")[1]
  533. });
  534. }
  535. }
  536. }
  537. // 组件挂载后执行
  538. onMounted(() => {
  539. // 初始化漫画列表数据
  540. inti_manga_data()
  541. // 开始渐进式加载漫画封面
  542. progressiveLoad();
  543. });
  544.  
  545. ///////////////////////////////////////////////////////////////////////////////
  546. // 修改列数
  547. watch(columns, (new_val, old_val) => {
  548. const el = document.querySelector(".mt_manga_message_container")
  549. el.style.gridTemplateColumns = `repeat(${new_val}, 1fr)`;
  550.  
  551. GM_setValue("columns", new_val)
  552. })
  553. onMounted(() => {
  554. const el = document.querySelector(".mt_manga_message_container")
  555. el.style.gridTemplateColumns = `repeat(${columns.value}, 1fr)`;
  556. })
  557.  
  558. ///////////////////////////////////////////////////////////////////////////////////////////
  559. return {
  560. comicList: computedComicList,
  561. columns
  562. };
  563. }
  564. }
  565. )
  566. .mount("#moderate")
  567. }
  568.  
  569. };
  570.  
  571.  
  572. // 漫画页面组件 ---------------------------------------------------------------------------
  573. class MangaComponent {
  574. constructor() {
  575. console.log("漫画页面加载");
  576.  
  577. // 用于 与vue通信,是否加载漫画模式
  578. MangaComponent.model_state = 1
  579.  
  580. // 百合会»论坛›江湖›貼圖區›中文百合漫画区›【提灯喵汉化组】[金子ある]平良深姐妹都“病”得不轻 1 ...
  581. // 获取索要创建按钮的位置
  582. MangaComponent.pt = document.querySelector("#pt")
  583.  
  584. // 创建 模式切换按钮
  585. MangaComponent.mt_cut = document.createElement("div")
  586. MangaComponent.mt_cut.id = "mt_cut"
  587. MangaComponent.mt_cut.innerText = "[漫画模式]"
  588. // 切换模式
  589. MangaComponent.mt_cut.addEventListener("click", () => {
  590. if (MangaComponent.model_state === 1) {
  591. document.querySelector("#mt_old").style.display = "none"
  592. document.querySelector("#mt_manga").style.display = "block"
  593. MangaComponent.mt_cut.innerText = "[原始模式]"
  594. MangaComponent.model_state = 2
  595. } else {
  596. document.querySelector("#mt_old").style.display = "block"
  597. document.querySelector("#mt_manga").style.display = "none"
  598. MangaComponent.mt_cut.innerText = "[漫画模式]"
  599. MangaComponent.model_state = 1
  600. }
  601. })
  602. // 添加
  603. MangaComponent.pt.appendChild(MangaComponent.mt_cut)
  604.  
  605. // 定义自己el,用于存放两种模式
  606. MangaComponent.my_el = document.createElement("div")
  607. MangaComponent.my_el.id = "my_el"
  608. // 在他的下方插入自己的el
  609. MangaComponent.pt.parentNode.insertBefore(MangaComponent.my_el, MangaComponent.pt.nextSibling)
  610.  
  611. // html模板
  612. MangaComponent.my_el.innerHTML = `
  613. <div id="mt_old"></div>
  614. <div id="mt_manga" style="display: none;">
  615. <div >
  616. <div v-if="urlsLoaded" id = "mt_manga_box" @dblclick="fullScreen = !fullScreen"
  617. :class="{is_one_p:onePage , not_one_p:!onePage , is_r_to_l:rightToLeft, is_w_bg:whiteBg}">
  618. <div id="mt_set" v-show="showSettings">
  619. <div class="block">
  620. <el-slider v-model="imageSize"></el-slider>
  621. <el-checkbox v-model="rightToLeft">从右向左</el-checkbox>
  622. <el-checkbox v-model="onePage">单开</el-checkbox>
  623. <el-checkbox v-model="whiteBg">白底</el-checkbox>
  624. <el-radio>
  625. <span @click="download()">{{downloadInfo}}</span>
  626. </el-radio>
  627. </div>
  628. </div>
  629.  
  630. <img :style="{'width':imageSize +'%'}"
  631. :src="url" class="mt_manga_img" v-for="url in urlList"
  632. @click="showSettings = !showSettings"
  633. >
  634. </div>
  635. </div>
  636. </div>
  637. `
  638. // 样式
  639. MangaComponent.style = `
  640. #mt_cut{
  641. float: right;
  642. font-weight: bold;
  643. }
  644. .mt_manga_img{
  645. }
  646. #mt_manga_box{
  647. overflow: auto;
  648. display: flex;
  649. }
  650. .is_one_p{
  651. flex-direction: column;
  652. flex-wrap: nowrap;
  653. align-items: center
  654. }
  655. .not_one_p{
  656. flex-wrap: wrap;
  657. align-content: flex-start;
  658. justify-content: center
  659. }
  660. .is_r_to_l{
  661. flex-direction: row-reverse;
  662. }
  663. .is_w_bg{
  664. background-color: white;
  665. }
  666.  
  667. #mt_manga_box::-webkit-scrollbar{ /* 整体*/
  668. width: 3px;
  669. height: 10px
  670. }
  671. #mt_manga_box::-webkit-scrollbar-thumb{ /* 滑动条*/
  672. background-color: rgb(208, 208, 208);
  673. border-radius: 1px;
  674. }
  675. #mt_set{
  676. width: 360px;
  677. height: 70px;
  678. background-color: rgb(235 179 82);
  679. position: fixed;
  680. border-radius: 5px;
  681. padding: 10px;
  682. top: 50%;
  683. left: 50%;
  684. transform: translate(-50%, -50%);
  685. }
  686. `
  687. GM_addStyle(MangaComponent.style)
  688.  
  689. // 碎片 用户发布部分
  690. MangaComponent.f = PubFun.Fragment("#ct")
  691. document.querySelector("#mt_old").appendChild(MangaComponent.f)
  692.  
  693.  
  694. // 初始化数据
  695. // 解析页面中的图片地址
  696. MangaComponent.urls = PubFun.getUrls(document.querySelector("#mt_old").innerHTML);
  697.  
  698. localStorage.getItem('size') ? null : localStorage.setItem('size', "100")
  699.  
  700. // 创建
  701. createApp(
  702. {
  703. setup() {
  704. // 定义模式状态,1为默认模式,2为漫画模式
  705. const model_state = ref(MangaComponent.model);
  706. // 定义图片地址列表
  707. const urlList = MangaComponent.urls;
  708. // 定义图片地址是否加载完成状态
  709. const urlsLoaded = ref(true);
  710. // 定义是否全屏状态
  711. const fullScreen = ref(false);
  712. // 定义是否显示设置面板状态
  713. const showSettings = ref(false);
  714. // 定义下载按钮文字
  715. const downloadInfo = ref("下载");
  716. // 定义下载状态
  717. const downloadState = reactive({ done: 0, error: 0, allTasks: 0, pack: 0 });
  718. // 定义图片大小
  719. const imageSize = ref(localStorage.getItem('size') - 0);
  720. // 定义是否从右向左阅读
  721. const rightToLeft = ref(localStorage.getItem("rightToLeft") === "1");
  722. // 定义是否单页显示
  723. const onePage = ref(localStorage.getItem("onePage") === "1");
  724. // 定义是否使用白色背景
  725. const whiteBg = ref(localStorage.getItem("whiteBg") === "1");
  726.  
  727. // 下载漫画
  728. const download = async () => {
  729. // 如果打包状态为0,则开始打包下载
  730. if (downloadState.pack === 0) {
  731. // 获取漫画名称
  732. const packName = document.getElementById("thread_subject").innerText;
  733. // 调用下载方法
  734. await PubFun.fflate_zip(packName, urlList, downloadState);
  735. }
  736. };
  737.  
  738. // 监听图片大小变化
  739. watch(imageSize, (newSize) => {
  740. localStorage.setItem("size", newSize);
  741. });
  742. // 监听阅读方向变化
  743. watch(rightToLeft, (newValue) => {
  744. localStorage.setItem("rightToLeft", newValue ? "1" : "0");
  745. });
  746. // 监听单页显示状态变化
  747. watch(onePage, (newValue) => {
  748. localStorage.setItem("onePage", newValue ? "1" : "0");
  749. });
  750. // 监听白色背景状态变化
  751. watch(whiteBg, (newValue) => {
  752. localStorage.setItem("whiteBg", newValue ? "1" : "0");
  753. });
  754. // 监听下载状态变化
  755. watch(downloadState, () => {
  756. // 更新下载按钮文字
  757. downloadInfo.value = `${downloadState.done}/${downloadState.allTasks}`;
  758. if (downloadState.pack === 1) {
  759. downloadInfo.value = "开始打包,请等待...";
  760. }
  761. if (downloadState.pack === 2) {
  762. downloadInfo.value = "完成";
  763. }
  764. }, { deep: true });
  765. // 全屏
  766. watch(fullScreen, (new_val) => {
  767. const elem = document.documentElement;
  768. // 全屏模式的函数
  769. function full_screen() {
  770. if (elem.requestFullscreen) {
  771. elem.requestFullscreen();
  772. } else if (elem.mozRequestFullScreen) { // 兼容 Firefox
  773. elem.mozRequestFullScreen();
  774. } else if (elem.webkitRequestFullscreen) { // 兼容 Chrome, Safari 和 Opera
  775. elem.webkitRequestFullscreen();
  776. } else if (elem.msRequestFullscreen) { // 兼容 IE/Edge
  777. elem.msRequestFullscreen();
  778. }
  779. }
  780.  
  781. // 退出全屏模式的函数
  782. function exit_fullscreen() {
  783. if (document.exitFullscreen) {
  784. document.exitFullscreen();
  785. } else if (document.mozCancelFullScreen) { // 兼容 Firefox
  786. document.mozCancelFullScreen();
  787. } else if (document.webkitExitFullscreen) { // 兼容 Chrome, Safari 和 Opera
  788. document.webkitExitFullscreen();
  789. } else if (document.msExitFullscreen) { // 兼容 IE/Edge
  790. document.msExitFullscreen();
  791. }
  792. }
  793.  
  794. if (new_val) {
  795. full_screen()
  796. } else {
  797. exit_fullscreen()
  798. }
  799. })
  800.  
  801.  
  802. return {
  803. model_state,
  804. urlList,
  805. urlsLoaded,
  806. fullScreen,
  807. showSettings,
  808. downloadInfo,
  809. downloadState,
  810. imageSize,
  811. rightToLeft,
  812. onePage,
  813. whiteBg,
  814. download
  815. };
  816. }
  817. })
  818. .use(ElementPlus)
  819. .mount("#mt_manga")
  820. }
  821. };
  822.  
  823. ///////////////////////////////////////////////////////////////////////////////////
  824. // 根据当前页面挂载组件
  825. // 主题页面
  826. if (window.location.href.match("https://bbs.yamibo.com/thread") || window.location.href.match(/forum.php\?mod=viewthread/)) {
  827. // 依赖加载完成
  828. document.querySelector("#element").onload = () => {
  829. new MangaComponent()
  830. }
  831. }
  832.  
  833. // 主题列表页面
  834. else if (window.location.href.match("https://bbs.yamibo.com/forum")) {
  835. new MangaListComponent()
  836. }
  837. })();