Kitsu External Links

adds a link to myanimelist and anilist on Kitsu entries

  1. // ==UserScript==
  2. // @name Kitsu External Links
  3. // @namespace https://greasyfork.org/users/649
  4. // @version 1.0.1
  5. // @description adds a link to myanimelist and anilist on Kitsu entries
  6. // @author Adrien Pyke
  7. // @match *://kitsu.io/*
  8. // @grant none
  9. // @require https://cdn.jsdelivr.net/gh/fuzetsu/userscripts@ec863aa92cea78a20431f92e80ac0e93262136df/wait-for-elements/wait-for-elements.js
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. 'use strict';
  14.  
  15. const Api = {
  16. cache: {},
  17. getId: () =>
  18. document
  19. .querySelector('.cover-photo')
  20. .getAttribute('style')
  21. .match(/cover_images\/(?<id>\d+)/iu).groups.id,
  22. getType: () =>
  23. location.href.match(/kitsu\.io\/(?<type>anime|manga)/iu).groups.type,
  24. getLinks: async (id, type) => {
  25. const endpoint = `https://kitsu.io/api/edge/${type}/${id}?include=mappings`;
  26. if (Api.cache[endpoint]) return Api.cache[endpoint];
  27. const response = await fetch(endpoint);
  28. const json = await response.json();
  29. const mappings = json.included
  30. .filter(i =>
  31. i.attributes.externalSite.match(
  32. /^(myanimelist|anilist)\/(anime|manga)$/iu
  33. )
  34. )
  35. .map(i => i.attributes);
  36. Api.cache[endpoint] = mappings;
  37. return mappings;
  38. }
  39. };
  40.  
  41. const getLinksContainer = container => {
  42. const node = document.createElement('div');
  43. node.classList.add('where-to-watch-widget');
  44. const links = document.createElement('ul');
  45. links.classList.add('nav');
  46. node.appendChild(links);
  47. container.appendChild(node);
  48. return links;
  49. };
  50.  
  51. const getLinkNode = mapping => {
  52. const { site, type } = mapping.externalSite.match(
  53. /^(?<site>myanimelist|anilist)\/(?<type>anime|manga)$/iu
  54. ).groups;
  55. const webSite =
  56. site === 'anilist' ? 'https://anilist.co' : 'https://myanimelist.net';
  57. const href = `${webSite}/${type}/${mapping.externalId}`;
  58.  
  59. const node = document.createElement('li');
  60. const link = document.createElement('a');
  61. link.classList.add('hint--top', 'hint--bounce', 'hint--rounded');
  62. Object.assign(link, { href, target: '_blank', rel: 'noopener noreferrer' });
  63. link.setAttribute('aria-label', site);
  64. node.appendChild(link);
  65. const logo = document.createElement('img');
  66. Object.assign(logo, {
  67. src:
  68. site === 'anilist'
  69. ? 'https://anilist.co/img/icons/android-chrome-512x512.png'
  70. : 'https://upload.wikimedia.org/wikipedia/commons/7/7a/MyAnimeList_Logo.png',
  71. width: '20',
  72. height: '20'
  73. });
  74. link.appendChild(logo);
  75. return node;
  76. };
  77.  
  78. waitForElems({
  79. sel: '.media-sidebar',
  80. onmatch(container) {
  81. const links = getLinksContainer(container);
  82. const id = Api.getId();
  83. const type = Api.getType();
  84. Api.getLinks(id, type).then(list =>
  85. list.forEach(m => links.appendChild(getLinkNode(m)))
  86. );
  87. }
  88. });
  89. })();