Skip to content

Instantly share code, notes, and snippets.

@qctfw
Last active April 6, 2024 08:30
Show Gist options
  • Save qctfw/dd71a93bffee961b884fd6a16e26daa8 to your computer and use it in GitHub Desktop.
Save qctfw/dd71a93bffee961b884fd6a16e26daa8 to your computer and use it in GitHub Desktop.
UserScript of Integrating WibuSaka into MyAnimeList for displaying available anime streaming platforms in Indonesia
// ==UserScript==
// @name WibuSaka for MyAnimeList
// @namespace http://wibusaka.moe/
// @version 0.4.3
// @description Integrate Indonesian Streaming Platforms in MyAnimeList
// @author Azhar
// @homepage https://wibusaka.moe/
// @match https://myanimelist.net/anime/*
// @match https://myanimelist.net/animelist/*
// @icon https://wibusaka.moe/img/favicons/wibusaka_icon-196x196.png
// @icon64 https://wibusaka.moe/img/favicons/wibusaka_icon-64x64.png
// @updateURL https://gist.github.com/raw/dd71a93bffee961b884fd6a16e26daa8/wibusaka-for-mal.user.js
// @downloadURL https://gist.github.com/raw/dd71a93bffee961b884fd6a16e26daa8/wibusaka-for-mal.user.js
// @connect api.wibusaka.moe
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
/* globals $ */
'use strict';
const malCategory = window.location.pathname.split('/')[1];
const animeCategory = window.location.pathname.split('/')[2];
if (malCategory === 'animelist') {
mainList();
}
else if (malCategory === 'anime' && ['season', 'genre'].includes(animeCategory)) {
mainList();
}
else if (malCategory === 'anime' && !Number.isNaN(+animeCategory)) {
mainId(animeCategory);
}
function mainId(animeId) {
if (!checkAiredDate()) {
return;
}
const resourceModalSelector = '.mal-modal .mal-modal-body .broadcasts.wibusaka';
const resourceLeftSideSelector = '#wibusaka-anime-availability';
let resources = [];
let resourceLoaded = false;
$('.leftside').children().last().prev().before(`<h2>
Streaming Platforms
<br>
<span style="font-weight: normal;font-size:9px;">Provided by <a href="https://wibusaka.moe" target="_blank">WibuSaka</a></span>
</h2>
<div id="${resourceLeftSideSelector.slice(1)}" class="broadcasts wibusaka">
Loading...
</div>`);
if ($('.js-broadcast-button').length > 0) {
$('.js-broadcast-button').on('click', function (e) {
if ($(resourceModalSelector).length <= 0) {
$('.mal-modal .mal-modal-body').prepend(`<div style="padding:0 34px;margin-bottom:-11px;margin-top:11px;">MyAnimeList Resources</div>`);
$('.mal-modal .mal-modal-body').append(`<div style="padding:0 34px;">Resources Provided by <a href="https://wibusaka.moe">WibuSaka</a></div><ul class="broadcasts wibusaka" style="margin-top:0.2em;"></ul>`);
}
if (resourceLoaded) {
addToStreamingModal(resources);
}
});
}
getResources(animeId, function (response) {
resources = response.response.data[0].resources;
resourceLoaded = true;
$(resourceLeftSideSelector).html(null);
if (resources.length == 0) {
$(resourceModalSelector).html('<i>No platforms available for this anime.</i>');
$(resourceLeftSideSelector).html('<i>No platforms available for this anime.</i>');
}
resources.forEach(element => {
const resourceLeftSide = `<div class="broadcast">
<a href="${element.url}" target="_blank" title="${element.platform_name}" class="broadcast-item" data-available="1">
${getIconResource(element.platform_name)}
<div class="caption" ${element.note ? 'style="height:unset;"' : ''}>${element.platform_name}${element.is_paid ? ' (Paid)' : ''}${element.note ? `<br /><em>(${element.note})</em>` : ''}</div>
</a>
</div>`;
$(resourceLeftSideSelector).append(resourceLeftSide);
});
if (document.querySelector('.mal-modal.show')) {
$('.js-broadcast-button').trigger('click');
}
}, function(response) {
if (response.status == 429) {
$(resourceModalSelector).html('<i>Too many requests. Please try again later.</i>');
$(resourceLeftSideSelector).html('<i>Too many requests. Please try again later.</i>');
}
else if (response.status == 503) {
$(resourceModalSelector).html('<i>API is on maintenance. Please try again later.</i>');
$(resourceLeftSideSelector).html('<i>API is on maintenance. Please try again later.</i>');
}
else {
$(resourceModalSelector).html('<i>An error has been occured.</i>');
$(resourceLeftSideSelector).html('<i>An error has been occured.</i>');
console.error('WibuSaka error: ' + response.status + ' ' + response.statusText);
console.error(response.response);
}
});
}
function mainList() {
const allResources = new Map();
$('.js-broadcast-button, [data-ga-click-type=broadcast-ownlist]').click(function (e) {
const animeId = $(this).data('gaClickParam').slice(4);
$('.mal-modal .mal-modal-body').prepend(`<div style="padding:0 34px;margin-bottom:-11px;margin-top:11px;">MyAnimeList Resources</div>`);
$('.mal-modal .mal-modal-body').append(`<div style="padding:0 34px;">
Resources Provided by <a href="https://wibusaka.moe" target="_blank">WibuSaka</a>
</div>
<ul class="broadcasts wibusaka" style="margin-top:0.2em;">Loading...</ul>`);
if (allResources.has(animeId)) {
addToStreamingModal(allResources.get(animeId));
return;
}
getResources(animeId, function (response) {
const resources = response.response.data[0].resources;
allResources.set(animeId, resources);
addToStreamingModal(resources);
}, function(response) {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html(null);
if (response.status == 429) {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html('<i>Too many requests. Please try again later.</i>');
}
else if (response.status == 503) {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html('<i>API is on maintenance. Please try again later.</i>');
}
else {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html('<i>An error has been occured.</i>');
console.error('WibuSaka error: ' + response.status + ' ' + response.statusText);
console.error(response.response);
}
});
});
}
function checkAiredDate() {
let airedDate = $('.leftside div:contains("Aired:")').text().replace('Aired:', '').trim().split(' to ')[0];
if (airedDate == 'Not available') {
return false;
}
airedDate = new Date(airedDate);
let todayDate = (new Date()).setHours(0, 0, 0, 0);
const difference = (airedDate.getTime() - todayDate) / (1000 * 3600 * 24);
return difference <= 14;
}
function addToStreamingModal(resources) {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html(null);
if (resources.length == 0) {
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').html('<i>No platforms available for this anime.</i>');
}
resources.forEach(element => {
const resourceText = `<li class="broadcast" data-available="true">
<a href="${element.url}" target="_blank">
${getIconResource(element.platform_name)}
<div class="caption" ${element.note ? 'style="height:unset;"' : ''}>${element.platform_name}${element.is_paid ? ' (Paid)' : ''}${element.note ? `<br /><em>(${element.note})</em>` : ''}</div>
</a>
</li>`;
$('.mal-modal .mal-modal-body .broadcasts.wibusaka').append(resourceText);
});
}
function getResources(id, success, error) {
GM_xmlhttpRequest({
method: "GET",
url: `https://api.wibusaka.moe/v1/resources/anime/myanimelist?id=${id}`,
revalidate: true,
responseType: 'json',
onload: success,
onerror: error
});
}
function getIconResource(name) {
name = name.toLowerCase();
const availableIcons = [
'crunchyroll',
'netflix',
'hidive',
'funimation',
'hulu',
'aisplay',
'animaxasia',
'animaxkorea',
'animedigitalnetwork',
'animeondemand',
'ani-oneasia',
'aniplusasia',
'aniplustv',
'aniverse',
'asiancrush',
'bahamutanimecrazy',
'bilibili',
'bilibiliintl',
'catchplay',
'danet',
'dimsument',
'flixer',
'iqiyi',
'laftel',
'museasia',
'pops',
'selectavision',
'shahid',
'starzplay',
'tubitv',
'viu',
'vivoplay',
'vvvvid',
'wakanim',
'wetv',
'yamatoanimation',
'amazonprimevideo',
'trueid',
'genflix',
'jonuplay',
'mewatch',
'sushiroll',
'upstream',
'animaxmongolia',
'disneyplus',
'prosiebenmaxx',
'disneyhotstar'
];
const alternativeIconNames = {
"bstation": "bilibiliintl",
"catchplay+": "catchplay",
"prime video": "amazonprimevideo",
"hotstar": "disneyhotstar",
};
const alternativeIconsUrl = {
"vidio": `https://assets.wibusaka.moe/img/logos/vidio.webp`,
"twitter": `https://cdn.myanimelist.net/img/common/external_links/101.png`,
"youtube": `https://cdn.myanimelist.net/img/common/external_links/102.png`,
}
if (name in alternativeIconNames) {
name = alternativeIconNames[name];
}
else if (name in alternativeIconsUrl) {
return `<img src="${alternativeIconsUrl[name]}" class="link_icon" alt="icon" width="20" height="20" />`;
}
else if (!availableIcons.includes(name)) {
return `<div style="width:20px;height:17px;display:flex;align-items:center;justify-content:center;"><i class="link_icon fa-solid fa-link"></i></div>`;
}
return `<i class="spicon spicon-${name}"></i>`;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment