Skip to content
Completed Version - v2.2

Manga Functions

Installations needed

NPM Package Install
npm install aoi.js
npm install jikan4.js
npm install fs

Basic Info

This wiki is all about the use of Jikan package for interacting with the MAL(My Anime List) API and getting the manga, character info from it.

This gives the information as per the result type specified in the custom function and you can make it into an embed like the normal AOI uses. This code is for those who are fans of manga and wanna make such a system but, getting it hard to code. Use this function as I say or you can also edit this if you have experience in DJS and API interacts.

Index config

Before heading to the custom funcs code, we need to config our index.js a little so that instead of cluttering the index with random custom funcs, we do that in a seperate directory(folder). Here, we use a directory named customFunctions.

Here is the thing that you must add in your index, probably towards the end of it.

index.js
const funcsdir = "./customFunctions/";
const fs = require('fs');
files = fs.readdirSync(funcsdir);
files.forEach((file) => {
let func = require(funcsdir+file);
func(bot);
// Replace "bot" with "client" if necessary.
});

Custom Function Code

This is split into two parts, anime and manga. We here discuss the manga part only whereas, I have also made the anime part and posted it here. Also, the basic custom function which both of em needs and that is, the $getID which gets Manga ID from its name.

So, let’s head to the important part AKA the code!

$getID

This function deals with the fetching of anime/manga ID from its name. This also simplifies the code as we don’t need to fetch ID every time to get it’s info. Instead, we use this function alongside the other to fetch ID as other funcs has manga ID parameter NOT manga name.

getID.js
const Jikan = require('jikan4.js');
const JIKAN_CLIENT = new Jikan.Client();
// Edit the "bot" as per your config in index.js
// Replace it with "client" if necessary.
module.exports = (bot) => {
bot.functionManager.createFunction({
name: "$getID",
type: "djs",
code: async d => {
const data = d.util.aoiFunc(d);
if (data.err) return d.error(data.err);
const [name, type = 'Anime'] = data.inside.splits;
if (!name) {
return d.aoiError.fnError(d, "custom", {}, "Anime/Manga name not specified");
}
if (!type) return d.aoiError.fnError(d, "custom", {}, "Type not specified.")
let searchResults;
let foundID;
const ty = type.toLowerCase().trim();
const n = name.trim();
if (ty != 'anime' && ty != 'manga') {
return d.aoiError.fnError(d, "custom", {}, "Invalid type specified. Either Manga / Anime only");
}
switch (ty) {
case 'anime':
searchResults = await JIKAN_CLIENT.anime.search(n);
break;
case 'manga':
searchResults = await JIKAN_CLIENT.manga.search(n);
break;
}
const quarterLength = Math.ceil(searchResults.length / 4);
for (let i = 0; i < quarterLength; i++) {
const result = searchResults[i].title.default.toLowerCase();
if (n === result) {
foundID = searchResults[i].id;
} else {
foundID = searchResults[0].id;
}
}
if (!foundID) {
foundID = "NFError";
}
data.result = foundID;
return {
code: d.util.setCode(data),
};
},
});
}

$mangaInfo

This function deals with getting info of the manga from the ID passed and returns the output as per the parameter passed. It has 2 parameters.

