// ==UserScript==
// @name AniCHAT - Discuss Anime Episodes
// @namespace https://greasyfork.org/en/users/781076-jery-js
// @version 2.2.0
// @description Get discussions from popular sites like MAL and Reddit for the anime you are watching right below your episode
// @icon https://image.myanimelist.net/ui/OK6W_koKDTOqqqLDbIoPAiC8a86sHufn_jOI-JGtoCQ
// @author Jery
// @license MIT
// @match https://yugenanime.*/*
// @match https://yugenanime.tv/*
// @match https://yugenanime.sx/*
// @match https://animepahe.*/*
// @match https://animepahe.com/*/
// @match https://anitaku.*/*
// @match https://anitaku.so/*
// @match https://gogoanime.*/*
// @match https://gogoanime.to/*
// @match https://gogoanime3.*/*
// @match https://gogoanime3.co/*
// @match https://aniwave.*/watch/*
// @match https://aniwave.to/watch/*
// @match https://aniwave.vc/watch/*
// @match https://aniwave.ti/watch/*
// @match https://aniwatchtv.*/watch/*
// @match https://aniwatchtv.to/watch/*
// @match https://hianime.*/watch/*
// @match https://hianime.to/watch/*
// @match https://kayoanime.*/*
// @match https://kayoanime.com/*
// @match https://kaas.*/*/*
// @match https://kaas.to/*/*
// @match https://kickassanimes.*/*/*
// @match https://kickassanimes.io/*/*
// @match https://*.kickassanime.*/*/*
// @match https://*.kickassanime.mx/*/*
// @match https://anix.*/*/*/*
// @match https://anix.to/*/*/*
// @match https://anix.ac/*/*/*
// @match https://anix.vc/*/*/*
// @match https://animeflix.*/watch/*
// @match https://animeflix.live/watch/*
// @match https://animehub.*/watch/*
// @match https://animehub.ac/watch/*
// @match https://animesuge.*/anime/*
// @match https://animesuge.to/anime/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
// @require https://unpkg.com/axios/dist/axios.min.js
// ==/UserScript==
/**************************
* CONSTANTS
***************************/
// seconds to wait before loading the discussions (to avoid spamming the service)
const TIMEOUT = 30000; // in milliseconds
// proxy to bypass the cors restriction on services like MAL
const PROXYURL = "https://proxy.cors.sh/"; //"https://test.cors.workers.dev/?"; //'https://corsproxy.io/?';
/***************************************************************
* ANIME SITES & SERVICES
***************************************************************/
const animeSites = [
{
name: "yugenanime",
url: ["yugenanime.tv", "yugenanime.sx"],
chatArea: ".box.m-10-t.m-25-b.p-15",
getAnimeTitle: () => document.querySelector(".ani-info-ep a > h1").textContent,
getEpTitle: () => document.querySelector("h1.text-semi-bold.m-5-b").textContent,
getEpNum: () => window.location.href.split("/")[6],
styles: null,
},
{
name: "animepahe",
url: ["animepahe.ru", "animepahe.com"],
chatArea: ".theatre",
getAnimeTitle: () => document.querySelector(".theatre-info > h1 > a").textContent.split(' - ')[0],
getEpTitle: () => document.querySelector(".theatre-info > h1 > a").textContent.split(' - ')[0],
getEpNum: () => document.querySelector(".dropup.episode-menu > button").innerText.split("Episode ")[1],
styles: '.discussion-area { max-width:1100px; margin:15px auto 0; }',
},
{
name: "gogoanime",
url: ['gogoanime3', 'gogoanimehd', 'gogoanime', 'anitaku'],
chatArea: ".anime_video_body_comment_center",
getAnimeTitle: () => document.querySelector(".anime-info > a").textContent,
getEpTitle: () => document.querySelector(".anime-info > a").textContent,
getEpNum: () => window.location.href.split("-episode-")[1],
styles: `.chat-msg { color: white; font-size: 14px; } .discussion-title > a { font-size: 24px; color: goldenrod; }`
},
{
name: "aniwave",
url: ['aniwave', 'lite.aniwave'],
chatArea: "#comments",
getAnimeTitle: () => document.querySelector(".name .title").textContent,
getEpTitle: () => document.querySelector(".name .title").textContent,
getEpNum: () => window.location.href.split("/ep-")[1],
},
{
name: "hianime",
url: ["aniwatchtv", "hianime.to", "hianime.nz", "hianime.mm", "hianime.sx", "hianime"],
chatArea: ".show-comments",
getAnimeTitle: () => document.querySelector("h2.film-name > a").textContent,
getEpTitle: () => document.querySelector("div.ssli-detail > .ep-name").textContent,
getEpNum: () => waitForElm(".ssl-item.ep-item.active > .ssli-order").then(elm => elm.textContent),
styles: `.chat-row .user-avatar { width: auto; overflow: visible; }`
},
{
name: "kayoanime",
url: ["kayoanime.com"],
chatArea: "#the-post",
getAnimeTitle: () => document.querySelector("h1.entry-title").textContent.split(/Episode \d+ English.+/)[0].trim(),
getEpTitle: () => document.querySelector(".toggle-head").textContent.trim(),
getEpNum: () => document.querySelector("h1.entry-title").textContent.split(/Episode (\d+) English.+/)[1],
},
{
name: "kickassanime",
url: ["kaas", "kickassanimes", "kickassanime"],
chatArea: () => document.querySelector("#disqus_thread").parentElement,
getAnimeTitle: () => document.querySelector(".text-h6").textContent,
getEpTitle: () => document.querySelector(".text-h6").textContent,
getEpNum: () => document.querySelector(".d-block .text-overline").textContent.split("Episode")[1].trim(),
},
{
name: "anix",
url: ["anix"],
chatArea: () => document.querySelector("#disqus_thread").parentElement,
getAnimeTitle: () => document.querySelector(".ani-name").textContent,
getEpTitle: () => document.querySelector(".ani-name").textContent,
getEpNum: () => window.location.href.split("/ep-")[1],
},
{
name: "animeflix",
url: ["animeflix"],
chatArea: 'main',
getAnimeTitle: () => document.querySelector(".details .title").textContent,
getEpTitle: () => document.querySelector(".details .title").textContent,
getEpNum: () => window.location.href.split("-episode-")[1],
},
{
name: "animehub",
url: ["animehub"],
chatArea: 'mawdawin',
getAnimeTitle: () => document.querySelector(".dc-title").textContent,
getEpTitle: () => document.querySelector(".dc-title").textContent,
getEpNum: () => document.querySelector("#current_episode_name").textContent.split("Episode")[1].trim(),
},
{
name: "animesuge",
url: ["animesuge.to", "animesuge"],
chatArea: '#comment',
getAnimeTitle: () => document.querySelector("#media-info .maindata > h1").textContent,
getEpTitle: () => document.querySelector("#media-info .maindata > h1").textContent,
getEpNum: () => window.location.href.split("/ep-")[1],
}
];
const services = [
{
name: "MyAnimeList",
icon: "https://image.myanimelist.net/ui/OK6W_koKDTOqqqLDbIoPAiC8a86sHufn_jOI-JGtoCQ",
url: "https://myanimelist.net/",
_clientId: "dbe5cec5a2f33fdda148a6014384b984",
_proxyKey: "temp_2ed7d641dd52613591687200e7f7958b",
async getDiscussion(animeTitle, epNum) {
let animeId, topic, url, response, data;
let headers = {headers: {"X-MAL-CLIENT-ID": this._clientId, "x-cors-api-key": this._proxyKey, 'x-requested-with': 'XMLHttpRequest', 'origin': window.location.origin}};
// get the anime's MAL id using MAL API (or use Jikan API if title is too long)
try {
if (animeTitle.length > 500) {
url = PROXYURL + `https://api.myanimelist.net/v2/anime?q=${animeTitle}&limit=1`;
response = await axios.get(url, headers);
animeId = response.data.data[0].node.id;
} else {
url = PROXYURL + `https://api.jikan.moe/v4/anime?q=${animeTitle}&limit=1`;
animeId = GM_getValue('cachedId_'+url, null);
if (!animeId) {
response = await axios.get(url, headers);
animeId = response.data.data[0].mal_id;
GM_setValue('cachedId_'+url, animeId);
}
}
console.log(`animeId: ${animeId}`);
} catch (e) {
throw new Error(`Couldn't find the anime id. Retry after a while or switch to another service.\n${e.code} : ${e}`);
}
// get the discussion url from the anime
try {
url = PROXYURL + `https://api.jikan.moe/v4/anime/${animeId}/forum`;
response = await axios.get(url, headers);
topic = response.data.data.find(it => it.title.includes(`Episode ${epNum} Discussion`));
console.log(`topic: ${topic}`);
} catch (e) {
throw new Error(`No discussion found. Retry after a while or switch to another service.\n${e.code} : ${e}`);
}
// get the forum page
try {
url = PROXYURL + `https://api.myanimelist.net/v2/forum/topic/${topic.mal_id}?limit=100`;
response = await axios.get(url, headers);
data = response.data.data;
console.log(`data: ${data}`);
} catch (e) {
throw new Error(`Error getting the discusssion (${topic}). Retry after a while or switch to another service.\n${e.code} : ${e}`);
}
let chats = [];
data.posts.forEach((post) => {
const user = post.created_by.name;
const userLink = "https://myanimelist.net/profile/" + user;
const avatar = post.created_by.forum_avator;
const msg = this._parseBBCode(post.body);
const timestamp = new Date(post.created_at).getTime();
chats.push(new Chat(user, userLink, avatar, msg, timestamp, null));
});
const discussion = new Discussion(topic.title, topic.url, chats);
return discussion;
},
_parseBBCode(bbcode) {
const mappings = [
{ bbcode: /\[b\](.*?)\[\/b\]/g, html: "<strong>$1</strong>" },
{ bbcode: /\[i\](.*?)\[\/i\]/g, html: "<em>$1</em>" },
{ bbcode: /\[u\](.*?)\[\/u\]/g, html: "<u>$1</u>" },
{ bbcode: /\[s\](.*?)\[\/s\]/g, html: "<s>$1</s>" },
{ bbcode: /\[url=(.*?)\](.*?)\[\/url\]/g, html: '<a href="$1">$2</a>' },
{ bbcode: /\[img.*?\](.*?)\[\/img\]/g, html: '<img src="$1" alt="">' },
{ bbcode: /\[code\]([\s\S]*?)\[\/code\]/g, html: "<code>$1</code>" },
{ bbcode: /\[quote\]/g, html: '<blockquote class="quote" style="font-size: 90%; border: 1px solid; padding: 5px;">' },
{ bbcode: /\[quote=(.*?)\s*(message=\d+)?\]/g, html: '<blockquote class="quote" style="font-size: 90%; border: 1px solid; padding: 5px;"><h4>$1 Said:</h4>' },
{ bbcode: /\[\/quote\]/g, html: '</blockquote>' },
{ bbcode: /\[color=(.*?)\](.*?)\[\/color\]/g, html: '<span style="color: $1;">$2</span>' },
{ bbcode: /\[size=(.*?)\](.*?)\[\/size\]/g, html: '<span style="font-size: $1;">$2</span>' },
{ bbcode: /\[center\](.*?)\[\/center\]/g, html: '<div style="text-align: center;">$1</div>' },
{ bbcode: /\[list\](.*?)\[\/list\]/g, html: "<ul>$1</ul>" },
{ bbcode: /\[list=(.*?)\](.*?)\[\/list\]/g, html: '<ol start="$1">$2</ol>' },
{ bbcode: /\[\*\](.*?)\[\/\*\]/g, html: "<li>$1</li>" },
{ bbcode: /\[spoiler\]([\s\S]*?)\[\/spoiler\]/g, html: '<div class="spoiler"><input type="button" onclick="this.nextSibling.style.display=\'inline-block\';this.style.display=\'none\';" value="Show spoiler" style="display: inline-block;"><span class="spoiler_content" style="display: none;"><input type="button" onclick="this.parentNode.style.display=\'none\';this.parentNode.parentNode.childNodes[0].style.display=\'inline-block\';" value="Hide spoiler">$1</span></div>' },
{ bbcode: /\[spoiler=(.*?)\]([\s\S]*?)\[\/spoiler\]/g, html: '<div class="spoiler"><input type="button" onclick="this.nextSibling.style.display=\'inline-block\';this.style.display=\'none\';" value="Show $1" style="display: inline-block;"><span class="spoiler_content" style="display: none;"><input type="button" onclick="this.parentNode.style.display=\'none\';this.parentNode.parentNode.childNodes[0].style.display=\'inline-block\';" value="Hide $1">$2</span></div>' },
{ bbcode: /@(\S+)/g, html: '<a href="https://myanimelist.net/profile/$1" target="_blank">@$1</a>' },
];
let html = bbcode;
for (const mapping of mappings) { html = html.replace(mapping.bbcode, mapping.html); }
return html;
}
},
{
name: "Reddit",
icon: "https://www.redditstatic.com/desktop2x/img/favicon/apple-icon-57x57.png",
url: "https://www.reddit.com/",
_clientId: "dbe5cec5a2f33fdda148a6014384b984",
_proxyKey: "temp_2ed7d641dd52613591687200e7f7958b",
async getDiscussion(animeTitle, epNum) {
let animeId, topic, url, response, posts;
let headers = {headers: {"x-cors-api-key": this._proxyKey, 'x-requested-with': 'XMLHttpRequest', 'origin': window.location.origin}};
// get the anime's MAL id
try {
url = PROXYURL + `https://api.jikan.moe/v4/anime?q=${animeTitle}&limit=1`;
animeId = GM_getValue('cachedId_'+url, '');
if (animeId == '') {
response = await axios.get(url, headers);
if (response.data.data.length>0) {
animeId = response.data.data[0].mal_id;
GM_setValue('cachedId_'+url, animeId);
}
}
} catch (e) {
throw new Error(`Couldn't find the anime id. Retry after a while or switch to another service.\n${e.code} : ${e.message}`);
}
// Get the discussion
try {
url = `https://api.reddit.com/r/anime/search.json?q=${animeTitle}+-+Episode+${epNum}+discussion+author:AutoLovepon&restrict_sr=on&include_over_18=on&sort=relevance&limit=50`;
response = await axios.get(url);
topic = response.data.data.children.find(it => it.data.title.includes(` - Episode ${epNum} discussion`) && it.data.selftext.includes(`[MyAnimeList](https://myanimelist.net/anime/${animeId}`))?.data;
} catch (e) {
throw new Error(`No discussion found. Retry after a while or switch to another service.\n${e.code} : ${e.message}`);
}
// get the comments in the discussion
try {
url = topic.url.replace('www.reddit.com', 'api.reddit.com');
response = await axios.get(url);
posts = response.data[1].data.children;
if (posts[0].data.author == "AutoModerator") posts.shift(); // skip the first bot post
} catch (e) {
throw new Error(`Error getting the discusssion. Retry after a while or switch to another service.\n${e.code} : ${e.message}`);
}
let chats = [];
for (let post of posts) chats.push(this._processPost(post));
const discussion = new Discussion(topic.title, topic.url, chats);
return discussion;
},
_processPost(post) {
const user = post.data.author;
const userLink = "https://www.reddit.com/user/" + user;
// const about = axios.get(`https://api.reddit.com/user/${user}/about`);
const avatar = axios.get(`https://api.reddit.com/user/${user}/about`).then(r=>r.data.data.icon_img.split('?')[0]);
const msg = ((el) => { el.innerHTML = post.data.body_html; return el.value; })(document.createElement('textarea'));
const timestamp = post.data.created_utc * 1000;
let replies = [];
if (post.data.replies && post.data.replies.data)
for (let reply of post.data.replies.data.children)
replies.push(this._processPost(reply));
return new Chat(user, userLink, avatar, msg, timestamp, replies);
}
},
];
/***************************************************************
* Classes for handling various data like settings & discussions
***************************************************************/
// User settings
class UserSettings {
constructor(usernames = {}) {
this.usernames = usernames;
}
static load() {
return GM_getValue("userSettings", new UserSettings());
}
}
// Class to hold each row of a discussion
class Chat {
constructor(user, userLink, avatar, msg, timestamp, replies) {
this.user = user;
this.userLink = userLink;
this.avatar = avatar;
this.msg = msg;
this.timestamp = timestamp;
this.replies = replies;
}
getRelativeTime() {
const now = new Date().getTime();
const diff = now - this.timestamp;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const weeks = Math.floor(days / 7);
const months = Math.floor(days / 30);
const years = Math.floor(days / 365);
if (years > 0) {
return `${years} ${years === 1 ? "year" : "years"} ago`;
} else if (months > 0) {
return `${months} ${months === 1 ? "month" : "months"} ago`;
} else if (weeks > 0) {
return `${weeks} ${weeks === 1 ? "week" : "weeks"} ago`;
} else if (days > 0) {
return `${days} ${days === 1 ? "day" : "days"} ago`;
} else if (hours > 0) {
return `${hours} ${hours === 1 ? "hour" : "hours"} ago`;
} else if (minutes > 0) {
return `${minutes} ${minutes === 1 ? "minute" : "minutes"} ago`;
} else {
return `${seconds} ${seconds === 1 ? "second" : "seconds"} ago`;
}
}
}
// Class to hold the complete discussions
class Discussion {
constructor(title, link, chats) {
this.title = title;
this.link = link;
this.chats = chats;
}
}
/***************************************************************
* The UI elements
***************************************************************/
// generate the discussion area
async function generateDiscussionArea() {
const discussionArea = document.createElement("div");
discussionArea.className = "discussion-area";
const discussionTitle = document.createElement("h3");
discussionTitle.className = "discussion-title";
const discussionTitleText = document.createElement("a");
discussionTitleText.textContent = `${await site.getAnimeTitle()} Episode ${await site.getEpNum()} Discussion`;
discussionTitleText.title = "Click to view the original discussion";
discussionTitleText.target = "_blank";
discussionTitle.appendChild(discussionTitleText);
const serviceSwitcher = buildServiceSwitcher();
discussionTitle.appendChild(serviceSwitcher);
const discussionList = document.createElement("ul");
discussionList.className = "discussion-list";
discussionArea.appendChild(discussionTitle);
discussionArea.appendChild(discussionList);
return discussionArea;
}
function buildServiceSwitcher() {
const servicesArea = document.createElement('div');
servicesArea.id = 'service-switcher';
servicesArea.innerHTML = `<img class="service-icon selected" title="Powered by ${service.name}" src="${service.icon}"><a style="padding-right:5px">▶</a>`;
services.forEach(it => {
servicesArea.innerHTML += `<img class="service-icon other" data-opt="${services.indexOf(it)}" title="Switch to ${it.name}" src="${it.icon}" style="cursor:pointer;">`;
});
servicesArea.querySelectorAll('.other').forEach(it => {
it.addEventListener('click', () =>{
const serviceOpt = parseInt(it.getAttribute('data-opt'));
console.log(serviceOpt);
GM_setValue("service", serviceOpt);
service = services[serviceOpt];
document.querySelector('.discussion-area').remove();
run(0);
});
});
return servicesArea;
}
// build a row for a single chat in the discussion
async function buildChatRow(chat) {
const chatRow = document.createElement("li");
chatRow.className = "chat-row";
const chatContent = document.createElement("div");
chatContent.className = "chat-content";
const userAvatar = document.createElement("div");
userAvatar.className = "user-avatar";
userAvatar.innerHTML = `<img src="${service.icon}" alt="${chat.user}">`;
if (chat.avatar instanceof Promise) chat.avatar.then(avatarUrl => userAvatar.firstChild.src = avatarUrl);
else userAvatar.firstChild.src = chat.avatar;
const userMsg = document.createElement("div");
userMsg.className = "user-msg";
const name = document.createElement("span");
name.className = "chat-name";
name.textContent = chat.user;
const time = document.createElement("span");
time.className = "chat-time";
time.textContent = chat.getRelativeTime();
time.title = new Date(chat.timestamp).toLocaleString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
});
const msg = document.createElement("span");
msg.className = "chat-msg";
msg.innerHTML = chat.msg;
userMsg.appendChild(name);
userMsg.appendChild(time);
userMsg.appendChild(msg);
chatContent.appendChild(userAvatar);
chatContent.appendChild(userMsg);
chatRow.appendChild(chatContent);
if (chat.replies && chat.replies.length > 0) {
const repliesDiv = document.createElement("div");
repliesDiv.className = "reply";
for (let reply of chat.replies) {
const replyRow = await buildChatRow(reply);
repliesDiv.appendChild(replyRow);
}
chatRow.appendChild(repliesDiv);
}
return chatRow;
}
// Countdown to show before load the discussions
function setLoadingTimeout(timeout) {
let countdown = timeout;
const loadingArea = document.createElement("div");
loadingArea.className = "loading-discussion";
const loadingElement = document.createElement("div");
loadingElement.innerHTML = `<img src="https://flyclipart.com/thumb2/explosion-gif-transparent-transparent-gif-sticker-741584.png" style="width: 150px; margin-right: 10px;">`;
loadingElement.style.cssText = `display: flex; align-items: center;`;
const progressBar = document.createElement("div");
progressBar.className = "progress-bar";
progressBar.style.cssText = `width: "100%"; height: 10px; background-color: #ccc; position: relative;`;
const progressFill = document.createElement("div");
progressFill.className = "progress-fill";
progressFill.style.cssText = `width: 0%; height: 100%; background-color: #4CAF50; position: absolute; top: 0; left: 0;`;
const message = document.createElement("span");
message.textContent = `This ${
timeout / 1000
} secs timeout is set to reduce the load on the service and you can configure the TIMEOUT by editing the script (line 21)`;
message.style.cssText = "font-size: 14px; color: darkgrey;";
progressBar.appendChild(progressFill);
loadingElement.appendChild(message);
loadingArea.appendChild(loadingElement);
loadingArea.appendChild(progressBar);
console.log("Countdown started: " + countdown + "ms");
const countdownInterval = setInterval(() => {
countdown -= 100;
const progressWidth = 100 - (countdown / timeout) * 100;
progressFill.style.width = `${progressWidth}%`;
if (countdown <= 0) {
message.remove();
loadingElement.remove();
clearInterval(countdownInterval);
}
}, 100);
return loadingArea;
}
// Add CSS styles to the page
const styles = `
.discussion-area {
border-radius: 10px;
padding: 10px;
}
.discussion-title {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.discussion-title > a {
margin-right: 20px;
}
.service-icon {
height: 25px;
padding-right: 10px;
}
#service-switcher {
width: 7%;
transition: width 0.3s ease-in-out;
overflow: hidden;
display: flex;
}
#service-switcher:hover {
width: ${8+5*services.length}%;
}
ul.discussion-list {
overflow: auto;
max-height: 90vh;
}
.chat-row {
display: flex;
flex-direction: column;
padding: 10px 0;
border-top: 1px solid #eee;
}
.chat-content {
display: flex;
flex-direction: row;
}
.chat-row > .reply {
display: flex;
flex-direction: column;
padding-left: 55px;
border-left: 0.7px solid #eee;
}
.user-avatar {
width: 55px;
height: 55px;
margin-right: 10px;
}
.user-avatar > img {
width: 55px;
height: 55px;
object-fit: cover;
border-radius: 15px;
}
.user-msg {
display: flex;
flex-direction: column;
}
.chat-name {
font-weight: bold;
font-size: 15px;
}
.chat-time {
font-size: 12px;
font-weight: bold;
padding-top: 5px;
color: darkgrey;
}
.chat-msg {
padding: 10px 0;
}
.chat-msg img {
max-width: 100%;
}
.error-message {
color: red;
white-space: pre-wrap;
}
`;
/***************************************************************
* Initialize all data and setup menu commands
***************************************************************/
// User settings
let userSettings = UserSettings.load();
// Site instance
let site = getCurrentSite();
// Service instance
let service = services[GM_getValue("service", 0)];
/***************************************************************
* Functions for working of the script
***************************************************************/
// Returns a promise of the given element. Resolves when the element is found in the DOM.
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
let elm = document.querySelector(selector);
// console.log(`Element Found!!: ${elm.textContent}`);
return resolve(elm);
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
let elm = document.querySelector(selector);
// console.log(`Element Detected!: ${elm.textContent}`);
resolve(elm);
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
// Get the current website based on the URL
function getCurrentSite() {
const currentUrl = window.location.href.toLowerCase();
return animeSites.find((website) => website.url.some((site) => currentUrl.includes(site)));
}
// Run the script
async function run(timeout=TIMEOUT) {
// initialize the discussionArea
const discussionArea = await generateDiscussionArea();
// Fallback techniques to use when chatArea cant be detected
const selectors = [
{ selector: () => site.chatArea && typeof site.chatArea === "string" ? document.querySelector(site.chatArea) : site.chatArea(), prepend: false },
{ selector: () => document.querySelector('#main > .container'), prepend: false },
{ selector: () => document.querySelector('#footer'), prepend: true },
{ selector: () => document.querySelector('footer'), prepend: true },
{ selector: () => document.body, prepend: false },
];
for (let i = 0; i < selectors.length; i++) {
try {
const element = selectors[i].selector();
if (selectors[i].prepend) {
element.prepend(discussionArea);
} else {
element.appendChild(discussionArea);
}
break;
} catch (error) {
continue;
}
}
// Add custom css styles to the page
const styleElement = document.createElement("style");
styleElement.textContent = styles + (site.styles || '');
discussionArea.append(styleElement);
// Attach the loading element to the page
discussionArea.appendChild(setLoadingTimeout(timeout));
// Load the discussion after a set timeout
setTimeout(async () => {
try {
const discussion = await service.getDiscussion(await site.getAnimeTitle(), await site.getEpNum());
console.log(discussion);
discussion.chats.forEach(async (chat) => {
discussionArea.querySelector("ul").appendChild(await buildChatRow(chat));
});
discussionArea.querySelector(".discussion-title a").href = discussion.link;
discussionArea.querySelector(".discussion-title a").textContent = discussion.title;
} catch (error) {
console.error(`${error.message}\n\n${error.stack}`);
const errorElement = document.createElement("span");
errorElement.className = "error-message";
errorElement.textContent = `AniCHAT:\n${error.stack}\n\nCheck the console logs for more detail.`;
discussionArea.appendChild(errorElement);
}
}, timeout);
}
try {
run();
} catch (e) {
console.error(`${e.message}\n\n${e.stack}`);
}