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
aa2ab653b9
tdoss: Improve error handling of downloadImage + add the tdoss template
All checks were successful
CI / CI (push) Successful in 1m22s
Also use tdoss directory in the data/ dir so I won't have a bunch of
random directories in the data dir.
2025-05-10 20:39:51 +02:00
2f371d00ed
Add message latency to ping command
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-09 04:03:32 +02:00
28d1f6d0a2
Add total members to botinfo command
All checks were successful
CI / CI (push) Successful in 1m22s
2025-05-08 17:37:05 +02:00
7053495f40
Guilds: Add members count to each server
All checks were successful
CI / CI (push) Successful in 1m23s
2025-05-08 16:23:03 +02:00
0d10a15f5b
Remove unused variable
All checks were successful
CI / CI (push) Successful in 1m22s
2025-05-04 21:58:01 +02:00
0a7f8b9ba3
Add permissions to workflow
All checks were successful
CI / CI (push) Successful in 1m22s
Mainly to get rid of code scanning alert lol
2025-05-04 04:22:15 +02:00
dependabot[bot]
99e0f3a59d
Bump cross-spawn from 7.0.3 to 7.0.6 (#139)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-version: 7.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-04 04:20:13 +02:00
dependabot[bot]
8481f4563c
Bump @babel/helpers from 7.25.7 to 7.27.1 (#138)
Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.25.7 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-version: 7.27.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-04 04:17:19 +02:00
dependabot[bot]
d39a1a005a
Bump tar-fs from 2.1.1 to 2.1.2 (#137)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.1 to 2.1.2.
- [Commits](https://github.com/mafintosh/tar-fs/compare/v2.1.1...v2.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-04 04:13:17 +02:00
007c6db3bc
Update discord.js to 14.19.3 and dotenv to 16.5.0
All checks were successful
CI / CI (push) Successful in 1m24s
2025-05-04 04:07:05 +02:00
b2c6c705c3
Add more types of whitelisting, plus support for subcommands
All checks were successful
CI / CI (push) Successful in 1m24s
2025-05-04 04:02:03 +02:00
99d6fc2401
Add whitelist for users as well
All checks were successful
CI / CI (push) Successful in 1m25s
2025-05-03 07:27:42 +02:00
30c7fd171f
Fix crash when command does not exist
All checks were successful
CI / CI (push) Successful in 1m24s
This regression appeared when I removed custom command support
2025-05-02 16:50:20 +02:00
6b45bc6799
Switch to gemini 2.0 LLM
All checks were successful
CI / CI (push) Successful in 1m26s
2025-05-02 16:42:25 +02:00
52c7a4cbad
Remove chat command
All checks were successful
CI / CI (push) Successful in 1m23s
Discord has started banning bots that train on input. Since I use free
APIs, I cannot guarantee that the prompts won't be used for training.
2025-05-02 16:32:45 +02:00
ca646fc5ee
Fix downloadImage not returning 2 when content-type isn't image
All checks were successful
CI / CI (push) Successful in 1m24s
2025-04-30 23:17:49 +02:00
43 changed files with 3044 additions and 1655 deletions

View file

@ -2,6 +2,9 @@ name: CI
on: [push] on: [push]
permissions:
contents: read
jobs: jobs:
CI: CI:
runs-on: node runs-on: node
@ -13,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

@ -2,18 +2,20 @@ const sqlite3 = require('sqlite3').verbose();
module.exports = { module.exports = {
name: 'whitelist', name: 'whitelist',
description: 'Whitelist a command in a specific server.', description: 'Whitelist a command in a specific server, or for a user.',
admin: true, admin: true,
async execute({message, args, prefix, client}) { async execute({message, args, prefix, client}) {
if(args.length < 2){ if(args.length < 3){
message.channel.send(`You need to supply the command and channel that you want to whitelist\n\`${prefix}whitelist <command> <server_id>\``); message.channel.send(`You need to supply the command and guild that you want to whitelist\n\`${prefix}whitelist <user|guild> <command> <user_id|guild_id>\``);
return; return;
} }
let command = args[0]; let option = args[0];
let guild = args[1]; let command = args[1];
if(guild === "this") guild = message.guild.id; let guildOrUser = args[2];
// TODO: Add ability to remove server from whitelist.
const whitelistedCommands = client.whitelist.get(guild); if(option === "guild" || option === "server"){
if(guildOrUser === "this") guildOrUser = message.guild.id;
const whitelistedCommands = client.whitelist.guild.get(guildOrUser);
if(whitelistedCommands && whitelistedCommands.includes(command)){ if(whitelistedCommands && whitelistedCommands.includes(command)){
message.channel.send("Command is already whitelisted in that server.") message.channel.send("Command is already whitelisted in that server.")
return; return;
@ -23,13 +25,13 @@ module.exports = {
const db = new sqlite3.Database(databasePath) const db = new sqlite3.Database(databasePath)
let err = false; let err = false;
await new Promise((resolve, reject)=>{ await new Promise((resolve, reject)=>{
db.run(`INSERT INTO whitelist ( db.run(`INSERT INTO guild_whitelist (
serverId, guildId,
command, command,
dateAdded dateAdded
) VALUES (?, ?, ?)`, ) VALUES (?, ?, ?)`,
[ [
guild, guildOrUser,
command, command,
new Date().getTime() new Date().getTime()
], function (error) { ], function (error) {
@ -42,15 +44,64 @@ module.exports = {
} }
}); });
}) })
db.close();
if(!err){ if(!err){
if(whitelistedCommands){ if(whitelistedCommands){
whitelistedCommands.push(command); whitelistedCommands.push(command);
client.whitelist.set(guild, whitelistedCommands); client.whitelist.guild.set(guildOrUser, whitelistedCommands);
}else { }else {
client.whitelist.set(guild, command); client.whitelist.guild.set(guildOrUser, command);
} }
message.channel.send("Command has been whitelisted in that server.") message.channel.send(`"${command}" has been whitelisted in that server.`)
} else message.channel.send("Could not whitelist the server with that command. Check the logs.") } else message.channel.send("Could not whitelist the server with that command. Check the logs.")
return;
}else if (option === "user"){
const whitelistedCommands = client.whitelist.user.get(guildOrUser);
if(whitelistedCommands && whitelistedCommands.includes(command)){
message.channel.send("Command is already whitelisted for that user.")
return;
} }
const databasePath = 'data/database.db'
const db = new sqlite3.Database(databasePath)
let err = false;
await new Promise((resolve, reject)=>{
db.run(`INSERT INTO user_whitelist (
userId,
command,
dateAdded
) VALUES (?, ?, ?)`,
[
guildOrUser,
command,
new Date().getTime()
], function (error) {
if (error) {
console.error(error);
err = true;
resolve();
}else{
resolve();
}
});
})
db.close();
if(!err){
if(whitelistedCommands){
whitelistedCommands.push(command);
client.whitelist.user.set(guildOrUser, whitelistedCommands);
}else {
client.whitelist.user.set(guildOrUser, command);
}
message.channel.send(`"${command}" has been whitelisted for ${guildOrUser}.`)
} else message.channel.send("Could not whitelist the server with that command. Check the logs.")
return;
}
// TODO: Add ability to remove server from whitelist.
}
}; };

View file

@ -1,6 +1,6 @@
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const getCreationDate = require('../../util/getCreationDate'); const getCreationDate = require('../../util/getCreationDate');
const getGuildCount = require('../../util/getGuildCount'); const getGuildInfo = require('../../util/getGuildInfo');
@ -8,15 +8,28 @@ module.exports = {
name: 'botinfo', name: 'botinfo',
description: 'Shows information about the bot', description: 'Shows information about the bot',
execute({message, client, prefix}) { execute({message, client, prefix}) {
let guildCount = getGuildCount(client) let guildInfo = getGuildInfo(client)
let descriptionArr = [`Name: ${client.user.username}`,
`Prefix: ${prefix}`,
`Total Servers: ${guildInfo.guildCount}`,
`Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`,
`Total Commands: ${client.commands.size}`,
`Creation Date: ${getCreationDate(client)}`,
`Source: [Click Here](https://github.com/SileNce5k/discord_bot)`,
`Current Version: ${client.githash}`
]
let description = "";
descriptionArr.forEach(desc => {
description += `${desc}\n`;
})
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(15780145) .setColor(15780145)
.setTitle("Information about bot") .setTitle("Information about bot")
.setTimestamp() .setTimestamp()
.setAuthor({name: client.user.username, iconURL: client.user.avatarURL({ format: 'png', dynamic: true, size: 2048 })}) .setAuthor({name: client.user.username, iconURL: client.user.avatarURL({ format: 'png', dynamic: true, size: 2048 })})
.addFields({ .setDescription(description)
name: "General info", value: `Name: ${client.user.username}\nPrefix: ${prefix}\nTotal Servers: ${guildCount}\nTotal Commands: ${client.commands.size}\nCreation Date: ${getCreationDate(client)}\nSource: [Click Here](https://github.com/SileNce5k/discord_bot)`,
},)
message.channel.send({embeds :[embed]}) message.channel.send({embeds :[embed]})

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 + "\n"; .map(guild => `${guild.name} (${guild.memberCount} members)`)
}); .join("\n");
message.channel.send(guildNames) message.channel.send(guildNames)
} }
}; };

View file

@ -13,7 +13,6 @@ module.exports = {
execute({ message, args, prefix, client }) { execute({ message, args, prefix, client }) {
let commands = "" let commands = ""
let commandFiles = getSubdirFiles('commands/') let commandFiles = getSubdirFiles('commands/')
let x = false;
let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`; let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`;
let iteration = 0; let iteration = 0;
let num_in_args = false; let num_in_args = false;
@ -32,7 +31,6 @@ module.exports = {
fieldName = fieldName.replace("[[page]]", page); fieldName = fieldName.replace("[[page]]", page);
const max_commands = iteration + 10; const max_commands = iteration + 10;
if (x) return;
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(15780145) .setColor(15780145)

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},
]) ])

View file

@ -1,48 +0,0 @@
require("dotenv").config();
module.exports = {
name: 'chat',
description: 'A chat command that uses an LLM to answer your prompts (server must be whitelisted)',
needsWhitelist: true,
async execute({ message, args }) {
if(args.length === 0){
message.channel.send("You have to set your prompt in the arguments");
return;
}
const prompt = args.join(" ");
let answer = "";
const initialMessage = await message.channel.send("Generating response... This may take a moment.")
message.channel.sendTyping();
// TODO: More configuration. Have a basic setup but allow setting system prompt, max tokens and model.
await fetch(`https://openrouter.ai/api/v1/chat/completions`, {
method: `POST`,
headers: {
"Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": `application/json`
},
body: JSON.stringify({
"model": `deepseek/deepseek-chat-v3-0324:free`,
"messages": [
{
"role": `user`,
"content": prompt
}
],
"max_tokens": 250
})
}).then(response => response.json()).then(data => {
answer = data.choices[0].message.content;
}).catch(error => {
console.error(error);
});
if(answer.length > 0){
if(answer.length > 1999){
initialMessage.edit("Unfortunately the length of the message was longer than what discord allows.")
}
initialMessage.edit(answer)
}else {
initialMessage.edit("Something went wrong. The owner should check the console.")
}
}
};

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

@ -9,6 +9,7 @@ const getTopArtists = require("../../util/lastfm/getTopArtists");
const getTopAlbums = require("../../util/lastfm/getTopAlbums"); const getTopAlbums = require("../../util/lastfm/getTopAlbums");
const {EmbedBuilder} = require('discord.js'); const {EmbedBuilder} = require('discord.js');
const getNickname = require('../../util/getNickname'); const getNickname = require('../../util/getNickname');
const isWhitelisted = require("../../util/isWhitelisted");
module.exports = { module.exports = {
name: 'fm', name: 'fm',
description: 'Last fm commands. See `<prefix>help fm` for more info.', description: 'Last fm commands. See `<prefix>help fm` for more info.',
@ -16,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`"
@ -34,12 +37,31 @@ 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;
case "roast": case "roast":
const commandName = "fmroast"
if(!isWhitelisted.isWhitelisted(commandName, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)) {
message.channel.send("fmroast has not been whitelisted for you or this server.")
return;
}
let topArtists = await getTopArtists(message.author.id, ["yearly"], message.guild, true); let topArtists = await getTopArtists(message.author.id, ["yearly"], message.guild, true);
let topAlbums = await getTopAlbums(message.author.id, ["yearly"], message.guild, true); let topAlbums = await getTopAlbums(message.author.id, ["yearly"], message.guild, true);
message.channel.sendTyping();
let result = await roast(topArtists, topAlbums); let result = await roast(topArtists, topAlbums);
let parse = parseMention(message.author.id, message.guild) let parse = parseMention(message.author.id, message.guild)
@ -65,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);
if(!sendText.embed.data.color){
let roleColor = 15788778; let roleColor = 15788778;
if (user.roles.color) { if (user.roles.color) {
roleColor = user.roles.color.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,7 +1,11 @@
module.exports = { module.exports = {
name: 'ping', name: 'ping',
description: 'Just ping.', description: 'Just ping.',
execute({message, client}) { async execute({message, client}) {
message.channel.send(`Pong.\n${client.ws.ping}ms`) let apiLatency = client.ws.ping;
const time = process.hrtime.bigint();
let initialMessage = await message.channel.send(`Pong.`)
const diff = Number(process.hrtime.bigint() - time) * 1e-6;
initialMessage.edit(`Pong.\nAPI latency: ${apiLatency} ms\nMessage latency: ${diff.toFixed(0)} ms.`);
} }
}; };

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,16 +3,18 @@ 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',
description: 'Combine picture with tdoss album cover template', description: 'Combine picture with tdoss album cover template',
async execute({ message, args }) { async execute({ message, args }) {
let dataDir = path.resolve(__dirname, '..', '..', 'data'); let tdossDir = path.resolve(process.cwd(), 'data', 'tdoss');
const directory = path.resolve(dataDir, Math.floor(new Date).toString()) const tdossTemplate = path.resolve(process.cwd(), 'resources', 'tdoss_template.png');
fs.mkdirSync(directory)
const directory = path.resolve(tdossDir, Math.floor(new Date).toString())
fs.mkdirSync(directory, {recursive: true})
let url = ""; let url = "";
if(message.attachments.size > 0){ if(message.attachments.size > 0){
@ -44,15 +46,22 @@ module.exports = {
} }
// TODO: Download with correct extension. // TODO: Download with correct extension.
message.channel.sendTyping(); message.channel.sendTyping();
if(await this.downloadImage(url, path.resolve(directory, "input.png")) != 0){ let downloadResult = await this.downloadImage(url, path.resolve(directory, "input.png"));
message.channel.send("Something went wrong during the download.\nThe link might be unreachable for the bot or it's not an image.") if(downloadResult.value != this.ERROR_CODES.SUCCESS){
if(downloadResult.value === this.ERROR_CODES.FETCH_ERROR){
message.channel.send(`Failed to download the provided image, got error '${downloadResult.errorMessage}'`);
}else if (downloadResult.value === this.ERROR_CODES.HTTP_ERROR){
message.channel.send(`Failed to download the provided image, got response status '${downloadResult.errorMessage}'`);
}else if(downloadResult.value === this.ERROR_CODES.NOT_IMAGE){
message.channel.send(`The provided url was not an image.`)
}
fs.rmSync(`${directory}`, {recursive: true}) fs.rmSync(`${directory}`, {recursive: true})
return; return;
} }
const command = `magick ${dataDir}/tdoss_template.png \\( ${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
@ -66,30 +75,26 @@ 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) {
const res = await fetch(url); let res;
if(!res.ok) return 1; try {
if(!res.headers.get('content-type').startsWith("image")) 2; res = await fetch(url);
} catch (error) {
return {value: this.ERROR_CODES.FETCH_ERROR, errorMessage: error.cause?.message || error.message};
}
if(!res.ok) return {value: this.ERROR_CODES.HTTP_ERROR, errorMessage: res.status.toString()};
const contentType = res.headers.get('content-type');
if(!contentType || !contentType.startsWith("image")) return {value: this.ERROR_CODES.NOT_IMAGE, errorMessage: contentType || "No content-type header"};
const stream = Readable.fromWeb(res.body) const stream = Readable.fromWeb(res.body)
await writeFile(path, stream); await writeFile(path, stream);
return 0; return {value: this.ERROR_CODES.SUCCESS, errorMessage: ""};
},
ERROR_CODES: {
SUCCESS: 0,
HTTP_ERROR: 1,
NOT_IMAGE: 2,
FETCH_ERROR: 3
} }
} }

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);
}
}
};

