🚀 First release v1.0.0

This commit is contained in:
Axel Olausson Holtenäs 2022-02-26 23:45:14 +01:00
parent 430f92cde7
commit 0d3fad2683
No known key found for this signature in database
GPG key ID: E3AE7E194AE017ED
14 changed files with 727 additions and 0 deletions

46
.eslintrc Normal file
View file

@ -0,0 +1,46 @@
{
"extends": ["airbnb", "prettier"],
"plugins": ["prettier"],
"rules": {
"arrow-spacing": ["warn", { "before": true, "after": true }],
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"handle-callback-err": "off",
"indent": ["error", "tab"],
"keyword-spacing": "error",
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"no-console": "off",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
"no-trailing-spaces": ["error"],
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"space-before-blocks": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

136
.gitignore vendored Normal file
View file

@ -0,0 +1,136 @@
json.sqlite
node_modules
.env
config.json
package-lock.json
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

4
.prettierrc Normal file
View file

@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}

49
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,49 @@
{
"editor.wordWrapColumn": 100,
"editor.tabSize": 2,
// "workbench.colorCustomizations": {
// "titleBar.activeBackground": "#336699",
// "titleBar.inactiveBackground": "#33669999",
// "titleBar.activeForeground": "#e7e7e7",
// "titleBar.inactiveForeground": "#e7e7e799",
// "statusBarItem.hoverBackground": "#407fbf",
// "activityBar.activeBackground": "#407fbf",
// "activityBar.activeBorder": "#77284f",
// "activityBar.background": "#407fbf",
// "activityBar.foreground": "#e7e7e7",
// "activityBar.inactiveForeground": "#e7e7e799",
// "activityBarBadge.background": "#77284f",
// "activityBarBadge.foreground": "#e7e7e7",
// "sash.hoverBorder": "#407fbf",
// "statusBarItem.remoteBackground": "#336699",
// "statusBarItem.remoteForeground": "#e7e7e7"
// },
"editor.formatOnSave": true,
"javascript.format.enable": false,
"eslint.autoFixOnSave": true,
"files.eol": "\n",
"prettier.endOfLine": "lf",
"prettier.singleQuote": true,
"prettier.trailingComma": "es5",
"peacock.color": "#369",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#407fbf",
"activityBar.activeBorder": "#77284f",
"activityBar.background": "#407fbf",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#77284f",
"activityBarBadge.foreground": "#e7e7e7",
"sash.hoverBorder": "#407fbf",
"statusBarItem.hoverBackground": "#407fbf",
"statusBarItem.remoteBackground": "#336699",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#336699",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#33669999",
"titleBar.inactiveForeground": "#e7e7e799"
}
}

83
commands/balance.js Normal file
View file

