<\/p>$/)){
createResultRow(activity.siteUrl,"Link-only post. Spam?",activity.text)
}
})
}
if(checkMessages){
data.data.messages.activities.forEach(activity => {
if(activity.message.match(/^ <\/p>$/)){
createResultRow(activity.siteUrl,"Link-only message. Spam?",activity.message)
}
})
}
}
if(document.getElementById("piracy").checked){
const badDomains = [556415734,1724824539,-779421562,-1111399772,-93654449,1120312799,-781704176,-1550515495,3396395,567115318,-307082983,1954992241,-307211474,-307390044,1222804306,-795095039,-1014860289,403785740]
if(checkActivities){
allActivities.forEach(activity => {
(activity.text.match(/ {
let linker = (
new URL(
(link.match(/\"(.*?)\"/) || ["",""])[1]
)
).host;
if(linker && linker.split(".").length >= 2){
linker = linker.split(".")[linker.split(".").length - 2];
if(
badDomains.includes(hashCode(linker))
){
createResultRow(activity.siteUrl,"Possible piracy link",activity.text)
}
}
})
})
}
if(checkMessages){
data.data.messages.activities.forEach(activity => {
(activity.message.match(/ {
let linker = (
new URL(
(link.match(/\"(.*?)\"/) || ["",""])[1]
)
).host;
if(linker && linker.split(".").length >= 2){
linker = linker.split(".")[linker.split(".").length - 2];
if(
badDomains.includes(hashCode(linker))
){
createResultRow(activity.siteUrl,"Possible piracy link",activity.message)
}
}
})
})
}
}
if(document.getElementById("badWords").checked){
if(checkActivities){
allActivities.forEach(activity => {
let badList = badWords.filter(word => activity.text.toUpperCase().includes(word.toUpperCase()))
if(badList.length){
createResultRow(activity.siteUrl,"Word match [" + badList.join("],[") + "]",activity.text)
}
})
data.data.messages.activities.forEach(activity => {
let badList = badWords.filter(word => activity.message.toUpperCase().includes(word.toUpperCase()))
if(badList.length){
createResultRow(activity.siteUrl,"Word match [" + badList.join("],[") + "]",activity.message)
}
})
}
}
if(document.getElementById("highActivity").checked){
if(checkActivities){
let countMap = new Map();
allActivities.map(act => act.user.name).forEach(person => {
countMap.set(person,(countMap.get(person) || 0) + 1)
})
countMap.forEach((value,key) => {
if(value >= allActivities.length/10){
createResultRow("https://anilist.co/user/" + key,value + " posts in the " + allActivities.length + " most recent posts","")
}
})
}
}
if(document.getElementById("weirdMarkup").checked){
if(checkActivities){
allActivities.forEach(activity => {
if(activity.text.length > 50 && LZString.compress(activity.text).length/activity.text.length < 0.1){
createResultRow(activity.siteUrl,"Low entropy",activity.text)
}
})
}
}
if(miscResults.innerText === ""){
miscResults.innerText = "Inspection completed. Nothing unusual found."
}
}
)
}
}},
{name: "Autorecs",
setup: function(){
let select = create("select","#typeSelect",false,miscOptions);
let animeOption = create("option",false,"Anime",select);
let mangaOption = create("option",false,"Manga",select);
animeOption.value = "ANIME";
mangaOption.value = "MANGA";
},
code: function(){
miscResults.innerText = "Collecting list data...";
generalAPIcall(
`query($name: String!){
User(name: $name){
statistics{
${document.getElementById("typeSelect").value.toLowerCase()}{
meanScore
standardDeviation
}
}
}
MediaListCollection(userName: $name,type: ${document.getElementById("typeSelect").value},status_not: PLANNING){
lists{
entries{
mediaId
score(format: POINT_100)
status
media{
recommendations(sort:RATING_DESC,perPage:5){
nodes{
rating
mediaRecommendation{
id
title{romaji native english}
}
}
}
}
}
}
}
}`,
{name: user},function(data){
miscResults.innerText = "Processing...";
const list = returnList(data,true).filter(
media => media.status !== "PLANNING"
);
const existingSet = new Set(
list.map(media => media.mediaId)
);
const statistics = data.data.User.statistics[document.getElementById("typeSelect").value.toLowerCase()];
const recsMap = new Map();
list.filter(
media => media.score
).forEach(media => {
let adjustedScore = (media.score - statistics.meanScore)/statistics.standardDeviation;
media.media.recommendations.nodes.forEach(rec => {
if(
rec.mediaRecommendation
&& !existingSet.has(rec.mediaRecommendation.id)
&& rec.rating > 0
){
if(!recsMap.has(rec.mediaRecommendation.id)){
recsMap.set(
rec.mediaRecommendation.id,
{title: titlePicker(rec.mediaRecommendation),score: 0}
)
}
recsMap.get(rec.mediaRecommendation.id).score += adjustedScore * (2 - 1/rec.rating)
}
})
});
miscResults.innerText = "";
[...recsMap].map(
pair => ({
id: pair[0],
title: pair[1].title,
score: pair[1].score
})
).sort(
(b,a) => a.score - b.score
).slice(0,25).forEach(rec => {
let card = create("p",false,false,miscResults);
let score = create("span","hohMonospace",rec.score.toPrecision(3) + " ",card,"margin-right:10px;");
create("a",false,rec.title,card)
.href = "/" + document.getElementById("typeSelect").value.toLowerCase() + "/" + rec.id + "/"
})
}
)
}},
{name: "Find a status",setup: function(){
let input = create("input","#searchInput",false,miscOptions);
input.placeholder = "text or regex to match";
},code: function(){
let searchQuery = document.getElementById("searchInput").value;
if(statusSearchCache.length){
miscResults.innerText = "";
let results = create("p",false,false,miscResults);
statusSearchCache.forEach(function(act){
if(act.match(new RegExp(searchQuery,"i"))){
let newDate = create("p",false,false,results,"font-family:monospace;margin-right:10px;");
let newPage = create("a","newTab",act.siteUrl,newDate,"color:rgb(var(--color-blue));");
newPage.href = act.siteUrl;
newDate.innerHTML += DOMPurify.sanitize(act);//reason for innerHTML: preparsed sanitized HTML from the Anilist API
create("hr",false,false,results)
}
})
}
else{
generalAPIcall("query($name:String){User(name:$name){id}}",{name: user},function(data){
const query = `
query($userId: Int,$page: Int){
Page(page: $page){
pageInfo{
currentPage
total
lastPage
}
activities (userId: $userId, sort: ID_DESC, type: TEXT){
... on TextActivity{
siteUrl
text(asHtml: true)
}
}
}
}`;
miscResults.innerText = "";
let results = create("p",false,false,miscResults);
let posts = 0;
let progress = create("p",false,false,miscResults);
let userId = data.data.User.id;
let addNewUserData = function(data){
console.log(data);
if(!data){
return
}
if(data.data.Page.pageInfo.currentPage === 1){
for(var i=2;i<=data.data.Page.pageInfo.lastPage && i < ANILIST_QUERY_LIMIT;i++){
generalAPIcall(query,{userId: userId,page: i},addNewUserData)
}
};
posts += data.data.Page.activities.length;
progress.innerText = "Searching status post " + posts + "/" + data.data.Page.pageInfo.total;
data.data.Page.activities.forEach(function(act){
if(act.text.match(new RegExp(searchQuery,"i"))){
let newDate = create("p",false,false,results,"font-family:monospace;margin-right:10px;");
let newPage = create("a","newTab",act.siteUrl,newDate,"color:rgb(var(--color-blue));");
newPage.href = act.siteUrl;
newDate.innerHTML += DOMPurify.sanitize(act.text);//reason for innerHTML: preparsed sanitized HTML from the Anilist API
create("hr",false,false,results)
}
statusSearchCache.push(act.text)
})
};
generalAPIcall(query,{userId: userId,page: 1},addNewUserData);
},"hohIDlookup" + user.toLowerCase())
}
}},
{name: "Find a message",setup: function(){
let input = create("input","#searchInput",false,miscOptions);
input.placeholder = "text or regex to match";
},code: function(){
generalAPIcall("query($name:String){User(name:$name){id}}",{name: user},function(data){
let userId = data.data.User.id;
miscResults.innerText = "";
let posts = 0;
let progress = create("p",false,false,miscResults);
let results = create("p",false,false,miscResults);
let searchQuery = document.getElementById("searchInput").value;
const query = `
query($userId: Int,$page: Int){
Page(page: $page){
pageInfo{
currentPage
total
lastPage
}
activities (userId: $userId, sort: ID_DESC, type: MESSAGE){
... on MessageActivity{
siteUrl
message(asHtml: true)
}
}
}
}`;
let addNewUserData = function(data){
if(data.data.Page.pageInfo.currentPage === 1){
for(var i=2;i<=data.data.Page.pageInfo.lastPage;i++){
generalAPIcall(query,{userId: userId,page: i},addNewUserData)
}
};
posts += data.data.Page.activities.length;
progress.innerText = "Searching message post " + posts + "/" + data.data.Page.pageInfo.total;
data.data.Page.activities.forEach(function(act){
if(act.message.match(new RegExp(searchQuery,"i"))){
let newDate = create("p",false,false,results,"font-family:monospace;margin-right:10px;");
let newPage = create("a","newTab",act.siteUrl,newDate,"color:rgb(var(--color-blue));");
newPage.href = act.siteUrl;
newDate.innerHTML += DOMPurify.sanitize(act.message);//reason for innerHTML: preparsed sanitized HTML from the Anilist API
create("hr",false,false,results)
}
})
};
generalAPIcall(query,{userId: userId,page: 1},addNewUserData);
},"hohIDlookup" + user.toLowerCase())
}},
{name: "Most liked status posts",code: function(){
generalAPIcall("query($name:String){User(name:$name){id}}",{name: user},function(data){
let userId = data.data.User.id;
let list = [];
miscResults.innerText = "";
let progress = create("p",false,false,miscResults);
let results = create("p",false,false,miscResults);
const query = `
query($userId: Int,$page: Int){
Page(page: $page){
pageInfo{
currentPage
total
lastPage
}
activities (userId: $userId, sort: ID_DESC, type: TEXT){
... on TextActivity{
siteUrl
likes{id}
}
}
}
}`;
let addNewUserData = function(data){
list = list.concat(data.data.Page.activities);
if(data.data.Page.pageInfo.currentPage === 1){
for(var i=2;i<=data.data.Page.pageInfo.lastPage;i++){
generalAPIcall(query,{userId: userId,page: i},addNewUserData);
};
};
list.sort(function(b,a){return a.likes.length - b.likes.length});
progress.innerText = "Searching status post " + list.length + "/" + data.data.Page.pageInfo.total;
removeChildren(results)
for(var i=0;i<20;i++){
let newDate = create("p",false,list[i].likes.length + " likes ",results,"font-family:monospace;margin-right:10px;");
let newPage = create("a","newTab",list[i].siteUrl,newDate,"color:rgb(var(--color-blue));");
newPage.href = list[i].siteUrl;
};
};
generalAPIcall(query,{userId: userId,page: 1},addNewUserData);
},"hohIDlookup" + user.toLowerCase());
}},
{name: "Monthly stats",code: function(){
generalAPIcall("query($name:String){User(name:$name){id}}",{name: user},function(data){
let userId = data.data.User.id;
let currentYear = new Date().getFullYear();
let presentTime = new Date();
let limitTime;
for(let i=1;i<12;i++){
if(new Date(currentYear,i,1) > presentTime){
limitTime = new Date(currentYear,i - 1,1).valueOf();
break
}
if(i === 11){
limitTime = new Date(currentYear,11,1).valueOf()
}
}
let activityQuery =
`query ($userId: Int, $page: Int) {
Page(perPage: 50, page: $page){
activities(sort: ID_DESC,userId: $userId){
... on MessageActivity {
type
createdAt
}
... on TextActivity {
type
createdAt
}
... on ListActivity {
type
createdAt
status
progress
media{
episodes
chapters
duration
format
id
}
}
}
}
}`;
let statsBuffer = [];
let currentPage = 1;
let getData = function(){
generalAPIcall(activityQuery,{userId: userId,page: currentPage},function(data){
statsBuffer = statsBuffer.concat(data.data.Page.activities.filter(act => act.createdAt*1000 >= limitTime));
miscResults.innerText = "This month:";
let messageCount = statsBuffer.filter(activity => activity.type === "MESSAGE").length;
let statusCount = statsBuffer.filter(activity => activity.type === "TEXT").length;
let animeCount = statsBuffer.filter(activity => activity.type === "ANIME_LIST").length;
let mangaCount = statsBuffer.filter(activity => activity.type === "MANGA_LIST").length;
create("p",false,"Messages received: " +messageCount,miscResults);
create("p",false,"Status posts: " + statusCount,miscResults);
create("p",false,"Anime feed items: " + animeCount,miscResults);
create("p",false,"Manga feed items: " + mangaCount,miscResults);
let animeEntries = {};
let mangaEntries = {};
statsBuffer.forEach(entry => {
if(entry.type === "ANIME_LIST"){
let addDefault = function(){
animeEntries[entry.media.id] = animeEntries[entry.media.id] || {
completionCount: 0,
lowestProgress: entry.media.episodes || Number.MAX_SAFE_INTEGER,
highestProgress: 0,
duration: entry.media.duration || 1,
format: entry.media.format,
total: entry.media.episodes || 1
}
}
if(entry.status === "watched episode" || entry.status === "rewatched episode"){
addDefault();
let splitProgress = entry.progress.split(" - ").map(num => parseInt(num));
animeEntries[entry.media.id].lowestProgress = Math.min(
animeEntries[entry.media.id].lowestProgress,
splitProgress[0]
)
animeEntries[entry.media.id].highestProgress = Math.max(
animeEntries[entry.media.id].highestProgress,
splitProgress[splitProgress.length - 1]
)
}
else if(entry.status === "completed"){
addDefault();
animeEntries[entry.media.id].highestProgress = animeEntries[entry.media.id].total || 1;
animeEntries[entry.media.id].completionCount++
}
}
else if(entry.type === "MANGA_LIST"){
let addDefault = function(){
mangaEntries[entry.media.id] = mangaEntries[entry.media.id] || {
completionCount: 0,
lowestProgress: entry.media.chapters || Number.MAX_SAFE_INTEGER,
highestProgress: 0,
format: entry.media.format,
total: entry.media.chapters || 1
}
}
if(entry.status === "read chapter" || entry.status === "reread chapter"){
addDefault();
let splitProgress = entry.progress.split(" - ").map(num => parseInt(num));
mangaEntries[entry.media.id].lowestProgress = Math.min(
mangaEntries[entry.media.id].lowestProgress,
splitProgress[0]
)
mangaEntries[entry.media.id].highestProgress = Math.max(
mangaEntries[entry.media.id].highestProgress,
splitProgress[splitProgress.length - 1]
)
}
else if(entry.status === "completed"){
addDefault();
mangaEntries[entry.media.id].highestProgress = mangaEntries[entry.media.id].total || 1;
mangaEntries[entry.media.id].completionCount++
}
}
});
let formatDigest = {};
Object.keys(distributionFormats).forEach(key => formatDigest[key] = 0);
let digestMinutesAnime = 0;
Object.keys(animeEntries).forEach(key => {
let epCount = Math.max(0,animeEntries[key].highestProgress - animeEntries[key].lowestProgress + 1);
if(animeEntries[key].completionCount > 1){
epCount += animeEntries[key].completionCount - 1
}
digestMinutesAnime += epCount * animeEntries[key].duration;
formatDigest[animeEntries[key].format] += epCount
});
Object.keys(mangaEntries).forEach(key => {
let chCount = Math.max(0,mangaEntries[key].highestProgress - mangaEntries[key].lowestProgress + 1);
if(mangaEntries[key].completionCount > 1){
chCount += mangaEntries[key].completionCount - 1
}
formatDigest[mangaEntries[key].format] += chCount
});
create("hr",false,false,miscResults);
create("p",false,"Time Watched: " + formatTime(digestMinutesAnime * 60),miscResults);
if(formatDigest["TV"]){
create("p",false,"TV eps: " + formatDigest["TV"],miscResults)
}
if(formatDigest["OVA"]){
create("p",false,"OVA eps: " + formatDigest["OVA"],miscResults)
}
if(formatDigest["ONA"]){
create("p",false,"ONA eps: " + formatDigest["ONA"],miscResults)
}
if(formatDigest["MOVIE"]){
create("p",false,"Movies: " + formatDigest["MOVIE"],miscResults)
}
if(formatDigest["TV_SHORT"]){
create("p",false,"Short TV eps: " + formatDigest["TV_SHORT"],miscResults)
}
if(formatDigest["SPECIAL"]){
create("p",false,"Specials: " + formatDigest["SPECIAL"],miscResults)
}
if(formatDigest["MUSIC"]){
create("p",false,"Music vids: " + formatDigest["MUSIC"],miscResults)
}
create("hr",false,false,miscResults);
if(formatDigest["MANGA"]){
create("p",false,"Manga chapters: " + formatDigest["MANGA"],miscResults)
}
if(formatDigest["NOVEL"]){
create("p",false,"LN chapters: " + formatDigest["NOVEL"],miscResults)
}
if(formatDigest["ONE_SHOT"]){
create("p",false,"One shots: " + formatDigest["ONE_SHOT"],miscResults)
}
if(data.data.Page.activities[data.data.Page.activities.length - 1].createdAt*1000 >= limitTime){
currentPage++;
create("p",false,"Loading more activities...",miscResults);
getData();
}
})
};getData()
},"hohIDlookup" + user.toLowerCase());
}},
];
let miscInputSelect = create("select",false,false,miscInput);
let miscInputButton = create("button",["button","hohButton"],"Run",miscInput);
availableQueries.forEach(que => {
create("option",false,que.name,miscInputSelect).value = que.name
});
miscInputSelect.oninput = function(){
miscOptions.innerText = "";
let relevant = availableQueries.find(que => que.name === miscInputSelect.value);
if(relevant.setup){
miscResults.innerText = "";
relevant.setup()
}
};
miscInputButton.onclick = function(){
miscResults.innerText = "Loading...";
availableQueries.find(que => que.name === miscInputSelect.value).code()
}
let customTagsCollection = function(list,title,fields){
let customTags = new Map();
let regularTags = new Map();
let customLists = new Map();
(
JSON.parse(localStorage.getItem("regularTags" + title)) || []
).forEach(
tag => regularTags.set(tag,{
name : tag,
list : []
})
);
customLists.set("Not on custom list",{name: "Not on custom list",list: []});
customLists.set("All media",{name: "All media",list: []});
list.forEach(media => {
let item = {};
fields.forEach(field => {
item[field.key] = field.method(media)
});
if(media.notes){
(
media.notes.match(/(#(\\\s|\S)+)/g) || []
).filter(
tagMatch => !tagMatch.match(/^#039/)
).map(
tagMatch => evalBackslash(tagMatch)
).forEach(tagMatch => {
if(!customTags.has(tagMatch)){
customTags.set(tagMatch,{name: tagMatch,list: []})
}
customTags.get(tagMatch).list.push(item)
});
(//candidates for multi word tags, which we try to detect even if they are not allowed
media.notes.match(/(#\S+\ [^#]\S+)/g) || []
).filter(
tagMatch => !tagMatch.match(/^#039/)
).map(
tagMatch => evalBackslash(tagMatch)
).forEach(tagMatch => {
if(!customTags.has(tagMatch)){
customTags.set(tagMatch,{name: tagMatch,list: []})
}
customTags.get(tagMatch).list.push(item)
})
};
media.media.tags.forEach(mediaTag => {
if(regularTags.has(mediaTag.name)){
regularTags.get(mediaTag.name).list.push(item)
}
});
if(media.isCustomList){
media.listLocations.forEach(location => {
if(!customLists.has(location)){
customLists.set(location,{name: location,list: []})
}
customLists.get(location).list.push(item)
})
}
else if(useScripts.negativeCustomList){
customLists.get("Not on custom list").list.push(item)
};
if(useScripts.globalCustomList){
customLists.get("All media").list.push(item)
}
});
if(customTags.has("##STRICT")){
customTags.delete("##STRICT")
}
else{
for(let [key,value] of customTags){//filter our multi word candidates
if(key.includes(" ")){
if(value.list.length === 1){//if it's just one of them, the prefix tag takes priority
customTags.delete(key)
}
else{
let prefix = key.split(" ")[0];
if(customTags.has(prefix)){
if(customTags.get(prefix).list.length === value.list.length){
customTags.delete(prefix)
}
else{
customTags.delete(key)
}
}
}
}
}
for(let [key,value] of customTags){//fix the basic casing error, like #shoujo vs #Shoujo. Will only merge if one is of length 1
if(key[1] === key[1].toUpperCase()){
let lowerCaseKey = "#" + key[1].toLowerCase() + key.slice(2);
let lowerCaseValue = customTags.get(lowerCaseKey);
if(lowerCaseValue){
if(value.list.length === 1){
lowerCaseValue.list = lowerCaseValue.list.concat(value.list);
customTags.delete(key)
}
else if(lowerCaseValue.list.length === 1){
value.list = value.list.concat(lowerCaseValue.list);
customTags.delete(lowerCaseKey)
}
}
}
}
}
if(!customLists.get("Not on custom list").list.length){
customLists.delete("Not on custom list")
};
if(!customLists.get("All media").list.length){
customLists.delete("All media")
};
return [...customTags, ...regularTags, ...customLists].map(
pair => pair[1]
).map(tag => {
let amountCount = 0;
let average = 0;
tag.list.forEach(item => {
if(item.score !== 0){
amountCount++;
average += item.score;
};
fields.forEach(field => {
if(field.sumable){
tag[field.key] = field.sumable(tag[field.key],item[field.key]);
}
})
});
tag.average = average/amountCount || 0;
tag.list.sort((b,a) => a.score - b.score);
return tag;
}).sort(
(b,a) => a.list.length - b.list.length || b.name.localeCompare(a.name)
)
};
let regularTagsCollection = function(list,fields,extracter){
let tags = new Map();
list.forEach(media => {
let item = {};
fields.forEach(field => {
item[field.key] = field.method(media)
});
extracter(media).forEach(tag => {
if(useScripts.SFWmode && tag.name === "Hentai"){
return
}
if(!tags.has(tag.name)){
tags.set(tag.name,{name: tag.name,list: []})
}
tags.get(tag.name).list.push(item)
})
});
tags.forEach(tag => {
tag.amountCount = 0;
tag.average = 0;
tag.list.forEach(item => {
if(item.score){
tag.amountCount++;
tag.average += item.score;
};
fields.forEach(field => {
if(field.sumable){
tag[field.key] = field.sumable(tag[field.key],item[field.key])
}
})
});
tag.average = tag.average/tag.amountCount || 0;
tag.list.sort((b,a) => a.score - b.score)
});
return [...tags].map(
tag => tag[1]
).sort(
(b,a) => (a.average*a.amountCount + ANILIST_WEIGHT)/(a.amountCount + 1) - (b.average*b.amountCount + ANILIST_WEIGHT)/(b.amountCount + 1) || a.list.length - b.list.length
)
};
let drawTable = function(data,formatter,tableLocation,isTag,autoHide){
removeChildren(tableLocation)
tableLocation.innerText = "";
let hasScores = data.some(elem => elem.average);
let header = create("p",false,formatter.title);
let tableContent = create("div",["table","hohTable"]);
let headerRow = create("div",["header","row"],false,tableContent);
let indexAccumulator = 0;
formatter.headings.forEach(function(heading){
if(!hasScores && heading === "Mean Score"){
return
};
let columnTitle = create("div",false,heading,headerRow);
if(heading === "Tag" && !isTag && formatter.isMixed){
columnTitle.innerText = "Genre"
}
if(formatter.focus === indexAccumulator){
columnTitle.innerText += " ";
columnTitle.appendChild(svgAssets2.angleDown.cloneNode(true))
};
columnTitle.index = +indexAccumulator;
columnTitle.addEventListener("click",function(){
formatter.focus = this.index;
data.sort(formatter.sorting[this.index]);
drawTable(data,formatter,tableLocation,isTag,autoHide)
});
indexAccumulator++;
});
for(var i=0;i " + activity.text.replace(/\n\n/g," ") + "" + thread.title + "
" + thread.text;
return thread
}).filter(thread => thread.replyCount || !onlyReplies.checked),"thread",requestTime);
handleNotifications(data);
}
);
}
else if(onlyReviews.checked){
authAPIcall(
`
query($page: Int){
Page(page: $page,perPage: 20){
reviews(sort:CREATED_AT_DESC${(onlyUser.checked ? ",userId: " + userID : "")}${onlyMedia.checked && onlyMediaResult.id ? ",mediaId: " + onlyMediaResult.id : ""}){
id
createdAt
user{name}
media{
id
type
title{romaji native english}
}
summary
body(asHtml: true)
rating
ratingAmount
}
}
Viewer{unreadNotificationCount}
}`,
{page: npage},
function(data){
buildPage(data.data.Page.reviews.map(review => {
review.type = "TEXT";
review.likes = [];
review.replies = [{
id: review.id,
user: review.user,
likes: [],
text: review.body,
createdAt: review.createdAt
}];
review.text = review.summary
return review
}),"review",requestTime);
handleNotifications(data)
}
);
}
else{
authAPIcall(
`
query($page: Int,$types: [ActivityType]){
Page(page: $page){
activities(${(onlyUser.checked || onlyGlobal.checked ? "" : "isFollowing: true,")}sort: ID_DESC,type_not_in: $types${(onlyReplies.checked ? ",hasReplies: true" : "")}${(onlyUser.checked ? ",userId: " + userID : "")}${(onlyGlobal.checked ? ",hasRepliesOrTypeText: true" : "")}${onlyMedia.checked && onlyMediaResult.id ? ",mediaId: " + onlyMediaResult.id : ""}${date ? ",createdAt_greater: " + (dateToJST(date)/1000) + ",createdAt_lesser: " + (dateToJST(date)/1000 + 24*60*60) : ""}){
... on MessageActivity{
id
type
createdAt
user:messenger{name}
text:message(asHtml: true)
likes{name}
replies{
id
user{name}
likes{name}
text(asHtml: true)
createdAt
}
}
... on TextActivity{
id
type
createdAt
user{name}
text(asHtml: true)
likes{name}
replies{
id
user{name}
likes{name}
text(asHtml: true)
createdAt
}
}
... on ListActivity{
id
type
createdAt
user{name}
status
progress
media{
id
type
title{romaji native english}
}
likes{name}
replies{
id
user{name}
likes{name}
text(asHtml: true)
createdAt
}
}
}
}
Viewer{unreadNotificationCount}
}`,
{page: npage,types:types},
function(data){
buildPage(data.data.Page.activities,"activity",requestTime);
handleNotifications(data)
}
);
}
};
requestPage(page);
let setInputs = function(){
statusInputTitle.style.display = "none";
if(onlyReviews.checked){
inputArea.placeholder = "Writing reviews not supported yet...";
publishButton.innerText = "Publish";
}
else if(onlyForum.checked){
inputArea.placeholder = "Write a forum post...";
statusInputTitle.style.display = "block";
publishButton.innerText = "Publish";
}
else if(onlyUser.checked && onlyUserInput.value && onlyUserInput.value.toLowerCase() !== whoAmI.toLowerCase()){
inputArea.placeholder = "Write a message...";
publishButton.innerText = "Send";
}
else{
inputArea.placeholder = "Write a status...";
publishButton.innerText = "Publish";
}
};
topPrevious.onclick = function(){
loading.innerText = "Loading...";
if(page === 1){
requestPage(1)
}
else{
requestPage(page - 1)
}
};
topNext.onclick = function(){
loading.innerText = "Loading...";
requestPage(page + 1);
};
onlyGlobal.onchange = function(){
loading.innerText = "Loading...";
statusInputTitle.style.display = "none";
inputArea.placeholder = "Write a status...";
onlyUser.checked = false;
onlyForum.checked = false;
onlyReviews.checked = false;
requestPage(1);
};
onlyStatus.onchange = function(){
loading.innerText = "Loading...";
onlyForum.checked = false;
onlyReviews.checked = false;
onlyMedia.checked = false;
requestPage(1);
};
onlyReplies.onchange = function(){
loading.innerText = "Loading...";
onlyReviews.checked = false;
requestPage(1);
};
onlyUser.onchange = function(){
setInputs();
loading.innerText = "Loading...";
onlyGlobal.checked = false;
requestPage(1);
};
onlyForum.onchange = function(){
setInputs();
loading.innerText = "Loading...";
onlyGlobal.checked = false;
onlyStatus.checked = false;
onlyReviews.checked = false;
requestPage(1);
};
onlyMedia.onchange = function(){
setInputs();
loading.innerText = "Loading...";
requestPage(1);
};
onlyReviews.onchange = function(){
setInputs();
onlyGlobal.checked = false;
onlyStatus.checked = false;
onlyForum.checked = false;
onlyReplies.checked = false;
loading.innerText = "Loading...";
requestPage(1);
}
let oldOnlyUser = "";
onlyUserInput.onfocus = function(){
oldOnlyUser = onlyUserInput.value
};
let oldOnlyMedia = "";
onlyMediaInput.onfocus = function(){
oldOnlyMedia = onlyMediaInput.value
};
onlyMediaInput.onblur = function(){
if(onlyMediaInput.value === oldOnlyMedia){
return;
}
if(onlyMediaInput.value === ""){
removeChildren(mediaDisplayResults)
onlyMediaResult.id = false;
}
else{
if(!mediaDisplayResults.childElementCount){
create("span",false,"Searching...",mediaDisplayResults);
}
generalAPIcall(`
query($search: String){
Page(page:1,perPage:5){
media(search:$search,sort:SEARCH_MATCH){
title{romaji}
id
type
}
}
}`,
{search: onlyMediaInput.value},
function(data){
removeChildren(mediaDisplayResults)
data.data.Page.media.forEach((media,index) => {
let result = create("span",["hohSearchResult",media.type.toLowerCase()],media.title.romaji,mediaDisplayResults);
if(index === 0){
result.classList.add("selected");
onlyMediaResult.id = media.id;
onlyMediaResult.type = media.type;
}
result.onclick = function(){
mediaDisplayResults.querySelector(".selected").classList.toggle("selected");
result.classList.add("selected");
onlyMediaResult.id = media.id;
onlyMediaResult.type = media.type;
onlyMedia.checked = true;
onlyStatus.checked = false;
loading.innerText = "Loading...";
requestPage(1);
}
});
if(data.data.Page.media.length){
onlyMedia.checked = true;
onlyStatus.checked = false;
loading.innerText = "Loading...";
requestPage(1);
}
else{
create("span",false,"No results found",mediaDisplayResults);
onlyMediaResult.id = false;
}
}
)
};
};
onlyUserInput.onblur = function(){
if(onlyForum.checked){
inputArea.placeholder = "Write a forum post...";
publishButton.innerText = "Publish";
}
else if(
(onlyUser.checked && onlyUserInput.value && onlyUserInput.value.toLowerCase() !== whoAmI.toLowerCase())
|| (oldOnlyUser !== onlyUserInput.value && onlyUserInput.value !== "")
){
inputArea.placeholder = "Write a message...";
publishButton.innerText = "Send";
}
else{
inputArea.placeholder = "Write a status...";
publishButton.innerText = "Publish";
}
if(oldOnlyUser !== onlyUserInput.value && onlyUserInput.value !== ""){
loading.innerText = "Loading...";
onlyUser.checked = true;
requestPage(1);
}
else if(onlyUser.checked && oldOnlyUser !== onlyUserInput.value){
loading.innerText = "Loading...";
requestPage(1);
}
};
onlyUserInput.addEventListener("keyup",function(event){
if(event.key === "Enter"){
onlyUserInput.blur();
}
});
onlyMediaInput.addEventListener("keyup",function(event){
if(event.key === "Enter"){
onlyMediaInput.blur();
}
});
inputArea.onfocus = function(){
cancelButton.style.display = "inline";
publishButton.style.display = "inline";
};
cancelButton.onclick = function(){
inputArea.value = "";
cancelButton.style.display = "none";
publishButton.style.display = "none";
loading.innerText = "";
onlySpecificActivity = false;
document.activeElement.blur();
};
publishButton.onclick = function(){
if(onlyForum.checked){
alert("Sorry, not implemented yet");
//loading.innerText = "Publishing forum post...";
return;
}
else if(onlyReviews.checked){
alert("Sorry, not implemented yet");
//loading.innerText = "Publishing review...";
return;
}
else if(onlySpecificActivity){
loading.innerText = "Publishing...";
authAPIcall(
"mutation($text: String,$id: Int){SaveTextActivity(id: $id,text: $text){id}}",
{text: inputArea.value,id: onlySpecificActivity},
function(data){
onlySpecificActivity = false;
requestPage(1);
}
);
}
else if(onlyUser.checked && onlyUserInput.value && onlyUserInput.value.toLowerCase() !== whoAmI.toLowerCase()){
loading.innerText = "Sending Message...";
generalAPIcall("query($name:String){User(name:$name){id}}",{name: onlyUserInput.value},function(data){
if(data){
authAPIcall(
"mutation($text: String,$recipientId: Int){SaveMessageActivity(message: $text,recipientId: $recipientId){id}}",
{
text: emojiSanitize(inputArea.value),
recipientId: data.data.User.id
},
function(data){
requestPage(1);
}
)
}
else{
loading.innerText = "Not Found";
}
},"hohIDlookup" + onlyUserInput.value.toLowerCase());
}
else{
loading.innerText = "Publishing...";
authAPIcall(
"mutation($text: String){SaveTextActivity(text: $text){id}}",
{text: emojiSanitize(inputArea.value)},
function(data){
requestPage(1);
}
);
}
inputArea.value = "";
cancelButton.style.display = "none";
publishButton.style.display = "none";
document.activeElement.blur();
};
let sideBarContent = create("div","sidebar",false,feed,"position:absolute;left:20px;top:200px;max-width:150px;");
let buildPreview = function(data){
if(!data){
return;
}
removeChildren(sideBarContent)
let mediaLists = data.data.Page.mediaList.map(mediaList => {
if(aliases.has(mediaList.media.id)){
mediaList.media.title.userPreferred = aliases.get(mediaList.media.id)
}
return mediaList
});
mediaLists.slice(0,20).forEach(mediaList => {
let mediaEntry = create("div",false,false,sideBarContent,"border-bottom: solid;border-bottom-width: 1px;margin-bottom: 10px;border-radius: 3px;padding: 2px;");
create("a","link",mediaList.media.title.userPreferred,mediaEntry,"min-height:40px;display:inline-block;")
.href = "/anime/" + mediaList.media.id + "/" + safeURL(mediaList.media.title.userPreferred);
let progress = create("div",false,false,mediaEntry,"font-size: small;");
create("span",false,"Progress: ",progress);
let number = create("span",false,mediaList.progress + (mediaList.media.episodes ? "/" + mediaList.media.episodes : ""),progress);
let plusProgress = create("span",false,"+",progress,"padding-left:5px;padding-right:5px;cursor:pointer;");
let isBlocked = false;
plusProgress.onclick = function(e){
if(isBlocked){
return
};
if(mediaList.media.episodes){
if(mediaList.progress < mediaList.media.episodes){
mediaList.progress++;
number.innerText = mediaList.progress + (mediaList.media.episodes ? "/" + mediaList.media.episodes : "");
isBlocked = true;
setTimeout(function(){
isBlocked = false;
},300);
if(mediaList.progress === mediaList.media.episodes){
plusProgress.innerText = "";
if(mediaList.status === "REWATCHING"){//don't overwrite the existing end date
authAPIcall(
`mutation($progress: Int,$id: Int){
SaveMediaListEntry(progress: $progress,id:$id,status:COMPLETED){id}
}`,
{id: mediaList.id,progress: mediaList.progress},
data => {}
);
}
else{
authAPIcall(
`mutation($progress: Int,$id: Int,$date:FuzzyDateInput){
SaveMediaListEntry(progress: $progress,id:$id,status:COMPLETED,completedAt:$date){id}
}`,
{
id: mediaList.id,
progress: mediaList.progress,
date: {
year: (new Date()).getUTCFullYear(),
month: (new Date()).getUTCMonth() + 1,
day: (new Date()).getUTCDate(),
}
},
data => {}
);
};
mediaEntry.style.backgroundColor = "rgba(0,200,0,0.1)";
}
else{
authAPIcall(
`mutation($progress: Int,$id: Int){
SaveMediaListEntry(progress: $progress,id:$id){id}
}`,
{id: mediaList.id,progress: mediaList.progress},
data => {}
);
}
localStorage.setItem("hohListPreview",JSON.stringify(data));
}
}
else{
mediaList.progress++;
number.innerText = mediaList.progress + (mediaList.media.episodes ? "/" + mediaList.media.episodes : "");
isBlocked = true;
setTimeout(function(){
isBlocked = false;
},300);
authAPIcall(
`mutation($progress: Int,$id: Int){
SaveMediaListEntry(progress: $progress,id:$id){id}
}`,
{id: mediaList.id,progress: mediaList.progress},
data => {}
);
localStorage.setItem("hohListPreview",JSON.stringify(data));
};
e.stopPropagation();
e.preventDefault();
return false
}
});
};
authAPIcall(
`query($name: String){
Page(page:1){
mediaList(type:ANIME,status_in:[CURRENT,REPEATING],userName:$name,sort:UPDATED_TIME_DESC){
id
priority
scoreRaw: score(format: POINT_100)
progress
status
media{
id
episodes
coverImage{large color}
title{userPreferred}
nextAiringEpisode{episode timeUntilAiring}
}
}
}
}`,{name: whoAmI},function(data){
localStorage.setItem("hohListPreview",JSON.stringify(data));
buildPreview(data,true);
}
);
buildPreview(JSON.parse(localStorage.getItem("hohListPreview")),false);
}
})
function viewAdvancedScores(url){
let URLstuff = url.match(/^https:\/\/anilist\.co\/user\/(.+)\/(anime|manga)list\/?/);
let name = decodeURIComponent(URLstuff[1]);
generalAPIcall(
`query($name:String!){
User(name:$name){
mediaListOptions{
animeList{advancedScoringEnabled}
mangaList{advancedScoringEnabled}
}
}
}`,
{name: name},function(data){
if(
!(
(URLstuff[2] === "anime" && data.data.User.mediaListOptions.animeList.advancedScoringEnabled)
|| (URLstuff[2] === "manga" && data.data.User.mediaListOptions.mangaList.advancedScoringEnabled)
)
){
return
};
generalAPIcall(
`query($name:String!,$listType:MediaType){
MediaListCollection(userName:$name,type:$listType){
lists{
entries{mediaId advancedScores}
}
}
}`,
{name: name,listType: URLstuff[2].toUpperCase()},
function(data2){
let list = new Map(returnList(data2,true).map(a => [a.mediaId,a.advancedScores]));
let finder = function(){
if(!document.URL.match(/^https:\/\/anilist\.co\/user\/(.+)\/(anime|manga)list\/?/)){
return
};
document.querySelectorAll(
".list-entries .entry .title > a:not(.hohAdvanced)"
).forEach(function(entry){
entry.classList.add("hohAdvanced");
let key = parseInt(entry.href.match(/\/(\d+)\//)[1]);
let dollar = create("span","hohAdvancedDollar","$",entry.parentNode);
let advanced = list.get(key);
let reasonable = Object.keys(advanced).map(
key => [key,advanced[key]]
).filter(
a => a[1]
);
dollar.title = reasonable.map(
a => a[0] + ": " + a[1]
).join("\n");
if(!reasonable.length){
dollar.style.display = "none"
}
});
setTimeout(finder,1000);
};finder();
}
)
})
};
exportModule({
id: "webmResize",
description: "Resize videos with a width in the URL hash (like #220 or #40%)",
isDefault: true,
categories: ["Feeds"],
visible: true
})
if(useScripts.webmResize){
setInterval(function(){
document.querySelectorAll("source").forEach(video => {
let hashMatch = (video.src || "").match(/#(image)?(\d+(\.\d+)?%?)$/);
if(hashMatch && !video.parentNode.width){
video.parentNode.setAttribute("width",hashMatch[2])
}
if(video.src.match(/#image\d*(\.\d+)?%?$/)){
video.parentNode.removeAttribute("controls")
}
})
},500)
}
function yearStepper(){
if(!location.pathname.match(/\/user\/.*\/(anime|manga)list/)){
return
}
let slider = document.querySelector(".el-slider");
if(!slider){
setTimeout(yearStepper,200);
return
};
const maxYear = parseInt(slider.getAttribute("aria-valuemax"));
const minYear = parseInt(slider.getAttribute("aria-valuemin"));
const yearRange = maxYear - minYear;
let clickSlider = function(year){//thanks, mator!
let runway = slider.children[0];
let r = runway.getBoundingClientRect();
const x = r.left + r.width * ((year - minYear) / yearRange);
const y = r.top + r.height / 2;
runway.dispatchEvent(new MouseEvent("click",{
clientX: x,
clientY: y
}))
};
let adjuster = function(delta){
let heading = slider.previousElementSibling;
if(heading.children.length === 0){
if(delta === -1){
clickSlider(maxYear)
}
else{
clickSlider(minYear)
}
}
else{
let current = parseInt(heading.children[0].innerText);
clickSlider(current + delta);
}
};
if(document.querySelector(".hohStepper")){
return
};
slider.style.position = "relative";
let decButton = create("span","hohStepper","<",slider,"left:-27px;font-size:200%;top:0px;");
let incButton = create("span","hohStepper",">",slider,"right:-27px;font-size:200%;top:0px;");
decButton.onclick = function(){
adjuster(-1)
};
incButton.onclick = function(){
adjuster(1)
}
}
exportModule({
id: "youtubeFullscreen",
description: "Enable fullscreen button on youtube videos",
isDefault: false,
categories: ["Feeds"],
visible: true
})
if(useScripts.youtubeFullscreen){
setInterval(function(){
document.querySelectorAll(".youtube iframe").forEach(video => {
if(!video.hasAttribute("allowfullscreen")){
video.setAttribute("allowfullscreen","allowfullscreen");
video.setAttribute("frameborder","0")
}
})
},1000)
}
//create your own module
//make a javascript file, called yourModule.js, in the directory "modules"
//include the following code:
exportModule({
id: "howto",//an unique identified for your module
description: "what your module does",
extendedDescription: `
A long description of what your module does.
This appears when people click the "more info" icon (🛈) on the settings page.
`,
isDefault: false,
importance: 0,//a number, which determines the order of the settings page. Higher numbers are more important. Leave it as 0 if unsure.
categories: ["Script"],//what categories your module belongs in
//Notifications, Feeds, Forum, Lists, Profiles, Stats, Media, Navigation, Browse, Script, Login, Newly Added
visible: false,//if the module should be visible in the settings
urlMatch: function(url,oldUrl){//a function that returns true when on the parts of the site you want it to run. url is the current url, oldUrl is the previous page
//example: return url === "https://anilist.co/reviews"
return false;
},
code: function(){
//your code goes here
},
css: ""//css rules you need
})
//your module can also have extra code and utility functions
})()
//Automail built at 1601898650