// ==UserScript== // @name Activity Bookmark & Like History // @namespace https://github.com/KanashiiDev // @match https://anilist.co/* // @grant none // @version 1.2.1 // @author KanashiiDev // @supportURL https://github.com/KanashiiDev/Ani-ActivitySaver/issues // @description Simple userscript/extension for AniList that allows users to bookmark text activities. // @license GPL-3.0-or-later // @run-at document-end // @require https://code.jquery.com/jquery-3.3.1.min.js // @require https://cdn.jsdelivr.net/npm/dompurify@3.2.3/dist/purify.min.js // @require https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js // @require https://cdn.jsdelivr.net/npm/lz-string@1.5.0/libs/lz-string.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js // @downloadURL none // ==/UserScript== //CSS var styles = ` .activityData span.markdown_spoiler { display:block; margin:10px } .activityData span.markdown_spoiler_cont { margin-top:10px } .activityData span.markdown_spoiler_show { cursor: pointer; padding: 5px; padding-top: 3px; font-weight: 700; padding-bottom: 3px; background: rgb(var(--color-foreground)); font-size:12px; color: rgb(var(--color-blue)); -webkit-border-radius: 5px; border-radius: 5px } .activityData .button.liked { color: rgb(var(--color-red)); } .activityData .activityLinksDiv { color: rgb(var(--color-blue-dim)); position: relative; left: -webkit-calc(100% - 105px); left: calc(100% - 105px); display: -webkit-inline-box; display: -ms-inline-flexbox; display: -webkit-inline-flex; display: inline-flex; font-family: Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; font-weight: 800; } .activityData .actions { color: rgb(var(--color-blue-dim)); position: relative; left: -webkit-calc(100% - 85px); left: calc(100% - 85px); display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; width: 95px; font-family: Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; font-weight: 800; -webkit-box-pack: end; -webkit-justify-content: flex-end; -ms-flex-pack: end; justify-content: flex-end; } .activityData .action { color: rgb(var(--color-blue-dim))!important; cursor: pointer; display: inline-block; padding-left: 5px; -webkit-transition: .2s; -o-transition: .2s; transition: .2s; } .activityData .time { font: 800 1.1rem Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; color: rgb(var(--color-text-lighter)); position: relative; right: 12px; top: 12px; } .activityData .acttime { font: 800 1.1rem Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; color: rgb(var(--color-text-lighter)); margin-top:3px } .activityData .reply .actions { font-family: Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; font-weight: 700; top: 12px; position: absolute; width:125px; left: -webkit-calc(100% - 135px); left: calc(100% - 135px); -webkit-box-align: center; -ms-flex-align: center; -webkit-align-items: center; align-items: center } .activityData .reply .action { padding-left: 5px; -webkit-transition: .2s; -o-transition: .2s; transition: .2s; } .activityData .reply .action.likes { padding-right: 10px; } .activityData .reply .time { color: rgb(var(--color-text-lighter)); display: contents!important; } .activityData .reply-markdown { padding: 0px 2px } .activityData .reply-wrap .name { padding: 8px 2px } .activityData .name { margin-left: 5px; position: absolute; font-weight: bold; } .activityData .reply { margin-top: 15px; margin-bottom: 10px; background: rgba(var(--color-foreground)); -webkit-border-radius: 10px; border-radius: 10px; padding: 14px; position: relative; font-size: 1.3rem } .activityData { min-width: 100%; padding: 20px 25px 10px; margin-bottom: 15px; -webkit-border-radius: 10px; border-radius: 10px; background: rgb(var(--color-background)) } .activityInner { text-align: -webkit-center; margin-bottom:10px } .activityData img { max-width: 100%; margin-bottom:5px } .activityData blockquote { background: rgb(var(--color-foreground)) } .activityData .reply blockquote { background: rgb(var(--color-background)) } .activityDataImage { background-size: cover; background-repeat: no-repeat; display: inline-block; width: 45px; -webkit-border-radius: 5px; border-radius: 5px; margin-bottom: 10px; height: 45px } .activityDataUsername { font-weight: 700; left: 50px; top: 7px; position: relative; width: 150px; display: block; } .reply-wrap .activityDataImage { width: 30px; height: 30px } .reply-wrap .reply .header { height:40px } .activityData .saveEmbed { background: rgb(var(--color-foreground)); font-size: 12px; color: rgb(var(--color-text)); -webkit-border-radius: 3px; border-radius: 3px; display: -ms-inline-grid; display: inline-grid; grid-auto-flow: column; -ms-grid-columns: 50px; grid-template-columns: 50px; justify-items: center; } .activityData .reply-markdown .markdown{ overflow:hidden!important } .activityData .reply-markdown .saveEmbed, .activityData blockquote span.markdown_spoiler_show, .activityData blockquote .saveEmbed{ background: rgb(var(--color-background)) } #removereply:hover, #editreply:hover, .activityData .action:hover, .activityData .activityLink:hover{ color: rgb(var(--color-blue))!important; } .activityData .saveEmbed b { display: -ms-grid; display: grid; word-break: break-word; margin: 3px; padding: 10px; justify-items: center; line-height:18px } .activityData .saveEmbed .cover { background-repeat: no-repeat; background-position: 50%; background-size: cover; height: 100%; width: 100%; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; } .saveEmbedDetails { font-size: 1rem; display: -webkit-inline-box; display: -ms-inline-flexbox; display: -webkit-inline-flex; display: inline-flex; color: rgb(var(--color-text-light))!important; pointer-events: none; } .activityData a, .activityData a.embedLink a.saveEmbed{ color: rgb(var(--color-blue)); } .activityData a[href^="https://anilist.co/manga/"], .activityData a.embedLink[href^="https://anilist.co/manga/"] a.saveEmbed{ color: rgb(var(--color-green)); } .activityDatauserdiv { width: 100%; display: -webkit-inline-box; display: -ms-inline-flexbox; display: -webkit-inline-flex; display: inline-flex; } .activityLink { padding-left: 15px; height: 0; display: inline-block; position: relative; cursor: pointer; color: rgba(var(--color-text))!important; } .saveActivity { -webkit-box-align: center; -ms-flex-align: center; -webkit-align-items: center; align-items: center; display: -ms-grid; display: grid; grid-gap: 8px; -ms-grid-columns: 20px 8px 1fr; grid-template-columns: 20px 1fr; padding: 2px 12px; padding-right: 17px; } .mainbtn { list-style: none; cursor: pointer; color: rgb(var(--color-text)); } .mainbtns { font: 900 1.3rem Overpass,-apple-system,BlinkMacSystemFont,"Segoe UI",Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",sans-serif; -webkit-transition: .25s; -o-transition: .25s; transition: .25s; border: 0; -webkit-border-radius: 4px; border-radius: 4px; padding: 4px; margin: 4px; text-align:center; cursor: pointer; background: rgb(var(--color-background)); color: rgb(var(--color-text)); } .btn-active { background: #28384d; color: #9fadbd; } .mainbtns:active { background: rgba(40,56,77); } .mainbtns:hover { color: rgb(var(--color-blue)); } .maindiv { width: 100%; -webkit-transition: 1s; -o-transition: 1s; transition: 1s; position: relative; background: rgb(var(--color-foreground)); overflow-y: auto; display: -ms-grid; display: grid; color: rgb(var(--color-text)); padding: 10px; padding-bottom: 0; margin-right: 10px; margin-bottom: 20px; border: 1px solid #6969694d; -webkit-border-radius: 10px; border-radius: 10px; } .expanded { margin-top: 10px; } .expanded2 { max-height: -webkit-calc(95vh - 100px) !important; max-height: calc(95vh - 100px) !important } @media (max-width: 1200px) { .expanded .activityDataDiv { max-height: -webkit-calc(90vh - 65px) !important; max-height: calc(90vh - 65px) !important } } @media (max-width: 980px) { .expanded .activityDataDiv { max-height: -webkit-calc(90vh - 25px) !important; max-height: calc(90vh - 25px) !important } } @media (max-width: 480px) { .expanded .activityDataDiv { max-height: -webkit-calc(90vh - 35px) !important; max-height: calc(90vh - 35px) !important } } .ResultDivInside { overflow-y: auto; -webkit-border-radius: 10px; border-radius: 10px; padding: 10px; padding-top: 20px; padding-bottom: 0; margin-top: 10px; margin-bottom: 10px; } .activityDataDiv { display: -ms-grid; display: grid; max-height: -webkit-calc(90vh - 100px); max-height: calc(90vh - 100px); overflow-y: auto; padding-top: 10px; margin-top: 3px; } @media (max-width: 1200px) { .activityDataDiv { max-height: -webkit-calc(90vh - 120px); max-height: calc(90vh - 120px); } } @media (max-width: 768px) { .activityDataDiv { max-height: -webkit-calc(90vh - 80px); max-height: calc(90vh - 80px); } } @media (max-width: 480px) { .activityDataDiv { max-height: -webkit-calc(90vh - 90px); max-height: calc(90vh - 90px); } } .activityDataDiv .loadMoreButton { margin: 10px auto; display: block; padding: 10px 20px; font-size: 16px; font-weight: bold; cursor: pointer; width: 97%; text-align: center; -webkit-border-radius: 10px; border-radius: 10px; background: rgb(13 21 34) } .ResultDivInside, .activityDataDiv { -webkit-mask-image: -webkit-gradient(linear,left top, left bottom,color-stop(0, transparent),color-stop(black),color-stop(black),to(transparent)); -webkit-mask-image: linear-gradient(to bottom,transparent 0,black var(--top-mask-size),black -webkit-calc(100% - var(--bottom-mask-size)),transparent 100%); mask-image: -webkit-gradient(linear,left top, left bottom,color-stop(0, transparent),color-stop(black),color-stop(black),to(transparent)); mask-image: linear-gradient(to bottom,transparent 0,black var(--top-mask-size),black calc(100% - var(--bottom-mask-size)),transparent 100%); --bottom-mask-size: 10px; --top-mask-size: 10px; } #settingDiv { top:5px; padding-bottom: 5px; padding-top: 5px; margin-bottom:5px; -ms-grid-columns: 1fr 1fr; grid-template-columns: 1fr 1fr; display: -ms-grid; display: grid } #settingDiv .settingsText, #settingDiv #resetDbBtn { grid-column: 1 / -1 } button#removereply, button#expandbtn, button#settingsbtn, button#closedivbtn{ position: absolute; right: 0; top:4px; background:transparent } button#settingsbtn { right:20px } button#expandbtn { right:40px } #importBtn{ -moz-text-align-last:center; text-align-last:center } .reply-wrap .replybutton { -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; background: rgb(var(--color-blue)); -webkit-border-radius: 4px; border-radius: 4px; color: rgb(var(--color-text-bright)); cursor: pointer; display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; font-family: Overpass,-apple-system,BlinkMacSystemFont,Segoe UI,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; font-size: 1.3rem; font-weight: 900; margin-left: 18px; padding: 10px 15px; -webkit-transition: .2s; -o-transition: .2s; transition: .2s; } .reply-wrap .replybutton .cancel { background: rgb(var(--color-foreground)); color: rgb(var(--color-text-lighter)); } .reply-wrap textarea{width: 96%;border-width: 1px;font-size: 1.3rem;padding: 8px 15px;resize: none;min-height: 36px;-webkit-border-radius: 5px;border-radius: 5px;} #removereply,#editreply{visibility:hidden;-webkit-transition:0.5s;-o-transition:0.5s;transition:0.5s} .reply-wrap .header:hover #removereply, .reply-wrap .header:hover #editreply { visibility: visible!important; } `; //Add CSS var styleSheet = document.createElement('style'); styleSheet.innerText = styles; document.head.appendChild(styleSheet); //Simple Create Element Shorthand Function function create(e, t, n) { if (!e) throw SyntaxError("'tag' not defined"); var r, i, f = document.createElement(e); if (t) for (r in t) if ('style' === r) for (i in t.style) f.style[i] = t.style[i]; else t[r] && f.setAttribute(r, t[r]); return n && (f.innerHTML = n), f; } //Create Element Function 2 function create2(t, e, a, s, n) { let i = document.createElement(t); return ( Array.isArray(e) ? (i.classList.add(...e), e.includes('newTab') && i.setAttribute('target', '_blank')) : e && ('#' === e[0] ? (i.id = e.substring(1)) : (i.classList.add(e), 'newTab' === e && i.setAttribute('target', '_blank'))), (a || 0 === a) && (i.innerText = a), s && s.appendChild && s.appendChild(i), n && (i.style.cssText = n), i ); } //Set Element Function function set(t, e, n) { if (!t) throw new SyntaxError("'tag' not defined"); var r, i, f = t; if (e) for (r in e) if ('style' === r) for (i in e.style) f.style[i] = e.style[i]; else e[r] && f.setAttribute(r, e[r]); return n && (f.innerHTML = n), f; } // Improved Element Functions const ElementPrototype = Element.prototype; const { hasOwn, setPrototypeOf, isFrozen, getPrototypeOf, getOwnPropertyDescriptor } = Object; const cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); function lookupGetter(obj, prop) { while (obj !== null) { const descriptor = getOwnPropertyDescriptor(obj, prop); if (descriptor) { if (descriptor.get) return unapply(descriptor.get); if (typeof descriptor.value === 'function') return unapply(descriptor.value); } obj = getPrototypeOf(obj); } return (fallbackArg) => { console.warn('Fallback value for', fallbackArg); return null; }; } function hasOwnPropertyCompat(obj, prop) { return hasOwn ? hasOwn(obj, prop) : Object.prototype.hasOwnProperty.call(obj, prop); } function unapply(fn) { return function (context, ...args) { return fn.apply(context, args); }; } // Delay Function const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Time Function for Activities function nativeTimeElement(timestamp) { const date = new Date(timestamp * 1000); const timeElement = create2('time', 'activitytime'); timeElement.setAttribute('datetime', date.toISOString()); timeElement.title = date.toLocaleString(); const updateTimeText = () => { const now = Math.round(Date.now() / 1000); let elapsed = now - Math.round(date.getTime() / 1000); if (elapsed === 0) { timeElement.innerText = 'Now'; } else if (elapsed === 1) { timeElement.innerText = '1 second ago'; } else if (elapsed < 60) { timeElement.innerText = `${elapsed} seconds ago`; } else if ((elapsed = Math.floor(elapsed / 60)) === 1) { timeElement.innerText = '1 minute ago'; } else if (elapsed < 60) { timeElement.innerText = `${elapsed} minutes ago`; } else if ((elapsed = Math.floor(elapsed / 60)) === 1) { timeElement.innerText = '1 hour ago'; } else if (elapsed < 24) { timeElement.innerText = `${elapsed} hours ago`; } else if ((elapsed = Math.floor(elapsed / 24)) === 1) { timeElement.innerText = '1 day ago'; } else if (elapsed < 7) { timeElement.innerText = `${elapsed} days ago`; } else if (elapsed < 14) { timeElement.innerText = '1 week ago'; } else if (elapsed < 30) { timeElement.innerText = `${Math.floor(elapsed / 7)} weeks ago`; } else if (elapsed < 365) { const months = Math.floor(elapsed / 30); timeElement.innerText = months === 1 ? '1 month ago' : `${months} months ago`; } else { const years = Math.floor(elapsed / 365); timeElement.innerText = years === 1 ? '1 year ago' : `${years} years ago`; } // Schedule next update setTimeout(() => { if (document.body.contains(timeElement)) updateTimeText(); }, 20000); }; updateTimeText(); return timeElement; } //Showdown Youtube Extension 1.2.1 //https://github.com/showdownjs/youtube-extension //Changed some regex codes !(function (e) { 'use strict'; if ('undefined' != typeof showdown) e(showdown); else if ('function' == typeof define && define.amd) define(['showdown'], e); else { if ('object' != typeof exports) throw Error('Could not find showdown library'); module.exports = e(require('showdown')); } })(function (e) { 'use strict'; var t = /(?:
)?'
: ''),
e.replace(t, function (e, t, s) {
var d,
f = (function (e, t) {
var o, i, r, s, n;
return (
(s = t.youtubeWidth ? t.youtubeWidth : 420),
(n = t.youtubeHeight ? t.youtubeHeight : 315),
e && ((o = (r = /width="(.+?)"/.exec(e)) ? r[1] : s), (i = (r = /height="(.+?)"/.exec(e)) ? r[1] : n)),
/^\d+$/gm.exec(o) && (o += 'px'),
/^\d+$/gm.exec(i) && (i += 'px'),
{ width: o, height: i }
);
})(s, n),
h = '';
if ((d = i.exec(t)) || (d = o.exec(t))) h = 'https://www.youtube.com/embed/' + d[1] + '?rel=0';
else {
if (!(d = r.exec(t))) return e;
h = 'https://player.vimeo.com/video/' + d[1];
}
return l.replace(/%1/g, h).replace('%2', f.width).replace('%3', f.height);
})
);
},
},
];
});
});
/*! showdown Options. */
showdown.setOption('strikethrough', true);
showdown.setOption('ghMentions', true);
showdown.setOption('emoji', true);
showdown.setOption('tables', true);
showdown.setOption('simpleLineBreaks', true);
showdown.setOption('simplifiedAutoLink', true);
showdown.setOption('noHeaderId', true);
showdown.setOption('omitExtraWLInCodeBlocks', true);
showdown.setOption('ghMentionsLink', 'https://anilist.co/user/{u}/');
showdown.setOption('youtubeHeight', '300px');
showdown.setOption('youtubeWidth', '300px');
showdown.setOption('openLinksInNewWindow', true);
const converter = new showdown.Converter({ extensions: ['youtube'] });
//make HTML
let makeHtml = function (e) {
let htmlPreserveRegex = /<\/?(h[1-6]|a|p|div|span|b|i|u|center|blockquote|h5|h4)[^>]*>/gi;
let preservedHtml = [];
e = e.replace(htmlPreserveRegex, (match) => {
preservedHtml.push(match);
return `oWoHTML${preservedHtml.length - 1}oWo`;
});
let t = (e = e.replace('----', '---')).split('~~~');
let r = /img(\d+%?)?\(http.+?\)/gi;
t = t.map((e) => {
let t = e.match(r);
return (
t &&
t.forEach((t) => {
let r = t.match(/^img(\d+%?)?\((http.+?)\)$/i);
if (r) {
e = e.replace(
t,
`
`
);
}
}),
e
);
});
let a = /webm\(http.+?\)/gi;
t = t.map((e) => {
let t = e.match(a);
return (
t &&
t.forEach((t) => {
let r = t.match(/^webm\((http.+?)\)$/i);
if (r) {
e = e.replace(
t,
``
);
}
}),
e
);
});
let c = /youtube\(.+?\)/gi;
t = t.map((e) => {
let t = e.match(c);
return (
t &&
t.forEach((t) => {
let r = t.match(/^youtube\((.+?)\)$/i);
if (r) {
e = e.replace(t, `${r[1]}`);
}
}),
e
);
});
let l = [t[0]];
let m = false;
for (let n = 1; n < t.length; n++) {
l.push(m ? '' : '
' + '$1')
.replace(/(?<=[^_]|^)__([^_].*?[^_]*)__/gm, '' + '$1' + '')
.replace(/(?<=[^_]|^)_([^_].*?[^_]*)_/gm, '' + '$1' + '')
.replace(/^(?<=[^#]|^)#####([^#\n].*?[^\n]*)/gm, '' + '$1' + '
')
.replace(/^(?<=[^#]|^)####([^#\n].*?[^\n]*)/gm, '' + '$1' + '
')
.replace(/^(?<=[^#]|^)###([^#\n].*?[^\n]*)/gm, '' + '$1' + '
')
.replace(/^(?<=[^#]|^)##([^#\n].*?[^\n]*)/gm, '' + '$1' + '
')
.replace(/^(?<=[^#]|^)#([^#\n].*?[^\n]*)/gm, '' + '$1' + '
')
.replace(/(?<=[^`]|^)\`([^`].*?[^`]*)\`/gm, '' + '$1' + '
');
const finalText = processedText.replace(/oWoLINKTEMP(\d+)oWo/g, (_, index) => links[index])
.replace(/\[(.+?)\]\((.+?)\)/g, '' + '$1' + '')
.replace(/\[(.+?)\]\(\)/g, '' + '$1' + '')
.replace(/(?<=[^~]|^)\~\~([^~].*?[^~]*)\~\~/gm, '' + '$1' + '')
.replace(/youtube\(+((?!https:).*).*\)/gim, ' youtube(https://www.youtube.com/watch?v=$1)')
.replace(/youtube.(h).((.*?)\))/gi, ' 
.replace(/(?')
.replace(/^(?!>|#)\n/gm, '\n\n');
return finalText;
}
// Optimize HTML Saved Activity
async function actHTMLFix(text) {
let actFixedText = '';
const actTextToFix = await processMarkdown(text);
const sanitizedText = DOMPurify.sanitize(actTextToFix);
actFixedText = await makeHtml(actTextToFix);
actFixedText = actFixedText
.replace(/\n/gm, '
')
.replace(/(\
\s*){2,99}/gm, '
')
.replace(/\
/gm,'')
.replace(/<\/p>
/gm, '
/gm, '
\n') .replace(/
/, ''); return actFixedText; } //Anilist Query const requestQueue = []; let isProcessing = false; async function processQueue() { if (isProcessing || requestQueue.length === 0) { return; } isProcessing = true; while (requestQueue.length > 0) { const { query, variables, options, resolve, reject } = requestQueue.shift(); try { const result = await alQueryInternal(query, variables, options); resolve(result); } catch (error) { reject(error); } // Delay between requests await delay(1000); } isProcessing = false; } async function alQueryInternal(query, variables, options = {}) { const maxRetries = 3; const fetchOptions = { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", ...options.headers, // Merge any additional headers }, body: JSON.stringify({ query, variables }), }; for (let i = 0; i < maxRetries; i++) { const response = await fetch("https://graphql.anilist.co", fetchOptions); if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000; // More reasonable default console.warn( `Rate limit exceeded. Retrying after ${waitTime / 1000} seconds.` ); await delay(waitTime); continue; } else if (!response.ok) { if (response.status === 404 ) { await removeActivity(variables.id); await removeActivity(variables.id,'likeHistory'); } else { throw new Error( `Error: ${response.status} ${response.statusText} (query: ${query}, variables: ${JSON.stringify( variables )})` ); } } else { return response.json(); } } } function alQuery(query, variables, options = {}) { return new Promise((resolve, reject) => { requestQueue.push({ query, variables, options, resolve, reject }); processQueue(); }); } // Anilist - AuthAPIQuery let API_LIMIT = 90; let apiCallsUsed = 0; let apiCallsUsedShortTerm = 0; const pending = {}; // Reset API call counters periodically const resetApiCalls = () => (apiCallsUsed = 0); const resetApiCallsShortTerm = () => (apiCallsUsedShortTerm = 0); setInterval(resetApiCalls, 60000); // Reset every 60 seconds setInterval(resetApiCallsShortTerm, 10000); // Reset every 10 seconds // Handle API response const handleResponse = async (response) => { apiCallsUsed = (API_LIMIT = response.headers.get("x-ratelimit-limit")) - response.headers.get("x-ratelimit-remaining"); try { const data = await response.json(); return response.ok ? data : Promise.reject(data); } catch (error) { console.warn("Error parsing response: ", error, response); throw error; } }; // Perform authenticated API call function authAPIcall(query, variables, callback, cacheKey, timeFresh, useLocalStorage, overwriteCache, oldCallback) { if (!accessToken) return; // Check rate limits if (apiCallsUsedShortTerm > 18 || apiCallsUsed > API_LIMIT - 2) { setTimeout(() => { authAPIcall(query, variables, callback, cacheKey, timeFresh, useLocalStorage, overwriteCache, oldCallback); }, 2000); return; } // Cache handling if (cacheKey) { const storage = useLocalStorage ? localStorage : sessionStorage; const cachedData = JSON.parse(storage.getItem(cacheKey)); if (cachedData) { const isFresh = !cachedData.duration || NOW() < cachedData.time + cachedData.duration; if (isFresh && !overwriteCache) { callback(cachedData.data, variables); return; } if (oldCallback) oldCallback(cachedData.data, variables); storage.removeItem(cacheKey); } } // Success handler const handleSuccess = (data, error = null) => { callback(data, variables, error); if (cacheKey) { const storage = useLocalStorage ? localStorage : sessionStorage; const cachedEntry = JSON.stringify({ data, time: NOW(), duration: timeFresh }); storage.setItem(cacheKey, cachedEntry); } }; // Request setup const requestConfig = { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ query, variables }), }; // Error handler const handleError = (errorResponse) => { if (errorResponse.status === 429) { // API rate limit exceeded, retry after specified time const retryAfter = errorResponse.headers.get("Retry-After"); const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000; // Default to 60 seconds console.warn(`Rate limit exceeded. Retrying after ${waitTime / 1000} seconds.`); setTimeout(() => { authAPIcall(query, variables, callback, cacheKey, timeFresh, useLocalStorage, overwriteCache, oldCallback); }, waitTime); return; } console.error("Error: ", errorResponse); // Handle expired token if (errorResponse.errors?.some((err) => err.message === "Invalid token")) { const loginLink = create( "a", { class: "mainbtns", id: "signIn", href: "https://anilist.co/api/v2/oauth/authorize?client_id=12455&response_type=token", }, "Error: Expired Token. Click here to renew token." ); listDiv2.insertBefore(loginLink, listDiv2.children[1]); accessToken = ""; localStorage.setItem("savetkn", accessToken); return; } if (query.includes("mutation")) { callback(errorResponse.errors); } else { handleSuccess(null, errorResponse); } }; // Execute fetch request fetch("https://graphql.anilist.co", requestConfig) .then(handleResponse) .then((data) => handleSuccess(data)) .catch(handleError); // Update API call counters apiCallsUsed++; apiCallsUsedShortTerm++; } //SVG const svgns = 'http://www.w3.org/2000/svg', svgShape = function (e, t, a, c, r) { e = e || 'g'; let l = document.createElementNS('http://www.w3.org/2000/svg', e); return ( Object.keys(a || {}).forEach((e) => { l.setAttributeNS(null, e, a[e]); }), r && l.appendChild(document.createTextNode(r)), t && t.appendChild(l), (c || []).forEach((e) => { e.element ? svgShape(e.element, l, e.attributes, e.children, e.content) : l.appendChild(e); }), l ); }, svg = {}; [ { name: 'pinned', shape: { element: 'svg', attributes: { focusable: 'false', 'data-prefix': 'fas', 'data-icon': 'thumbtack', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 384 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', 'aria-hidden': 'true', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M298.028 214.267L285.793 96H328c13.255 0 24-10.745 24-24V24c0-13.255-10.745-24-24-24H56C42.745 0 32 10.745 32 24v48c0 13.255 10.745 24 24 24h42.207L85.972 214.267C37.465 236.82 0 277.261 0 328c0 13.255 10.745 24 24 24h136v104.007c0 1.242.289 2.467.845 3.578l24 48c2.941 5.882 11.364 5.893 14.311 0l24-48a8.008 8.008 0 0 0 .845-3.578V352h136c13.255 0 24-10.745 24-24-.001-51.183-37.983-91.42-85.973-113.733z', }, }, ], }, }, { name: 'likeNative', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'heart', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 512 512', class: 'svg-inline--fa fa-heart fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z', }, }, ], }, }, { name: 'reply', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'comments', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 576 512', class: 'svg-inline--fa fa-comments fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160zm122 220c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z', }, }, ], }, }, { name: 'expand', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'expand', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 448 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z', }, }, ], }, }, { name: 'compress', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'compress', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 448 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z', }, }, ], }, }, { name: 'link', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'link', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 512 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z', }, }, ], }, }, { name: 'xmark', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'link', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 55 400 400', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z', }, }, ], }, }, { name: 'gear', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'link', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 512 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z', }, }, ], }, }, { name: 'edit', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'link', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 512 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z', }, }, ], }, }, { name: 'cross', shape: { element: 'svg', attributes: { 'aria-hidden': 'true', 'data-prefix': 'fas', 'data-icon': 'cross', role: 'img', xmls: 'http://www.w3.org/2000/svg', viewBox: '0 0 448 512', class: 'svg-inline--fa fa-link fa-w-16 fa-sm', }, children: [ { element: 'path', attributes: { fill: 'currentColor', d: 'M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z', }, }, ], }, }, ].forEach((e) => { svg[e.name] = svgShape(e.shape.element, !1, e.shape.attributes, e.shape.children, e.shape.content); }); //MAIN let auth; let user = ''; let userid = 0; try { auth = JSON.parse(localStorage.getItem('auth')); } catch (e) { console.warn('could not get auth'); } if (auth) (user = auth.name), (userid = auth.id); else try { user = document.querySelector(".nav .links .link[href^='/user/']").href.match(/\/user\/(.*)\//)[1]; } catch (e) { alert('Please login before to use -Activity Saver- script.'); } //Variables let username = String(user); let usernameurl = String('https://anilist.co/user/' + user + '/'); var val = 0; let currentIndex = 0; const itemsPerLoad = 2; var active = false; var mainArray = []; var likeArray = []; let likeHistory = false; let onMainDiv = false; var autosave = false; var expanded = false; var settings = false; var autosaveLikes = false; var canRemoveActivity = true; var isLoading = false; var oldHref = document.location.href; interval = null; var button = create('li',{class: 'el-dropdown-menu__item mainbtn',id: 'Saved Activities',},'Saved Activities'); var button2 = create('li',{class: 'el-dropdown-menu__item mainbtn',id: 'History',},'Like History'); const loadMoreButton = create("a", {class: "loadMoreButton"}, "Load More"); //Button onclicks loadMoreButton.addEventListener("click", async () => { if(loadMoreButton.textContent === "Load More") { loadMoreButton.textContent = "Loading..."; canRemoveActivity = false; await loadActivities(); await delay(500); loadMoreButton.textContent = "Load More"; canRemoveActivity = true; } }); button.onclick = () => { if (!isLoading) { isLoading = true; likeArray = []; likeHistory = false; currentIndex = 0; getSavedDiv(button); } }; button2.onclick = async () => { if (!isLoading) { isLoading = true; currentIndex = 0; likeArray = await mainDB.getItem('likeHistory') || []; likeHistory = true; if(likeArray.length > 0) getSavedDiv(button2); } }; var accessToken = ''; // Check Token Function function checkToken() { let currentURL = ''; const mainLoop = setInterval(() => { if (document.URL !== currentURL) { currentURL = document.URL; // Check if the URL contains the access token if (/^https:\/\/anilist\.co\/home#access_token/.test(currentURL)) { const tokenList = location.hash.split('&').map(param => param.split('=')); accessToken = tokenList[0][1]; // Save the token to local storage localStorage.setItem('savetkn', LZString.compressToBase64(JSON.stringify(accessToken))); // Redirect to the base URL to remove the access_token from the hash location.replace(`${location.protocol}//${location.hostname}${location.pathname}`); } } }, 200); } checkToken(); // Check Local Storage for Token function loadTokenFromLocalStorage() { const savedToken = localStorage.getItem('savetkn'); if (savedToken) { const decompressedToken = LZString.decompressFromBase64(savedToken); if (decompressedToken && decompressedToken.length > 10) { accessToken = JSON.parse(decompressedToken); } else { localStorage.removeItem('savetkn'); } } } loadTokenFromLocalStorage(); //LocalForage DB let mainDB = localforage.createInstance({ name: "Anilist-Activity-Saver", storeName: "main" }); //Add Buttons to Filters function addToFilters() { if (!/^\/home\/?([\w-]+)?\/?$/.test(location.pathname)) { return; } const checkForFilters = () => { const filters = document.querySelector('.el-dropdown-menu:not(.details *):not(.time *):not(.actions *)'); if (!filters) { setTimeout(checkForFilters, 100); return; } if (filters.children[0].innerText.trim() === 'All') { filters.append(button, button2); set(button, { class: 'el-dropdown-menu__item' }); set(button2, { class: 'el-dropdown-menu__item' }); } }; checkForFilters(); const autosaved = JSON.parse(localStorage.getItem('actautosave')); const autosaveEnabled = autosaved && accessToken.length > 5; if (autosaved && accessToken.length > 5) { autosave = true; } else { autosave = false; } addSavetoActivities(); } addToFilters(); //Build Activities let activitiesArray = ''; async function buildActivity() { let activityDataDiv = document.getElementById('activityDataDiv'); let savedActivities = await mainDB.getItem('savedActivities'); if(savedActivities) { const filteredData = savedActivities.filter(item => item !== ''); mainDB.setItem('savedActivities', filteredData); } if (activityDataDiv) { activityDataDiv.innerHTML = ''; } if (autosave) { await autoSaveActivity(); await buildActivities(); } else { if (savedActivities !== null) { activitiesArray = savedActivities; await buildActivities(); } } isLoading = false; } async function buildActivities() { let mainActDiv = document.querySelector("#activityDataDiv"); const listLoading = create("div", { class: "listLoading", style: { position: 'absolute', fontSize: '16px', MsGridColumnAlign: 'center', justifySelf: 'center', WebkitAlignSelf: 'center', MsFlexItemAlign: 'center', MsGridRowAlign: 'center', alignSelf: 'center', }, }, "Loading" + ''); mainActDiv.style.opacity = 0; document.querySelector("#activityDataDiv").parentElement.append(listLoading); // İlk 5 aktiviteyi yükle await loadActivities(); mainActDiv.style.opacity = 1; listLoading.remove(); } //Load Activities async function loadActivities() { const endIndex = currentIndex + itemsPerLoad; let arr = likeArray.length > 0 ? likeArray : activitiesArray; const currentBatch = arr.slice(currentIndex, endIndex); for (let activityId of currentBatch) { await delay(500); await getActivity(activityId); } currentIndex = endIndex; if (currentIndex >= arr.length) { loadMoreButton.remove(); } else { document.querySelector("#activityDataDiv").append(loadMoreButton); } } //AutoSave Function async function autoSaveActivity(opt) { if (autosave && accessToken.length > 5) { canRemoveActivity = false; let userAboutData = ''; let activitiesIdArray = await mainDB.getItem('savedActivities'); const query = `query($userName: String) { User(name: $userName) { about } }`; const variables = {userName: username,}; await alQuery(query, variables).then( await checkAbout); async function checkAbout(data) { userAboutData = data.data.User.about; let jsonMatch = (userAboutData || '').match(/(\[\]\(actjson)([A-Za-z0-9+\/=]+)(\))/); if (jsonMatch) { jsonMatch = jsonMatch[0].replace(/(\[\]\(actjson)([A-Za-z0-9+\/=]+)(\))/, '$2'); let decompressedData = JSON.parse(LZString.decompressFromBase64(jsonMatch)); let values = Object.values(decompressedData); let apiArray = JSON.stringify(values).replace(/\\*"|\[|\]/g, '').split(/[.,!?]/); if (!opt && apiArray.length > 0 && (!activitiesIdArray || activitiesIdArray.length < 1)) { await mainDB.setItem('savedActivities', apiArray); } if(opt) { await updateAbout(); } } if (activitiesIdArray && activitiesIdArray.length > 0) { await updateAbout(); activitiesArray = JSON.stringify(activitiesIdArray).replace(/\\*"|\[|\]/g, '').split(/[.,!,?]/); } canRemoveActivity = true; } async function updateAbout(data) { let jsonMatch = userAboutData.match(/(\[\]\(actjson)([A-Za-z0-9+\/=]+)(\))/); let customcssmatch = userAboutData.match(/\[\]\(json([A-Za-z0-9+\/=]+)\)/); let customcssmatched = customcssmatch ? customcssmatch[0] : ''; activitiesIdArray = JSON.stringify(activitiesIdArray); let profileJson = { activitiesIdArray }; let base64Data = LZString.compressToBase64(JSON.stringify(profileJson)); let newDescription = ''; if (jsonMatch) { userAboutData = userAboutData.replace(customcssmatched, ''); newDescription = customcssmatched + userAboutData.replace(/(\[\]\(actjson)([A-Za-z0-9+/=]+)(\))/,'$1' + base64Data + '$3'); } else { if (customcssmatched) { userAboutData = userAboutData.replace(customcssmatched, ''); newDescription = userAboutData.replace(customcssmatched, customcssmatched + '[](actjson' + base64Data + ')'); } else { newDescription = '[](actjson' + base64Data + ')' + userAboutData; } } await authAPIcall(`mutation($about: String) { UpdateUser(about: $about) { about } }`,{ about: newDescription },function (data) { if (!data) { console.error('Failed to update user data.'); return; } } ); } } } //Save Activity Button Function function addSavetoActivities(type) { let ActivitySave = false; if (!/^\/(home|user|activity)\/?([\w-]+)?\/?$/.test(location.pathname)) { return; } setTimeout(addSavetoActivities, 500); let activityCollection = document.querySelectorAll('.activity-extras-dropdown'); activityCollection.forEach(function (activity) { if (!hasOwn(activity, 'ActivitySave')) { activity.ActivitySave = true; let activitySave = create( 'a', { dataIcon: 'link', class: 'saveActivity el-dropdown-menu__item', id: 'saveActivity', }, 'Save Activity', ); activitySave.onclick = async function () { let el = activitySave; let id = el.parentElement.children[0].href.split('/')[4]; el.click(); el.click(); el.onclick = async () => { el.lastElementChild.innerText = 'Saved!'; let activitiesIdArray = await mainDB.getItem('savedActivities'); if(!Array.isArray(activitiesIdArray)) { await mainDB.setItem('savedActivities', []); } let mainArray = activitiesIdArray ? activitiesIdArray : []; if (mainArray.includes(id)) { el.lastElementChild.innerText = 'Already Saved!'; return; } mainArray.push(id); await mainDB.setItem('savedActivities', mainArray); let activityDataDiv = document.getElementById('activityDataDiv'); if (activityDataDiv) { buildActivity(); } }; }; if (activity.closest('.activity-text') || activity.closest('.activity-message')) { activity.append(activitySave); activitySave.insertBefore(svg.pinned.cloneNode(true), activitySave.children[0]); } } }); let likeButtonCollection = document.querySelectorAll('.activity-text .actions .button'); likeButtonCollection.forEach((button) => { if (!hasOwn(button, 'ActivityHistory')) { button.ActivityHistory = true; button.addEventListener('click', async function () { try { const buttonId = button.getAttribute('id'); const isMainDiv = $(this).attr('mainDiv'); const id = buttonId ? buttonId : $(this).closest('.activity-text').find('.el-dropdown-menu__item:contains("Direct Link")')?.attr('href').split('/')[2]; let isLiked = $(this).hasClass('liked'); if (isMainDiv) { isLiked = !isLiked; } if (!id) { return; } else { button.setAttribute('id', id); } let mainArray = await mainDB.getItem('likeHistory') || []; if(!Array.isArray(mainArray)) { await mainDB.setItem('likeHistory', []); } if (mainArray.includes(id)) { if (isLiked) { if (typeof mainArray === "string") { mainArray = JSON.parse(mainArray); } mainArray = mainArray.map(item => String(item).trim()); // Find and remove the ID const index = mainArray.indexOf(id); if (index !== -1) { mainArray.splice(index, 1); } // Update the mainArray and save back to the database mainArray = mainArray; await mainDB.setItem("likeHistory", mainArray); } return; } mainArray = [id, ...mainArray]; await mainDB.setItem('likeHistory', mainArray); } catch (error) { console.error('An error occurred while saving liked activity', error); } }); }; }); } //Create Main Div function creatediv(btn) { btn.setAttribute('class', 'el-dropdown-menu__item active'); var listDiv2 = create( 'div', { class: 'maindiv', id: 'listDiv2', }, '' + btn.innerText + '', ); let expandbtn = create('button', { class: 'mainbtns', id: 'expandbtn', }); let settingsbtn = create('button', { class: 'mainbtns', id: 'settingsbtn', }); let closebtn = create('button', { class: 'mainbtns', id: 'closedivbtn', }); expandbtn.insertBefore(svg.expand.cloneNode(true), expandbtn.children[0]); settingsbtn.insertBefore(svg.gear.cloneNode(true), settingsbtn.children[0]); closebtn.insertBefore(svg.xmark.cloneNode(true), closebtn.children[0]); expandbtn.onclick = () => { expandDiv(); }; settingsbtn.onclick = () => { settingsDiv(); }; closebtn.onclick = () => { closeDiv(); }; var list = document.querySelector('.activity-feed-wrap + div'); listDiv2.append(expandbtn, settingsbtn, closebtn); list.insertBefore(listDiv2, list.children[0]); var activityDataDiv = create('div', { class: 'activityDataDiv', id: 'activityDataDiv', }); listDiv2.appendChild(activityDataDiv); if (!accessToken) { let loginLink = create( 'a', { class: 'mainbtns', id: 'signIn', href: 'https://anilist.co/api/v2/oauth/authorize?client_id=12455&response_type=token', }, 'Sign In', ); listDiv2.insertBefore(loginLink, listDiv2.children[1]); } } //Export Activities function saveAs(data, fileName, pureText) { let link = create2('a'); document.body.appendChild(link); let json = pureText ? data : JSON.stringify(data); let blob = new Blob([json], { type: 'octet/stream', }); let url = window.URL.createObjectURL(blob); link.href = url; link.download = fileName; link.click(); window.URL.revokeObjectURL(url); document.body.removeChild(link); } //Settings Div function settingsDiv() { settings = !settings; if (settings) { let settingDiv = create('div', {class: 'maindiv', id: 'settingDiv', }, 'Settings'); let importBtn = create('input', {class: 'mainbtns', id: 'importBtn', type: 'button', value: 'Import Saved Activites'}); let exportBtn = create('a', {class: 'mainbtns', id: 'exportBtn',},'Export Saved Activites'); let exportHistoryBtn = create('a', {class: 'mainbtns', id: 'exportHistoryBtn',},'Export Like History'); let importHistoryBtn = create('input', {class: 'mainbtns', id: 'importHistoryBtn', type: 'button', value: 'Import Like History'}); let clearHistoryBtn = create('a', {class: 'mainbtns', id: 'clearHistoryBtn',},'Clear Like History'); let autoSaveBtn = create('a', {class: 'mainbtns', id: 'autoSaveBtn',},'Auto Backup Activities to Profile'); let resetDbBtn = create('a', {class: 'mainbtns', id: 'resetDbBtn',},'Reset Databases'); //Export Saved Activities Button Onclick exportBtn.onclick = async function () { let savedArray = await mainDB.getItem('savedActivities'); let export_activities = '[](actjson' + LZString.compressToBase64(JSON.stringify(savedArray)) + ')'; if (username && savedArray !== null && savedArray.length > 0) { saveAs(export_activities, 'ActivityBookmark' + '_SavedActivities_' + username + '.json'); } else { exportBtn.innerText = 'Error: The list is empty.'; delay(3000).then(() => { exportBtn.innerText = 'Export Saved Activities'; }); } }; //Import Saved Activities Button Onclick importBtn.onclick = async function () { importBtn.setAttribute('type', 'file'); importBtn.setAttribute('name', 'json'); importBtn.setAttribute('accept', 'application/json'); importBtn.oninput = async function () { let reader = new FileReader(); reader.readAsText(importBtn.files[0], 'UTF-8'); reader.onload = async function (evt) { let fileContent = ''; try { fileContent = JSON.parse(evt.target.result); let compressedDataMatch = fileContent.match(/\[\]\(actjson([A-Za-z0-9+\/=]+)\)/); if (compressedDataMatch && fileContent !== '[](actjsonETI=)') { let base64Data = compressedDataMatch[1]; let decompressedData = LZString.decompressFromBase64(base64Data); let activitiesArray = JSON.parse(decompressedData); let processedActivities = activitiesArray.map(activity => activity.trim()).filter(activity => activity.length > 0); // Save the processed activities in localForage await mainDB.setItem('savedActivities', processedActivities); } else { resetImportButton('Error: not valid backup file.'); return; } if (autosave) { if (activityDataDiv) { await buildActivity(); } } resetImportButton('Activities Imported!'); buildActivity(); } catch (e) { resetImportButton('Error: not valid backup file.'); } }; }; // Helper function to reset the import button to its default state function resetImportButton(message) { importBtn.setAttribute('type', 'button'); importBtn.removeAttribute('accept'); importBtn.removeAttribute('name'); importBtn.value = message; delay(3000).then(() => (importBtn.value = 'Import Saved Activities')); } }; //Export History Button Onclick exportHistoryBtn.onclick = async function () { let savedArray = await mainDB.getItem('likeHistory'); let export_activities = '[](actjson' + LZString.compressToBase64(JSON.stringify(savedArray)) + ')'; if (username && savedArray !== null && savedArray.length > 0) { saveAs(export_activities, 'ActivityBookmark' + '_LikeHistory_' + username + '.json'); } else { exportHistoryBtn.innerText = 'Error: The list is empty.'; delay(3000).then(() => { exportHistoryBtn.innerText = 'Export Like History'; }); } }; //Import History Button Onclick importHistoryBtn.onclick = async function () { importHistoryBtn.setAttribute('type', 'file'); importHistoryBtn.setAttribute('name', 'json'); importHistoryBtn.setAttribute('accept', 'application/json'); importHistoryBtn.oninput = async function () { let reader = new FileReader(); reader.readAsText(importHistoryBtn.files[0], 'UTF-8'); reader.onload = async function (evt) { let fileContent = ''; try { fileContent = JSON.parse(evt.target.result); let compressedDataMatch = fileContent.match(/\[\]\(actjson([A-Za-z0-9+\/=]+)\)/); if (compressedDataMatch && fileContent !== '[](actjsonETI=)') { let base64Data = compressedDataMatch[1]; let decompressedData = LZString.decompressFromBase64(base64Data); let activitiesArray = JSON.parse(decompressedData); let processedActivities = activitiesArray.map(activity => activity.trim()).filter(activity => activity.length > 0); // Save the processed activities in localForage await mainDB.setItem('likeHistory', processedActivities); } else { resetImportButton('Error: not valid backup file.'); return; } resetImportButton('Like History Imported!'); buildActivity(); } catch (e) { resetImportButton('Error: not valid backup file.'); } }; }; // Helper function to reset the import button to its default state function resetImportButton(message) { importHistoryBtn.setAttribute('type', 'button'); importHistoryBtn.removeAttribute('accept'); importHistoryBtn.removeAttribute('name'); importHistoryBtn.value = message; delay(3000).then(() => (importHistoryBtn.value = 'Import Like History')); } }; //autoSave Button Onclick autoSaveBtn.classList.toggle('btn-active', JSON.parse(localStorage.getItem('actautosave'))); if (accessToken.length < 5) { localStorage.setItem('actautosave', autosave); autoSaveBtn.classList.toggle('btn-active', JSON.parse(localStorage.getItem('actautosave'))); } autoSaveBtn.onclick = async function () { if (accessToken.length > 5) { await autoSaveActivity(); autosave = !autosave; localStorage.setItem('actautosave', autosave); SavetoBtn.classList.toggle('btn-active', JSON.parse(localStorage.getItem('actautosave'))); } else { SavetoBtn.innerText = 'Error: Token not found. Please Sign in.'; delay(3000).then(() => (SavetoBtn.innerText = 'Auto Backup Activities to Profile')); } }; //Clear History Button Onclick let confirmation = 0; clearHistoryBtn.onclick = async function () { confirmation++; if (confirmation == 1) { clearHistoryBtn.innerHTML = 'Clear Like History (CONFIRM)'; } if (confirmation == 2) { await mainDB.setItem('likeHistory', []); clearHistoryBtn.innerHTML = 'Like History Deleted!'; await delay(2000); clearHistoryBtn.innerHTML = 'Clear Like History'; confirmation = 0; } }; //Reset Databases Button Onclick let resetConfirmation = 0; resetDbBtn.onclick = async function () { resetConfirmation++; if (resetConfirmation == 1) { resetDbBtn.innerHTML = 'Reset Databases (CONFIRM)'; } if (resetConfirmation == 2) { await mainDB.setItem('likeHistory', []); await mainDB.setItem('savedActivities', []); resetDbBtn.innerHTML = 'The databases have been reset!'; await delay(2000); resetDbBtn.innerHTML = 'Reset Databases'; resetConfirmation = 0; } }; listDiv2.insertBefore(settingDiv, listDiv2.children[1]); settingDiv.append(importBtn, importHistoryBtn, exportBtn, exportHistoryBtn, autoSaveBtn, clearHistoryBtn, resetDbBtn); } else { if (document.getElementById('settingDiv')) { document.getElementById('settingDiv').remove(); } } } //Expand Button Function function expandDiv() { expanded = !expanded; if (expanded) { let x = document.querySelector('.container'); x.insertBefore(listDiv2, x.children[0]); expandbtn.innerHTML = ''; expandbtn.insertBefore(svg.compress.cloneNode(true), expandbtn.children[0]); listDiv2.setAttribute('class', 'maindiv expanded'); activityDataDiv.setAttribute('class', 'activityDataDiv expanded2'); } else { let x = document.querySelector('.activity-feed-wrap + div'); x.insertBefore(listDiv2, x.children[0]); expandbtn.innerHTML = ''; expandbtn.insertBefore(svg.expand.cloneNode(true), expandbtn.children[0]); listDiv2.className = listDiv2.className.replace(/(?:^|\s)expanded(?!\S)/g, ''); activityDataDiv.className = activityDataDiv.className.replace(/(?:^|\s)expanded2(?!\S)/g, ''); } } //Toggle Saved Divs function getSavedDiv(btn,type) { active = !active; let activefilter = document.querySelector('li.el-dropdown-menu__item.active'); if (activefilter) { delay(1000).then(() => (activefilter.className = activefilter.className.replace(/(?:^|\s)active(?!\S)/g, ''))); } if (active) { creatediv(btn); buildActivity(); } if (!active) { closeDiv(); } } //Close Button Function function closeDiv() { var list = document.querySelectorAll('li:nth-child(1)'); button.setAttribute('class', 'el-dropdown-menu__item'); button2.setAttribute('class', 'el-dropdown-menu__item'); listDiv2.remove(); active = false; isLoading = false; } async function updateEmbeds() { await delay(50); await getSpoilers(); await delay(50); await getEmbedIds(); } //Get Activity from API async function getActivity(id, source) { source = likeHistory; if (id === '') { return; } var query = `query($id: Int){Activity(id: $id){ ... on TextActivity{id type siteUrl createdAt text user{name avatar{medium}}likes{name}replies{id createdAt text user{name avatar{medium}}likes{name}}} ... on MessageActivity{id type siteUrl createdAt text: message user: messenger{name avatar{medium}}recipient{name}likes{name}replies{id createdAt text user{name avatar{medium}}likes{name}}}}}`; var variables = { id: id, }; async function handleData(data) { if (data) { let activity = data.data.Activity; let id = activity.id; let acttext = activity.text; if (active) { let activityInner = create('div', { class: 'activityInner markdown', }); let aimg = create('a', { class: 'activityDataImage', id: 'activityDataImage', href: 'https://anilist.co/user/' + activity.user.name, style: { backgroundImage: 'url(' + activity.user.avatar.medium + ')', }, }); let actusername = create('a', { class: 'activityDataUsername', id: 'activityDataUsername', target:'_blank', href: 'https://anilist.co/user/' + activity.user.name, }, '' + activity.user.name,); let activityDiv = create('div', { class: 'activity-text activityData wrap', id: activity.id, time: activity.createdAt, }); let actlinks = create('a', { class: 'activityLinksDiv', }); if(source) actlinks.style.left = 'calc(100% - 70px)'; let actlink = create('a', { class: 'activityLink', id: activity.id, target:'_blank', href: activity.siteUrl, }); let actremove = create('a', { class: 'activityLink', id: activity.id, }); actremove.onclick = async () => { await removeActivity(id); }; let userdiv = create('div', { class: 'activityDatauserdiv', id: activity.id, }); if (acttext === undefined) { await removeActivity(id); return; } if (acttext !== undefined) { activityInner.innerHTML = await actHTMLFix(acttext); } activityDataDiv.appendChild(activityDiv); activityDiv.appendChild(activityInner); activityDiv.appendChild(userdiv); aimg.appendChild(actusername); activityDiv.appendChild(actlinks); source ? actlinks.append(actlink) : actlinks.append(actlink, actremove); actlink.insertBefore(svg.link.cloneNode(true), actlink.children[0]); actremove.insertBefore(svg.cross.cloneNode(true), actremove.children[0]); activityDiv.insertBefore(userdiv, activityDiv.children[0]); userdiv.append(aimg, actlinks); let timeWrapper = create2('div', 'acttime', false, actusername); let time = nativeTimeElement(activity.createdAt); timeWrapper.appendChild(time); let actions = create2('div', 'actions', false, activityDiv); let actionReplies = create2('a', ['action', 'replies'], false, actions); let replyCount = create2('span', ['count'], activity.replies.length || '', actionReplies); replyCount.appendChild(document.createTextNode(' ')); actionReplies.appendChild(svg.reply.cloneNode(true)); actions.appendChild(document.createTextNode(' ')); let actionLikes = create2('div', ['action', 'likes'], false, actions); actionLikes.title = activity.likes.map((like) => like.name).join('\n'); let likeWrap = create2('div', ['like-wrap', 'activity'], false, actionLikes); let likeButton = create2('div', 'button', false, likeWrap); let likeCount = create2('span', 'count', activity.likes.length || '', likeButton); likeButton.appendChild(document.createTextNode(' ')); likeButton.appendChild(svg.likeNative.cloneNode(true)); likeButton.setAttribute('id',activity.id); likeButton.setAttribute('mainDiv',"1"); if (activity.likes.findIndex((thing) => thing.name === username) !== -1) { likeButton.classList.add('liked'); } if(source) { let isLiked = likeButton.classList.contains('liked'); if (!isLiked) { await removeActivity(id,'likeHistory'); } } if (accessToken) { likeButton.onclick = function () { let indexPlace = activity.likes.findIndex((thing) => thing.name === username); if (indexPlace === -1) { activity.likes.push({ name: username, }); likeButton.classList.add('liked'); } else { activity.likes.splice(indexPlace, 1); likeButton.classList.remove('liked'); } likeCount.innerText = activity.likes.length || ''; authAPIcall( 'mutation($id:Int){ToggleLike(id:$id,type:ACTIVITY){id}}', { id: activity.id, }, function (data) { if (!data) { authAPIcall( 'mutation($id:Int){ToggleLike(id:$id,type:ACTIVITY){id}}', { id: activity.id, }, (data) => { }, ); } }, ); }; } let replyWrap = create2('div', 'reply-wrap', false, activityDiv, 'display:none;'); actionReplies.onclick = function () { if (replyWrap.style.display === 'none') { replyWrap.style.display = 'block'; } else { replyWrap.style.display = 'none'; } }; let activityReplies = create2('div', 'activity-replies', false, replyWrap); activity.replies.forEach(async (rep) => { let reply = create2('div', 'reply', false, activityReplies); let header = create2('div', 'header', false, reply); let replyAvatar = create2('a', 'activityDataImage', false, header); replyAvatar.href = '/user/' + rep.user.name; replyAvatar.style.backgroundImage = `url("${rep.user.avatar.medium}")`; header.appendChild(document.createTextNode(' ')); let repName = create2('a', 'name', rep.user.name, header); repName.href = '/user/' + rep.user.name; let corner = create2('div', 'actions', false, header); let replyActionLikes = create2('div', ['action', 'likes'], false, corner, 'display: inline-block'); let replyLikeWrap = create2('div', 'like-wrap', false, replyActionLikes); let replyLikeButton = create2('div', 'button', false, replyLikeWrap); let replyLikeCount = create2('span', 'count', rep.likes.length || '', replyLikeButton); replyLikeButton.appendChild(document.createTextNode(' ')); replyLikeButton.appendChild(svg.likeNative.cloneNode(true)); replyLikeButton.title = rep.likes.map((a) => a.name).join('\n'); if (rep.likes.some((like) => like.name === username)) { replyLikeButton.classList.add('liked'); } if (accessToken) { replyLikeButton.onclick = function () { authAPIcall( 'mutation($id:Int){ToggleLike(id:$id,type:ACTIVITY_REPLY){id}}', { id: rep.id, }, function (data2) { if (!data2) { authAPIcall( 'mutation($id:Int){ToggleLike(id:$id,type:ACTIVITY_REPLY){id}}', { id: rep.id, }, function (data3) { }, ); } }, ); if (rep.likes.some((like) => like.name === username)) { rep.likes.splice( rep.likes.findIndex((user) => user.name === username), 1, ); replyLikeButton.classList.remove('liked'); if (rep.likes.length > 0) { replyLikeButton.querySelector('.count').innerText = rep.likes.length; } else { replyLikeButton.querySelector('.count').innerText = ''; } } else { rep.likes.push({ name: username, }); replyLikeButton.classList.add('liked'); replyLikeButton.querySelector('.count').innerText = rep.likes.length; } }; if (rep.user.name === username) { corner.style.cssText = 'width: 165px;left: calc(100% - 175px);top: 6px'; let replyEdit = create('div', { class: 'mainbtns', id: 'editreply', style: { background: 'transparent', color: 'rgb(var(--color-blue-dim))', }, }); replyEdit.insertBefore(svg.edit.cloneNode(true), replyEdit.children[0]); corner.insertBefore(replyEdit, corner.children[0]); let active = true; replyEdit.onclick = function () { if (active) { let statusInput = create2('div', 'inputbox', false, 'text-align: -webkit-center;'); let inputArea = create2('textarea', 'el-textarea__inner', false, statusInput); let inputButtons = create2('div', 'inputButtons', false, statusInput, 'margin-bottom:10px;float: right;padding: 20px 2% 15px 15px;'); let cancelButton = create2( 'div', ['replybutton', 'cancel'], 'Cancel', inputButtons, 'background: rgb(var(--color-foreground));display:none;color: rgb(159, 173, 189);', ); let publishButton = create2('div', 'replybutton', 'Publish', inputButtons, 'display:none;'); inputArea.value = rep.text; reply.parentNode.insertBefore(statusInput, reply.nextSibling); inputArea.style.cssText = 'height:0px'; statusInput.style.cssText = 'display: flow-root'; inputArea.onfocus = function () { cancelButton.style.display = 'inline'; publishButton.style.display = 'inline'; }; inputArea.addEventListener('keydown', autosize); function autosize() { var el = this; setTimeout(function () { el.style.cssText = 'height:auto'; el.style.cssText = 'height:' + el.scrollHeight + 'px'; }, 0); } cancelButton.onclick = function () { inputArea.value = ''; inputArea.style.cssText = 'height:0px'; cancelButton.style.display = 'none'; publishButton.style.display = 'none'; active = true; statusInput.remove(); }; publishButton.onclick = function () { authAPIcall(`mutation($text: String,$Id: Int){id text(asHtml: true)}}`, { text: inputArea.value, Id: rep.id, }, (data) => { if (data) { delay(1000).then(() => buildActivity()); } }, ); }; cancelButton.style.display = 'none'; publishButton.style.display = 'none'; } active = false; }; let replyRemove = create('div', { class: 'mainbtns', id: 'removereply', style: { background: 'transparent', transform: 'translateX(2px)', color: 'rgb(var(--color-blue-dim))', }, }); replyRemove.insertBefore(svg.xmark.cloneNode(true), replyRemove.children[0]); corner.insertBefore(replyRemove, corner.children[0]); replyRemove.onclick = function () { authAPIcall(`mutation($Id: Int){DeleteActivityReply(id: $Id) {deleted}}`, { Id: rep.id, }, (data) => { if (data) { delay(1000).then(() => buildActivity()); } },); }; } } let replyActionTime = create2('div', ['action', 'time'], false, corner, 'display: inline-block'); let replyTime = nativeTimeElement(rep.createdAt); replyActionTime.appendChild(replyTime); let replyMarkdown = create2('div', 'reply-markdown', false, reply); let markdown = create2('div', 'markdown', false, replyMarkdown); let repText = rep.text; if (repText !== undefined) { markdown.innerHTML = await actHTMLFix(repText); } }); if (accessToken) { let statusInput = create2('div', false, false, replyWrap, 'padding-top:10px; text-align: -webkit-center;'); let inputArea = create2('textarea', 'el-textarea__inner', false, statusInput); let inputButtons = create2('div', 'inputButtons', false, statusInput, 'float: right;padding: 20px 2% 15px 15px;'); let cancelButton = create2('div', ['replybutton', 'cancel'], 'Cancel', inputButtons, 'background: rgb(var(--color-foreground));display:none;color: rgb(159, 173, 189);'); let publishButton = create2('div', 'replybutton', 'Publish', inputButtons, 'display:none;'); inputArea.placeholder = 'Write a reply..'; inputArea.style.cssText = 'height:0px'; inputArea.onfocus = function () { cancelButton.style.display = 'inline'; publishButton.style.display = 'inline'; }; inputArea.addEventListener('keydown', autosize); function autosize() { var el = this; setTimeout(function () { if (inputArea.scrollHeight > 54) { el.style.cssText = 'height:auto'; el.style.cssText = 'height:' + el.scrollHeight + 'px'; } if (inputArea.value.length < 20) { el.style.cssText = 'height:0'; } }, 0); } cancelButton.onclick = function () { inputArea.value = ''; inputArea.style.cssText = 'height:0px'; cancelButton.style.display = 'none'; publishButton.style.display = 'none'; }; publishButton.onclick = function () { authAPIcall(`mutation($text: String,$activityId: Int){SaveActivityReply(text: $text,activityId: $activityId){id user{name} likes{name} text(asHtml: true) createdAt}}`, { text: inputArea.value, activityId: activity.id, }, (data) => { if (data) { delay(1000).then(() => buildActivity()); } }, ); }; inputArea.value = ''; cancelButton.style.display = 'none'; publishButton.style.display = 'none'; } } } } function handleError(e) { console.log(e); } await alQuery(query, variables).then(handleData).then().catch(handleError); await updateEmbeds(); } // Remove Activity Function async function removeActivity(id,db) { let idDB = db ? db : "savedActivities"; if (canRemoveActivity) { document.getElementById(id)?.remove(); let activitiesIdArray = await mainDB.getItem(idDB); if (typeof activitiesIdArray === "string") { activitiesIdArray = JSON.parse(activitiesIdArray); } id = String(id).trim(); activitiesIdArray = activitiesIdArray.map(item => String(item).trim()); // Find and remove the ID const index = activitiesIdArray.indexOf(id); if (index !== -1) { activitiesIdArray.splice(index, 1); } // Update the mainArray and save back to the database mainArray = activitiesIdArray; await mainDB.setItem(idDB, activitiesIdArray); if (autosave) { await autoSaveActivity(1); } } } //Add Save Button to Activities var target = document.querySelector('body'); var oldHref = document.location.href; var observer = new MutationObserver(function (mutationsList, observer) { for (var mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(async (node) => { if ( node.nodeType === Node.ELEMENT_NODE && ( node.classList.contains('activity-entry') || node.classList.contains('activity-text') || node.querySelector('.activity-entry, .activity-text') ) ) { await delay(2000); addSavetoActivities(); } }); if (oldHref !== document.location.href) { oldHref = document.location.href; active = false; addToFilters(); } } } }); observer.observe(target, { childList: true, subtree: true, });