Initial Commit

This commit is contained in:
Mars Niermann 2024-03-11 01:37:31 +01:00
commit e1da30687e
12 changed files with 2908 additions and 0 deletions

7
.envrc Normal file
View file

@ -0,0 +1,7 @@
# Automatically sets up your devbox environment whenever you cd into this
# directory via our direnv integration:
eval "$(devbox generate direnv --print-envrc)"
# check out https://www.jetpack.io/devbox/docs/ide_configuration/direnv/
# for more details

130
.gitignore vendored Normal file
View file

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

71
README.md Normal file
View file

@ -0,0 +1,71 @@
# Kiku
## TODO
Add lavalink
## Packages
https://github.com/discordjs/discord.js
https://github.com/shipgirlproject/Shoukaku
https://github.com/Takiyo0/Kazagumo
https://www.npmjs.com/package/kazagumo-spotify
<!-- gen-readme start - generated by https://github.com/jetpack-io/devbox/ -->
## Getting Started
This project uses [devbox](https://github.com/jetpack-io/devbox) to manage its development environment.
Install devbox:
```sh
curl -fsSL https://get.jetpack.io/devbox | bash
```
Start the devbox shell:
```sh
devbox shell
```
Run a script in the devbox environment:
```sh
devbox run <script>
```
## Scripts
Scripts are custom commands that can be run using this project's environment. This project has the following scripts:
- [run_test](#devbox-run-run_test)
## Shell Init Hook
The Shell Init Hook is a script that runs whenever the devbox environment is instantiated. It runs
on `devbox shell` and on `devbox run`.
```sh
npm install
npm run build
```
## Packages
- [nodejs@18](https://www.nixhub.io/packages/nodejs)
## Script Details
### devbox run run_test
```sh
npm run start
```
&ensp;
<!-- gen-readme end -->

12
biome.json Normal file
View file

@ -0,0 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
}
}
}

15
devbox.json Normal file
View file

@ -0,0 +1,15 @@
{
"packages": [
"nodejs@18",
"jdk@17.0.10"
],
"shell": {
"init_hook": [
"npm install",
"npm run build"
],
"scripts": {
"run_test": "npm run start"
}
}
}

39
devbox.lock Normal file
View file

@ -0,0 +1,39 @@
{
"lockfile_version": "1",
"packages": {
"jdk@17.0.10": {
"last_modified": "2024-02-24T23:06:34Z",
"resolved": "github:NixOS/nixpkgs/9a9dae8f6319600fa9aebde37f340975cab4b8c0#jdk17",
"source": "devbox-search",
"version": "17.0.10",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/4p4k1w2q7fz1hwc55wjk44bza0vhh71h-zulu-ca-jdk-17.0.10"
},
"x86_64-darwin": {
"store_path": "/nix/store/kfmkczf4h268yax8bg5nlyapbms6zcav-zulu-ca-jdk-17.0.10"
}
}
},
"nodejs@18": {
"last_modified": "2024-02-24T23:06:34Z",
"resolved": "github:NixOS/nixpkgs/9a9dae8f6319600fa9aebde37f340975cab4b8c0#nodejs_18",
"source": "devbox-search",
"version": "18.19.1",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/fxly5x870ssyw5rbvdi58jbhc4j03mzk-nodejs-18.19.1"
},
"aarch64-linux": {
"store_path": "/nix/store/6iirvvbd99b7dfwk3z8phry2yliwvm99-nodejs-18.19.1"
},
"x86_64-darwin": {
"store_path": "/nix/store/rzsav2ndbzah0vkmyqpnhcwxp4n0zdm6-nodejs-18.19.1"
},
"x86_64-linux": {
"store_path": "/nix/store/c8phnfr1s43123qm3fmyiq5n1hs5csdv-nodejs-18.19.1"
}
}
}
}
}

BIN
lavalink/Lavalink.jar Normal file

Binary file not shown.

6
lavalink/application.yml Normal file
View file

@ -0,0 +1,6 @@
server: # REST and WS server
port: 2333
address: 0.0.0.0
lavalink:
server:
password: "youshallnotpass"

