Kit Kasune 3 years ago
commit a2663eafe4
  1. 35
      .vscode/discordjs.code-snippets
  2. 137
      README.md
  3. 2
      commands/dev/eval.js
  4. 52
      commands/fun/kiss.js
  5. 72
      commands/misc/ar.js
  6. 2
      commands/misc/emoji.js
  7. 38
      commands/social/kiss.js
  8. 2
      events/guildCreate.js
  9. 2
      events/guildMemberAdd.js
  10. 2
      events/messageUpdate.js

@ -0,0 +1,35 @@
{
// Place your Natsuki workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"discord.js bot command": {
"scope": "javascript,typescript",
"prefix": "djscmd",
"body": [
"const Discord = require('discord.js');${1:\nconst = require(\"\");\n}",
"module.exports = {",
" name: \"$2\",",
" aliases: [$3],",
" meta: {",
" category: '$4',",
" description: \"$5\",",
" syntax: '`$6`',",
" extra: ${7:null}${8:,\n guildOnly: true}",
" },",
" help: new Discord.MessageEmbed()",
" .setTitle(\"Help -> $9\")",
" .setDescription(\"$10\")${11:\n .addField(\"\", \"\")}",
" .addField(\"Syntax\", \"`$6`\"),",
" async execute(message, msg, args, cmd, prefix, mention, client) {",
" if (!args.length) {return message.channel.send(`Syntax: \\`\\${prefix}$6\\``);}",
" $0",
" }",
"};",
],
"description": "Creates a new command"
}
}