mangaInfo.js
const Jikan = require('jikan4.js');
const JIKAN_CLIENT = new Jikan.Client();
// Replace "bot" with "client" if necessary (as per your index.js config
module.exports = (bot) => {
bot.functionManager.createFunction({
name: "$mangaInfo",
type: "djs",
code: async d => {
const data = d.util.aoiFunc(d);
if (data.err) return d.error(data.err);
const [mangaID, res] = data.inside.splits;
if (!mangaID) return d.aoiError.fnError(d, "custom", {}, "Anime ID not provided!")
if (!res) return d.aoiError.fnError(d, "custom", {}, "No result type specified.")
const mID = mangaID.trim()
const resu = res.trim().toLowerCase()
const type = ['title', 'synopsis', 'background', 'image', 'ratings', 'genre', 'url', 'volumes', 'serializations', 'year', 'author', 'popularity'];
if (!type.includes(resu)) return d.aoiError.fnError(d, "custom", {}, "Invalid result type.");
const manga = await JIKAN_CLIENT.manga.get(mID);
if (!manga) return d.aoiError.fnError(d, "custom", {}, "Invalid manga ID.");
const stats = await JIKAN_CLIENT.manga.getStatistics(mID);
let genres = manga.genres.map(genre => genre.name).join(', ');
if (!genres || genres.trim() === '') {
genres = 'No genre information.';
}
let ratings = '';
if (stats.scores) {
let totalScore = 0;
let totalVotes = 0;
for (const obj of stats.scores) {
totalScore += obj.score * obj.votes;
totalVotes += obj.votes;
}
const averageScore = totalScore / totalVotes;
ratings = `Average score based off ${totalVotes.toLocaleString()} votes: ${averageScore.toFixed(2) + ' / 10'}`;
} else {
ratings = 'Rating information not found.';
}
//SYNOPSIS, URL, EPISODES, GENRES, RATINGS, ETC.
const SYNOPSIS = manga.synopsis ?? "Synopsis not found!";
const BACKGROUND = manga.background ?? "Background not found!";
const URL = manga.url ?? 'URL not found.';
const GENRES = genres;
const RATINGS = ratings;
const TITLE = manga.title.default;
const IMAGE = manga.image.webp.default;
const YEAR = manga.publishInfo?.publishedFrom?.getFullYear() ?? 'Year information not found.';
const AUTHOR = manga.authors[0].name ?? "Author information unavailable!";
const VOLUMES = manga.volumes?.toLocaleString() ?? "Volume information not found!";
const POPULARITY = manga.popularity?.toLocaleString() ?? "Popularity information not found!";
const SERIALIZATIONS = manga.serializations[0]?.name ?? "Serialization info not found!";
let result = '';
switch(resu) {
case 'title':
result = TITLE;
break;
case 'synopsis':
result = SYNOPSIS;
break;
case 'background':
result = BACKGROUND;
break;
case 'url':
result = URL;
break;
case 'genre':
result = GENRES;
break;
case 'image':
result = IMAGE;
break;
case 'ratings':
result = RATINGS;
break;
case 'author':
result = AUTHOR;
break;
case 'year':
result = YEAR;
break;
case 'volumes':
result = VOLUMES;
break;
case 'popularity':
result = POPULARITY;
break;
case 'serializations':
result = SERIALIZATIONS;
break;
}
data.result = result;
return {
code: d.util.setCode(data),
};
},
})
}

$mangaCharInfo

This function deals with getting the character info of the character and anime(which includes the character) passed. The output will be dependent on the parameter you pass. It has 3 parameters.

