Skip to content

Commit 86bcb00

Browse files
committed
v0.0.1
0 parents  commit 86bcb00

7 files changed

Lines changed: 3625 additions & 0 deletions

File tree

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Compiled output
2+
dist/
3+
4+
# Dependencies
5+
node_modules/
6+
7+
# Log files
8+
npm-debug.log*
9+
yarn-debug.log*
10+
yarn-error.log*
11+
12+
# OS-specific files
13+
.DS_Store
14+
15+
# IDE-specific files
16+
.vscode/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License with Attribution
2+
3+
Copyright (c) 2025 Juna
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
1. The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# CommandKit Plugin: Riffy
2+
3+
[![npm version](https://img.shields.io/npm/v/commandkit-plugin-riffy.svg)](https://www.npmjs.com/package/commandkit-plugin-riffy)
4+
5+
This plugin for **CommandKit** seamlessly integrates the [Riffy](https://riffy.js.org) Lavalink client, enabling robust audio playback for your Discord bot using CommandKit's intuitive event system. It fully manages the Riffy client's lifecycle, from initialization to event handling.
6+
7+
## Features
8+
9+
* **Seamless Integration**: Automatically initializes and manages the Riffy client.
10+
* **Event-Driven**: Leverages CommandKit's event system to handle all Riffy events.
11+
* **Easy Configuration**: Simple to set up within your `commandkit.config.ts`.
12+
* **Extensible**: Provides direct access to the Riffy instance via `client.riffy`.
13+
14+
## Installation
15+
16+
```bash
17+
npm install commandkit-plugin-riffy
18+
```
19+
20+
## Usage
21+
22+
To get started, register the plugin in your `commandkit.config.ts` file.
23+
24+
```ts
25+
import { defineConfig } from 'commandkit';
26+
import { riffyPlugin } from 'commandkit-plugin-riffy';
27+
28+
export default defineConfig({
29+
plugins: [
30+
riffyPlugin({
31+
riffyNodes: [
32+
{
33+
host: 'localhost',
34+
port: 2333,
35+
password: 'your_lavalink_password',
36+
secure: false,
37+
},
38+
],
39+
}),
40+
],
41+
});
42+
```
43+
44+
This setup initializes Riffy and makes it available throughout your CommandKit application.
45+
46+
## Configuration Options
47+
48+
The plugin can be customized with the following options:
49+
50+
| Option | Type | Description | Required |
51+
| ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | :------: |
52+
| `riffyNodes` | `LavalinkNode[]` | An array of Lavalink node objects for Riffy to connect to. | Yes |
53+
| `riffyOptions` | `Partial<RiffyOptions>` | Optional settings to pass to the Riffy constructor, which will be merged with the plugin's defaults. | No |
54+
| `eventNamespace` | `string` | The CommandKit event namespace for all Riffy events. Defaults to `'riffy'`. | No |
55+
56+
## Handling Events
57+
58+
The plugin bridges Riffy events into CommandKit's event system under a specified namespace (defaulting to `riffy`).
59+
60+
To handle these events, create files inside the `events` directory, following the CommandKit event handling structure.
61+
62+
> **Note:** The folder for the event namespace must be enclosed in parentheses, for example: `(riffy)`.
63+
64+
### Project Structure
65+
66+
Your event handlers should be organized as follows:
67+
68+
```
69+
src/
70+
└── events/
71+
└── (riffy)/
72+
├── nodeConnect/
73+
│ └── logger.js
74+
└── trackStart/
75+
└── now-playing.js
76+
```
77+
78+
### Example: Handling Events
79+
80+
Here are a few examples of how you can listen to Riffy events:
81+
82+
#### Logging Node Connections
83+
84+
This handler will log a message whenever a Lavalink node connects.
85+
86+
**File:** `/src/events/(riffy)/nodeConnect/logging.js`
87+
88+
```javascript
89+
export default function handleNodeConnect(node) {
90+
console.log(`[Riffy] Node "${node.name}" has connected.`);
91+
}
92+
```
93+
94+
#### Announcing Started Tracks
95+
96+
This handler sends a "Now Playing" message when a new track begins.
97+
98+
**File:** `/src/events/(riffy)/trackStart/nowPlaying.js`
99+
100+
```javascript
101+
import { EmbedBuilder } from 'discord.js';
102+
103+
export default async function handleTrackStart(player, track) {
104+
const channel = player.textChannel;
105+
if (!channel) return;
106+
107+
const embed = new EmbedBuilder()
108+
.setColor('#00FF00')
109+
.setTitle('Now Playing')
110+
.setDescription(`[${track.title}](${track.uri})`)
111+
.setThumbnail(track.thumbnail)
112+
.addFields({ name: 'Author', value: track.author, inline: true })
113+
.setTimestamp();
114+
115+
await channel.send({ embeds: [embed] });
116+
}
117+
```
118+
119+
### Available Events
120+
121+
The plugin emits the following events within the configured namespace:
122+
123+
* `nodeConnect`
124+
* `nodeDisconnect`
125+
* `nodeError`
126+
* `nodeReconnect`
127+
* `nodeCreate`
128+
* `nodeDestroy`
129+
* `playerCreate`
130+
* `playerDisconnect`
131+
* `playerMove`
132+
* `playerUpdate`
133+
* `trackStart`
134+
* `trackEnd`
135+
* `trackError`
136+
* `trackStuck`
137+
* `queueEnd`
138+
* `debug`
139+
140+
## Accessing the Riffy Client
141+
142+
The Riffy instance is attached to the Discord.js `Client` object and can be accessed anywhere via `client.riffy`.
143+
144+
### Example: Play Command
145+
146+
Here is a basic example of a `play` command that uses the `client.riffy` instance to search for and play a track.
147+
148+
**File:** `/src/commands/play.js`
149+
```javascript
150+
import { SlashCommandBuilder } from 'discord.js';
151+
152+
export const data = new SlashCommandBuilder()
153+
.setName('play')
154+
.setDescription('Plays a song in your voice channel.')
155+
.addStringOption(option =>
156+
option.setName('query')
157+
.setDescription('The name of the song or a URL.')
158+
.setRequired(true)
159+
);
160+
161+
export const chatInput = async (ctx) => {
162+
const { guild, member, channel } = ctx.interaction;
163+
const query = interaction.options.getString('query');
164+
165+
// Check if the user is in a voice channel
166+
if (!member.voice.channel) {
167+
return interaction.reply({
168+
content: 'You must be in a voice channel to use this command.',
169+
ephemeral: true,
170+
});
171+
}
172+
173+
let player = client.riffy.players.get(guild.id);
174+
175+
if (!player) {
176+
player = client.riffy.createConnection({
177+
guildId: guild.id,
178+
voiceChannel: voiceChannel.id,
179+
textChannel: channel.id,
180+
deaf: true,
181+
defaultVolume: client.config.riffyOptions.defaultVolume || 50,
182+
});
183+
}
184+
185+
await interaction.deferReply();
186+
187+
// Resolve the query to get track(s)
188+
const resolve = await client.riffy.resolve({
189+
query: query,
190+
requester: interaction.user
191+
});
192+
193+
const { loadType, tracks, playlistInfo } = resolve;
194+
195+
// Add track(s) to the queue based on the load type
196+
if (loadType === 'PLAYLIST_LOADED') {
197+
for (const track of tracks) {
198+
player.queue.add(track);
199+
}
200+
await interaction.editReply(`Added **${tracks.length}** tracks from the playlist "${playlistInfo.name}" to the queue.`);
201+
} else if (loadType === 'SEARCH_RESULT' || loadType === 'TRACK_LOADED') {
202+
const track = tracks.shift();
203+
player.queue.add(track);
204+
await interaction.editReply(`Added **${track.info.title}** to the queue.`);
205+
} else {
206+
return interaction.editReply('I could not find any tracks for that query.');
207+
}
208+
209+
// Start playing if the player is not already active
210+
if (!player.playing && !player.paused) {
211+
player.play();
212+
}
213+
}
214+
```
215+
216+
## License
217+
218+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
219+
220+
## Contributing
221+
222+
Contributions are welcome! Please feel free to open an issue or submit a pull request for any improvements or bug fixes.
223+
224+
## Acknowledgments
225+
226+
* **[Riffy](https://riffy.js.org/)**: For the powerful Lavalink client.
227+
* **[CommandKit](https://commandkit.dev/)**: For the modern and feature-rich Discord.js framework.

0 commit comments

Comments
 (0)