3589
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.16.3", "discord.js": "^14.21.0",
"dotenv": "^16.4.5", "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"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

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();
@ -44,13 +45,20 @@ const {
client.settings = new Collection(); client.settings = new Collection();
client.commands = new Collection(); client.commands = new Collection();
client.serverPrefixes = new Collection(); client.serverPrefixes = new Collection();
client.whitelist = new Collection(); client.whitelist = {
guild: 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');
@ -71,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,34 +1,61 @@
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
module.exports = async function (clientWhitelist) { module.exports = async function (clientWhitelist) {
const db = new sqlite3.Database('data/database.db'); const db = new sqlite3.Database('data/database.db');
await createGuildWhitelist(db);
await createUserWhitelist(db);
db.close();
loadWhitelist(clientWhitelist);
}
async function createGuildWhitelist(db) {
await new Promise ((resolve, reject)=>{ await new Promise ((resolve, reject)=>{
db.run( db.run(
`CREATE TABLE IF NOT EXISTS whitelist ( `CREATE TABLE IF NOT EXISTS guild_whitelist (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
serverId TEXT, guildId TEXT,
command TEXT, command TEXT,
dateAdded INTEGER)`, dateAdded INTEGER)`,
(err) => { (err) => {
if (err) { if (err) {
console.error(`Error while creating table 'whitelist': ${err}`); db.close();
console.error(`Error while creating table 'guild_whitelist': ${err}`);
reject(err); reject(err);
} else { } else {
console.log("Table 'whitelist' created successfully."); console.log("Table 'guild_whitelist' created successfully.");
resolve(); resolve();
} }
db.close();
} }
); );
}) });
loadWhitelist(clientWhitelist); }
async function createUserWhitelist(db) {
await new Promise ((resolve, reject)=>{
db.run(
`CREATE TABLE IF NOT EXISTS user_whitelist (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
userId TEXT,
command TEXT,
dateAdded INTEGER)`,
(err) => {
if (err) {
db.close();
console.error(`Error while creating table 'user_whitelist': ${err}`);
reject(err);
} else {
console.log("Table 'user_whitelist' created successfully.");
resolve();
}
}
);
});
} }
async function loadWhitelist(clientWhitelist) { async function loadWhitelist(clientWhitelist) {
const db = new sqlite3.Database('data/database.db'); const db = new sqlite3.Database('data/database.db');
let rows = await new Promise((resolve) => { let guildRows = await new Promise((resolve) => {
db.all(`SELECT * FROM whitelist`, function (error, rows){ db.all(`SELECT * FROM guild_whitelist`, function (error, rows){
if(error){ if(error){
console.error("Failed to read whitelist table") console.error("Failed to read guild_whitelist table")
console.error(error); console.error(error);
resolve([]); resolve([]);
}else{ }else{
@ -36,13 +63,33 @@ async function loadWhitelist(clientWhitelist) {
} }
}); });
}); });
rows.forEach(row => { let userRows = await new Promise((resolve) => {
if(clientWhitelist.has(row.serverId)){ db.all(`SELECT * FROM user_whitelist`, function (error, rows){
let oldEntry = clientWhitelist.get(row.serverId); if(error){
console.error("Failed to read user_whitelist table")
console.error(error);
resolve([]);
}
})
})
guildRows.forEach(row => {
if(clientWhitelist.guild.has(row.guildId)){
let oldEntry = clientWhitelist.guild.get(row.guildId);
oldEntry.push(row.command) oldEntry.push(row.command)
clientWhitelist.set(row.serverId, oldEntry) clientWhitelist.guild.set(row.guildId, oldEntry)
}else { }else {
clientWhitelist.set(row.serverId, [row.command]) clientWhitelist.guild.set(row.guildId, [row.command])
} }
}); });
userRows.forEach(row => {
if(clientWhitelist.user.has(row.userId)){
let oldEntry = clientWhitelist.user.get(row.userId);
oldEntry.push(row.command)
clientWhitelist.user.set(row.userId, oldEntry)
}else {
clientWhitelist.user.set(row.userId, [row.command])
}
});
db.close();
} }

View file

@ -1,5 +1,6 @@
module.exports = function(client, owners, message, globalPrefix){ const isWhitelisted = require('../util/isWhitelisted')
let prefix = globalPrefix; module.exports = function(client, owners, message){
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;
@ -19,17 +20,15 @@ module.exports = function(client, owners, message, globalPrefix){
const commandName = args.shift().toLowerCase(); const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName); const command = client.commands.get(commandName);
if(command.needsWhitelist){ if(!command) return;
let isWhitelisted = client.whitelist.get(message.guild.id)?.includes(command.name); if(command.needsWhitelist && !isWhitelisted.isWhitelisted(command.name, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)){
if(!isWhitelisted){ message.channel.send(`\`${command.name}\` is not whitelisted for you or this server. The bot admin needs to whitelist the command for you or this server.`)
message.channel.send(`\`${command.name}\` is not whitelisted in this server. The bot admin needs to whitelist the command in this server for it to work`)
return; return;
} }
}
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 getGuildCount = require('../util/getGuildCount');
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(getGuildCount(client)).toBe(i);
}
})

View file

@ -0,0 +1,30 @@
const isWhitelisted = require('../util/isWhitelisted');
const whitelist = {
guild: new Map(),
user: new Map()
}
whitelist.guild.set(20, ["test1", "test3"]);
whitelist.guild.set(21, ["test2", "test4"]);
whitelist.user.set(1001, ["test1", "test3"]);
whitelist.user.set(1002, ["test2", "test4"]);
test('Test isWhitelisted', () => {
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1001, isWhitelisted.whitelistTypes.BOTH)).toBe(true);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 20, 1002, isWhitelisted.whitelistTypes.BOTH)).toBe(false);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 21, 1001, isWhitelisted.whitelistTypes.BOTH)).toBe(false);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1001, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 21, 1001, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1002, isWhitelisted.whitelistTypes.USER_ONLY)).toBe(false);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 20, 1001, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(false);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 21, 1001, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 21, 1002, isWhitelisted.whitelistTypes.GUILD_ONLY)).toBe(true);
expect(isWhitelisted.isWhitelisted("test1", whitelist, 20, 1002, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test2", whitelist, 20, 1002, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test3", whitelist, 21, 1001, isWhitelisted.whitelistTypes.EITHER)).toBe(true);
expect(isWhitelisted.isWhitelisted("test4", whitelist, 20, 1001, isWhitelisted.whitelistTypes.EITHER)).toBe(false);
});

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,8 +0,0 @@
module.exports = function(client){
let guildCount = 0;
client.guilds.cache.each(() => {
guildCount++
});
return guildCount;
}

