Compare commits

...
Author SHA1 Message Date
bc497c7dea
Fix sending links if they didn't get changed
Some checks failed
CI / CI (push) Has been cancelled
2026-02-27 19:55:37 +01:00
d7b8e706b8
Add ig link replacing command
Some checks are pending
CI / CI (push) Waiting to run
2026-02-27 19:28:54 +01:00
7791fa5654
Improve error message when message.delete() fails
Some checks are pending
CI / CI (push) Waiting to run
2026-02-27 19:25:40 +01:00
9796e9245a
Don't send an unchanged link in x command 2026-02-27 19:24:01 +01:00
62b8ebb4cf
Use lookbehind to improve x command regex 2026-02-27 19:23:36 +01:00
62fbb445e2
Send a message if no X/Twitter links are provided
Some checks are pending
CI / CI (push) Waiting to run
2026-02-26 18:30:02 +01:00
1f48a896a5
Add X command that replaces X/Twitter links with fxtwitter
Some checks failed
CI / CI (push) Has been cancelled
2026-02-26 10:58:18 +01:00
94d1b0f297
Make the say command admin only
Some checks are pending
CI / CI (push) Waiting to run
2026-02-26 10:52:40 +01:00
cafcc007c1
Don't specify global pfp when there's only one
All checks were successful
CI / CI (push) Successful in 22s
2026-01-29 22:37:59 +01:00
af4c42009c
Add channel count to serverinfo
All checks were successful
CI / CI (push) Successful in 22s
2025-10-22 09:45:52 +02:00
f1f9e3aaa3
Use correct variables
All checks were successful
CI / CI (push) Successful in 25s
2025-09-07 08:01:09 +02:00
4aaf72e4b4
Limit iterations to listens if less than 10
All checks were successful
CI / CI (push) Successful in 22s
2025-09-07 07:57:20 +02:00
69872ba0d1
Add artist count check to getTopArtists
All checks were successful
CI / CI (push) Successful in 22s
2025-09-07 07:49:10 +02:00
7dab688298
Slice off alpha from imagemagick output
All checks were successful
CI / CI (push) Successful in 22s
2025-09-06 03:47:45 +02:00
SileNce5k
3ee2a80201
Set embed to the average color of the album cover on fm command (#168)
And do not set a color in misc/fm.js when there already is one set.
2025-09-06 03:32:36 +02:00
8655f279f9
Use correct whitelist property
All checks were successful
CI / CI (push) Successful in 1m36s
2025-07-27 14:59:32 +02:00
aa10d6bc27
Fix mc command by converting all info to strings
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 14:55:15 +02:00
3d674f12e0
Fix mc command
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 14:45:23 +02:00
0a8df28a94
Add minecraft command to get server info
Some checks are pending
CI / CI (push) Waiting to run
2025-07-27 13:52:53 +02:00
dependabot[bot]
f78f4648d0
Bump dotenv from 17.2.0 to 17.2.1 (#161)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.2.0 to 17.2.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.2.0...v17.2.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 20:05:41 +02:00
dependabot[bot]
7f0029d9f9
Bump jest from 30.0.4 to 30.0.5 (#160)
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.4 to 30.0.5.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.5/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 19:59:22 +02:00
dependabot[bot]
681eca87e0
Bump dotenv from 17.1.0 to 17.2.0 (#159)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.1.0 to 17.2.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.1.0...v17.2.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 06:52:44 +02:00
dependabot[bot]
914aceeb44
Bump dotenv from 17.0.1 to 17.1.0 (#158)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.0.1 to 17.1.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.0.1...v17.1.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-09 04:27:47 +02:00
dependabot[bot]
dc7b49d255
Bump jest from 30.0.3 to 30.0.4 (#157)
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.3 to 30.0.4.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.4/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-04 03:30:50 +02:00
dependabot[bot]
c1d942e204
Bump dotenv from 17.0.0 to 17.0.1 (#156)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.0.0 to 17.0.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.0.0...v17.0.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-02 02:47:58 +02:00
dependabot[bot]
c53b707163
Bump dotenv from 16.6.1 to 17.0.0 (#155)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.6.1 to 17.0.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v16.6.1...v17.0.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 02:07:59 +02:00
dependabot[bot]
9dcc7adab4
Bump dotenv from 16.5.0 to 16.6.1 (#154)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.5.0 to 16.6.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v16.5.0...v16.6.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 16.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-27 23:52:30 +02:00
1de446aede
Use npm ci to install dependencies
All checks were successful
CI / CI (push) Successful in 23s
2025-06-26 23:30:55 +02:00
dependabot[bot]
94467bd57e
Bump discord.js from 14.20.0 to 14.21.0 (#153)
All checks were successful
CI / CI (push) Successful in 23s
Bumps [discord.js](https://github.com/discordjs/discord.js/tree/HEAD/packages/discord.js) from 14.20.0 to 14.21.0.
- [Release notes](https://github.com/discordjs/discord.js/releases)
- [Changelog](https://github.com/discordjs/discord.js/blob/14.21.0/packages/discord.js/CHANGELOG.md)
- [Commits](https://github.com/discordjs/discord.js/commits/14.21.0/packages/discord.js)

---
updated-dependencies:
- dependency-name: discord.js
  dependency-version: 14.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-26 23:25:57 +02:00
dependabot[bot]
be0091546f
Bump jest from 30.0.2 to 30.0.3 (#152)
All checks were successful
CI / CI (push) Successful in 21s
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 30.0.2 to 30.0.3.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.3/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 20:22:06 +02:00
6ce4ca6fb8
Add unique member count to botinfo
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:47:30 +02:00
a891b37a31
Update githash properly
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:46:17 +02:00
e79d2a7a7e
Add output to executeCommand return
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:40:44 +02:00
24e019e34f
Add latest git hash to botinfo command
All checks were successful
CI / CI (push) Successful in 21s
2025-06-24 03:38:24 +02:00
9c4a57a098
Assign sliced string 2025-06-24 03:35:54 +02:00
723f14405a
Rename uniqueMemberCount to uniqueMembers
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 03:29:41 +02:00
b9e0b6abd3
Use has instead of get
All checks were successful
CI / CI (push) Successful in 22s
2025-06-24 02:49:52 +02:00
28cc7d6f15
Use const 2025-06-24 02:48:41 +02:00
4f0e948a84
Remove getGuildCount test
This function has been renamed and stuff so I have to rethink how I'll
implement tests for it.
2025-06-24 02:47:01 +02:00
9611046393
Add uniqueMemberCount as a variable in setPresence
Some checks failed
CI / CI (push) Failing after 22s
2025-06-24 02:32:32 +02:00
bbaf2205ad
Add unique members to getGuildInfo 2025-06-24 02:32:10 +02:00
08b52216a9
Compare with strict equality
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 17:51:56 +02:00
SileNce5k
4d42609f89
Separate executeCommand into its own util function 2025-06-21 17:50:07 +02:00
f55b54eb49
Update package-lock.json
All checks were successful
CI / CI (push) Successful in 22s
2025-06-21 16:51:17 +02:00
SileNce5k
18c2d9dc18
Add dl command for downloading videos
All checks were successful
CI / CI (push) Successful in 1m25s
2025-06-21 16:47:36 +02:00
dependabot[bot]
600173771e
Bump discord.js from 14.19.3 to 14.20.0 (#145)
All checks were successful
CI / CI (push) Successful in 1m25s
Bumps [discord.js](https://github.com/discordjs/discord.js/tree/HEAD/packages/discord.js) from 14.19.3 to 14.20.0.
- [Release notes](https://github.com/discordjs/discord.js/releases)
- [Changelog](https://github.com/discordjs/discord.js/blob/14.20.0/packages/discord.js/CHANGELOG.md)
- [Commits](https://github.com/discordjs/discord.js/commits/14.20.0/packages/discord.js)

---
updated-dependencies:
- dependency-name: discord.js
  dependency-version: 14.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 22:04:02 +02:00
dependabot[bot]
6c514b7396
Bump @types/jest from 29.5.14 to 30.0.0 (#146)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.14 to 30.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-version: 30.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 22:03:35 +02:00
dependabot[bot]
ee21f4d75f
Bump jest from 29.7.0 to 30.0.0 (#144)
All checks were successful
CI / CI (push) Successful in 1m33s
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 29.7.0 to 30.0.0.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.0/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-14 21:20:10 +02:00
dependabot[bot]
561925a18e
Bump tar-fs from 2.1.2 to 2.1.3 (#143)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.2 to 2.1.3.
- [Commits](https://github.com/mafintosh/tar-fs/commits)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-14 21:19:35 +02:00
47dba48c76
Setpresence: Add a force subcommand to update presence immediately
All checks were successful
CI / CI (push) Successful in 1m23s
2025-06-09 22:03:37 +02:00
e92210e856
Save globalPrefix in the settings map
All checks were successful
CI / CI (push) Successful in 1m23s
2025-06-09 21:50:27 +02:00
ba99719c20
Update setPresence custom variable logic
All checks were successful
CI / CI (push) Successful in 1m25s
2025-06-09 20:38:34 +02:00
69c7fd8d53
Sort guilds by amount of members
All checks were successful
CI / CI (push) Successful in 1m22s
2025-06-01 13:24:18 +02:00
8b2314ef1c
Use discord time format
All checks were successful
CI / CI (push) Successful in 1m23s
2025-06-01 13:20:31 +02:00
e5f4295281
Add info on when presence updates
All checks were successful
CI / CI (push) Successful in 1m23s
2025-06-01 13:10:43 +02:00
15749b1a9f
Don't check for variables when updating presence 2025-06-01 13:08:06 +02:00
cf41d58b47
Only save the presence in the setPresence command
All checks were successful
CI / CI (push) Successful in 1m24s
2025-06-01 12:36:41 +02:00
ddb7cb128f
Require outside the function 2025-06-01 12:35:24 +02:00
e06c6640b7
Use setInterval on updatePresence instead of setTimeout
All checks were successful
CI / CI (push) Successful in 1m24s
2025-06-01 11:18:24 +02:00
ac03b9d136
Add totalMember variable to presence
All checks were successful
CI / CI (push) Successful in 1m24s
2025-05-18 14:17:23 +02:00
15fb1d544f
Add colon to source info
All checks were successful
CI / CI (push) Successful in 1m24s
2025-05-18 13:52:43 +02:00
f14eff3f9c
Fix check if valid first arg on timer command
Some checks failed
CI / CI (push) Has been cancelled
2025-05-17 11:25:30 +02:00
ed77c2aaf1
Allow dates to be used in timer command
All checks were successful
CI / CI (push) Successful in 1m24s
And some general cleanup of a couple of functions.
2025-05-17 11:15:05 +02:00
c74f323c1b
Log command executed before executing
All checks were successful
CI / CI (push) Successful in 1m22s
Sometimes I use shift() on the args to handle them which causes the
logging to not log everything 100% accurately
2025-05-13 00:19:35 +02:00
be8d9660ba
Fix permissions checking not working if false
All checks were successful
CI / CI (push) Successful in 1m22s
2025-05-12 20:11:34 +02:00
9ac900bc4d
fm: Add top artists and albums to moreHelp
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-12 18:00:23 +02:00
6592f25882
Shift arguments so time option works
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-12 17:56:38 +02:00
4f57499cca
Add top artists to fm command
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-12 17:52:03 +02:00
9cb5ea22cb
Add top albums to fm command
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-12 03:08:25 +02:00
33 changed files with 2806 additions and 1437 deletions

View file

@ -16,6 +16,6 @@ jobs:
with: with:
node-version: 'latest' node-version: 'latest'
- name: install dependencies - name: install dependencies
run: npm install run: npm ci
- name: run tests - name: run tests
run: npm test run: npm test

View file

@ -1,3 +1,4 @@
const savePresence = require("../../util/savePresence");
const setPresence = require("../../util/setPresence"); const setPresence = require("../../util/setPresence");
module.exports = { module.exports = {
@ -10,12 +11,20 @@ module.exports = {
,"Updates once a minute if custom variables are used." ,"Updates once a minute if custom variables are used."
,"" ,""
,"Custom Variables:" ,"Custom Variables:"
,"${guilds},${prefix},${uptime}"], ,"${guilds},${prefix},${uptime},{members}"],
admin: true, admin: true,
execute({message, client, args, globalPrefix}) { execute({message, client, args, prefix}) {
const savePresence = require("../../util/savePresence"); let forceUpdate = false;
if(args.length > 1 && args[0] === "force") {
forceUpdate = true;
args.shift();
}
if(args.length < 1){
message.channel.send(`You need at least two arguments for this command, see \`${prefix}help setpresence\``)
return;
}
let presenceType = args[0].toLocaleUpperCase(); let presenceType = args[0].toLocaleUpperCase();
let sendText = "Updated presence"; let sendText = "Presence has been set.";
switch (presenceType) { switch (presenceType) {
case "PLAY": case "PLAY":
@ -46,8 +55,11 @@ module.exports = {
const firstArg = args[0].length + 1; const firstArg = args[0].length + 1;
let temp = args.join(" "); let temp = args.join(" ");
let presenceText = temp.slice(firstArg, temp.length) let presenceText = temp.slice(firstArg, temp.length)
setPresence({presenceText: presenceText,presenceType: presenceType, client: client, globalPrefix: globalPrefix});
savePresence(presenceType, presenceText, client); savePresence(presenceType, presenceText, client);
if(forceUpdate)
setPresence({presenceText, presenceType, client})
else
sendText = `${sendText} It will update <t:${Math.floor((client.lastPresenceUpdate + 60000) / 1000)}:R>`
} }
message.channel.send(sendText); message.channel.send(sendText);

View file

@ -1,4 +1,5 @@
const calculateReloaded = require("../../util/calculateReloaded"); const calculateReloaded = require("../../util/calculateReloaded");
const executeCommand = require("../../util/executeCommand");
const reloadCommands = require("../../util/reloadCommands"); const reloadCommands = require("../../util/reloadCommands");
module.exports = { module.exports = {
@ -27,10 +28,12 @@ module.exports = {
let commitCount = stdout.split(/\r\n|\r|\n/).length - 1 let commitCount = stdout.split(/\r\n|\r|\n/).length - 1
sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}` sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}`
if(sendText.length >= 2000){ if(sendText.length >= 2000){
sendText.slice(1955) sendText = sendText.slice(1955)
sendText = `${sendText}\n... Message is too long to show everything` sendText = `${sendText}\n... Message is too long to show everything`
} }
message.channel.send(sendText) message.channel.send(sendText)
const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]);
client.githash = githash.error ? "N/A" : githash.output;
if (err) console.log(stderr) if (err) console.log(stderr)
}) })
} }

View file

@ -12,10 +12,11 @@ module.exports = {
let descriptionArr = [`Name: ${client.user.username}`, let descriptionArr = [`Name: ${client.user.username}`,
`Prefix: ${prefix}`, `Prefix: ${prefix}`,
`Total Servers: ${guildInfo.guildCount}`, `Total Servers: ${guildInfo.guildCount}`,
`Total Members: ${guildInfo.totalMembers}`, `Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`,
`Total Commands: ${client.commands.size}`, `Total Commands: ${client.commands.size}`,
`Creation Date: ${getCreationDate(client)}`, `Creation Date: ${getCreationDate(client)}`,
`Source [Click Here](https://github.com/SileNce5k/discord_bot)` `Source: [Click Here](https://github.com/SileNce5k/discord_bot)`,
`Current Version: ${client.githash}`
] ]
let description = ""; let description = "";

View file

@ -3,10 +3,10 @@ module.exports = {
description: 'Returns guild names', description: 'Returns guild names',
admin: true, admin: true,
execute({message, client}) { execute({message, client}) {
let guildNames = ""; let guildNames = client.guilds.cache
client.guilds.cache.each(guild => { .sort((a, b) => b.memberCount - a.memberCount)
guildNames = `${guildNames}${guild.name} (${guild.memberCount} members)\n` .map(guild => `${guild.name} (${guild.memberCount} members)`)
}); .join("\n");
message.channel.send(guildNames) message.channel.send(guildNames)
} }
}; };

103
commands/info/mc.js Normal file
View file

@ -0,0 +1,103 @@
// Code is taken from https://github.com/stphnduvall/mcstatus/blob/master/src/index.ts
// and converted to pure js.
const net = require('net')
const { EmbedBuilder } = require('discord.js');
module.exports = {
name: 'mc',
description: 'get minecraft server information',
hidden: true,
needsWhitelist: true,
async execute({ message, args }) {
let host = "";
let port = 25565;
if (args[0]) host = args[0];
if (host.includes(":")) {
port = host.replace(/.+(?:\:)/g, "");
host = host.match(/.+(?:\:)/g, "")[0].replace(":", "");
}
if(host === "") return message.channel.send("No host provided")
let info = await getMinecraftServerInfo(host, port);
if (info) {
const embed = new EmbedBuilder()
embed.setColor("#ee7939")
embed.setTimestamp()
embed.addFields(
{ name: "ping", value: info.ping.toString(), inline: false },
{ name: "Player Count", value: info.playercount.toString(), inline: false },
{ name: "Max Players", value: info.maxPlayers.toString(), inline: false },
{ name: "MOTD", value: info.motd, inline: false },
)
message.channel.send({ embeds: [embed] });
} else {
message.channel.send("Something went wrong\nThe minecraft server is likely not reachable from the discord bot")
}
}
};
async function getMinecraftServerInfo(host, port = 25565) {
let serverInfo = {
ping: undefined,
maxPlayers: undefined,
version: undefined,
playercount: undefined,
motd: undefined
}
let startTime = new Date();
let data;
let ping;
serverInfo = await new Promise((resolve) => {
const client = net.connect({ host, port }, () => {
ping = Math.round(new Date().getMilliseconds() - startTime.getMilliseconds());
let buff = Buffer.from([0xFE, 0x01]);
client.write(buff);
})
let error = false;
client.on('data', (d) => {
data = d.toString()
client.destroy();
})
client.once('error', (error) => {
console.error(error)
error = true;
})
client.once('connectionAttemptFailed', (ip) => {
console.error("in attempt failed")
error = true;
})
client.once('connectionAttemptTimeout', (ip) => {
console.error("in attempt timeout")
error = true;
})
client.on('close', () => {
if (!error) {
let _serverInfo = data?.split('\x00\x00\x00');
if (!_serverInfo) {
console.log("Something went wrong.")
resolve(serverInfo)
}
serverInfo.version = _serverInfo[2].replace(/\u0000/g, '')
serverInfo.motd = _serverInfo[3].replace(/\u0000/g, '')
serverInfo.playercount = Number(_serverInfo[4].replace(/\u0000/g, ''))
serverInfo.maxPlayers = Number(_serverInfo[5].replace(/\u0000/g, ''))
serverInfo.ping = Number(ping)
resolve(serverInfo)
} else {
resolve(0)
}
})
})
return serverInfo
}

View file

@ -16,9 +16,9 @@ module.exports = {
let user = message.guild.members.cache.get(info); let user = message.guild.members.cache.get(info);
let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096}); let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096});
let globalPfp = user.user.avatarURL({format: 'png', dynamic: true, size: 4096}); let globalPfp = user.user.avatarURL({format: 'png', dynamic: true, size: 4096});
let sendText = `Global pfp:\n${globalPfp}`; let sendText = `${globalPfp}`;
if(guildPfp != null){ if(guildPfp != null){
sendText = `${sendText}\nGuild pfp:\n${guildPfp}`; sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`;
} }
message.channel.send(sendText) message.channel.send(sendText)

View file

@ -15,6 +15,7 @@ module.exports = {
{name: "Server Name", value: message.guild.name, inline: false}, {name: "Server Name", value: message.guild.name, inline: false},
{name: "Created", value: convertDateToISOString(message.guild.createdAt), inline: false}, {name: "Created", value: convertDateToISOString(message.guild.createdAt), inline: false},
{name: "Members", value: message.guild.memberCount.toString(), inline: false}, {name: "Members", value: message.guild.memberCount.toString(), inline: false},
{name: "Channels", value: message.guild.channels.channelCountWithoutThreads.toString(), inline: false},
]) ])

71
commands/misc/dl.js Normal file
View file

@ -0,0 +1,71 @@
const path = require('path');
const fs = require('fs')
const executeCommand = require('../../util/executeCommand');
module.exports = {
name: 'dl',
description: 'Download a video',
moreHelp: [
"Usage: <prefix>dl <url>"
],
async execute({message, args}) {
const downloadsDir = path.resolve(process.cwd(), 'data', 'downloads', Date.now().toString());
const cookieFilepath = path.resolve(process.cwd(), 'data', 'cookies.txt')
if(!fs.existsSync(cookieFilepath)) {
message.channel.send("Some dependencies are needed for the command to work properly. Please let the bot's owner know.")
return;
}
fs.mkdirSync(downloadsDir, {recursive: true});
let url;
if(args.length > 0){
if(args[0].charAt(0) === '<' && args[0].charAt(args[0].length - 1) === '>'){
args[0] = args[0].slice(1, args[0].length - 1)
}
try {
url = new URL(args[0]);
url = url.href;
} catch (error) {
this.cleanUp(downloadsDir);
message.channel.send("Could not parse the provided argument as a URL.");
return;
}
} else {
this.cleanUp(downloadsDir);
return message.channel.send("You have to provide a URL in an argument.")
}
const originalMessage = await message.channel.send("Downloading video...")
if(executeCommand("yt-dlp", [url, "-P", downloadsDir, "--cookies", cookieFilepath]).error){
originalMessage.edit("An error occurred when downloading the video.");
this.cleanUp(downloadsDir);
return;
}
let files = fs.readdirSync(downloadsDir);
if(files.length < 1) {
this.cleanUp(downloadsDir);
originalMessage.edit("Something went wrong when downloading the video.")
return;
}
const filename = files[0];
await originalMessage.edit({
content: null,
files: [{
attachment: path.resolve(downloadsDir, filename)
}]})
this.cleanUp(downloadsDir);
},
cleanUp(downloadsDir){
fs.rmSync(downloadsDir, {force: true, recursive: true});
},
}

View file

@ -17,6 +17,8 @@ module.exports = {
"They behave the same (for example: `<prefix>fmtt` and `<prefix>fm tt`)", "They behave the same (for example: `<prefix>fmtt` and `<prefix>fm tt`)",
"Set username: `<prefix>fmset <lastfm_username>`", "Set username: `<prefix>fmset <lastfm_username>`",
"Get current scrobble: `<prefix>fm`", "Get current scrobble: `<prefix>fm`",
"Get top artists `<prefix>fmas`",
"Get top albums: `<prefix>fmabl`",
"Get top tracks: `<prefix>fmtt`", "Get top tracks: `<prefix>fmtt`",
"Get album cover for current scrobble: `<prefix>fmcover`", "Get album cover for current scrobble: `<prefix>fmcover`",
"Get a roast from an LLM using your top artists and albums: `<prefix>fmroast`" "Get a roast from an LLM using your top artists and albums: `<prefix>fmroast`"
@ -35,6 +37,19 @@ module.exports = {
args.shift(); args.shift();
sendText = await getTopTracks(message.author.id, args, message.guild); sendText = await getTopTracks(message.author.id, args, message.guild);
break; break;
case "topalbums":
case "topalbum":
case "abl":
args.shift();
sendText = await getTopAlbums(message.author.id, args, message.guild)
break;
case "topartists":
case "topartist":
case "ta":
case "as":
args.shift();
sendText = await getTopArtists(message.author.id, args, message.guild);
break;
case "cover": case "cover":
sendText = await getCurrentCover(message.author.id, message.guild); sendText = await getCurrentCover(message.author.id, message.guild);
break; break;
@ -72,11 +87,13 @@ module.exports = {
if(sendText.embed != null){ if(sendText.embed != null){
let parse = parseMention(message.author.id, message.guild) let parse = parseMention(message.author.id, message.guild)
let user = message.guild.members.cache.get(parse); let user = message.guild.members.cache.get(parse);
let roleColor = 15788778; if(!sendText.embed.data.color){
if (user.roles.color) { let roleColor = 15788778;
roleColor = user.roles.color.color; if (user.roles.color) {
roleColor = user.roles.color.color;
}
sendText.embed.setColor(roleColor);
} }
sendText.embed.setColor(roleColor);
message.channel.send({embeds :[sendText.embed]}) message.channel.send({embeds :[sendText.embed]})
}else{ }else{
message.channel.send(sendText.text.replaceAll("<prefix>", prefix)); message.channel.send(sendText.text.replaceAll("<prefix>", prefix));

28
commands/misc/ig.js Normal file
View file

@ -0,0 +1,28 @@
module.exports = {
name: 'ig',
description: 'Replaces Instagram links with kkinstagram and deletes the original message',
execute({message, args}) {
const noUrlErr = "You need to provide an Instagram link to use this command."
if(args.length < 1) {
message.channel.send(noUrlErr);
return;
}
let replacedLink = "";
const regex = /(?<=\/)(instagram)\.com/g
if(args[0].startsWith("https://") || args[0].startsWith("http://")){
replacedLink = args[0].replace(regex, "kkinstagram.com");
}
if(replacedLink.length === 0 || args[0] === replacedLink){
message.channel.send(noUrlErr);
return;
}
message.channel.send(replacedLink);
try{
message.delete()
}catch(err){
console.error(`${this.name}: An error occurred while trying to delete the original message.`)
console.error(err);
}
}
};

View file

@ -1,10 +1,11 @@
const setServerPrefix = require("../../util/setServerPrefix"); const setServerPrefix = require("../../util/setServerPrefix");
const { PermissionsBitField } = require('discord.js');
module.exports = { module.exports = {
name: 'prefix', name: 'prefix',
description: 'Change the prefix of the bot in this server.', description: 'Change the prefix of the bot in this server.',
execute({ message, client, args, prefix }) { execute({ message, client, args, prefix }) {
if (!message.member.permissions.has('MANAGE_GUILD')) { if (!message.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
message.channel.send("You do not have sufficient permissions(MANAGE_GUILD) to change the prefix of this server.") message.channel.send("You do not have sufficient permissions(MANAGE_GUILD) to change the prefix of this server.")
return; return;
} }

View file

@ -1,6 +1,7 @@
module.exports = { module.exports = {
name: 'say', name: 'say',
description: 'Repeats arguments', description: 'Repeats arguments',
admin: true,
execute({message, args}) { execute({message, args}) {
if(args.length == 0) if(args.length == 0)

View file

@ -3,7 +3,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const { writeFile } = require('node:fs/promises') const { writeFile } = require('node:fs/promises')
const { Readable } = require('node:stream') const { Readable } = require('node:stream')
const executeCommand = require('../../util/executeCommand');
module.exports = { module.exports = {
name: 'tdoss', name: 'tdoss',
@ -60,8 +60,8 @@ module.exports = {
} }
const command = `magick ${tdossTemplate} \\( ${directory}/input.png -resize 800x800^ -gravity center -extent 1000x1000 \\) -compose dst-over -composite ${directory}/tdoss_result.png`; const commandArgs = [tdossTemplate, "(", `${directory}/input.png`, "-resize", "800x800^", "-gravity", "center", "-extent", "1000x1000", ")", "-compose", "dst-over", "-composite", `${directory}/tdoss_result.png`]
if (this.executeCommand(command).error === true) { if (executeCommand("magick", commandArgs).error === true) {
message.channel.send("Something went wrong during image manipulation.\nTry again and if it keeps happening, contact the owner of the bot.") message.channel.send("Something went wrong during image manipulation.\nTry again and if it keeps happening, contact the owner of the bot.")
fs.rmSync(`${directory}`, {recursive: true}) fs.rmSync(`${directory}`, {recursive: true})
return return
@ -75,20 +75,6 @@ module.exports = {
await message.channel.send({files: [final_image]}) await message.channel.send({files: [final_image]})
fs.rmSync(`${directory}`, {recursive: true}) fs.rmSync(`${directory}`, {recursive: true})
}, },
executeCommand(command) {
console.log("Executing:", command)
try {
const output = execSync(command, { encoding: 'utf-8' })
if (output.length != 0)
console.log(output)
} catch (error) {
console.error(`Error executing ${command.split(" ")[0]} command:`, error);
return { error: true };
}
return { error: false };
},
// https://stackoverflow.com/a/77210219 // https://stackoverflow.com/a/77210219
async downloadImage(url, path) { async downloadImage(url, path) {
let res; let res;

View file

@ -4,11 +4,11 @@ const parseTime = require('../../util/timer/parseTime');
const showTimer = require('../../util/timer/showTimer'); const showTimer = require('../../util/timer/showTimer');
module.exports = { module.exports = {
name: "timer", name: "timer",
description: "Set a timer for a time in minutes.", description: "Set a timer for a date or time duration.",
moreHelp: ["Usage:" moreHelp: ["Usage:"
,"`<prefix>timer [add|create] <time_in_minutes> <message_to_send>`" ,"`<prefix>timer [add|create] <time_in_minutes> <message_to_send>`"
,"`<prefix>timer <time>(d|h|m|s|t) <message_to_send>`" ,"`<prefix>timer <time>(d|h|m|s|t) <message_to_send>`"
,"`<prefix>timer <time_in_minutes> <message_to_send>`" ,"`<prefix>timer <future_date> <message_to_send>`"
,"`<prefix>timer edit <timer_id> <new_time_in_minutes> <new_message_to_send>` (not implemented)" ,"`<prefix>timer edit <timer_id> <new_time_in_minutes> <new_message_to_send>` (not implemented)"
,"`<prefix>timer [delete|remove] <timer_id>`" ,"`<prefix>timer [delete|remove] <timer_id>`"
,"`<prefix>timer show <timer_id>`" ,"`<prefix>timer show <timer_id>`"
@ -18,7 +18,8 @@ module.exports = {
switch (args[0]) { switch (args[0]) {
case "add": case "add":
case "create": case "create":
sendText = await createTimer(message, args, false); args.shift()
sendText = await createTimer(message, args);
break; break;
case "edit": case "edit":
sendText = "not implemented yet" sendText = "not implemented yet"
@ -37,8 +38,8 @@ module.exports = {
sendText = "Please specify a time, and a message to send after the timer has finished"; sendText = "Please specify a time, and a message to send after the timer has finished";
break; break;
} }
if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000)))) if(!isNaN(parseTime(args[0], Math.floor(new Date() / 1000))) || !isNaN(Date.parse(args[0])))
sendText = await createTimer(message, args, true); sendText = await createTimer(message, args);
break; break;
} }
message.channel.send(sendText); message.channel.send(sendText);

28
commands/misc/x.js Normal file
View file

@ -0,0 +1,28 @@
module.exports = {
name: 'x',
description: 'Replaces X/Twitter links with fxtwitter and deletes the original message',
execute({message, args}) {
const noUrlErr = "You need to provide an X or twitter link to use this command."
if(args.length < 1) {
message.channel.send(noUrlErr);
return;
}
let replacedLink = "";
const regex = /(?<=\/)(x|twitter)\.com/g
if(args[0].startsWith("https://") || args[0].startsWith("http://")){
replacedLink = args[0].replace(regex, "fxtwitter.com");
}
if(replacedLink.length === 0 || args[0] === replacedLink){
message.channel.send(noUrlErr);
return;
}
message.channel.send(replacedLink);
try{
message.delete()
}catch(err){
console.error(`${this.name}: An error occurred while trying to delete the original message.`)
console.error(err);
}
}
};

3360
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,8 @@
"main": "server.js", "main": "server.js",
"dependencies": { "dependencies": {
"@zuzak/owo": "^1.14.1", "@zuzak/owo": "^1.14.1",
"discord.js": "^14.19.3", "discord.js": "^14.21.0",
"dotenv": "^16.5.0", "dotenv": "^17.2.1",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"sqlite3": "^5.1.6" "sqlite3": "^5.1.6"
}, },
@ -20,7 +20,7 @@
"author": "SileNce5k", "author": "SileNce5k",
"license": "UNLICENSE", "license": "UNLICENSE",
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.14", "@types/jest": "^30.0.0",
"jest": "^29.7.0" "jest": "^30.0.5"
} }
} }

View file

@ -1,6 +1,7 @@
const fs = require('fs'); const fs = require('fs');
const createInitialConfig = require("./util/createInitialConfig") const createInitialConfig = require("./util/createInitialConfig")
const convertJSONToSQL = require('./util/timer/convertJSONToSQL'); const convertJSONToSQL = require('./util/timer/convertJSONToSQL');
const executeCommand = require('./util/executeCommand.js');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
if(!fs.existsSync("./data/config.json")) { if(!fs.existsSync("./data/config.json")) {
createInitialConfig(); createInitialConfig();
@ -48,11 +49,16 @@ client.whitelist = {
guild: new Collection(), guild: new Collection(),
user: new Collection() user: new Collection()
} }
process.env.TZ = "UTC";
createAndLoadWhitelistTable(client.whitelist); createAndLoadWhitelistTable(client.whitelist);
client.settings.set("presenceType", presenceType); client.settings.set("presenceType", presenceType);
client.settings.set("presenceText", presenceText); client.settings.set("presenceText", presenceText);
client.settings.set("globalPrefix", globalPrefix);
const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]);
client.githash = githash.error ? "N/A" : githash.output;
const reloadCommands = require("./util/reloadCommands.js"); const reloadCommands = require("./util/reloadCommands.js");
const onMessage = require('./server/message'); const onMessage = require('./server/message');
@ -73,7 +79,7 @@ client.once('disconnect', () => {
}); });
client.on('messageCreate', async message => { client.on('messageCreate', async message => {
onMessage(client, owners, message, globalPrefix); onMessage(client, owners, message);
}); });

View file

@ -1,6 +1,6 @@
const isWhitelisted = require('../util/isWhitelisted') const isWhitelisted = require('../util/isWhitelisted')
module.exports = function(client, owners, message, globalPrefix){ module.exports = function(client, owners, message){
let prefix = globalPrefix; let prefix = client.settings.get("globalPrefix");
let serverPrefix = client.serverPrefixes.get(message.guild.id); let serverPrefix = client.serverPrefixes.get(message.guild.id);
if (serverPrefix) { if (serverPrefix) {
prefix = serverPrefix; prefix = serverPrefix;
@ -27,8 +27,8 @@ module.exports = function(client, owners, message, globalPrefix){
} }
if (command.admin && owners.indexOf(message.author.id.toString()) == -1) return; if (command.admin && owners.indexOf(message.author.id.toString()) == -1) return;
try { try {
command.execute({ message: message, args: args, client: client, prefix: prefix, owners: owners, globalPrefix: globalPrefix})
console.log(`${message.author.username}(id: ${message.author.id}) executed ${command.name} with '${args}' as arguments`) console.log(`${message.author.username}(id: ${message.author.id}) executed ${command.name} with '${args}' as arguments`)
command.execute({ message: message, args: args, client: client, prefix: prefix, owners: owners})
} catch (error) { } catch (error) {
let divider = "------------------------" let divider = "------------------------"
console.log(divider) console.log(divider)

View file

@ -3,7 +3,15 @@ const checkTimer = require('../util/timer/checkTimer');
const updatePresence = require('../util/updatePresence'); const updatePresence = require('../util/updatePresence');
module.exports = function(client, enableLoginMessage, loginChannel, loginMessage) { module.exports = function(client, enableLoginMessage, loginChannel, loginMessage) {
updatePresence(client) updatePresence(client)
client.lastPresenceUpdate = Date.now()
setInterval(() => {
updatePresence(client)
client.lastPresenceUpdate = Date.now()
}, 60 * 1000);
console.log('Ready!'); console.log('Ready!');
if (enableLoginMessage === true) if (enableLoginMessage === true)
try{ try{

View file

@ -1,17 +0,0 @@
const getGuildInfo = require('../util/getGuildInfo');
test("Testing getGuildCount", () => {
for(let i = 1; i < 200000; i = i+i*30 ){
let client = {guilds: {cache: new Map()}}
client.guilds.cache.each = client.guilds.cache.forEach;
for(let j = 0; j < i; j++){
client.guilds.cache.set(`num: ${j}`, j);
}
expect(getGuildInfo(client).guildCount).toBe(i);
}
})

15
util/executeCommand.js Normal file
View file

@ -0,0 +1,15 @@
const { execFileSync } = require('child_process');
module.exports = function(command, commandArgs, verbose=false) {
if (typeof command !== 'string' || !Array.isArray(commandArgs)) return { error: true };
console.log("Executing:", command, commandArgs.join(" "));
let output;
try {
output = execFileSync(command, commandArgs, {encoding: 'utf8'})
if (output.length !== 0 && verbose)
console.log(output)
} catch (error) {
console.error(`Error executing ${command} command:`, error);
return { error: true };
}
return { error: false, output};
}

View file

@ -1,9 +1,16 @@
module.exports = function(client){ module.exports = function(client){
let guildCount = 0; let guildCount = 0;
let totalMembers = 0; let totalMembers = 0;
const uniqueMembers = new Map();
client.guilds.cache.each(guild => { client.guilds.cache.each(guild => {
guildCount++ guildCount++
totalMembers += guild.memberCount; totalMembers += guild.memberCount;
guild.members.cache.each(member => {
if(!uniqueMembers.has(member.id)){
uniqueMembers.set(member.id, true);
}
})
}); });
return {guildCount: guildCount, totalMembers: totalMembers}; const uniqueMemberCount = uniqueMembers.size;
return {guildCount, totalMembers, uniqueMemberCount};
} }

View file

@ -2,6 +2,11 @@ const getNickname = require("../getNickname");
const parseMention = require("../parseMention"); const parseMention = require("../parseMention");
const getFmUsername = require("./getFmUsername"); const getFmUsername = require("./getFmUsername");
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const path = require('path');
const fs = require('fs');
const { writeFile } = require('node:fs/promises')
const executeCommand = require('../executeCommand');
const { Readable } = require('node:stream')
require("dotenv").config(); require("dotenv").config();
module.exports = async function (userID, guild) { module.exports = async function (userID, guild) {
@ -58,21 +63,56 @@ module.exports = async function (userID, guild) {
sendText.text = tracks.errorMsg; sendText.text = tracks.errorMsg;
return sendText; return sendText;
} }
let coverDir = path.resolve(process.cwd(), 'data', 'covers');
let color = "#C27D0E"
const directory = path.resolve(coverDir, Math.floor(new Date).toString())
fs.mkdirSync(directory, {recursive: true})
const coverFile = path.resolve(directory, "cover")
let downloadResult = await downloadImage(tracks[0].cover, coverFile);
if(downloadResult.value === ERROR_CODES.SUCCESS){
const commandArgs = [`${coverFile}.${downloadResult.ext}`, "-resize", "1x1", "txt:-"]
let res = executeCommand("magick", commandArgs);
if(!res.error) color = res.output.split("\n")[1].split(" ")[3].slice(0,7);
}
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })}) .setAuthor({name: `Now playing - ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
.setThumbnail(tracks[0].cover) .setThumbnail(tracks[0].cover)
.setColor(15780145) .setColor(color)
.addFields({ .addFields({
name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}` name: `${isCurrentScrobble}:`, value: `${tracks[0].song}\n **${tracks[0].artist} • ** ${tracks[0].album}`
},) },)
if (isCurrentScrobble === "Current") { if (isCurrentScrobble === "Current") {
embed.addFields({ embed.addFields({
name: "Previous:", value: `${tracks[1].song}\n **${tracks[1].artist} • ** ${tracks[1].album}` name: "Previous:", value: `${tracks[1].song}\n **${tracks[1].artist} • ** ${tracks[1].album}`
},) },)
} }
sendText.embed = embed; sendText.embed = embed;
fs.rmSync(`${directory}`, {recursive: true})
} else { } else {
sendText.text = "You haven't set your last.fm username yet. Use `<prefix>fm set <lastfm_username>` to set it."; sendText.text = "You haven't set your last.fm username yet. Use `<prefix>fm set <lastfm_username>` to set it.";
} }
return sendText; return sendText;
} }
const ERROR_CODES = {
SUCCESS: 0,
HTTP_ERROR: 1,
NOT_IMAGE: 2,
FETCH_ERROR: 3
}
async function downloadImage(url, downloadPath) {
let res;
try {
res = await fetch(url);
} catch (error) {
return {value: ERROR_CODES.FETCH_ERROR, errorMessage: error.cause?.message || error.message};
}
if(!res.ok) return {value: ERROR_CODES.HTTP_ERROR, errorMessage: res.status.toString()};
const contentType = res.headers.get('content-type');
if(!contentType || !contentType.startsWith("image")) return {value: ERROR_CODES.NOT_IMAGE, errorMessage: contentType || "No content-type header"};
const fileExt = contentType.split("/")[1]
const stream = Readable.fromWeb(res.body)
await writeFile(`${downloadPath}.${fileExt}`, stream);
return {value: ERROR_CODES.SUCCESS, errorMessage: "", ext: fileExt};
}

View file

@ -77,7 +77,8 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopalbums&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
for(let i = 0; i < 10; i++){ const maxIterations = data.topalbums.album.length >= 10 ? 10 : data.topalbums.album.length;
for(let i = 0; i < maxIterations; i++){
let album = {} let album = {}
let currentAlbum = data.topalbums.album[i]; let currentAlbum = data.topalbums.album[i];
album.artist = currentAlbum.artist.name; album.artist = currentAlbum.artist.name;
@ -93,27 +94,24 @@ module.exports = async function (userID, option, guild, compatibility=false) {
}); });
}); });
} }
// const embed = new EmbedBuilder() const embed = new EmbedBuilder()
// .setAuthor({name: `Top ${duration} albums for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })}) .setAuthor({name: `Top ${duration} albums for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
// .setThumbnail(albums[0].cover) .setColor(15780145)
// .setColor(15780145) let albumInfo = "";
// let tracksInfo = ""; for(let i = 0; i < albums.length; i++){
// for(let i = 0; i < albums.length; i++){ let pluralCharacter = albums[i].playcount > 1 ? 's' : '';
// let pluralCharacter = albums[i].playcount > 1 ? 's' : ''; let album = `${i}. **${albums[i].artist}** - ${albums[i].name} - *${albums[i].playcount} play${pluralCharacter}*`;
// let track = `${i}. **${albums[i].artist}** - ${albums[i].song} - *${albums[i].playcount} play${pluralCharacter}*`; if(i < albums.length - 1){
// if(i < albums.length - 1){ albumInfo += `${album}\n`;
// tracksInfo += `${track}\n`; }else{
// }else{ albumInfo += `${album}`;
// tracksInfo += `${track}`; }
// } }
// } embed.setDescription(albumInfo);
// embed.addFields({ sendText.embed = embed;
// name: ` `, value: `${tracksInfo}` if(compatibility)
// },)
// sendText.embed = embed;
// if(compatibility)
return albums; return albums;
// else else
// return sendText; return sendText;
} }

View file

@ -77,7 +77,8 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${lastfmUsername}&period=${option[0]}&api_key=${process.env.LAST_FM_API_KEY}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
for(let i = 0; i < 10; i++){ const maxIterations = data.topartists.artist.length >= 10 ? 10 : data.topartists.artist.length;
for(let i = 0; i < maxIterations; i++){
let artist = {} let artist = {}
let currentArtist = data.topartists.artist[i]; let currentArtist = data.topartists.artist[i];
artist.name = currentArtist.name; artist.name = currentArtist.name;
@ -92,27 +93,24 @@ module.exports = async function (userID, option, guild, compatibility=false) {
}); });
}); });
} }
// const embed = new EmbedBuilder() const embed = new EmbedBuilder()
// .setAuthor({name: `Top ${duration} tracks for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })}) .setAuthor({name: `Top ${duration} artists for ${nickname}`, iconURL: user.user.avatarURL({ dynamic: true, size: 4096 })})
// .setThumbnail(tracks[0].cover) .setColor(15780145)
// .setColor(15780145) let artistsInfo = "";
// let tracksInfo = ""; for(let i = 0; i < artists.length; i++){
// for(let i = 0; i < tracks.length; i++){ let pluralCharacter = artists[i].playcount > 1 ? 's' : '';
// let pluralCharacter = tracks[i].playcount > 1 ? 's' : ''; let track = `${i}. **${artists[i].name}** - *${artists[i].playcount} play${pluralCharacter}*`;
// let track = `${i}. **${tracks[i].artist}** - ${tracks[i].song} - *${tracks[i].playcount} play${pluralCharacter}*`; if(i < artists.length - 1){
// if(i < tracks.length - 1){ artistsInfo += `${track}\n`;
// tracksInfo += `${track}\n`; }else{
// }else{ artistsInfo += `${track}`;
// tracksInfo += `${track}`; }
// } }
// } embed.setDescription(artistsInfo);
// embed.addFields({ sendText.embed = embed;
// name: ` `, value: `${tracksInfo}` if(compatibility)
// },)
// sendText.embed = embed;
// if(compatibility)
return artists; return artists;
// else else
// return sendText; return sendText;
} }

View file

@ -78,7 +78,8 @@ module.exports = async function (userID, option, guild, compatibility=false) {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${lastfmUsername}&period=${option[0]}&api_key=${apiKey}&format=json`) fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${lastfmUsername}&period=${option[0]}&api_key=${apiKey}&format=json`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
for(let i = 0; i < 10; i++){ const maxIterations = data.toptracks.track.length >= 10 ? 10 : data.toptracks.track.length;
for(let i = 0; i < maxIterations; i++){
let track = {} let track = {}
let currentTrack = data.toptracks.track[i]; let currentTrack = data.toptracks.track[i];
track.artist = currentTrack.artist.name; track.artist = currentTrack.artist.name;

View file

@ -2,8 +2,8 @@ const getGuildInfo = require("./getGuildInfo")
const parseMS = require('./parseMS'); const parseMS = require('./parseMS');
const convertDateToISOString = require('./convertDateToISOString') const convertDateToISOString = require('./convertDateToISOString')
module.exports = function ({presenceText, presenceType, client}) { module.exports = function ({presenceText, presenceType, client}) {
const {globalPrefix} = require ('../data/config.json') const globalPrefix = client.settings.get("globalPrefix")
let guildCount = getGuildInfo(client).guildCount const guildInfo = getGuildInfo(client);
let uptime = parseMS(client.uptime); let uptime = parseMS(client.uptime);
let uptimeFormat = ""; let uptimeFormat = "";
let uptimeSingularOrPlural; let uptimeSingularOrPlural;
@ -18,10 +18,23 @@ module.exports = function ({presenceText, presenceType, client}) {
uptimeFormat = `less than a minute` uptimeFormat = `less than a minute`
} }
let regex = [/\${guilds}/g,/\${prefix}/g,/\${uptime}/g]; let presenceVariables = {
let replaceValue = [guildCount, globalPrefix, uptimeFormat]; guilds: guildInfo.guildCount,
for(let i = 0; i < regex.length; i++){ prefix: globalPrefix,
presenceText = presenceText.replace(regex[i], replaceValue[i]); uptime: uptimeFormat,
members: guildInfo.totalMembers,
uniqueMembers: guildInfo.uniqueMemberCount
}
const regex = /(?<=\${)(.*?)(?=})/g;
const matches = presenceText.match(regex);
if(matches){
matches.forEach(match => {
if(presenceVariables[match]){
presenceText = presenceText.replace(`\${${match}}`, presenceVariables[match]);
}
});
} }
try { try {

View file

@ -1,17 +1,26 @@
const fs = require('fs');
const parseTime = require('./parseTime'); const parseTime = require('./parseTime');
const timeUntil = require('./timeUntil');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
module.exports = async function (message, args, compatibility) { module.exports = async function (message, args) {
const databasePath = 'data/database.db' const databasePath = 'data/database.db'
if (args.length < 2) if (args.length < 2)
return message.channel.send("Please specify a time, and a message to send after the timer has finished"); return "Please specify a time, and a message to send after the timer has finished";
let currentUnixTime = Math.floor(new Date() / 1000); let currentUnixTime = Math.floor(new Date() / 1000);
let timeInSeconds = compatibility ? parseTime(args[0], currentUnixTime) : parseTime(args[1], currentUnixTime);
let timeInSeconds;
if(!isNaN(Date.parse(args[0])) && isNaN(parseTime(args[0], currentUnixTime))){
timeInSeconds = timeUntil(args[0]).totalInSeconds;
if(timeInSeconds < 0){
return "The date must not be in the past."
}
}else {
timeInSeconds = parseTime(args[0], currentUnixTime);
}
if (isNaN(timeInSeconds)) { if (isNaN(timeInSeconds)) {
return message.channel.send("Please specify a time, and a message to send after the timer has finished") return "Please specify a time, and a message to send after the timer has finished"
} }
let customMessage = compatibility ? args.slice(1).join(" ") : args.slice(2).join(" "); const customMessage = args.slice(1).join(" ")
let reminderTime = currentUnixTime + timeInSeconds const reminderTime = currentUnixTime + timeInSeconds
let newTimerID; let newTimerID;
const db = new sqlite3.Database(databasePath) const db = new sqlite3.Database(databasePath)
let sendText = await new Promise((resolve, reject)=>{ let sendText = await new Promise((resolve, reject)=>{

View file

@ -1,7 +1,7 @@
module.exports = function(time, currentUnixTime){ module.exports = function(time, currentUnixTime){
let timeInSeconds = parseFloat(time.slice(0, time.length - 1)) let timeInSeconds = parseFloat(time)
let letter = time.slice(time.length - 1) const letterCount = time.length - timeInSeconds.toString().length;
if(!isNaN(letter)) return parseFloat(time) * 60; const letter = time.slice(time.length - letterCount);
switch (letter.toUpperCase()) { switch (letter.toUpperCase()) {
case "H": case "H":
timeInSeconds = timeInSeconds * 3_600; timeInSeconds = timeInSeconds * 3_600;
@ -14,7 +14,8 @@ module.exports = function(time, currentUnixTime){
case "D": case "D":
timeInSeconds = timeInSeconds * 86_400; timeInSeconds = timeInSeconds * 86_400;
break; break;
case "T": // TODO: Make it so that I can have multiple letters per case, so that "TS" would work here. case "TS": // Unix timestamp
case "T":
timeInSeconds = timeInSeconds - currentUnixTime; timeInSeconds = timeInSeconds - currentUnixTime;
break; break;
case "W": case "W":
@ -22,17 +23,6 @@ module.exports = function(time, currentUnixTime){
break; break;
default: default:
timeInSeconds = NaN; timeInSeconds = NaN;
if(time.includes(':'))
timeInSeconds = getTime(time, currentUnixTime);
} }
return timeInSeconds; return timeInSeconds;
} }
function getTime(time, currentUnixTime) {
return timeInSeconds;
}

20
util/timer/timeUntil.js Normal file
View file

@ -0,0 +1,20 @@
module.exports = function(targetTime) {
const countDownDate = new Date(targetTime).getTime();
const now = new Date().getTime();
const distance = countDownDate - now;
let days = Math.floor(distance / (1000 * 60 * 60 * 24));
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
if (seconds < 0) { // Due to how the math above works, if the input time is in the past, the time will be off by 1.
days = days + 1;
hours = hours + 1;
minutes = minutes + 1;
seconds = seconds + 1;
}
const totalInSeconds = (days * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds;
return { days, hours, minutes, seconds, totalInSeconds };
}

View file

@ -1,14 +1,8 @@
const setPresence = require('./setPresence') const setPresence = require('./setPresence')
module.exports = function (client) { module.exports = function (client) {
const updatePresence = require('./updatePresence')
let presenceText = client.settings.get("presenceText") let presenceText = client.settings.get("presenceText")
let presenceType = client.settings.get("presenceType") let presenceType = client.settings.get("presenceType")
if(presenceText.includes("${guilds}") || presenceText.includes("${prefix}") || presenceText.includes("${uptime}")) { setPresence({presenceText: presenceText, presenceType: presenceType, client: client});
setPresence({presenceText: presenceText, presenceType: presenceType, client: client});
}
setTimeout(updatePresence, 60000, client)
} }