mangaCharacterInfo.js
const Jikan = require('jikan4.js');
const JIKAN_CLIENT = new Jikan.Client();
function getFirstName(characterName, databaseNames) {
let res = false;
const nameParts = databaseNames.split(',').map(part => part.trim().toLowerCase());
if (nameParts.includes(characterName.toLowerCase())) {
res = true;
};
return res;
};
function getDescription(characterDescription) {
let description;
if (!characterDescription) {
return description = 'Description not found.';
} else {
if (characterDescription.length > 1024) {
const midPoint = characterDescription.lastIndexOf('.', 1024);
if (midPoint !== -1) {
const descriptionFirstPart = characterDescription.substring(0, midPoint + 1);
description = descriptionFirstPart;
}
} else {
description = characterDescription
}
}
return description;
}
// Replace "bot" with "client" if necessary (as per your index.js config
module.exports = (bot) => {
bot.functionManager.createFunction({
name: "$mangaCharInfo",
type: "djs",
code: async d => {
const data = d.util.aoiFunc(d);
if (data.err) return d.error(data.err);
const [mangaID, character, res] = data.inside.splits;
if (!mangaID) return d.aoiError.fnError(d, "custom", {}, "Anime ID not provided.");
if (!character) return d.aoiError.fnError(d, "custom", {}, "Character not provided.");
if (!res) return d.aoiError.fnError(d, "custom", {}, "Enter the result type.")
const mID = mangaID.trim();
const characterName = character.trim().toLowerCase();
const resu = res.trim().toLowerCase();
const type = ["name", "url", , "image", "nicknames", "role", "description", "manga"];
if (!type.includes(resu)) return d.aoiError.fnError(d, "custom", {}, "Invalid result type entered.")
const manga = await JIKAN_CLIENT.manga.get(mID);
if (!manga) return d.aoiError.fnError(d, "custom", {}, "Manga not found.");
const ch = await JIKAN_CLIENT.manga.getCharacters(mID);
let description = '';
let characterObj;
let characterFound = false;
let characterArr = [];
let result = '';
for (let i = 0; i < ch.length; i++) {
if (getFirstName(characterName, (ch[i].character.name).toLowerCase())) {
characterArr.push(ch[i]);
characterFound = true;
}
}
if (!characterFound) {
result = "MCNFError";
}
let i = 0;
if (characterFound) {
characterObj = await JIKAN_CLIENT.characters.getFull(characterArr[i].character.id);
if (characterObj) {
description = getDescription(characterObj.about);
} else {
description = 'Description not found.';
}
let nicknames = '';
if (characterObj.nicknames[0] === undefined) {
nicknames = 'None';
} else {
nicknames = characterObj.nicknames.map(item => item).join(', ');
}
//last calling of vars
const CHARACTER = characterArr[i].character.name;
const URL = characterArr[i].character.url;
const MANGA = manga.title.default;
const ROLE = characterArr[i]?.role ?? 'Role information not found.';
const DESCRIPTION = description;
const IMAGE = characterArr[i].character.image.webp.default;
const NICKNAMES = nicknames;
switch(resu) {
case 'name':
result = CHARACTER;
break;
case 'url':
result = URL;
break;
case 'image':
result = IMAGE;
break;
case 'nicknames':
result = NICKNAMES;
break;
case 'manga':
result = MANGA;
break;
case 'description':
result = DESCRIPTION;
break;
case 'role':
result = ROLE;
break;
}
}
data.result = result;
return {
code: d.util.setCode(data),
};
}
})
}

And these are the codes you must add in your /customFunctions/ or whatever you call it directory.

Syntax & Tables

Now, let’s know the syntax and other thing on how to use the above functions.

Syntax Table

The syntax is:

$getID[name;type?]
OptionTypeOptionalExample
NameStringfalseAny anime/manga name as per type
TypeStringtrueSpecify anime(default) or manga

Use like: $getID[$message/manga_name;manga(else anime)]


Result Type Table

Now, let’s head towards the table for the result. Or rather, what you must put in the result parameter in both cases.

OptionOutput
titleReturns the name of manga
synopsisReturns the synopsis of manga
backgroundReturns the background of manga
urlReturns the URL of manga in MAL website
genreReturns the genre of manga
imageReturns image of manga
ratingsReturns the rating of manga
popularityReturns popularity of manga
yearReturns release year of manga
authorReturns author of manga
serializationsReturns the serializations of manga
volumesReturns volume count of manga

Error Table

There are two errors which will be most common when making a command out of it and that should be caught. They are Not Found (Anime/Manga) and Manga Character Not Found. I have made error catching for this which can be caught using $onlyIf which makes sure that you won’t see unnecessary AOIErrors while making embeds.

ErrorCatchFunction
Not Found (Anime/Manga)NFError$getID
Manga Character Not FoundMCNFError$mangaCharInfo

Usage:

  • $onlyIf[$getID[manga_name;manga]!=NFError;]
  • $onlyIf[$mangaCharInfo[$getID[manga_name];character;type]!=MCNFError;]

Important reference

Credits