diff --git a/README.md b/README.md
index 867c15f2..79e99872 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ Xiao is a Discord bot coded in JavaScript with
7. Run `npm i -g pm2` to install PM2.
8. Run `pm2 start Xiao.js --name xiao` to run the bot.
-## Commands (333)
+## Commands (334)
### Utility:
* **eval:** Executes JavaScript code.
@@ -159,6 +159,7 @@ Xiao is a Discord bot coded in JavaScript with
* **azur-lane:** Responds with information on an Azur Lane ship.
* **book:** Searches Google Books for a book.
* **bulbapedia:** Searches Bulbapedia for your query.
+* **character:** Searches AniList for your query, getting character results.
* **danbooru:** Responds with an image from Danbooru, with optional query.
* **define:** Defines a word.
* **derpibooru:** Responds with an image from Derpibooru.
diff --git a/commands/search/anime.js b/commands/search/anime.js
index 78462b8d..382a288d 100644
--- a/commands/search/anime.js
+++ b/commands/search/anime.js
@@ -18,11 +18,15 @@ const resultGraphQL = stripIndents`
english
userPreferred
}
- coverImage { large }
+ coverImage {
+ large
+ medium
+ }
startDate { year }
- description
+ description(asHtml: false)
season
type
+ siteUrl
status
episodes
isAdult
@@ -30,6 +34,18 @@ const resultGraphQL = stripIndents`
}
}
`;
+const seasons = {
+ WINTER: 'Winter',
+ SPRING: 'Spring',
+ SUMMER: 'Summer',
+ FALL: 'Fall'
+};
+const statuses = {
+ FINISHED: 'Finished',
+ RELEASING: 'Releasing',
+ NOT_YET_RELEASED: 'Unreleased',
+ CANCELLED: 'Cancelled'
+};
module.exports = class AnimeCommand extends Command {
constructor(client) {
@@ -58,14 +74,14 @@ module.exports = class AnimeCommand extends Command {
const embed = new MessageEmbed()
.setColor(0x02A9FF)
.setAuthor('AniList', 'https://i.imgur.com/iUIRC7v.png', 'https://anilist.co/')
- .setURL(`https://anilist.co/anime/${anime.id}`)
- .setThumbnail(anime.coverImage.large || null)
+ .setURL(anime.siteUrl)
+ .setThumbnail(anime.coverImage.large || anime.coverImage.medium || null)
.setTitle(anime.title.english || anime.title.userPreferred)
- .setDescription(anime.description ? shorten(anime.description.replace(/(
)+/g, '\n')) : 'No description.')
- .addField('❯ Status', anime.status, true)
- .addField('❯ Episodes', anime.episodes, true)
- .addField('❯ Season', `${anime.season} ${anime.startDate.year}`, true)
- .addField('❯ Average Score', `${anime.meanScore}/100`, true);
+ .setDescription(anime.description ? shorten(anime.description) : 'No description.')
+ .addField('❯ Status', statuses[anime.status], true)
+ .addField('❯ Episodes', anime.episodes || '???', true)
+ .addField('❯ Season', anime.season ? `${seasons[anime.season]} ${anime.startDate.year}` : '???', true)
+ .addField('❯ Average Score', anime.meanScore ? `${anime.meanScore}/100` : '???', true);
return msg.embed(embed);
} catch (err) {
return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
diff --git a/commands/search/character.js b/commands/search/character.js
new file mode 100644
index 00000000..839d31c4
--- /dev/null
+++ b/commands/search/character.js
@@ -0,0 +1,105 @@
+const Command = require('../../structures/Command');
+const { MessageEmbed } = require('discord.js');
+const request = require('node-superfetch');
+const { stripIndents } = require('common-tags');
+const { shorten } = require('../../util/Util');
+const searchGraphQL = stripIndents`
+ query ($search: String) {
+ characters: Page (perPage: 1) {
+ results: characters (search: $search) { id }
+ }
+ }
+`;
+const resultGraphQL = stripIndents`
+ query ($id: Int!) {
+ Character (id: $id) {
+ id
+ name { native }
+ image {
+ large
+ medium
+ }
+ description(asHtml: false)
+ siteUrl
+ media(page: 1, perPage: 10) {
+ edges {
+ node {
+ title {
+ english
+ userPreferred
+ }
+ type
+ siteUrl
+ }
+ }
+ }
+ }
+ }
+`;
+const types = {
+ ANIME: 'Anime',
+ MANGA: 'Manga'
+};
+
+module.exports = class CharacterCommand extends Command {
+ constructor(client) {
+ super(client, {
+ name: 'character',
+ aliases: ['anilist-character', 'anime-character', 'manga-character', 'manga-char', 'ani-char', 'char'],
+ group: 'search',
+ memberName: 'character',
+ description: 'Searches AniList for your query, getting character results.',
+ clientPermissions: ['EMBED_LINKS'],
+ args: [
+ {
+ key: 'query',
+ prompt: 'What character would you like to search for?',
+ type: 'string'
+ }
+ ]
+ });
+ }
+
+ async run(msg, { query }) {
+ try {
+ const id = await this.search(query);
+ if (!id) return msg.say('Could not find any results.');
+ const character = await this.fetchCharacter(id);
+ const embed = new MessageEmbed()
+ .setColor(0x02A9FF)
+ .setAuthor('AniList', 'https://i.imgur.com/iUIRC7v.png', 'https://anilist.co/')
+ .setURL(character.siteUrl)
+ .setThumbnail(character.image.large || character.image.medium || null)
+ .setTitle(character.name.native)
+ .setDescription(character.description ? shorten(character.description) : 'No description.')
+ .addField('❯ Appearances', character.edges.map(edge => {
+ const title = edge.node.title.english || edge.node.title.userPreferred;
+ return `[${title} (${types[edge.node.type]})](${edge.node.siteUrl})`;
+ }).join(', '));
+ return msg.embed(embed);
+ } catch (err) {
+ return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
+ }
+ }
+
+ async search(query) {
+ const { body } = await request
+ .post('https://graphql.anilist.co/')
+ .send({
+ variables: { search: query },
+ query: searchGraphQL
+ });
+ if (!body.data.characters.results.length) return null;
+ return body.data.characters.results[0].id;
+ }
+
+ async fetchCharacter(id) {
+ const { body } = await request
+ .post('https://graphql.anilist.co/')
+ .send({
+ variables: { id },
+ query: resultGraphQL
+ });
+ return body.data.Character;
+ }
+};
diff --git a/commands/search/manga.js b/commands/search/manga.js
index 05c5c938..3d3f44aa 100644
--- a/commands/search/manga.js
+++ b/commands/search/manga.js
@@ -20,7 +20,8 @@ const resultGraphQL = stripIndents`
}
coverImage { large }
startDate { year }
- description
+ description(asHtml: false)
+ siteUrl
type
status
volumes
@@ -30,6 +31,12 @@ const resultGraphQL = stripIndents`
}
}
`;
+const statuses = {
+ FINISHED: 'Finished',
+ RELEASING: 'Releasing',
+ NOT_YET_RELEASED: 'Unreleased',
+ CANCELLED: 'Cancelled'
+};
module.exports = class MangaCommand extends Command {
constructor(client) {
@@ -58,14 +65,14 @@ module.exports = class MangaCommand extends Command {
const embed = new MessageEmbed()
.setColor(0x02A9FF)
.setAuthor('AniList', 'https://i.imgur.com/iUIRC7v.png', 'https://anilist.co/')
- .setURL(`https://anilist.co/manga/${manga.id}`)
- .setThumbnail(manga.coverImage.large || null)
+ .setURL(manga.siteUrl)
+ .setThumbnail(manga.coverImage.large || manga.coverImage.medium || null)
.setTitle(manga.title.english || manga.title.userPreferred)
- .setDescription(manga.description ? shorten(manga.description.replace(/(
)+/g, '\n')) : 'No description.')
- .addField('❯ Status', manga.status, true)
+ .setDescription(manga.description ? shorten(manga.description) : 'No description.')
+ .addField('❯ Status', statuses[manga.status], true)
.addField('❯ Chapters / Volumes', `${manga.chapters || '???'}/${manga.volumes || '???'}`, true)
- .addField('❯ Year', manga.startDate.year, true)
- .addField('❯ Average Score', `${manga.meanScore}/100`, true);
+ .addField('❯ Year', manga.startDate.year || '???', true)
+ .addField('❯ Average Score', manga.meanScore ? `${manga.meanScore}/100` : '???', true);
return msg.embed(embed);
} catch (err) {
return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
diff --git a/package.json b/package.json
index f222e9d0..5bb54f28 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "xiao",
- "version": "101.2.1",
+ "version": "101.3.0",
"description": "Your personal server companion.",
"main": "Xiao.js",
"scripts": {