2333
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

31
package.json Normal file
View file

@ -0,0 +1,31 @@
{
"name": "kiku",
"main": "src/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"watch": "tsc watch"
},
"devDependencies": {
"@biomejs/biome": "1.6.0",
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.7.18",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.57.0",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"discord.js": "^14.14.1",
"dotenv": "^16.4.5",
"kazagumo": "^3.0.1",
"kazagumo-spotify": "^2.0.1",
"pretty-ms": "^9.0.0",
"shoukaku": "^4.0.1"
}
}

243
src/index.ts Normal file
View file

@ -0,0 +1,243 @@
import {
type APIEmbed,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelType,
Client,
EmbedBuilder,
GatewayIntentBits,
type Message,
SlashCommandBuilder,
} from "discord.js";
import { Connectors, Track } from "shoukaku";
import { Kazagumo, type KazagumoPlayer, type KazagumoTrack, type Payload } from "kazagumo";
import KazagumoPlugin from "kazagumo-spotify";
import prettyMilliseconds from 'pretty-ms';
const { Guilds, GuildVoiceStates, GuildMessages, MessageContent } = GatewayIntentBits;
import * as dotenv from "dotenv";
dotenv.config();
if (!process.env.SPOTIFY_ID || !process.env.SPOTIFY_SECRET) {
throw "Spotfiy Credentials missing";
}
const Nodes = [
{
name: "lavalink",
url: "localhost:2333",
auth: "youshallnotpass",
secure: false,
},
];
const client = new Client({
intents: [Guilds, GuildVoiceStates, GuildMessages, MessageContent],
});
const kazagumo = new Kazagumo(
{
defaultSearchEngine: "youtube",
send: (guildId: string, payload:Payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
},
plugins: [
new KazagumoPlugin({
clientId: process.env.SPOTIFY_ID,
clientSecret: process.env.SPOTIFY_SECRET,
playlistPageLimit: 1, // optional ( 100 tracks per page )
albumPageLimit: 1, // optional ( 50 tracks per page )
searchLimit: 10, // optional ( track search limit. Max 50 )
searchMarket: "US", // optional || default: US ( Enter the country you live in. [ Can only be of 2 letters. For eg: US, IN, EN ] )//
}),
],
},
new Connectors.DiscordJS(client),
Nodes,
);
client.on("ready", () => console.log(`${client.user!.tag}·Ready!`));
kazagumo.shoukaku.on("ready", (name:string) =>
console.log(`Lavalink ${name}: Ready!`),
);
kazagumo.shoukaku.on("error", (name:string, error:Error) =>
console.error(`Lavalink ${name}: Error Caught,`, error),
);
kazagumo.shoukaku.on("close", (name:string, code:unknown, reason:unknown) =>
console.warn(
`Lavalink ${name}: Closed, Code ${code}, Reason ${reason || "No reason"}`,
),
);
kazagumo.shoukaku.on("debug", (name:string, info:string) =>
console.debug(`Lavalink ${name}: Debug,`, info),
);
kazagumo.shoukaku.on("disconnect", (name:string, count:number) => {
// if (moved) return;
// players.map((player:any) => player.connection.disconnect());
console.warn(`Lavalink ${name}: Disconnected`);
});
kazagumo.on("playerStart", (player:KazagumoPlayer, track:KazagumoTrack) => {
if (!player.textId) return;
const channel = client.channels.cache.get(player.textId)
if (!channel) return;
if (channel.type === ChannelType.GuildText)
{ channel.send({ content: `Now playing **${track.title}** by **${track.author}**` })
.then((x:any) => player.data.set("message", x));}
});
kazagumo.on("playerEnd", (player:KazagumoPlayer) => {
player.data.get("message")?.edit({ content: "Finished playing" });
});
kazagumo.on("playerEmpty", (player:KazagumoPlayer) => {
if (!player.textId) return;
const channel = client.channels.cache.get(player.textId)
if (!channel) return;
if (channel.type === ChannelType.GuildText)
{ channel.send({ content: "Destroyed player due to inactivity." })
.then((x:any) => player.data.set("message", x));}
player.destroy();
});
client.on("messageCreate", async (msg:Message) => {
if (msg.author.bot) return;
if (!msg.guild) return;
if (!msg.member) return;
if (msg.channel.type !== ChannelType.GuildText) return;
if (msg.channel.name === "moe-song-requests") {
const query = msg.content;
const { channel } = msg.member.voice;
if (!channel){
msg.reply(
"You need to be in a voice channel to use this command!",
);
return;
}
// if (channel.type !== ChannelType.GuildVoice) {return;}
const player = await kazagumo.createPlayer({
guildId: msg.guild.id,
textId: msg.channel.id,
voiceId: channel.id,
volume: 40,
});
console.log("Player created")
const result = await kazagumo.search(query, { requester: msg.author });
if (!result.tracks.length) msg.reply("No results found!");
if (result.type === "PLAYLIST")
for (const track of result.tracks) player.queue.add(track);
else player.queue.add(result.tracks[0]);
const play = new ButtonBuilder()
.setCustomId("play")
.setLabel("Play/Pause")
.setStyle(ButtonStyle.Secondary)
.setEmoji("889943073793122355");
const stop = new ButtonBuilder()
.setCustomId("stop")
.setLabel("Stop Playing")
.setStyle(ButtonStyle.Secondary)
.setEmoji("889943074258694184");
const skip = new ButtonBuilder()
.setCustomId("skip")
.setLabel("Skip Song")
.setStyle(ButtonStyle.Secondary)
.setEmoji("889943074233516072");
const loop = new ButtonBuilder()
.setCustomId("loop")
.setLabel("Loop Song")
.setStyle(ButtonStyle.Secondary)
.setEmoji("889943073667289099");
const shuffle = new ButtonBuilder()
.setCustomId("shuffle")
.setLabel("Shuffle Queue")
.setStyle(ButtonStyle.Secondary)
.setEmoji("890325437962678352");
const seek = new ButtonBuilder()
.setCustomId("seek")
.setLabel("Seek Forward")
.setStyle(ButtonStyle.Secondary)
.setEmoji("890325511878889504");
const previous = new ButtonBuilder()
.setCustomId("previous")
.setLabel("Previous Song")
.setStyle(ButtonStyle.Secondary)
.setEmoji("890325512071831562");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(play, stop, skip, loop);
const row2 = new ActionRowBuilder<ButtonBuilder>().addComponents(shuffle, seek, previous);
let track_author:string
if (result.tracks[0].author) {
track_author = result.tracks[0].author
} else {
track_author = ""
}
let track_duration:string
if (result.tracks[0].length) {
track_duration = prettyMilliseconds(result.tracks[0].length)
} else {
track_duration = "NaN:NaN"
}
let track_thumbnail:string
if (result.tracks[0].thumbnail) {
track_thumbnail = result.tracks[0].thumbnail.toString()
} else {
track_thumbnail = "NaN:NaN"
}
const embed = new EmbedBuilder()
.setAuthor({
name: "Added to queue",
iconURL: "https://cdn.m3.fyi/MoeLogo.gif",
})
.setTitle(
result.type === "PLAYLIST"
? `Queued ${result.tracks.length} from ${result.playlistName}`
: `Queued ${result.tracks[0].title}`,
)
.setDescription(`[${result.tracks[0].title}](${result.tracks[0].uri})`)
.addFields(
{
name: "Author",
value: track_author,
inline: true,
},
{
name: "Duration",
value: track_duration,
inline: true,
},
)
.setThumbnail(track_thumbnail)
.setColor("#ff0047");
if (!player.playing && !player.paused) player.play();
const response = msg.channel.send({
embeds: [embed],
components: [row, row2],
})
return;
}
})
client.login(process.env.TOKEN);

21
tsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 18",
"_version": "18.2.0",
"compilerOptions": {
"lib": [
"es2022"
],
"module": "node16",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node16",
"outDir": "dist",
},
"include": [
"src/*"
],
}