@ -0,0 +1,83 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const db = require('quick.db');
const credits = new db.table('credits');
module.exports = {
data: new SlashCommandBuilder()
.setName('balance')
.setDescription('Check your balance or view top balance.')
.addSubcommand((subcommand) =>
subcommand
.setName('check')
.setDescription('Check your balance')
.addUserOption((option) =>
option
.setName('user')
.setDescription('The user whose balance you want to check.')
.setRequired(false)
)
)
.addSubcommand((subcommand) => subcommand.setName('top').setDescription('View top balance')),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
if (interaction.options.getSubcommand() === 'check') {
const user = await interaction.options.getUser('user');
const amount = await credits.get(user ? user.id : interaction.user.id);
if (amount) {
const embed = {
title: 'Balance',
description: `${user ? `${user} has` : 'You have'} ${
amount <= 1 ? `${amount} credit` : `${amount} credits`
}.`,
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else {
const embed = {
title: 'Balance',
description: `${user} has no credits.`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
}
} else if (interaction.options.getSubcommand() === 'top') {
const { client } = interaction;
const all = credits.all();
const allSorted = all.sort((a, b) => (a.data > b.data ? -1 : 1));
const topTen = allSorted.slice(0, 10);
const topTens = [];
Promise.all(
topTen.map(async (x, i) => {
user = await client.users.fetch(`${x.ID}`);
topTens.push({ index: i, user: user, credits: x.data });
})
).then(async () => {
const embed = {
title: 'Balance Top',
description: `Below are the top ten.\n
${topTens
.map(
(x) =>
`**Top ${x.index + 1}** - ${x.user}: ${
x.credits <= 1 ? `${x.credits} credit` : `${x.credits} credits`
}`
)
.join('\n')}`,
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
});
}
},
};

70
commands/gift.js Normal file
View file

@ -0,0 +1,70 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const db = require('quick.db');
const credits = new db.table('credits');
module.exports = {
data: new SlashCommandBuilder()
.setName('gift')
.setDescription('Gift from your credits to another user.')
.addUserOption((option) =>
option.setName('user').setDescription('The user you want to pay.').setRequired(true)
)
.addIntegerOption((option) =>
option.setName('amount').setDescription('The amount you will pay.').setRequired(true)
),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
const user = await interaction.options.getUser('user');
const amount = await interaction.options.getInteger('amount');
if (user.id === interaction.user.id) {
const embed = {
title: 'Gift failed',
description: "You can't pay yourself.",
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else if (amount <= 0) {
const embed = {
title: 'Gift failed',
description: "You can't pay zero or below.",
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else {
if ((await credits.get(interaction.user.id)) < amount) {
const embed = {
title: 'Gift',
description: `You have insufficient credits. Your balance is ${credits.get(
interaction.user.id
)}`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else {
await credits.subtract(interaction.user.id, amount);
await credits.add(user.id, amount);
const embed = {
title: 'Gift',
description: `You sent ${
amount <= 1 ? `${amount} credit` : `${amount} credits`
} to ${user}. Your new balance is ${await credits.get(interaction.user.id)}.`,
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
}
}
},
};

52
commands/give.js Normal file
View file

@ -0,0 +1,52 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { Permissions } = require('discord.js');
const db = require('quick.db');
const credits = new db.table('credits');
module.exports = {
data: new SlashCommandBuilder()
.setName('give')
.setDescription('Give a user credits (ADMIN).')
.addUserOption((option) =>
option.setName('user').setDescription('The user you want to pay.').setRequired(true)
)
.addIntegerOption((option) =>
option.setName('amount').setDescription('The amount you will pay.').setRequired(true)
),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
if (!interaction.member.permissions.has(Permissions.FLAGS.MANAGE_GUILD)) {
const embed = {
title: 'Give failed',
description: 'You need to have permission to manage this guild (MANAGE_GUILD)',
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
}
const user = await interaction.options.getUser('user');
const amount = await interaction.options.getInteger('amount');
if (amount <= 0) {
const embed = {
title: 'Give',
description: "You can't give zero or below.",
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else {
await credits.add(user.id, amount);
const embed = {
title: 'Give',
description: `Gave ${amount <= 1 ? `${amount} credit` : `${amount} credits`} to ${user}.`,
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
}
},
};

107
commands/redeem.js Normal file
View file

@ -0,0 +1,107 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { disableRedeem } = require('../config.json');
const db = require('quick.db');
const credits = new db.table('credits');
const api = require('../handlers/api.js');
const { v4: uuidv4 } = require('uuid');
module.exports = {
data: new SlashCommandBuilder()
.setName('redeem')
.setDescription('Redeem your credits.')
.addIntegerOption((option) =>
option.setName('amount').setDescription('How much credit you want to withdraw.')
),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
if (disableRedeem) {
const embed = {
title: 'Redeem failed',
description: `Redeem is disabled until further.`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await await interaction.editReply({ embeds: [embed], ephemeral: true });
}
const amount = await interaction.options.getInteger('amount');
const userCredits = await credits.get(interaction.user.id);
if ((amount || userCredits) < 100) {
const embed = {
title: 'Redeem',
description: `You can't redeem below 100. Your balance is ${userCredits}.`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else if ((amount || userCredits) > 1000000) {
const embed = {
title: 'Redeem',
description: `You can't redeem over 1,000,000. Your balance is ${userCredits}.`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else if (userCredits < amount) {
const embed = {
title: 'Redeem',
description: `You have insufficient credits. Your balance is ${userCredits}.`,
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
} else {
const code = uuidv4();
api
.post('vouchers', {
uses: 1,
code,
credits: amount || userCredits,
memo: `${interaction.createdTimestamp} - ${interaction.user.id}`,
})
.then(async (res) => {
const embed = {
title: 'Redeem',
description: `Your new balance is ${userCredits - (amount || userCredits)}.`,
fields: [
{ name: 'Code', value: `${code}`, inline: true },
{
name: 'Credits',
value: `${amount || userCredits}`,
inline: true,
},
],
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
await credits.subtract(interaction.user.id, amount || userCredits);
await interaction.editReply({ embeds: [embed], ephemeral: true });
})
.catch(async (err) => {
console.log(err);
const embed = {
title: 'Redeem',
description: 'Something went wrong.',
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.editReply({ embeds: [embed], ephemeral: true });
});
}
},
};

59
commands/take.js Normal file
View file

@ -0,0 +1,59 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { Permissions } = require('discord.js');
const db = require('quick.db');
const credits = new db.table('credits');
module.exports = {
data: new SlashCommandBuilder()
.setName('take')
.setDescription('Take credits from a user (ADMIN).')
.addUserOption((option) =>
option
.setName('user')
.setDescription('The user you want to take credits from.')
.setRequired(true)
)
.addIntegerOption((option) =>
option.setName('amount').setDescription('The amount you will take.').setRequired(true)
),
async execute(interaction) {
if (!interaction.member.permissions.has(Permissions.FLAGS.MANAGE_GUILD)) {
const embed = {
title: 'Take',
description: 'You need to have permission to manage this guild (MANAGE_GUILD)',
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.reply({ embeds: [embed], ephemeral: true });
}
const user = await interaction.options.getUser('user');
const amount = await interaction.options.getInteger('amount');
if (amount <= 0) {
const embed = {
title: 'Take',
description: "You can't take zero or below.",
color: 0xbb2124,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.reply({ embeds: [embed], ephemeral: true });
} else {
await credits.subtract(user.id, amount);
const embed = {
title: 'Take',
description: `You took ${
amount <= 1 ? `${amount} credit` : `${amount} credits`
} to ${user}.`,
color: 0x22bb33,
timestamp: new Date(),
footer: { text: 'Zyner Bot' },
};
return await interaction.reply({ embeds: [embed], ephemeral: true });
}
},
};

10
config.json.example Normal file
View file

@ -0,0 +1,10 @@
{
"clientId": "BOT CLIENT ID",
"guildId": "THE GUILD ID YOU WANT THE BOT TO WORK IN",
"token": "BOT TOKEN",
"api": {
"url": "scheme://DOMAIN/api/",
"token": "CONTROLPANEL.GG TOKEN"
},
"disableRedeem": false
}

21
deploy-commands.js Normal file
View file

@ -0,0 +1,21 @@
const fs = require("node:fs");
const { REST } = require("@discordjs/rest");
const { Routes } = require("discord-api-types/v9");
const { clientId, guildId, token } = require("./config.json");
const commands = [];
const commandFiles = fs
.readdirSync("./commands")
.filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
commands.push(command.data.toJSON());
}
const rest = new REST({ version: "9" }).setToken(token);
rest
.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
.then(() => console.log("Successfully registered application commands."))
.catch(console.error);

7
handlers/api.js Normal file
View file

@ -0,0 +1,7 @@
const axios = require('axios');
const { api } = require('../config.json');
module.exports = axios.create({
baseURL: api.url,
headers: { Authorization: `Bearer ${api.token}` },
});

49
index.js Normal file
View file

@ -0,0 +1,49 @@
const fs = require('node:fs');
const { Client, Collection, Intents } = require('discord.js');
const { token } = require('./config.json');
const db = require('quick.db');
const credits = new db.table('credits');
const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
client.commands = new Collection();
const commandFiles = fs.readdirSync('./commands').filter((file) => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.data.name, command);
}
client.once('ready', () => {
console.log('Ready!');
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) return;
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
await interaction.reply({
content: 'There was an error while executing this command!',
ephemeral: true,
});
}
});
client.on('messageCreate', async (message) => {
if (message.author.id === message.client.user.id) return;
credits.add(message.author.id, 1);
console.log(message.author, message.content);
});
client.login(token);

34
package.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "controlpanel.gg-bot",
"version": "1.0.0",
"description": "Earn credits when chatting",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"zyner",
"controlpanel",
"controlpanel.gg",
"vermium"
],
"repository": {
"type": "git",
"url": "https://github.com/ZynerOrg/controlpanel.gg-bot.git"
},
"author": "Vermium Sifell <vermium@zyner.org> (https://zyner.org)",
"license": "GPL-3.0-only",
"bugs": {
"url": "https://github.com/ZynerOrg/controlpanel.gg-bot/issues",
"email": "vermium@zyner.org"
},
"dependencies": {
"@discordjs/rest": "^0.3.0",
"axios": "^0.26.0",
"better-sqlite3": "^7.5.0",
"discord-api-types": "^0.27.3",
"discord.js": "^13.6.0",
"quick.db": "^7.1.3",
"uuid": "^8.3.2"
}
}