16
util/getGuildInfo.js Normal file
View file

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

22
util/isWhitelisted.js Normal file
View file

@ -0,0 +1,22 @@
// type: 0 for both, 1 for guild, 2 for userId, 3 for either.
function isWhitelisted(command, whitelist, guildId, userId, type=0){
const isGuildWhitelisted = Boolean(whitelist.guild.get(guildId)?.includes(command))
if(type === whitelistTypes.GUILD_ONLY) return isGuildWhitelisted;
if(type === whitelistTypes.BOTH && !isGuildWhitelisted) return false;
if(type === whitelistTypes.EITHER && isGuildWhitelisted) return true;
const isUserWhitelisted = Boolean(whitelist.user.get(userId)?.includes(command))
if(type === whitelistTypes.USER_ONLY) return isUserWhitelisted;
if(type === whitelistTypes.BOTH && isGuildWhitelisted && isUserWhitelisted) return true;
if(type === whitelistTypes.EITHER && (isGuildWhitelisted || isUserWhitelisted)) return true;
return false;
}
const whitelistTypes = {
BOTH: 0,
GUILD_ONLY: 1,
USER_ONLY: 2,
EITHER: 3
}
module.exports = {isWhitelisted, whitelistTypes}

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,10 +63,22 @@ 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}`
},) },)
@ -71,8 +88,31 @@ module.exports = async function (userID, guild) {
},) },)
} }
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

@ -19,7 +19,7 @@ module.exports = async function(topArtists, topAlbums) {
"Content-Type": `application/json` "Content-Type": `application/json`
}, },
body: JSON.stringify({ body: JSON.stringify({
"model": `deepseek/deepseek-chat-v3-0324:free`, "model": `google/gemini-2.0-flash-001`,
"messages": [ "messages": [
{ {
"role": `system`, "role": `system`,

View file

@ -1,9 +1,9 @@
const getGuildCount = require("./getGuildCount") 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 = getGuildCount(client) 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);
if (isNaN(timeInSeconds)) { let timeInSeconds;
return message.channel.send("Please specify a time, and a message to send after the timer has finished") 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."
} }
let customMessage = compatibility ? args.slice(1).join(" ") : args.slice(2).join(" "); }else {
let reminderTime = currentUnixTime + timeInSeconds timeInSeconds = parseTime(args[0], currentUnixTime);
}
if (isNaN(timeInSeconds)) {
return "Please specify a time, and a message to send after the timer has finished"
}
const customMessage = args.slice(1).join(" ")
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)
}