Last active
December 24, 2019 00:17
-
-
Save noaione/58cdd25a1cc19388021deb0a77582c97 to your computer and use it in GitHub Desktop.
Discord.py Pagination example for anyone to use. I'm using it for Anime/MangaSynopsys list API from Anilist.co that have multiple list of data (json format)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
import time | |
from datetime import datetime, timedelta | |
import aiohttp | |
import discord | |
import discord.ext.commands as commands | |
""" | |
Note: I'm using Discord.py version 1.2.5 (Not async version) | |
""" | |
""" | |
Variable | |
""" | |
prefix = 'z!' | |
token = 'Enter your bot token here or just copy the things below' | |
bot = commands.Bot(commands.when_mentioned_or(prefix)) | |
anilist_query = ''' | |
query ($page: Int, $perPage: Int, $search: String) { | |
Page (page: $page, perPage: $perPage) { | |
pageInfo { | |
total | |
currentPage | |
lastPage | |
hasNextPage | |
perPage | |
} | |
media(search: $search, type: %s) { | |
id | |
idMal | |
title { | |
romaji | |
english | |
native | |
} | |
coverImage { | |
large | |
} | |
averageScore | |
chapters | |
volumes | |
episodes | |
format | |
status | |
source | |
genres | |
description(asHtml:false) | |
startDate { | |
year | |
month | |
day | |
} | |
endDate { | |
year | |
month | |
day | |
} | |
nextAiringEpisode { | |
airingAt | |
timeUntilAiring | |
episode | |
} | |
} | |
} | |
} | |
''' | |
def monthintext(number): | |
idn = ["January", "February", "March", "April", | |
"May", "June", "July", "August", | |
"September", "October", "November", "December"] | |
if number is None: | |
return "Unknown" | |
x = number - 1 | |
if x < 0: | |
return "Unknown" | |
return idn[number - 1] | |
def create_time_format(secs): | |
months = int(secs // 2592000) # 30 days format | |
secs -= months * 2592000 | |
days = int(secs // 86400) | |
secs -= days * 86400 | |
hours = int(secs // 3600) | |
secs -= hours * 3600 | |
minutes = int(secs // 60) | |
secs -= minutes * 60 | |
return_text = '' | |
if months != 0: | |
return_text += '{} months '.format(months) | |
return return_text + '{} days {} hours {} minutes {} seconds left'.format(days, hours, minutes, secs) | |
def html2markdown(text): | |
re_list = { | |
'<br>': '\n', | |
'</br>': '\n', | |
'<i>': '*', | |
'</i>': '*', | |
'<b>': '**', | |
'</b>': '**', | |
'\n\n': '\n' | |
} | |
for k, v in re_list.items(): | |
text = text.replace(k, v) | |
return text | |
async def fetch_anilist(title, method): | |
variables = { | |
'search': title, | |
'page': 1, | |
'perPage': 50 | |
} | |
async with aiohttp.ClientSession() as sesi: | |
try: | |
async with sesi.post('https://graphql.anilist.co', json={'query': anilist_query % method.upper(), 'variables': variables}) as r: | |
try: | |
data = await r.json() | |
except IndexError: | |
return 'ERROR: Terjadi kesalahan internal' | |
if r.status != 200: | |
if r.status == 404: | |
return "Tidak ada hasil." | |
elif r.status == 500: | |
return "ERROR: Internal Error :/" | |
try: | |
query = data['data']['Page']['media'] | |
except IndexError: | |
return "Tidak ada hasil." | |
except aiohttp.ClientError: | |
return 'ERROR: Koneksi terputus' | |
# Koleksi translasi dan perubahan teks | |
status_tl = { | |
'finished': 'Tamat', | |
'releasing': 'Sedang Berlangsung', | |
'not_yet_released': 'Belum Rilis', | |
'cancelled': 'Batal Tayang' | |
} | |
format_tl = { | |
"TV": "Anime", | |
"TV_SHORT": "Anime Pendek", | |
"MOVIE": "Film", | |
"SPECIAL": "Spesial", | |
"OVA": "OVA", | |
"ONA": "ONA", | |
"MUSIC": "MV", | |
"NOVEL": "Novel", | |
"MANGA": "Manga", | |
"ONE_SHOT": "One-Shot", | |
None: "Lainnya" | |
} | |
source_tl = { | |
"ORIGINAL": "Original", | |
"MANGA": "Manga", | |
"VISUAL_NOVEL": "Visual Novel", | |
"LIGHT_NOVEL": "Novel Ringan", | |
"VIDEO_GAME": "Gim", | |
"OTHER": "Lainnya", | |
None: "Lainnya" | |
} | |
if not query: | |
return "Tidak ada hasil." | |
full_query_result = [] | |
for entry in query: | |
start_y = entry['startDate']['year'] | |
end_y = entry['endDate']['year'] | |
if not start_y: | |
start = 'Belum Rilis' | |
else: | |
start = '{}'.format(start_y) | |
start_m = entry['startDate']['month'] | |
if start_m: | |
start = '{}/{}'.format(start, start_m) | |
start_d = entry['startDate']['day'] | |
if start_d: | |
start = '{}/{}'.format(start, start_d) | |
if not end_y: | |
end = 'Belum Berakhir' | |
else: | |
end = '{}'.format(end_y) | |
end_m = entry['endDate']['month'] | |
if end_m: | |
end = '{}/{}'.format(end, end_m) | |
end_d = entry['endDate']['day'] | |
if end_d: | |
end = '{}/{}'.format(end, end_d) | |
title = entry['title']['romaji'] | |
ani_id = str(entry['id']) | |
try: | |
mal_id = str(entry['idMal']) | |
except: | |
mal_id = None | |
other_title = entry['title']['native'] | |
english_title = entry['title']['english'] | |
if english_title: | |
if other_title: | |
other_title += '\n' + english_title | |
else: | |
other_title = english_title | |
score_rate = None | |
score_rate_anilist = entry['averageScore'] | |
if score_rate: | |
score_rate = '{}/10'.format(score_rate_anilist/10) | |
description = entry['description'] | |
if description is not None: | |
description = html2markdown(description) | |
if len(description) > 1023: | |
description = description[:1020] + '...' | |
genres = ', '.join(entry['genres']).lower() | |
status = entry['status'].lower() | |
img = entry['coverImage']['large'] | |
ani_link = 'https://anilist.co/{m}/{id}'.format(m=method, id=ani_id) | |
dataset = { | |
'title': title, | |
'title_other': other_title, | |
'start_date': start, | |
'end_date': end, | |
'poster_img': img, | |
'synopsis': description, | |
'status': status.capitalize(), | |
'format': entry['format'].capitalize(), | |
'source_fmt': entry['source'].capitalize(), | |
'link': ani_link, | |
'score': score_rate, | |
'footer': "ID: {} | {}".format(ani_id, genres) | |
} | |
if method == 'manga': | |
vol = entry['volumes'] | |
ch = entry['chapters'] | |
ch_vol = '{c} chapterXXC/{v} volumeXXV'.format(c=ch, v=vol).replace('None', '??') | |
if ch: | |
if ch > 1: | |
ch_vol = ch_vol.replace('XXC', 's') | |
ch_vol = ch_vol.replace('XXC', '') | |
if vol: | |
if vol > 1: | |
ch_vol = ch_vol.replace('XXV', 's') | |
ch_vol = ch_vol.replace('XXV', '') | |
dataset['ch_vol'] = ch_vol | |
if method == 'anime': | |
dataset['episodes'] = entry["episodes"] | |
if status in ['releasing', 'not_yet_released']: | |
ne_data = entry['nextAiringEpisode'] | |
if ne_data: | |
airing_time = ne_data['airingAt'] | |
d_airing_time = timedelta(seconds=abs(airing_time)) | |
time_tuple = datetime(1,1,1) + d_airing_time | |
dataset['airing_date'] = '{d} {m} {y}'.format(d=time_tuple.day, m=monthintext(time_tuple.month), y=time.strftime('%Y')) | |
dataset['next_episode'] = ne_data['episode'] | |
dataset['time_remain'] = create_time_format(ne_data['timeUntilAiring']) | |
for k, v in dataset.items(): | |
if not v: | |
dataset[k] = 'No Data' | |
full_query_result.append(dataset) | |
return {'result': full_query_result, 'data_total': len(full_query_result)} | |
@bot.command() | |
async def anime(ctx, *, title): | |
"""Search anime information using Anilist GraphQL API.""" | |
print('[@] Searching anime: {}'.format(title)) | |
aqres = await fetch_anilist(title, 'anime') | |
if isinstance(aqres, str): | |
return await ctx.send(aqres) | |
max_page = aqres['data_total'] | |
resdata = aqres['result'] | |
print('\t>> Total result: {}'.format(max_page)) | |
first_run = True | |
time_table = False | |
num = 1 | |
while True: | |
if first_run: | |
print('\t>> Showing result') | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Episode", value=data['episodes'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
first_run = False | |
msg = await ctx.send(embed=embed) | |
reactmoji = [] | |
if time_table: | |
reactmoji.append('👍') | |
elif max_page == 1 and num == 1: | |
pass | |
elif num == 1: | |
reactmoji.append('⏩') | |
elif num == max_page: | |
reactmoji.append('⏪') | |
elif num > 1 and num < max_page: | |
reactmoji.extend(['⏪', '⏩']) | |
if 'next_episode' in data and not time_table: | |
reactmoji.append('⏳') | |
reactmoji.append('✅') | |
for react in reactmoji: | |
await msg.add_reaction(react) | |
def check_react(reaction, user): | |
if reaction.message.id != msg.id: | |
return False | |
if user != ctx.message.author: | |
return False | |
if str(reaction.emoji) not in reactmoji: | |
return False | |
return True | |
try: | |
res, user = await bot.wait_for('reaction_add', timeout=30.0, check=check_react) | |
except asyncio.TimeoutError: | |
return await msg.clear_reactions() | |
if user != ctx.message.author: | |
pass | |
elif '⏪' in str(res.emoji): | |
print('<< Going backward') | |
num = num - 1 | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Episode", value=data['episodes'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '⏩' in str(res.emoji): | |
print('\t>> Going forward') | |
num = num + 1 | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Episode", value=data['episodes'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '👍' in str(res.emoji): | |
print('<< Reshowing anime info') | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Episode", value=data['episodes'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
time_table = False | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '⏳' in str(res.emoji): | |
print('\t>> Showing next episode airing time') | |
ep_txt = 'Episode ' + str(data['next_episode']) | |
embed = discord.Embed(color=0x19212d) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text='Airing at {}'.format(data['airing_date'])) | |
embed.add_field(name=ep_txt, value=data['time_remain'], inline=False) | |
time_table = True | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '✅' in str(res.emoji): | |
await ctx.message.delete() | |
return await msg.delete() | |
@bot.command() | |
async def manga(ctx, *, title): | |
"""Search anime information using Anilist GraphQL API.""" | |
print('[@] Searching manga: {}'.format(title)) | |
aqres = await fetch_anilist(title, 'manga') | |
if isinstance(aqres, str): | |
return await ctx.send(aqres) | |
max_page = aqres['data_total'] | |
resdata = aqres['result'] | |
print('\t>> Total result: {}'.format(max_page)) | |
first_run = True | |
num = 1 | |
while True: | |
if first_run: | |
print('\t>> Showing result') | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Chapter/Volume", value=data['ch_vol'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
first_run = False | |
msg = await ctx.send(embed=embed) | |
reactmoji = [] | |
if max_page == 1 and num == 1: | |
pass | |
elif num == 1: | |
reactmoji.append('⏩') | |
elif num == max_page: | |
reactmoji.append('⏪') | |
elif num > 1 and num < max_page: | |
reactmoji.extend(['⏪', '⏩']) | |
reactmoji.append('✅') | |
for react in reactmoji: | |
await msg.add_reaction(react) | |
def check_react(reaction, user): | |
if reaction.message.id != msg.id: | |
return False | |
if user != ctx.message.author: | |
return False | |
if str(reaction.emoji) not in reactmoji: | |
return False | |
return True | |
try: | |
res, user = await bot.wait_for('reaction_add', timeout=30.0, check=check_react) | |
except asyncio.TimeoutError: | |
return await msg.clear_reactions() | |
if user != ctx.message.author: | |
pass | |
elif '⏪' in str(res.emoji): | |
print('<< Going backward') | |
num = num - 1 | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Chapter/Volume", value=data['ch_vol'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '⏩' in str(res.emoji): | |
print('\t>> Going forward') | |
num = num + 1 | |
data = resdata[num - 1] | |
embed = discord.Embed(color=0x19212d) | |
embed.set_thumbnail(url=data['poster_img']) | |
embed.set_author(name=data['title'], url=data['link'], icon_url="https://anilist.co/img/icons/apple-touch-icon-152x152.png") | |
embed.set_footer(text=data['footer']) | |
embed.add_field(name="Other Names", value=data['title_other'], inline=True) | |
embed.add_field(name="Chapter/Volume", value=data['ch_vol'], inline=True) | |
embed.add_field(name="Status", value=data['status'], inline=True) | |
embed.add_field(name="Scores", value=data['score'], inline=True) | |
embed.add_field(name="Released", value=data['start_date'], inline=True) | |
embed.add_field(name="Ended", value=data['end_date'], inline=True) | |
embed.add_field(name="Format", value=data['format'], inline=True) | |
embed.add_field(name="Source Material", value=data['source_fmt'], inline=True) | |
embed.add_field(name="Synopsis", value=data['synopsis'], inline=False) | |
await msg.clear_reactions() | |
await msg.edit(embed=embed) | |
elif '✅' in str(res.emoji): | |
await ctx.message.delete() | |
return await msg.delete() | |
@bot.event | |
async def on_ready(): | |
print('Connected.') | |
presence = '{}help for... help.'.format(prefix) | |
await bot.change_presence(game=discord.Game(name=presence)) | |
print('---------------------------------------------------------------') | |
print('Logged in as:') | |
print('Bot name: ' + bot.user.name) | |
print('With Client ID: ' + bot.user.id) | |
print('---------------------------------------------------------------') | |
bot.run(token) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment