shikiPlus

script add viewer and playeer on shikimori

  1. // ==UserScript==
  2. // @name shikiPlus
  3. // @author chsa13
  4. // @description script add viewer and playeer on shikimori
  5. // @namespace http://shikimori.me/
  6. // @version 2.1.2
  7. // @match *://shikimori.org/*
  8. // @match *://shikimori.one/*
  9. // @match *://shikimori.me/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=shikimori.me
  11. // @license MIT
  12. // @copyright Copyright © 2024 chsa13. All rights reserved.
  13. // ==/UserScript==
  14. let RonobeLibLink = "https://ranobelib.me/ru/"
  15. let MangaLibLink = "https://test-front.mangalib.me/ru/"
  16. let AnimeLibLink = "https://anilib.me/ru/anime/"
  17. let LibSocialApiLink = 'https://api.lib.social/api'
  18.  
  19. function launchFullScreen(element) {
  20. if (element.requestFullScreen) {
  21. element.requestFullScreen();
  22. }
  23. else if (element.webkitRequestFullScreen) {
  24. element.webkitRequestFullScreen();
  25. }
  26. else if (element.mozRequestFullScreen) {
  27. element.mozRequestFullScreen();
  28. }
  29. }
  30. function addStyles(){
  31. $(document.body).append($("<style>").prop("type", "text/css").html(`
  32. .shiki-plus .iframe{
  33. width: 100%;
  34. height: unset;
  35. aspect-ratio: 1.36;
  36. min-height: 650px;
  37. border: none;
  38. }
  39. .shiki-plus .black-div {
  40. position: absolute;
  41. background-color: black;
  42. width: 100%;
  43. height: unset;
  44. aspect-ratio: 1.36;
  45. min-height: 650px;
  46. border: none;
  47. }
  48. .shiki-plus .options {
  49. margin-bottom: 10px;
  50. width: 100%;
  51. height: 40px;
  52. display: flex;
  53. align-items: center;
  54. justify-content: space-between;
  55. }
  56. .shiki-plus .options .sweatcher {
  57. opacity: 1;
  58. display: flex;
  59. align-items: center;
  60. font-size: 20px;
  61. color: var(--color-text-primary, --font-main, #F8F8F2);
  62. white-space: nowrap;
  63. }
  64. .shiki-plus .options .sweatcher span {
  65. margin-right: 10px;
  66. color: var(--color-text-primary, --font-main, #112233);
  67. }
  68. .shiki-plus .options .sweatcher .variant {
  69. min-width: unset;
  70. margin-bottom: unset;
  71. margin-right: 10px;
  72. }
  73. .shiki-plus .options .sweatcher .variant.selected {
  74. opacity: 0.85;
  75. }
  76. @media (max-width: 768px) {
  77. .shiki-plus .hide-on-mobile-text {
  78. display: none !important;
  79. }
  80. }
  81. `))
  82. }
  83. function ready(fn) {
  84. document.addEventListener('page:load', fn);
  85. document.addEventListener('turbolinks:load', fn);
  86. if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") fn();
  87. else document.addEventListener('DOMContentLoaded', fn);
  88. }
  89.  
  90. async function sleep(timeMs) {
  91. await new Promise(resolve => setTimeout(resolve, timeMs));
  92. }
  93.  
  94. async function GetResourceAsync(uri, config = {}) {
  95. return await new Promise((resolve, reject) => {
  96. $.ajax(Object.assign({
  97. url: uri,
  98. dataType: 'json',
  99. async: true,
  100. cache: false,
  101. headers: {
  102. 'Accept': 'application/json, text/javascript, */*; q=0.01',
  103. 'Accept-Language': 'en-US,en;q=0.9',
  104. },
  105. success: function(res) {
  106. resolve(res);
  107. },
  108. error: function(xhr) {
  109. reject(xhr);
  110. }
  111. }, config));
  112. });
  113. }
  114.  
  115.  
  116. async function createReader(hrefs, type) {
  117. let shikiPlus = $("<div>").addClass("shiki-plus")
  118. let options = $("<div>").addClass("options");
  119. let link
  120. if (type == "manga") {
  121. link = MangaLibLink + hrefs[0] + "/read/v01/c01";
  122. } else if (type == "ranobe") {
  123. link = RonobeLibLink + hrefs[0] + "/read/v01/c01";
  124. }
  125. let iframe = $("<iframe>").addClass("iframe").attr({
  126. 'allowFullScreen': 'allowfullscreen',
  127. "src": link,
  128. });
  129. let blackdiv = $("<div>").addClass("black-div");
  130. iframe.on("load", ()=>{
  131. blackdiv.remove();
  132. })
  133. shikiPlus.append(options).append(blackdiv).append(iframe)
  134. let currentVal = 0
  135. let variants = []
  136. let sweatcher = $("<div>").addClass("sweatcher");
  137. sweatcher.append($("<span>").text("Выбрать версию:").addClass("hide-on-mobile-text"))
  138. for (let i in hrefs) {
  139. let variant = $("<div>").addClass("b-link_button").addClass("dark").addClass("variant").text((String(Number(i) + 1)));
  140. variant.on("click", ()=>{
  141. if (i == currentVal) return
  142. let link
  143. if (type == "manga") {
  144. link = MangaLibLink + hrefs[i] + "/read/v01/c01";
  145. } else if (type == "ranobe") {
  146. link = RonobeLibLink + hrefs[i] + "/read/v01/c01";
  147. }
  148. for (let i of variants){
  149. i.removeClass("selected")
  150. }
  151. variant.addClass("selected")
  152. currentVal = i
  153. iframe.attr("src", link)
  154. })
  155. sweatcher.append(variant);
  156. variants.push(variant)
  157. }
  158. variants[0].addClass("selected")
  159.  
  160. if (hrefs.length >= 2) options.append(sweatcher);
  161. let fullScreenBtn = $("<div>").addClass("full-screen-btn").addClass("b-link_button").addClass("dark").text("Полноэкранный режим");
  162.  
  163.  
  164. fullScreenBtn.on("click", ()=>{
  165. // fullScreenBtn.removeClass("touched")
  166. launchFullScreen(iframe[0]);
  167. })
  168. options.append(fullScreenBtn);
  169.  
  170. let container = $('.b-db_entry');
  171. let aboutSection = container.find('.c-about');
  172. let descriptionSection = $('.c-description');
  173. let imageSection = container.find('.c-image');
  174.  
  175. // Проверка на мобильную версию
  176. if ($(window).width() <= 768) {
  177. if (imageSection.length) {
  178. imageSection.append(shikiPlus);
  179. }
  180. } else if (aboutSection.length && descriptionSection.length) {
  181. shikiPlus.insertBefore(descriptionSection);
  182. }
  183. }
  184.  
  185.  
  186. function createWatcher(href) {
  187. let shikiPlus = $("<div>").addClass("shiki-plus")
  188. let iframe = $("<iframe>").addClass("iframe").attr({
  189. "src": AnimeLibLink + href.split("?")[0] + "/watch",
  190. "allowFullScreen": "allowfullscreen"
  191. });
  192.  
  193. let blackdiv = $("<div>").addClass("black-div");
  194. shikiPlus.append(blackdiv).append(iframe)
  195. // Находим контейнер
  196. let container = $('.b-db_entry');
  197. let descriptionSection = $('.c-description');
  198.  
  199. // Проверяем, существует ли нужный элемент
  200. if (descriptionSection.length) {
  201. // Если это мобильное устройство
  202. if ($(window).width() <= 768) {
  203. let cImageElement = container.find('.c-image');
  204. if (cImageElement.length) {
  205. cImageElement.append(shikiPlus);
  206. }
  207. } else {
  208. shikiPlus.insertBefore(descriptionSection);
  209. }
  210. }
  211. iframe.on("load", ()=>{
  212. blackdiv.remove();
  213. })
  214. }
  215.  
  216.  
  217. async function addBtn() {
  218. if (document.querySelector('.shikiPlus')) return
  219. let lic = false
  220. let btn = $("<div>").addClass("shikiPlus").addClass("b-link_button").addClass("dark")
  221. let href = ""
  222. let type
  223. if (location.href.includes("/animes/")) {
  224. type = "anime"
  225. let id = document.querySelector('.b-user_rate').getAttribute('data-target_id')
  226. let title = document.querySelector("meta[property='og:title']").getAttribute('content')
  227. try {
  228. title = title.split(" ")[0]
  229. }
  230. catch {return}
  231. let response = await GetResourceAsync(LibSocialApiLink + "/anime?q=" + title)
  232. href = ""
  233. for (let i in response.data) {
  234. if (id == response.data[i].shikimori_href.split("/")[4]) {
  235. lic = response.data.is_licensed
  236. href = response.data[i].slug_url
  237. }
  238. }
  239. btn.text("Смотреть")
  240. }
  241. else if (location.href.includes("/mangas/")) {
  242. let links = document.querySelectorAll(".mangalib > a")
  243. type = "manga"
  244. btn.text("Читать")
  245. await findInLinks(links)
  246. }
  247. else if (location.href.includes("/ranobe/")) {
  248. let links = document.querySelectorAll(".ranobelib > a")
  249. type = "ranobe"
  250. btn.text("Читать")
  251. await findInLinks(links)
  252. }
  253. async function findInLinks(as) {
  254. href = []
  255. for (let i in as) {
  256. if (as[i].href) {
  257. i = (as[i].href.split("?")[0].replace("https://ranobelib.me/", "").replace("https://mangalib.me/", "").replace("ru/", "").replace("book/", "").replace("ranobes/", "").replace("ranobe/", "").replace("manga/", "").replace("mangas/", ""))
  258. let response = await GetResourceAsync(LibSocialApiLink + "/manga/" + i)
  259. lic = response.data.is_licensed
  260. href.push(response.data.slug_url)
  261. }
  262. }
  263. }
  264. btn.on("click", ()=>{
  265. btn.remove();
  266. document.querySelector('.c-description').style.cssText = `
  267. margin-left:0;
  268. `
  269. if (type == "anime"){
  270. createWatcher(href)
  271. }
  272. else if (type == "manga" || type == "ranobe"){
  273. createReader(href, type)
  274. }
  275. })
  276. if (!lic && (href || href.length))
  277. document.querySelectorAll('.c-image').forEach(e => e.appendChild(btn[0]))
  278. }
  279. ready(addStyles)
  280. ready(addBtn)