Compare commits
287 Commits
d147167682
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ab43d009b0 | |||
| b18c90996e | |||
| 1f4dec517f | |||
| 1704537a13 | |||
| 69c5a106b5 | |||
| a2966b1475 | |||
| d04200742d | |||
| fbb082fee8 | |||
| bb80d22568 | |||
| 721435f039 | |||
| e85d88de69 | |||
| a4730289a0 | |||
| 02be479a15 | |||
| 1b2df7a1cb | |||
| 7eb01dda9d | |||
| c130ef1506 | |||
| 660234acfd | |||
| c0470e89b6 | |||
| 823c398a5a | |||
| 9b84e4ff7c | |||
| e19f11acae | |||
| 0fcd5f125c | |||
| 1316460838 | |||
| ed76ad6044 | |||
| c0036e7471 | |||
| 55812d6422 | |||
| 93b40700e0 | |||
| 345e368faa | |||
| 523f057ea3 | |||
| a0a9383d32 | |||
| 06685d0ad4 | |||
| 17d295043f | |||
| e069b80a1d | |||
| a016217a06 | |||
| 6dcd1b2665 | |||
| 6f369ee441 | |||
| 79611c34ae | |||
| bec6a886b1 | |||
| 0cd23ff738 | |||
| ba8d2f5cb9 | |||
| fdbf05b846 | |||
| 25ec6a1906 | |||
| f73217e282 | |||
| e0122e5365 | |||
| 7ccfe95b33 | |||
| eb48b90c96 | |||
| f62d629b1f | |||
| 2772a16525 | |||
| 502cffbf06 | |||
| 8fde14112b | |||
| dd159fa854 | |||
| dec7d3a50a | |||
| 978e08b713 | |||
| fd9cc47836 | |||
| 75fa8fce66 | |||
| 51691595ce | |||
| 3f64f2e8e5 | |||
| 8db44d917c | |||
| bb66625541 | |||
| 81a73d519f | |||
| b44a3c482e | |||
| b02ac22ab7 | |||
| bfd9f8be0d | |||
| 54840a409a | |||
| a4cbd51dd6 | |||
| a215a981aa | |||
| af43453a84 | |||
| 732a9c1ee7 | |||
| 32cc7389e3 | |||
| 13fb466144 | |||
| 606aa4b72f | |||
| c788a1ff04 | |||
| 4fe8f05ce7 | |||
| 16a4c78441 | |||
| bc12b7aff8 | |||
| ec6e6e0f60 | |||
| 028395b9b2 | |||
| 4821faef3b | |||
| ae1eb37784 | |||
| 776b6b7b3e | |||
| 0e48ee306f | |||
| 6f285c730a | |||
| dbe7bba3fe | |||
| 03378d23df | |||
| ba38b60d4e | |||
| 5bd4b3da19 | |||
| ecbdfae381 | |||
| 2d4426fb5a | |||
| 71768f7b19 | |||
| 8345cb60f6 | |||
| fdc52f6a5f | |||
| 3a705d7024 | |||
| 39f7dc147c | |||
| f8f6844924 | |||
| 5522d0f81e | |||
| 8a9d9b8f15 | |||
| 2e1e19bd55 | |||
| 493d260d44 | |||
| 75f455e27f | |||
| 7818dd69b1 | |||
| c59b2f88c8 | |||
| 472bee74d6 | |||
| ae9d4b6517 | |||
| cc846ac132 | |||
| 53f327acdc | |||
| 5d6dcbf8c1 | |||
| 82d55023b4 | |||
| 2d9bd9d717 | |||
| 881bd240c0 | |||
| bf532fe639 | |||
| c21ca1bc81 | |||
| 430a6b6584 | |||
| c7d490334e | |||
| e1d5140b28 | |||
| 28fcdf2dc7 | |||
| 0fc7ae09df | |||
| 1a741833bc | |||
| 69e64d6422 | |||
| c68e12d965 | |||
| bb9349bd1a | |||
| ec51b5d5ec | |||
| e73bd3af92 | |||
| 171bc65bf4 | |||
| 9dd975cf89 | |||
| 4dc8fb19fb | |||
| bcfd056790 | |||
| 170bb431f5 | |||
| 1188883f20 | |||
| 4fea681a5f | |||
| 513a9f76aa | |||
| 5445802fe1 | |||
| 6ddae9982f | |||
| 2e1e7b2b97 | |||
| 8fa3f489b2 | |||
| d9ae24c8c1 | |||
| 09cb02c622 | |||
| 4fbde804e2 | |||
| a5b2280d95 | |||
| 8fd6f072ac | |||
| dd46202817 | |||
| dedfc1e59a | |||
| 67fa789c37 | |||
| cb6134ce35 | |||
| eac4015e5e | |||
| e96e97c846 | |||
| befc8352f4 | |||
| a8f8834095 | |||
| 94920c469b | |||
| 93aeb20fe1 | |||
| a8d8fafcc6 | |||
| d3a04a0304 | |||
| 5b7d8ecdc5 | |||
| 6909b6fe45 | |||
| 6e200cf3af | |||
| d732ef2c1b | |||
| 272a3503a0 | |||
| d7ef17724d | |||
| 8d1384cd33 | |||
| d225fae91d | |||
| c2c14b461f | |||
| 52543c40c1 | |||
| dd63afbf4c | |||
| d8354cd2de | |||
| 30ec46ff75 | |||
| 9e210dcc09 | |||
| 20d8b65534 | |||
| f415202f28 | |||
| ecbb7156a2 | |||
| dc0ead189d | |||
| cce2db6a71 | |||
| 9640abd7bb | |||
| dbfdd6525a | |||
| 047bdb98da | |||
| f18c7e8b11 | |||
| 7567c9b98f | |||
| 70f1eeaa03 | |||
| e3a0260e61 | |||
| 5e29c0422b | |||
| 6230f9f0d1 | |||
| bcd51de025 | |||
| 218c9f22e5 | |||
| 5a94678f8d | |||
| 4723ef657c | |||
| 785259166e | |||
| 573705066f | |||
| a2ac1de7c8 | |||
| 7350f2f7b9 | |||
| dbc65a006c | |||
| 9ecc7d4d29 | |||
| e41db72c22 | |||
| 67e45f3bac | |||
| 6a2d8b4eda | |||
| dc604ff4a9 | |||
| a39dfb7d6e | |||
| 351dcee051 | |||
| ad014ffc39 | |||
| e5c2a4aad9 | |||
| e198b6b9cd | |||
| a5ffb8c7ac | |||
| 6371dc8b84 | |||
| 0b2611bf3d | |||
| c6e984e411 | |||
| 1a37e13aa9 | |||
| 46f94fe075 | |||
| 752ce397de | |||
| fbfc614dc9 | |||
| d04de003d3 | |||
| 51952c4dfe | |||
| a559a55abd | |||
| 5cb2d91873 | |||
| c3e880cc0a | |||
| 67b42021c7 | |||
| f3659e07b0 | |||
| d792487489 | |||
| 77abdd9d0f | |||
| 971b3d0e8f | |||
| 0609867fc0 | |||
| 93c0ee7f21 | |||
| e950e31a99 | |||
| 9f9647cfe7 | |||
| 2ebd4b3208 | |||
| 267427cdf6 | |||
| faff65eb82 | |||
| 2b483c3fda | |||
| 1c7b992464 | |||
| 11e6130148 | |||
| fc54fdcf33 | |||
| fa7770ea44 | |||
| 712bc0933a | |||
| 574990c9ed | |||
| c5ef41c5ca | |||
| 1ed4d70e89 | |||
| 82a262c2b7 | |||
| 02796cf10f | |||
| 35701510fa | |||
| 0626be1ec3 | |||
| 56bdca8c21 | |||
| 491af971dc | |||
| 6bfb2f028f | |||
| 453aa99f7d | |||
| 426e30bb9b | |||
| 105884e862 | |||
| 5363f91d2b | |||
| be78ea71c6 | |||
| 397454468b | |||
| 09fd78bd79 | |||
| bb4049ac5e | |||
| 0eee329d3a | |||
| 3b8270e9a7 | |||
| f5ba580eb1 | |||
| ace5721457 | |||
| 72891fc374 | |||
| 348ee8ea2a | |||
| 47d33f6667 | |||
| ce435d01a1 | |||
| 71031af2ce | |||
| 684c6b2377 | |||
| 894e0bfab8 | |||
| 38b2331eb6 | |||
| ba5e53e4ff | |||
| bc90f41268 | |||
| 097d186f58 | |||
| cf3adae647 | |||
| 6926e6c7e5 | |||
| 2c166ecdb2 | |||
| ebbaec9e0e | |||
| 53db080d92 | |||
| 0b989fb317 | |||
| c3c1a39227 | |||
| abaf60e259 | |||
| 7bc66164f4 | |||
| 1f05770a2d | |||
| ff3fb6756c | |||
| 9548ff3a54 | |||
| e4ecdfb84f | |||
| 70ef295b3b | |||
| 9ae9db12a2 | |||
| 5deb5935c7 | |||
| 17e2e0a18b | |||
| 43efb787f8 | |||
| 0cb95e2b0a | |||
| 43d19d0808 | |||
| 9fe5943675 | |||
| 6cfb2fdedb | |||
| 14ec1867d5 | |||
| 7ff1e84f21 | |||
| 083095e636 |
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
secrets.env
|
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
secrets.env
|
secrets.env
|
||||||
|
data
|
||||||
|
tokens.txt
|
||||||
Vendored
+2
-1
@@ -11,7 +11,8 @@
|
|||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
],
|
],
|
||||||
"program": "${workspaceFolder}\\main.js"
|
"program": "${workspaceFolder}\\index.js",
|
||||||
|
"envFile": "${workspaceFolder}\\secrets.env"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
FROM node:latest
|
|
||||||
ADD . /app
|
|
||||||
WORKDIR /app
|
|
||||||
RUN npm ci
|
|
||||||
CMD ["node", "main.js"]
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var app = require("./www");
|
||||||
|
var DataStore = require("./datastore");
|
||||||
|
|
||||||
|
var ds = new DataStore("activity");
|
||||||
|
|
||||||
|
|
||||||
|
app.get("/detect/:code", (req, res) => {
|
||||||
|
res.sendFile(process.cwd() + "/track-image.png");
|
||||||
|
if (req.headers["user-agent"].includes("Discordbot")) return;
|
||||||
|
onActivity(ds.get(req.params.code));
|
||||||
|
ds.del(req.params.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
client.on("messageCreate", async m => m.guildId == config.guild && onActivity(m.author.id));
|
||||||
|
client.on("interactionCreate", async i => i.guildId == config.guild && onActivity(i.user.id));
|
||||||
|
client.on("typingStart", async t => t.guild?.id == config.guild && onActivity(t.user.id));
|
||||||
|
client.on("guildMemberAdd", async m => m.guild.id == config.guild && onActivity(m.user.id));
|
||||||
|
client.on("messageReactionAdd", async (r, user) => r.message?.guild?.id == config.guild && onActivity(user.id));
|
||||||
|
client.on("messageReactionRemove", async (r, user) => r.message?.guild?.id == config.guild && onActivity(user.id));
|
||||||
|
client.on("messageUpdate", async (oldMessage, newMessage) => newMessage.guild?.id == config.guild && (oldMessage.editedAt != newMessage.editedAt) && onActivity(newMessage.author.id));
|
||||||
|
|
||||||
|
|
||||||
|
async function onActivity(user_id) {
|
||||||
|
if (!user_id) return;
|
||||||
|
let user = client.users.resolve(user_id);
|
||||||
|
if (!user || user.bot) return;
|
||||||
|
ds.put(user_id, Date.now());
|
||||||
|
if (ds.get(user_id + "deactivated")) {
|
||||||
|
let member = client.guilds.resolve(config.guild).members.resolve(user_id);
|
||||||
|
await reactivateMember(member);
|
||||||
|
/*await client.channels.resolve(config.default_channel)?.send(random([
|
||||||
|
`${member.displayName} bacc`,
|
||||||
|
`${member.displayName} is bacc`,
|
||||||
|
`hi ${member.displayName}`
|
||||||
|
]));*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.interval = setInterval(async () => {
|
||||||
|
var guild = client.guilds.resolve(config.guild);
|
||||||
|
if (!guild) return;
|
||||||
|
guild.members.cache.filter(m => !m.user.bot).forEach(member => {
|
||||||
|
let lastActivityTime = ds.get(member.user.id);
|
||||||
|
if (!lastActivityTime) return;
|
||||||
|
if (Date.now() - lastActivityTime > 1000*60*60*72) { // if last activity > 72 hours ago
|
||||||
|
if (!ds.get(member.id + "deactivated"))
|
||||||
|
deactivateMember(member);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 60*60*1000); // every hour i guess
|
||||||
|
|
||||||
|
|
||||||
|
async function deactivateMember(member) {
|
||||||
|
await member.roles.add(config.inactive_role);
|
||||||
|
ds.put(member.id + "deactivated");
|
||||||
|
ds.put(member.id, Date.now());
|
||||||
|
|
||||||
|
var magic_channel = client.channels.resolve(ds.get(member.id + "magicchannelid"));
|
||||||
|
if (!magic_channel) {
|
||||||
|
magic_channel = await member.guild.channels.create("inactive", {
|
||||||
|
permissionOverwrites: [
|
||||||
|
{
|
||||||
|
id: member.guild.roles.everyone,
|
||||||
|
deny: "VIEW_CHANNEL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: client.user,
|
||||||
|
allow: "VIEW_CHANNEL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: member,
|
||||||
|
allow: "VIEW_CHANNEL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
ds.put(member.id + "magicchannelid", magic_channel.id);
|
||||||
|
} else {
|
||||||
|
magic_channel.permissionOverwrites.edit(member, {"VIEW_CHANNEL": true});
|
||||||
|
}
|
||||||
|
|
||||||
|
var magic_channel_message_id = ds.get(member.id + "magicchannelmessage");
|
||||||
|
var unique_code = Math.random().toString();
|
||||||
|
ds.put(unique_code, member.id);
|
||||||
|
var content = `${config.base_uri}/detect/${unique_code}`;
|
||||||
|
if (!magic_channel_message_id) {
|
||||||
|
var magic_channel_message = await magic_channel.send({content});
|
||||||
|
ds.put(member.id + "magicchannelmessage", magic_channel_message.id)
|
||||||
|
} else {
|
||||||
|
await magic_channel.messages.edit(magic_channel_message_id, {content});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reactivateMember(member) {
|
||||||
|
await member.roles.remove(config.inactive_role);
|
||||||
|
ds.del(member.id + "deactivated");
|
||||||
|
var magic_channel = client.channels.resolve(ds.get(member.id + "magicchannelid"));
|
||||||
|
if (magic_channel) await magic_channel.permissionOverwrites.edit(member, {"VIEW_CHANNEL": false});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.deactivateMember = deactivateMember;
|
||||||
|
module.exports.reactivateMember = reactivateMember;
|
||||||
|
|
||||||
|
|
||||||
|
client.on("messageCreate", async message => {
|
||||||
|
if (message.guildId != config.guild) return;
|
||||||
|
let deactivatedMembersMentionedViaRoles = [...new Set(message.mentions.roles.flatMap(r => r.members).values())].filter(x => ds.get(x.id + "deactivated"));
|
||||||
|
if (deactivatedMembersMentionedViaRoles.length) {
|
||||||
|
for (let m of deactivatedMembersMentionedViaRoles) await reactivateMember(m);
|
||||||
|
await message.reply({content: deactivatedMembersMentionedViaRoles.map(String).join(' '), allowedMentions:{repliedUser: false}});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
module.exports = class DiscordBackup {
|
||||||
|
constructor(guild) {
|
||||||
|
this.guild = guild;
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async backupGuild() {
|
||||||
|
// includes emojis, stickers, roles
|
||||||
|
this.data.guild = await this.guild.client.api.guilds(this.guild.id).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async backupMembers() {
|
||||||
|
this.data.members = await this.guild.client.api.guilds(this.guild.id).members.get({query: {limit: 1000}});
|
||||||
|
}
|
||||||
|
|
||||||
|
async backupChannel(channelId) {
|
||||||
|
var data = {
|
||||||
|
channel: await this.guild.client.api.channels[channelId].get(),
|
||||||
|
messages: []
|
||||||
|
};
|
||||||
|
this.data.channels ||= [];
|
||||||
|
this.data.channels.push(data);
|
||||||
|
do {
|
||||||
|
var messages = await this.guild.client.api.channels[channelId].messages.get({query: {
|
||||||
|
before: messages?.at(-1)?.id, limit: 100
|
||||||
|
}});
|
||||||
|
data.messages.push(...messages);
|
||||||
|
} while (messages.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async backupEverything() {
|
||||||
|
await this.backupGuild();
|
||||||
|
await this.backupMembers();
|
||||||
|
for (let channelId of this.guild.channels.cache.keys()) {
|
||||||
|
try {
|
||||||
|
await this.backupChannel(channelId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return JSON.stringify(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var fs = require("fs");
|
||||||
|
|
||||||
|
|
||||||
|
var data = fs.existsSync("data/buttonthing.json") ? JSON.parse(fs.readFileSync("data/buttonthing.json", "utf8")) : ({
|
||||||
|
userVoteMap: {},
|
||||||
|
votes: {
|
||||||
|
miku: 0,
|
||||||
|
teto: 0,
|
||||||
|
both: 0,
|
||||||
|
what: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function setVote(user, vote) {
|
||||||
|
var existingVote = data.userVoteMap[user];
|
||||||
|
if (vote == existingVote) {
|
||||||
|
data.votes[existingVote]--;
|
||||||
|
data.userVoteMap[user] = undefined;
|
||||||
|
} else {
|
||||||
|
if (existingVote) data.votes[existingVote]--;
|
||||||
|
data.votes[vote]++;
|
||||||
|
data.userVoteMap[user] = vote;
|
||||||
|
}
|
||||||
|
fs.writeFileSync("data/buttonthing.json", JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var thing = () => ({
|
||||||
|
content: "Miku or Teto?",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: "ACTION_ROW",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: "BUTTON",
|
||||||
|
label: `Miku (${data.votes.miku})`,
|
||||||
|
customId: "miku",
|
||||||
|
style: "SUCCESS",
|
||||||
|
emoji: "<:MikuSquish:868670741518888961>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "BUTTON",
|
||||||
|
label: `Teto (${data.votes.teto})`,
|
||||||
|
customId: "teto",
|
||||||
|
style: "DANGER",
|
||||||
|
emoji: "<:tetodrill2:705605869571801120>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "BUTTON",
|
||||||
|
label: `both/idk (${data.votes.both})`,
|
||||||
|
customId: "both",
|
||||||
|
style: "PRIMARY",
|
||||||
|
emoji: "<:squee:707727925587345490>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "BUTTON",
|
||||||
|
label: `what/idk (${data.votes.what})`,
|
||||||
|
customId: "what",
|
||||||
|
style: "SECONDARY",
|
||||||
|
emoji: "😕"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.init = function(channel = client.channels.resolve(config.announcement_channel)) {
|
||||||
|
channel.send(thing());
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on("interactionCreate", i => {
|
||||||
|
if (!Object.keys(data.votes).includes(i.customId)) return;
|
||||||
|
setVote(i.user.id, i.customId);
|
||||||
|
i.update(thing());
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
var Discord = require("discord.js");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
var client = module.exports = new Discord.Client({
|
||||||
|
partials: ['MESSAGE','REACTION'],
|
||||||
|
intents: 32767 //all
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(config.token).then(async () => {
|
||||||
|
console.log("ready");
|
||||||
|
(await client.channels.fetch(config.bot_channel))?.send('a');
|
||||||
|
});
|
||||||
@@ -1,33 +1,160 @@
|
|||||||
|
var fetch = require("node-fetch");
|
||||||
|
var {getAverageColor} = require("fast-average-color-node");
|
||||||
|
var fs = require("fs");
|
||||||
|
var Discord = require("discord.js");
|
||||||
|
|
||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var commands = require("./commands");
|
||||||
|
|
||||||
|
|
||||||
|
function getColorRoleFor(user_id) {
|
||||||
|
try {
|
||||||
|
return client.guilds.resolve(config.guild)?.roles.fetch(
|
||||||
|
fs.readFileSync(config.data_dir + "color/" + user_id, "utf8")
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
module.exports.getColorRoleFor = getColorRoleFor;
|
||||||
|
|
||||||
|
|
||||||
client.on("guildMemberAdd", async function (member) {
|
client.on("guildMemberAdd", async function (member) {
|
||||||
member.roles.add(await member.guild.roles.create({data:{
|
if (member.guild.id != config.guild) return;
|
||||||
|
var role = await member.guild.roles.create({
|
||||||
name: member.user.username,
|
name: member.user.username,
|
||||||
color: await user2color(member.user) || "#FF0000",
|
color: await user2color(member.user) || "#FF0000",
|
||||||
mentionable: true,
|
mentionable: true,
|
||||||
permissions: 0
|
permissions: 0n
|
||||||
}}));
|
});
|
||||||
|
member.roles.add(role);
|
||||||
|
fs.writeFileSync(config.data_dir + "color/" + member.id, role.id);
|
||||||
});
|
});
|
||||||
client.on("userUpdate", async function (oldUser, user) {
|
client.on("userUpdate", async function (oldUser, user) {
|
||||||
var colorRole = client.guilds.resolve(config.guild)?.members.resolve(user)?.roles.color;
|
var colorRole = await getColorRoleFor(user.id);
|
||||||
if (!colorRole) return;
|
if (!colorRole) return;
|
||||||
if (oldUser.username != user.username) await colorRole.setName(user.username);
|
if (oldUser.username != user.username)
|
||||||
|
if (!fs.existsSync(`${config.data_dir}color/${user.id}_custom-name`))
|
||||||
|
await colorRole.setName(user.username);
|
||||||
if (oldUser.avatar != user.avatar) {
|
if (oldUser.avatar != user.avatar) {
|
||||||
|
if (fs.existsSync(`${config.data_dir}color/${user.id}_custom`)) return;
|
||||||
let c = await user2color(user);
|
let c = await user2color(user);
|
||||||
await colorRole.setColor(c);
|
await colorRole.setColor(c);
|
||||||
if (!colorRole.color) {
|
|
||||||
console.warn("role color set fail:", colorRole.name, c);
|
|
||||||
await colorRole.setColor("#FF0000");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
client.on("guildMemberRemove", async function (member) {
|
client.on("guildMemberRemove", async function (member) {
|
||||||
var colorRole = member.roles.color;
|
if (member.guild.id != config.guild) return;
|
||||||
|
var colorRole = await getColorRoleFor(member.user.id);
|
||||||
if (!colorRole) return;
|
if (!colorRole) return;
|
||||||
if (!colorRole.members.size) colorRole.delete();
|
if (!colorRole.members.size) colorRole.delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "setcolor",
|
||||||
|
description: "Set yourself to a custom color",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "color",
|
||||||
|
description: "Hex string or color name.",
|
||||||
|
type: "STRING",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
var colorRole = await getColorRoleFor(i.user.id);
|
||||||
|
if (!colorRole) return void i.reply({content: "You don't have color role!"});
|
||||||
|
let x = i.options.getString("color");
|
||||||
|
if (x) {
|
||||||
|
// if (/^[a-zA-Z_]+$/.test(x))
|
||||||
|
x = x.toUpperCase();
|
||||||
|
// else if (!/^#?[a-fA-F0-9]{6}$/.test(x)) {
|
||||||
|
// let m = x.match(/(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
||||||
|
// if (m) x = [Number(x[1]), Number(x[2]), Number(x[3])];
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await colorRole.setColor(x);
|
||||||
|
} catch(error) {
|
||||||
|
return void await i.reply({content: error.message});
|
||||||
|
}
|
||||||
|
fs.writeFileSync(`${config.data_dir}color/${i.user.id}_custom`, '');
|
||||||
|
await i.reply({content: colorRole.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "settitle",
|
||||||
|
description: "Change your color role name",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "Name of role",
|
||||||
|
type: "STRING",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
var colorRole = await getColorRoleFor(i.user.id);
|
||||||
|
if (!colorRole) return void i.reply({content: "You don't have color role!"});
|
||||||
|
try {
|
||||||
|
await colorRole.setName(i.options.getString("name"));
|
||||||
|
} catch(error) {
|
||||||
|
await i.reply({content: error.message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(`${config.data_dir}color/${i.user.id}_custom-name`, '');
|
||||||
|
await i.reply({content: colorRole.toString()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "seticon",
|
||||||
|
description: "Set your role icon",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
description: "Image URL or an emoji",
|
||||||
|
type: "STRING"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
await i.deferReply();
|
||||||
|
var colorRole = await getColorRoleFor(i.user.id);
|
||||||
|
if (!colorRole) return void i.editReply({content: "You don't have color role!"});
|
||||||
|
try {
|
||||||
|
let icon = i.options.getString("icon");
|
||||||
|
if (/^https?:\/\//i.test(icon)) {
|
||||||
|
await colorRole.setIcon(icon);
|
||||||
|
await i.editReply({files: [{
|
||||||
|
attachment: icon,
|
||||||
|
name: icon.match(/\/(\w*(?:\.png|\.jpg|\.jpeg|\.gif|\.webp))$/i)?.[1] || "icon.png"
|
||||||
|
}]});
|
||||||
|
} else if (icon) {
|
||||||
|
var emoji = Discord.Util.parseEmoji(icon);
|
||||||
|
if (emoji?.id) {
|
||||||
|
await colorRole.setIcon(i.client.rest.cdn.Emoji(emoji.id, emoji.animated ? 'gif' : 'png'));
|
||||||
|
} else {
|
||||||
|
await colorRole.setUnicodeEmoji(icon);
|
||||||
|
}
|
||||||
|
await i.editReply({content: icon});
|
||||||
|
} else {
|
||||||
|
await colorRole.setIcon(null);
|
||||||
|
await i.editReply({content: "icon removed"});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await i.editReply({content: error.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function user2color(user) {
|
async function user2color(user) {
|
||||||
var avatarURL = user.avatarURL({format:'png', size: 16}) || user.defaultAvatarURL;
|
var avatarURL = user.avatarURL({format:'png', size: 16}) || user.defaultAvatarURL;
|
||||||
var image = await (await require("node-fetch")(avatarURL)).buffer();
|
var image = await (await fetch(avatarURL)).buffer();
|
||||||
return (await require("fast-average-color-node").getAverageColor(image)).value;
|
return (await getAverageColor(image)).value;
|
||||||
}
|
}
|
||||||
|
|||||||
+209
-38
@@ -1,49 +1,220 @@
|
|||||||
client.on("messageCreate", async function (message) {
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var Discord = require("discord.js");
|
||||||
|
var BaseCommandInteraction = require("discord.js/src/structures/BaseCommandInteraction.js")
|
||||||
|
|
||||||
if (!message.content.startsWith('!')) return;
|
var commands = module.exports = [
|
||||||
let args = message.content.substr(1).split(' '), cmd = args[0].toLowerCase(), query = args.slice(1).join(' ');
|
{
|
||||||
let say = message.channel.send.bind(message.channel);
|
name: "say",
|
||||||
|
description: "test command",
|
||||||
switch (cmd) {
|
options: [
|
||||||
case "ping": return void say("pong");
|
{
|
||||||
case "owo":
|
name: "text",
|
||||||
|
description: "text to say",
|
||||||
|
type: 3, // string
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => {
|
||||||
|
i.reply(i.options.getString("text") || "bruh");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "owo",
|
||||||
|
description: "random owo",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "length",
|
||||||
|
description: "length of owo",
|
||||||
|
type: 4, // integer
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => {
|
||||||
let owo = Math.round(Math.random()) ? 'O' : 'o';
|
let owo = Math.round(Math.random()) ? 'O' : 'o';
|
||||||
let n = Math.min(Number(query) || 9, 1998);
|
let n = Math.min(i.options.getInteger("length") || 9, 1998);
|
||||||
for (let i = 0; i < n; i++) owo += ['o','w','O','W'][Math.floor(Math.random() * 4)];
|
for (let i = 0; i < n; i++) owo += ['o','w','O','W'][Math.floor(Math.random() * 4)];
|
||||||
owo += owo = Math.round(Math.random()) ? 'O' : 'o';
|
owo += owo = Math.round(Math.random()) ? 'O' : 'o';
|
||||||
say(owo);
|
i.reply(owo);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
if (!message.member?.roles.cache.has(config.admin_role)) return;
|
name: "avatar",
|
||||||
|
description: "View a user's original avatar (and save permanently as attachment)",
|
||||||
if (message.content.startsWith("!>")) {
|
options: [
|
||||||
with (message) {
|
{
|
||||||
|
name: "user",
|
||||||
|
description: "can i not leave some obvious descriptions blank?",
|
||||||
|
type: "USER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
var user = i.options.getUser("user") || i.user;
|
||||||
|
var au = user.avatarURL({size:4096, dynamic: true});
|
||||||
|
var an = au.split('/').pop(); an = an.substring(0, an.indexOf('?'));
|
||||||
|
i.reply({files: [{attachment: au, name: an}], embeds: [{
|
||||||
|
title: `Avatar for ${user.tag}`,
|
||||||
|
url: au,
|
||||||
|
image: {url: `attachment://${an}`},
|
||||||
|
color: (await require('./colors').getColorRoleFor(user.id)).color
|
||||||
|
}]});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "steal",
|
||||||
|
description: "Copy an emoji to this server",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "emoji",
|
||||||
|
description: "The emoji to steal",
|
||||||
|
type: "STRING",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "Optional rename of stolen emoji",
|
||||||
|
type: "STRING",
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
await i.deferReply();
|
||||||
try {
|
try {
|
||||||
var x = await eval(message.content.substr(2).trim());
|
var input = i.options.getString("emoji") || (await i.channel.messages.fetch(i.targetId))?.content;
|
||||||
} catch(e) {
|
var emoji = Discord.Util.parseEmoji(input);
|
||||||
var x = e.message;
|
if (!emoji.id || !emoji.name) return void await i.editReply({content: `Invalid emoji input: ${input}`});
|
||||||
|
var url = client.rest.cdn.Emoji(emoji.id, emoji.animated ? 'gif' : 'png');
|
||||||
|
var emoji2 = await i.guild.emojis.create(url, i.options.getString("name") || emoji.name);
|
||||||
|
await i.editReply({content: emoji2.toString()});
|
||||||
|
} catch (error) {
|
||||||
|
await i.editReply({content: error.message});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof x == "undefined") return void await message.react(config.eval_undefined_emoji);
|
},
|
||||||
let t = typeof x == 'string' ? 'txt' : 'js';
|
{
|
||||||
if (typeof x != 'string' && typeof x != "function") x = require('util').inspect(x, {depth: 1});
|
name: "Steal Emoji",
|
||||||
message.channel.send(`\`\`\`${t}\n${x}\`\`\``, {split:{maxLength:2000,prepend:`\`\`\`${t}\n`,append:'```'}});
|
type: "MESSAGE",
|
||||||
}
|
exec: i => commands.find(x => x.name == "steal").exec(i)
|
||||||
|
},
|
||||||
else if (message.content.startsWith("!$")) {
|
{
|
||||||
let cp = require("child_process").spawn("bash", ["-c", message.content.substr(2).trim()]);
|
name: "setserverbanner",
|
||||||
cp.stdout.on("data", data => {
|
description: "Set the server banner image",
|
||||||
message.channel.send(data.toString(), {split:{char:'\n',length:2000}}).catch(()=>{
|
options: [
|
||||||
message.channel.send(data.toString(), {split:{char:'',length:2000}});
|
{
|
||||||
});
|
name: "url",
|
||||||
});
|
description: "HTTP(S) URL to an image",
|
||||||
cp.stderr.on("data", data => {
|
type: "STRING"
|
||||||
message.channel.send(data.toString(), {split:{char:'\n',length:2000}}).catch(()=>{
|
|
||||||
message.channel.send(data.toString(), {split:{char:'',length:2000}});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
var url = i.options.getString("url");
|
||||||
|
try {
|
||||||
|
if (!url) {
|
||||||
|
await i.guild.setBanner(null);
|
||||||
|
await i.reply("cleared the server banner");
|
||||||
|
} else {
|
||||||
|
if (/^https?:\/\//i.test(url)) {
|
||||||
|
await i.guild.setBanner(url);
|
||||||
|
await i.reply(url);
|
||||||
|
} else {
|
||||||
|
await i.reply("http image url only!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await i.reply(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "setservericon",
|
||||||
|
description: "Change the server icon",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "url",
|
||||||
|
description: "HTTP(S) URL to an image",
|
||||||
|
type: "STRING"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: async i => {
|
||||||
|
var url = i.options.getString("url");
|
||||||
|
try {
|
||||||
|
if (!url) {
|
||||||
|
await i.guild.setIcon(null);
|
||||||
|
await i.reply("cleared the server icon");
|
||||||
|
} else {
|
||||||
|
if (/^https?:\/\//i.test(url)) {
|
||||||
|
await i.guild.setIcon(url);
|
||||||
|
await i.reply(url);
|
||||||
|
} else {
|
||||||
|
await i.reply("http image url only!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await i.reply(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getemoji",
|
||||||
|
description: "Generate a URL for an emoji",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "emoji",
|
||||||
|
description: "The emoji (code) or the name of the emoji (case-sensitive)",
|
||||||
|
type: "STRING",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format",
|
||||||
|
description: "choose image format",
|
||||||
|
type: "STRING",
|
||||||
|
choices: [
|
||||||
|
{name: "PNG", value: "png"},
|
||||||
|
{name: "JPG", value: "jpg"},
|
||||||
|
{name: "WEBP", value: "webp"},
|
||||||
|
{name: "GIF", value: "gif"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "size",
|
||||||
|
description: "choose image size",
|
||||||
|
type: "INTEGER",
|
||||||
|
choices: "16,20,22,24,28,32,40,44,48,56,60,64,80,96,100,128".split(',').map(s => ({name: s, value: Number(s)}))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => {
|
||||||
|
var emojiname = i.options.getString("emoji");
|
||||||
|
if (emojiname.startsWith('<') && emojiname.endsWith('>')) emoji = Discord.Util.parseEmoji(emojiname);
|
||||||
|
else {
|
||||||
|
if (emojiname.startsWith(':')) emojiname = emojiname.slice(1);
|
||||||
|
if (emojiname.endsWith(':')) emojiname = emojiname.slice(-1);
|
||||||
|
var emoji = client.emojis.cache.find(e => e.name == emojiname);
|
||||||
|
if (!emoji) emoji = client.emojis.cache.find(e => e.name.toLowerCase() == emojiname.toLowerCase());
|
||||||
|
if (!emoji) return void i.reply(`could not find emoji named ${emojiname}`);
|
||||||
|
}
|
||||||
|
if (!emoji.id) return void i.reply(`invalid input`);
|
||||||
|
var qs = [];
|
||||||
|
var size = i.options.getInteger("size");
|
||||||
|
if (size) qs.push(`size=${size}`);
|
||||||
|
var format = i.options.getString("format");
|
||||||
|
if (!format) format = emoji.animated ? "gif" : "png";
|
||||||
|
if (format == "gif" && !emoji.animated) return void i.reply(`Non-animated emoji is not available as GIF.`);
|
||||||
|
if (format == "webp") qs.push(`quality=lossless`);
|
||||||
|
var url = `https://media.discordapp.net/emojis/${emoji.id}.${format}`;
|
||||||
|
if (qs.length > 0) url += '?' + qs.join('&');
|
||||||
|
i.reply(`${emoji.name}.${format}\n${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
client.on("interactionCreate", interaction => {
|
||||||
|
if (interaction instanceof BaseCommandInteraction)
|
||||||
|
commands.find(x => x.name == interaction.commandName)?.exec?.(interaction);
|
||||||
|
});
|
||||||
|
client.once("ready", async () => {
|
||||||
|
let global_commands = commands.filter(x => x.global);
|
||||||
|
let guild_commands = commands.filter(x => !x.global);
|
||||||
|
let guild = client.guilds.resolve(config.guild);
|
||||||
|
await guild.commands.set(guild_commands);
|
||||||
|
await client.application.commands.set(global_commands);
|
||||||
});
|
});
|
||||||
@@ -1,13 +1,88 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
token: process.env.TOKEN,
|
token: process.env.TOKEN,
|
||||||
|
secret: process.env.SECRET,
|
||||||
|
deepl_auth_key: process.env.DEEPL_KEY,
|
||||||
|
application_id: "707451582454824980",
|
||||||
guild: "672956423545815040",
|
guild: "672956423545815040",
|
||||||
admin_role: "776899554603565116",
|
admin_role: "776899554603565116",
|
||||||
human_role: "672956630962274306",
|
human_role: "672956630962274306",
|
||||||
bot_role: "673671040010027034",
|
bot_role: "673671040010027034",
|
||||||
eval_undefined_emoji: "707729833601531935",
|
inactive_role: "892869309603389500",
|
||||||
|
verified_role: "949064806030254130",
|
||||||
|
view_archived_channels_role: "916056534402863125",
|
||||||
|
eval_undefined_emoji: "🅱️",
|
||||||
|
mi_emoji: "887931046086185060",
|
||||||
|
ki_emoji: "887935846710394910",
|
||||||
|
default_channel: "949831184957980722",
|
||||||
|
bot_channel: "949831221981097984",
|
||||||
archive_channel: "802280618636869663",
|
archive_channel: "802280618636869663",
|
||||||
porn_channel: "835734868427669574",
|
moe_channel: "871864787213111366",
|
||||||
avatar_cache_dir: "/srv/www/ldb/avatars/",
|
porn_channel: "949831237927862333",
|
||||||
avatar_cache_url: "https://ldb.owo69.me/avatars/",
|
announcement_channel: "876010629490683955",
|
||||||
|
miku_channel: "900583427483516938",
|
||||||
|
archive_category: "887838689533771776",
|
||||||
|
archive_portal_voice_channel: "916057450313023508",
|
||||||
|
data_dir: process.cwd() + "/data/",
|
||||||
|
base_uri: "https://ldb.owo69.me",
|
||||||
|
world_clock: [
|
||||||
|
{
|
||||||
|
flag: "🇺🇸",
|
||||||
|
timezone: "America/Los_Angeles",
|
||||||
|
channel: "887897732428226660"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: "🇺🇸",
|
||||||
|
timezone: "America/New_York",
|
||||||
|
channel: "888507872932143155"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: "🇩🇪",
|
||||||
|
timezone: "Europe/Berlin",
|
||||||
|
channel: "887897886879281203"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: "🇷🇺",
|
||||||
|
timezone: "Europe/Moscow",
|
||||||
|
channel: "887897904738599002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: "🇵🇭",
|
||||||
|
timezone: "Asia/Manila",
|
||||||
|
channel: "888505937315389451"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: "🇯🇵",
|
||||||
|
timezone: "Asia/Tokyo",
|
||||||
|
channel: "887897753198419999"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pixiv_subscriptions: [
|
||||||
|
{
|
||||||
|
tag: "初音ミク",
|
||||||
|
channel: "900583427483516938"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "鏡音リン",
|
||||||
|
channel: "904093976615849996"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "重音テト",
|
||||||
|
channel: "904093991224627270"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "巡音ルカ",
|
||||||
|
channel: "916444961958928394"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
vrchat_status_category: "959236139913445376",
|
||||||
|
vrchat_configuration: {
|
||||||
|
username: process.env.VRCHAT_USERNAME,
|
||||||
|
password: process.env.VRCHAT_PASSWORD
|
||||||
|
},
|
||||||
|
masto: {
|
||||||
|
url: "https://mastodong.lol",
|
||||||
|
accessToken: process.env.MASTO_TOKEN
|
||||||
|
},
|
||||||
|
masto_account_id: "108643271047165149",
|
||||||
|
masto_webhook: process.env.DISCORD_WEBHOOK_FOR_MASTO
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
var commands = require("./commands");
|
||||||
|
var app = require("./www");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
var mediaProxyUAs = [
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11.6; rv:92.0) Gecko/20100101 Firefox/92.0",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"
|
||||||
|
];
|
||||||
|
var img = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAACMAAAAhCAYAAABTERJSAAACpUlEQVRYCa2XvW7bQAzH6SRApyJAkSHwlqHoJHSNgSx+lQx9iD5AH6KDX0VLAHctNHboFmQoCnTs0Kb40yJL8cg7WY4A43Q8fvxM8k7Sik64rq/ePtfMn358W9XW/dqFF7TmFuBxuKPV9S40eX66p3VHCjsHbDa5QDx+viTadCFAKtwPtP7wi5drULNgALIIwtONUBlQE+bFQASsAlSFURAi2n78K+507D+dHV8yWCdAKYyCbDrabr8qQHSzCCoAOoucWxC7jqB9//7wQ1bGK8qarKXjpiP0IWKJTpgZhhnuRCcf94OWD5BLrnX3QNLQBUyWFdTZZmBRaSJaU6740AvOEQsCn5j3feSdwh5LM8exHtjR/8LHfo+WZs0OebYmQSYwWiJZPWHkMgb2EZA08gSGbYMSQe6d+7nEld3myxr5EBt5vJQ9sx/ig2zTpT2iTls3mQ/EJKISxjm0aU2b0NksnZZlMp4Awmk3B5xZrt76Mvp5ZBxnZjxT1AH3Uf2RUDjPSuIVxxJBHMLwGdJ6CLpDUGLwH0g2gehkY1Gm319+HnStQ0MvjqLdgjXIbZ+JfjiOfiXmJDN4Rtzs8CI1ZYwCh82cZKsGgrWb3Tk/nyYwOPTeXb2Otzb+deNVAo5DSE8TZBoqCqOnry3P6GRugCiDngMPXHsdSnTOIoapgVjD6n1rxzkI8SUlwvziZBDTJ0UGEwABsVlhGF4ISiMGdsx6Rs+jRnDrCyA2K1jTnrGK2b3+c38oZgaJPAKB6nQPJ8aFeGYmCzsiykCge1RmYJCVKgrsZTUQ6B4yc0StUSrtER8tmQMCn7e+R7w6v5Av2VF4q/9+/4de3b7xPnkuWZBF+QKQeTTq1wGAoND8pjZv82ITOYZsDoC1VRgRtgIsCSK+W+M/mSFEURCM3rAAAAAASUVORK5CYII=","base64");
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "count",
|
||||||
|
description: "Estimate how many clients are looking at the channel",
|
||||||
|
exec: async i => {
|
||||||
|
var count = 0;
|
||||||
|
var route = `/tmp/${Math.random()}`;
|
||||||
|
app.get(route, (req, res) => {
|
||||||
|
if (mediaProxyUAs.includes(req.headers["user-agent"])) count++;
|
||||||
|
res.type("png").send(img);
|
||||||
|
});
|
||||||
|
await i.reply(config.base_uri + route);
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
await i.editReply(`It appears there might be **${count}** clients viewing this channel right now.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
var fs = require("fs");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
module.exports = class DataStore {
|
||||||
|
constructor(id) {
|
||||||
|
if (id.includes('/')) throw new Error("NO");
|
||||||
|
this.dir = `${config.data_dir}/${id}/`;
|
||||||
|
if (!fs.existsSync(this.dir)) fs.mkdirSync(this.dir);
|
||||||
|
}
|
||||||
|
get(key) {
|
||||||
|
if (key.includes('/')) throw new Error("NO");
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(this.dir + key, "utf8") || true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code == "ENOENT") return false;
|
||||||
|
else throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put(key, value) {
|
||||||
|
if (key.includes('/')) throw new Error("NO");
|
||||||
|
return fs.writeFileSync(this.dir + key, String(value) || '');
|
||||||
|
}
|
||||||
|
del(key) {
|
||||||
|
if (key.includes('/')) throw new Error("NO");
|
||||||
|
try {
|
||||||
|
return fs.unlinkSync(this.dir + key);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code != "ENOENT") throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
var client = require("./client.js");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
|
||||||
|
client.on("guildMemberAdd", member => {
|
||||||
|
if (member.guild.id != config.guild) return;
|
||||||
|
// add role
|
||||||
|
member.roles.add(member.user.bot ? config.bot_role : config.human_role);
|
||||||
|
// welcome message
|
||||||
|
/*setTimeout(() => {
|
||||||
|
client.channels.resolve(config.default_channel)?.send(
|
||||||
|
`Welcome ${member}. Please tell from where you entered this server and some other info about yourself in order to gain access to message history.`
|
||||||
|
);
|
||||||
|
}, 3000);*/
|
||||||
|
member.roles.add(config.verified_role);
|
||||||
|
});
|
||||||
|
// join message
|
||||||
|
/*client.on("messageDelete", message => {
|
||||||
|
if (message.channel.id != config.default_channel) return;
|
||||||
|
if (message.type != "GUILD_MEMBER_JOIN") return;
|
||||||
|
client.channels.resolve(config.default_channel)?.send(
|
||||||
|
`sussy baka ${message.author} deleted their join message ||(from <t:${Math.floor(message.createdAt.valueOf()/1000)}>)||`
|
||||||
|
);
|
||||||
|
});*/
|
||||||
|
client.on("guildMemberRemove", member => {
|
||||||
|
if (member.guild.id != config.guild) return;
|
||||||
|
// leave message
|
||||||
|
client.channels.resolve(config.default_channel)?.send(random([
|
||||||
|
`${member.user.username} left`,
|
||||||
|
`${member.user.username} disappeared`,
|
||||||
|
`${member.user.username.toLowerCase()} is gone`
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
client.on("messageCreate", message => {
|
||||||
|
// comment thread on announcements
|
||||||
|
message.channel.id == config.announcement_channel && message.startThread({name: "Comments"});
|
||||||
|
// stupid m bot
|
||||||
|
message.author.id == "732072478519722096" && message.content.endsWith("is bad letter m is much better") && message.delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// add reactions to video and audio
|
||||||
|
{
|
||||||
|
let a = async m => {
|
||||||
|
if (m.guild?.id != config.guild) return;
|
||||||
|
if ((m.embeds.some(e => e.type == "video") || m.attachments.some(a => a.contentType?.startsWith('video'))) && !m.g) {
|
||||||
|
m.g = true;
|
||||||
|
m.react(config.mi_emoji);
|
||||||
|
}
|
||||||
|
if (m.attachments.some(a => a.contentType?.startsWith('audio')) && !m.d) {
|
||||||
|
m.d = true;
|
||||||
|
m.react(config.ki_emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.on("messageCreate", a);
|
||||||
|
client.on("messageUpdate", (r, q) => a(q));
|
||||||
|
}
|
||||||
|
|
||||||
|
// thing to access archived channels
|
||||||
|
client.on("voiceStateUpdate", (oldState, newState) => {
|
||||||
|
if (newState.guild.id != config.guild) return;
|
||||||
|
if (oldState.channelId != config.archive_portal_voice_channel && newState.channelId == config.archive_portal_voice_channel) {
|
||||||
|
// join
|
||||||
|
newState.member?.roles.add(config.view_archived_channels_role);
|
||||||
|
} else if (oldState.channelId == config.archive_portal_voice_channel && newState.channelId != config.archive_portal_voice_channel) {
|
||||||
|
// leave
|
||||||
|
newState.member?.roles.remove(config.view_archived_channels_role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// save deleted emojis
|
||||||
|
client.on("emojiDelete", emoji => {
|
||||||
|
client.channels.resolve(config.bot_channel)?.send({
|
||||||
|
content: "emoji deleted",
|
||||||
|
files: [{attachment: emoji.url, name: `${emoji.name}.${emoji.url.split('.').pop()}`}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//g=setInterval(() => client.guilds.resolve(config.guild)?.setIcon(`mf/${Math.floor(Math.random()*14548)}.png`), 1800000);
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
cd `dirname "$0"`
|
|
||||||
docker stop ldb
|
|
||||||
docker rm ldb
|
|
||||||
docker build -t ledlamp/lampdiscordbot .
|
|
||||||
docker run -d --name ldb --restart=unless-stopped --env-file=secrets.env -v /srv/www/ldb/:/srv/www/ldb/ ledlamp/lampdiscordbot
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var Discord = require("discord.js");
|
||||||
|
|
||||||
|
|
||||||
|
client.on("messageCreate", async function (message) {
|
||||||
|
if (message.author.id == client.user.id) return;
|
||||||
|
if (!message.member?.roles.cache.has(config.admin_role)) return;
|
||||||
|
if (message.content.startsWith("!>")) {
|
||||||
|
with (message) {
|
||||||
|
try {
|
||||||
|
var x = await eval(message.content.substring(2).trim());
|
||||||
|
} catch(e) {
|
||||||
|
var x = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof x == "undefined") return void await message.react(config.eval_undefined_emoji);
|
||||||
|
let t = typeof x == 'string' ? 'txt' : 'js';
|
||||||
|
if (typeof x != 'string' && typeof x != "function") x = require('util').inspect(x, {depth: 1});
|
||||||
|
let cb = `\`\`\`${t}\n${x}\`\`\``;
|
||||||
|
if (cb.length <= 2000)
|
||||||
|
message.channel.send(cb);
|
||||||
|
else
|
||||||
|
message.channel.send({files:[{
|
||||||
|
attachment: Buffer.from(x),
|
||||||
|
name: `output.${t}`
|
||||||
|
}]});
|
||||||
|
}
|
||||||
|
else if (message.content.startsWith("!$")) {
|
||||||
|
let cp = require("child_process").spawn("bash", ["-c", message.content.substr(2).trim()]);
|
||||||
|
function ondat(a) {
|
||||||
|
try {
|
||||||
|
var split = Discord.Util.splitMessage(a.toString(), {split:{char:'\n',length:2000}});
|
||||||
|
} catch(x) {
|
||||||
|
var split = Discord.Util.splitMessage(a.toString(), {split:{char:'',length:2000}});
|
||||||
|
}
|
||||||
|
split.forEach(message.channel.send.bind(message.channel));
|
||||||
|
}
|
||||||
|
cp.stdout.on("data", ondat);
|
||||||
|
cp.stderr.on("data", ondat);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
process.title = "lamp discord bot";
|
||||||
|
process.on("unhandledRejection", error => {
|
||||||
|
console.error("Unhandled Rejection:\n", error.stack || error);
|
||||||
|
});
|
||||||
|
|
||||||
|
require("./util"); // global variables set in here
|
||||||
|
require("./client");
|
||||||
|
require("./discord-misc");
|
||||||
|
require('./eval-exec');
|
||||||
|
require('./colors');
|
||||||
|
require('./www');
|
||||||
|
require('./pinboard');
|
||||||
|
require('./pixiv-embedder');
|
||||||
|
require('./translate2');
|
||||||
|
require('./world-clock');
|
||||||
|
require('./buttonthing');
|
||||||
|
require("./activitytracker");
|
||||||
|
require("./vocabularygame");
|
||||||
|
require("./pixiv-subscribe");
|
||||||
|
require("./count-cmd");
|
||||||
|
require("./masto");
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bot for Lamp Discord Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=ldb
|
||||||
|
Group=ldb
|
||||||
|
WorkingDirectory=/srv/ldb
|
||||||
|
EnvironmentFile=/srv/ldb/secrets.env
|
||||||
|
ExecStart=node index.js
|
||||||
|
Restart=on-success
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
global.config = require("./config");
|
|
||||||
global.Discord = require("discord.js");
|
|
||||||
|
|
||||||
global.client = new Discord.Client({
|
|
||||||
partials: ['MESSAGE','REACTION'],
|
|
||||||
intents: 32767 //all
|
|
||||||
});
|
|
||||||
client.login(config.token).then(() => console.log("ready"));
|
|
||||||
|
|
||||||
client.on("guildMemberAdd", member => {
|
|
||||||
member.guild.id == config.guild && member.roles.add(member.user.bot ? config.bot_role : config.human_role)
|
|
||||||
});
|
|
||||||
|
|
||||||
require('./commands.js');
|
|
||||||
require('./colors.js');
|
|
||||||
require('./pinboard.js');
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
var {login} = require("masto");
|
||||||
|
var config = require("./config");
|
||||||
|
var client = require("./client");
|
||||||
|
var {WebhookClient} = require("discord.js");
|
||||||
|
|
||||||
|
var webhook = new WebhookClient({url: config.masto_webhook});
|
||||||
|
module.exports.webhook = webhook;
|
||||||
|
|
||||||
|
client.once("ready", async () => {
|
||||||
|
var donger = await login(config.masto);
|
||||||
|
console.log("donger logged in");
|
||||||
|
module.exports.donger = donger;
|
||||||
|
(async function openStream() {
|
||||||
|
try {
|
||||||
|
var stream = await donger.stream.streamUser();
|
||||||
|
module.exports.stream = stream;
|
||||||
|
stream.on("update", toot => {
|
||||||
|
|
||||||
|
if (toot.account.id != config.masto_account_id) return;
|
||||||
|
if (toot.visibility != "public") return;
|
||||||
|
if (toot.inReplyToAccountId && toot.inReplyToAccountId != toot.account.id) return;
|
||||||
|
|
||||||
|
webhook.send(toot.url || toot.reblog.url); //todo maybe custom embed
|
||||||
|
|
||||||
|
//todo maybe handle deletes
|
||||||
|
});
|
||||||
|
stream.ws.on("close", () => {
|
||||||
|
console.log("donger stream closed");
|
||||||
|
setTimeout(openStream, 10000);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("donger stream", error.message);
|
||||||
|
setTimeout(openStream, 60000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
Generated
+1810
-203
File diff suppressed because it is too large
Load Diff
+10
-2
@@ -1,6 +1,14 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^13.1.0",
|
"@discordjs/voice": "^0.7.5",
|
||||||
"fast-average-color-node": "^1.0.3"
|
"deepl": "^1.0.12",
|
||||||
|
"discord.js": "^13.6.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"fast-average-color-node": "^1.0.3",
|
||||||
|
"kuroshiro": "^1.2.0",
|
||||||
|
"kuroshiro-analyzer-kuromoji": "^1.1.0",
|
||||||
|
"libsodium-wrappers": "^0.7.9",
|
||||||
|
"masto": "^4.4.0",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-31
@@ -1,13 +1,35 @@
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var fetch = require("node-fetch");
|
var fetch = require("node-fetch");
|
||||||
|
var Discord = require("discord.js");
|
||||||
|
|
||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var app = require("./www");
|
||||||
|
var commands = require("./commands");
|
||||||
|
|
||||||
|
|
||||||
|
/*commands.push({
|
||||||
|
type: "MESSAGE",
|
||||||
|
name: "Archive",
|
||||||
|
exec: async i => {
|
||||||
|
let message = await i.channel.messages.fetch(i.targetId);
|
||||||
|
let asdf = await doThing(message, i.user);
|
||||||
|
i.reply({
|
||||||
|
content: `https://discord.com/channels/${asdf.guild.id}/${asdf.channel.id}/${asdf.id}`,
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
|
|
||||||
client.on("messageReactionAdd", async (reaction, user) => {
|
client.on("messageReactionAdd", async (reaction, user) => {
|
||||||
if (reaction.emoji.name == '📍' || reaction.emoji.name == '📌') {
|
if (reaction.emoji.name == '📍' || reaction.emoji.name == '📌') {
|
||||||
if (!reaction.message.guild) return;
|
if (!reaction.message.guild) return;
|
||||||
if (reaction.message.channel.id == config.archive_channel) return;
|
if (reaction.message.channel.id == config.archive_channel) return;
|
||||||
if (reaction.message['has been "pinned"'] || reaction.count > 1) return;
|
if (reaction.message['has been pinned'] || reaction.count > 1) return;
|
||||||
reaction.message['has been "pinned"'] = true;
|
reaction.message['has been pinned'] = true;
|
||||||
|
|
||||||
if (reaction.message.channel.id == config.porn_channel) {
|
/*if (reaction.message.channel.id == config.porn_channel)*/ {
|
||||||
try {
|
try {
|
||||||
await reaction.message.pin();
|
await reaction.message.pin();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -25,33 +47,33 @@ client.on("messageReactionAdd", async (reaction, user) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache avatar because discord doesn't keep it if they change it
|
doThing(reaction.message, user);
|
||||||
var avatarURL = reaction.message.author.avatarURL({dynamic: true});
|
|
||||||
if (avatarURL) {
|
|
||||||
let afn = avatarURL.split('/').pop();
|
|
||||||
let lapath = config.avatar_cache_dir + afn;
|
|
||||||
if (!fs.existsSync(lapath)) {
|
|
||||||
try {
|
|
||||||
(await fetch(avatarURL)).body.pipe(fs.createWriteStream(lapath));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("avatar download", error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
avatarURL = config.avatar_cache_url + afn;
|
|
||||||
} else avatarURL = reaction.message.author.defaultAvatarURL;
|
|
||||||
|
|
||||||
let imageCandidate = reaction.message.attachments.find(a => [".png",".jpg",".jpeg",".webp",".gif"].some(e => a.url.toLowerCase().endsWith(e)));
|
|
||||||
if (imageCandidate) imageCandidate["will be used for the image of the embed"] = true;
|
|
||||||
else imageCandidate = reaction.message.embeds.find(e => e.type == 'image');
|
|
||||||
let embed = new Discord.MessageEmbed()
|
|
||||||
.setAuthor(reaction.message.member?.displayName || reaction.message.author.username, avatarURL)
|
|
||||||
.setDescription(reaction.message.content)
|
|
||||||
.setImage(imageCandidate?.url)
|
|
||||||
.setFooter(`Pinned by ${reaction.message.guild.members.resolve(user)?.displayName || user.username}`)
|
|
||||||
.setTimestamp(reaction.message.createdAt)
|
|
||||||
.setColor(reaction.message.member?.roles.color?.color);
|
|
||||||
let attachments = reaction.message.attachments.filter(a => !a["will be used for the image of the embed"]).map(a => `[${a.name}](${a.url})`).join('\n');
|
|
||||||
if (attachments) embed.addField("Attachments", attachments);
|
|
||||||
(await client.channels.fetch(config.archive_channel))?.send(`https://discord.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id}`, embed);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function doThing(message, user) {
|
||||||
|
var avatarURL = message.author.avatarURL({dynamic: true}) || message.author.defaultAvatarURL;
|
||||||
|
var avatarName = avatarURL.split('/').pop();
|
||||||
|
let imageCandidate = message.attachments.find(a => [".png",".jpg",".jpeg",".webp",".gif"].some(e => a.url.toLowerCase().endsWith(e)));
|
||||||
|
if (imageCandidate) imageCandidate["will be used for the image of the embed"] = true;
|
||||||
|
else imageCandidate = message.embeds.find(e => e.type == 'image');
|
||||||
|
let embed = new Discord.MessageEmbed()
|
||||||
|
.setAuthor(message.member?.displayName || message.author.username, `attachment://${avatarName}`)
|
||||||
|
.setDescription(message.content)
|
||||||
|
.setImage(imageCandidate?.url)
|
||||||
|
.setFooter(`Pinned by ${message.guild.members.resolve(user)?.displayName || user.username}`)
|
||||||
|
.setTimestamp(message.createdAt)
|
||||||
|
.setColor(message.member?.roles.color?.color);
|
||||||
|
let attachments = message.attachments.filter(a => !a["will be used for the image of the embed"]).map(a => `[${a.name}](${a.url})`).join('\n');
|
||||||
|
if (attachments) embed.addField("Attachments", attachments);
|
||||||
|
return (await client.channels.fetch(config.archive_channel))?.send({
|
||||||
|
content: `https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`,
|
||||||
|
files: [{attachment: avatarURL, name: avatarName}],
|
||||||
|
embeds:[embed]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.use("/avatars/", require("express").static(config.data_dir + "avatars/"));
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
var fetch = require("node-fetch");
|
||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
var commands = require("./commands");
|
||||||
|
|
||||||
|
client.on("messageCreate", async message => {
|
||||||
|
if (message.guild?.id != config.guild) return;
|
||||||
|
if (message.author.bot) return;
|
||||||
|
if (message.author.id == client.user.id) return;
|
||||||
|
let pixiv_urls = new Set(message.content.match(/(?<!\/)https?:\/\/(?:www\.)?pixiv\.net(?:\/en)?\/artworks\/\d+\b/g));
|
||||||
|
if (!pixiv_urls.size) return;
|
||||||
|
message.suppressEmbeds();
|
||||||
|
// suppressing embeds doesn't work on embeds that load after
|
||||||
|
client.on("messageUpdate", function arf(g,updatedMessage) {
|
||||||
|
if (updatedMessage.id == message.id) {
|
||||||
|
updatedMessage.suppressEmbeds();
|
||||||
|
// sometimes it still doesn't work
|
||||||
|
setTimeout(() => {
|
||||||
|
updatedMessage.suppressEmbeds();
|
||||||
|
}, 1000);
|
||||||
|
client.removeListener("messageUpdate", arf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
message.channel.sendTyping();
|
||||||
|
try {
|
||||||
|
await embedPixiv(message.channel, pixiv_urls, message.channel.send.bind(message.channel), pixiv_urls.size > 1);
|
||||||
|
} catch (error) {
|
||||||
|
if (pixiv_urls.size == 1) message.react('⚠');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "miku",
|
||||||
|
description: "Random Miku pic",
|
||||||
|
exec: async i => {
|
||||||
|
var mikutachi = require('./mikutachi');
|
||||||
|
await i.deferReply();
|
||||||
|
await embedPixiv(i.channel, [`https://www.pixiv.net/en/artworks/${random(mikutachi)}`], i.editReply.bind(i), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// using this code for embedding links and for slash commands
|
||||||
|
async function embedPixiv(/*message*/ channel, /*array of*/ links, send = channel.send.bind(channel) /* or interaction.reply function */, showLinks /* include pixiv links in response content */) {
|
||||||
|
for (let link of links) {
|
||||||
|
try {
|
||||||
|
let illust = Object.values(JSON.parse((await (await fetch(link)).text()).match(/<meta name="preload-data" id="meta-preload-data" content='(.*)'>/)[1]).illust)[0];
|
||||||
|
let content = `**${illust.title}**\n`;
|
||||||
|
if (showLinks || illust.illustType == 2) {
|
||||||
|
content += `<${link}>${illust.illustType == 2 ? " is an animation (must open link to play)" : ''}`;
|
||||||
|
}
|
||||||
|
let images = [];
|
||||||
|
for (let i = 0; i < illust.pageCount; i++) images.push({url: illust.urls.original.replace('p0', 'p'+i)});
|
||||||
|
if (images.length <= 10) {
|
||||||
|
try {
|
||||||
|
for (let image of images) {
|
||||||
|
image.data = await (await fetch(image.url, {headers: {"Referer": "https://www.pixiv.net/"}})).buffer();
|
||||||
|
}
|
||||||
|
await send({
|
||||||
|
content,
|
||||||
|
files: images.map(image => ({attachment: image.data || "error", name: ((illust.xRestrict && !channel.nsfw) ? 'SPOILER_' : '') + image.url.split('/').pop()}))
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message == "Request entity too large") await fallback();
|
||||||
|
else throw error;
|
||||||
|
}
|
||||||
|
} else await fallback();
|
||||||
|
function fallback() {
|
||||||
|
let urls = images.map(image => image.url.replace("i.pximg.net", "px.owo39.me"));
|
||||||
|
if (illust.xRestrict && !channel.nsfw) urls = urls.map(url => `||${url} ||`);
|
||||||
|
urls = urls.join('\n');
|
||||||
|
if (urls.length > 2000) {
|
||||||
|
return send({content, files:[{attachment: Buffer.from(urls), name:"message.txt"}]});
|
||||||
|
} else {
|
||||||
|
return send((content ? content + '\n' : '') + urls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("pixiv embed error,", error.stack);
|
||||||
|
if (showLinks) {
|
||||||
|
send(`<${link}> failed to be embedded`);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.embedPixiv = embedPixiv;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
var client = require("./client");
|
||||||
|
var DataStore = require("./datastore");
|
||||||
|
var fetch = require("node-fetch");
|
||||||
|
var {embedPixiv} = require("./pixiv-embedder");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
async function check(tag, channel) {
|
||||||
|
var ds = new DataStore(`s${tag}`);
|
||||||
|
var t = encodeURIComponent(tag);
|
||||||
|
var data = await (await fetch(`https://www.pixiv.net/ajax/search/artworks/${t}?word=${t}&order=date_d&mode=all&p=1&s_mode=s_tag_full&type=all&lang=en`)).json();
|
||||||
|
var illusts = data.body.illustManga.data;
|
||||||
|
var newPosts = [];
|
||||||
|
for (let owo of illusts) {
|
||||||
|
if (!ds.get(owo.id)) {
|
||||||
|
newPosts.push(owo);
|
||||||
|
ds.put(owo.id);
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
for (let i = newPosts.length - 1; i >= 0; i--) {
|
||||||
|
let url = `https://www.pixiv.net/en/artworks/${newPosts[i].id}`;
|
||||||
|
await embedPixiv(
|
||||||
|
client.channels.resolve(channel),
|
||||||
|
[url],
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports.check = check;
|
||||||
|
module.exports.interval = setInterval(function() {
|
||||||
|
config.pixiv_subscriptions.forEach(x => check(x.tag, x.channel));
|
||||||
|
}, 1000*60*60);
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
+168
@@ -0,0 +1,168 @@
|
|||||||
|
var commands = require("./commands");
|
||||||
|
var config = require("./config");
|
||||||
|
var deepl = require("deepl");
|
||||||
|
var Kuroshiro = require("kuroshiro").default;
|
||||||
|
var KuromojiAnalyzer = require("kuroshiro-analyzer-kuromoji");
|
||||||
|
var kuroshiro = new Kuroshiro();
|
||||||
|
kuroshiro.init(new KuromojiAnalyzer());
|
||||||
|
|
||||||
|
var lang2flag = {
|
||||||
|
BG: '🇧🇬',
|
||||||
|
CS: '🇨🇿',
|
||||||
|
DA: '🇩🇰',
|
||||||
|
DE: '🇩🇪',
|
||||||
|
EL: '🇬🇷',
|
||||||
|
EN: '🇬🇧',
|
||||||
|
'EN-GB': '🇬🇧',
|
||||||
|
'EN-US': '🇺🇸',
|
||||||
|
ES: '🇪🇸',
|
||||||
|
ET: '🇪🇪',
|
||||||
|
FI: '🇫🇮',
|
||||||
|
FR: '🇫🇷',
|
||||||
|
HU: '🇭🇺',
|
||||||
|
IT: '🇮🇹',
|
||||||
|
JA: '🇯🇵',
|
||||||
|
LT: '🇱🇹',
|
||||||
|
LV: '🇱🇻',
|
||||||
|
NL: '🇳🇱',
|
||||||
|
PL: '🇵🇱',
|
||||||
|
PT: '🇵🇹',
|
||||||
|
'PT-PT': '🇵🇹',
|
||||||
|
'PT-BR': '🇧🇷',
|
||||||
|
RO: '🇷🇴',
|
||||||
|
RU: '🇷🇺',
|
||||||
|
SK: '🇸🇰',
|
||||||
|
SL: '🇸🇮',
|
||||||
|
SV: '🇸🇻',
|
||||||
|
ZH: '🇨🇳'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
name: "toen",
|
||||||
|
description: "Translate text to English",
|
||||||
|
global: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: "STRING",
|
||||||
|
name: "text",
|
||||||
|
description: "Text to translate",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => t(i, "EN")
|
||||||
|
});
|
||||||
|
commands.push({
|
||||||
|
name: "toja",
|
||||||
|
description: "Translate text to Japanese",
|
||||||
|
global: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: "STRING",
|
||||||
|
name: "text",
|
||||||
|
description: "Text to translate",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => t(i, "JA")
|
||||||
|
});
|
||||||
|
commands.push({
|
||||||
|
name: "tolang",
|
||||||
|
description: "Translate text to any language",
|
||||||
|
global: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: "STRING",
|
||||||
|
name: "lang",
|
||||||
|
description: "Language to translate to",
|
||||||
|
required: true,
|
||||||
|
choices: [
|
||||||
|
{ name: 'Bulgarian', value: 'BG' },
|
||||||
|
{ name: 'Czech', value: 'CS' },
|
||||||
|
{ name: 'Danish', value: 'DA' },
|
||||||
|
{ name: 'German', value: 'DE' },
|
||||||
|
{ name: 'Greek', value: 'EL' },
|
||||||
|
{ name: 'English (British)', value: 'EN-GB' },
|
||||||
|
{ name: 'English (American)', value: 'EN-US' },
|
||||||
|
{ name: 'Spanish', value: 'ES' },
|
||||||
|
{ name: 'Estonian', value: 'ET' },
|
||||||
|
{ name: 'Finnish', value: 'FI' },
|
||||||
|
{ name: 'French', value: 'FR' },
|
||||||
|
{ name: 'Hungarian', value: 'HU' },
|
||||||
|
{ name: 'Italian', value: 'IT' },
|
||||||
|
{ name: 'Japanese', value: 'JA' },
|
||||||
|
{ name: 'Lithuanian', value: 'LT' },
|
||||||
|
{ name: 'Latvian', value: 'LV' },
|
||||||
|
{ name: 'Dutch', value: 'NL' },
|
||||||
|
{ name: 'Polish', value: 'PL' },
|
||||||
|
// { name: 'Portuguese (all Portuguese varieties excluding Brazilian Portuguese)', value: 'PT-PT' },
|
||||||
|
{ name: 'Portuguese (Brazilian)', value: 'PT-BR' },
|
||||||
|
{ name: 'Romanian', value: 'RO' },
|
||||||
|
{ name: 'Russian', value: 'RU' },
|
||||||
|
{ name: 'Slovak', value: 'SK' },
|
||||||
|
{ name: 'Slovenian', value: 'SL' },
|
||||||
|
{ name: 'Swedish', value: 'SV' },
|
||||||
|
{ name: 'Chinese', value: 'ZH' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "STRING",
|
||||||
|
name: "text",
|
||||||
|
description: "Text to translate",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exec: i => t(i, i.options.getString("lang"))
|
||||||
|
});
|
||||||
|
|
||||||
|
async function t(i, target_lang) {
|
||||||
|
var text = i.options.getString("text");
|
||||||
|
await i.deferReply();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var translation = (await deepl({
|
||||||
|
text,
|
||||||
|
target_lang,
|
||||||
|
free_api: true,
|
||||||
|
auth_key: config.deepl_auth_key
|
||||||
|
})).data.translations[0];
|
||||||
|
} catch (error) {
|
||||||
|
return void await i.editReply(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translation.detected_source_language == "JA") try {
|
||||||
|
var input_romaji = await kuroshiro.convert(text, {to: "romaji", mode: "spaced"});
|
||||||
|
} catch (error) {}
|
||||||
|
if (target_lang == "JA") try {
|
||||||
|
var output_romaji = await kuroshiro.convert(translation.text, {to: "romaji", mode: "spaced"});
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
let input_flag = lang2flag[translation.detected_source_language] || `[${translation.detected_source_language}]`;
|
||||||
|
let output_flag = lang2flag[target_lang] || `[${target_lang}]`;
|
||||||
|
|
||||||
|
let msg = input_flag + ' ' + text;
|
||||||
|
if (input_romaji) msg += '\n' + input_flag + ' ' + input_romaji;
|
||||||
|
msg += '\n' + output_flag + ' ' + translation.text;
|
||||||
|
if (output_romaji) msg += '\n' + output_flag + ' ' + output_romaji;
|
||||||
|
|
||||||
|
await i.editReply(msg);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var reverse_translation = (await deepl({
|
||||||
|
text: translation.text,
|
||||||
|
target_lang: translation.detected_source_language,
|
||||||
|
free_api: true,
|
||||||
|
auth_key: config.deepl_auth_key
|
||||||
|
})).data.translations[0];
|
||||||
|
if (normalize(text) != normalize(reverse_translation.text)){
|
||||||
|
if (translation.detected_source_language == "JA") try {
|
||||||
|
var reverse_romaji = await kuroshiro.convert(reverse_translation.text, {to: "romaji", mode: "spaced"});
|
||||||
|
} catch (error) {}
|
||||||
|
await i.editReply(`${msg}\n🔁 ${reverse_translation.text}${reverse_romaji ? `\n🔁 ${reverse_romaji}` : ''}`);
|
||||||
|
}
|
||||||
|
} catch(error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(text) {
|
||||||
|
return text.toLowerCase()//.split('').filter(x => x.match(/[a-z0-9 ]/)).join('');
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
|||||||
|
var client = require("./client");
|
||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
client.once("ready", () => {
|
||||||
|
(function clock() {
|
||||||
|
var d = new Date();
|
||||||
|
for (let x of config.world_clock) {
|
||||||
|
let t = x.flag + ' ' + Intl.DateTimeFormat("en", {
|
||||||
|
timeZone: x.timezone,
|
||||||
|
hour: 'numeric',
|
||||||
|
//minute: 'numeric',
|
||||||
|
timeZoneName: 'long',
|
||||||
|
hour12: true
|
||||||
|
}).format(d);
|
||||||
|
t = t.replace(/[a-z]+(?: |$)/g, ''); // most of the abbrv are GMT+n >:(
|
||||||
|
client.channels.resolve(x.channel)?.setName(t);
|
||||||
|
}
|
||||||
|
d.setMinutes(60);
|
||||||
|
setTimeout(clock, d - Date.now());
|
||||||
|
})();
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
var express = require("express");
|
||||||
|
var crypto = require("crypto");
|
||||||
|
var child_process = require("child_process");
|
||||||
|
var config = require("./config");
|
||||||
|
var client = require("./client");
|
||||||
|
|
||||||
|
var app = module.exports = express();
|
||||||
|
|
||||||
|
app.get("/update", (req, res) => {
|
||||||
|
if (!req.query.payload) return res.status(400).send("missing payload");
|
||||||
|
if (!req.headers["x-gitea-signature"]) return res.status(400).send("missing signature");
|
||||||
|
if (crypto.createHmac("sha256", config.secret).update(req.query.payload).digest("hex") != req.headers["x-gitea-signature"])
|
||||||
|
return res.status(400).send("Signature verification failed");
|
||||||
|
let data = JSON.parse(req.query.payload);
|
||||||
|
res.sendStatus(200);
|
||||||
|
if (data.commits.some(commit => commit.message.includes("%%"))) return;
|
||||||
|
child_process.exec("git fetch && git reset --hard origin/master", function(error) {
|
||||||
|
if (error) return void client.channels.resolve(config.bot_channel)?.send(`git pull: ${error.message}`);
|
||||||
|
child_process.exec("npm install", async function(error) {
|
||||||
|
if (error) return void client.channels.resolve(config.bot_channel)?.send(`npm install: ${error.message}`);
|
||||||
|
await client.channels.resolve(config.bot_channel)?.send('t');
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.type("text/plain").send(require("util").inspect(client));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.server = app.listen(9251);
|
||||||
Reference in New Issue
Block a user