mirror of
https://gitlab.com/bignutty/labscore.git
synced 2025-06-08 22:23:03 -04:00
[core] new swag paginator
This commit is contained in:
parent
f4a22934c6
commit
b1cc10c6ef
7 changed files with 438 additions and 24 deletions
|
@ -1,6 +1,6 @@
|
||||||
const { Constants, ClusterClient, CommandClient } = require('detritus-client');
|
const { Constants, ClusterClient, CommandClient } = require('detritus-client');
|
||||||
const { createPaginator } = require('./paginator')
|
//const { createPaginator } = require('./paginator')
|
||||||
const Paginator = require('detritus-pagination').PaginatorCluster
|
const Paginator = require('./paginator').PaginatorCluster
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
const cluster = new ClusterClient("", {
|
const cluster = new ClusterClient("", {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
const Paginator = require('detritus-pagination').PaginatorCluster
|
|
||||||
|
|
||||||
const paginators = {}
|
|
||||||
|
|
||||||
function createPaginator(client){
|
|
||||||
return new Paginator(client, {
|
|
||||||
// Maximum number of milliseconds for the bot to paginate
|
|
||||||
// It is recommended not to set this too high
|
|
||||||
// Defaults to 300000ms (5 minutes)
|
|
||||||
maxTime: 300000,
|
|
||||||
// Whether it should jump back to page 1 if the user tried to go past the last page
|
|
||||||
// Defaults to false
|
|
||||||
pageLoop: true,
|
|
||||||
// Whether a page number should be shown in embed footers
|
|
||||||
// If a string is passed as page, it will append the page number to the string
|
|
||||||
pageNumber: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createPaginator
|
|
||||||
}
|
|
7
labscore/paginator/index.js
Normal file
7
labscore/paginator/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
Paginator: require("./structures/Paginator"),
|
||||||
|
PaginatorCluster: require("./structures/PaginatorCluster"),
|
||||||
|
get version() {
|
||||||
|
return require("../package").version;
|
||||||
|
}
|
||||||
|
};
|
139
labscore/paginator/structures/BasePaginator.js
Normal file
139
labscore/paginator/structures/BasePaginator.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
const EventEmitter = require("eventemitter3");
|
||||||
|
const { Context } = require("detritus-client/lib/command");
|
||||||
|
|
||||||
|
module.exports = class BasePaginator extends EventEmitter {
|
||||||
|
constructor(client, data) {
|
||||||
|
super();
|
||||||
|
this.client = client;
|
||||||
|
this.message = BasePaginator.asMessage(data.message);
|
||||||
|
this.commandMessage = data.commandMessage || null;
|
||||||
|
this.pages = data.pages;
|
||||||
|
this.index = 0;
|
||||||
|
this.targetUser = data.targetUser || this.message.author.id;
|
||||||
|
|
||||||
|
// Reference to reply function
|
||||||
|
// Uses context.editOrReply if an instance of Context was passed
|
||||||
|
// Defaults to message.reply
|
||||||
|
this.editOrReply = (data.message.editOrReply || data.message.reply).bind(data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static asMessage(ctx) {
|
||||||
|
return ctx instanceof Context ? ctx.message : ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isShared() {
|
||||||
|
return this.commandMessage instanceof Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCommandMessage(messageId) {
|
||||||
|
if (!this.commandMessage) return false;
|
||||||
|
|
||||||
|
return this.isShared ? this.commandMessage.has(messageId) : this.commandMessage.id === messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInChannel(channelId) {
|
||||||
|
if (!this.commandMessage) return false;
|
||||||
|
|
||||||
|
return this.isShared ? Array.from(this.commandMessage.values()).some(x => x.channelId === channelId) : this.commandMessage.channelId === channelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTarget(user) {
|
||||||
|
return this.targetUser instanceof Set ? this.targetUser.has(user) : this.targetUser === user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(data) {
|
||||||
|
if (this.isShared) {
|
||||||
|
for (const m of this.commandMessage.values()) {
|
||||||
|
await m.edit(data);
|
||||||
|
}
|
||||||
|
} else if (this.commandMessage) {
|
||||||
|
this.commandMessage.edit(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// Create Components
|
||||||
|
let msg = this.pages[this.index];
|
||||||
|
msg.components = await this.client.components(this)
|
||||||
|
return this.commandMessage = await this.editOrReply(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async previous() {
|
||||||
|
if (Array.isArray(this.pages) && this.pages.length > 0) {
|
||||||
|
if (this.client.pageLoop) {
|
||||||
|
await this.update(this.pages[this.index === 0 ? this.index = this.pages.length - 1 : --this.index]);
|
||||||
|
} else if (this.index !== 0) {
|
||||||
|
await this.update(this.pages[--this.index]);
|
||||||
|
} else {
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit("previous", this);
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrevious() {
|
||||||
|
if (Array.isArray(this.pages) && this.pages.length > 0) {
|
||||||
|
if (this.client.pageLoop) {
|
||||||
|
return this.pages[this.index === 0 ? this.index = this.pages.length - 1 : --this.index]
|
||||||
|
} else if (this.index !== 0) {
|
||||||
|
return this.pages[--this.index]
|
||||||
|
} else {
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit("previous", this);
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async next() {
|
||||||
|
if (Array.isArray(this.pages) && this.pages.length > 0) {
|
||||||
|
if (this.client.pageLoop) {
|
||||||
|
await this.update(this.pages[this.index === this.pages.length - 1 ? this.index = 0 : ++this.index]);
|
||||||
|
} else if (this.index !== this.pages.length - 1) {
|
||||||
|
await this.update(this.pages[++this.index]);
|
||||||
|
} else {
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit("next", this);
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNext() {
|
||||||
|
if (Array.isArray(this.pages) && this.pages.length > 0) {
|
||||||
|
if (this.client.pageLoop) {
|
||||||
|
return this.pages[this.index === this.pages.length - 1 ? this.index = 0 : ++this.index]
|
||||||
|
} else if (this.index !== this.pages.length - 1) {
|
||||||
|
return this.pages[++this.index]
|
||||||
|
} else {
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit("next", this);
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async jumpTo(page) {
|
||||||
|
if (isNaN(page) || this.pages[page] === undefined) {
|
||||||
|
throw new Error("Invalid page");
|
||||||
|
}
|
||||||
|
await this.update(this.pages[page]);
|
||||||
|
|
||||||
|
this.emit("page", {
|
||||||
|
page,
|
||||||
|
paginator: this
|
||||||
|
});
|
||||||
|
return this.commandMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(timeout = false) {
|
||||||
|
this.emit("stop", this, timeout);
|
||||||
|
this.removeAllListeners();
|
||||||
|
const targetIndex = this.client.activeListeners.findIndex(v => v.message.id === this.message.id);
|
||||||
|
this.client.activeListeners.splice(targetIndex, 1);
|
||||||
|
// Disable components
|
||||||
|
this.update({components:[]});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
205
labscore/paginator/structures/Paginator.js
Normal file
205
labscore/paginator/structures/Paginator.js
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
const ReactionPaginator = require("./ReactionPaginator");
|
||||||
|
const assert = require("assert");
|
||||||
|
|
||||||
|
const { Constants, Utils } = require('detritus-client')
|
||||||
|
const { Components, ComponentActionRow } = Utils
|
||||||
|
const { InteractionCallbackTypes } = Constants
|
||||||
|
|
||||||
|
const allowedEvents = new Set([
|
||||||
|
"MESSAGE_REACTION_ADD",
|
||||||
|
"MESSAGE_CREATE"
|
||||||
|
]);
|
||||||
|
|
||||||
|
function deprecate(message) {
|
||||||
|
console.warn(`[detritus-pagination] Deprecation warning: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonEmoji = Object.freeze({
|
||||||
|
NEXT: '<:right:977871577758707782>',
|
||||||
|
PREVIOUS: '<:left:977871577532211200>',
|
||||||
|
STOP: '<:ico_trash:929498022386221096>'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { hasOwnProperty } = Object.prototype;
|
||||||
|
|
||||||
|
// Keep track of created instances in a WeakSet to prevent memory leaks
|
||||||
|
// We do this to notify the user when a Paginator is attached to the same client
|
||||||
|
const instances = new WeakSet();
|
||||||
|
|
||||||
|
module.exports = class Paginator {
|
||||||
|
constructor(client, data = {}) {
|
||||||
|
if (instances.has(client)) {
|
||||||
|
deprecate("Avoid attaching multiple Paginators to the same client, as it can lead to memory leaks");
|
||||||
|
} else {
|
||||||
|
instances.add(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
hasOwnProperty.call(client, "gateway"),
|
||||||
|
"Provided `client` has no `gateway` property. Consider using `require('detritus-pagination').PaginatorCluster` if you're using CommandClient."
|
||||||
|
);
|
||||||
|
|
||||||
|
this.client = client;
|
||||||
|
this.maxTime = data.maxTime || 300000;
|
||||||
|
this.pageLoop = typeof data.pageLoop !== "boolean" ? false : data.pageLoop;
|
||||||
|
this.pageNumber = typeof data.pageNumber !== "boolean" ? false : data.pageNumber;
|
||||||
|
this.activeListeners = [];
|
||||||
|
|
||||||
|
this.client.gateway.on("packet", async packet => {
|
||||||
|
const {
|
||||||
|
d: data,
|
||||||
|
t: event
|
||||||
|
} = packet;
|
||||||
|
if (!data) return;
|
||||||
|
if (!allowedEvents.has(event)) return;
|
||||||
|
|
||||||
|
for (const listener of this.activeListeners) {
|
||||||
|
if (!(listener instanceof ReactionPaginator)) continue;
|
||||||
|
if (!listener.commandMessage) continue;
|
||||||
|
|
||||||
|
if (listener.isCommandMessage(data.message_id) &&
|
||||||
|
listener.isTarget(data.user_id)) {
|
||||||
|
await this.handleReactionEvent(data, listener);
|
||||||
|
} else if (event === "MESSAGE_CREATE" &&
|
||||||
|
listener.isInChannel(data.channel_id) &&
|
||||||
|
listener.isTarget(data.user_id) &&
|
||||||
|
listener.waitingForPage) {
|
||||||
|
await this.handleMessageEvent(data, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleButtonEvent(context) {
|
||||||
|
// Get listener
|
||||||
|
let listener;
|
||||||
|
for (const l of this.activeListeners) {
|
||||||
|
if (!(l instanceof ReactionPaginator)) continue;
|
||||||
|
if (!l.commandMessage) continue;
|
||||||
|
|
||||||
|
if (l.isCommandMessage(context.message.id)) {
|
||||||
|
listener = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!listener.isTarget(context.user.id)) {
|
||||||
|
await context.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (context.customId) {
|
||||||
|
case "next":
|
||||||
|
//await context.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE)
|
||||||
|
//listener.next();
|
||||||
|
await context.respond(InteractionCallbackTypes.UPDATE_MESSAGE, await listener.getNext())
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
await context.respond(InteractionCallbackTypes.UPDATE_MESSAGE, await listener.getPrevious())
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
await context.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE)
|
||||||
|
listener.stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Clean up legacy code from ReactionPaginator
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
async handleReactionEvent(data, listener) {
|
||||||
|
switch (data.emoji.name) {
|
||||||
|
case listener.reactions.nextPage:
|
||||||
|
listener.next();
|
||||||
|
break;
|
||||||
|
case listener.reactions.previousPage:
|
||||||
|
listener.previous();
|
||||||
|
break;
|
||||||
|
case listener.reactions.firstPage:
|
||||||
|
listener.jumpTo(0);
|
||||||
|
break;
|
||||||
|
case listener.reactions.lastPage:
|
||||||
|
listener.jumpTo(listener.pages.length - 1);
|
||||||
|
break;
|
||||||
|
case listener.reactions.stop:
|
||||||
|
listener.stop();
|
||||||
|
break;
|
||||||
|
case listener.reactions.skipTo:
|
||||||
|
if (listener.waitingForPage) return;
|
||||||
|
listener.waitingForPage = await this.client.rest.createMessage(data.channel_id, "What page do you want to go to?");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!Object.values(listener.reactions).includes(data.emoji.name)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.emit("raw", data);
|
||||||
|
listener.clearReaction(data.emoji.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMessageEvent(data, listener) {
|
||||||
|
const page = parseInt(data.content, 10);
|
||||||
|
if (isNaN(page)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.jumpTo(page - 1)
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
await listener.waitingForPage.delete();
|
||||||
|
await this.client.rest.deleteMessage(data.channel_id, data.id);
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
listener.waitingForPage = null;
|
||||||
|
}).catch(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
async components(listener) {
|
||||||
|
const components = new Components({
|
||||||
|
timeout: this.expires,
|
||||||
|
run: this.handleButtonEvent.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
components.createButton({
|
||||||
|
customId: "previous",
|
||||||
|
disabled: 0,
|
||||||
|
style: 2,
|
||||||
|
emoji: ButtonEmoji.PREVIOUS
|
||||||
|
});
|
||||||
|
components.createButton({
|
||||||
|
customId: "next",
|
||||||
|
disabled: 0,
|
||||||
|
style: 2,
|
||||||
|
emoji: ButtonEmoji.NEXT
|
||||||
|
});
|
||||||
|
|
||||||
|
//components.createButton({
|
||||||
|
// customId: "stop",
|
||||||
|
// disabled: 0,
|
||||||
|
// style: 2,
|
||||||
|
// emoji: ButtonEmoji.STOP
|
||||||
|
//});
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createReactionPaginator(data) {
|
||||||
|
if (this.pageNumber && Array.isArray(data.pages)) {
|
||||||
|
for (let i = 0; i < data.pages.length; ++i) {
|
||||||
|
const element = data.pages[i];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new ReactionPaginator(this, data);
|
||||||
|
this.activeListeners.push(instance);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
instance.stop(true);
|
||||||
|
}, data.maxTime || this.maxTime);
|
||||||
|
|
||||||
|
if (instance.commandMessage === null && data.pages) {
|
||||||
|
await instance.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
//await instance.addReactions();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
};
|
37
labscore/paginator/structures/PaginatorCluster.js
Normal file
37
labscore/paginator/structures/PaginatorCluster.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
const { ClusterClient } = require("detritus-client");
|
||||||
|
const Paginator = require("./Paginator");
|
||||||
|
const assert = require("assert");
|
||||||
|
|
||||||
|
module.exports = class PaginatorCluster {
|
||||||
|
constructor(clusterClient, data = {}) {
|
||||||
|
assert.ok(
|
||||||
|
clusterClient instanceof ClusterClient,
|
||||||
|
"clusterClient must be an instance of ClusterClient"
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginators = new WeakMap();
|
||||||
|
|
||||||
|
for (const [, client] of clusterClient.shards) {
|
||||||
|
paginators.set(client, new Paginator(client, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = data;
|
||||||
|
this.paginators = paginators;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOrSetPaginator(client) {
|
||||||
|
const cachedPaginator = this.paginators.get(client);
|
||||||
|
if (cachedPaginator) return cachedPaginator;
|
||||||
|
|
||||||
|
const paginator = new Paginator(client, this.data);
|
||||||
|
this.paginators.set(client, paginator);
|
||||||
|
|
||||||
|
return paginator;
|
||||||
|
}
|
||||||
|
|
||||||
|
createReactionPaginator(data) {
|
||||||
|
const targetPaginator = this.findOrSetPaginator(data.message.client);
|
||||||
|
|
||||||
|
return targetPaginator.createReactionPaginator(data);
|
||||||
|
}
|
||||||
|
}
|
48
labscore/paginator/structures/ReactionPaginator.js
Normal file
48
labscore/paginator/structures/ReactionPaginator.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const BasePaginator = require("./BasePaginator");
|
||||||
|
|
||||||
|
module.exports = class ReactionPaginator extends BasePaginator {
|
||||||
|
constructor(client, data) {
|
||||||
|
super(client, data);
|
||||||
|
this.waitingForPage = null;
|
||||||
|
this.reactions = data.reactions || {
|
||||||
|
firstPage: "⏮️",
|
||||||
|
previousPage: "⬅️",
|
||||||
|
nextPage: "➡️",
|
||||||
|
lastPage: "⏭️",
|
||||||
|
skipTo: "🔢",
|
||||||
|
stop: "⏹️"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async addReactions() {
|
||||||
|
if (!this.commandMessage) return;
|
||||||
|
|
||||||
|
for (const reactions of Object.values(this.reactions)) {
|
||||||
|
if (this.isShared) {
|
||||||
|
for (const msg of this.commandMessage.values()) {
|
||||||
|
await msg.react(reactions).catch();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.commandMessage.react(reactions).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this only works if cache is enabled
|
||||||
|
// perhaps add option to use REST API to fetch all reactions?
|
||||||
|
async clearReactions() {
|
||||||
|
const reactions = this.isShared ? Array.from(this.commandMessage.values()).map(x => Array.from(x.reactions.values())).flat() : this.commandMessage.reactions.values();
|
||||||
|
|
||||||
|
for (const reaction of reactions) {
|
||||||
|
this.clearReaction(reaction.emoji.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearReaction(emoji) {
|
||||||
|
const reaction = this.commandMessage.reactions.find(x => x.emoji.name === emoji);
|
||||||
|
|
||||||
|
if (reaction) {
|
||||||
|
reaction.delete(this.message.author.id).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue