.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9bc213b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+
+
+
+
+# @kirishima/queue
+
+
+
+# Features
+- Written in TypeScript
+- Support ESM & CommonJS
\ No newline at end of file
diff --git a/build.config.ts b/build.config.ts
new file mode 100644
index 0000000..a8f1354
--- /dev/null
+++ b/build.config.ts
@@ -0,0 +1,9 @@
+import { defineBuildConfig } from 'unbuild'
+
+export default defineBuildConfig({
+ entries: [
+ 'src/',
+ ],
+ clean: true,
+ declaration: true,
+})
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8c39678
--- /dev/null
+++ b/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "@kirishima/queue",
+ "author": {
+ "name": "KagChi"
+ },
+ "description": "A built-in queue plugin for beginner user",
+ "main": "dist/index.js",
+ "module": "dist/index.mjs",
+ "exports": {
+ ".": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.js"
+ }
+ },
+ "version": "0.1.0",
+ "license": "GPL-3.0",
+ "repository": {
+ "url": "https://github.com/kirishima-ship/queue"
+ },
+ "bugs": {
+ "url": "https://github.com/kirishima-ship/queue/issues"
+ },
+ "readme": "https://github.com/kirishima-ship/queue/blob/main/README.md",
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=7.0.0"
+ },
+ "scripts": {
+ "build": "rimraf dist && unbuild && tsc",
+ "lint": "eslint src",
+ "lint:fix": "eslint src --fix",
+ "format": "prettier --write {src,tests}/**/*.ts"
+ },
+ "devDependencies": {
+ "@babel/types": "7.17.0",
+ "@sapphire/eslint-config": "4.3.0",
+ "@sapphire/prettier-config": "1.4.0",
+ "@sapphire/ts-config": "3.3.2",
+ "eslint": "8.11.0",
+ "prettier": "2.5.1",
+ "rimraf": "3.0.2",
+ "typescript": "4.6.2",
+ "unbuild": "0.7.0"
+ },
+ "prettier": "@sapphire/prettier-config",
+ "dependencies": {
+ "@kirishima/core": "^0.2.3",
+ "@kirishima/ws": "^0.2.1",
+ "lavalink-api-types": "^0.1.6"
+ }
+}
diff --git a/src/Structures/KirishimaNode.ts b/src/Structures/KirishimaNode.ts
new file mode 100644
index 0000000..78fcb8b
--- /dev/null
+++ b/src/Structures/KirishimaNode.ts
@@ -0,0 +1,103 @@
+import { isPartialTrack, Kirishima, KirishimaNodeOptions, Structure } from '@kirishima/core';
+import type { Gateway } from '@kirishima/ws';
+import {
+ TrackEndEventPayload,
+ TrackExceptionEventPayload,
+ TrackStartEventPayload,
+ TrackStuckEventPayload,
+ WebSocketClosedEventPayload,
+ WebSocketTypeEnum
+} from 'lavalink-api-types';
+import { KirishimaPlayer, LoopType } from './KirishimaPlayer';
+export class KirishimaNode extends Structure.get('KirishimaNode') {
+ public constructor(options: KirishimaNodeOptions, kirishima: Kirishima) {
+ super(options, kirishima);
+ kirishima.on('nodeRaw', this.handleNodeRaw.bind(this));
+ }
+
+ private handleNodeRaw(_node: KirishimaNode, _gateway: Gateway, message: NodeTrackRawMessage) {
+ const player = this.kirishima.players!.get(message.guildId);
+ if (!player) return;
+
+ if (message.type === WebSocketTypeEnum.TrackEndEvent) {
+ void this.trackEnd(player, message);
+ }
+
+ if (message.type === WebSocketTypeEnum.TrackExceptionEvent) {
+ this.trackException(player, message);
+ }
+
+ if (message.type === WebSocketTypeEnum.TrackStuckEvent) {
+ this.trackStuck(player, message);
+ }
+
+ if (message.type === WebSocketTypeEnum.TrackStartEvent) {
+ this.trackStart(player, message);
+ }
+ }
+
+ private async trackEnd(player: KirishimaPlayer, message: TrackEndEventPayload) {
+ if (message.reason === 'REPLACED') return;
+ try {
+ if (player.loopType === LoopType.Track) {
+ if (isPartialTrack(player.queue.current) && player.queue.current) {
+ const track = await player.resolvePartialTrack(player.queue.current);
+ if (track.tracks.length) {
+ await player.playTrack(track.tracks[0].track);
+ return;
+ }
+ await player.stopTrack();
+ return;
+ }
+
+ if (player.queue.current) {
+ await player.playTrack(player.queue.current);
+ return;
+ }
+ }
+
+ if (player.queue.current && player.loopType === LoopType.Queue) {
+ player.queue.add(player.queue.current);
+ player.queue.previous = player.queue.current;
+ player.queue.current = player.queue.shift() ?? null;
+
+ if (player.queue.current) {
+ if (isPartialTrack(player.queue.current)) {
+ const track = await player.resolvePartialTrack(player.queue.current);
+ if (track.tracks.length) {
+ await player.playTrack(track.tracks[0].track);
+ return;
+ }
+ await player.stopTrack();
+ return;
+ }
+
+ await player.playTrack(player.queue.current!);
+ return;
+ }
+ this.kirishima.emit('queueEnd', player, player);
+ }
+ } catch (e) {
+ this.kirishima.emit('playerError', player, e);
+ }
+ }
+
+ private trackException(player: KirishimaPlayer, message: TrackExceptionEventPayload) {
+ this.kirishima.emit('trackException', player, player.queue.current, message);
+ }
+
+ private trackStuck(player: KirishimaPlayer, message: TrackStuckEventPayload) {
+ this.kirishima.emit('trackStuck', player, player.queue.current, message);
+ }
+
+ private trackStart(player: KirishimaPlayer, message: TrackStartEventPayload) {
+ this.kirishima.emit('trackStart', player, player.queue.current, message);
+ }
+}
+
+export type NodeTrackRawMessage =
+ | TrackStartEventPayload
+ | TrackEndEventPayload
+ | TrackExceptionEventPayload
+ | TrackStuckEventPayload
+ | WebSocketClosedEventPayload;
diff --git a/src/Structures/KirishimaPlayer.ts b/src/Structures/KirishimaPlayer.ts
new file mode 100644
index 0000000..a6b66a4
--- /dev/null
+++ b/src/Structures/KirishimaPlayer.ts
@@ -0,0 +1,37 @@
+import { Kirishima, KirishimaNode, KirishimaPartialTrack, KirishimaPlayerOptions, Structure } from '@kirishima/core';
+import type { LoadTrackResponse } from 'lavalink-api-types';
+
+import { KirishimaQueueTracks } from './KirishimaQueueTracks';
+
+export class KirishimaPlayer extends Structure.get('KirishimaPlayer') {
+ public queue = new KirishimaQueueTracks();
+ public loopType: LoopType = LoopType.None;
+
+ public constructor(options: KirishimaPlayerOptions, kirishima: Kirishima, node: KirishimaNode) {
+ super(options, kirishima, node);
+ }
+
+ public setLoop(type: LoopType) {
+ this.loopType = type;
+ return this;
+ }
+
+ public resolvePartialTrack(track: KirishimaPartialTrack) {
+ return this.kirishima.resolveTracks(`${track.info.title} - ${track.info.author ? track.info.author : ''}`);
+ }
+}
+
+declare module '@kirishima/core' {
+ export interface KirishimaPlayer {
+ queue: KirishimaQueueTracks;
+ loopType: LoopType;
+ setLoop(type: LoopType): this;
+ resolvePartialTrack(track: KirishimaPartialTrack): Promise;
+ }
+}
+
+export enum LoopType {
+ None = 0,
+ Track = 1,
+ Queue = 2
+}
diff --git a/src/Structures/KirishimaQueue.ts b/src/Structures/KirishimaQueue.ts
new file mode 100644
index 0000000..8ac47ef
--- /dev/null
+++ b/src/Structures/KirishimaQueue.ts
@@ -0,0 +1,16 @@
+import { KirishimaPlugin, Structure } from '@kirishima/core';
+import { KirishimaNode } from './KirishimaNode';
+import { KirishimaPlayer } from './KirishimaPlayer';
+
+export class KirishimaQueue extends KirishimaPlugin {
+ public constructor() {
+ super({
+ name: 'KirishimaQueue'
+ });
+ }
+
+ public load() {
+ Structure.extend('KirishimaPlayer', () => KirishimaPlayer);
+ Structure.extend('KirishimaNode', () => KirishimaNode);
+ }
+}
diff --git a/src/Structures/KirishimaQueueTracks.ts b/src/Structures/KirishimaQueueTracks.ts
new file mode 100644
index 0000000..b439db7
--- /dev/null
+++ b/src/Structures/KirishimaQueueTracks.ts
@@ -0,0 +1,34 @@
+/* eslint-disable no-negated-condition */
+import { isPartialTrack, isTrack, KirishimaPartialTrack, KirishimaTrack } from '@kirishima/core';
+
+export class KirishimaQueueTracks extends Array {
+ public current: KirishimaPartialTrack | KirishimaTrack | null = null;
+ public previous: KirishimaPartialTrack | KirishimaTrack | null = null;
+
+ public add(trackOrTracks: KirishimaPartialTrack | KirishimaTrack | (KirishimaPartialTrack | KirishimaTrack)[]) {
+ if (!Array.isArray(trackOrTracks) && (!isTrack(trackOrTracks) || !isPartialTrack(trackOrTracks)))
+ throw new Error('Track must be a "KirishimaPartialTrack" or "KirishimaTrack".');
+
+ if (Array.isArray(trackOrTracks) && !ValidateValidArrayTracks(trackOrTracks))
+ throw new Error('Track must be a "KirishimaPartialTrack[]" or "KirishimaTrack[]".');
+
+ if (!this.current) {
+ if (!Array.isArray(trackOrTracks)) {
+ this.current = trackOrTracks;
+ } else {
+ this.current = (trackOrTracks = [...trackOrTracks]).shift()!;
+ }
+ }
+
+ if (Array.isArray(trackOrTracks)) this.push(...trackOrTracks);
+ else this.push(trackOrTracks);
+ }
+}
+
+export function ValidateValidArrayTracks(tracks: (KirishimaPartialTrack | KirishimaTrack)[]) {
+ for (const track of tracks) {
+ if (!isTrack(track) || !isPartialTrack(track)) return false;
+ }
+
+ return true;
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..fa9f0ca
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,3 @@
+export const main = () => 'Kirishima ship ready to serve !';
+
+export default main;
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
new file mode 100644
index 0000000..c2f78f8
--- /dev/null
+++ b/tsconfig.eslint.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src"]
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..f8d28c8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@sapphire/ts-config",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "composite": true,
+ "preserveConstEnums": true
+ },
+ "include": ["src/**/*.ts"]
+}
\ No newline at end of file