@ -1,12 +1,106 @@
# Natsuki
The official repository of Natsuki the Discord Bot. Currently in the Early Development phase, and is expected to release to a few servers and eventually bot lists relatively soon.
The official repository of Natsuki the Discord Bot.
Developed solely by WubzyGD, and intended to help others learn the ropes of discord.js, as well as provide some useful utilities to make the troubles of bot development just a little easier.
Skip down to [**Open-Source**](https://github.com/NatsukiDev/Natsuki#open-source) if you wanna get straight to ~~robbing me~~ seeing some of the awesome stuff Natsuki has to offer.
> Natsuki is now also on Discord.js' latest update, v13!
## Features
Natsuki is an anime-focused Discord bot with more than her fair share of features.
She features lots of different focuses in her commands, as well as:
- **99.99%** uptime over her year of life
- Less than ~**100-150ms** command response time
- **Active**, **open-source** development
> *Oh yeah, and a developer with no life, so she's always being updated*
Natsuki's commands and abilities include, but are absolutely not limited to:
### Moderation
- Kick/Ban/Softban
- Advanced Warning System
- Custom Welcome/Leave Messages
- Autorole/Join-role
### Fun
- Deathnote 👀
- Last.fm API (nowplaying, etc)
- A fully-functional Secret Santa
- ~~A fortune-teller~~ 8ball
### Social
- Over 20 emotes like hug/kiss/cry/sip etc
- AFK/DnD commands
- Bio and user info
- Customizable Star Board
- Customizable auto-responses
### Utility
- Server activity monitoring
- Emoji utilities like creation/robbing
- Random number utilities
- Advanced to-do lists
- Coin-flipping
- Custom prefix
### Leveling
- Levelup messages
- Custom level message channel
- Leveling Roles
- Leaderboard
## FAQ
> *Can I self-host Natuski?*
Natsuki is not meant to be self-hosted. I suppose she is indeed open-source, so if you knew what you were doing, you could build her and self-host her, but I don't condone it.
> *Can I copy Natsuki's code?*
Natuski's utilities (see below) are meant to be downloaded and copied, and some of her frameworking like command handlers are great for copying, but the point of her open-sourcing is not for you to rip apart every command and event. It's primarily for you to make use of the utilities and use the rest as a learning tool to reference and take example from.
Do note that **all** Discord bot lists **do not** allow *any* copying or forking of code. Using my utility classes and functions? Totally okay! :D Forking the repository or copy-pasting all the commands etc etc? Not coolio.
Bottom line, take what you want, but know that taking too much is not my intention and will hinder your bot's growth.
> *Can I help develop for Natsuki?*
Well gee, I thought you'd never ask! Yes! Simply make changes and submit them as a Pull Request.
If you're an active contributor, I might be able and willing to welcome you as a more permanent and official developer. Important contributors are a important part of the bot, and will be given credit for their efforts!
If developing isn't your thing, you're free to join the support server and suggest something or report a bug. I'm very very much open to suggestions and feedback.
# Open-Source
Here are some useful things to... well... make *use* of in your own code!
## Handlers/Structure
If you check out anything in the `/handle` folder, you'll find some super amazing awesome command and event loaders.
The command loader reads command files in `/commands` based on the template in `template.js`. Aliases are optional, and the help field can be a string or an embed. Commands are automatically loaded up to one subdirectory deep, meaning `commands/command.js` will load, and `commands/somefolder/command.js` will also load.
You'll have to write the commands' execution and help displays on your own, but to get them loaded into client seamlessly and effectively, this is a super strong method.
The event loader will load events from `/events`, whose file names are the discord.js event name. These are automatically placed into your client, so you shouldn't have to worry about adding your own code in this case.
## Utils
*Some useful stuff we have in our bot that you can make use of!*
Just about anything in the utils folder could be useful for a number of reasons, but here's a few big ones:
### Tags
> util/tagfilter.js and util/tag.js
Pass in a list of Tags. Each Tag has a list of triggers, its name, then its mode.
@ -33,6 +127,7 @@ let options = new TagFilter([
```
### Quick awaitMessages
> util/ask.js
Ask a question and wait for an answer. Returns the string of the user's response, or nothing if there was no response. **Function is asynchronous!**
@ -50,6 +145,7 @@ new Pagination()
```
### Pagination
> util/pagination.ts
Create a pagination based on a list of Discord MessageEmbeds.
@ -74,4 +170,39 @@ await help.start({
_Please note that the Pagination class is still in the works. Only one bug is currently known, and it's that the Pagination will error when a user tries to end the pagination in a DM channel, but this error is not extremely high-level and shouldn't have any major effects on your node process._
_Another note: you'll want to go into the pagination.js file and search for .setFooter() and change the name Natsuki to whatever name your bot is_
_Another note: you'll want to go into the pagination.js file and search for .setFooter() and change the name Natsuki to whatever name your bot is_
### Clean Timeout
> util/wait.js
Using Promises, wait an amount of time before you do something, but not have to deal with pesky setTimeout syntax, especially when you don't want to get way too nested with your code.
```js
const wait = require('./util/wait');
let m = await message.channel.send("hacking the mainframe...");
await wait(5000); // time is in ms
return m.edit("mainframe hacked.");
```
Or, alternatively, use `.then()` syntax so your whole code doesn't have to wait.
```js
const wait = require('./util/wait');
let m = await message.channel.send("hacking the mainframe...");
wait(5000).then(() => {return m.edit("mainframe hacked.");});
//some other stuff that will happen without waiting for the message to edit
```
### Other Utils
Have lots of free time and a good knowledge of JavaScript and discord.js?
Check out these files for some more reference and inspiration.
- `/cache/` and `/cache.js` -> A strong way to cache stuff from your database on startup.
- `/lxp/cacheloop.js` -> How I keep a cache of leveling information that is synced periodically and has a 10-minute entry expiry to save memory.
- `/makeid.js` -> Creates a hash string of your choice in length
- Anything in `/response/` -> A massive amount of code to save, parse, send, and filter responses saved by users, which encompasses things like custom embeds and messages, allowing welcome and leave messages to work.

@ -6,7 +6,7 @@ const {TagFilter} = require('../../util/tagfilter');
module.exports = {
name: 'eval',
aliases: ['ev', ':', 'e'],
aliases: ['ev', ':'],
help: "Evaluates raw JavaScript code. *This is a __developer-only__ command.* Usage: `{{p}}eval <code>`",
meta: {
category: 'Developer',

@ -1,52 +0,0 @@
const Discord = require('discord.js');
const Saves = require('../../models/saves');
const UserData = require('../../models/user');
const makeId = require('../../util/makeid');
module.exports = {
name: "kiss",
help: "Ask for a kiss with `{{p}}kiss`, or give one by mentioning someone!",
meta: {
category: 'Fun',
description: "Give someone a kiss, or show that you're looking for one. Best of luck pal!",
syntax: '`kiss <@user>`',
extra: null
},
async execute(message, msg, args, cmd, prefix, mention, client) {
let savess = await Saves.findOne({name: 'kiss'}) ? await Saves.findOne({name: 'kiss'}) : new Saves({name: 'kiss'});
let saves = savess.saves;
if (!args.length) {
return message.channel.send(message.guild ? {embeds: [new Discord.MessageEmbed()
.setTitle(`${message.guild ? message.member.displayName : message.author.username} wants a kiss!`)
.setThumbnail(message.author.avatarURL({size: 2048}))
.setDescription(`Give them a little kiss with \`${prefix}kiss @${message.member.displayName}\`!`)
.setColor('c375f0')
.setFooter('Natsuki', client.user.avatarURL())
.setTimestamp()]}
: "I'm not really into that kind of thing. Maybe try asking in a server?"
);}
if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) {
if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to kiss!");}
if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");}
if (message.author.id === mention.id) {return message.reply("A self-kiss ought to be a little hard, don't you think?");}
return message.channel.send({embeds: [new Discord.MessageEmbed()
.setAuthor(`${message.guild ? message.member.displayName : message.author.username} kisses ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL())
.setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)]))
.setColor('d428a0')
]});
}
if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) {
if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');}
let tu = await UserData.findOne({uid: message.author.id});
if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new kiss GIFs.");}
let e = true;
let id;
while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}}
args.shift();
saves.set(id, args.join(" ").trim());
savess.saves = saves;
savess.save();
return message.channel.send("Save added!");
}
}
};

@ -4,6 +4,7 @@ const AR = require('../../models/ar');
const GuildData = require('../../models/guild');
const ask = require('../../util/ask');
const {Pagination} = require('../../util/pagination');
module.exports = {
name: "ar",
@ -27,23 +28,66 @@ module.exports = {
function sortARs(tar) {
let t = tar.triggers;
let res = {};
res.paginate = t.length > 10;
let ar = tar.ars;
let s = '';
for (let i=0;i<t.length;i++) {s+=`\`${i+1}.\` ${t[i]}\n-> ${ar[i]}\n\n`;}
return s;
if (res.paginate) {
let pages = [];
let cond = false;
let x = 0;
while (true) {
let s = '';
for (let i = 0; i < 10; i++) {
if (ar[(x * 10) + i] === undefined) {cond = true; break;}
s += `\`${(x*10)+i+1}.\` ${t[(x * 10) + i]}\n-> ${ar[(x * 10) + i]}\n\n`;
if ((x * 10) + i >= ar.length) {cond = true; break;}
}
pages.push(new Discord.MessageEmbed()
.setTitle(`Auto-Responses in this Server`)
.setDescription(s)
.setColor('c375f0')
.setTimestamp()
);
if (cond) {break;}
x++;
}
res.pagination = new Pagination(message.channel, pages, message, client, true);
} else {
let s = '';
for (let i=0;i<t.length;i++) {s+=`\`${i+1}.\` ${t[i]}\n-> ${ar[i]}\n\n`;}
res.s = s;
}
return res;
}
function viewARs(string) {
return new Discord.MessageEmbed()
.setTitle("Auto-Responses in this Server")
.setDescription(string)
.setColor('c375f0')
.setFooter("Natsuki", client.user.avatarURL())
.setTimestamp();
function viewARs(res, mode) {
return new Promise(async resolve => {
if (res.paginate) {
if (mode) {res.pagination.pages.forEach(page => page.addField(mode === 'edit' ? "Editing" : 'Deletion', `Please say the **number** of the AR you wish to ${mode}.`));}
let r = await res.pagination.start({endTime: 60000, user: message.author.id});
return resolve(r);
} else {
let string = res.s;
let embed = new Discord.MessageEmbed()
.setTitle("Auto-Responses in this Server")
.setDescription(string)
.setColor('c375f0')
.setFooter("Natsuki", client.user.avatarURL())
.setTimestamp();
if (mode) {embed.addField(mode === 'edit' ? "Editing" : 'Deletion', `Please say the **number** of the AR you wish to ${mode}.`);}
let r = await message.channel.send({embeds: [embed]});
return resolve(r);
}
});
}
if (['a', 'add'].includes(args[0].toLowerCase())) {
let trigger = await ask(message, "What would you like the trigger to be? This is the message that will make your AR work.", 120000); if (!trigger) {return;}
let trigger;
if (args[1]) {
let targs = args.slice(0);
targs.shift();
trigger = targs.join(' ');
} else {trigger = await ask(message, "What would you like the trigger to be? This is the message that will make your AR work.", 120000); if (!trigger) {return;}}
if (`${trigger}`.length > 150) {return message.channel.send("Your trigger needs to be less than 150 characters, please!");}
let response = await ask(message, "What would you like my response to be?", 120000); if (!response) {return;}
if (`${response}`.length > 300) {return message.channel.send("Your response needs to be less than 300 characters, please!");}
@ -64,7 +108,7 @@ module.exports = {
if (!tar || !tar.triggers.length) {return message.channel.send("You can't edit any auto-responses... because there aren't any here...");}
let sar = sortARs(tar);
await message.channel.send({embeds: [viewARs(sar).addField("Editing", "Please say the **number** of the AR you wish to edit.")]});
await viewARs(sar, 'edit');
let collected;
try {collected = await message.channel.awaitMessages({filter: m => m.author.id === message.author.id, errors: ['time'], time: 60001, max: 1});}
catch {return message.channel.send("This question has timed out. Please try again!");}
@ -89,7 +133,7 @@ module.exports = {
if (!tar || !tar.triggers.length) {return message.channel.send("It's not like this server has any ARs for me to delete in the first place!");}
let sar = sortARs(tar);
await message.channel.send({embeds: [viewARs(sar).addField("Deletion", "Please say the **number** of the AR you wish to delete.")]});
await viewARs(sar, 'delete');
let collected;
try {collected = await message.channel.awaitMessages({filter: m => m.author.id === message.author.id, errors: ['time'], time: 60000, max: 1});}
catch {return message.channel.send("This question has timed out. Please try again!");}
@ -113,7 +157,7 @@ module.exports = {
if (['v', 'view', 'l', 'list'].includes(args[0].toLowerCase())) {
let tar = await AR.findOne({gid: message.guild.id});
if (!tar || !tar.triggers.length) {return message.channel.send("This server has no ARs!");}
return message.channel.send({embeds: [viewARs(sortARs(tar))]});
return viewARs(sortARs(tar));
}
if (['s', 'settings'].includes(args[0].toLowerCase())) {

@ -4,7 +4,7 @@ const {Pagination} = require('../../util/pagination');
module.exports = {
name: "emoji",
aliases: ['emote', 'emojiinfo', 'emoteinfo', 'ei'],
aliases: ['emote', 'emojiinfo', 'emoteinfo', 'ei', 'e'],
meta: {
category: 'Misc',
description: "Get info on a certain emoji",

@ -1,9 +1,6 @@
const Discord = require('discord.js');
const Saves = require('../../models/saves');
const UserData = require('../../models/user');
const VC = require('../../models/vscount');
const makeId = require('../../util/makeid');
module.exports = {
@ -20,35 +17,28 @@ module.exports = {
let saves = savess.saves;
if (!args.length) {
return message.channel.send(message.guild ? {embeds: [new Discord.MessageEmbed()
.setTitle(`${message.guild ? message.member.displayName : message.author.username} wants a kiss!`)
.setThumbnail(message.author.avatarURL({size: 2048}))
.setDescription(`Give them a little kiss with \`${prefix}kiss @${message.member.displayName}\`!`)
.setColor('328ba8')
.setFooter('Luno', client.user.avatarURL())
.setTimestamp()]}
: "Sorry..my lips are for Crescent only!"
);}
.setTitle(`${message.guild ? message.member.displayName : message.author.username} wants a kiss!`)
.setThumbnail(message.author.avatarURL({size: 2048}))
.setDescription(`Give them a little kiss with \`${prefix}kiss @${message.member.displayName}\`!`)
.setColor('c375f0')
.setFooter('Natsuki', client.user.avatarURL())
.setTimestamp()]}
: "I'm not really into that kind of thing. Maybe try asking in a server?"
);}
if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) {
if (!message.guild) {return message.reply("I'd rather kiss in public..maybe try in a server?");}
if (!message.guild.members.cache.has(mention.id)) {return message.reply("That person must have ran from your love..");}
if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to kiss!");}
if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");}
if (message.author.id === mention.id) {return message.reply("A self-kiss ought to be a little hard, don't you think?");}
let kiss = await VC.findOne({uid: message.author.id, countOf: 'kiss'}) || new VC({uid: message.author.id, countOf: 'kiss'});
kiss.against[mention.id] = kiss.against[mention.id] ? kiss.against[mention.id] + 1 : 1;
kiss.total++;
kiss.markModified(`against.${mention.id}`);
kiss.save();
return message.channel.send({embeds: [new Discord.MessageEmbed()
.setAuthor(`${message.guild ? message.member.displayName : message.author.username} gives ${message.guild.members.cache.get(mention.id).displayName} a kiss!`, message.author.avatarURL())
.setDescription(`You've kissed them **${kiss.against[mention.id] === 1 ? 'once' : `${kiss.against[mention.id]} times!`}**`)
.setAuthor(`${message.guild ? message.member.displayName : message.author.username} kisses ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL())
.setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)]))
.setColor('ac0f0f')
.setFooter(`${kiss.total} kisse${kiss.total === 1 ? '' : 's'} total`)
.setColor('d428a0')
]});
}
if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) {
if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');}
let tu = await UserData.findOne({uid: message.author.id});
if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new kissing GIFs.");}
if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new kiss GIFs.");}
let e = true;
let id;
while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}}
@ -56,7 +46,7 @@ module.exports = {
saves.set(id, args.join(" ").trim());
savess.saves = saves;
savess.save();
return message.channel.send("Save added master!");
return message.channel.send("Save added!");
}
}
};

@ -19,7 +19,7 @@ module.exports = async (client, guild) => {
.setTitle(guild.name)
.setThumbnail(guild.iconURL({size: 2048}))
.addField('Owner', client.users.cache.get(guild.ownerId).tag, true)
.addField('Members', guild.members.cache.size, true)
.addField('Members', `${guild.members.cache.size}`, true)
.addField('Position', `Server #${client.guilds.cache.size}`, true)
.setColor('55ff7f')
.setFooter('Natsuki')

@ -14,6 +14,6 @@ module.exports = async (client, member) => {
&& member.guild.channels.cache.get(tg.wch).permissionsFor(client.user.id).has("SEND_MESSAGES")
&& !client.users.cache.get(member.id).bot
) {
try {member.guild.channels.cache.get(tg.wch).send(await sendResponse(member, member.guild.channels.cache.get(tg.wch), 'xdlol', client, tr.responses.get(tr.bindings.get('welcome'))));} catch {}
try {member.guild.channels.cache.get(tg.wch).send(await sendResponse(member, member.guild.channels.cache.get(tg.wch), 'xdlol', client, tr.responses.get(tr.bindings.get('welcome'))).catch(() => {}));} catch {}
}
};

@ -9,6 +9,8 @@ module.exports = async (client, oldM, newM) => {
//if (!Object.keys(snipe.edit).includes(oldM.guild.id)) {snipe.edit[oldM.guild.id] = {};};
//snipe.edit[oldM.guild.id][oldM.channel.id] = {old: oldM, cur: newM};
if (!oldM.guild.id) {return;}
let ts = client.guildconfig.logs.has(oldM.guild.id) && client.guildconfig.logs.get(oldM.guild.id).has('medit') ? client.guildconfig.logs.get(oldM.guild.id).get('medit') : null;
if (ts) {if (oldM.guild.channels.cache.has(ts) && oldM.guild.channels.cache.get(ts).permissionsFor(client.user.id).has("SEND_oldMS")) {
let embed = new Discord.MessageEmbed()

Loading…
Cancel
Save