From 78b2d02e9f936642aaed85833eb4a1f34c796142 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 15 Mar 2022 22:38:39 +0100
Subject: [PATCH 01/21] Add .gitattributes, .gitignore, and README.md.

---
 .gitattributes |  63 +++++++++
 .gitignore     | 363 +++++++++++++++++++++++++++++++++++++++++++++++++
 README.md      |   1 +
 3 files changed, 427 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 README.md

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9491a2f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,363 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1b39896
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+TomatenMusic V2
\ No newline at end of file
-- 
2.45.2


From ae4125574c16a1665b55fe7abc1da261095fb586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 15 Mar 2022 22:38:41 +0100
Subject: [PATCH 02/21] Add project files.

---
 TomatenMusic Api/.config/dotnet-tools.json    |  12 +
 .../Auth/Controllers/UsersController.cs       |  37 ++
 TomatenMusic Api/Auth/Entities/User.cs        |  14 +
 TomatenMusic Api/Auth/Helpers/AppSettings.cs  |   6 +
 .../Auth/Helpers/AuthorizeAttribute.cs        |  19 +
 .../Auth/Helpers/JwtMiddleware.cs             |  58 +++
 .../Auth/Models/AuthenticateRequest.cs        |  12 +
 .../Auth/Models/AuthenticateResponse.cs       |  22 +
 TomatenMusic Api/Auth/Services/UserService.cs |  75 +++
 .../Controllers/PlayerController.cs           |  92 ++++
 TomatenMusic Api/Models/BasicTrackInfo.cs     |  42 ++
 .../Models/ChannelConnectRequest.cs           |  13 +
 .../Models/PlayerConnectionInfo.cs            |  62 +++
 TomatenMusic Api/Program.cs                   |  45 ++
 .../Properties/launchSettings.json            |  31 ++
 TomatenMusic Api/Services/EventBus.cs         |  30 ++
 .../Services/TomatenMusicDataService.cs       |  88 ++++
 .../Services/TomatenMusicService.cs           |  54 ++
 TomatenMusic Api/TomatenMusic Api.csproj      |  35 ++
 TomatenMusic Api/appsettings.Development.json |   8 +
 TomatenMusic Api/appsettings.json             |  11 +
 TomatenMusic Api/config.json                  |   7 +
 TomatenMusic V2.sln                           |  31 ++
 .../Commands/Checks/OnlyGuildCheck.cs         |  24 +
 .../Checks/UserInMusicChannelCheck.cs         |  41 ++
 .../Checks/UserInVoiceChannelCheck.cs         |  27 +
 TomatenMusicCore/Commands/MusicCommands.cs    | 266 ++++++++++
 TomatenMusicCore/Commands/PlayCommandGroup.cs | 294 +++++++++++
 .../Music/Entitites/FullTrackContext.cs       |  69 +++
 .../Music/Entitites/LavalinkPlaylist.cs       |  34 ++
 .../Music/Entitites/SpotifyPlaylist.cs        |  29 ++
 .../Music/Entitites/YoutubePlaylist.cs        |  38 ++
 TomatenMusicCore/Music/GuildPlayer.cs         | 333 +++++++++++++
 TomatenMusicCore/Music/LoopType.cs            |  17 +
 TomatenMusicCore/Music/MusicActionResponse.cs |  23 +
 TomatenMusicCore/Music/PlayerQueue.cs         | 161 ++++++
 TomatenMusicCore/Music/TrackProvider.cs       |  79 +++
 .../Prompt/Buttons/AddToQueueButton.cs        |  56 +++
 .../Prompt/Implementation/QueuePrompt.cs      | 275 ++++++++++
 .../Prompt/Implementation/SongActionPrompt.cs |  29 ++
 .../Implementation/SongListActionPrompt.cs    |  40 ++
 .../Implementation/SongSelectorPrompt.cs      | 100 ++++
 .../Implementation/StringSelectorPrompt.cs    |  45 ++
 TomatenMusicCore/Prompt/Model/ButtonPrompt.cs |  41 ++
 .../Prompt/Model/CombinedPrompt.cs            |  62 +++
 .../Prompt/Model/DiscordPromptBase.cs         | 471 ++++++++++++++++++
 .../Prompt/Model/PaginatedButtonPrompt.cs     | 138 +++++
 .../Prompt/Model/PaginatedSelectPrompt.cs     | 160 ++++++
 TomatenMusicCore/Prompt/Model/PromptState.cs  |  15 +
 TomatenMusicCore/Prompt/Model/SelectPrompt.cs |  48 ++
 .../Prompt/Option/ButtonPromptOption.cs       |  32 ++
 .../Prompt/Option/IPromptOption.cs            |  26 +
 .../Prompt/Option/SelectMenuOption.cs         |  23 +
 .../Prompt/Option/SelectMenuPromptOption.cs   |  50 ++
 TomatenMusicCore/Services/SpotifyService.cs   | 156 ++++++
 TomatenMusicCore/Services/YoutubeService.cs   | 127 +++++
 TomatenMusicCore/TomatenMusicBot.cs           | 199 ++++++++
 TomatenMusicCore/TomatenMusicCore.csproj      |  31 ++
 TomatenMusicCore/Util/CollectionUtil.cs       |  56 +++
 TomatenMusicCore/Util/Common.cs               | 275 ++++++++++
 TomatenMusicCore/Util/PageManager.cs          |  68 +++
 TomatenMusicCore/Util/RandomUtil.cs           |  16 +
 TomatenMusicCore/config.json                  |   7 +
 63 files changed, 4785 insertions(+)
 create mode 100644 TomatenMusic Api/.config/dotnet-tools.json
 create mode 100644 TomatenMusic Api/Auth/Controllers/UsersController.cs
 create mode 100644 TomatenMusic Api/Auth/Entities/User.cs
 create mode 100644 TomatenMusic Api/Auth/Helpers/AppSettings.cs
 create mode 100644 TomatenMusic Api/Auth/Helpers/AuthorizeAttribute.cs
 create mode 100644 TomatenMusic Api/Auth/Helpers/JwtMiddleware.cs
 create mode 100644 TomatenMusic Api/Auth/Models/AuthenticateRequest.cs
 create mode 100644 TomatenMusic Api/Auth/Models/AuthenticateResponse.cs
 create mode 100644 TomatenMusic Api/Auth/Services/UserService.cs
 create mode 100644 TomatenMusic Api/Controllers/PlayerController.cs
 create mode 100644 TomatenMusic Api/Models/BasicTrackInfo.cs
 create mode 100644 TomatenMusic Api/Models/ChannelConnectRequest.cs
 create mode 100644 TomatenMusic Api/Models/PlayerConnectionInfo.cs
 create mode 100644 TomatenMusic Api/Program.cs
 create mode 100644 TomatenMusic Api/Properties/launchSettings.json
 create mode 100644 TomatenMusic Api/Services/EventBus.cs
 create mode 100644 TomatenMusic Api/Services/TomatenMusicDataService.cs
 create mode 100644 TomatenMusic Api/Services/TomatenMusicService.cs
 create mode 100644 TomatenMusic Api/TomatenMusic Api.csproj
 create mode 100644 TomatenMusic Api/appsettings.Development.json
 create mode 100644 TomatenMusic Api/appsettings.json
 create mode 100644 TomatenMusic Api/config.json
 create mode 100644 TomatenMusic V2.sln
 create mode 100644 TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
 create mode 100644 TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
 create mode 100644 TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
 create mode 100644 TomatenMusicCore/Commands/MusicCommands.cs
 create mode 100644 TomatenMusicCore/Commands/PlayCommandGroup.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/FullTrackContext.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/LavalinkPlaylist.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
 create mode 100644 TomatenMusicCore/Music/GuildPlayer.cs
 create mode 100644 TomatenMusicCore/Music/LoopType.cs
 create mode 100644 TomatenMusicCore/Music/MusicActionResponse.cs
 create mode 100644 TomatenMusicCore/Music/PlayerQueue.cs
 create mode 100644 TomatenMusicCore/Music/TrackProvider.cs
 create mode 100644 TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PromptState.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/SelectPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/IPromptOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
 create mode 100644 TomatenMusicCore/Services/SpotifyService.cs
 create mode 100644 TomatenMusicCore/Services/YoutubeService.cs
 create mode 100644 TomatenMusicCore/TomatenMusicBot.cs
 create mode 100644 TomatenMusicCore/TomatenMusicCore.csproj
 create mode 100644 TomatenMusicCore/Util/CollectionUtil.cs
 create mode 100644 TomatenMusicCore/Util/Common.cs
 create mode 100644 TomatenMusicCore/Util/PageManager.cs
 create mode 100644 TomatenMusicCore/Util/RandomUtil.cs
 create mode 100644 TomatenMusicCore/config.json

diff --git a/TomatenMusic Api/.config/dotnet-tools.json b/TomatenMusic Api/.config/dotnet-tools.json
new file mode 100644
index 0000000..23f4f07
--- /dev/null
+++ b/TomatenMusic Api/.config/dotnet-tools.json	
@@ -0,0 +1,12 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-ef": {
+      "version": "6.0.3",
+      "commands": [
+        "dotnet-ef"
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Controllers/UsersController.cs b/TomatenMusic Api/Auth/Controllers/UsersController.cs
new file mode 100644
index 0000000..f441cf2
--- /dev/null
+++ b/TomatenMusic Api/Auth/Controllers/UsersController.cs	
@@ -0,0 +1,37 @@
+namespace WebApi.Controllers;
+
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Models;
+using TomatenMusic_Api.Auth.Services;
+
+[ApiController]
+[Route("api/[controller]")]
+public class UsersController : ControllerBase
+{
+    private IUserService _userService;
+
+    public UsersController(IUserService userService)
+    {
+        _userService = userService;
+    }
+
+    [HttpPost("authenticate")]
+    public IActionResult Authenticate(AuthenticateRequest model)
+    {
+        var response = _userService.Authenticate(model);
+
+        if (response == null)
+            return BadRequest(new { message = "Username or password is incorrect" });
+
+        return Ok(response);
+    }
+
+    [Authorize]
+    [HttpGet]
+    public IActionResult GetAll()
+    {
+        var users = _userService.GetAll();
+        return Ok(users);
+    }
+}
diff --git a/TomatenMusic Api/Auth/Entities/User.cs b/TomatenMusic Api/Auth/Entities/User.cs
new file mode 100644
index 0000000..27a63fc
--- /dev/null
+++ b/TomatenMusic Api/Auth/Entities/User.cs	
@@ -0,0 +1,14 @@
+namespace TomatenMusic_Api.Auth.Entities;
+
+using System.Text.Json.Serialization;
+
+public class User
+{
+    public int Id { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+    public string Username { get; set; }
+
+    [JsonIgnore]
+    public string Password { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Helpers/AppSettings.cs b/TomatenMusic Api/Auth/Helpers/AppSettings.cs
new file mode 100644
index 0000000..c0c6dfc
--- /dev/null
+++ b/TomatenMusic Api/Auth/Helpers/AppSettings.cs	
@@ -0,0 +1,6 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+public class AppSettings
+{
+    public string Secret { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Helpers/AuthorizeAttribute.cs b/TomatenMusic Api/Auth/Helpers/AuthorizeAttribute.cs
new file mode 100644
index 0000000..dac92ab
--- /dev/null
+++ b/TomatenMusic Api/Auth/Helpers/AuthorizeAttribute.cs	
@@ -0,0 +1,19 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using TomatenMusic_Api.Auth.Entities;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class AuthorizeAttribute : Attribute, IAuthorizationFilter
+{
+    public void OnAuthorization(AuthorizationFilterContext context)
+    {
+        var user = (User)context.HttpContext.Items["User"];
+        if (user == null)
+        {
+            // not logged in
+            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
+        }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Helpers/JwtMiddleware.cs b/TomatenMusic Api/Auth/Helpers/JwtMiddleware.cs
new file mode 100644
index 0000000..f1102d9
--- /dev/null
+++ b/TomatenMusic Api/Auth/Helpers/JwtMiddleware.cs	
@@ -0,0 +1,58 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Text;
+using TomatenMusic_Api.Auth.Services;
+
+public class JwtMiddleware
+{
+    private readonly RequestDelegate _next;
+    private readonly AppSettings _appSettings;
+
+    public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
+    {
+        _next = next;
+        _appSettings = appSettings.Value;
+    }
+
+    public async Task Invoke(HttpContext context, IUserService userService)
+    {
+        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+
+        if (token != null)
+            attachUserToContext(context, userService, token);
+
+        await _next(context);
+    }
+
+    private void attachUserToContext(HttpContext context, IUserService userService, string token)
+    {
+        try
+        {
+            var tokenHandler = new JwtSecurityTokenHandler();
+            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
+            tokenHandler.ValidateToken(token, new TokenValidationParameters
+            {
+                ValidateIssuerSigningKey = true,
+                IssuerSigningKey = new SymmetricSecurityKey(key),
+                ValidateIssuer = false,
+                ValidateAudience = false,
+                // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
+                ClockSkew = TimeSpan.Zero
+            }, out SecurityToken validatedToken);
+
+            var jwtToken = (JwtSecurityToken)validatedToken;
+            var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
+
+            // attach user to context on successful jwt validation
+            context.Items["User"] = userService.GetById(userId);
+        }
+        catch
+        {
+            // do nothing if jwt validation fails
+            // user is not attached to context so request won't have access to secure routes
+        }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Models/AuthenticateRequest.cs b/TomatenMusic Api/Auth/Models/AuthenticateRequest.cs
new file mode 100644
index 0000000..7c9426d
--- /dev/null
+++ b/TomatenMusic Api/Auth/Models/AuthenticateRequest.cs	
@@ -0,0 +1,12 @@
+namespace TomatenMusic_Api.Auth.Models;
+
+using System.ComponentModel.DataAnnotations;
+
+public class AuthenticateRequest
+{
+    [Required]
+    public string Username { get; set; }
+
+    [Required]
+    public string Password { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Models/AuthenticateResponse.cs b/TomatenMusic Api/Auth/Models/AuthenticateResponse.cs
new file mode 100644
index 0000000..1817949
--- /dev/null
+++ b/TomatenMusic Api/Auth/Models/AuthenticateResponse.cs	
@@ -0,0 +1,22 @@
+namespace TomatenMusic_Api.Auth.Models;
+
+using TomatenMusic_Api.Auth.Entities;
+
+public class AuthenticateResponse
+{
+    public int Id { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+    public string Username { get; set; }
+    public string Token { get; set; }
+
+
+    public AuthenticateResponse(User user, string token)
+    {
+        Id = user.Id;
+        FirstName = user.FirstName;
+        LastName = user.LastName;
+        Username = user.Username;
+        Token = token;
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Auth/Services/UserService.cs b/TomatenMusic Api/Auth/Services/UserService.cs
new file mode 100644
index 0000000..f6ab8b8
--- /dev/null
+++ b/TomatenMusic Api/Auth/Services/UserService.cs	
@@ -0,0 +1,75 @@
+namespace TomatenMusic_Api.Auth.Services;
+
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using TomatenMusic_Api.Auth.Entities;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Models;
+
+public interface IUserService
+{
+    AuthenticateResponse Authenticate(AuthenticateRequest model);
+    IEnumerable<User> GetAll();
+    User GetById(int id);
+}
+
+public class UserService : IUserService
+{
+    // users hardcoded for simplicity, store in a db with hashed passwords in production applications
+    private List<User> _users = new List<User>
+    {
+        new User { Id = 1, FirstName = "Jannick", LastName = "Voss", Username = "Glowman", Password = "RX5GXstLLBvdt#_N" },
+        new User { Id = 2, FirstName = "Tim", LastName= "M�ller", Password= "SGWaldsolms9", Username = "Tueem"}
+
+    };
+
+    private readonly AppSettings _appSettings;
+
+    public UserService(IOptions<AppSettings> appSettings)
+    {
+        _appSettings = appSettings.Value;
+    }
+
+    public AuthenticateResponse Authenticate(AuthenticateRequest model)
+    {
+        var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);
+
+        // return null if user not found
+        if (user == null) return null;
+
+        // authentication successful so generate jwt token
+        var token = generateJwtToken(user);
+
+        return new AuthenticateResponse(user, token);
+    }
+
+    public IEnumerable<User> GetAll()
+    {
+        return _users;
+    }
+
+    public User GetById(int id)
+    {
+        return _users.FirstOrDefault(x => x.Id == id);
+    }
+
+    // helper methods
+
+    private string generateJwtToken(User user)
+    {
+        // generate token that is valid for 7 days
+        var tokenHandler = new JwtSecurityTokenHandler();
+        var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
+        var tokenDescriptor = new SecurityTokenDescriptor
+        {
+            Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
+            Expires = DateTime.UtcNow.AddDays(1),
+            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+        };
+        var token = tokenHandler.CreateToken(tokenDescriptor);
+        return tokenHandler.WriteToken(token);
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/Controllers/PlayerController.cs b/TomatenMusic Api/Controllers/PlayerController.cs
new file mode 100644
index 0000000..241f606
--- /dev/null
+++ b/TomatenMusic Api/Controllers/PlayerController.cs	
@@ -0,0 +1,92 @@
+using DSharpPlus.Entities;
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic;
+using TomatenMusic_Api;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Models;
+
+namespace TomatenMusic_Api.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class PlayerController : ControllerBase
+{
+
+
+	private readonly ILogger<PlayerController> _logger;
+	private readonly InProcessEventBus _eventBus;
+	private readonly TomatenMusicDataService _tomatenMusicDataService;
+
+	public PlayerController(
+		ILogger<PlayerController> logger,
+		InProcessEventBus eventBus, TomatenMusicDataService dataService)
+		
+	{
+		_logger = logger;
+		_eventBus = eventBus;
+		_tomatenMusicDataService = dataService;
+	}
+
+	[HttpGet("{guild_id}")]
+	public async Task<IActionResult> Get(ulong guild_Id)
+	{
+        Models.PlayerConnectionInfo response = await _tomatenMusicDataService.GetConnectionInfoAsync(guild_Id);
+
+		if (response == null)
+        {
+			return BadRequest("The Bot is not connected or the guild is unknown");
+        }
+
+		return Ok(response);
+	}
+	[HttpGet]
+	public async Task<IActionResult> Get()
+    {
+        List<Models.PlayerConnectionInfo> response = await _tomatenMusicDataService.GetAllGuildPlayersAsync();
+
+		if (response == null)
+        {
+			return BadRequest("An Error occured while parsing the Guilds, Guilds were Empty");
+        }
+
+		return Ok(response);
+    }
+
+	[HttpPost("connect")]
+	public async Task<IActionResult> PostConnection(ChannelConnectRequest request)
+	{
+		try
+		{
+			await _tomatenMusicDataService.GetGuildAsync(request.Guild_Id);
+		}catch (Exception ex)
+		{
+			return NotFound("That Guild was not found");
+		}
+
+
+		Boolean? playing = await _tomatenMusicDataService.IsPlayingAsync(request.Guild_Id);
+
+		DiscordChannel channel;
+
+		if (playing == true)
+			return BadRequest("The Bot is already playing");
+
+		if (await _tomatenMusicDataService.IsConnectedAsync(request.Guild_Id) == true)
+			return BadRequest("The Bot is already connected");
+
+		try
+        {
+			channel = await _tomatenMusicDataService.GetDiscordChannelAsync(request.Channel_Id);
+		}catch (Exception ex)
+        {
+			return NotFound("Channel was not Found");
+		}
+
+
+
+		_eventBus.OnConnectRequestEvent(new InProcessEventBus.ChannelConnectEventArgs(request.Guild_Id, channel));
+
+		return Ok();
+	}
+}
diff --git a/TomatenMusic Api/Models/BasicTrackInfo.cs b/TomatenMusic Api/Models/BasicTrackInfo.cs
new file mode 100644
index 0000000..dd245ac
--- /dev/null
+++ b/TomatenMusic Api/Models/BasicTrackInfo.cs	
@@ -0,0 +1,42 @@
+using Lavalink4NET.Player;
+using System.Text.Json.Serialization;
+using TomatenMusic.Music.Entitites;
+
+namespace TomatenMusic_Api.Models
+{
+    public class BasicTrackInfo
+    {
+        public string Name { get; set; }
+
+        public TrackPlatform Platform { get; set; }
+
+        public string YoutubeId { get; set; }
+
+        public string SpotifyId { get; set; }
+
+        public Uri URL { get; set; }
+
+        public BasicTrackInfo(LavalinkTrack track)
+        {
+            if (track == null)
+                return;
+            FullTrackContext ctx = (FullTrackContext)track.Context;
+
+            if (ctx == null)
+                return;
+
+            Name = track.Title;
+            Platform = ctx.SpotifyIdentifier == null ? TrackPlatform.YOUTUBE : TrackPlatform.SPOTIFY;
+            YoutubeId = track.Identifier;
+            SpotifyId = ctx.SpotifyIdentifier;
+            URL = ctx.YoutubeUri;
+        }
+    }
+
+    public enum TrackPlatform
+    {
+        YOUTUBE,
+        SPOTIFY,
+        FILE
+    }
+}
diff --git a/TomatenMusic Api/Models/ChannelConnectRequest.cs b/TomatenMusic Api/Models/ChannelConnectRequest.cs
new file mode 100644
index 0000000..0e90699
--- /dev/null
+++ b/TomatenMusic Api/Models/ChannelConnectRequest.cs	
@@ -0,0 +1,13 @@
+using DSharpPlus.Entities;
+using Emzi0767.Utilities;
+using Newtonsoft.Json;
+
+namespace TomatenMusic_Api.Models
+{
+    public class ChannelConnectRequest
+    {
+        public ulong Channel_Id { get; set; }
+        public ulong Guild_Id { get; set; }
+
+    }
+}
diff --git a/TomatenMusic Api/Models/PlayerConnectionInfo.cs b/TomatenMusic Api/Models/PlayerConnectionInfo.cs
new file mode 100644
index 0000000..7d113ed
--- /dev/null
+++ b/TomatenMusic Api/Models/PlayerConnectionInfo.cs	
@@ -0,0 +1,62 @@
+using DSharpPlus.Entities;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using TomatenMusic;
+using TomatenMusic.Music;
+
+namespace TomatenMusic_Api.Models
+{
+    public class PlayerConnectionInfo
+    {
+
+        public static async Task<PlayerConnectionInfo> Create(GuildPlayer player)
+        {
+            PlayerConnectionInfo response = new PlayerConnectionInfo();
+
+            response.PlaybackPosition = player.TrackPosition;
+            response.Channel_Id = (ulong)player.VoiceChannelId;
+            response.Guild_Id = player.GuildId;
+            response.Paused = player.State == PlayerState.Paused;
+            response.CurrentTrack = new BasicTrackInfo(player.CurrentTrack);
+            response.LoopType = player.PlayerQueue.LoopType;
+
+            response.Queue = player.PlayerQueue.Queue.ToList().ConvertAll(x => new BasicTrackInfo(x));
+            response.PlayedTracks = player.PlayerQueue.PlayedTracks.ToList().ConvertAll(x => new BasicTrackInfo(x));
+            response.State = player.State;
+
+            return response;
+        }
+
+        // Summary:
+        //     Gets the current playback position.
+        public TimeSpan PlaybackPosition
+        {
+            get;
+            internal set;
+        }
+        public PlayerState State { get; set; }
+        //
+        // Summary:
+        //     Gets the voice channel associated with this connection.
+        public ulong Channel_Id { get; set; }
+
+        //
+        // Summary:
+        //     Gets the guild associated with this connection.
+        public ulong Guild_Id {get; set; }
+
+        public bool Paused { get; set; }
+
+        public BasicTrackInfo CurrentTrack { get; set; }
+
+        public LoopType LoopType { get; set; }
+
+        public List<BasicTrackInfo> Queue { get; set; }
+
+        public List<BasicTrackInfo> PlayedTracks { get; set; }
+
+
+    }
+
+
+}
diff --git a/TomatenMusic Api/Program.cs b/TomatenMusic Api/Program.cs
new file mode 100644
index 0000000..7a496fc
--- /dev/null
+++ b/TomatenMusic Api/Program.cs	
@@ -0,0 +1,45 @@
+using TomatenMusic_Api;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+builder.Services.AddCors();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+// configure strongly typed settings object
+builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
+builder.Services.AddScoped<IUserService, UserService>();
+
+builder.Services.AddSingleton<InProcessEventBus>();
+
+builder.Services.AddSingleton<IHostedService, TomatenMusicService>();
+builder.Services.AddSingleton<TomatenMusicDataService>();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+
+	app.UseSwagger();
+	app.UseSwaggerUI();
+
+app.UseHttpsRedirection();
+app.UseWebSockets();
+
+app.UseCors(x => x
+    .AllowAnyOrigin()
+    .AllowAnyMethod()
+    .AllowAnyHeader());
+
+// custom jwt auth middleware
+app.UseMiddleware<JwtMiddleware>();
+
+app.MapControllers();
+
+
+app.Run();
diff --git a/TomatenMusic Api/Properties/launchSettings.json b/TomatenMusic Api/Properties/launchSettings.json
new file mode 100644
index 0000000..fb8b128
--- /dev/null
+++ b/TomatenMusic Api/Properties/launchSettings.json	
@@ -0,0 +1,31 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:46317",
+      "sslPort": 44369
+    }
+  },
+  "profiles": {
+    "TomatenMusic_Api": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "https://localhost:7210;http://localhost:5210",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
diff --git a/TomatenMusic Api/Services/EventBus.cs b/TomatenMusic Api/Services/EventBus.cs
new file mode 100644
index 0000000..9ec1f8a
--- /dev/null
+++ b/TomatenMusic Api/Services/EventBus.cs	
@@ -0,0 +1,30 @@
+using DSharpPlus.Entities;
+using Emzi0767.Utilities;
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic_Api.Models;
+
+namespace TomatenMusic_Api;
+
+public class InProcessEventBus
+{
+	public event AsyncEventHandler<InProcessEventBus, ChannelConnectEventArgs>? OnConnectRequest;
+
+	public void OnConnectRequestEvent(ChannelConnectEventArgs e)
+	{
+		_ = OnConnectRequest?.Invoke(this, e);
+	}
+
+	public class ChannelConnectEventArgs : AsyncEventArgs
+    {
+		public ulong Guild_Id { get; set; }
+
+		public DiscordChannel Channel { get; set; }
+
+        public ChannelConnectEventArgs(ulong guild_Id, DiscordChannel channel)
+        {
+			Guild_Id = guild_Id;
+			Channel = channel;
+        }
+	}
+}
+
diff --git a/TomatenMusic Api/Services/TomatenMusicDataService.cs b/TomatenMusic Api/Services/TomatenMusicDataService.cs
new file mode 100644
index 0000000..b0bef08
--- /dev/null
+++ b/TomatenMusic Api/Services/TomatenMusicDataService.cs	
@@ -0,0 +1,88 @@
+using TomatenMusic.Music;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using TomatenMusic_Api.Models;
+using Lavalink4NET.Player;
+using TomatenMusic;
+using Lavalink4NET;
+
+namespace TomatenMusic_Api
+{
+    public class TomatenMusicDataService : IHostedService
+    {
+        private ILogger<TomatenMusicDataService> _logger;
+        public IServiceProvider _serviceProvider { get; set; } = TomatenMusicBot.ServiceProvider;
+        public IAudioService _audioService { get; set; }
+        public TomatenMusicDataService(ILogger<TomatenMusicDataService> logger)
+        {
+            _logger = logger;
+            _audioService = _serviceProvider.GetRequiredService<IAudioService>();
+        }
+
+        public async Task<PlayerConnectionInfo> GetConnectionInfoAsync(ulong guild_id)
+        {
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(guild_id);
+            if (player == null)
+                return null;
+            return await PlayerConnectionInfo.Create(player);
+        }
+
+        public async Task<Boolean?> IsPlayingAsync(ulong guild_id)
+        {
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(guild_id);
+
+            if (player == null)
+                return false;
+            return player.State == PlayerState.Playing;
+        }
+        public async Task<Boolean?> IsConnectedAsync(ulong guild_id)
+        {
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(guild_id);
+
+            if (player == null)
+                return false;
+
+            return player.State != PlayerState.NotConnected;
+        }
+
+        public async Task<List<PlayerConnectionInfo>> GetAllGuildPlayersAsync()
+        {
+            List<PlayerConnectionInfo> list = new List<PlayerConnectionInfo>();
+            foreach (var guild in _audioService.GetPlayers<GuildPlayer>())
+            {
+                list.Add(await PlayerConnectionInfo.Create(guild));
+            }
+            if (list.Count == 0)
+                return null;              
+
+            return list;
+        }
+
+        public Task<DiscordChannel> GetDiscordChannelAsync(ulong channel_id)
+        {
+            DiscordClient client = _serviceProvider.GetRequiredService<DiscordClient>();
+
+            return client.GetChannelAsync(channel_id);
+        }
+
+        public Task<DiscordGuild> GetGuildAsync(ulong guild_id)
+        {
+            DiscordClient client = _serviceProvider.GetRequiredService<DiscordClient>();
+
+            return client.GetGuildAsync(guild_id);
+        }
+
+        public Task StartAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogInformation("TomatenMusicDataService starting...");
+            return Task.CompletedTask;
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogInformation("TomatenMusicDataService stopping...");
+            return Task.CompletedTask;
+
+        }
+    }
+}
diff --git a/TomatenMusic Api/Services/TomatenMusicService.cs b/TomatenMusic Api/Services/TomatenMusicService.cs
new file mode 100644
index 0000000..28b6f2c
--- /dev/null
+++ b/TomatenMusic Api/Services/TomatenMusicService.cs	
@@ -0,0 +1,54 @@
+using Lavalink4NET;
+using TomatenMusic;
+using TomatenMusic.Music;
+using TomatenMusic_Api.Models;
+using static TomatenMusic_Api.InProcessEventBus;
+
+namespace TomatenMusic_Api
+{
+    public class TomatenMusicService : IHostedService
+    {
+		private readonly InProcessEventBus _inProcessEventBus;
+		private readonly ILogger<TomatenMusicService> _logger;
+        public TomatenMusicBot _bot { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public TomatenMusicService(InProcessEventBus inProcessEventBus, ILogger<TomatenMusicService> logger)
+		{
+			_inProcessEventBus = inProcessEventBus;
+			_logger = logger;
+
+			Initialize();
+		}
+
+		private void Initialize()
+		{
+            _inProcessEventBus.OnConnectRequest += _inProcessEventBus_OnConnectRequest;
+		}
+
+        private async Task _inProcessEventBus_OnConnectRequest(InProcessEventBus sender, ChannelConnectEventArgs e)
+        {
+			_logger.LogInformation("Channel Connected!");
+			GuildPlayer player = await _audioService.JoinAsync<GuildPlayer>(e.Guild_Id, e.Channel.Id, true);
+        }
+
+        public async Task StartAsync(CancellationToken cancellationToken)
+		{
+			_logger.LogInformation("Starting service...");
+			_bot = new TomatenMusicBot();
+			await _bot.InitBotAsync();
+			_audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+			_logger.LogInformation("Service started!");
+
+		}
+
+		public async Task StopAsync(CancellationToken cancellationToken)
+		{
+			_logger.LogInformation("Shutting down service...");
+			await _bot.ShutdownBotAsync();
+			_logger.LogInformation("Service shut down!");
+
+		}
+	}
+}
+
diff --git a/TomatenMusic Api/TomatenMusic Api.csproj b/TomatenMusic Api/TomatenMusic Api.csproj
new file mode 100644
index 0000000..1d0c4f1
--- /dev/null
+++ b/TomatenMusic Api/TomatenMusic Api.csproj	
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <RootNamespace>TomatenMusic_Api</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Remove="config.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="config.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.WebSockets" Version="0.2.3.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+	<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
+	<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\TomatenMusicCore\TomatenMusicCore.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="Auth\" />
+  </ItemGroup>
+
+</Project>
diff --git a/TomatenMusic Api/appsettings.Development.json b/TomatenMusic Api/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/TomatenMusic Api/appsettings.Development.json	
@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
diff --git a/TomatenMusic Api/appsettings.json b/TomatenMusic Api/appsettings.json
new file mode 100644
index 0000000..a618725
--- /dev/null
+++ b/TomatenMusic Api/appsettings.json	
@@ -0,0 +1,11 @@
+{
+  "AppSettings": {
+    "Secret": "WWT9uwYzMkhOnUrZD7CSeT9forbwpbci"
+  },
+    "Logging": {
+      "LogLevel": {
+        "Default": "Debug",
+        "Microsoft.AspNetCore": "Debug"
+      }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic Api/config.json b/TomatenMusic Api/config.json
new file mode 100644
index 0000000..40eb8ec
--- /dev/null
+++ b/TomatenMusic Api/config.json	
@@ -0,0 +1,7 @@
+{
+  "TOKEN": "ODQwNjQ5NjU1MTAwMjQzOTY4.YJbSAA.jwzaw5N-tAUeiEdvE969wfBAU7o",
+  "LavaLinkPassword": "SGWaldsolms9",
+  "SpotifyClientId": "14b77fa47f2f492db58cbdca8f1e5d9c",
+  "SpotifyClientSecret": "c247625f0cfe4b72a1faa01b7c5b8eea",
+  "YoutubeApiKey": "AIzaSyBIcTl9JQ9jF412mX0Wfp_3Y-4a-V0SASQ"
+}
\ No newline at end of file
diff --git a/TomatenMusic V2.sln b/TomatenMusic V2.sln
new file mode 100644
index 0000000..5141398
--- /dev/null
+++ b/TomatenMusic V2.sln	
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32228.430
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusic Api", "TomatenMusic Api\TomatenMusic Api.csproj", "{DC429A00-E2CC-4E66-A3B9-AE5F6B37E93A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusicCore", "TomatenMusicCore\TomatenMusicCore.csproj", "{54481E45-9FE3-4CF3-9CE9-489B678AE472}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{DC429A00-E2CC-4E66-A3B9-AE5F6B37E93A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DC429A00-E2CC-4E66-A3B9-AE5F6B37E93A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DC429A00-E2CC-4E66-A3B9-AE5F6B37E93A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DC429A00-E2CC-4E66-A3B9-AE5F6B37E93A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{54481E45-9FE3-4CF3-9CE9-489B678AE472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{54481E45-9FE3-4CF3-9CE9-489B678AE472}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{54481E45-9FE3-4CF3-9CE9-489B678AE472}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{54481E45-9FE3-4CF3-9CE9-489B678AE472}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {9ED3DD11-344B-4EB0-99E9-ED348676163A}
+	EndGlobalSection
+EndGlobal
diff --git a/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs b/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
new file mode 100644
index 0000000..7ab5d9f
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus;
+using TomatenMusic.Music;
+
+namespace TomatenMusic.Commands.Checks
+{
+    public class OnlyGuildCheck : SlashCheckBaseAttribute
+    {
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+            if (ctx.Guild == null)
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("This Command is only available on Guilds.").AsEphemeral(true));
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs b/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
new file mode 100644
index 0000000..8ff6719
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.EventArgs;
+using DSharpPlus;
+using TomatenMusic.Music;
+using Emzi0767.Utilities;
+using Lavalink4NET;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace TomatenMusic.Commands.Checks
+{
+    public class UserInMusicChannelCheck : SlashCheckBaseAttribute
+    {
+        public bool _passIfNull { get; set; }
+        public UserInMusicChannelCheck(bool passIfNull = false)
+        {
+            _passIfNull = passIfNull;
+        }
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+            IAudioService audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+
+            GuildPlayer player = audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            bool allowed;
+            //TODO
+            if (player != null)
+            {
+                allowed = ctx.Member.VoiceState.Channel != null && ctx.Member.VoiceState.Channel.Id == player.VoiceChannelId;
+            }
+            else
+                allowed = _passIfNull;
+
+            if (!allowed)
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("❌ Please connect to the Bots Channel to use this Command").AsEphemeral(true));
+            return allowed;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs b/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
new file mode 100644
index 0000000..e8c0369
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus;
+using TomatenMusic.Music;
+
+namespace TomatenMusic.Commands.Checks
+{
+    class UserInVoiceChannelCheck : SlashCheckBaseAttribute
+    {
+
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+
+            if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null)
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("You are not in a Voice Channel.").AsEphemeral(true));
+                return false;
+            }
+
+            return true;
+            
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/MusicCommands.cs b/TomatenMusicCore/Commands/MusicCommands.cs
new file mode 100644
index 0000000..da39d31
--- /dev/null
+++ b/TomatenMusicCore/Commands/MusicCommands.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Commands.Checks;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Prompt.Implementation;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Commands
+{
+    public class MusicCommands : ApplicationCommandModule
+    {
+        public IAudioService _audioService { get; set; }
+        public ILogger<MusicCommands> _logger { get; set; }
+        public TrackProvider _trackProvider { get; set; }
+
+        public MusicCommands(IAudioService audioService, ILogger<MusicCommands> logger, TrackProvider trackProvider)
+        {
+            _audioService = audioService;
+            _logger = logger;
+            _trackProvider = trackProvider;
+        }
+
+        [SlashCommand("stop", "Stops the current Playback and clears the Queue")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task StopCommand(InteractionContext ctx)
+        {
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            try
+            {
+                await player.DisconnectAsync();
+            }catch (Exception ex)
+            {
+                await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder
+                {
+                    Content = $"❌ An Error occured : ``{ex.Message}``",
+                    IsEphemeral = true
+                });
+                return;
+            }
+            await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder
+            {
+                Content = $"✔️ The Bot was stopped successfully",
+                IsEphemeral = true
+            });
+
+        }
+
+
+        [SlashCommand("skip", "Skips the current song and plays the next one in the queue")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task SkipCommand(InteractionContext ctx)
+        {
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+            LavalinkTrack oldTrack = player.CurrentTrack;
+            try
+            {
+                await player.SkipAsync();
+            }
+            catch (Exception e) 
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"⛔ Could not Skip Song, Queue Empty!").AsEphemeral(true));
+                return;
+            }
+
+            _ = ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"Skipped From Song ``{oldTrack.Title}`` To Song:")
+                .AddEmbed(Common.AsEmbed(player.CurrentTrack, loopType: player.PlayerQueue.LoopType)).AsEphemeral(true));
+        }
+
+        [SlashCommand("fav", "Shows the favorite Song Panel")]
+        [OnlyGuildCheck]
+        public async Task FavCommand(InteractionContext ctx)
+        {
+            
+        }
+
+        [SlashCommand("search", "Searches for a specific query")]
+        [OnlyGuildCheck]
+        public async Task SearchCommand(InteractionContext ctx, [Option("query", "The Search Query")] string query)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            MusicActionResponse response;
+            try
+            {
+                response = await _trackProvider.SearchAsync(query, true);
+            }catch (Exception e)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ Search failed: ``{e.Message}``"));
+                return;
+            }
+
+            var prompt = new SongSelectorPrompt($"Search results for {query}", response.Tracks);
+                prompt.ConfirmCallback = async (tracks) =>
+                {
+                    var selectPrompt = new SongListActionPrompt(tracks, ctx.Member, prompt);
+                    await selectPrompt.UseAsync(prompt.Interaction, prompt.Message);
+                };
+
+                await prompt.UseAsync(ctx.Interaction, await ctx.GetOriginalResponseAsync());
+        }
+
+        [SlashCommand("time", "Sets the playing position of the current Song.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task TimeCommand(InteractionContext ctx, [Option("time", "The time formatted like this: Hours: 1h, Minutes: 1m, Seconds 1s")] string time)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+            TimeSpan timeSpan;
+
+            try
+            {
+                timeSpan = TimeSpan.Parse(time);
+            }
+            catch (Exception e)
+            {
+                try
+                {
+                    timeSpan = Common.ToTimeSpan(time);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ An Error occured when parsing your input."));
+                    return;
+                }
+            }
+
+            try
+            {
+                await player.SeekPositionAsync(timeSpan);
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An Error occured while Seeking the Track: ``{ex.Message}``"));
+                return;
+            }
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"✔️ You successfully set the Song to ``{Common.GetTimestamp(timeSpan)}``."));
+        }
+
+        [SlashCommand("pause", "Pauses or Resumes the current Song.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task PauseCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            try
+            {
+                await player.TogglePauseAsync();
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An Error occured changing the pause state of the Song: ``{ex.Message}``"));
+                return;
+            }
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"✔️ You {(player.State == PlayerState.Paused ? "successfully paused the Track" : "successfully resumed the Track")}"));
+
+        }
+
+        [SlashCommand("shuffle", "Shuffles the Queue.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task ShuffleCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            try
+            {
+                await player.ShuffleAsync();
+            }
+            catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An error occured while shuffling the Queue: ``{ex.Message}``"));
+                return;
+            }
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"😀 You shuffled the Queue."));
+
+        }
+
+        [SlashCommand("loop", "Sets the loop type of the current player.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task LoopCommand(InteractionContext ctx, [Option("Looptype", "The loop type which the player should be set to")] LoopType type)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            
+            try
+            {
+                await player.SetLoopAsync(type);
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An error occured while change the Queue Loop: ``{ex.Message}``"));
+
+            }
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"😀 You have set the Loop to ``{type.ToString()}``."));
+
+        }
+
+        [SlashCommand("autoplay", "Enables/Disables Autoplay")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task AutoplayCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+            player.Autoplay = !player.Autoplay;
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"You have set Autoplay to ``{(player.Autoplay ? "Enabled" : "Disabled")}``"));
+
+        }
+
+        [SlashCommand("queue", "Shows the Queue")]
+        [OnlyGuildCheck]
+        public async Task QueueCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+            if (player == null)
+            {
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ ``Theres currently nothing playing``"));
+                return;
+            }
+
+            LavalinkTrack track = player.CurrentTrack;
+
+            if (track == null)
+            {
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ ``Theres currently nothing playing``"));
+                return;
+            }
+
+            QueuePrompt prompt = new QueuePrompt(player);
+
+            _ = prompt.UseAsync(ctx.Interaction, await ctx.GetOriginalResponseAsync());
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Commands/PlayCommandGroup.cs b/TomatenMusicCore/Commands/PlayCommandGroup.cs
new file mode 100644
index 0000000..210a988
--- /dev/null
+++ b/TomatenMusicCore/Commands/PlayCommandGroup.cs
@@ -0,0 +1,294 @@
+using DSharpPlus.Entities;
+using DSharpPlus.SlashCommands;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Commands.Checks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Util;
+
+namespace TomatenMusic.Commands
+{
+
+    [SlashCommandGroup("play", "Play a song.")]
+    public class PlayCommandGroup : ApplicationCommandModule
+    {
+
+        [SlashCommandGroup("now", "Plays the specified Song now and prepends the Current song to the Queue.")]
+        public class PlayNowGroup : ApplicationCommandModule
+        {
+            public IAudioService _audioService { get; set; }
+            public ILogger<PlayCommandGroup> _logger { get; set; }
+            public TrackProvider _trackProvider { get; set; }
+
+            public PlayNowGroup(IAudioService audioService, ILogger<PlayCommandGroup> logger, TrackProvider trackProvider)
+            {
+                _audioService = audioService;
+                _logger = logger;
+                _trackProvider = trackProvider;
+            }
+
+            [SlashCommand("query", "Play a song with its youtube/spotify link. (or youtube search)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayQueryCommand(InteractionContext ctx, [Option("query", "The song search query.")] string query)
+            {
+                await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+                
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(query);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your query: ``{ex.Message}``")
+                      );
+                    return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                        return;
+                    }
+                }
+
+                try
+                {
+                    if (response.isPlaylist)
+                    {
+                        LavalinkPlaylist playlist = response.Playlist;
+                        await player.PlayPlaylistNowAsync(playlist);
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
+                        Common.AsEmbed(playlist)
+                        ));
+
+                    }
+                    else
+                    {
+                        LavalinkTrack track = response.Track;
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Playing Now")
+                            .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, 0)));
+
+                        await player.PlayNowAsync(response.Track);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while playing your Query: ``{ex.Message}``")
+                      );
+                    return;
+                }
+            }
+
+            [SlashCommand("file", "Play a song file. (mp3/mp4)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayFileCommand(InteractionContext ctx, [Option("File", "The File that should be played.")] DiscordAttachment file)
+            {
+
+                await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(new Uri(file.Url));
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your file: ``{ex.Message}``")
+                      );
+                    return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                        return;
+                    }
+                }
+
+                LavalinkTrack track = response.Track;
+
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Playing Now")
+                    .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, 0)));
+
+                await player.PlayNowAsync(response.Track);
+            }
+        }
+
+        [SlashCommandGroup("queue", "Queues or plays the Song")]
+        public class PlayQueueGroup : ApplicationCommandModule
+        {
+            public IAudioService _audioService { get; set; }
+            public ILogger<PlayCommandGroup> _logger { get; set; }
+            public TrackProvider _trackProvider { get; set; }
+
+            public PlayQueueGroup(IAudioService audioService, ILogger<PlayCommandGroup> logger, TrackProvider trackProvider)
+            {
+                _audioService = audioService;
+                _logger = logger;
+                _trackProvider = trackProvider;
+            }
+
+
+            [SlashCommand("query", "Play a song with its youtube/spotify link. (or youtube search)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayQueryCommand(InteractionContext ctx, [Option("query", "The song search query.")] string query)
+            {
+                await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(query);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your query: ``{ex.Message}``")
+                      );
+                    return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                        return;
+                    }
+                }
+
+                try
+                {
+                    if (response.isPlaylist)
+                    {
+                        LavalinkPlaylist playlist = response.Playlist;
+                        await player.PlayPlaylistAsync(playlist);
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
+                        Common.AsEmbed(playlist)
+                        ));
+
+                    }
+                    else
+                    {
+                        LavalinkTrack track = response.Track;
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(player.State == PlayerState.NotPlaying ? "Now Playing:" : "Added to Queue")
+                            .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, player.State == PlayerState.NotPlaying ? 0 : player.PlayerQueue.Queue.Count + 1)));
+
+                        await player.PlayAsync(response.Track);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while playing your Track: ``{ex.Message}``")
+                      );
+                    return;
+                }
+            }
+
+            [SlashCommand("file", "Play a song file. (mp3/mp4)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayFileCommand(InteractionContext ctx, [Option("File", "The File that should be played.")] DiscordAttachment file)
+            {
+
+                await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(new Uri(file.Url));
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your file: ``{ex.Message}``")
+                      );
+                    return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                        return;
+                    }
+                }
+
+                LavalinkTrack track = response.Track;
+
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(player.State == PlayerState.NotPlaying ? "Now Playing:" : "Added to Queue")
+                    .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, player.State == PlayerState.NotPlaying ? 0 : player.PlayerQueue.Queue.Count + 1)));
+
+                await player.PlayAsync(response.Track);
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/FullTrackContext.cs b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
new file mode 100644
index 0000000..44b5ee9
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Services;
+using System.Linq;
+using SpotifyAPI.Web;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class FullTrackContext
+    {
+        public bool IsFile { get; set; }
+        public string YoutubeDescription { get; set; }
+        public Uri YoutubeUri { get; set; }
+        public IEnumerable<string> YoutubeTags { get; set; }
+        public ulong YoutubeViews { get; set; }
+        public ulong YoutubeLikes { get; set; }
+        public Uri YoutubeThumbnail { get; set; }
+        public DateTime YoutubeUploadDate { get; set; }
+        //
+        // Summary:
+        //     Gets the author of the track.
+        public Uri YoutubeAuthorThumbnail { get; set; }
+        public ulong YoutubeAuthorSubs { get; set; }
+        public Uri YoutubeAuthorUri { get; set; }
+        public ulong? YoutubeCommentCount { get; set; }
+        public string SpotifyIdentifier { get; set; }
+        public SimpleAlbum SpotifyAlbum { get; set; }
+        public List<SimpleArtist> SpotifyArtists { get; set; }
+        public int SpotifyPopularity { get; set; }
+        public Uri SpotifyUri { get; set; }
+
+        public static async Task<LavalinkTrack> PopulateAsync(LavalinkTrack track, string spotifyIdentifier = null)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            if (context == null)
+                context = new FullTrackContext();
+
+            var spotifyService = TomatenMusicBot.ServiceProvider.GetRequiredService<ISpotifyService>();
+            var youtubeService = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+            context.SpotifyIdentifier = spotifyIdentifier;
+            context.YoutubeUri = new Uri($"https://youtu.be/{track.TrackIdentifier}");
+            track.Context = context;
+            Console.WriteLine(context);
+            await youtubeService.PopulateTrackInfoAsync(track);
+            await spotifyService.PopulateTrackAsync(track);
+            
+            return track;
+        }
+
+        public static async Task<IEnumerable<LavalinkTrack>> PopulateTracksAsync(IEnumerable<LavalinkTrack> tracks)
+        {
+            foreach (var trackItem in tracks)
+            {
+                await PopulateAsync(trackItem);
+            }
+
+            return tracks;
+        }
+
+
+        
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/LavalinkPlaylist.cs b/TomatenMusicCore/Music/Entitites/LavalinkPlaylist.cs
new file mode 100644
index 0000000..33eb033
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/LavalinkPlaylist.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using TomatenMusic.Util;
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public interface LavalinkPlaylist
+    {
+        public string Name { get; }
+        public IEnumerable<LavalinkTrack> Tracks { get; }
+        public Uri Url { get; }
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public string Identifier { get; }
+        public Uri AuthorThumbnail { get; set; }
+
+        public TimeSpan GetLength()
+        {
+            TimeSpan timeSpan = TimeSpan.FromTicks(0);
+
+            foreach (var track in Tracks)
+            {
+                timeSpan = timeSpan.Add(track.Duration);
+            }
+
+            return timeSpan;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
new file mode 100644
index 0000000..ed4745d
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
@@ -0,0 +1,29 @@
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class SpotifyPlaylist : LavalinkPlaylist
+    {
+        public string Name { get; }
+        public IEnumerable<LavalinkTrack> Tracks { get; }
+        public Uri Url { get; set; }
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public int Followers { get; set; }
+        public string Identifier { get; }
+        public Uri AuthorThumbnail { get; set; }
+
+
+        public SpotifyPlaylist(string name, string id, IEnumerable<LavalinkTrack> tracks, Uri uri)
+        {
+            Name = name;
+            Identifier = id;
+            Tracks = tracks;
+            Url = uri;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
new file mode 100644
index 0000000..2a35419
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using Google.Apis.YouTube.v3.Data;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class YoutubePlaylist : LavalinkPlaylist
+    {
+        public string Name { get; }
+
+        public IEnumerable<LavalinkTrack> Tracks { get; }
+
+        public int TrackCount { get; }
+
+        public Uri Url { get; }
+
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public Uri Thumbnail { get; set; }
+        public DateTime CreationTime { get; set; }
+        public string Identifier { get; }
+        public Playlist YoutubeItem { get; set; }
+        public Uri AuthorThumbnail { get; set; }
+
+        public YoutubePlaylist(string name, IEnumerable<LavalinkTrack> tracks, Uri uri)
+        {
+            Identifier = uri.ToString().Replace("https://www.youtube.com/playlist?list=", "");
+            Name = name;
+            Tracks = tracks;
+            Url = uri;
+            TrackCount = tracks.Count();
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
new file mode 100644
index 0000000..9942bd1
--- /dev/null
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -0,0 +1,333 @@
+using DSharpPlus;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using System.Linq;
+using TomatenMusic.Music.Entitites;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Services;
+using TomatenMusic.Prompt.Implementation;
+using Lavalink4NET.Player;
+using Lavalink4NET.Events;
+using Lavalink4NET;
+using Lavalink4NET.Rest;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET.Decoding;
+
+namespace TomatenMusic.Music
+{
+    public class GuildPlayer : LavalinkPlayer
+    {
+
+        ILogger<GuildPlayer> _logger { get; set; }
+        public PlayerQueue PlayerQueue { get;} = new PlayerQueue();
+        public DiscordClient _client { get; set; }
+        public ISpotifyService _spotify { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public bool Autoplay { get; set; } = false;
+
+        public GuildPlayer()
+        {
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            _logger = serviceProvider.GetRequiredService<ILogger<GuildPlayer>>();
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard(GuildId);
+
+            _spotify = serviceProvider.GetRequiredService<ISpotifyService>();
+            _audioService = serviceProvider.GetRequiredService<IAudioService>();
+        }
+
+        public async override Task PlayAsync(LavalinkTrack track, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+
+            EnsureConnected();
+            EnsureNotDestroyed();
+
+            if (State == PlayerState.NotPlaying)
+            {
+                PlayerQueue.LastTrack = track;
+                await base.PlayAsync(track, startTime, endTime, noReplace);
+                _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);
+            }else
+                PlayerQueue.QueueTrack(track);
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayNowAsync(LavalinkTrack track, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+
+            EnsureConnected();
+            EnsureNotDestroyed();
+
+            if (!withoutQueuePrepend)
+                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));
+
+            PlayerQueue.LastTrack = track;
+            await base.PlayAsync(track, startTime, endTime);
+            _logger.LogInformation("Started playing Track {0} now on Guild {1}", track.Title, (await GetGuildAsync()).Name);
+
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayTracksAsync(List<LavalinkTrack> tracks)
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            _logger.LogInformation("Started playing TrackList {0} on Guild {1}", tracks.ToString(), (await GetGuildAsync()).Name);
+
+            await PlayerQueue.QueueTracksAsync(tracks);
+
+            if (State == PlayerState.NotPlaying)
+            {
+                LavalinkTrack nextTrack = PlayerQueue.NextTrack().Track;
+                await base.PlayAsync(nextTrack);
+            }
+            QueuePrompt.UpdateFor(GuildId);
+        }
+        public async Task PlayTracksNowAsync(IEnumerable<LavalinkTrack> tracks)
+        {
+
+            EnsureConnected();
+            EnsureNotDestroyed();
+            Queue<LavalinkTrack> reversedTracks = new Queue<LavalinkTrack>(tracks);
+
+            LavalinkTrack track = reversedTracks.Dequeue();
+            PlayerQueue.LastTrack = track;
+            await base.PlayAsync(track);
+            _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);
+
+            reversedTracks.Reverse();
+
+            foreach (var item in reversedTracks)
+            {
+                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));
+            }
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayPlaylistAsync(LavalinkPlaylist playlist)
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            _logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Name, (await GetGuildAsync()).Name);
+
+            await PlayerQueue.QueuePlaylistAsync(playlist);
+
+
+            if (State == PlayerState.NotPlaying)
+            {
+                LavalinkTrack nextTrack = PlayerQueue.NextTrack().Track;
+                await base.PlayAsync(nextTrack);
+            }
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayPlaylistNowAsync(LavalinkPlaylist playlist)
+        {
+
+            EnsureConnected();
+            EnsureNotDestroyed();
+            if (!PlayerQueue.Queue.Any())
+                PlayerQueue.CurrentPlaylist = playlist;
+
+            Queue<LavalinkTrack> reversedTracks = new Queue<LavalinkTrack>(playlist.Tracks);
+
+            LavalinkTrack track = reversedTracks.Dequeue();
+            PlayerQueue.LastTrack = track;
+            await base.PlayAsync(track);
+            _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);
+
+            reversedTracks.Reverse();
+
+            foreach (var item in reversedTracks)
+            {
+                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));
+            }
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task RewindAsync()
+        {
+            MusicActionResponse response = PlayerQueue.Rewind();
+
+            _logger.LogInformation($"Rewinded Track {CurrentTrack.Title} for Track {response.Track.Title}");
+            await base.PlayAsync(response.Track);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task SkipAsync()
+        {
+            MusicActionResponse response = PlayerQueue.NextTrack(true);
+
+            _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Track {response.Track.Title}");
+            await base.PlayAsync(response.Track);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task TogglePauseAsync()
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant pause Song! Nothing is Playing.");
+
+
+            if (State == PlayerState.Paused)
+                await ResumeAsync();
+            else
+                await PauseAsync();
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task SetLoopAsync(LoopType type)
+        {
+
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
+
+            _ = PlayerQueue.SetLoopAsync(type);
+            QueuePrompt.UpdateFor(GuildId);
+
+        }
+
+        public async Task ShuffleAsync()
+        {
+
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            await PlayerQueue.ShuffleAsync();
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+        public async override Task ConnectAsync(ulong voiceChannelId, bool selfDeaf = true, bool selfMute = false)
+        {
+            EnsureNotDestroyed();
+
+            DiscordChannel channel = await _client.GetChannelAsync(voiceChannelId);
+
+            if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage) throw new ArgumentException("The channel Id provided was not a voice channel");
+
+            if (State != PlayerState.NotConnected)
+                throw new InvalidOperationException("The Bot is already connected.");
+
+            await base.ConnectAsync(voiceChannelId, selfDeaf, selfMute);
+
+            if (channel.Type == ChannelType.Stage)
+            {
+                DiscordStageInstance stageInstance = await channel.GetStageInstanceAsync();
+
+                if (stageInstance == null)
+                    stageInstance = await channel.CreateStageInstanceAsync("Music");
+                await stageInstance.Channel.UpdateCurrentUserVoiceStateAsync(false);
+            }
+
+            _logger.LogInformation("Connected to Channel {0} on Guild {1}", channel.Name, channel.Guild.Name);
+        }
+        public override Task DisconnectAsync()
+        {
+            _logger.LogInformation("Disconnected from Channel {0} on Guild {1}", VoiceChannelId, GuildId);
+
+            QueuePrompt.InvalidateFor(GuildId);
+            return base.DisconnectAsync();
+        }
+
+        public override async Task SeekPositionAsync(TimeSpan timeSpan)
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
+
+            if (timeSpan.CompareTo(CurrentTrack.Duration) == 1) throw new ArgumentException("Please specify a TimeSpan shorter than the Track");
+
+            await base.SeekPositionAsync(timeSpan);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+        public async override Task OnTrackEndAsync(TrackEndEventArgs eventArgs)
+        {
+            DisconnectOnStop = false;
+            YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+            var oldTrack = CurrentTrack;
+
+            if (eventArgs.Reason != TrackEndReason.Finished)
+                return;
+
+            if (eventArgs.MayStartNext)
+            {
+                try
+                {
+                    MusicActionResponse response = PlayerQueue.NextTrack();
+                    _ = PlayNowAsync(response.Track, withoutQueuePrepend: true);
+                }
+                catch (Exception ex)
+                {
+                    if (!Autoplay)
+                    {
+                        _logger.LogInformation("Track has ended and Queue was Empty... Idling");
+                        await base.OnTrackEndAsync(eventArgs);
+                        return;
+                    }
+
+                    LavalinkTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier);
+                    _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
+                    await PlayNowAsync(newTrack, withoutQueuePrepend: true);
+
+                    /*                   try
+                                       {
+                                           LavalinkTrack track = await youtube.GetRelatedTrackAsync(eventArgs.TrackIdentifier);
+                                           _logger.LogInformation($"Autoplaying for track {eventArgs.TrackIdentifier} with Track {track.TrackIdentifier}");
+                                           await PlayAsync(track);
+                                       }
+                                       catch (Exception ex2)
+                                       {
+                                           await base.OnTrackEndAsync(eventArgs);
+                                       }*/
+                }
+            }
+
+            
+        }
+
+        public async Task<DiscordChannel> GetChannelAsync()
+        {
+            EnsureConnected();
+            EnsureNotDestroyed();
+            DiscordGuild guild = await GetGuildAsync();
+
+            return guild.GetChannel((ulong) VoiceChannelId);
+        }
+
+        public async Task<DiscordGuild> GetGuildAsync()
+        {
+            return await _client.GetGuildAsync(GuildId);
+        }
+        public async Task<bool> AreActionsAllowedAsync(DiscordMember member)
+        {
+            if (member.VoiceState == null || member.VoiceState.Channel == null)
+            {
+                return false;
+            }
+
+            if (await GetChannelAsync() != null && await GetChannelAsync() != member.VoiceState.Channel)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Music/LoopType.cs b/TomatenMusicCore/Music/LoopType.cs
new file mode 100644
index 0000000..f44ac43
--- /dev/null
+++ b/TomatenMusicCore/Music/LoopType.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus.SlashCommands;
+
+namespace TomatenMusic.Music
+{
+    public enum LoopType
+    {
+        [ChoiceName("Track")]
+        TRACK,
+        [ChoiceName("Queue")]
+        QUEUE,
+        [ChoiceName("None")]
+        NONE
+    }
+}
diff --git a/TomatenMusicCore/Music/MusicActionResponse.cs b/TomatenMusicCore/Music/MusicActionResponse.cs
new file mode 100644
index 0000000..2c58b47
--- /dev/null
+++ b/TomatenMusicCore/Music/MusicActionResponse.cs
@@ -0,0 +1,23 @@
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Music.Entitites;
+
+namespace TomatenMusic.Music
+{
+    public class MusicActionResponse
+    {
+        public LavalinkPlaylist Playlist { get; }
+        public LavalinkTrack Track { get; }
+        public IEnumerable<LavalinkTrack> Tracks { get; }
+        public bool isPlaylist { get; }
+        public MusicActionResponse(LavalinkTrack track = null, LavalinkPlaylist playlist = null, IEnumerable<LavalinkTrack> tracks = null)
+        {
+            Playlist = playlist;
+            Track = track;
+            isPlaylist = playlist != null;
+            Tracks = tracks;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
new file mode 100644
index 0000000..ce90a26
--- /dev/null
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using TomatenMusic.Music.Entitites;
+using System.Threading.Tasks;
+using System.Linq;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace TomatenMusic.Music
+{
+    public class PlayerQueue
+    {
+        
+        public Queue<LavalinkTrack> Queue { get; set; } = new Queue<LavalinkTrack>();
+        public Queue<LavalinkTrack> PlayedTracks { get; set; } = new Queue<LavalinkTrack>();
+        public ILogger<PlayerQueue> _logger { get; set; } = TomatenMusicBot.ServiceProvider.GetRequiredService<ILogger<PlayerQueue>>();
+        public LavalinkPlaylist CurrentPlaylist { get; set; }
+
+        public LoopType LoopType { get; private set; } = LoopType.NONE;
+
+        public LavalinkTrack LastTrack { get; set; }
+
+        public List<LavalinkTrack> QueueLoopList { get; private set; }
+
+        public void QueueTrack(LavalinkTrack track)
+        {
+            CurrentPlaylist = null;
+            Queue.Enqueue(track);
+            _logger.LogInformation("Queued Track {0}", track.Title);
+
+            if (LoopType == LoopType.QUEUE)
+                QueueLoopList.Add(track);
+        }
+
+        public Task QueuePlaylistAsync(LavalinkPlaylist playlist)
+        {
+            return Task.Run(() =>
+            {
+                if (CurrentPlaylist == null)
+                    CurrentPlaylist = playlist;
+
+                _logger.LogInformation("Queued Playlist {0}", playlist.Name);
+                foreach (LavalinkTrack track in playlist.Tracks)
+                {
+                    Queue.Enqueue(track);
+                }
+
+
+
+                if (LoopType == LoopType.QUEUE)
+                    QueueLoopList.AddRange(playlist.Tracks);
+            });
+
+        }
+
+        public Task QueueTracksAsync(List<LavalinkTrack> tracks)
+        {
+            return Task.Run(() =>
+            {
+                CurrentPlaylist = null;
+                _logger.LogInformation("Queued TrackList {0}", tracks.ToString());
+                foreach (LavalinkTrack track in tracks)
+                {
+                    Queue.Enqueue(track);
+                }
+                if (LoopType == LoopType.QUEUE)
+                    QueueLoopList.AddRange(tracks);
+            });
+
+        }
+
+        public void Clear()
+        {
+            Queue.Clear();
+            PlayedTracks.Clear();
+        }
+
+        public void RemoveAt(int index)
+        {
+            if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
+            List<LavalinkTrack> tracks = Queue.ToList();
+            tracks.RemoveAt(index);
+            Queue = new Queue<LavalinkTrack>(tracks);
+
+        }
+
+        public MusicActionResponse NextTrack(bool ignoreLoop = false)
+        {
+            if (LastTrack != null)
+                PlayedTracks = new Queue<LavalinkTrack>(PlayedTracks.Prepend(LastTrack));
+
+            switch (LoopType)
+            {
+                case LoopType.NONE:
+                    if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
+
+                    LastTrack = Queue.Dequeue();
+
+                    return new MusicActionResponse(LastTrack);
+                case LoopType.TRACK:
+                    if (ignoreLoop)
+                    {
+                        LastTrack = Queue.Dequeue();
+                        return new MusicActionResponse(LastTrack);
+                    }
+
+                    return new MusicActionResponse(LastTrack);
+                case LoopType.QUEUE:
+                    if (!Queue.Any())
+                    {
+                        if (CurrentPlaylist != null)
+                            Queue = new Queue<LavalinkTrack>(CurrentPlaylist.Tracks);
+                        else
+                            Queue = new Queue<LavalinkTrack>(QueueLoopList);
+                    }
+
+                    LastTrack = Queue.Dequeue();
+
+                    return new MusicActionResponse(LastTrack);
+                default:
+                    throw new NullReferenceException("LoopType was null");
+            }
+        }
+
+        public MusicActionResponse Rewind()
+        {
+
+            if (!PlayedTracks.Any()) throw new InvalidOperationException("There are no songs that could be rewinded to yet.");
+
+            Queue = new Queue<LavalinkTrack>(Queue.Prepend(LastTrack));
+            LastTrack = PlayedTracks.Dequeue();
+
+            return new MusicActionResponse(LastTrack);
+        }
+
+        public Task ShuffleAsync()
+        {
+            if (Queue.Count == 0) throw new InvalidOperationException("Queue is Empty");
+
+            List<LavalinkTrack> tracks = new List<LavalinkTrack>(Queue);
+            tracks.Shuffle();
+            Queue = new Queue<LavalinkTrack>(tracks);
+            return Task.CompletedTask;
+        }
+
+        public async Task SetLoopAsync(LoopType type)
+        {
+            LoopType = type;
+
+            if (type == LoopType.QUEUE)
+            {
+                QueueLoopList = new List<LavalinkTrack>(Queue);
+                QueueLoopList.Add(LastTrack);
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/TrackProvider.cs b/TomatenMusicCore/Music/TrackProvider.cs
new file mode 100644
index 0000000..652ca9e
--- /dev/null
+++ b/TomatenMusicCore/Music/TrackProvider.cs
@@ -0,0 +1,79 @@
+using Lavalink4NET;
+using Lavalink4NET.Rest;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Services;
+
+namespace TomatenMusic.Music
+{
+    public class TrackProvider
+    {
+        public ISpotifyService _spotifyService { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public TrackProvider(ISpotifyService spotify, IAudioService audioService)
+        {
+            _audioService = audioService;
+            _spotifyService = spotify;
+        }
+
+        public async Task<MusicActionResponse> SearchAsync(string query, bool withSearchResults = false)
+        {
+
+            Uri uri;
+            TrackLoadResponsePayload loadResult;
+            bool isSearch = true;
+
+            if (query.StartsWith("https://open.spotify.com"))
+            {
+                return await _spotifyService.ConvertURL(query);
+            }
+
+            if (Uri.TryCreate(query, UriKind.Absolute, out uri))
+            {
+                loadResult = await _audioService.LoadTracksAsync(uri.ToString());
+                isSearch = false;
+            }
+            else
+                loadResult = await _audioService.LoadTracksAsync(query, SearchMode.YouTube);
+
+
+            if (loadResult.LoadType == TrackLoadType.LoadFailed) throw new ArgumentException("Track loading failed");
+
+            if (loadResult.LoadType == TrackLoadType.NoMatches) throw new FileNotFoundException("Query resulted in no Matches");
+
+            if (withSearchResults && loadResult.LoadType == TrackLoadType.SearchResult)
+            {
+                return new MusicActionResponse(tracks: await FullTrackContext.PopulateTracksAsync(loadResult.Tracks));
+            }
+
+            if (loadResult.LoadType == TrackLoadType.PlaylistLoaded && !isSearch)
+                return new MusicActionResponse(
+                    playlist: new YoutubePlaylist(loadResult.PlaylistInfo.Name, await FullTrackContext.PopulateTracksAsync(loadResult.Tracks), uri));
+            else
+                return new MusicActionResponse(await FullTrackContext.PopulateAsync(loadResult.Tracks.First()));
+
+        }
+
+        public async Task<MusicActionResponse> SearchAsync(Uri fileUri)
+        {
+
+            var loadResult = await _audioService.GetTrackAsync(fileUri.ToString());
+            loadResult.Context = new FullTrackContext
+            {
+                IsFile = true
+            };
+
+            if (loadResult == null)
+                throw new FileNotFoundException("The file was not found");
+
+            return new MusicActionResponse(loadResult);
+
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs b/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
new file mode 100644
index 0000000..a7bda05
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
@@ -0,0 +1,56 @@
+using DSharpPlus.Entities;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace TomatenMusic.Prompt.Buttons
+{
+    class AddToQueueButton : ButtonPromptOption
+    {
+        public List<LavalinkTrack> Tracks { get; set; }
+
+        public AddToQueueButton(List<LavalinkTrack> tracks, int row, DiscordMember requestMember)
+        {
+            Tracks = tracks;
+            Emoji = new DiscordComponentEmoji("▶️");
+                Row = row;
+                Style = DSharpPlus.ButtonStyle.Primary;
+                UpdateMethod = (prompt) =>
+                {
+                    if (requestMember.VoiceState == null || requestMember.VoiceState.Channel == null)
+                        prompt.Disabled = true;
+
+                    return Task.FromResult(prompt);
+                };
+            Run = async (args, sender, option) =>
+            {
+                IAudioService audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+                GuildPlayer player;player = audioService.GetPlayer<GuildPlayer>(args.Guild.Id);
+
+                try
+                {
+                    try
+                    {
+                        player = await audioService.JoinAsync<GuildPlayer>(args.Guild.Id, ((DiscordMember)args.User).VoiceState.Channel.Id, true);
+
+                    }catch (Exception ex)
+                    {
+                        player = audioService.GetPlayer<GuildPlayer>(args.Guild.Id);
+                    }
+                    await player.PlayTracksAsync(Tracks);
+                }
+                catch (Exception ex)
+                {
+
+                }
+            };
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
new file mode 100644
index 0000000..1c97992
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
@@ -0,0 +1,275 @@
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+using TomatenMusic.Music;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Util;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class QueuePrompt : ButtonPrompt
+    {
+
+        public static void InvalidateFor(ulong guildId)
+        {
+            foreach (var prompt in ActivePrompts)
+            {
+                if (prompt.State != PromptState.OPEN)
+                    continue;
+                if (!(prompt is QueuePrompt))
+                    continue;
+                if (((QueuePrompt)prompt).Player.GuildId != guildId)
+                    continue;
+                _ = prompt.InvalidateAsync();
+
+            }
+        }
+        public static void UpdateFor(ulong guildId)
+        {
+            _ = Task.Delay(600).ContinueWith(async (task) =>
+            {
+                foreach (var prompt in ActivePrompts)
+                {
+                    if (prompt.State != PromptState.OPEN)
+                        continue;
+                    if (!(prompt is QueuePrompt))
+                        continue;
+                    if (((QueuePrompt)prompt).Player.GuildId != guildId)
+                        continue;
+                    _ = prompt.UpdateAsync();
+                }
+            });
+        }
+
+        public GuildPlayer Player { get; private set; }
+
+        public QueuePrompt(GuildPlayer player, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(lastPrompt, embeds: embeds)
+        {
+            Player = player;
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Emoji = new DiscordComponentEmoji("⏯️"),
+                    Row = 1,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+                        if (player.State == PlayerState.Paused)
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+                        else
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+
+                        return Task.FromResult((IPromptOption) button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        await Player.TogglePauseAsync();
+                    }
+                }
+                );
+
+            AddOption(new ButtonPromptOption()
+                {
+                Emoji = new DiscordComponentEmoji("⏮️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+                    try
+                    {
+                        await Player.RewindAsync();
+                    }catch (Exception ex)
+                    {
+
+                    }
+                }
+            }
+            );
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("⏹️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.DisconnectAsync();
+                }
+            });
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("⏭️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.SkipAsync();
+
+                    System.Timers.Timer timer = new System.Timers.Timer(800);
+                    timer.Elapsed += (s, args) =>
+                    {
+                        _ = UpdateAsync();
+                        timer.Stop();
+                    };
+                    timer.Start();
+                }
+            }
+            );
+
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Row = 1,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+
+                        if (player.PlayerQueue.LoopType == LoopType.TRACK)
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                            button.Emoji = new DiscordComponentEmoji("🔂");
+                        }
+                        else if (player.PlayerQueue.LoopType == LoopType.QUEUE)
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                            button.Emoji = new DiscordComponentEmoji("🔁");
+                        }
+                        else
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+                            button.Emoji = null;
+                            button.Content = "Loop";
+                        }
+
+
+                        return Task.FromResult((IPromptOption)button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        switch (player.PlayerQueue.LoopType)
+                        {
+                            case LoopType.NONE:
+                                _ = Player.SetLoopAsync(LoopType.QUEUE);
+                                break;
+                            case LoopType.QUEUE:
+                                _ = Player.SetLoopAsync(LoopType.TRACK);
+                                break;
+                            case LoopType.TRACK:
+                                _ = Player.SetLoopAsync(LoopType.NONE);
+                                break;
+                        }
+                    }
+                }
+                );
+
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("🔀"),
+                Row = 2,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.ShuffleAsync();
+
+                }
+            });
+
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("🚫"),
+                Row = 2,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    Player.PlayerQueue.Queue.Clear();
+
+                    _ = UpdateAsync();
+                }
+            });
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Emoji = new DiscordComponentEmoji("➡️"),
+                    Content = "AutoPlay",
+                    Row = 2,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+                        if (player.Autoplay)
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                        else
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+
+                        return Task.FromResult((IPromptOption)button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        Player.Autoplay = !Player.Autoplay;
+
+                        _ = UpdateAsync();
+                    }
+                }
+                );
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder().AddEmbed(Common.GetQueueEmbed(Player)).AddEmbed(await Common.CurrentSongEmbedAsync(Player)).AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
new file mode 100644
index 0000000..ba2d4f1
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
@@ -0,0 +1,29 @@
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Prompt.Buttons;
+using TomatenMusic.Prompt.Model;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class SongActionPrompt : ButtonPrompt
+    {
+        public LavalinkTrack Track { get; set; }
+        public SongActionPrompt(LavalinkTrack track, DiscordMember requestMember, List<DiscordEmbed> embeds = null)
+        {
+            Embeds = embeds;
+            Track = track;
+
+            AddOption(new AddToQueueButton(new List<LavalinkTrack>() { track }, 1, requestMember));
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder().AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
new file mode 100644
index 0000000..776adf6
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
@@ -0,0 +1,40 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Prompt.Model;
+using System.Linq;
+using TomatenMusic.Util;
+using TomatenMusic.Music;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Buttons;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class SongListActionPrompt : ButtonPrompt
+    {
+        //TODO
+        public List<LavalinkTrack> Tracks { get; private set; }
+
+        public SongListActionPrompt(List<LavalinkTrack> tracks, DiscordMember requestMember, DiscordPromptBase lastPrompt = null) : base(lastPrompt)
+        {
+            Tracks = tracks;
+
+            AddOption(new AddToQueueButton(tracks, 1, requestMember));
+        }
+
+        protected override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle("What do you want to do with these Tracks?");
+
+            builder.WithDescription(Common.TrackListString(Tracks));
+
+            return Task.FromResult(new DiscordMessageBuilder().WithEmbed(builder.Build()));
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
new file mode 100644
index 0000000..2f075c4
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus;
+using System.Threading.Tasks;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using DSharpPlus.Entities;
+using TomatenMusic.Util;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Music;
+using System.Linq;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    sealed class SongSelectorPrompt : PaginatedSelectPrompt<LavalinkTrack>
+    {
+        public bool IsConfirmed { get; set; }
+        public Func<List<LavalinkTrack>, Task> ConfirmCallback { get; set; } = (tracks) =>
+        {
+            return Task.CompletedTask;
+        };
+
+        public IEnumerable<LavalinkTrack> Tracks { get; private set; }
+
+        public SongSelectorPrompt(string title, IEnumerable<LavalinkTrack> tracks, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(title, tracks.ToList(), lastPrompt, embeds)
+        {
+            Title = title;
+            Tracks = tracks;
+            AddOption(new ButtonPromptOption
+            {
+                Emoji = new DiscordComponentEmoji("✔️"),
+                Row = 3,
+                Style = ButtonStyle.Success,
+                Run = async (args, client, option) =>
+                {
+                    if (SelectedItems.Count == 0)
+                    {
+                        await args.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("Please Select a Song!").AsEphemeral(true));
+                        return;
+                    }
+                    IsConfirmed = true;
+                    _ = ConfirmCallback.Invoke(SelectedItems);
+                }
+            });
+        }
+        public override Task<PaginatedSelectMenuOption<LavalinkTrack>> ConvertToOption(LavalinkTrack item)
+        {
+            return Task.FromResult<PaginatedSelectMenuOption<LavalinkTrack>>(new PaginatedSelectMenuOption<LavalinkTrack>
+            {
+                Label = item.Title,
+                Description = item.Author
+            });
+
+        }
+
+        public override Task OnSelect(LavalinkTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Added {item.Title}, {SelectedItems}");
+            return Task.CompletedTask;
+        }
+
+        public override Task OnUnselect(LavalinkTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Removed {item.Title}");
+            return Task.CompletedTask;
+
+        }
+
+        public async Task<List<LavalinkTrack>> AwaitSelectionAsync()
+        {
+            return await Task.Run(() =>
+            {
+                while (!IsConfirmed) 
+                {
+                    if (State == PromptState.INVALID)
+                        throw new InvalidOperationException("Prompt has been Invalidated");
+                }
+                IsConfirmed = false;
+                return SelectedItems;
+            });
+        }
+
+        protected override DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder)
+        {
+
+            builder.WithTitle(Title);
+            builder.WithDescription(Common.TrackListString(PageManager.GetPage(CurrentPage)));
+            List<DiscordEmbed> embeds = new List<DiscordEmbed>();
+            embeds.Add(builder.Build());
+
+            if (Embeds != null)
+                embeds.AddRange(Embeds);
+
+            return new DiscordMessageBuilder().AddEmbeds(embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs b/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
new file mode 100644
index 0000000..de67a13
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus;
+using System.Threading.Tasks;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using DSharpPlus.Entities;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class StringSelectorPrompt : PaginatedSelectPrompt<string>
+    {
+        public StringSelectorPrompt(string title, List<string> strings, DiscordPromptBase lastPrompt = null) : base(title, strings, lastPrompt)
+        {
+        }
+        public async override Task<PaginatedSelectMenuOption<string>> ConvertToOption(string item)
+        {
+            return new PaginatedSelectMenuOption<string>
+            {
+                Label = item
+            };
+        }
+
+        public async override Task OnSelect(string item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+        }
+
+        public async override Task OnUnselect(string item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+
+        }
+
+        protected override DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder)
+        {
+            foreach (var item in PageManager.GetPage(CurrentPage))
+            {
+                builder.AddField(item, item);
+            }
+
+            return new DiscordMessageBuilder().WithEmbed(builder);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs b/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
new file mode 100644
index 0000000..771ec8a
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
@@ -0,0 +1,41 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TomatenMusic.Prompt.Model
+{
+    class ButtonPrompt : DiscordPromptBase
+    {
+        public string Content { get; protected set; } = "";
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+
+        public ButtonPrompt(DiscordPromptBase lastPrompt = null, string content = " ", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+            var myOption = (ButtonPromptOption)option;
+            DiscordComponent component;
+
+            if (myOption.Link != null)
+                component = new DiscordLinkButtonComponent(myOption.Link, myOption.Content, myOption.Disabled, myOption.Emoji);
+            else
+                component = new DiscordButtonComponent(myOption.Style, myOption.CustomID, myOption.Content, myOption.Disabled, myOption.Emoji);
+            return Task.FromResult<DiscordComponent>(component);
+        }
+
+        protected override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return Task.FromResult<DiscordMessageBuilder>(new DiscordMessageBuilder()
+            .WithContent(Content)
+            .AddEmbeds(Embeds));
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs b/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
new file mode 100644
index 0000000..4c5c8a9
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
@@ -0,0 +1,62 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+
+
+namespace TomatenMusic.Prompt.Model
+{
+    class CombinedPrompt : DiscordPromptBase
+    {
+        public string Content { get; protected set; } = "";
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+
+        public CombinedPrompt(DiscordPromptBase lastPrompt = null, string content = "Example Content", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            this.LastPrompt = lastPrompt;
+
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected async override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+            if (option is SelectMenuPromptOption)
+            {
+                SelectMenuPromptOption selectOption = (SelectMenuPromptOption)option;
+                List<DiscordSelectComponentOption> options = new List<DiscordSelectComponentOption>();
+                foreach (var item in selectOption.Options)
+                {
+                    options.Add(new DiscordSelectComponentOption(item.Label, item.CustomID, item.Description, item.Default, item.Emoji));
+                }
+
+                return new DiscordSelectComponent(selectOption.CustomID, selectOption.Content, options, selectOption.Disabled, selectOption.MinValues, selectOption.MaxValues);
+            }
+            else
+            {
+                var myOption = (ButtonPromptOption)option;
+                DiscordComponent component;
+
+                if (myOption.Link != null)
+                    component = new DiscordLinkButtonComponent(myOption.Link, myOption.Content, myOption.Disabled, myOption.Emoji);
+                else
+                    component = new DiscordButtonComponent(myOption.Style, myOption.CustomID, myOption.Content, myOption.Disabled, myOption.Emoji);
+                return component;
+            }
+
+
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder()
+            .WithContent(Content)
+            .AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs b/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
new file mode 100644
index 0000000..8a214d4
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
@@ -0,0 +1,471 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Util;
+using DSharpPlus.Exceptions;
+using Microsoft.Extensions.DependencyInjection;
+using DSharpPlus;
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class DiscordPromptBase
+    {
+        public static List<DiscordPromptBase> ActivePrompts { get; } = new List<DiscordPromptBase>();
+
+        public PromptState State { get; protected set; }
+        public DiscordMessage Message { get; private set; }
+        public DiscordInteraction Interaction { get; private set; }
+        public List<IPromptOption> Options { get; protected set; } = new List<IPromptOption>();
+        public DiscordClient _client { get; set; }
+        public DiscordPromptBase LastPrompt { get; protected set; }
+
+        protected ILogger<DiscordPromptBase> _logger { get; set; }
+
+        protected EventId eventId = new EventId(16, "Prompts");
+
+        protected DiscordPromptBase(DiscordPromptBase lastPrompt)
+        {
+            LastPrompt = lastPrompt;
+            Options = new List<IPromptOption>();
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+
+            _logger = serviceProvider.GetRequiredService<ILogger<DiscordPromptBase>>();
+ 
+
+            if (lastPrompt != null)
+            {
+                Options.Add(new ButtonPromptOption
+                {
+                    Style = DSharpPlus.ButtonStyle.Danger,
+                    Row = 5,
+                    Emoji = new DiscordComponentEmoji("↩️"),
+                    Run = async (args, sender, option) =>
+                    {
+                        _ = BackAsync();
+                    }
+                });
+            }
+
+            Options.Add(new ButtonPromptOption
+            {
+                Style = DSharpPlus.ButtonStyle.Danger,
+                Row = 5,
+                Emoji = new DiscordComponentEmoji("❌"),
+                Run = async (args, sender, option) =>
+                {
+                    _ = InvalidateAsync();
+                }
+            });
+
+        }
+
+        public async Task InvalidateAsync(bool withEdit = true, bool destroyHistory = false)
+        {
+            foreach (var option in Options)
+                option.UpdateMethod = (prompt) =>
+                {
+                    prompt.Disabled = true;
+                    return Task.FromResult<IPromptOption>(prompt);
+                };
+
+            if (withEdit)
+                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
+            ActivePrompts.Remove(this);
+            if (destroyHistory)
+            {
+                if (LastPrompt != null)
+                    await LastPrompt.InvalidateAsync(false);
+                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
+            }
+
+            if (State == PromptState.INVALID)
+                return;
+            State = PromptState.INVALID;
+            
+
+            _client.ComponentInteractionCreated -= Discord_ComponentInteractionCreated;
+
+        }
+
+        public async Task SendAsync(DiscordChannel channel)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard( (ulong) channel.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+            AddGuids();
+            DiscordMessageBuilder builder = await GetMessageAsync();
+            builder = await AddComponentsAsync(builder);
+
+
+            Message = await builder.SendAsync(channel);
+            State = PromptState.OPEN;
+        }
+
+        public async Task SendAsync(DiscordInteraction interaction, bool ephemeral = false)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)interaction.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+
+            AddGuids();
+            DiscordFollowupMessageBuilder builder = await GetFollowupMessageAsync();
+            builder = await AddComponentsAsync(builder);
+            builder.AsEphemeral(ephemeral);
+
+            Interaction = interaction;
+            Message = await interaction.CreateFollowupMessageAsync(builder);
+            State = PromptState.OPEN;
+        }
+
+        public async Task UseAsync(DiscordMessage message)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)message.Channel.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+
+            AddGuids();
+            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
+
+            await EditMessageAsync(builder);
+            State = PromptState.OPEN;
+
+        }
+
+        public async Task UseAsync(DiscordInteraction interaction, DiscordMessage message)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)interaction.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+            AddGuids();
+            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
+            Interaction = interaction;
+            Message = message;
+            await EditMessageAsync(builder);
+            State = PromptState.OPEN;
+        }
+
+        private void AddGuids()
+        {
+            foreach (var item in Options)
+            {
+                item.CustomID = RandomUtil.GenerateGuid();
+                if (item is SelectMenuPromptOption)
+                {
+                    SelectMenuPromptOption menuItem = (SelectMenuPromptOption)item;
+                    foreach (var option in menuItem.Options)
+                    {
+                        option.CustomID = RandomUtil.GenerateGuid();
+                    }
+                }
+
+            }
+            //this.Options = options;
+        }
+
+        protected abstract Task<DiscordComponent> GetComponentAsync(IPromptOption option);
+
+        protected abstract Task<DiscordMessageBuilder> GetMessageAsync();
+
+        private async Task<DiscordFollowupMessageBuilder> GetFollowupMessageAsync()
+        {
+            DiscordMessageBuilder oldBuilder = await GetMessageAsync();
+
+            return new DiscordFollowupMessageBuilder()
+                .WithContent(oldBuilder.Content)
+                .AddEmbeds(oldBuilder.Embeds);
+                
+        }
+        private async Task<DiscordWebhookBuilder> GetWebhookMessageAsync()
+        {
+            DiscordMessageBuilder oldBuilder = await GetMessageAsync();
+
+            return new DiscordWebhookBuilder()
+                .WithContent(oldBuilder.Content)
+                .AddEmbeds(oldBuilder.Embeds);
+
+        }
+
+        public async Task UpdateAsync()
+        {
+           if (State == PromptState.INVALID)
+                return;
+           await EditMessageAsync(await GetWebhookMessageAsync());
+           
+        }
+
+        private async Task UpdateOptionsAsync()
+        {
+            List<IPromptOption> options = new List<IPromptOption>();
+            foreach (var option in this.Options)
+                options.Add(await option.UpdateMethod.Invoke(option));
+            this.Options = options;
+        }
+
+        protected async Task Discord_ComponentInteractionCreated(DSharpPlus.DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs e)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            foreach (var option in Options)
+            {
+                if (option.CustomID == e.Id)
+                {
+
+                    await e.Interaction.CreateResponseAsync(DSharpPlus.InteractionResponseType.DeferredMessageUpdate);
+                    _ = option.Run.Invoke(e, sender, option);
+                }
+            }
+        }
+
+        public async Task EditMessageAsync(DiscordWebhookBuilder builder)
+        {
+            try
+            {
+                if (Interaction != null)
+                {
+                    await AddComponentsAsync(builder);
+                    try
+                    {
+                        Message = await Interaction.EditFollowupMessageAsync(Message.Id, builder);
+                    }catch (Exception e)
+                    {
+                        Message = await Interaction.EditOriginalResponseAsync(builder);
+                    }
+
+                }
+                else
+                {
+                    DiscordMessageBuilder msgbuilder = new DiscordMessageBuilder()
+                        .AddEmbeds(builder.Embeds)
+                        .WithContent(builder.Content);
+                    await AddComponentsAsync(msgbuilder);
+                    Message = await Message.ModifyAsync(msgbuilder);
+                }
+            }catch (BadRequestException e)
+            {
+                _logger.LogError(e.Errors);
+            }
+
+        }
+
+        protected async Task<DiscordMessageBuilder> AddComponentsAsync(DiscordMessageBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        protected async Task<DiscordFollowupMessageBuilder> AddComponentsAsync(DiscordFollowupMessageBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        protected async Task<DiscordWebhookBuilder> AddComponentsAsync(DiscordWebhookBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        public async Task BackAsync()
+        {
+
+            if (LastPrompt == null)
+                return;
+            _client.ComponentInteractionCreated -= LastPrompt.Discord_ComponentInteractionCreated;
+
+            await InvalidateAsync(false);
+            if (Interaction == null)
+                await LastPrompt.UseAsync(Message);
+            else
+                await LastPrompt.UseAsync(Interaction, Message);
+        }
+
+        public void AddOption(IPromptOption option)
+        {
+            Options.Add(option);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs b/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
new file mode 100644
index 0000000..3e35fbb
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
@@ -0,0 +1,138 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+
+
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class PaginatedButtonPrompt<T> : ButtonPrompt
+    {
+        protected PageManager<T> PageManager { get; set; }
+        protected int CurrentPage { get; set; } = 1;
+        public string Title { get; set; }
+
+        public PaginatedButtonPrompt(string title, List<T> items, DiscordPromptBase lastPrompt = null) : base(lastPrompt)
+        {
+            PageManager = new PageManager<T>(items, 9);
+            Title = title;
+
+            for (int i = 0; i < 9; i++)
+            {
+                int currentNumber = i + 1;
+                ButtonPromptOption option = new ButtonPromptOption()
+                {
+                    Style = DSharpPlus.ButtonStyle.Primary,
+                    Row = i < 5 ? 1 : 2,
+                    UpdateMethod = async (option) =>
+                    {
+                        option.Disabled = PageManager.GetPage(CurrentPage).Count < currentNumber;
+                        return option;
+                    },
+                    Run = async (args, sender, prompt) =>
+                    {
+                        List<T> items = PageManager.GetPage(CurrentPage);
+                        await OnSelect(items[currentNumber-1], args, sender);
+                    }
+                };
+
+                switch (i)
+                {
+                    case 0:
+                        option.Emoji = new DiscordComponentEmoji("1️⃣");
+                        break;
+                    case 1:
+                        option.Emoji = new DiscordComponentEmoji("2️⃣");
+                        break;
+                    case 2:
+                        option.Emoji = new DiscordComponentEmoji("3️⃣");
+                        break;
+                    case 3:
+                        option.Emoji = new DiscordComponentEmoji("4️⃣");
+                        break;
+                    case 4:
+                        option.Emoji = new DiscordComponentEmoji("5️⃣");
+                        break;
+                    case 5:
+                        option.Emoji = new DiscordComponentEmoji("6️⃣");
+                        break;
+                    case 6:
+                        option.Emoji = new DiscordComponentEmoji("7️⃣");
+                        break;
+                    case 7:
+                        option.Emoji = new DiscordComponentEmoji("8️⃣");
+                        break;
+                    case 8:
+                        option.Emoji = new DiscordComponentEmoji("9️⃣");
+                        break;
+                }
+
+                AddOption(option);
+            }
+
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji= new DiscordComponentEmoji("⬅️"),
+                Row = 3,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = CurrentPage - 1 == 0;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage--;
+                    await UpdateAsync();
+
+                }
+            });
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("➡️"),
+                Row = 3,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = PageManager.GetTotalPages() == CurrentPage;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage++;
+                    await UpdateAsync();
+                }
+            });
+
+        }
+
+        public abstract Task OnSelect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        protected int GetTotalPages()
+        {
+            return PageManager.GetTotalPages();
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle(Title)
+                .WithFooter($"Page {CurrentPage} of {GetTotalPages()}")
+                .WithDescription("Select your desired Tracks");
+
+            return PopulateMessage(builder);
+
+        }
+
+        protected abstract DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder);
+
+
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs b/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
new file mode 100644
index 0000000..967fa06
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
@@ -0,0 +1,160 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class PaginatedSelectPrompt<T> : CombinedPrompt
+    {
+        protected PageManager<T> PageManager { get; set; }
+        protected int CurrentPage { get; set; } = 1;
+
+        public string Title { get; set; }
+        public List<T> SelectedItems { get; set; } = new List<T>();
+
+
+        public PaginatedSelectPrompt(string title, List<T> items, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            Embeds = embeds;
+            PageManager = new PageManager<T>(items, 25);
+            Title = title;
+            AddOption(new SelectMenuPromptOption
+            {
+                Row = 1,
+                MinValues = 1,
+                MaxValues = PageManager.GetPage(CurrentPage).Count,
+                Content = "Select a Value",
+                UpdateMethod = async (option) =>
+                {
+                    SelectMenuPromptOption _option = (SelectMenuPromptOption)option;
+                    
+                    _option.MaxValues = PageManager.GetPage(CurrentPage).Count;
+                    _option.Options.Clear();
+                    foreach (var item in PageManager.GetPage(CurrentPage))
+                    {
+                        _option.Options.Add(await GetOption(item));
+                    }
+                    foreach (var item in _option.Options)
+                    {
+                        foreach (var sOption in SelectedItems)
+                        {
+                            PaginatedSelectMenuOption<T> _item = (PaginatedSelectMenuOption<T>)item;
+                            if (_item.Item.Equals(sOption))
+                            {
+                                _option.CurrentValues.Add(_item.CustomID);
+                            }
+                        }
+                        
+                    }
+
+                    return _option;
+                }
+            });
+
+
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("⬅️"),
+                Row = 2,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = CurrentPage - 1 == 0;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage--;
+                    await UpdateAsync();
+
+                }
+            });
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("➡️"),
+                Row = 2,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = PageManager.GetTotalPages() == CurrentPage;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage++;
+                    await UpdateAsync();
+                }
+            });
+
+        }
+
+        private async Task<PaginatedSelectMenuOption<T>> GetOption(T item)
+        {
+            var option = await ConvertToOption(item);
+            option.Item = item;
+            option.CustomID = RandomUtil.GenerateGuid();
+            option.Default = SelectedItems.Contains(item);
+            option.OnSelected = async (args, sender, option) =>
+            {
+                PaginatedSelectMenuOption<T> _option = (PaginatedSelectMenuOption<T>)option;
+                if (!SelectedItems.Contains(_option.Item))
+                    SelectedItems.Add(_option.Item);
+                await OnSelect(_option.Item, args, sender);
+                
+            };
+            option.OnUnselected = async (args, sender, option) =>
+            {
+                PaginatedSelectMenuOption<T> _option = (PaginatedSelectMenuOption<T>)option;
+                SelectedItems.Remove(_option.Item);
+                await OnUnselect(_option.Item, args, sender);
+            };
+
+
+            return option;
+        }
+        public abstract Task<PaginatedSelectMenuOption<T>> ConvertToOption(T item);
+
+        public abstract Task OnSelect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        public abstract Task OnUnselect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        protected int GetTotalPages()
+        {
+            return PageManager.GetTotalPages();
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            DiscordEmbedBuilder builder;
+            if (Embeds != null)
+            {
+                builder = new DiscordEmbedBuilder(Embeds[0]);
+            }else
+            {
+                builder = new DiscordEmbedBuilder();
+            }
+
+            builder
+                .WithTitle(Title)
+                .WithFooter($"Page {CurrentPage} of {GetTotalPages()}");
+
+            return PopulateMessage(builder);
+
+        }
+
+        protected abstract DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder);
+
+        public class PaginatedSelectMenuOption<I> : SelectMenuOption
+        {
+            public I Item { get; set; }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PromptState.cs b/TomatenMusicCore/Prompt/Model/PromptState.cs
new file mode 100644
index 0000000..1b1d0e8
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PromptState.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TomatenMusic.Prompt.Model
+{
+    enum PromptState
+    {
+
+        PREPARED,
+        OPEN,
+        INVALID,
+        RESPONDED
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/SelectPrompt.cs b/TomatenMusicCore/Prompt/Model/SelectPrompt.cs
new file mode 100644
index 0000000..3253f1b
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/SelectPrompt.cs
@@ -0,0 +1,48 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Util;
+
+
+namespace TomatenMusic.Prompt.Model
+{
+    class SelectPrompt : DiscordPromptBase
+    {
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+        public string Content { get; protected set; } = "";
+        public SelectPrompt(DiscordPromptBase lastPrompt = null, string content = " Example", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected async override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+
+            SelectMenuPromptOption selectOption = (SelectMenuPromptOption)option;
+            List<DiscordSelectComponentOption> options = new List<DiscordSelectComponentOption>();
+            foreach ( var item in selectOption.Options)
+            {
+                options.Add(new DiscordSelectComponentOption(item.Label, item.CustomID, item.Description, item.Default, item.Emoji));
+            }
+
+                return new DiscordSelectComponent(selectOption.CustomID, selectOption.Content, options, selectOption.Disabled, selectOption.MinValues, selectOption.MaxValues);
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder()
+                .WithContent(Content)
+                .AddEmbeds(Embeds);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs b/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
new file mode 100644
index 0000000..0a9f61f
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Prompt.Model;
+
+
+namespace TomatenMusic.Prompt
+{
+    class ButtonPromptOption : IPromptOption
+    {
+
+        public ButtonStyle Style { get; set; } = ButtonStyle.Primary;
+        public string Content { get; set; } = " ";
+        public DiscordComponentEmoji Emoji { get; set; }
+        public bool Disabled { get; set; } = false;
+        public string CustomID { get; set; }
+        public string Link { get; set; }
+        public int Row { get; set; }
+        public Func<Option.IPromptOption, Task<Option.IPromptOption>> UpdateMethod { get; set; } = async prompt =>
+        {
+            return prompt;
+        };
+        public Func<DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; } = async (args, sender, prompt) =>
+        {
+
+        };
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/IPromptOption.cs b/TomatenMusicCore/Prompt/Option/IPromptOption.cs
new file mode 100644
index 0000000..ed77dd2
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/IPromptOption.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Prompt.Model;
+
+
+namespace TomatenMusic.Prompt.Option
+{
+    interface IPromptOption
+    {
+        public string Content { get; set; }
+        public string CustomID { get; set; }
+        public int Row { get; set; }
+        public bool Disabled { get; set; }
+        public Func<IPromptOption, Task<IPromptOption>> UpdateMethod { get; set; }
+        public Func<DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; }
+
+
+
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs b/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
new file mode 100644
index 0000000..b3e49c5
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
@@ -0,0 +1,23 @@
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus.Entities;
+
+namespace TomatenMusic.Prompt.Option
+{
+    class SelectMenuOption
+    {
+        public string Label { get; set; }
+        public string CustomID { get; set; }
+        public string Description { get; set; }
+        public bool Default { get; set; }
+        public DiscordComponentEmoji Emoji { get; set; }
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, SelectMenuOption, Task> OnSelected { get; set; }
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, SelectMenuOption, Task> OnUnselected { get; set; }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs b/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
new file mode 100644
index 0000000..01b717d
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
@@ -0,0 +1,50 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.EventArgs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Model;
+using System.Linq;
+
+
+namespace TomatenMusic.Prompt.Option
+{
+    class SelectMenuPromptOption : IPromptOption
+    {
+        public string Content { get; set; } = " ";
+        public string CustomID { get; set; }
+        public int Row { get; set; } = 1;
+        public bool Disabled { get; set; } = false;
+        public List<SelectMenuOption> Options { get; set; } = new List<SelectMenuOption>();
+        public int MinValues { get; set; } = 1;
+        public int MaxValues { get; set; } = 1;
+        public List<string> CurrentValues { get; set; } = new List<string>();
+
+
+        public Func<IPromptOption, Task<IPromptOption>> UpdateMethod { get; set; } = async (prompt) =>
+        {
+            return prompt;
+        };
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; } = async (args, sender, option) =>
+        {
+            SelectMenuPromptOption _option = (SelectMenuPromptOption)option;
+            foreach (var item in _option.Options)
+            {
+                if (_option.CurrentValues.Contains(item.CustomID) && !args.Values.Contains(item.CustomID))
+                {
+                    await item.OnUnselected.Invoke(args, sender, item);
+                }
+                if (!_option.CurrentValues.Contains(item.CustomID) && args.Values.Contains(item.CustomID))
+                {
+                    await item.OnSelected.Invoke(args, sender, item);
+                }
+            }
+            _option.CurrentValues = new List<string>(args.Values);
+
+        };
+
+    }
+}
diff --git a/TomatenMusicCore/Services/SpotifyService.cs b/TomatenMusicCore/Services/SpotifyService.cs
new file mode 100644
index 0000000..8caeb8b
--- /dev/null
+++ b/TomatenMusicCore/Services/SpotifyService.cs
@@ -0,0 +1,156 @@
+using SpotifyAPI.Web;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Services;
+using TomatenMusic.Music;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Services
+{
+
+    public interface ISpotifyService
+    {
+        public Task<MusicActionResponse> ConvertURL(string url);
+        public Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist);
+        public Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist);
+        public Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track);
+
+    }
+
+    public class SpotifyService : SpotifyClient, ISpotifyService
+    {
+        public ILogger _logger { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public SpotifyService(SpotifyClientConfig config, ILogger<SpotifyService> logger, IAudioService audioService) : base(config)
+        {
+            _logger = logger;
+            _audioService = audioService;
+        }
+
+        public async Task<MusicActionResponse> ConvertURL(string url)
+        {
+            string trackId = url
+                .Replace("https://open.spotify.com/track/", "")
+                .Replace("https://open.spotify.com/album/", "")
+                .Replace("https://open.spotify.com/playlist/", "")
+                .Substring(0, 22);
+
+            if (url.StartsWith("https://open.spotify.com/track"))
+            {
+                FullTrack sTrack = await Tracks.Get(trackId);
+
+                _logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists)}");
+
+                var track = await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}", Lavalink4NET.Rest.SearchMode.YouTube);
+
+                if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+
+                return new MusicActionResponse(await FullTrackContext.PopulateAsync(track, sTrack.Uri));
+
+            }
+            else if (url.StartsWith("https://open.spotify.com/album"))
+            {
+                List<LavalinkTrack> tracks = new List<LavalinkTrack>();
+
+                FullAlbum album = await Albums.Get(trackId);
+
+                foreach (var sTrack in await PaginateAll(album.Tracks))
+                {
+                    _logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}");
+
+                    var track = await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}", Lavalink4NET.Rest.SearchMode.YouTube);
+
+                    if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+
+                    tracks.Add(await FullTrackContext.PopulateAsync(track, sTrack.Uri));
+                }
+                Uri uri;
+                Uri.TryCreate(url, UriKind.Absolute, out uri);
+
+                SpotifyPlaylist playlist = new SpotifyPlaylist(album.Name, album.Id, tracks, uri);
+                await PopulateSpotifyAlbumAsync(playlist);
+
+                return new MusicActionResponse(playlist: playlist);
+
+            }
+            else if (url.StartsWith("https://open.spotify.com/playlist"))
+            {
+                List<LavalinkTrack> tracks = new List<LavalinkTrack>();
+
+                FullPlaylist spotifyPlaylist = await Playlists.Get(trackId);
+                
+                foreach (var sTrack in await PaginateAll(spotifyPlaylist.Tracks))
+                {
+                    if (sTrack.Track is FullTrack)
+                    {
+                        FullTrack fullTrack = (FullTrack)sTrack.Track;
+                        _logger.LogInformation($"Searching youtube from spotify with query: {fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}");
+
+                        var track = await _audioService.GetTrackAsync($"{fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}", Lavalink4NET.Rest.SearchMode.YouTube);
+
+                        if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+
+                        tracks.Add(await FullTrackContext.PopulateAsync(track, fullTrack.Uri));
+                    }
+
+                }
+                Uri uri;
+                Uri.TryCreate(url, UriKind.Absolute, out uri);
+                SpotifyPlaylist playlist = new SpotifyPlaylist(spotifyPlaylist.Name, spotifyPlaylist.Id, tracks, uri);
+                await PopulateSpotifyPlaylistAsync(playlist);
+
+                return new MusicActionResponse(playlist: playlist);
+            }
+            return null;
+        }
+   
+        public async Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist)
+        {
+            var list = await this.Playlists.Get(playlist.Identifier);
+            playlist.Description = list.Description;
+            playlist.AuthorUri = new Uri(list.Owner.Uri);
+            playlist.AuthorName = list.Owner.DisplayName;
+            playlist.Followers = list.Followers.Total;
+            playlist.Url = new Uri(list.Uri);
+            playlist.AuthorThumbnail = new Uri(list.Owner.Images.First().Url);
+            return playlist;
+        }
+
+        public async Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist)
+        {
+            var list = await this.Albums.Get(playlist.Identifier);
+            playlist.Description = list.Label;
+            playlist.AuthorUri = new Uri(list.Artists.First().Uri);
+            playlist.AuthorName = list.Artists.First().Name;
+            playlist.Followers = list.Popularity;
+            playlist.Url = new Uri(list.Uri);
+
+            return playlist;
+        }
+
+        public async Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+            if (context.SpotifyIdentifier == null)
+                return track;
+
+            var spotifyTrack = await this.Tracks.Get(context.SpotifyIdentifier);
+
+            context.SpotifyAlbum = spotifyTrack.Album;
+            context.SpotifyArtists = spotifyTrack.Artists;
+            context.SpotifyPopularity = spotifyTrack.Popularity;
+            context.SpotifyUri = new Uri(spotifyTrack.Uri);
+            track.Context = context;
+
+            return track;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Services/YoutubeService.cs b/TomatenMusicCore/Services/YoutubeService.cs
new file mode 100644
index 0000000..12bf269
--- /dev/null
+++ b/TomatenMusicCore/Services/YoutubeService.cs
@@ -0,0 +1,127 @@
+using Google.Apis.Services;
+using Google.Apis.YouTube.v3;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using System.Linq;
+using Google.Apis.YouTube.v3.Data;
+using Microsoft.Extensions.Logging;
+using static TomatenMusic.TomatenMusicBot;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET;
+
+namespace TomatenMusic.Services
+{
+    public class YoutubeService
+    {
+        public YouTubeService Service { get; }
+        public ILogger<YoutubeService> _logger { get; set; }
+        public YoutubeService(ILogger<YoutubeService> logger, ConfigJson config)
+        {
+            Service = new YouTubeService(new BaseClientService.Initializer()
+            {
+                ApiKey = config.YoutubeAPIKey,
+                ApplicationName = "TomatenMusic"
+            });
+            _logger = logger;
+        }
+
+        public async Task<LavalinkTrack> PopulateTrackInfoAsync(LavalinkTrack track)
+        {
+            var video = await GetVideoAsync(track.TrackIdentifier);
+            var channel = await GetChannelAsync(video.Snippet.ChannelId);
+            FullTrackContext context = track.Context == null ? new FullTrackContext() : (FullTrackContext)track.Context;
+
+            if (channel.Statistics.SubscriberCount != null)
+                context.YoutubeAuthorSubs = (ulong) channel.Statistics.SubscriberCount;
+            context.YoutubeAuthorThumbnail = new Uri(channel.Snippet.Thumbnails.High.Url);
+            context.YoutubeAuthorUri = new Uri($"https://www.youtube.com/channel/{channel.Id}");
+            context.YoutubeDescription = video.Snippet.Description;
+            if (video.Statistics.LikeCount != null)
+                context.YoutubeLikes = (ulong) video.Statistics.LikeCount;
+            context.YoutubeTags = video.Snippet.Tags;
+            context.YoutubeThumbnail = new Uri(video.Snippet.Thumbnails.High.Url);
+            context.YoutubeUploadDate = (DateTime)video.Snippet.PublishedAt;
+            context.YoutubeViews = (ulong)video.Statistics.ViewCount;
+            context.YoutubeCommentCount = video.Statistics.CommentCount;
+            track.Context = context;
+            return track;
+        }
+
+        public async Task<List<LavalinkTrack>> PopulateMultiTrackListAsync(IEnumerable<LavalinkTrack> tracks)
+        {
+            List<LavalinkTrack> newTracks = new List<LavalinkTrack>();
+            foreach (var track in tracks)
+                newTracks.Add(await PopulateTrackInfoAsync(track));
+
+            return newTracks;
+        }
+        public async Task<LavalinkPlaylist> PopulatePlaylistAsync(YoutubePlaylist playlist)
+        {
+            var list = await GetPlaylistAsync(playlist.Identifier);
+            var channel = await GetChannelAsync(list.Snippet.ChannelId);
+
+            string desc = list.Snippet.Description;
+
+            playlist.Description = desc.Substring(0, Math.Min(desc.Length, 4092)) + (desc.Length > 4092 ? "..." : " ");
+            playlist.Thumbnail = new Uri(list.Snippet.Thumbnails.High.Url);
+            playlist.CreationTime = (DateTime)list.Snippet.PublishedAt;
+            playlist.YoutubeItem = list;
+            playlist.AuthorThumbnail = new Uri(channel.Snippet.Thumbnails.High.Url);
+            playlist.AuthorUri = new Uri($"https://www.youtube.com/playlist?list={playlist.Identifier}");
+
+            return playlist;
+        }
+
+        public async Task<Video> GetVideoAsync(string id)
+        {
+            var search = Service.Videos.List("contentDetails,id,liveStreamingDetails,localizations,player,recordingDetails,snippet,statistics,status,topicDetails");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+            return response.Items.First();
+        }
+
+        public async Task<Channel> GetChannelAsync(string id)
+        {
+            var search = Service.Channels.List("brandingSettings,contentDetails,contentOwnerDetails,id,localizations,snippet,statistics,status,topicDetails");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+
+            return response.Items.First();
+        }
+        public async Task<Playlist> GetPlaylistAsync(string id)
+        {
+            var search = Service.Playlists.List("snippet,contentDetails,status");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+
+            return response.Items.First();
+        }
+
+        public async Task<SearchResult> GetRelatedVideoAsync(string id)
+        {
+            var search = Service.Search.List("snippet");
+            search.RelatedToVideoId = id;
+            search.Type = "video";
+            var response = await search.ExecuteAsync();
+            return response.Items.First(s => s.Snippet != null);
+        }
+
+        public async Task<LavalinkTrack> GetRelatedTrackAsync(string id)
+        {
+            var audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+
+            var video = await GetRelatedVideoAsync(id);
+            var loadResult = await audioService.GetTrackAsync($"https://youtu.be/{video.Id.VideoId}");
+
+            if (loadResult == null)
+                throw new Exception("An Error occurred while processing the Request");
+
+            return await FullTrackContext.PopulateAsync(loadResult);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/TomatenMusicBot.cs b/TomatenMusicCore/TomatenMusicBot.cs
new file mode 100644
index 0000000..464ca2c
--- /dev/null
+++ b/TomatenMusicCore/TomatenMusicBot.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using DSharpPlus.Entities;
+using DSharpPlus.Net;
+using System.Linq;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.SlashCommands.EventArgs;
+using TomatenMusic.Commands;
+using Newtonsoft.Json;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using TomatenMusic.Music;
+using SpotifyAPI.Web.Auth;
+using SpotifyAPI.Web;
+using DSharpPlus.Exceptions;
+using Lavalink4NET;
+using Lavalink4NET.DSharpPlus;
+using Lavalink4NET.MemoryCache;
+using TomatenMusic.Services;
+using Lavalink4NET.Tracking;
+using Lavalink4NET.Lyrics;
+using Microsoft.Extensions.Hosting;
+using Lavalink4NET.Logging;
+using Lavalink4NET.Logging.Microsoft;
+using TomatenMusic.Music.Entitites;
+
+namespace TomatenMusic
+{
+    public class TomatenMusicBot
+    {
+
+        public class ConfigJson
+        {
+            [JsonProperty("TOKEN")]
+            public string Token { get; private set; }
+            [JsonProperty("LavaLinkPassword")]
+            public string LavaLinkPassword { get; private set; }
+            [JsonProperty("SpotifyClientId")]
+            public string SpotifyClientId { get; private set; }
+            [JsonProperty("SpotifyClientSecret")]
+            public string SpotifyClientSecret { get; private set; }
+            [JsonProperty("YoutubeApiKey")]
+            public string YoutubeAPIKey { get; private set; }
+
+        }
+
+        public static IServiceProvider ServiceProvider { get; private set; }
+
+        public IHost _host { get; set; }
+
+        private async Task<IServiceProvider> BuildServiceProvider()
+        {
+            var json = "";
+            using (var fs = File.OpenRead("config.json"))
+            using (var sr = new StreamReader(fs, new UTF8Encoding(false)))
+                json = await sr.ReadToEndAsync();
+            ConfigJson config = JsonConvert.DeserializeObject<ConfigJson>(json);
+
+
+
+            _host = Host.CreateDefaultBuilder()
+                .ConfigureServices((_, services) =>
+                services
+                    .AddSingleton(config)
+                    .AddMicrosoftExtensionsLavalinkLogging()
+                    .AddSingleton<TrackProvider>()
+                    .AddSingleton<DiscordShardedClient>()
+                    .AddSingleton( s => new DiscordConfiguration
+                    {
+                        Token = config.Token,
+                        Intents = DiscordIntents.All,
+                        LoggerFactory = s.GetRequiredService<ILoggerFactory>()
+
+                    })
+
+                    // Lavalink
+                    .AddSingleton<IDiscordClientWrapper, DiscordShardedClientWrapper>()
+                    .AddSingleton<IAudioService, LavalinkNode>()
+                    .AddSingleton(new InactivityTrackingOptions
+                    {
+                        TrackInactivity = true
+                    })
+                    .AddSingleton<InactivityTrackingService>()
+
+                    .AddSingleton(
+                          new LavalinkNodeOptions
+                          {
+                              RestUri = "http://116.202.92.16:2333",
+                              Password = config.LavaLinkPassword,
+                              WebSocketUri = "ws://116.202.92.16:2333",
+                              AllowResuming = true
+
+                          })
+
+                    .AddSingleton<ILavalinkCache, LavalinkCache>()
+                    .AddSingleton<ISpotifyService, SpotifyService>()
+                    .AddSingleton<YoutubeService>()
+                    .AddSingleton<LyricsOptions>()
+                    .AddSingleton<LyricsService>()
+                    .AddSingleton(SpotifyClientConfig.CreateDefault().WithAuthenticator(new ClientCredentialsAuthenticator(config.SpotifyClientId, config.SpotifyClientSecret))))
+                .Build();
+
+            ServiceProvider = _host.Services;
+            return ServiceProvider;
+        }
+
+
+
+        public async Task InitBotAsync()
+        {
+            await BuildServiceProvider();
+
+            //_ = _host.StartAsync();
+
+            _host.Start();
+            var client = ServiceProvider.GetRequiredService<DiscordShardedClient>();
+            var audioService = ServiceProvider.GetRequiredService<IAudioService>();
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            client.ClientErrored += Discord_ClientErrored;
+            var slash = await client.UseSlashCommandsAsync(new SlashCommandsConfiguration
+            {
+                Services = ServiceProvider
+            });
+
+            slash.RegisterCommands<MusicCommands>(888493810554900491);
+            slash.RegisterCommands<PlayCommandGroup>(888493810554900491);
+
+            await client.StartAsync();
+            client.Ready += Client_Ready;
+            await audioService.InitializeAsync();
+
+            var trackingService = ServiceProvider.GetRequiredService<InactivityTrackingService>();
+            trackingService.ClearTrackers();
+            trackingService.AddTracker(DefaultInactivityTrackers.UsersInactivityTracker);
+            trackingService.AddTracker(DefaultInactivityTrackers.ChannelInactivityTracker);
+
+        }
+
+        private Task Client_Ready(DiscordClient sender, ReadyEventArgs e)
+        {
+            var slash = sender.GetSlashCommands();
+            slash.SlashCommandInvoked += Slash_SlashCommandInvoked;
+            slash.SlashCommandErrored += Slash_SlashCommandErrored;
+            sender.UpdateStatusAsync(new DiscordActivity($"/ commands! Shard {sender.ShardId}", ActivityType.Watching));
+
+            return Task.CompletedTask;
+        }
+
+        public async Task ShutdownBotAsync()
+        {
+            var audioService = ServiceProvider.GetRequiredService<IAudioService>();
+            var client = ServiceProvider.GetRequiredService<DiscordShardedClient>();
+
+            
+            audioService.Dispose();
+            await client.StopAsync();
+        }
+
+
+        private Task Discord_ClientErrored(DiscordClient sender, ClientErrorEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogDebug("Event {0} errored with Exception {3}", e.EventName, e.Exception);
+            if (e.Exception is NotFoundException)
+                logger.LogDebug($"{ ((NotFoundException)e.Exception).JsonMessage }");
+            if (e.Exception is BadRequestException)
+                logger.LogDebug($"{ ((BadRequestException)e.Exception).Errors }");
+            return Task.CompletedTask;
+        }
+
+        private Task Slash_SlashCommandErrored(SlashCommandsExtension sender, SlashCommandErrorEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogInformation("Command {0} invoked by {1} on Guild {2} with Exception {3}", e.Context.CommandName, e.Context.Member, e.Context.Guild, e.Exception);
+            if (e.Exception is NotFoundException)
+                logger.LogDebug($"{ ((NotFoundException)e.Exception).JsonMessage }");
+            if (e.Exception is BadRequestException)
+                logger.LogDebug($"{ ((BadRequestException)e.Exception).JsonMessage }");
+            return Task.CompletedTask;
+
+        }
+
+        private Task Slash_SlashCommandInvoked(SlashCommandsExtension sender, DSharpPlus.SlashCommands.EventArgs.SlashCommandInvokedEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogInformation("Command {0} invoked by {1} on Guild {2}", e.Context.CommandName, e.Context.Member, e.Context.Guild);
+
+            return Task.CompletedTask;
+        }
+    }
+}
diff --git a/TomatenMusicCore/TomatenMusicCore.csproj b/TomatenMusicCore/TomatenMusicCore.csproj
new file mode 100644
index 0000000..b424b4c
--- /dev/null
+++ b/TomatenMusicCore/TomatenMusicCore.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="DSharpPlus" Version="4.2.0-nightly-01088" />
+		<PackageReference Include="DSharpPlus.Interactivity" Version="4.2.0-nightly-01084" />
+		<PackageReference Include="DSharpPlus.SlashCommands" Version="4.2.0-nightly-01088" />
+		<PackageReference Include="Google.Apis.YouTube.v3" Version="1.56.0.2617" />
+		<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
+		<PackageReference Include="Lavalink4NET" Version="2.1.1" />
+		<PackageReference Include="Lavalink4NET.DSharpPlus" Version="2.1.1" />
+		<PackageReference Include="Lavalink4NET.Logging.Microsoft" Version="2.1.1-preview.5" />
+		<PackageReference Include="Lavalink4NET.MemoryCache" Version="2.1.1" />
+		<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.7" />
+		<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
+		<PackageReference Include="SpotifyAPI.Web" Version="6.2.2" />
+		<PackageReference Include="SpotifyAPI.Web.Auth" Version="6.2.2" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<None Update="config.json">
+			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
+		</None>
+	</ItemGroup>
+
+</Project>
diff --git a/TomatenMusicCore/Util/CollectionUtil.cs b/TomatenMusicCore/Util/CollectionUtil.cs
new file mode 100644
index 0000000..1511b14
--- /dev/null
+++ b/TomatenMusicCore/Util/CollectionUtil.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Security.Cryptography;
+using System.Linq;
+
+namespace TomatenMusic.Util
+{
+    static class CollectionUtil
+    {
+        public static void Shuffle<T>(this IList<T> list)
+        {
+            RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
+            int n = list.Count;
+            while (n > 1)
+            {
+                byte[] box = new byte[1];
+                do provider.GetBytes(box);
+                while (!(box[0] < n * (Byte.MaxValue / n)));
+                int k = (box[0] % n);
+                n--;
+                T value = list[k];
+                list[k] = list[n];
+                list[n] = value;
+            }
+        }
+
+        /* public static void Remove<T>(this IList<T> list, T item)
+         {
+             List<T> newList = new List<T>();
+             bool done = false;
+             foreach (var i in list)
+             {
+                 if (i.Equals(item) && !done)
+                 {
+                     done = true;
+                     continue;
+                 }
+
+                 newList.Add(i);
+             }
+
+             list = newList;
+         }*/
+
+
+
+        public static class Arrays
+        {
+            public static IList<T> AsList<T>(params T[] source)
+            {
+                return source;
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Util/Common.cs b/TomatenMusicCore/Util/Common.cs
new file mode 100644
index 0000000..773b7a4
--- /dev/null
+++ b/TomatenMusicCore/Util/Common.cs
@@ -0,0 +1,275 @@
+using DSharpPlus.Entities;
+using HtmlAgilityPack;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Music;
+using System.Threading.Tasks;
+using System.Linq;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Util
+{
+    class Common
+    {
+
+        public static DiscordEmbed AsEmbed(LavalinkTrack track, LoopType loopType, int position = -1)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle(track.Title)
+                .AddField("Length", Common.GetTimestamp(track.Duration), true);
+            if (context.IsFile)
+            {
+                builder.WithAuthor(track.Author);
+            }
+            else
+                builder
+                    .WithAuthor(track.Author, context.YoutubeAuthorUri.ToString(), context.YoutubeAuthorThumbnail.ToString())
+                    .WithUrl(context.YoutubeUri)
+                    .WithImageUrl(context.YoutubeThumbnail)
+                    .WithDescription(context.YoutubeDescription);
+
+
+
+            if (position != -1)
+            {
+                builder.AddField("Position", (position == 0 ? "Now Playing" : position.ToString()), true);
+            }
+            builder.AddField("Current Queue Loop", loopType.ToString(), true);
+            if (!context.IsFile)
+            {
+                builder.AddField("Views", $"{context.YoutubeViews:N0} Views", true);
+                builder.AddField("Rating", $"{context.YoutubeLikes:N0} 👍", true);
+                builder.AddField("Upload Date", $"{context.YoutubeUploadDate.ToString("dd. MMMM, yyyy")}", true);
+                builder.AddField("Comments", context.YoutubeCommentCount == null ? "Comments Disabled" : $"{context.YoutubeCommentCount:N0} Comments", true);
+                builder.AddField("Channel Subscriptions", $"{context.YoutubeAuthorSubs:N0} Subscribers", true);
+            }
+
+            return builder;
+        }
+
+        public static DiscordEmbed AsEmbed(LavalinkTrack track, int position = -1)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle(track.Title)
+                .WithUrl(context.YoutubeUri)
+                .WithImageUrl(context.YoutubeThumbnail)
+                .WithDescription(context.YoutubeDescription)
+                .AddField("Length", Common.GetTimestamp(track.Duration), true);
+
+            if (context.IsFile)
+            {
+                builder.WithAuthor(track.Author);
+            }
+            else
+                builder
+                    .WithAuthor(track.Author, context.YoutubeAuthorUri.ToString(), context.YoutubeAuthorThumbnail.ToString())
+                    .WithUrl(context.YoutubeUri);
+
+            if (position != -1)
+            {
+                builder.AddField("Position", (position == 0 ? "Now Playing" : position.ToString()), true);
+            }
+
+            if (!context.IsFile)
+            {
+                builder.AddField("Views", $"{context.YoutubeViews:N0} Views", true);
+                builder.AddField("Rating", $"{context.YoutubeLikes:N0} 👍", true);
+                builder.AddField("Upload Date", $"{context.YoutubeUploadDate.ToString("dd. MMMM, yyyy")}", true);
+                builder.AddField("Comments", context.YoutubeCommentCount == null ? "Comments Disabled" : $"{context.YoutubeCommentCount:N0} Comments", true);
+                builder.AddField("Channel Subscriptions", $"{context.YoutubeAuthorSubs:N0} Subscribers", true);
+            }
+
+            return builder;
+        }
+
+        public static DiscordEmbed AsEmbed(LavalinkPlaylist playlist)
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+
+            if (playlist is YoutubePlaylist)
+            {
+                YoutubePlaylist youtubePlaylist = (YoutubePlaylist)playlist;
+                builder
+                .WithAuthor(playlist.AuthorName, playlist.AuthorUri.ToString(), youtubePlaylist.AuthorThumbnail.ToString())
+                .WithTitle(playlist.Name)
+                .WithUrl(playlist.Url)
+                .WithDescription(playlist.Description)
+                .WithImageUrl(youtubePlaylist.Thumbnail)
+                .AddField("Tracks", TrackListString(playlist.Tracks), false)
+                .AddField("Track Count", $"{playlist.Tracks.Count()} Tracks", true)
+                .AddField("Length", $"{Common.GetTimestamp(playlist.GetLength())}", true)
+                .AddField("Create Date", $"{youtubePlaylist.CreationTime:dd. MMMM, yyyy}", true);
+                
+            }else if (playlist is SpotifyPlaylist)
+            {
+                SpotifyPlaylist spotifyPlaylist = (SpotifyPlaylist)playlist;
+                builder
+                .WithAuthor(playlist.AuthorName, playlist.AuthorUri.ToString(), spotifyPlaylist.AuthorThumbnail.ToString())
+                .WithTitle(playlist.Name)
+                .WithUrl(playlist.Url)
+                .WithDescription(playlist.Description)
+                .AddField("Tracks", TrackListString(playlist.Tracks), false)
+                .AddField("Track Count", $"{playlist.Tracks.Count()} Tracks", true)
+                .AddField("Length", $"{Common.GetTimestamp(playlist.GetLength())}", true)
+                .AddField("Spotify Followers", $"{spotifyPlaylist.Followers:N0}", true);
+            }
+
+            return builder;
+        }
+
+        public static DiscordEmbed GetQueueEmbed(GuildPlayer player)
+        {
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+
+            builder.WithDescription(TrackListString(player.PlayerQueue.Queue));
+            builder.WithTitle("Current Queue");
+            builder.WithAuthor($"{player.PlayerQueue.Queue.Count} Songs");
+
+            TimeSpan timeSpan = TimeSpan.FromTicks(0);
+
+            foreach (var track in player.PlayerQueue.Queue)
+            {
+                timeSpan = timeSpan.Add(track.Duration);
+            }
+
+            builder.AddField("Length", GetTimestamp(timeSpan), true);
+            builder.AddField("Loop Type", player.PlayerQueue.LoopType.ToString(), true);
+            builder.AddField("Autoplay", player.Autoplay ? "✅" : "❌", true);
+            if (player.PlayerQueue.PlayedTracks.Any())
+                builder.AddField("History", TrackListString(player.PlayerQueue.PlayedTracks), true);
+            if (player.PlayerQueue.CurrentPlaylist != null)
+                builder.AddField("Current Playlist", $"[{player.PlayerQueue.CurrentPlaylist.Name}]({player.PlayerQueue.CurrentPlaylist.Url})", true);
+
+            return builder;
+        }
+
+        public static string TrackListString(IEnumerable<LavalinkTrack> tracks)
+        {
+            StringBuilder builder = new StringBuilder();
+            int count = 1;
+            foreach (LavalinkTrack track in tracks)
+            {
+                FullTrackContext context = (FullTrackContext)track.Context;
+                if (count > 15)
+                {
+                    builder.Append(String.Format("***And {0} more...***", tracks.Count() - 15));
+                    break;
+                }
+
+                builder.Append(count).Append(": ").Append($"[{track.Title}]({context.YoutubeUri})").Append(" [").Append(Common.GetTimestamp(track.Duration)).Append("] | ");
+                builder.Append($"[{track.Author}]({context.YoutubeAuthorUri})").Append("\n\n");
+                count++;
+            }
+            builder.Append(" ");
+            return builder.ToString();
+        }
+
+        public static string GetTimestamp(TimeSpan timeSpan)
+        {
+            if (timeSpan.Hours > 0)
+                return String.Format("{0:hh\\:mm\\:ss}", timeSpan);
+            else
+                return String.Format("{0:mm\\:ss}", timeSpan);
+        }
+
+        public static TimeSpan ToTimeSpan(string text)
+        {
+            string[] input = text.Split(" ");
+            TimeSpan timeSpan = TimeSpan.FromMilliseconds(0);
+
+            foreach (var item in input)
+            {
+                var l = item.Length - 1;
+                var value = item.Substring(0, l);
+                var type = item.Substring(l, 1);
+
+                switch (type)
+                {
+                    case "d": 
+                        timeSpan = timeSpan.Add(TimeSpan.FromDays(double.Parse(value)));
+                        break;
+                    case "h":
+                        timeSpan = timeSpan.Add(TimeSpan.FromHours(double.Parse(value))); 
+                        break;
+                    case "m":
+                        timeSpan = timeSpan.Add(TimeSpan.FromMinutes(double.Parse(value)));
+                        break;
+                    case "s":
+                        timeSpan = timeSpan.Add(TimeSpan.FromSeconds(double.Parse(value)));
+                        break;
+                    case "f":
+                        timeSpan = timeSpan.Add(TimeSpan.FromMilliseconds(double.Parse(value))); 
+                        break;
+                    case "z":
+                        timeSpan = timeSpan.Add(TimeSpan.FromTicks(long.Parse(value)));
+                        break;
+                    default:
+                        timeSpan = timeSpan.Add(TimeSpan.FromDays(double.Parse(value)));
+                        break;
+                }
+            }
+
+            return timeSpan;
+        }
+
+       public static string ProgressBar(int current, int max)
+        {
+            int percentage = (current * 100) / max;
+            int rounded = (int) Math.Round(((double) percentage / 10));
+
+            StringBuilder builder = new StringBuilder();
+
+            for (int i = 0; i <= 10; i++)
+            {
+                if (i == rounded)
+                    builder.Append("🔘");
+                else
+                    builder.Append("─");
+            }
+
+            return builder.ToString();
+        }
+
+        public async static Task<DiscordEmbed> CurrentSongEmbedAsync(GuildPlayer player)
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+            LavalinkTrack track = player.CurrentTrack;
+
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            string progressBar = $"|{ProgressBar((int)player.Position.Position.TotalSeconds, (int)track.Duration.TotalSeconds)}|\n [{Common.GetTimestamp(player.Position.Position)}/{Common.GetTimestamp(track.Duration)}]";
+
+            builder.WithAuthor(track.Author, context.YoutubeAuthorUri.ToString(), context.YoutubeAuthorThumbnail.ToString());
+            builder.WithTitle(track.Title);
+            builder.WithUrl(context.YoutubeUri);
+            builder.WithImageUrl(context.YoutubeThumbnail);
+            builder.AddField("Length", Common.GetTimestamp(track.Duration), true);
+            builder.AddField("Loop", player.PlayerQueue.LoopType.ToString(), true);
+            builder.AddField("Progress", progressBar, true);
+            if (!context.IsFile)
+            {
+                builder.AddField("Views", $"{context.YoutubeViews:N0} Views", true);
+                builder.AddField("Rating", $"{context.YoutubeLikes:N0} 👍", true);
+                builder.AddField("Upload Date", $"{context.YoutubeUploadDate.ToString("dd. MMMM, yyyy")}", true);
+                builder.AddField("Comments", $"{context.YoutubeCommentCount:N0} Comments", true);
+                builder.AddField("Channel Subscriptions", $"{context.YoutubeAuthorSubs:N0} Subscribers", true);
+
+            }
+
+
+            return builder;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Util/PageManager.cs b/TomatenMusicCore/Util/PageManager.cs
new file mode 100644
index 0000000..b78849a
--- /dev/null
+++ b/TomatenMusicCore/Util/PageManager.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+
+namespace TomatenMusic.Util
+{
+    class PageManager<T>
+    {
+
+		private List<T> Items;
+		private int PageSize;
+
+		public PageManager(List<T> allItems, int pageSize)
+		{
+			this.Items = allItems;
+			this.PageSize = pageSize;
+		}
+
+		public List<T> GetPage(int page)
+		{
+			if (page <= GetTotalPages() && page > 0)
+			{
+				List<T> onPage = new List<T>();
+				page--;
+
+				int lowerBound = page * PageSize;
+				int upperBound = Math.Min(lowerBound + PageSize, Items.Count);
+
+				for (int i = lowerBound; i < upperBound; i++)
+				{
+					onPage.Add(Items[i]);
+				}
+
+				return onPage;
+			}
+			else
+				return new List<T>();
+		}
+
+		public void AddItem(T Item)
+		{
+			if (Items.Contains(Item))
+			{
+				return;
+			}
+			Items.Add(Item);
+		}
+
+		public void RemoveItem(T Item)
+		{
+
+		if (Items.Contains(Item))
+			Items.Remove(Item);
+		}
+
+		public int GetTotalPages()
+		{
+			int totalPages = (int)Math.Ceiling((double)Items.Count / PageSize);
+
+			return totalPages;
+		}
+
+		public List<T> GetContents()
+		{
+			return Items;
+		}
+    }
+}
diff --git a/TomatenMusicCore/Util/RandomUtil.cs b/TomatenMusicCore/Util/RandomUtil.cs
new file mode 100644
index 0000000..e776853
--- /dev/null
+++ b/TomatenMusicCore/Util/RandomUtil.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+
+namespace TomatenMusic.Util
+{
+    class RandomUtil
+    {
+
+        public static string GenerateGuid()
+        {
+            return String.Concat(Guid.NewGuid().ToString("N").Select(c => (char)(c + 17))).ToLower();
+        }
+    }
+}
diff --git a/TomatenMusicCore/config.json b/TomatenMusicCore/config.json
new file mode 100644
index 0000000..40eb8ec
--- /dev/null
+++ b/TomatenMusicCore/config.json
@@ -0,0 +1,7 @@
+{
+  "TOKEN": "ODQwNjQ5NjU1MTAwMjQzOTY4.YJbSAA.jwzaw5N-tAUeiEdvE969wfBAU7o",
+  "LavaLinkPassword": "SGWaldsolms9",
+  "SpotifyClientId": "14b77fa47f2f492db58cbdca8f1e5d9c",
+  "SpotifyClientSecret": "c247625f0cfe4b72a1faa01b7c5b8eea",
+  "YoutubeApiKey": "AIzaSyBIcTl9JQ9jF412mX0Wfp_3Y-4a-V0SASQ"
+}
\ No newline at end of file
-- 
2.45.2


From 048010a1e0d3a48664007c94b75afe85bc698df2 Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Tue, 15 Mar 2022 22:39:42 +0100
Subject: [PATCH 03/21] Delete config.json

---
 TomatenMusic Api/config.json | 7 -------
 1 file changed, 7 deletions(-)
 delete mode 100644 TomatenMusic Api/config.json

diff --git a/TomatenMusic Api/config.json b/TomatenMusic Api/config.json
deleted file mode 100644
index 40eb8ec..0000000
--- a/TomatenMusic Api/config.json	
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "TOKEN": "ODQwNjQ5NjU1MTAwMjQzOTY4.YJbSAA.jwzaw5N-tAUeiEdvE969wfBAU7o",
-  "LavaLinkPassword": "SGWaldsolms9",
-  "SpotifyClientId": "14b77fa47f2f492db58cbdca8f1e5d9c",
-  "SpotifyClientSecret": "c247625f0cfe4b72a1faa01b7c5b8eea",
-  "YoutubeApiKey": "AIzaSyBIcTl9JQ9jF412mX0Wfp_3Y-4a-V0SASQ"
-}
\ No newline at end of file
-- 
2.45.2


From 202ddfe02ecaaa7ffeef09b5a003cddfebc33957 Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Tue, 15 Mar 2022 22:39:55 +0100
Subject: [PATCH 04/21] Delete config.json

---
 TomatenMusicCore/config.json | 7 -------
 1 file changed, 7 deletions(-)
 delete mode 100644 TomatenMusicCore/config.json

diff --git a/TomatenMusicCore/config.json b/TomatenMusicCore/config.json
deleted file mode 100644
index 40eb8ec..0000000
--- a/TomatenMusicCore/config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "TOKEN": "ODQwNjQ5NjU1MTAwMjQzOTY4.YJbSAA.jwzaw5N-tAUeiEdvE969wfBAU7o",
-  "LavaLinkPassword": "SGWaldsolms9",
-  "SpotifyClientId": "14b77fa47f2f492db58cbdca8f1e5d9c",
-  "SpotifyClientSecret": "c247625f0cfe4b72a1faa01b7c5b8eea",
-  "YoutubeApiKey": "AIzaSyBIcTl9JQ9jF412mX0Wfp_3Y-4a-V0SASQ"
-}
\ No newline at end of file
-- 
2.45.2


From 81cf83f4b58edd46b323bb737335262adda26b51 Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Tue, 15 Mar 2022 22:49:56 +0100
Subject: [PATCH 05/21] Update TomatenMusicCore.csproj

---
 TomatenMusicCore/TomatenMusicCore.csproj | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/TomatenMusicCore/TomatenMusicCore.csproj b/TomatenMusicCore/TomatenMusicCore.csproj
index b424b4c..2a8bc65 100644
--- a/TomatenMusicCore/TomatenMusicCore.csproj
+++ b/TomatenMusicCore/TomatenMusicCore.csproj
@@ -4,6 +4,10 @@
     <TargetFramework>net6.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
+    <RestoreAdditionalProjectSources>
+      https://api.nuget.org/v3/index.json;
+      https://nuget.emzi0767.com/api/v3/index.json
+    </RestoreAdditionalProjectSources>
   </PropertyGroup>
 
 	<ItemGroup>
-- 
2.45.2


From 81eeda9c7cc0115d1d31e73168babde9665c65df Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Tue, 15 Mar 2022 22:52:06 +0100
Subject: [PATCH 06/21] Update TomatenMusic Api.csproj

---
 TomatenMusic Api/TomatenMusic Api.csproj | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/TomatenMusic Api/TomatenMusic Api.csproj b/TomatenMusic Api/TomatenMusic Api.csproj
index 1d0c4f1..720b797 100644
--- a/TomatenMusic Api/TomatenMusic Api.csproj	
+++ b/TomatenMusic Api/TomatenMusic Api.csproj	
@@ -5,6 +5,10 @@
     <Nullable>enable</Nullable>
     <ImplicitUsings>enable</ImplicitUsings>
     <RootNamespace>TomatenMusic_Api</RootNamespace>
+    <RestoreAdditionalProjectSources>
+      https://api.nuget.org/v3/index.json;
+      https://nuget.emzi0767.com/api/v3/index.json
+    </RestoreAdditionalProjectSources>
   </PropertyGroup>
 
   <ItemGroup>
-- 
2.45.2


From a3f8cd1bdc5465bb22a95fc5c0d08aa859ec3d9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 15 Mar 2022 23:07:24 +0100
Subject: [PATCH 07/21] config.json sample Add and Readme.md update

---
 README.md                    | 3 ++-
 TomatenMusic Api/config.json | 8 ++++++++
 TomatenMusic V2.sln          | 5 +++++
 3 files changed, 15 insertions(+), 1 deletion(-)
 create mode 100644 TomatenMusic Api/config.json

diff --git a/README.md b/README.md
index 1b39896..90cb3f9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-TomatenMusic V2
\ No newline at end of file
+![TeamCity build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build/statusIcon.svg)
+# TomatenMusic
\ No newline at end of file
diff --git a/TomatenMusic Api/config.json b/TomatenMusic Api/config.json
new file mode 100644
index 0000000..ae772c3
--- /dev/null
+++ b/TomatenMusic Api/config.json	
@@ -0,0 +1,8 @@
+{
+  "TOKEN"´: "YOUR_BOT_TOKEN",
+  "LavaLinkPassword": "",
+  "SpotifyClientId": "",
+  "SpotifyClientSecret": "",
+  "YoutubeApiKey": ""
+
+}
diff --git a/TomatenMusic V2.sln b/TomatenMusic V2.sln
index 5141398..2039acd 100644
--- a/TomatenMusic V2.sln	
+++ b/TomatenMusic V2.sln	
@@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusic Api", "Tomaten
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusicCore", "TomatenMusicCore\TomatenMusicCore.csproj", "{54481E45-9FE3-4CF3-9CE9-489B678AE472}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A3F84EF2-B7C2-44F0-B392-6824AE96530A}"
+	ProjectSection(SolutionItems) = preProject
+		Readme.md = Readme.md
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
-- 
2.45.2


From 92e3764c73a34b2e34239ed888d1b1745f4e0815 Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Wed, 16 Mar 2022 10:46:31 +0100
Subject: [PATCH 08/21] Update README.md

---
 README.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 90cb3f9..3aa60e7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
-![TeamCity build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build/statusIcon.svg)
-# TomatenMusic
\ No newline at end of file
+Dev ![TeamCity dev build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build,branch:name:dev/statusIcon.svg)
+Master ![TeamCity master build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build,branch:name:master/statusIcon.svg)
+
+# TomatenMusic
-- 
2.45.2


From 147eed234fd4298b2a9ac56665320d437f8cce45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 29 Mar 2022 22:12:22 +0200
Subject: [PATCH 09/21] INIT

---
 .gitattributes                                |  63 ++
 .gitignore                                    | 364 +++++++++++
 GitVersion.yml                                |  33 +
 README.md                                     |   6 +
 TomatenMusic V2.sln                           |  37 ++
 TomatenMusic/.config/dotnet-tools.json        |  12 +
 .../Auth/Controllers/UsersController.cs       |  37 ++
 TomatenMusic/Auth/Entities/User.cs            |  14 +
 TomatenMusic/Auth/Helpers/AppSettings.cs      |   6 +
 .../Auth/Helpers/AuthorizeAttribute.cs        |  19 +
 TomatenMusic/Auth/Helpers/JwtMiddleware.cs    |  58 ++
 .../Auth/Models/AuthenticateRequest.cs        |  12 +
 .../Auth/Models/AuthenticateResponse.cs       |  22 +
 TomatenMusic/Auth/Services/UserService.cs     |  75 +++
 TomatenMusic/Controllers/PlayerController.cs  | 146 +++++
 TomatenMusic/Models/BasicTrackInfo.cs         |  42 ++
 TomatenMusic/Models/ChannelConnectRequest.cs  |  13 +
 .../Models/ChannelDisconnectRequest.cs        |   7 +
 .../Models/EventArgs/ChannelConnectArgs.cs    |  18 +
 .../Models/EventArgs/ChannelDisconnectArgs.cs |  13 +
 .../Models/EventArgs/TrackPlayArgs.cs         |  22 +
 TomatenMusic/Models/PlayerConnectionInfo.cs   |  62 ++
 TomatenMusic/Models/TrackPlayRequest.cs       |  10 +
 TomatenMusic/Program.cs                       |  41 ++
 TomatenMusic/Properties/launchSettings.json   |  31 +
 TomatenMusic/Services/EventBus.cs             |  31 +
 .../Services/TomatenMusicDataService.cs       |  91 +++
 TomatenMusic/Services/TomatenMusicService.cs  |  92 +++
 TomatenMusic/TomatenMusic.csproj              |  39 ++
 TomatenMusic/appsettings.Development.json     |   8 +
 TomatenMusic/appsettings.json                 |  11 +
 TomatenMusic/config.json                      |   8 +
 .../Commands/Checks/OnlyGuildCheck.cs         |  24 +
 .../Checks/UserInMusicChannelCheck.cs         |  41 ++
 .../Checks/UserInVoiceChannelCheck.cs         |  27 +
 TomatenMusicCore/Commands/MusicCommands.cs    | 289 +++++++++
 TomatenMusicCore/Commands/PlayCommandGroup.cs | 327 ++++++++++
 .../Music/Entitites/FullTrackContext.cs       |  72 +++
 .../Music/Entitites/ILavalinkPlaylist.cs      |  36 ++
 .../Music/Entitites/IPlayableItem.cs          |  17 +
 .../Music/Entitites/SpotifyPlaylist.cs        |  63 ++
 .../Music/Entitites/TomatenMusicTrack.cs      |  50 ++
 TomatenMusicCore/Music/Entitites/TrackList.cs | 567 ++++++++++++++++++
 .../Music/Entitites/YoutubePlaylist.cs        |  77 +++
 TomatenMusicCore/Music/GuildPlayer.cs         | 290 +++++++++
 TomatenMusicCore/Music/LoopType.cs            |  17 +
 TomatenMusicCore/Music/MusicActionResponse.cs |  31 +
 TomatenMusicCore/Music/PlayerQueue.cs         | 165 +++++
 .../Prompt/Buttons/AddToQueueButton.cs        |  59 ++
 .../Prompt/Buttons/PlayNowButton.cs           |  60 ++
 .../PlaylistSongSelectorPrompt.cs             | 105 ++++
 .../Prompt/Implementation/QueuePrompt.cs      | 278 +++++++++
 .../Prompt/Implementation/SongActionPrompt.cs |  35 ++
 .../Implementation/SongListActionPrompt.cs    |  45 ++
 .../Implementation/SongSelectorPrompt.cs      | 103 ++++
 .../Implementation/StringSelectorPrompt.cs    |  45 ++
 TomatenMusicCore/Prompt/Model/ButtonPrompt.cs |  41 ++
 .../Prompt/Model/CombinedPrompt.cs            |  62 ++
 .../Prompt/Model/DiscordPromptBase.cs         | 499 +++++++++++++++
 .../Prompt/Model/PaginatedButtonPrompt.cs     | 138 +++++
 .../Prompt/Model/PaginatedSelectPrompt.cs     | 160 +++++
 TomatenMusicCore/Prompt/Model/PromptState.cs  |  15 +
 TomatenMusicCore/Prompt/Model/SelectPrompt.cs |  48 ++
 .../Prompt/Option/ButtonPromptOption.cs       |  32 +
 .../Prompt/Option/IPromptOption.cs            |  23 +
 .../Prompt/Option/SelectMenuOption.cs         |  23 +
 .../Prompt/Option/SelectMenuPromptOption.cs   |  50 ++
 TomatenMusicCore/Services/SpotifyService.cs   | 196 ++++++
 TomatenMusicCore/Services/TrackProvider.cs    | 135 +++++
 TomatenMusicCore/Services/YoutubeService.cs   | 150 +++++
 TomatenMusicCore/TomatenMusicBot.cs           | 204 +++++++
 TomatenMusicCore/TomatenMusicCore.csproj      |  35 ++
 TomatenMusicCore/Util/CollectionUtil.cs       |  56 ++
 TomatenMusicCore/Util/Common.cs               | 272 +++++++++
 TomatenMusicCore/Util/PageManager.cs          |  68 +++
 TomatenMusicCore/Util/RandomUtil.cs           |  16 +
 76 files changed, 6489 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 GitVersion.yml
 create mode 100644 README.md
 create mode 100644 TomatenMusic V2.sln
 create mode 100644 TomatenMusic/.config/dotnet-tools.json
 create mode 100644 TomatenMusic/Auth/Controllers/UsersController.cs
 create mode 100644 TomatenMusic/Auth/Entities/User.cs
 create mode 100644 TomatenMusic/Auth/Helpers/AppSettings.cs
 create mode 100644 TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs
 create mode 100644 TomatenMusic/Auth/Helpers/JwtMiddleware.cs
 create mode 100644 TomatenMusic/Auth/Models/AuthenticateRequest.cs
 create mode 100644 TomatenMusic/Auth/Models/AuthenticateResponse.cs
 create mode 100644 TomatenMusic/Auth/Services/UserService.cs
 create mode 100644 TomatenMusic/Controllers/PlayerController.cs
 create mode 100644 TomatenMusic/Models/BasicTrackInfo.cs
 create mode 100644 TomatenMusic/Models/ChannelConnectRequest.cs
 create mode 100644 TomatenMusic/Models/ChannelDisconnectRequest.cs
 create mode 100644 TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs
 create mode 100644 TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs
 create mode 100644 TomatenMusic/Models/EventArgs/TrackPlayArgs.cs
 create mode 100644 TomatenMusic/Models/PlayerConnectionInfo.cs
 create mode 100644 TomatenMusic/Models/TrackPlayRequest.cs
 create mode 100644 TomatenMusic/Program.cs
 create mode 100644 TomatenMusic/Properties/launchSettings.json
 create mode 100644 TomatenMusic/Services/EventBus.cs
 create mode 100644 TomatenMusic/Services/TomatenMusicDataService.cs
 create mode 100644 TomatenMusic/Services/TomatenMusicService.cs
 create mode 100644 TomatenMusic/TomatenMusic.csproj
 create mode 100644 TomatenMusic/appsettings.Development.json
 create mode 100644 TomatenMusic/appsettings.json
 create mode 100644 TomatenMusic/config.json
 create mode 100644 TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
 create mode 100644 TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
 create mode 100644 TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
 create mode 100644 TomatenMusicCore/Commands/MusicCommands.cs
 create mode 100644 TomatenMusicCore/Commands/PlayCommandGroup.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/FullTrackContext.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/IPlayableItem.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/TrackList.cs
 create mode 100644 TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
 create mode 100644 TomatenMusicCore/Music/GuildPlayer.cs
 create mode 100644 TomatenMusicCore/Music/LoopType.cs
 create mode 100644 TomatenMusicCore/Music/MusicActionResponse.cs
 create mode 100644 TomatenMusicCore/Music/PlayerQueue.cs
 create mode 100644 TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
 create mode 100644 TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/PlaylistSongSelectorPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/PromptState.cs
 create mode 100644 TomatenMusicCore/Prompt/Model/SelectPrompt.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/IPromptOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
 create mode 100644 TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
 create mode 100644 TomatenMusicCore/Services/SpotifyService.cs
 create mode 100644 TomatenMusicCore/Services/TrackProvider.cs
 create mode 100644 TomatenMusicCore/Services/YoutubeService.cs
 create mode 100644 TomatenMusicCore/TomatenMusicBot.cs
 create mode 100644 TomatenMusicCore/TomatenMusicCore.csproj
 create mode 100644 TomatenMusicCore/Util/CollectionUtil.cs
 create mode 100644 TomatenMusicCore/Util/Common.cs
 create mode 100644 TomatenMusicCore/Util/PageManager.cs
 create mode 100644 TomatenMusicCore/Util/RandomUtil.cs

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a069ae1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,364 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+/GitVersion.yml
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 0000000..af41166
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,33 @@
+mode: ContinuousDelivery
+branches:
+  main:
+    regex: ^master$|^main$
+    mode: ContinuousDelivery
+    tag: ''
+    increment: Minor
+    prevent-increment-of-merged-branch-version: true
+    track-merge-target: false
+    source-branches: [ 'develop', 'release' ]
+    tracks-release-branches: false
+    is-release-branch: true
+    is-mainline: true
+    pre-release-weight: 55000
+  develop:
+    regex: ^dev(elop)?(ment)?$
+    mode: ContinuousDeployment
+    tag: pre
+    increment: Patch
+    prevent-increment-of-merged-branch-version: false
+    track-merge-target: true
+    source-branches: []
+    tracks-release-branches: true
+    is-release-branch: false
+    is-mainline: false
+    pre-release-weight: 0
+ignore:
+  sha: []
+merge-message-formats: {}
+major-version-bump-message: '\+semver:\s?(breaking|major)'
+minor-version-bump-message: '\+semver:\s?(feature|minor)'
+patch-version-bump-message: '\+semver:\s?(fix|patch)'
+commit-message-incrementing: Enabled
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3223845
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+Dev ![TeamCity dev build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build,branch:name:dev/statusIcon.svg)
+Master ![TeamCity master build status](https://ci.tomatentum.net/app/rest/builds/buildType:id:TomatenMusicV2_Build,branch:name:master/statusIcon.svg)
+
+# TomatenMusic
+
+Project CI can be found [here](https://ci.tomatentum.net/project/TomatenMusicV2 "Tomatentum CI")
diff --git a/TomatenMusic V2.sln b/TomatenMusic V2.sln
new file mode 100644
index 0000000..b01aa23
--- /dev/null
+++ b/TomatenMusic V2.sln	
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32228.430
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A3F84EF2-B7C2-44F0-B392-6824AE96530A}"
+	ProjectSection(SolutionItems) = preProject
+		GitVersion.yml = GitVersion.yml
+		Readme.md = Readme.md
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusic", "TomatenMusic\TomatenMusic.csproj", "{E612AAB3-9A73-47F3-ACA0-D3A4CC627D4E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TomatenMusicCore", "TomatenMusicCore\TomatenMusicCore.csproj", "{40B1E82B-656D-413B-B636-EB0AE84391E2}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{E612AAB3-9A73-47F3-ACA0-D3A4CC627D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E612AAB3-9A73-47F3-ACA0-D3A4CC627D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E612AAB3-9A73-47F3-ACA0-D3A4CC627D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E612AAB3-9A73-47F3-ACA0-D3A4CC627D4E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{40B1E82B-656D-413B-B636-EB0AE84391E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{40B1E82B-656D-413B-B636-EB0AE84391E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{40B1E82B-656D-413B-B636-EB0AE84391E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{40B1E82B-656D-413B-B636-EB0AE84391E2}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {9ED3DD11-344B-4EB0-99E9-ED348676163A}
+	EndGlobalSection
+EndGlobal
diff --git a/TomatenMusic/.config/dotnet-tools.json b/TomatenMusic/.config/dotnet-tools.json
new file mode 100644
index 0000000..23f4f07
--- /dev/null
+++ b/TomatenMusic/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-ef": {
+      "version": "6.0.3",
+      "commands": [
+        "dotnet-ef"
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Controllers/UsersController.cs b/TomatenMusic/Auth/Controllers/UsersController.cs
new file mode 100644
index 0000000..f441cf2
--- /dev/null
+++ b/TomatenMusic/Auth/Controllers/UsersController.cs
@@ -0,0 +1,37 @@
+namespace WebApi.Controllers;
+
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Models;
+using TomatenMusic_Api.Auth.Services;
+
+[ApiController]
+[Route("api/[controller]")]
+public class UsersController : ControllerBase
+{
+    private IUserService _userService;
+
+    public UsersController(IUserService userService)
+    {
+        _userService = userService;
+    }
+
+    [HttpPost("authenticate")]
+    public IActionResult Authenticate(AuthenticateRequest model)
+    {
+        var response = _userService.Authenticate(model);
+
+        if (response == null)
+            return BadRequest(new { message = "Username or password is incorrect" });
+
+        return Ok(response);
+    }
+
+    [Authorize]
+    [HttpGet]
+    public IActionResult GetAll()
+    {
+        var users = _userService.GetAll();
+        return Ok(users);
+    }
+}
diff --git a/TomatenMusic/Auth/Entities/User.cs b/TomatenMusic/Auth/Entities/User.cs
new file mode 100644
index 0000000..27a63fc
--- /dev/null
+++ b/TomatenMusic/Auth/Entities/User.cs
@@ -0,0 +1,14 @@
+namespace TomatenMusic_Api.Auth.Entities;
+
+using System.Text.Json.Serialization;
+
+public class User
+{
+    public int Id { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+    public string Username { get; set; }
+
+    [JsonIgnore]
+    public string Password { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Helpers/AppSettings.cs b/TomatenMusic/Auth/Helpers/AppSettings.cs
new file mode 100644
index 0000000..c0c6dfc
--- /dev/null
+++ b/TomatenMusic/Auth/Helpers/AppSettings.cs
@@ -0,0 +1,6 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+public class AppSettings
+{
+    public string Secret { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs b/TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs
new file mode 100644
index 0000000..dac92ab
--- /dev/null
+++ b/TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs
@@ -0,0 +1,19 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using TomatenMusic_Api.Auth.Entities;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class AuthorizeAttribute : Attribute, IAuthorizationFilter
+{
+    public void OnAuthorization(AuthorizationFilterContext context)
+    {
+        var user = (User)context.HttpContext.Items["User"];
+        if (user == null)
+        {
+            // not logged in
+            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
+        }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Helpers/JwtMiddleware.cs b/TomatenMusic/Auth/Helpers/JwtMiddleware.cs
new file mode 100644
index 0000000..f1102d9
--- /dev/null
+++ b/TomatenMusic/Auth/Helpers/JwtMiddleware.cs
@@ -0,0 +1,58 @@
+namespace TomatenMusic_Api.Auth.Helpers;
+
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Text;
+using TomatenMusic_Api.Auth.Services;
+
+public class JwtMiddleware
+{
+    private readonly RequestDelegate _next;
+    private readonly AppSettings _appSettings;
+
+    public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
+    {
+        _next = next;
+        _appSettings = appSettings.Value;
+    }
+
+    public async Task Invoke(HttpContext context, IUserService userService)
+    {
+        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+
+        if (token != null)
+            attachUserToContext(context, userService, token);
+
+        await _next(context);
+    }
+
+    private void attachUserToContext(HttpContext context, IUserService userService, string token)
+    {
+        try
+        {
+            var tokenHandler = new JwtSecurityTokenHandler();
+            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
+            tokenHandler.ValidateToken(token, new TokenValidationParameters
+            {
+                ValidateIssuerSigningKey = true,
+                IssuerSigningKey = new SymmetricSecurityKey(key),
+                ValidateIssuer = false,
+                ValidateAudience = false,
+                // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
+                ClockSkew = TimeSpan.Zero
+            }, out SecurityToken validatedToken);
+
+            var jwtToken = (JwtSecurityToken)validatedToken;
+            var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
+
+            // attach user to context on successful jwt validation
+            context.Items["User"] = userService.GetById(userId);
+        }
+        catch
+        {
+            // do nothing if jwt validation fails
+            // user is not attached to context so request won't have access to secure routes
+        }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Models/AuthenticateRequest.cs b/TomatenMusic/Auth/Models/AuthenticateRequest.cs
new file mode 100644
index 0000000..7c9426d
--- /dev/null
+++ b/TomatenMusic/Auth/Models/AuthenticateRequest.cs
@@ -0,0 +1,12 @@
+namespace TomatenMusic_Api.Auth.Models;
+
+using System.ComponentModel.DataAnnotations;
+
+public class AuthenticateRequest
+{
+    [Required]
+    public string Username { get; set; }
+
+    [Required]
+    public string Password { get; set; }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Models/AuthenticateResponse.cs b/TomatenMusic/Auth/Models/AuthenticateResponse.cs
new file mode 100644
index 0000000..1817949
--- /dev/null
+++ b/TomatenMusic/Auth/Models/AuthenticateResponse.cs
@@ -0,0 +1,22 @@
+namespace TomatenMusic_Api.Auth.Models;
+
+using TomatenMusic_Api.Auth.Entities;
+
+public class AuthenticateResponse
+{
+    public int Id { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+    public string Username { get; set; }
+    public string Token { get; set; }
+
+
+    public AuthenticateResponse(User user, string token)
+    {
+        Id = user.Id;
+        FirstName = user.FirstName;
+        LastName = user.LastName;
+        Username = user.Username;
+        Token = token;
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Auth/Services/UserService.cs b/TomatenMusic/Auth/Services/UserService.cs
new file mode 100644
index 0000000..f6ab8b8
--- /dev/null
+++ b/TomatenMusic/Auth/Services/UserService.cs
@@ -0,0 +1,75 @@
+namespace TomatenMusic_Api.Auth.Services;
+
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using TomatenMusic_Api.Auth.Entities;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Models;
+
+public interface IUserService
+{
+    AuthenticateResponse Authenticate(AuthenticateRequest model);
+    IEnumerable<User> GetAll();
+    User GetById(int id);
+}
+
+public class UserService : IUserService
+{
+    // users hardcoded for simplicity, store in a db with hashed passwords in production applications
+    private List<User> _users = new List<User>
+    {
+        new User { Id = 1, FirstName = "Jannick", LastName = "Voss", Username = "Glowman", Password = "RX5GXstLLBvdt#_N" },
+        new User { Id = 2, FirstName = "Tim", LastName= "M�ller", Password= "SGWaldsolms9", Username = "Tueem"}
+
+    };
+
+    private readonly AppSettings _appSettings;
+
+    public UserService(IOptions<AppSettings> appSettings)
+    {
+        _appSettings = appSettings.Value;
+    }
+
+    public AuthenticateResponse Authenticate(AuthenticateRequest model)
+    {
+        var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);
+
+        // return null if user not found
+        if (user == null) return null;
+
+        // authentication successful so generate jwt token
+        var token = generateJwtToken(user);
+
+        return new AuthenticateResponse(user, token);
+    }
+
+    public IEnumerable<User> GetAll()
+    {
+        return _users;
+    }
+
+    public User GetById(int id)
+    {
+        return _users.FirstOrDefault(x => x.Id == id);
+    }
+
+    // helper methods
+
+    private string generateJwtToken(User user)
+    {
+        // generate token that is valid for 7 days
+        var tokenHandler = new JwtSecurityTokenHandler();
+        var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
+        var tokenDescriptor = new SecurityTokenDescriptor
+        {
+            Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
+            Expires = DateTime.UtcNow.AddDays(1),
+            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+        };
+        var token = tokenHandler.CreateToken(tokenDescriptor);
+        return tokenHandler.WriteToken(token);
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic/Controllers/PlayerController.cs b/TomatenMusic/Controllers/PlayerController.cs
new file mode 100644
index 0000000..49d900b
--- /dev/null
+++ b/TomatenMusic/Controllers/PlayerController.cs
@@ -0,0 +1,146 @@
+using DSharpPlus.Entities;
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic;
+using TomatenMusic.Music;
+using TomatenMusic_Api;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Models;
+using TomatenMusic_Api.Models.EventArgs;
+using static TomatenMusic_Api.InProcessEventBus;
+
+namespace TomatenMusic_Api.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class PlayerController : ControllerBase
+{
+
+
+	private readonly ILogger<PlayerController> _logger;
+	private readonly InProcessEventBus _eventBus;
+	private readonly TomatenMusicDataService _tomatenMusicDataService;
+
+	public PlayerController(
+		ILogger<PlayerController> logger,
+		InProcessEventBus eventBus, TomatenMusicDataService dataService)
+		
+	{
+		_logger = logger;
+		_eventBus = eventBus;
+		_tomatenMusicDataService = dataService;
+	}
+
+	[HttpGet("{guild_id}")]
+	public async Task<IActionResult> Get(ulong guild_Id)
+	{
+        Models.PlayerConnectionInfo response = await _tomatenMusicDataService.GetConnectionInfoAsync(guild_Id);
+
+		if (response == null)
+        {
+			return BadRequest("The Bot is not connected or the guild is unknown");
+        }
+
+		return Ok(response);
+	}
+	[HttpGet]
+	public async Task<IActionResult> Get()
+    {
+        List<Models.PlayerConnectionInfo> response = await _tomatenMusicDataService.GetAllGuildPlayersAsync();
+
+		if (response == null)
+        {
+			return BadRequest("An Error occured while parsing the Guilds, Guilds were Empty");
+        }
+
+		return Ok(response);
+    }
+
+	[HttpPost("connect")]
+	public async Task<IActionResult> PostConnect(ChannelConnectRequest request)
+	{
+
+		try
+		{
+			await _tomatenMusicDataService.GetGuildAsync(request.Guild_Id);
+		}catch (Exception ex)
+		{
+			return NotFound("That Guild was not found");
+		}
+
+
+		Boolean? playing = await _tomatenMusicDataService.IsPlayingAsync(request.Guild_Id);
+
+		DiscordChannel channel;
+
+		if (playing == true)
+			return BadRequest("The Bot is already playing");
+
+		if (await _tomatenMusicDataService.IsConnectedAsync(request.Guild_Id) == true)
+			return BadRequest("The Bot is already connected");
+
+		try
+        {
+			channel = await _tomatenMusicDataService.GetDiscordChannelAsync(request.Guild_Id, request.Channel_Id);
+		}catch (Exception ex)
+        {
+			return NotFound("Channel was not Found");
+		}
+
+
+
+		_eventBus.OnConnectRequestEvent(new ChannelConnectArgs(request.Guild_Id, channel));
+
+		return Ok();
+	}
+
+	[HttpPost("disconnect")]
+	public async Task<IActionResult> PostDisconnect(ChannelDisconnectRequest request)
+    {
+		try
+		{
+			await _tomatenMusicDataService.GetGuildAsync(request.GuildId);
+		}
+		catch (Exception ex)
+		{
+			return NotFound("That Guild was not found");
+		}
+
+		if (!await _tomatenMusicDataService.IsConnectedAsync(request.GuildId) == true)
+			return BadRequest("The Bot is not connected.");
+
+		_eventBus.OnDisconnectRequestEvent(new ChannelDisconnectArgs(request.GuildId));
+		return Ok();
+
+	}
+
+	[HttpPost("play")]
+	public async Task<IActionResult> PostPlay(TrackPlayRequest request)
+    {
+		try
+		{
+			await _tomatenMusicDataService.GetGuildAsync(request.GuildId);
+		}
+		catch (Exception ex)
+		{
+			return NotFound("That Guild was not found");
+		}
+
+		if (!await _tomatenMusicDataService.IsConnectedAsync(request.GuildId) == true)
+			return BadRequest("The Bot is not connected.");
+
+		MusicActionResponse response;
+
+		try
+        {
+			response = await _tomatenMusicDataService.TrackProvider.SearchAsync(request.TrackUri);
+		}catch (Exception ex)
+        {
+			return NotFound(ex.Message + "\n" + ex.StackTrace);
+        }
+
+		_eventBus.OnPlayRequestEvent(new TrackPlayArgs(response, request.GuildId, TimeSpan.FromSeconds(request.StartTimeSeconds), request.Now));
+
+		return Ok();
+    }
+}
diff --git a/TomatenMusic/Models/BasicTrackInfo.cs b/TomatenMusic/Models/BasicTrackInfo.cs
new file mode 100644
index 0000000..d353636
--- /dev/null
+++ b/TomatenMusic/Models/BasicTrackInfo.cs
@@ -0,0 +1,42 @@
+using Lavalink4NET.Player;
+using System.Text.Json.Serialization;
+using TomatenMusic.Music.Entitites;
+
+namespace TomatenMusic_Api.Models
+{
+    public class BasicTrackInfo
+    {
+        public string Name { get; set; }
+
+        public TrackPlatform Platform { get; set; }
+
+        public string YoutubeId { get; set; }
+
+        public string SpotifyId { get; set; }
+
+        public Uri URL { get; set; }
+
+        public BasicTrackInfo(LavalinkTrack track)
+        {
+            if (track == null)
+                return;
+            FullTrackContext ctx = (FullTrackContext)track.Context;
+
+            if (ctx == null)
+                return;
+
+            Name = track.Title;
+            Platform = ctx.SpotifyIdentifier == null ? TrackPlatform.YOUTUBE : TrackPlatform.SPOTIFY;
+            YoutubeId = track.TrackIdentifier;
+            SpotifyId = ctx.SpotifyIdentifier;
+            URL = new Uri(track.Source);
+        }
+    }
+
+    public enum TrackPlatform
+    {
+        YOUTUBE,
+        SPOTIFY,
+        FILE
+    }
+}
diff --git a/TomatenMusic/Models/ChannelConnectRequest.cs b/TomatenMusic/Models/ChannelConnectRequest.cs
new file mode 100644
index 0000000..0e90699
--- /dev/null
+++ b/TomatenMusic/Models/ChannelConnectRequest.cs
@@ -0,0 +1,13 @@
+using DSharpPlus.Entities;
+using Emzi0767.Utilities;
+using Newtonsoft.Json;
+
+namespace TomatenMusic_Api.Models
+{
+    public class ChannelConnectRequest
+    {
+        public ulong Channel_Id { get; set; }
+        public ulong Guild_Id { get; set; }
+
+    }
+}
diff --git a/TomatenMusic/Models/ChannelDisconnectRequest.cs b/TomatenMusic/Models/ChannelDisconnectRequest.cs
new file mode 100644
index 0000000..0e31e6d
--- /dev/null
+++ b/TomatenMusic/Models/ChannelDisconnectRequest.cs
@@ -0,0 +1,7 @@
+namespace TomatenMusic_Api.Models.EventArgs
+{
+    public class ChannelDisconnectRequest
+    {
+        public ulong GuildId { get; set; }
+    }
+}
diff --git a/TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs b/TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs
new file mode 100644
index 0000000..64d4d28
--- /dev/null
+++ b/TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs
@@ -0,0 +1,18 @@
+using DSharpPlus.Entities;
+using Emzi0767.Utilities;
+
+namespace TomatenMusic_Api.Models.EventArgs
+{
+	public class ChannelConnectArgs : AsyncEventArgs
+	{
+		public ulong Guild_Id { get; set; }
+
+		public DiscordChannel Channel { get; set; }
+
+		public ChannelConnectArgs(ulong guild_Id, DiscordChannel channel)
+		{
+			Guild_Id = guild_Id;
+			Channel = channel;
+		}
+	}
+}
diff --git a/TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs b/TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs
new file mode 100644
index 0000000..42e4404
--- /dev/null
+++ b/TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs
@@ -0,0 +1,13 @@
+using Emzi0767.Utilities;
+
+namespace TomatenMusic_Api.Models.EventArgs
+{
+    public class ChannelDisconnectArgs : AsyncEventArgs
+    {
+        public ulong GuildId { get; set; }
+
+        public ChannelDisconnectArgs(ulong guildId) { GuildId = guildId; }
+    }
+
+    
+}
diff --git a/TomatenMusic/Models/EventArgs/TrackPlayArgs.cs b/TomatenMusic/Models/EventArgs/TrackPlayArgs.cs
new file mode 100644
index 0000000..cd4b43e
--- /dev/null
+++ b/TomatenMusic/Models/EventArgs/TrackPlayArgs.cs
@@ -0,0 +1,22 @@
+using Emzi0767.Utilities;
+using Lavalink4NET.Player;
+using TomatenMusic.Music;
+
+namespace TomatenMusic_Api.Models.EventArgs
+{
+    public class TrackPlayArgs : AsyncEventArgs
+    {
+        public MusicActionResponse Response { get; set; }
+        public ulong GuildId { get; set; }
+        public TimeSpan StartTime { get; set; }
+        public bool Now { get; set; }
+
+        public TrackPlayArgs(MusicActionResponse response, ulong guildId, TimeSpan startTime, bool now)
+        {
+            Response = response;
+            GuildId = guildId;
+            StartTime = startTime;
+            Now = now;
+        }
+    }
+}
diff --git a/TomatenMusic/Models/PlayerConnectionInfo.cs b/TomatenMusic/Models/PlayerConnectionInfo.cs
new file mode 100644
index 0000000..7d113ed
--- /dev/null
+++ b/TomatenMusic/Models/PlayerConnectionInfo.cs
@@ -0,0 +1,62 @@
+using DSharpPlus.Entities;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using TomatenMusic;
+using TomatenMusic.Music;
+
+namespace TomatenMusic_Api.Models
+{
+    public class PlayerConnectionInfo
+    {
+
+        public static async Task<PlayerConnectionInfo> Create(GuildPlayer player)
+        {
+            PlayerConnectionInfo response = new PlayerConnectionInfo();
+
+            response.PlaybackPosition = player.TrackPosition;
+            response.Channel_Id = (ulong)player.VoiceChannelId;
+            response.Guild_Id = player.GuildId;
+            response.Paused = player.State == PlayerState.Paused;
+            response.CurrentTrack = new BasicTrackInfo(player.CurrentTrack);
+            response.LoopType = player.PlayerQueue.LoopType;
+
+            response.Queue = player.PlayerQueue.Queue.ToList().ConvertAll(x => new BasicTrackInfo(x));
+            response.PlayedTracks = player.PlayerQueue.PlayedTracks.ToList().ConvertAll(x => new BasicTrackInfo(x));
+            response.State = player.State;
+
+            return response;
+        }
+
+        // Summary:
+        //     Gets the current playback position.
+        public TimeSpan PlaybackPosition
+        {
+            get;
+            internal set;
+        }
+        public PlayerState State { get; set; }
+        //
+        // Summary:
+        //     Gets the voice channel associated with this connection.
+        public ulong Channel_Id { get; set; }
+
+        //
+        // Summary:
+        //     Gets the guild associated with this connection.
+        public ulong Guild_Id {get; set; }
+
+        public bool Paused { get; set; }
+
+        public BasicTrackInfo CurrentTrack { get; set; }
+
+        public LoopType LoopType { get; set; }
+
+        public List<BasicTrackInfo> Queue { get; set; }
+
+        public List<BasicTrackInfo> PlayedTracks { get; set; }
+
+
+    }
+
+
+}
diff --git a/TomatenMusic/Models/TrackPlayRequest.cs b/TomatenMusic/Models/TrackPlayRequest.cs
new file mode 100644
index 0000000..598f3f2
--- /dev/null
+++ b/TomatenMusic/Models/TrackPlayRequest.cs
@@ -0,0 +1,10 @@
+namespace TomatenMusic_Api.Models
+{
+    public class TrackPlayRequest
+    {
+        public ulong GuildId { get; set; }
+        public string TrackUri { get; set; }
+        public bool Now { get; set; }
+        public int StartTimeSeconds { get; set; }
+    }
+}
diff --git a/TomatenMusic/Program.cs b/TomatenMusic/Program.cs
new file mode 100644
index 0000000..a17217f
--- /dev/null
+++ b/TomatenMusic/Program.cs
@@ -0,0 +1,41 @@
+using TomatenMusic_Api;
+using TomatenMusic_Api.Auth.Helpers;
+using TomatenMusic_Api.Auth.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+
+builder.Services.AddControllers();
+builder.Services.AddCors();
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+// configure strongly typed settings object
+builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
+builder.Services.AddScoped<IUserService, UserService>();
+
+builder.Services.AddSingleton<InProcessEventBus>();
+
+builder.Services.AddSingleton<IHostedService, TomatenMusicService>();
+builder.Services.AddSingleton<TomatenMusicDataService>();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+app.UseSwagger();
+app.UseSwaggerUI();
+
+app.UseWebSockets();
+
+app.UseCors(x => x
+    .AllowAnyOrigin()
+    .AllowAnyMethod()
+    .AllowAnyHeader());
+
+// custom jwt auth middleware
+app.UseMiddleware<JwtMiddleware>();
+
+app.MapControllers();
+
+
+app.Run();
diff --git a/TomatenMusic/Properties/launchSettings.json b/TomatenMusic/Properties/launchSettings.json
new file mode 100644
index 0000000..fb8b128
--- /dev/null
+++ b/TomatenMusic/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:46317",
+      "sslPort": 44369
+    }
+  },
+  "profiles": {
+    "TomatenMusic_Api": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "https://localhost:7210;http://localhost:5210",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
diff --git a/TomatenMusic/Services/EventBus.cs b/TomatenMusic/Services/EventBus.cs
new file mode 100644
index 0000000..c58224d
--- /dev/null
+++ b/TomatenMusic/Services/EventBus.cs
@@ -0,0 +1,31 @@
+using DSharpPlus.Entities;
+using Emzi0767.Utilities;
+using Microsoft.AspNetCore.Mvc;
+using TomatenMusic_Api.Models;
+using TomatenMusic_Api.Models.EventArgs;
+
+namespace TomatenMusic_Api;
+
+public class InProcessEventBus
+{
+	public event AsyncEventHandler<InProcessEventBus, ChannelConnectArgs>? OnConnectRequest;
+
+	public event AsyncEventHandler<InProcessEventBus, ChannelDisconnectArgs>? OnDisconnectRequest;
+
+	public event AsyncEventHandler<InProcessEventBus, TrackPlayArgs> OnPlayRequest;
+	public void OnConnectRequestEvent(ChannelConnectArgs e)
+	{
+		_ = OnConnectRequest?.Invoke(this, e);
+	}
+
+	public void OnDisconnectRequestEvent(ChannelDisconnectArgs e)
+	{
+		_ = OnDisconnectRequest?.Invoke(this, e);
+	}
+
+	public void OnPlayRequestEvent(TrackPlayArgs e)
+	{
+		_ = OnPlayRequest?.Invoke(this, e);
+	}
+}
+
diff --git a/TomatenMusic/Services/TomatenMusicDataService.cs b/TomatenMusic/Services/TomatenMusicDataService.cs
new file mode 100644
index 0000000..53fc3db
--- /dev/null
+++ b/TomatenMusic/Services/TomatenMusicDataService.cs
@@ -0,0 +1,91 @@
+using TomatenMusic.Music;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using TomatenMusic_Api.Models;
+using Lavalink4NET.Player;
+using TomatenMusic;
+using Lavalink4NET;
+
+namespace TomatenMusic_Api
+{
+    public class TomatenMusicDataService : IHostedService
+    {
+        private ILogger<TomatenMusicDataService> _logger;
+        private IServiceProvider _serviceProvider { get; set; } = TomatenMusicBot.ServiceProvider;
+        public IAudioService _audioService { get; set; }
+        public TrackProvider TrackProvider { get; set; }
+        public TomatenMusicDataService(ILogger<TomatenMusicDataService> logger)
+        {
+            _logger = logger;
+            _audioService = _serviceProvider.GetRequiredService<IAudioService>();
+            TrackProvider = _serviceProvider.GetRequiredService<TrackProvider>();
+        }
+
+        public async Task<PlayerConnectionInfo> GetConnectionInfoAsync(ulong guild_id)
+        {
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(guild_id);
+            if (player == null)
+                return null;
+            return await PlayerConnectionInfo.Create(player);
+        }
+
+        public async Task<Boolean?> IsPlayingAsync(ulong guild_id)
+        {
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(guild_id);
+
+            if (player == null)
+                return false;
+            return player.State == PlayerState.Playing;
+        }
+        public async Task<Boolean?> IsConnectedAsync(ulong guild_id)
+        {
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(guild_id);
+
+            if (player == null)
+                return false;
+
+            return player.State != PlayerState.NotConnected;
+        }
+
+        public async Task<List<PlayerConnectionInfo>> GetAllGuildPlayersAsync()
+        {
+            List<PlayerConnectionInfo> list = new List<PlayerConnectionInfo>();
+            foreach (var guild in _audioService.GetPlayers<GuildPlayer>())
+            {
+                list.Add(await PlayerConnectionInfo.Create(guild));
+            }
+            if (list.Count == 0)
+                return null;              
+
+            return list;
+        }
+
+        public Task<DiscordChannel> GetDiscordChannelAsync(ulong guild_id, ulong channel_id)
+        {
+            var client = _serviceProvider.GetRequiredService<DiscordShardedClient>();
+            var guildClient = client.GetShard(guild_id);
+            return guildClient.GetChannelAsync(channel_id);
+        }
+
+        public Task<DiscordGuild> GetGuildAsync(ulong guild_id)
+        {
+            var client = _serviceProvider.GetRequiredService<DiscordShardedClient>();
+            var guildClient = client.GetShard(guild_id);
+
+            return guildClient.GetGuildAsync(guild_id);
+        }
+
+        public Task StartAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogInformation("TomatenMusicDataService starting...");
+            return Task.CompletedTask;
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogInformation("TomatenMusicDataService stopping...");
+            return Task.CompletedTask;
+
+        }
+    }
+}
diff --git a/TomatenMusic/Services/TomatenMusicService.cs b/TomatenMusic/Services/TomatenMusicService.cs
new file mode 100644
index 0000000..a6780ee
--- /dev/null
+++ b/TomatenMusic/Services/TomatenMusicService.cs
@@ -0,0 +1,92 @@
+using Lavalink4NET;
+using TomatenMusic;
+using TomatenMusic.Music;
+using TomatenMusic_Api.Models;
+using TomatenMusic_Api.Models.EventArgs;
+using static TomatenMusic_Api.InProcessEventBus;
+
+namespace TomatenMusic_Api
+{
+    public class TomatenMusicService : IHostedService
+    {
+		private readonly InProcessEventBus _inProcessEventBus;
+		private readonly ILogger<TomatenMusicService> _logger;
+        public TomatenMusicBot _bot { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public TomatenMusicService(InProcessEventBus inProcessEventBus, ILogger<TomatenMusicService> logger)
+		{
+			_inProcessEventBus = inProcessEventBus;
+			_logger = logger;
+
+			Initialize();
+		}
+
+		private void Initialize()
+		{
+            _inProcessEventBus.OnConnectRequest += _inProcessEventBus_OnConnectRequest;
+            _inProcessEventBus.OnDisconnectRequest += _inProcessEventBus_OnDisconnectRequest;
+            _inProcessEventBus.OnPlayRequest += _inProcessEventBus_OnPlayRequest;
+		}
+
+        private async Task _inProcessEventBus_OnPlayRequest(InProcessEventBus sender, TrackPlayArgs e)
+        {
+			GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(e.GuildId);
+
+			if (e.Response.Tracks != null && e.Response.Tracks.Any())
+            {
+				if (e.Now)
+					await player.PlayNowAsync(e.Response.Tracks);
+				else
+					await player.PlayItemAsync(e.Response.Tracks);
+
+				return;
+			}
+
+			if (e.Response.IsPlaylist)
+            {
+				if (e.Now)
+					await player.PlayPlaylistNowAsync(e.Response.Playlist);
+				else
+					await player.PlayPlaylistAsync(e.Response.Playlist);
+			}else
+            {
+				if (e.Now)
+					await player.PlayNowAsync(e.Response.Track, e.StartTime);
+				else
+					await player.PlayAsync(e.Response.Track, e.StartTime);
+			}
+
+		}
+
+		private async Task _inProcessEventBus_OnDisconnectRequest(InProcessEventBus sender, ChannelDisconnectArgs e)
+        {
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(e.GuildId);
+			player.DisconnectAsync();
+        }
+
+        private async Task _inProcessEventBus_OnConnectRequest(InProcessEventBus sender, ChannelConnectArgs e)
+        {
+			GuildPlayer player = await _audioService.JoinAsync<GuildPlayer>(e.Guild_Id, e.Channel.Id, true);
+        }
+
+        public async Task StartAsync(CancellationToken cancellationToken)
+		{
+			_logger.LogInformation("Starting service...");
+			_bot = new TomatenMusicBot();
+			await _bot.InitBotAsync();
+			_audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+			_logger.LogInformation("Service started!");
+
+		}
+
+		public async Task StopAsync(CancellationToken cancellationToken)
+		{
+			_logger.LogInformation("Shutting down service...");
+			await _bot.ShutdownBotAsync();
+			_logger.LogInformation("Service shut down!");
+
+		}
+	}
+}
+
diff --git a/TomatenMusic/TomatenMusic.csproj b/TomatenMusic/TomatenMusic.csproj
new file mode 100644
index 0000000..453b8d7
--- /dev/null
+++ b/TomatenMusic/TomatenMusic.csproj
@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <RootNamespace>TomatenMusic_Api</RootNamespace>
+    <RestoreAdditionalProjectSources>
+      https://api.nuget.org/v3/index.json;
+      https://nuget.emzi0767.com/api/v3/index.json
+    </RestoreAdditionalProjectSources>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Remove="config.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="config.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.WebSockets" Version="0.2.3.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+	<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
+	<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="Auth\" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\TomatenMusicCore\TomatenMusicCore.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/TomatenMusic/appsettings.Development.json b/TomatenMusic/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/TomatenMusic/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
diff --git a/TomatenMusic/appsettings.json b/TomatenMusic/appsettings.json
new file mode 100644
index 0000000..a618725
--- /dev/null
+++ b/TomatenMusic/appsettings.json
@@ -0,0 +1,11 @@
+{
+  "AppSettings": {
+    "Secret": "WWT9uwYzMkhOnUrZD7CSeT9forbwpbci"
+  },
+    "Logging": {
+      "LogLevel": {
+        "Default": "Debug",
+        "Microsoft.AspNetCore": "Debug"
+      }
+    }
+}
\ No newline at end of file
diff --git a/TomatenMusic/config.json b/TomatenMusic/config.json
new file mode 100644
index 0000000..5a3277c
--- /dev/null
+++ b/TomatenMusic/config.json
@@ -0,0 +1,8 @@
+{
+  "TOKEN": "TOKEN",
+  "LavaLinkPassword": " ",
+  "SpotifyClientId": " ",
+  "SpotifyClientSecret": " ",
+  "YoutubeApiKey": " "
+}
+
diff --git a/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs b/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
new file mode 100644
index 0000000..7ab5d9f
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus;
+using TomatenMusic.Music;
+
+namespace TomatenMusic.Commands.Checks
+{
+    public class OnlyGuildCheck : SlashCheckBaseAttribute
+    {
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+            if (ctx.Guild == null)
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("This Command is only available on Guilds.").AsEphemeral(true));
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs b/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
new file mode 100644
index 0000000..8ff6719
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.EventArgs;
+using DSharpPlus;
+using TomatenMusic.Music;
+using Emzi0767.Utilities;
+using Lavalink4NET;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace TomatenMusic.Commands.Checks
+{
+    public class UserInMusicChannelCheck : SlashCheckBaseAttribute
+    {
+        public bool _passIfNull { get; set; }
+        public UserInMusicChannelCheck(bool passIfNull = false)
+        {
+            _passIfNull = passIfNull;
+        }
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+            IAudioService audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+
+            GuildPlayer player = audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            bool allowed;
+            //TODO
+            if (player != null)
+            {
+                allowed = ctx.Member.VoiceState.Channel != null && ctx.Member.VoiceState.Channel.Id == player.VoiceChannelId;
+            }
+            else
+                allowed = _passIfNull;
+
+            if (!allowed)
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("❌ Please connect to the Bots Channel to use this Command").AsEphemeral(true));
+            return allowed;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs b/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
new file mode 100644
index 0000000..e8c0369
--- /dev/null
+++ b/TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus.SlashCommands;
+using DSharpPlus;
+using TomatenMusic.Music;
+
+namespace TomatenMusic.Commands.Checks
+{
+    class UserInVoiceChannelCheck : SlashCheckBaseAttribute
+    {
+
+        public override async Task<bool> ExecuteChecksAsync(InteractionContext ctx)
+        {
+
+            if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null)
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DSharpPlus.Entities.DiscordInteractionResponseBuilder().WithContent("You are not in a Voice Channel.").AsEphemeral(true));
+                return false;
+            }
+
+            return true;
+            
+        }
+    }
+}
diff --git a/TomatenMusicCore/Commands/MusicCommands.cs b/TomatenMusicCore/Commands/MusicCommands.cs
new file mode 100644
index 0000000..74930ee
--- /dev/null
+++ b/TomatenMusicCore/Commands/MusicCommands.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Commands.Checks;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Prompt.Implementation;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using TomatenMusicCore.Prompt.Implementation;
+
+namespace TomatenMusic.Commands
+{
+    public class MusicCommands : ApplicationCommandModule
+    {
+        public IAudioService _audioService { get; set; }
+        public ILogger<MusicCommands> _logger { get; set; }
+        public TrackProvider _trackProvider { get; set; }
+
+        public MusicCommands(IAudioService audioService, ILogger<MusicCommands> logger, TrackProvider trackProvider)
+        {
+            _audioService = audioService;
+            _logger = logger;
+            _trackProvider = trackProvider;
+        }
+
+        [SlashCommand("stop", "Stops the current Playback and clears the Queue")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task StopCommand(InteractionContext ctx)
+        {
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            try
+            {
+                await player.DisconnectAsync();
+            }catch (Exception ex)
+            {
+                await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder
+                {
+                    Content = $"❌ An Error occured : ``{ex.Message}``",
+                    IsEphemeral = true
+                });
+                return;
+            }
+            await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder
+            {
+                Content = $"✔️ The Bot was stopped successfully",
+                IsEphemeral = true
+            });
+
+        }
+
+
+        [SlashCommand("skip", "Skips the current song and plays the next one in the queue")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task SkipCommand(InteractionContext ctx)
+        {
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+            LavalinkTrack oldTrack = player.CurrentTrack;
+            try
+            {
+                await player.SkipAsync();
+            }
+            catch (Exception e) 
+            {
+                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"⛔ Could not Skip Song, Queue Empty!").AsEphemeral(true));
+                return;
+            }
+
+            _ = ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent($"Skipped From Song ``{oldTrack.Title}`` To Song:")
+                .AddEmbed(Common.AsEmbed(player.CurrentTrack, loopType: player.PlayerQueue.LoopType)).AsEphemeral(true));
+        }
+
+        [SlashCommand("fav", "Shows the favorite Song Panel")]
+        [OnlyGuildCheck]
+        public async Task FavCommand(InteractionContext ctx)
+        {
+            
+        }
+
+        [SlashCommand("search", "Searches for a specific query")]
+        [OnlyGuildCheck]
+        public async Task SearchCommand(InteractionContext ctx, [Option("query", "The Search Query")] string query)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            MusicActionResponse response;
+            try
+            {
+                response = await _trackProvider.SearchAsync(query, true);
+            }catch (Exception e)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ Search failed: ``{e.Message}``, ```{e.StackTrace}```"));
+                return;
+            }
+
+            DiscordPromptBase prompt;
+
+            if (!response.IsPlaylist && response.Tracks.Count() == 1) 
+            {
+                var sPrompt = new SongActionPrompt(response.Tracks.First(), ctx.Member);
+                prompt = sPrompt;
+            }
+            else if (response.IsPlaylist)
+            {
+                var sPrompt = new PlaylistSongSelectorPrompt(response.Playlist);
+                sPrompt.ConfirmCallback = async (tracks) =>
+                {
+                    var selectPrompt = new SongListActionPrompt(tracks, ctx.Member, sPrompt);
+                    await selectPrompt.UseAsync(sPrompt.Interaction, sPrompt.Message);
+                };
+                prompt = sPrompt;
+            }
+            else
+            {
+                var sPrompt = new SongSelectorPrompt($"Search results for {query}", response.Tracks);
+                sPrompt.ConfirmCallback = async (tracks) =>
+                {
+                    var selectPrompt = new SongListActionPrompt(tracks, ctx.Member, sPrompt);
+                    await selectPrompt.UseAsync(sPrompt.Interaction, sPrompt.Message);
+                };
+                prompt = sPrompt;
+            }
+
+
+            await prompt.UseAsync(ctx.Interaction, await ctx.GetOriginalResponseAsync());
+        }
+
+        [SlashCommand("time", "Sets the playing position of the current Song.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task TimeCommand(InteractionContext ctx, [Option("time", "The time formatted like this: Hours: 1h, Minutes: 1m, Seconds 1s")] string time)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+            TimeSpan timeSpan;
+
+            try
+            {
+                timeSpan = TimeSpan.Parse(time);
+            }
+            catch (Exception e)
+            {
+                try
+                {
+                    timeSpan = Common.ToTimeSpan(time);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ An Error occured when parsing your input."));
+                    return;
+                }
+            }
+
+            try
+            {
+                await player.SeekPositionAsync(timeSpan);
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An Error occured while Seeking the Track: ``{ex.Message}``"));
+                return;
+            }
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"✔️ You successfully set the Song to ``{Common.GetTimestamp(timeSpan)}``."));
+        }
+
+        [SlashCommand("pause", "Pauses or Resumes the current Song.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task PauseCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+            try
+            {
+                await player.TogglePauseAsync();
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An Error occured changing the pause state of the Song: ``{ex.Message}``"));
+                return;
+            }
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"✔️ You {(player.State == PlayerState.Paused ? "successfully paused the Track" : "successfully resumed the Track")}"));
+
+        }
+
+        [SlashCommand("shuffle", "Shuffles the Queue.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task ShuffleCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            try
+            {
+                await player.ShuffleAsync();
+            }
+            catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An error occured while shuffling the Queue: ``{ex.Message}``"));
+                return;
+            }
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"😀 You shuffled the Queue."));
+
+        }
+
+        [SlashCommand("loop", "Sets the loop type of the current player.")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task LoopCommand(InteractionContext ctx, [Option("Looptype", "The loop type which the player should be set to")] LoopType type)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+            
+            try
+            {
+                await player.SetLoopAsync(type);
+            }catch (Exception ex)
+            {
+                await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"❌ An error occured while change the Queue Loop: ``{ex.Message}``"));
+
+            }
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"😀 You have set the Loop to ``{type.ToString()}``."));
+
+        }
+
+        [SlashCommand("autoplay", "Enables/Disables Autoplay")]
+        [OnlyGuildCheck]
+        [UserInMusicChannelCheck]
+        public async Task AutoplayCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+            player.Autoplay = !player.Autoplay;
+
+            await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"You have set Autoplay to ``{(player.Autoplay ? "Enabled" : "Disabled")}``"));
+
+        }
+
+        [SlashCommand("queue", "Shows the Queue")]
+        [OnlyGuildCheck]
+        public async Task QueueCommand(InteractionContext ctx)
+        {
+            await ctx.DeferAsync(true);
+            GuildPlayer player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+            if (player == null)
+            {
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ ``Theres currently nothing playing``"));
+                return;
+            }
+
+            LavalinkTrack track = player.CurrentTrack;
+
+            if (track == null)
+            {
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("❌ ``Theres currently nothing playing``"));
+                return;
+            }
+
+            QueuePrompt prompt = new QueuePrompt(player);
+
+            _ = prompt.UseAsync(ctx.Interaction, await ctx.GetOriginalResponseAsync());
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Commands/PlayCommandGroup.cs b/TomatenMusicCore/Commands/PlayCommandGroup.cs
new file mode 100644
index 0000000..736594d
--- /dev/null
+++ b/TomatenMusicCore/Commands/PlayCommandGroup.cs
@@ -0,0 +1,327 @@
+using DSharpPlus.Entities;
+using DSharpPlus.SlashCommands;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Commands.Checks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Util;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Commands
+{
+
+        [SlashCommandGroup("playnow", "Plays the specified Song now and prepends the Current song to the Queue.")]
+        public class PlayNowGroup : ApplicationCommandModule
+        {
+            public IAudioService _audioService { get; set; }
+            public ILogger<PlayNowGroup> _logger { get; set; }
+            public TrackProvider _trackProvider { get; set; }
+
+            public PlayNowGroup(IAudioService audioService, ILogger<PlayNowGroup> logger, TrackProvider trackProvider)
+            {
+                _audioService = audioService;
+                _logger = logger;
+                _trackProvider = trackProvider;
+            }
+
+            [SlashCommand("query", "Play a song with its youtube/spotify link. (or youtube search)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayQueryCommand(InteractionContext ctx, [Option("query", "The song search query.")] string query)
+            {
+            var sw = Stopwatch.StartNew();
+
+                await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+                
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(query);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your query: ``{ex.Message}``, ```{ex.StackTrace}```")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                    sw.Stop();
+                    _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                    return;
+                    }
+                }
+
+                try
+                {
+                    if (response.IsPlaylist)
+                    {
+                        ILavalinkPlaylist playlist = response.Playlist;
+                        await player.PlayPlaylistNowAsync(playlist);
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
+                        Common.AsEmbed(playlist)
+                        ));
+
+                    }
+                    else
+                    {
+                        TomatenMusicTrack track = response.Track;
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Playing Now")
+                            .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, 0)));
+
+                        await player.PlayNowAsync(response.Track);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while playing your Query: ``{ex.Message}``")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+            sw.Stop();
+            _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+        }
+
+            [SlashCommand("file", "Play a song file. (mp3/mp4)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayFileCommand(InteractionContext ctx, [Option("File", "The File that should be played.")] DiscordAttachment file)
+            {
+            var sw = Stopwatch.StartNew();
+
+            await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(new Uri(file.Url));
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your file: ``{ex.Message}``")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                    sw.Stop();
+                    _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                    return;
+                    }
+                }
+
+                LavalinkTrack track = response.Track;
+
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Playing Now")
+                    .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, 0)));
+
+                await player.PlayNowAsync(response.Track);
+            sw.Stop();
+            _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+        }
+        }
+
+        [SlashCommandGroup("play", "Queues or plays the Song")]
+        public class PlayQueueGroup : ApplicationCommandModule
+        {
+            public IAudioService _audioService { get; set; }
+            public ILogger<PlayQueueGroup> _logger { get; set; }
+            public TrackProvider _trackProvider { get; set; }
+
+            public PlayQueueGroup(IAudioService audioService, ILogger<PlayQueueGroup> logger, TrackProvider trackProvider)
+            {
+                _audioService = audioService;
+                _logger = logger;
+                _trackProvider = trackProvider;
+            }
+
+
+            [SlashCommand("query", "Play a song with its youtube/spotify link. (or youtube search)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayQueryCommand(InteractionContext ctx, [Option("query", "The song search query.")] string query)
+            {
+            var sw = Stopwatch.StartNew();
+
+            await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(query);
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your query: ``{ex.Message}``, ```{ex.StackTrace}```")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                    sw.Stop();
+                    _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                    return;
+                    }
+                }
+
+            try
+                {
+                    if (response.IsPlaylist)
+                    {
+                        ILavalinkPlaylist playlist = response.Playlist;
+                        await player.PlayPlaylistAsync(playlist);
+
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
+                        Common.AsEmbed(playlist)
+                        ));
+
+                    }
+                    else
+                    {
+                        LavalinkTrack track = response.Track;
+
+                        _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(player.State == PlayerState.NotPlaying ? "Now Playing:" : "Added to Queue")
+                            .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, player.State == PlayerState.NotPlaying ? 0 : player.PlayerQueue.Queue.Count + 1)));
+
+                        await player.PlayItemAsync(response.Track);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while playing your Track: ``{ex.Message}``, ```{ex.StackTrace}```")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+            sw.Stop();
+            _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+        }
+
+            [SlashCommand("file", "Play a song file. (mp3/mp4)")]
+            [UserInVoiceChannelCheck]
+            [UserInMusicChannelCheck(true)]
+            [OnlyGuildCheck]
+            public async Task PlayFileCommand(InteractionContext ctx, [Option("File", "The File that should be played.")] DiscordAttachment file)
+            {
+            var sw = Stopwatch.StartNew();
+
+            await ctx.DeferAsync(true);
+
+                GuildPlayer player = (GuildPlayer)_audioService.GetPlayer(ctx.Guild.Id);
+
+                MusicActionResponse response;
+
+                try
+                {
+                    response = await _trackProvider.SearchAsync(new Uri(file.Url));
+                }
+                catch (Exception ex)
+                {
+                    await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                     .WithContent($"❌ An error occured while resolving your file: ``{ex.Message}``")
+                      );
+                sw.Stop();
+                _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                return;
+                }
+
+                try
+                {
+                    player = await _audioService.JoinAsync<GuildPlayer>(ctx.Guild.Id, ctx.Member.VoiceState.Channel.Id, true);
+                }
+                catch (Exception ex)
+                {
+                    player = _audioService.GetPlayer<GuildPlayer>(ctx.Guild.Id);
+                    if (player == null || player.VoiceChannelId == null)
+                    {
+                        await ctx.EditResponseAsync(new DiscordWebhookBuilder()
+                         .WithContent($"❌ An error occured while connecting to your Channel: ``{ex.Message}``")
+                          );
+                    sw.Stop();
+                    _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+                    return;
+                    }
+                }
+
+                LavalinkTrack track = response.Track;
+
+                _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(player.State == PlayerState.NotPlaying ? "Now Playing:" : "Added to Queue")
+                    .AddEmbed(Common.AsEmbed(track, player.PlayerQueue.LoopType, player.State == PlayerState.NotPlaying ? 0 : player.PlayerQueue.Queue.Count + 1)));
+
+                await player.PlayItemAsync(response.Track);
+
+            sw.Stop();
+            _logger.LogDebug($"Command {ctx.CommandName} took {sw.ElapsedMilliseconds}ms to execute.");
+            }
+        }
+}
diff --git a/TomatenMusicCore/Music/Entitites/FullTrackContext.cs b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
new file mode 100644
index 0000000..03e6b19
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Services;
+using System.Linq;
+using SpotifyAPI.Web;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class FullTrackContext
+    {
+        public bool IsFile { get; set; }
+        public string YoutubeDescription { get; set; }
+        public IEnumerable<string> YoutubeTags { get; set; }
+        public ulong YoutubeViews { get; set; }
+        public ulong YoutubeLikes { get; set; }
+        public Uri YoutubeThumbnail { get; set; }
+        public DateTime YoutubeUploadDate { get; set; }
+        //
+        // Summary:
+        //     Gets the author of the track.
+        public Uri YoutubeAuthorThumbnail { get; set; }
+        public ulong YoutubeAuthorSubs { get; set; }
+        public Uri YoutubeAuthorUri { get; set; }
+        public ulong? YoutubeCommentCount { get; set; }
+        public string SpotifyIdentifier { get; set; }
+        public SimpleAlbum SpotifyAlbum { get; set; }
+        public List<SimpleArtist> SpotifyArtists { get; set; }
+        public int SpotifyPopularity { get; set; }
+        public Uri SpotifyUri { get; set; }
+
+        public static async Task<TomatenMusicTrack> PopulateAsync(TomatenMusicTrack track, FullTrack spotifyTrack = null, string spotifyId = null)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            if (context == null)
+                context = new FullTrackContext();
+
+            var spotifyService = TomatenMusicBot.ServiceProvider.GetRequiredService<ISpotifyService>();
+            var youtubeService = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+            if (spotifyId != null)
+                context.SpotifyIdentifier = spotifyId;
+            else if (spotifyTrack != null)
+                context.SpotifyIdentifier = spotifyTrack.Id;
+                
+            track.Context = context;
+            await youtubeService.PopulateTrackInfoAsync(track);
+            await spotifyService.PopulateTrackAsync(track, spotifyTrack);
+            
+            return track;
+        }
+
+        public static async Task<TrackList> PopulateTracksAsync(TrackList tracks)
+        {
+            foreach (var trackItem in tracks)
+            {
+                await PopulateAsync(trackItem);
+            }
+
+            return tracks;
+        }
+
+
+        
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs b/TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs
new file mode 100644
index 0000000..6b88c74
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using TomatenMusic.Util;
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public interface ILavalinkPlaylist : IPlayableItem
+    {
+        public string Title { get; }
+        public TrackList Tracks { get; }
+        public Uri Url { get; }
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public string Identifier { get; }
+        public Uri AuthorThumbnail { get; set; }
+
+        public TimeSpan GetLength()
+        {
+            TimeSpan timeSpan = TimeSpan.FromTicks(0);
+
+            foreach (var track in Tracks)
+            {
+                timeSpan = timeSpan.Add(track.Duration);
+            }
+
+            return timeSpan;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/IPlayableItem.cs b/TomatenMusicCore/Music/Entitites/IPlayableItem.cs
new file mode 100644
index 0000000..d322170
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/IPlayableItem.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+
+namespace TomatenMusicCore.Music.Entities
+{
+    public interface IPlayableItem
+    {
+        public string Title { get; }
+        Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true);
+        Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false);
+
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
new file mode 100644
index 0000000..c2296c0
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
@@ -0,0 +1,63 @@
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class SpotifyPlaylist : ILavalinkPlaylist
+    {
+        public string Title { get; }
+        public TrackList Tracks { get; }
+        public Uri Url { get; set; }
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public int Followers { get; set; }
+        public string Identifier { get; }
+        public Uri AuthorThumbnail { get; set; }
+
+        public SpotifyPlaylist(string name, string id, TrackList tracks, Uri uri)
+        {
+            Title = name;
+            Identifier = id;
+            Tracks = tracks;
+            Url = uri;
+        }
+
+        public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+            await player.PlayerQueue.QueuePlaylistAsync(this);
+
+
+            if (player.State == PlayerState.NotPlaying)
+            {
+                LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
+                await player.PlayAsync(nextTrack);
+            }
+        }
+
+        public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+            if (!player.PlayerQueue.Queue.Any())
+                player.PlayerQueue.CurrentPlaylist = this;
+
+            player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
+            Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
+
+            TomatenMusicTrack track = reversedTracks.Dequeue();
+            player.PlayerQueue.LastTrack = track;
+            await player.PlayAsync(track);
+
+            reversedTracks.Reverse();
+
+            foreach (var item in reversedTracks)
+            {
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs b/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
new file mode 100644
index 0000000..658e0a6
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
@@ -0,0 +1,50 @@
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+using TomatenMusic.Prompt.Implementation;
+
+namespace TomatenMusicCore.Music.Entities
+{
+    public class TomatenMusicTrack : LavalinkTrack, IPlayableItem
+    {
+
+        public override TimeSpan Position { get; }
+        public TomatenMusicTrack
+            (LavalinkTrack track)
+            : base(track.Identifier, track.Author, track.Duration, track.IsLiveStream, track.IsSeekable, track.Source, track.Title, track.TrackIdentifier, track.Provider)
+        {
+            Context = track.Context;
+            Position = track.Position;
+        }
+
+        public string Title => base.Title;
+
+        public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+            
+            if (player.State == PlayerState.NotPlaying)
+            {
+                player.PlayerQueue.LastTrack = this;
+                await player.PlayAsync(this, startTime, endTime, noReplace);
+            }
+            else
+                player.PlayerQueue.QueueTrack(this);
+
+        }
+
+        public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+            if (!withoutQueuePrepend)
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
+
+            player.PlayerQueue.LastTrack = this;
+            await player.PlayAsync(this, startTime, endTime);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/TrackList.cs b/TomatenMusicCore/Music/Entitites/TrackList.cs
new file mode 100644
index 0000000..1ff34f4
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/TrackList.cs
@@ -0,0 +1,567 @@
+
+
+using Lavalink4NET.Player;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using TomatenMusic.Music;
+
+namespace TomatenMusicCore.Music.Entities
+{
+    //
+    // Summary:
+    //     A thread-safe queue for Lavalink4NET.Player.LavalinkTrack.
+    public sealed class TrackList : IList<TomatenMusicTrack>, ICollection<TomatenMusicTrack>, IEnumerable<TomatenMusicTrack>, IEnumerable, IPlayableItem
+    {
+        private readonly List<TomatenMusicTrack> _list;
+
+        private readonly object _syncRoot;
+
+        //
+        // Summary:
+        //     Gets the number of queued tracks.
+        //
+        // Remarks:
+        //     This property is thread-safe, so it can be used from multiple threads at once
+        //     safely.
+        public int Count
+        {
+            get
+            {
+                lock (_syncRoot)
+                {
+                    return _list.Count;
+                }
+            }
+        }
+
+        //
+        // Summary:
+        //     Gets a value indicating whether the queue is empty.
+        //
+        // Remarks:
+        //     This property is thread-safe, so it can be used from multiple threads at once
+        //     safely.
+        public bool IsEmpty => Count == 0;
+
+        //
+        // Summary:
+        //     Gets a value indicating whether the queue is read-only.
+        //
+        // Remarks:
+        //     This property is thread-safe, so it can be used from multiple threads at once
+        //     safely.
+        public bool IsReadOnly => false;
+
+        //
+        // Summary:
+        //     Gets or sets the enqueued tracks.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public IReadOnlyList<TomatenMusicTrack> Tracks
+        {
+            get
+            {
+                lock (_syncRoot)
+                {
+                    return _list.ToArray();
+                }
+            }
+            set
+            {
+                lock (_syncRoot)
+                {
+                    _list.Clear();
+                    _list.AddRange(value);
+                }
+            }
+        }
+
+        public string Title => $"Track List with {Count} Tracks";
+
+        //
+        // Summary:
+        //     Gets or sets the track at the specified index.
+        //
+        // Parameters:
+        //   index:
+        //     the zero-based position
+        //
+        // Returns:
+        //     the track at the specified index
+        //
+        // Remarks:
+        //     This indexer property is thread-safe, so it can be used from multiple threads
+        //     at once safely.
+        public TomatenMusicTrack this[int index]
+        {
+            get
+            {
+                lock (_syncRoot)
+                {
+                    return _list[index];
+                }
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException("value");
+                }
+
+                lock (_syncRoot)
+                {
+                    _list[index] = value;
+                }
+            }
+        }
+
+
+        public TrackList()
+        {
+            _list = new List<TomatenMusicTrack>();
+            _syncRoot = new object();
+        }
+
+        public TrackList(IEnumerable<LavalinkTrack> tracks)
+        {
+            _list = new List<TomatenMusicTrack>();
+            _syncRoot = new object();
+
+            foreach (var track in tracks)
+                Add(new TomatenMusicTrack(track));
+        }
+
+        //
+        // Summary:
+        //     Adds a track at the end of the queue.
+        //
+        // Parameters:
+        //   track:
+        //     the track to add
+        //
+        // Exceptions:
+        //   T:System.ArgumentNullException:
+        //     thrown if the specified track is null.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void Add(TomatenMusicTrack track)
+        {
+            if (track == null)
+            {
+                throw new ArgumentNullException("track");
+            }
+
+            lock (_syncRoot)
+            {
+                _list.Add(track);
+            }
+        }
+
+        //
+        // Summary:
+        //     Adds all specified tracks to the queue.
+        //
+        // Parameters:
+        //   tracks:
+        //     the tracks to enqueue
+        //
+        // Exceptions:
+        //   T:System.ArgumentNullException:
+        //     thrown if the specified tracks enumerable is null.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void AddRange(IEnumerable<TomatenMusicTrack> tracks)
+        {
+            if (tracks == null)
+            {
+                throw new ArgumentNullException("tracks");
+            }
+
+            lock (_syncRoot)
+            {
+                _list.AddRange(tracks);
+            }
+        }
+
+        //
+        // Summary:
+        //     Clears all tracks from the queue.
+        //
+        // Returns:
+        //     the number of tracks removed
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public int Clear()
+        {
+            lock (_syncRoot)
+            {
+                int count = _list.Count;
+                _list.Clear();
+                return count;
+            }
+        }
+
+        //
+        // Summary:
+        //     Gets a value indicating whether the specified track is in the queue.
+        //
+        // Parameters:
+        //   track:
+        //     the track to find
+        //
+        // Returns:
+        //     a value indicating whether the specified track is in the queue
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public bool Contains(TomatenMusicTrack track)
+        {
+            if (track == null)
+            {
+                throw new ArgumentNullException("track");
+            }
+
+            lock (_syncRoot)
+            {
+                return _list.Contains(track);
+            }
+        }
+
+        //
+        // Summary:
+        //     Copies all tracks to the specified array at the specified index.
+        //
+        // Parameters:
+        //   array:
+        //     the array to the tracks to
+        //
+        //   index:
+        //     the zero-based writing start index
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void CopyTo(TomatenMusicTrack[] array, int index)
+        {
+            lock (_syncRoot)
+            {
+                _list.CopyTo(array, index);
+            }
+        }
+
+        //
+        // Summary:
+        //     Dequeues a track using the FIFO method.
+        //
+        // Returns:
+        //     the dequeued track
+        //
+        // Exceptions:
+        //   T:System.InvalidOperationException:
+        //     thrown if no tracks were in the queue
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public TomatenMusicTrack Dequeue()
+        {
+            lock (_syncRoot)
+            {
+                if (_list.Count <= 0)
+                {
+                    throw new InvalidOperationException("No tracks in to dequeue.");
+                }
+
+                TomatenMusicTrack result = _list[0];
+                _list.RemoveAt(0);
+                return result;
+            }
+        }
+
+        //
+        // Summary:
+        //     Deletes all duplicate tracks from the queue.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void Distinct()
+        {
+            lock (_syncRoot)
+            {
+                if (_list.Count > 1)
+                {
+                    TomatenMusicTrack[] collection = (from track in _list
+                                                  group track by track.Identifier into s
+                                                  select s.First()).ToArray();
+                    _list.Clear();
+                    _list.AddRange(collection);
+                }
+            }
+        }
+
+        //
+        // Summary:
+        //     Gets the track enumerator.
+        //
+        // Returns:
+        //     the track enumerator
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public IEnumerator<TomatenMusicTrack> GetEnumerator()
+        {
+            lock (_syncRoot)
+            {
+                return _list.ToList().GetEnumerator();
+            }
+        }
+
+        //
+        // Summary:
+        //     Gets the zero-based index of the specified track.
+        //
+        // Parameters:
+        //   track:
+        //     the track to locate
+        //
+        // Returns:
+        //     the zero-based index of the specified track
+        //
+        // Exceptions:
+        //   T:System.ArgumentNullException:
+        //     thrown if the specified track is null.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public int IndexOf(TomatenMusicTrack track)
+        {
+            if (track == null)
+            {
+                throw new ArgumentNullException("track");
+            }
+
+            lock (_syncRoot)
+            {
+                return _list.IndexOf(track);
+            }
+        }
+
+        //
+        // Summary:
+        //     Inserts the specified track at the specified index.
+        //
+        // Parameters:
+        //   index:
+        //     the zero-based index to insert (e.g. 0 = top)
+        //
+        //   track:
+        //     the track to insert
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void Insert(int index, TomatenMusicTrack track)
+        {
+            lock (_syncRoot)
+            {
+                _list.Insert(index, track);
+            }
+        }
+
+        //
+        // Summary:
+        //     Tries to remove the specified track from the queue.
+        //
+        // Parameters:
+        //   track:
+        //     the track to remove
+        //
+        // Returns:
+        //     a value indicating whether the track was found and removed from the queue
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public bool Remove(TomatenMusicTrack track)
+        {
+            lock (_syncRoot)
+            {
+                return _list.Remove(track);
+            }
+        }
+
+        //
+        // Summary:
+        //     Removes all tracks that matches the specified predicate.
+        //
+        // Parameters:
+        //   predicate:
+        //     the track predicate
+        //
+        // Returns:
+        //     the number of tracks removed
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public int RemoveAll(Predicate<TomatenMusicTrack> predicate)
+        {
+            lock (_syncRoot)
+            {
+                return _list.RemoveAll(predicate);
+            }
+        }
+
+        //
+        // Summary:
+        //     Removes a track at the specified index.
+        //
+        // Parameters:
+        //   index:
+        //     the index to remove the track
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void RemoveAt(int index)
+        {
+            lock (_syncRoot)
+            {
+                _list.RemoveAt(index);
+            }
+        }
+
+        //
+        // Summary:
+        //     Removes all count tracks from the specified index.
+        //
+        // Parameters:
+        //   index:
+        //     the start index (zero-based)
+        //
+        //   count:
+        //     the number of tracks to remove
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void RemoveRange(int index, int count)
+        {
+            lock (_syncRoot)
+            {
+                _list.RemoveRange(index, count);
+            }
+        }
+
+        //
+        // Summary:
+        //     Shuffles / mixes all tracks in the queue.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public void Shuffle()
+        {
+            lock (_syncRoot)
+            {
+                if (_list.Count > 2)
+                {
+                    TomatenMusicTrack[] collection = _list.OrderBy((TomatenMusicTrack s) => Guid.NewGuid()).ToArray();
+                    _list.Clear();
+                    _list.AddRange(collection);
+                }
+            }
+        }
+
+        //
+        // Summary:
+        //     Tries to dequeue a track using the FIFO method.
+        //
+        // Parameters:
+        //   track:
+        //     the dequeued track; or default is the result is false.
+        //
+        // Returns:
+        //     a value indicating whether a track was dequeued.
+        //
+        // Exceptions:
+        //   T:System.InvalidOperationException:
+        //     thrown if no tracks were in the queue
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        public bool TryDequeue(out TomatenMusicTrack? track)
+        {
+            lock (_syncRoot)
+            {
+                if (_list.Count <= 0)
+                {
+                    track = null;
+                    return false;
+                }
+
+                track = _list[0];
+                _list.RemoveAt(0);
+                return true;
+            }
+        }
+
+        //
+        // Summary:
+        //     Clears the queue.
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        void ICollection<TomatenMusicTrack>.Clear()
+        {
+            lock (_syncRoot)
+            {
+                _list.Clear();
+            }
+        }
+
+        //
+        // Summary:
+        //     Gets the track enumerator.
+        //
+        // Returns:
+        //     the track enumerator
+        //
+        // Remarks:
+        //     This method is thread-safe, so it can be used from multiple threads at once safely.
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            lock (_syncRoot)
+            {
+                return _list.ToArray().GetEnumerator();
+            }
+        }
+
+        public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+            await player.PlayerQueue.QueueTracksAsync(this);
+
+            if (player.State == PlayerState.NotPlaying)
+            {
+                LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
+                await player.PlayAsync(nextTrack, startTime, endTime, noReplace);
+            }
+        }
+
+        public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+            Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(this);
+
+            player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
+            TomatenMusicTrack track = reversedTracks.Dequeue();
+            player.PlayerQueue.LastTrack = track;
+            await player.PlayAsync(track, startTime, endTime);
+
+            reversedTracks.Reverse();
+
+            foreach (var item in reversedTracks)
+            {
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
new file mode 100644
index 0000000..641a21e
--- /dev/null
+++ b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using Google.Apis.YouTube.v3.Data;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using TomatenMusic.Services;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music.Entitites
+{
+    public class YoutubePlaylist : ILavalinkPlaylist
+    {
+        public string Title { get; }
+
+        public TrackList Tracks { get; }
+
+        public int TrackCount { get; }
+
+        public Uri Url { get; }
+
+        public string AuthorName { get; set; }
+        public Uri AuthorUri { get; set; }
+        public string Description { get; set; }
+        public Uri Thumbnail { get; set; }
+        public DateTime CreationTime { get; set; }
+        public string Identifier { get; }
+        public Playlist YoutubeItem { get; set; }
+        public Uri AuthorThumbnail { get; set; }
+
+        public YoutubePlaylist(string name, TrackList tracks, string id)
+        {
+            Identifier = id;
+            Title = name;
+            Tracks = tracks;
+            Url = new Uri($"https://youtube.com/playlist?list={id}");
+            TrackCount = tracks.Count();
+
+        }
+
+        public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+            await player.PlayerQueue.QueuePlaylistAsync(this);
+
+
+            if (player.State == PlayerState.NotPlaying)
+            {
+                LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
+                await player.PlayAsync(nextTrack);
+            }
+        }
+
+        public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+            if (!player.PlayerQueue.Queue.Any())
+                player.PlayerQueue.CurrentPlaylist = this;
+
+            player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
+
+            Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
+
+            TomatenMusicTrack track = reversedTracks.Dequeue();
+            player.PlayerQueue.LastTrack = track;
+            await player.PlayAsync(track);
+
+            reversedTracks.Reverse();
+
+            foreach (var item in reversedTracks)
+            {
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
new file mode 100644
index 0000000..f0510ea
--- /dev/null
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -0,0 +1,290 @@
+using DSharpPlus;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using System.Linq;
+using TomatenMusic.Music.Entitites;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Services;
+using TomatenMusic.Prompt.Implementation;
+using Lavalink4NET.Player;
+using Lavalink4NET.Events;
+using Lavalink4NET;
+using Lavalink4NET.Rest;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET.Decoding;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music
+{
+    public class GuildPlayer : LavalinkPlayer
+    {
+
+        ILogger<GuildPlayer> _logger { get; set; }
+        public PlayerQueue PlayerQueue { get;} = new PlayerQueue();
+        public DiscordClient _client { get; set; }
+        public ISpotifyService _spotify { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public bool Autoplay { get; set; } = false;
+
+        public GuildPlayer()
+        {
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            _logger = serviceProvider.GetRequiredService<ILogger<GuildPlayer>>();
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard(GuildId);
+
+            _spotify = serviceProvider.GetRequiredService<ISpotifyService>();
+            _audioService = serviceProvider.GetRequiredService<IAudioService>();
+        }
+        public async Task PlayItemAsync(IPlayableItem item, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
+        {
+
+            EnsureConnected();
+            EnsureNotDestroyed();
+
+            _ =  item.Play(this, startTime, endTime, noReplace);
+            _logger.LogInformation("Started playing Item {0} on Guild {1}", item.Title, (await GetGuildAsync()).Name);
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayNowAsync(IPlayableItem item, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
+        {
+            EnsureConnected();
+            EnsureNotDestroyed();
+
+            _ = item.PlayNow(this, startTime, endTime, withoutQueuePrepend);
+            _logger.LogInformation("Started playing Item {0} now on Guild {1}", item.Title, (await GetGuildAsync()).Name);
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayPlaylistAsync(ILavalinkPlaylist playlist)
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            _logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Title, (await GetGuildAsync()).Name);
+
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task PlayPlaylistNowAsync(ILavalinkPlaylist playlist)
+        {
+            EnsureConnected();
+            EnsureNotDestroyed();
+
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task RewindAsync()
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+            if (Position.Position.Seconds > 5)
+            {
+                await ReplayAsync();
+                return;
+            }
+
+            MusicActionResponse response = PlayerQueue.Rewind();
+            
+            _logger.LogInformation($"Rewinded Track {CurrentTrack.Title} for Track {response.Track.Title}");
+            await base.PlayAsync(response.Track);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task SkipAsync()
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            MusicActionResponse response;
+            try
+            {
+                response = PlayerQueue.NextTrack(true);
+            }catch (Exception ex)
+            {
+                if (Autoplay)
+                {
+                    YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+                    LavalinkTrack newTrack = await youtube.GetRelatedTrackAsync(CurrentTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
+
+                    _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Autoplayed Track {newTrack.Title}");
+                    await PlayAsync(newTrack);
+                    QueuePrompt.UpdateFor(GuildId);
+                    return;
+                }
+                throw ex;
+            }
+
+            _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Track {response.Track.Title}");
+            await base.PlayAsync(response.Track);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task TogglePauseAsync()
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant pause Song! Nothing is Playing.");
+
+
+            if (State == PlayerState.Paused)
+                await ResumeAsync();
+            else
+                await PauseAsync();
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
+        public async Task SetLoopAsync(LoopType type)
+        {
+
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
+
+            _ = PlayerQueue.SetLoopAsync(type);
+            QueuePrompt.UpdateFor(GuildId);
+
+        }
+
+        public async Task ShuffleAsync()
+        {
+
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            await PlayerQueue.ShuffleAsync();
+
+            QueuePrompt.UpdateFor(GuildId);
+        }
+        public async override Task ConnectAsync(ulong voiceChannelId, bool selfDeaf = true, bool selfMute = false)
+        {
+            EnsureNotDestroyed();
+
+            DiscordChannel channel = await _client.GetChannelAsync(voiceChannelId);
+
+            if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage) throw new ArgumentException("The channel Id provided was not a voice channel");
+
+            if (State != PlayerState.NotConnected)
+                throw new InvalidOperationException("The Bot is already connected.");
+
+            await base.ConnectAsync(voiceChannelId, selfDeaf, selfMute);
+
+            if (channel.Type == ChannelType.Stage)
+            {
+                DiscordStageInstance stageInstance = await channel.GetStageInstanceAsync();
+
+                if (stageInstance == null)
+                    stageInstance = await channel.CreateStageInstanceAsync("Music");
+                await stageInstance.Channel.UpdateCurrentUserVoiceStateAsync(false);
+            }
+
+            _logger.LogInformation("Connected to Channel {0} on Guild {1}", channel.Name, channel.Guild.Name);
+        }
+        public override Task DisconnectAsync()
+        {
+            _logger.LogInformation("Disconnected from Channel {0} on Guild {1}", VoiceChannelId, GuildId);
+
+            QueuePrompt.InvalidateFor(GuildId);
+            return base.DisconnectAsync();
+        }
+
+        public override async Task SeekPositionAsync(TimeSpan timeSpan)
+        {
+            EnsureNotDestroyed();
+            EnsureConnected();
+
+            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");
+
+            if (timeSpan.CompareTo(CurrentTrack.Duration) == 1) throw new ArgumentException("Please specify a TimeSpan shorter than the Track");
+
+            await base.SeekPositionAsync(timeSpan);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+        protected override void Dispose(bool disposing)
+        {
+            QueuePrompt.InvalidateFor(GuildId);
+
+            base.Dispose(disposing);
+        }
+
+        public async override Task OnTrackEndAsync(TrackEndEventArgs eventArgs)
+        {
+            DisconnectOnStop = false;
+            YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+            var oldTrack = CurrentTrack;
+
+            if (eventArgs.Reason != TrackEndReason.Finished)
+                return;
+
+            if (eventArgs.MayStartNext)
+            {
+                try
+                {
+                    MusicActionResponse response = PlayerQueue.NextTrack();
+                    _ = PlayNowAsync(response.Track, withoutQueuePrepend: true);
+                }
+                catch (Exception ex)
+                {
+                    if (!Autoplay)
+                    {
+                        _logger.LogInformation("Track has ended and Queue was Empty... Idling");
+                        QueuePrompt.UpdateFor(GuildId);
+                        await base.OnTrackEndAsync(eventArgs);
+                        return;
+                    }
+
+                    TomatenMusicTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
+                    _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
+                    await base.OnTrackEndAsync(eventArgs);
+                    await newTrack.Play(this);
+                    QueuePrompt.UpdateFor(GuildId);
+
+                }
+            }
+
+            
+        }
+
+        public async Task<DiscordChannel> GetChannelAsync()
+        {
+            EnsureConnected();
+            EnsureNotDestroyed();
+            DiscordGuild guild = await GetGuildAsync();
+
+            return guild.GetChannel((ulong) VoiceChannelId);
+        }
+
+        public async Task<DiscordGuild> GetGuildAsync()
+        {
+            return await _client.GetGuildAsync(GuildId);
+        }
+        public async Task<bool> AreActionsAllowedAsync(DiscordMember member)
+        {
+            if (member.VoiceState == null || member.VoiceState.Channel == null)
+            {
+                return false;
+            }
+
+            if (await GetChannelAsync() != null && await GetChannelAsync() != member.VoiceState.Channel)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Music/LoopType.cs b/TomatenMusicCore/Music/LoopType.cs
new file mode 100644
index 0000000..f44ac43
--- /dev/null
+++ b/TomatenMusicCore/Music/LoopType.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus.SlashCommands;
+
+namespace TomatenMusic.Music
+{
+    public enum LoopType
+    {
+        [ChoiceName("Track")]
+        TRACK,
+        [ChoiceName("Queue")]
+        QUEUE,
+        [ChoiceName("None")]
+        NONE
+    }
+}
diff --git a/TomatenMusicCore/Music/MusicActionResponse.cs b/TomatenMusicCore/Music/MusicActionResponse.cs
new file mode 100644
index 0000000..17cd5ff
--- /dev/null
+++ b/TomatenMusicCore/Music/MusicActionResponse.cs
@@ -0,0 +1,31 @@
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Music.Entitites;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music
+{
+    public class MusicActionResponse
+    {
+        public ILavalinkPlaylist Playlist { get; }
+        public TomatenMusicTrack Track { get; }
+        public TrackList Tracks { get; }
+        public bool IsPlaylist { get; }
+        public MusicActionResponse(TomatenMusicTrack track = null, ILavalinkPlaylist playlist = null, TrackList tracks = null)
+        {
+            Playlist = playlist;
+            Track = track;
+            IsPlaylist = playlist != null;
+            Tracks = tracks;
+            if (track != null)
+            {
+                var list = new TrackList();
+                list.Add(track);
+                Tracks = list;
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
new file mode 100644
index 0000000..b705f8a
--- /dev/null
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using TomatenMusic.Music.Entitites;
+using System.Threading.Tasks;
+using System.Linq;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music
+{
+    public class PlayerQueue
+    {
+        
+        public Queue<TomatenMusicTrack> Queue { get; set; } = new Queue<TomatenMusicTrack>();
+        public Queue<TomatenMusicTrack> PlayedTracks { get; set; } = new Queue<TomatenMusicTrack>();
+        public ILogger<PlayerQueue> _logger { get; set; } = TomatenMusicBot.ServiceProvider.GetRequiredService<ILogger<PlayerQueue>>();
+        public ILavalinkPlaylist CurrentPlaylist { get; set; }
+
+        public LoopType LoopType { get; private set; } = LoopType.NONE;
+
+        public TomatenMusicTrack LastTrack { get; set; }
+
+        public List<TomatenMusicTrack> QueueLoopList { get; private set; }
+
+        public void QueueTrack(TomatenMusicTrack track)
+        {
+            CurrentPlaylist = null;
+            Queue.Enqueue(track);
+            _logger.LogInformation("Queued Track {0}", track.Title);
+
+            if (LoopType == LoopType.QUEUE)
+                QueueLoopList.Add(track);
+        }
+
+        public Task QueuePlaylistAsync(ILavalinkPlaylist playlist)
+        {
+            return Task.Run(() =>
+            {
+                if (CurrentPlaylist == null && Queue.Count == 0)
+                    CurrentPlaylist = playlist;
+                else
+                    CurrentPlaylist = null;
+
+                _logger.LogInformation("Queued Playlist {0}", playlist.Title);
+                foreach (var track in playlist.Tracks)
+                {
+                    Queue.Enqueue(track);
+                }
+
+
+
+                if (LoopType == LoopType.QUEUE)
+                    QueueLoopList.AddRange(playlist.Tracks);
+            });
+
+        }
+
+        public Task QueueTracksAsync(TrackList tracks)
+        {
+            return Task.Run(() =>
+            {
+                CurrentPlaylist = null;
+                _logger.LogInformation("Queued TrackList {0}", tracks.ToString());
+                foreach (var track in tracks)
+                {
+                    Queue.Enqueue(track);
+                }
+                if (LoopType == LoopType.QUEUE)
+                    QueueLoopList.AddRange(tracks);
+            });
+
+        }
+
+        public void Clear()
+        {
+            Queue.Clear();
+            PlayedTracks.Clear();
+        }
+
+        public void RemoveAt(int index)
+        {
+            if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
+            List<TomatenMusicTrack> tracks = Queue.ToList();
+            tracks.RemoveAt(index);
+            Queue = new Queue<TomatenMusicTrack>(tracks);
+
+        }
+
+        public MusicActionResponse NextTrack(bool ignoreLoop = false)
+        {
+            if (LastTrack != null)
+                PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(LastTrack));
+
+            switch (LoopType)
+            {
+                case LoopType.NONE:
+                    if (Queue.Count == 0) throw new InvalidOperationException("Queue was Empty");
+
+                    LastTrack = Queue.Dequeue();
+
+                    return new MusicActionResponse(LastTrack);
+                case LoopType.TRACK:
+                    if (ignoreLoop)
+                    {
+                        LastTrack = Queue.Dequeue();
+                        return new MusicActionResponse(LastTrack);
+                    }
+
+                    return new MusicActionResponse(LastTrack);
+                case LoopType.QUEUE:
+                    if (!Queue.Any())
+                    {
+                        if (CurrentPlaylist != null)
+                            Queue = new Queue<TomatenMusicTrack>(CurrentPlaylist.Tracks);
+                        else
+                            Queue = new Queue<TomatenMusicTrack>(QueueLoopList);
+                    }
+
+                    LastTrack = Queue.Dequeue();
+
+                    return new MusicActionResponse(LastTrack);
+                default:
+                    throw new NullReferenceException("LoopType was null");
+            }
+        }
+
+        public MusicActionResponse Rewind()
+        {
+
+            if (!PlayedTracks.Any()) throw new InvalidOperationException("There are no songs that could be rewinded to yet.");
+
+            Queue = new Queue<TomatenMusicTrack>(Queue.Prepend(LastTrack));
+            LastTrack = PlayedTracks.Dequeue();
+
+            return new MusicActionResponse(LastTrack);
+        }
+
+        public Task ShuffleAsync()
+        {
+            if (Queue.Count == 0) throw new InvalidOperationException("Queue is Empty");
+
+            List<TomatenMusicTrack> tracks = new List<TomatenMusicTrack>(Queue);
+            tracks.Shuffle();
+            Queue = new Queue<TomatenMusicTrack>(tracks);
+            return Task.CompletedTask;
+        }
+
+        public async Task SetLoopAsync(LoopType type)
+        {
+            LoopType = type;
+
+            if (type == LoopType.QUEUE)
+            {
+                QueueLoopList = new List<TomatenMusicTrack>(Queue);
+                QueueLoopList.Add(LastTrack);
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs b/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
new file mode 100644
index 0000000..951296c
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
@@ -0,0 +1,59 @@
+using DSharpPlus.Entities;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music;
+using TomatenMusic.Music.Entitites;
+using Microsoft.Extensions.DependencyInjection;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+using TomatenMusic.Prompt.Option;
+
+namespace TomatenMusic.Prompt.Buttons
+{
+    class AddToQueueButton : ButtonPromptOption
+    {
+        public TrackList Tracks { get; set; }
+
+        public AddToQueueButton(TrackList tracks, int row, DiscordMember requestMember)
+        {
+            Tracks = tracks;
+            Emoji = new DiscordComponentEmoji("▶️");
+                Row = row;
+                Style = DSharpPlus.ButtonStyle.Secondary;
+                UpdateMethod = (prompt) =>
+                {
+                    if (requestMember.VoiceState == null || requestMember.VoiceState.Channel == null)
+                        prompt.Disabled = true;
+
+                    return Task.FromResult(prompt);
+                };
+            Run = async (args, sender, option) =>
+            {
+                IAudioService audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+                GuildPlayer player;
+
+                try
+                {
+                    try
+                    {
+                        player = await audioService.JoinAsync<GuildPlayer>(args.Guild.Id, ((DiscordMember)args.User).VoiceState.Channel.Id, true);
+
+                    }catch (Exception ex)
+                    {
+                        player = audioService.GetPlayer<GuildPlayer>(args.Guild.Id);
+                    }
+                    await player.PlayItemAsync(Tracks);
+                }
+                catch (Exception ex)
+                {
+
+                }
+            };
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs b/TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs
new file mode 100644
index 0000000..48f4eca
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs
@@ -0,0 +1,60 @@
+using DSharpPlus.Entities;
+using Lavalink4NET;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic;
+using TomatenMusic.Music;
+using TomatenMusic.Prompt;
+using TomatenMusic.Prompt.Option;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusicCore.Prompt.Buttons
+{
+    class PlayNowButton : ButtonPromptOption
+    {
+        public TrackList Tracks { get; set; }
+
+        public PlayNowButton(TrackList tracks, int row, DiscordMember requestMember)
+        {
+            Tracks = tracks;
+            Emoji = new DiscordComponentEmoji("▶");
+            Content = "Now";
+            Row = row;
+            Style = DSharpPlus.ButtonStyle.Secondary;
+            UpdateMethod = (prompt) =>
+            {
+                if (requestMember.VoiceState == null || requestMember.VoiceState.Channel == null)
+                    prompt.Disabled = true;
+
+                return Task.FromResult(prompt);
+            };
+            Run = async (args, sender, option) =>
+            {
+                IAudioService audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+                GuildPlayer player;
+
+                try
+                {
+                    try
+                    {
+                        player = await audioService.JoinAsync<GuildPlayer>(args.Guild.Id, ((DiscordMember)args.User).VoiceState.Channel.Id, true);
+
+                    }
+                    catch (Exception ex)
+                    {
+                        player = audioService.GetPlayer<GuildPlayer>(args.Guild.Id);
+                    }
+                    await player.PlayNowAsync(Tracks);
+                }
+                catch (Exception ex)
+                {
+
+                }
+            };
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/PlaylistSongSelectorPrompt.cs b/TomatenMusicCore/Prompt/Implementation/PlaylistSongSelectorPrompt.cs
new file mode 100644
index 0000000..6b3e16a
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/PlaylistSongSelectorPrompt.cs
@@ -0,0 +1,105 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.EventArgs;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Prompt;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Util;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusicCore.Prompt.Implementation
+{
+    class PlaylistSongSelectorPrompt : PaginatedSelectPrompt<TomatenMusicTrack>
+    {
+        public bool IsConfirmed { get; set; }
+        public Func<TrackList, Task> ConfirmCallback { get; set; } = (tracks) =>
+        {
+            return Task.CompletedTask;
+        };
+
+        public ILavalinkPlaylist Playlist { get; private set; }
+
+        public PlaylistSongSelectorPrompt(ILavalinkPlaylist playlist, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(playlist.Title, playlist.Tracks.ToList(), lastPrompt, embeds)
+        {
+            Playlist = playlist;
+            AddOption(new ButtonPromptOption
+            {
+                Emoji = new DiscordComponentEmoji("✔️"),
+                Row = 3,
+                Style = ButtonStyle.Success,
+                Run = async (args, client, option) =>
+                {
+                    if (SelectedItems.Count == 0)
+                    {
+                        await args.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("Please Select a Song!").AsEphemeral(true));
+                        return;
+                    }
+                    IsConfirmed = true;
+                    _ = ConfirmCallback.Invoke(new TrackList(SelectedItems));
+                }
+            });
+        }
+        public override Task<PaginatedSelectMenuOption<TomatenMusicTrack>> ConvertToOption(TomatenMusicTrack item)
+        {
+            return Task.FromResult<PaginatedSelectMenuOption<TomatenMusicTrack>>(new PaginatedSelectMenuOption<TomatenMusicTrack>
+            {
+                Label = item.Title,
+                Description = item.Author
+            });
+
+        }
+
+        public override Task OnSelect(TomatenMusicTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Added {item.Title}, {SelectedItems}");
+            return Task.CompletedTask;
+        }
+
+        public override Task OnUnselect(TomatenMusicTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Removed {item.Title}");
+            return Task.CompletedTask;
+
+        }
+
+        public async Task<TrackList> AwaitSelectionAsync()
+        {
+            return await Task.Run(() =>
+            {
+                while (!IsConfirmed)
+                {
+                    if (State == PromptState.INVALID)
+                        throw new InvalidOperationException("Prompt has been Invalidated");
+                }
+                IsConfirmed = false;
+                return new TrackList(SelectedItems);
+            });
+        }
+
+        protected override DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder)
+        {
+
+            builder.WithTitle(Title);
+            builder.WithDescription(Common.TrackListString(PageManager.GetPage(CurrentPage), 4000));
+            builder.WithUrl(Playlist.Url);
+            builder.WithAuthor(Playlist.AuthorName, Playlist.AuthorUri.ToString(), Playlist.AuthorThumbnail.ToString());
+            
+            List<DiscordEmbed> embeds = new List<DiscordEmbed>();
+            embeds.Add(builder.Build());
+
+            if (Embeds != null)
+                embeds.AddRange(Embeds);
+
+            return new DiscordMessageBuilder().AddEmbeds(embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
new file mode 100644
index 0000000..fb087d8
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
@@ -0,0 +1,278 @@
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+using TomatenMusic.Music;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Util;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class QueuePrompt : ButtonPrompt
+    {
+
+        public static void InvalidateFor(ulong guildId)
+        {
+            foreach (var prompt in ActivePrompts)
+            {
+                if (prompt.State != PromptState.OPEN)
+                    continue;
+                if (!(prompt is QueuePrompt))
+                    continue;
+                if (((QueuePrompt)prompt).Player.GuildId != guildId)
+                    continue;
+                _ = prompt.InvalidateAsync();
+
+            }
+        }
+        public static void UpdateFor(ulong guildId)
+        {
+            _ = Task.Delay(400).ContinueWith(async (task) =>
+            {
+                foreach (var prompt in ActivePrompts)
+                {
+                    if (prompt.State != PromptState.OPEN)
+                        continue;
+                    if (!(prompt is QueuePrompt))
+                        continue;
+                    if (((QueuePrompt)prompt).Player.GuildId != guildId)
+                        continue;
+                    _ = prompt.UpdateAsync();
+                }
+            });
+        }
+
+        public GuildPlayer Player { get; private set; }
+
+        public QueuePrompt(GuildPlayer player, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(lastPrompt, embeds: embeds)
+        {
+            Player = player;
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Emoji = new DiscordComponentEmoji("⏯️"),
+                    Row = 1,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+                        if (player.State == PlayerState.Paused)
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+                        else
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+
+                        return Task.FromResult((IPromptOption) button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        await Player.TogglePauseAsync();
+                    }
+                }
+                );
+
+            AddOption(new ButtonPromptOption()
+                {
+                Emoji = new DiscordComponentEmoji("⏮️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+                    try
+                    {
+                        await Player.RewindAsync();
+                    }catch (Exception ex)
+                    {
+
+                    }
+                }
+            }
+            );
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("⏹️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.DisconnectAsync();
+                }
+            });
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("⏭️"),
+                Row = 1,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.SkipAsync();
+
+                    System.Timers.Timer timer = new System.Timers.Timer(800);
+                    timer.Elapsed += (s, args) =>
+                    {
+                        _ = UpdateAsync();
+                        timer.Stop();
+                    };
+                    timer.Start();
+                }
+            }
+            );
+
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Row = 1,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+
+                        if (player.PlayerQueue.LoopType == LoopType.TRACK)
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                            button.Emoji = new DiscordComponentEmoji("🔂");
+                        }
+                        else if (player.PlayerQueue.LoopType == LoopType.QUEUE)
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                            button.Emoji = new DiscordComponentEmoji("🔁");
+                        }
+                        else
+                        {
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+                            button.Emoji = null;
+                            button.Content = "Loop";
+                        }
+
+
+                        return Task.FromResult((IPromptOption)button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        switch (player.PlayerQueue.LoopType)
+                        {
+                            case LoopType.NONE:
+                                _ = Player.SetLoopAsync(LoopType.QUEUE);
+                                break;
+                            case LoopType.QUEUE:
+                                _ = Player.SetLoopAsync(LoopType.TRACK);
+                                break;
+                            case LoopType.TRACK:
+                                _ = Player.SetLoopAsync(LoopType.NONE);
+                                break;
+                        }
+                    }
+                }
+                );
+
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("🔀"),
+                Row = 2,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    await Player.ShuffleAsync();
+
+                }
+            });
+
+            AddOption(new ButtonPromptOption()
+            {
+                Emoji = new DiscordComponentEmoji("🚫"),
+                Row = 2,
+                Style = DSharpPlus.ButtonStyle.Secondary,
+                Run = async (args, sender, option) =>
+                {
+                    if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                    {
+                        _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                        return;
+                    }
+
+                    Player.PlayerQueue.Queue.Clear();
+
+                    _ = UpdateAsync();
+                }
+            });
+
+            AddOption(
+                new ButtonPromptOption()
+                {
+                    Emoji = new DiscordComponentEmoji("➡️"),
+                    Content = "AutoPlay",
+                    Row = 2,
+                    UpdateMethod = (option) =>
+                    {
+                        ButtonPromptOption button = (ButtonPromptOption)option;
+                        if (player.Autoplay)
+                            button.Style = DSharpPlus.ButtonStyle.Success;
+                        else
+                            button.Style = DSharpPlus.ButtonStyle.Danger;
+
+                        return Task.FromResult((IPromptOption)button);
+                    },
+                    Run = async (args, sender, option) =>
+                    {
+                        if (!await Player.AreActionsAllowedAsync((DiscordMember)args.User))
+                        {
+                            _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
+                            return;
+                        }
+
+                        Player.Autoplay = !Player.Autoplay;
+
+                        _ = UpdateAsync();
+                    }
+                }
+                );
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder()
+                .AddEmbed(Common.GetQueueEmbed(Player))
+                .AddEmbed(await Common.CurrentSongEmbedAsync(Player))
+                .AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
new file mode 100644
index 0000000..05ba0ba
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
@@ -0,0 +1,35 @@
+using DSharpPlus.Entities;
+using Lavalink4NET.Player;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Prompt.Buttons;
+using TomatenMusic.Prompt.Model;
+using TomatenMusic.Util;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+using TomatenMusicCore.Prompt.Buttons;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class SongActionPrompt : ButtonPrompt
+    {
+        public LavalinkTrack Track { get; set; }
+        public SongActionPrompt(TomatenMusicTrack track, DiscordMember requestMember, List<DiscordEmbed> embeds = null)
+        {
+            Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+            Track = track;
+
+            AddOption(new AddToQueueButton(new TrackList() { track }, 1, requestMember));
+            AddOption(new PlayNowButton(new TrackList() { track }, 1, requestMember));
+
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder().AddEmbed(Common.AsEmbed(Track)).AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
new file mode 100644
index 0000000..f39b339
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongListActionPrompt.cs
@@ -0,0 +1,45 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Prompt.Model;
+using System.Linq;
+using TomatenMusic.Util;
+using TomatenMusic.Music;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Buttons;
+using Lavalink4NET.Player;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+using TomatenMusicCore.Prompt.Buttons;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class SongListActionPrompt : ButtonPrompt
+    {
+        //TODO
+        public TrackList Tracks { get; private set; }
+
+        public SongListActionPrompt(TrackList tracks, DiscordMember requestMember, DiscordPromptBase lastPrompt = null) : base(lastPrompt)
+        {
+            Tracks = tracks;
+
+            AddOption(new AddToQueueButton(tracks, 1, requestMember));
+            AddOption(new PlayNowButton(tracks, 1, requestMember));
+
+        }
+
+        protected override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle("What do you want to do with these Tracks?");
+
+            builder.WithDescription(Common.TrackListString(Tracks, 1000));
+
+            return Task.FromResult(new DiscordMessageBuilder().WithEmbed(builder.Build()));
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs b/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
new file mode 100644
index 0000000..fbb2e46
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus;
+using System.Threading.Tasks;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using DSharpPlus.Entities;
+using TomatenMusic.Util;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Music;
+using System.Linq;
+using Lavalink4NET.Player;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+using TomatenMusic.Prompt.Option;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    sealed class SongSelectorPrompt : PaginatedSelectPrompt<LavalinkTrack>
+    {
+        public bool IsConfirmed { get; set; }
+        public Func<TrackList, Task> ConfirmCallback { get; set; } = (tracks) =>
+        {
+            return Task.CompletedTask;
+        };
+
+        public IEnumerable<LavalinkTrack> Tracks { get; private set; }
+
+        public SongSelectorPrompt(string title, IEnumerable<LavalinkTrack> tracks, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(title, tracks.ToList(), lastPrompt, embeds)
+        {
+            Title = title;
+            Tracks = tracks;
+            AddOption(new ButtonPromptOption
+            {
+                Emoji = new DiscordComponentEmoji("✔️"),
+                Row = 3,
+                Style = ButtonStyle.Success,
+                Run = async (args, client, option) =>
+                {
+                    if (SelectedItems.Count == 0)
+                    {
+                        await args.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("Please Select a Song!").AsEphemeral(true));
+                        return;
+                    }
+                    IsConfirmed = true;
+                    _ = ConfirmCallback.Invoke(new TrackList(SelectedItems));
+                }
+            });
+        }
+        public override Task<PaginatedSelectMenuOption<LavalinkTrack>> ConvertToOption(LavalinkTrack item)
+        {
+            return Task.FromResult<PaginatedSelectMenuOption<LavalinkTrack>>(new PaginatedSelectMenuOption<LavalinkTrack>
+            {
+                Label = item.Title,
+                Description = item.Author
+            });
+
+        }
+
+        public override Task OnSelect(LavalinkTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Added {item.Title}, {SelectedItems}");
+            return Task.CompletedTask;
+        }
+
+        public override Task OnUnselect(LavalinkTrack item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+            _logger.LogDebug($"Removed {item.Title}");
+            return Task.CompletedTask;
+
+        }
+
+        public async Task<List<LavalinkTrack>> AwaitSelectionAsync()
+        {
+            return await Task.Run(() =>
+            {
+                while (!IsConfirmed) 
+                {
+                    if (State == PromptState.INVALID)
+                        throw new InvalidOperationException("Prompt has been Invalidated");
+                }
+                IsConfirmed = false;
+                return SelectedItems;
+            });
+        }
+
+        protected override DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder)
+        {
+
+            builder.WithTitle(Title);
+            builder.WithDescription(Common.TrackListString(PageManager.GetPage(CurrentPage), 4000));
+            List<DiscordEmbed> embeds = new List<DiscordEmbed>();
+            embeds.Add(builder.Build());
+
+            if (Embeds != null)
+                embeds.AddRange(Embeds);
+
+            return new DiscordMessageBuilder().AddEmbeds(embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs b/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
new file mode 100644
index 0000000..de67a13
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Implementation/StringSelectorPrompt.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus;
+using System.Threading.Tasks;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using DSharpPlus.Entities;
+
+namespace TomatenMusic.Prompt.Implementation
+{
+    class StringSelectorPrompt : PaginatedSelectPrompt<string>
+    {
+        public StringSelectorPrompt(string title, List<string> strings, DiscordPromptBase lastPrompt = null) : base(title, strings, lastPrompt)
+        {
+        }
+        public async override Task<PaginatedSelectMenuOption<string>> ConvertToOption(string item)
+        {
+            return new PaginatedSelectMenuOption<string>
+            {
+                Label = item
+            };
+        }
+
+        public async override Task OnSelect(string item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+        }
+
+        public async override Task OnUnselect(string item, ComponentInteractionCreateEventArgs args, DiscordClient sender)
+        {
+
+        }
+
+        protected override DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder)
+        {
+            foreach (var item in PageManager.GetPage(CurrentPage))
+            {
+                builder.AddField(item, item);
+            }
+
+            return new DiscordMessageBuilder().WithEmbed(builder);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs b/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
new file mode 100644
index 0000000..771ec8a
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
@@ -0,0 +1,41 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TomatenMusic.Prompt.Model
+{
+    class ButtonPrompt : DiscordPromptBase
+    {
+        public string Content { get; protected set; } = "";
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+
+        public ButtonPrompt(DiscordPromptBase lastPrompt = null, string content = " ", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+            var myOption = (ButtonPromptOption)option;
+            DiscordComponent component;
+
+            if (myOption.Link != null)
+                component = new DiscordLinkButtonComponent(myOption.Link, myOption.Content, myOption.Disabled, myOption.Emoji);
+            else
+                component = new DiscordButtonComponent(myOption.Style, myOption.CustomID, myOption.Content, myOption.Disabled, myOption.Emoji);
+            return Task.FromResult<DiscordComponent>(component);
+        }
+
+        protected override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return Task.FromResult<DiscordMessageBuilder>(new DiscordMessageBuilder()
+            .WithContent(Content)
+            .AddEmbeds(Embeds));
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs b/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
new file mode 100644
index 0000000..4c5c8a9
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
@@ -0,0 +1,62 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+
+
+namespace TomatenMusic.Prompt.Model
+{
+    class CombinedPrompt : DiscordPromptBase
+    {
+        public string Content { get; protected set; } = "";
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+
+        public CombinedPrompt(DiscordPromptBase lastPrompt = null, string content = "Example Content", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            this.LastPrompt = lastPrompt;
+
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected async override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+            if (option is SelectMenuPromptOption)
+            {
+                SelectMenuPromptOption selectOption = (SelectMenuPromptOption)option;
+                List<DiscordSelectComponentOption> options = new List<DiscordSelectComponentOption>();
+                foreach (var item in selectOption.Options)
+                {
+                    options.Add(new DiscordSelectComponentOption(item.Label, item.CustomID, item.Description, item.Default, item.Emoji));
+                }
+
+                return new DiscordSelectComponent(selectOption.CustomID, selectOption.Content, options, selectOption.Disabled, selectOption.MinValues, selectOption.MaxValues);
+            }
+            else
+            {
+                var myOption = (ButtonPromptOption)option;
+                DiscordComponent component;
+
+                if (myOption.Link != null)
+                    component = new DiscordLinkButtonComponent(myOption.Link, myOption.Content, myOption.Disabled, myOption.Emoji);
+                else
+                    component = new DiscordButtonComponent(myOption.Style, myOption.CustomID, myOption.Content, myOption.Disabled, myOption.Emoji);
+                return component;
+            }
+
+
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder()
+            .WithContent(Content)
+            .AddEmbeds(Embeds);
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs b/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
new file mode 100644
index 0000000..a9db352
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
@@ -0,0 +1,499 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Util;
+using DSharpPlus.Exceptions;
+using Microsoft.Extensions.DependencyInjection;
+using DSharpPlus;
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class DiscordPromptBase
+    {
+        public static List<DiscordPromptBase> ActivePrompts { get; } = new List<DiscordPromptBase>();
+
+        public PromptState State { get; protected set; }
+        public DiscordMessage Message { get; private set; }
+        public DiscordInteraction Interaction { get; private set; }
+        public List<IPromptOption> Options { get; protected set; } = new List<IPromptOption>();
+        public DiscordClient _client { get; set; }
+        public DiscordPromptBase LastPrompt { get; protected set; }
+        public System.Timers.Timer TimeoutTimer { get; set; }
+
+        protected ILogger<DiscordPromptBase> _logger { get; set; }
+
+        protected EventId eventId = new EventId(16, "Prompts");
+
+        protected DiscordPromptBase(DiscordPromptBase lastPrompt)
+        {
+            LastPrompt = lastPrompt;
+            Options = new List<IPromptOption>();
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+
+            _logger = serviceProvider.GetRequiredService<ILogger<DiscordPromptBase>>();
+ 
+
+            if (lastPrompt != null)
+            {
+                Options.Add(new ButtonPromptOption
+                {
+                    Style = DSharpPlus.ButtonStyle.Danger,
+                    Row = 5,
+                    Emoji = new DiscordComponentEmoji("↩️"),
+                    Run = async (args, sender, option) =>
+                    {
+                        _ = BackAsync();
+                    }
+                });
+            }
+
+            Options.Add(new ButtonPromptOption
+            {
+                Style = DSharpPlus.ButtonStyle.Danger,
+                Row = 5,
+                Emoji = new DiscordComponentEmoji("❌"),
+                Run = async (args, sender, option) =>
+                {
+                    _ = InvalidateAsync();
+                }
+            });
+
+        }
+
+        public async Task InvalidateAsync(bool withEdit = true, bool destroyHistory = false)
+        {
+            foreach (var option in Options)
+                option.UpdateMethod = (prompt) =>
+                {
+                    prompt.Disabled = true;
+                    return Task.FromResult<IPromptOption>(prompt);
+                };
+
+            if (withEdit)
+                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
+            ActivePrompts.Remove(this);
+            if (destroyHistory)
+            {
+                if (LastPrompt != null)
+                    await LastPrompt.InvalidateAsync(false);
+                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
+            }
+
+            if (State == PromptState.INVALID)
+                return;
+            State = PromptState.INVALID;
+            
+
+            _client.ComponentInteractionCreated -= Discord_ComponentInteractionCreated;
+
+        }
+
+        public async Task SendAsync(DiscordChannel channel)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard( (ulong) channel.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+            AddGuids();
+            DiscordMessageBuilder builder = await GetMessageAsync();
+            builder = await AddComponentsAsync(builder);
+
+
+            Message = await builder.SendAsync(channel);
+            State = PromptState.OPEN;
+        }
+
+        public async Task SendAsync(DiscordInteraction interaction, bool ephemeral = false)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)interaction.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+
+            AddGuids();
+            DiscordFollowupMessageBuilder builder = await GetFollowupMessageAsync();
+            builder = await AddComponentsAsync(builder);
+            builder.AsEphemeral(ephemeral);
+
+            Interaction = interaction;
+            Message = await interaction.CreateFollowupMessageAsync(builder);
+            State = PromptState.OPEN;
+
+            long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+            if (TimeoutTimer != null)
+                TimeoutTimer.Close();
+
+            TimeoutTimer = new System.Timers.Timer(timeoutTime);
+            TimeoutTimer.Elapsed += OnTimeout;
+            TimeoutTimer.AutoReset = false;
+            TimeoutTimer.Start();
+        }
+
+        public async Task UseAsync(DiscordMessage message)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)message.Channel.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+            ActivePrompts.Add(this);
+
+            AddGuids();
+            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
+
+            await EditMessageAsync(builder);
+            State = PromptState.OPEN;
+
+        }
+
+        public async Task UseAsync(DiscordInteraction interaction, DiscordMessage message)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
+            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
+            _client = client.GetShard((ulong)interaction.GuildId);
+
+            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
+
+            ActivePrompts.Add(this);
+            AddGuids();
+            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
+            Interaction = interaction;
+            Message = message;
+            await EditMessageAsync(builder);
+            State = PromptState.OPEN;
+
+            long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+            if (TimeoutTimer != null)
+                TimeoutTimer.Close();
+
+            TimeoutTimer = new System.Timers.Timer(timeoutTime);
+            TimeoutTimer.Elapsed += OnTimeout;
+            TimeoutTimer.AutoReset = false;
+            TimeoutTimer.Start();
+            
+        }
+
+        private void OnTimeout(object? sender, System.Timers.ElapsedEventArgs e)
+        {
+            _ = InvalidateAsync();
+        }
+
+        private void AddGuids()
+        {
+            foreach (var item in Options)
+            {
+                item.CustomID = RandomUtil.GenerateGuid();
+                if (item is SelectMenuPromptOption)
+                {
+                    SelectMenuPromptOption menuItem = (SelectMenuPromptOption)item;
+                    foreach (var option in menuItem.Options)
+                    {
+                        option.CustomID = RandomUtil.GenerateGuid();
+                    }
+                }
+
+            }
+            //this.Options = options;
+        }
+
+        protected abstract Task<DiscordComponent> GetComponentAsync(IPromptOption option);
+
+        protected abstract Task<DiscordMessageBuilder> GetMessageAsync();
+
+        private async Task<DiscordFollowupMessageBuilder> GetFollowupMessageAsync()
+        {
+            DiscordMessageBuilder oldBuilder = await GetMessageAsync();
+
+            return new DiscordFollowupMessageBuilder()
+                .WithContent(oldBuilder.Content)
+                .AddEmbeds(oldBuilder.Embeds);
+                
+        }
+        private async Task<DiscordWebhookBuilder> GetWebhookMessageAsync()
+        {
+            DiscordMessageBuilder oldBuilder = await GetMessageAsync();
+
+            return new DiscordWebhookBuilder()
+                .WithContent(oldBuilder.Content)
+                .AddEmbeds(oldBuilder.Embeds);
+
+        }
+
+        public async Task UpdateAsync()
+        {
+           if (State == PromptState.INVALID)
+                return;
+           await EditMessageAsync(await GetWebhookMessageAsync());
+           
+        }
+
+        private async Task UpdateOptionsAsync()
+        {
+            List<IPromptOption> options = new List<IPromptOption>();
+            foreach (var option in this.Options)
+                options.Add(await option.UpdateMethod.Invoke(option));
+            this.Options = options;
+        }
+
+        protected async Task Discord_ComponentInteractionCreated(DSharpPlus.DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs e)
+        {
+            if (State == PromptState.INVALID)
+                return;
+
+            foreach (var option in Options)
+            {
+                if (option.CustomID == e.Id)
+                {
+
+                    await e.Interaction.CreateResponseAsync(DSharpPlus.InteractionResponseType.DeferredMessageUpdate);
+                    _ = option.Run.Invoke(e, sender, option);
+                }
+            }
+        }
+
+        public async Task EditMessageAsync(DiscordWebhookBuilder builder)
+        {
+            try
+            {
+                if (Interaction != null)
+                {
+                    await AddComponentsAsync(builder);
+                    try
+                    {
+                        Message = await Interaction.EditFollowupMessageAsync(Message.Id, builder);
+                    }catch (Exception e)
+                    {
+                        Message = await Interaction.EditOriginalResponseAsync(builder);
+                    }
+
+                }
+                else
+                {
+                    DiscordMessageBuilder msgbuilder = new DiscordMessageBuilder()
+                        .AddEmbeds(builder.Embeds)
+                        .WithContent(builder.Content);
+                    await AddComponentsAsync(msgbuilder);
+                    Message = await Message.ModifyAsync(msgbuilder);
+                }
+            }catch (BadRequestException e)
+            {
+                _logger.LogError(e.Errors);
+            }
+
+        }
+
+        protected async Task<DiscordMessageBuilder> AddComponentsAsync(DiscordMessageBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        protected async Task<DiscordFollowupMessageBuilder> AddComponentsAsync(DiscordFollowupMessageBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        protected async Task<DiscordWebhookBuilder> AddComponentsAsync(DiscordWebhookBuilder builder)
+        {
+            await UpdateOptionsAsync();
+            builder.ClearComponents();
+
+            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
+            List<DiscordComponent> row5 = new List<DiscordComponent>(5);
+
+            foreach (var option in Options)
+            {
+                switch (option.Row)
+                {
+                    case 1:
+                        row1.Add(await GetComponentAsync(option));
+                        break;
+                    case 2:
+                        row2.Add(await GetComponentAsync(option));
+                        break;
+                    case 3:
+                        row3.Add(await GetComponentAsync(option));
+                        break;
+                    case 4:
+                        row4.Add(await GetComponentAsync(option));
+                        break;
+                    case 5:
+                        row5.Add(await GetComponentAsync(option));
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
+                }
+            }
+            if (row1.Count != 0)
+            {
+                builder.AddComponents(row1);
+            }
+            if (row2.Count != 0)
+            {
+                builder.AddComponents(row2);
+            }
+            if (row3.Count != 0)
+            {
+                builder.AddComponents(row3);
+            }
+            if (row4.Count != 0)
+            {
+                builder.AddComponents(row4);
+            }
+            if (row5.Count != 0)
+            {
+                builder.AddComponents(row5);
+            }
+            return builder;
+        }
+
+        public async Task BackAsync()
+        {
+
+            if (LastPrompt == null)
+                return;
+            _client.ComponentInteractionCreated -= LastPrompt.Discord_ComponentInteractionCreated;
+
+            await InvalidateAsync(false);
+            if (Interaction == null)
+                await LastPrompt.UseAsync(Message);
+            else
+                await LastPrompt.UseAsync(Interaction, Message);
+        }
+
+        public void AddOption(IPromptOption option)
+        {
+            Options.Add(option);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs b/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
new file mode 100644
index 0000000..9de7069
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
@@ -0,0 +1,138 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Option;
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class PaginatedButtonPrompt<T> : ButtonPrompt
+    {
+        protected PageManager<T> PageManager { get; set; }
+        protected int CurrentPage { get; set; } = 1;
+        public string Title { get; set; }
+
+        public PaginatedButtonPrompt(string title, List<T> items, DiscordPromptBase lastPrompt = null) : base(lastPrompt)
+        {
+            PageManager = new PageManager<T>(items, 9);
+            Title = title;
+
+            for (int i = 0; i < 9; i++)
+            {
+                int currentNumber = i + 1;
+                
+                ButtonPromptOption option = new ButtonPromptOption()
+                {
+                    Style = DSharpPlus.ButtonStyle.Primary,
+                    Row = i < 5 ? 1 : 2,
+                    UpdateMethod = async (option) =>
+                    {
+                        option.Disabled = PageManager.GetPage(CurrentPage).Count < currentNumber;
+                        return option;
+                    },
+                    Run = async (args, sender, prompt) =>
+                    {
+                        List<T> items = PageManager.GetPage(CurrentPage);
+                        await OnSelect(items[currentNumber-1], args, sender);
+                    }
+                };
+
+                switch (i)
+                {
+                    case 0:
+                        option.Emoji = new DiscordComponentEmoji("1️⃣");
+                        break;
+                    case 1:
+                        option.Emoji = new DiscordComponentEmoji("2️⃣");
+                        break;
+                    case 2:
+                        option.Emoji = new DiscordComponentEmoji("3️⃣");
+                        break;
+                    case 3:
+                        option.Emoji = new DiscordComponentEmoji("4️⃣");
+                        break;
+                    case 4:
+                        option.Emoji = new DiscordComponentEmoji("5️⃣");
+                        break;
+                    case 5:
+                        option.Emoji = new DiscordComponentEmoji("6️⃣");
+                        break;
+                    case 6:
+                        option.Emoji = new DiscordComponentEmoji("7️⃣");
+                        break;
+                    case 7:
+                        option.Emoji = new DiscordComponentEmoji("8️⃣");
+                        break;
+                    case 8:
+                        option.Emoji = new DiscordComponentEmoji("9️⃣");
+                        break;
+                }
+
+                AddOption(option);
+            }
+
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji= new DiscordComponentEmoji("⬅️"),
+                Row = 3,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = CurrentPage - 1 == 0;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage--;
+                    await UpdateAsync();
+
+                }
+            });
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("➡️"),
+                Row = 3,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = PageManager.GetTotalPages() == CurrentPage;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage++;
+                    await UpdateAsync();
+                }
+            });
+
+        }
+
+        public abstract Task OnSelect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        protected int GetTotalPages()
+        {
+            return PageManager.GetTotalPages();
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle(Title)
+                .WithFooter($"Page {CurrentPage} of {GetTotalPages()}")
+                .WithDescription("Select your desired Tracks");
+
+            return PopulateMessage(builder);
+
+        }
+
+        protected abstract DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder);
+
+
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs b/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
new file mode 100644
index 0000000..421c52d
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
@@ -0,0 +1,160 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Util;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+
+namespace TomatenMusic.Prompt.Model
+{
+    abstract class PaginatedSelectPrompt<T> : CombinedPrompt
+    {
+        protected PageManager<T> PageManager { get; set; }
+        protected int CurrentPage { get; set; } = 1;
+
+        public string Title { get; set; }
+        public List<T> SelectedItems { get; set; } = new List<T>();
+
+
+        public PaginatedSelectPrompt(string title, List<T> items, DiscordPromptBase lastPrompt = null, List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+            Embeds = embeds;
+            PageManager = new PageManager<T>(items, 10);
+            Title = title;
+            AddOption(new SelectMenuPromptOption
+            {
+                Row = 1,
+                MinValues = 1,
+                MaxValues = PageManager.GetPage(CurrentPage).Count,
+                Content = "Select a Value",
+                UpdateMethod = async (option) =>
+                {
+                    SelectMenuPromptOption _option = (SelectMenuPromptOption)option;
+                    
+                    _option.MaxValues = PageManager.GetPage(CurrentPage).Count;
+                    _option.Options.Clear();
+                    foreach (var item in PageManager.GetPage(CurrentPage))
+                    {
+                        _option.Options.Add(await GetOption(item));
+                    }
+                    foreach (var item in _option.Options)
+                    {
+                        foreach (var sOption in SelectedItems)
+                        {
+                            PaginatedSelectMenuOption<T> _item = (PaginatedSelectMenuOption<T>)item;
+                            if (_item.Item.Equals(sOption))
+                            {
+                                _option.CurrentValues.Add(_item.CustomID);
+                            }
+                        }
+                        
+                    }
+
+                    return _option;
+                }
+            });
+
+
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("⬅️"),
+                Row = 2,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = CurrentPage - 1 == 0;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage--;
+                    await UpdateAsync();
+
+                }
+            });
+            AddOption(new ButtonPromptOption
+            {
+                Style = ButtonStyle.Secondary,
+                Emoji = new DiscordComponentEmoji("➡️"),
+                Row = 2,
+                UpdateMethod = async (prompt) =>
+                {
+                    prompt.Disabled = PageManager.GetTotalPages() == CurrentPage;
+                    return prompt;
+                },
+                Run = async (args, sender, prompt) =>
+                {
+                    CurrentPage++;
+                    await UpdateAsync();
+                }
+            });
+
+        }
+
+        private async Task<PaginatedSelectMenuOption<T>> GetOption(T item)
+        {
+            var option = await ConvertToOption(item);
+            option.Item = item;
+            option.CustomID = RandomUtil.GenerateGuid();
+            option.Default = SelectedItems.Contains(item);
+            option.OnSelected = async (args, sender, option) =>
+            {
+                PaginatedSelectMenuOption<T> _option = (PaginatedSelectMenuOption<T>)option;
+                if (!SelectedItems.Contains(_option.Item))
+                    SelectedItems.Add(_option.Item);
+                await OnSelect(_option.Item, args, sender);
+                
+            };
+            option.OnUnselected = async (args, sender, option) =>
+            {
+                PaginatedSelectMenuOption<T> _option = (PaginatedSelectMenuOption<T>)option;
+                SelectedItems.Remove(_option.Item);
+                await OnUnselect(_option.Item, args, sender);
+            };
+
+
+            return option;
+        }
+        public abstract Task<PaginatedSelectMenuOption<T>> ConvertToOption(T item);
+
+        public abstract Task OnSelect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        public abstract Task OnUnselect(T item, ComponentInteractionCreateEventArgs args, DiscordClient sender);
+
+        protected int GetTotalPages()
+        {
+            return PageManager.GetTotalPages();
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            DiscordEmbedBuilder builder;
+            if (Embeds != null)
+            {
+                builder = new DiscordEmbedBuilder(Embeds[0]);
+            }else
+            {
+                builder = new DiscordEmbedBuilder();
+            }
+
+            builder
+                .WithTitle(Title)
+                .WithFooter($"Page {CurrentPage} of {GetTotalPages()}");
+
+            return PopulateMessage(builder);
+
+        }
+
+        protected abstract DiscordMessageBuilder PopulateMessage(DiscordEmbedBuilder builder);
+
+        public class PaginatedSelectMenuOption<I> : SelectMenuOption
+        {
+            public I Item { get; set; }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/PromptState.cs b/TomatenMusicCore/Prompt/Model/PromptState.cs
new file mode 100644
index 0000000..1b1d0e8
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/PromptState.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TomatenMusic.Prompt.Model
+{
+    enum PromptState
+    {
+
+        PREPARED,
+        OPEN,
+        INVALID,
+        RESPONDED
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Model/SelectPrompt.cs b/TomatenMusicCore/Prompt/Model/SelectPrompt.cs
new file mode 100644
index 0000000..3253f1b
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Model/SelectPrompt.cs
@@ -0,0 +1,48 @@
+using DSharpPlus.Entities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using System.Linq;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Util;
+
+
+namespace TomatenMusic.Prompt.Model
+{
+    class SelectPrompt : DiscordPromptBase
+    {
+        public List<DiscordEmbed> Embeds { get; protected set; } = new List<DiscordEmbed>();
+        public string Content { get; protected set; } = "";
+        public SelectPrompt(DiscordPromptBase lastPrompt = null, string content = " Example", List<DiscordEmbed> embeds = null) : base(lastPrompt)
+        {
+
+            this.Content = content;
+            this.Embeds = embeds == null ? new List<DiscordEmbed>() : embeds;
+        }
+
+        protected async override Task<DiscordComponent> GetComponentAsync(IPromptOption option)
+        {
+
+            SelectMenuPromptOption selectOption = (SelectMenuPromptOption)option;
+            List<DiscordSelectComponentOption> options = new List<DiscordSelectComponentOption>();
+            foreach ( var item in selectOption.Options)
+            {
+                options.Add(new DiscordSelectComponentOption(item.Label, item.CustomID, item.Description, item.Default, item.Emoji));
+            }
+
+                return new DiscordSelectComponent(selectOption.CustomID, selectOption.Content, options, selectOption.Disabled, selectOption.MinValues, selectOption.MaxValues);
+        }
+
+        protected async override Task<DiscordMessageBuilder> GetMessageAsync()
+        {
+            return new DiscordMessageBuilder()
+                .WithContent(Content)
+                .AddEmbeds(Embeds);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs b/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
new file mode 100644
index 0000000..4d6a6b1
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Prompt.Model;
+
+
+namespace TomatenMusic.Prompt.Option
+{
+    class ButtonPromptOption : IPromptOption
+    {
+
+        public ButtonStyle Style { get; set; } = ButtonStyle.Primary;
+        public string Content { get; set; } = " ";
+        public DiscordComponentEmoji Emoji { get; set; }
+        public bool Disabled { get; set; } = false;
+        public string CustomID { get; set; }
+        public string Link { get; set; }
+        public int Row { get; set; }
+        public Func<Option.IPromptOption, Task<Option.IPromptOption>> UpdateMethod { get; set; } = async prompt =>
+        {
+            return prompt;
+        };
+        public Func<DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; } = async (args, sender, prompt) =>
+        {
+
+        };
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/IPromptOption.cs b/TomatenMusicCore/Prompt/Option/IPromptOption.cs
new file mode 100644
index 0000000..26e85d1
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/IPromptOption.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Option;
+using TomatenMusic.Prompt.Model;
+
+
+namespace TomatenMusic.Prompt.Option
+{
+    interface IPromptOption
+    {
+        public string Content { get; set; }
+        public string CustomID { get; set; }
+        public int Row { get; set; }
+        public bool Disabled { get; set; }
+        public Func<IPromptOption, Task<IPromptOption>> UpdateMethod { get; set; }
+        public Func<DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs b/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
new file mode 100644
index 0000000..b3e49c5
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
@@ -0,0 +1,23 @@
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Prompt.Model;
+using DSharpPlus.Entities;
+
+namespace TomatenMusic.Prompt.Option
+{
+    class SelectMenuOption
+    {
+        public string Label { get; set; }
+        public string CustomID { get; set; }
+        public string Description { get; set; }
+        public bool Default { get; set; }
+        public DiscordComponentEmoji Emoji { get; set; }
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, SelectMenuOption, Task> OnSelected { get; set; }
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, SelectMenuOption, Task> OnUnselected { get; set; }
+
+    }
+}
diff --git a/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs b/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
new file mode 100644
index 0000000..01b717d
--- /dev/null
+++ b/TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
@@ -0,0 +1,50 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.EventArgs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Prompt.Model;
+using System.Linq;
+
+
+namespace TomatenMusic.Prompt.Option
+{
+    class SelectMenuPromptOption : IPromptOption
+    {
+        public string Content { get; set; } = " ";
+        public string CustomID { get; set; }
+        public int Row { get; set; } = 1;
+        public bool Disabled { get; set; } = false;
+        public List<SelectMenuOption> Options { get; set; } = new List<SelectMenuOption>();
+        public int MinValues { get; set; } = 1;
+        public int MaxValues { get; set; } = 1;
+        public List<string> CurrentValues { get; set; } = new List<string>();
+
+
+        public Func<IPromptOption, Task<IPromptOption>> UpdateMethod { get; set; } = async (prompt) =>
+        {
+            return prompt;
+        };
+        public Func<ComponentInteractionCreateEventArgs, DiscordClient, IPromptOption, Task> Run { get; set; } = async (args, sender, option) =>
+        {
+            SelectMenuPromptOption _option = (SelectMenuPromptOption)option;
+            foreach (var item in _option.Options)
+            {
+                if (_option.CurrentValues.Contains(item.CustomID) && !args.Values.Contains(item.CustomID))
+                {
+                    await item.OnUnselected.Invoke(args, sender, item);
+                }
+                if (!_option.CurrentValues.Contains(item.CustomID) && args.Values.Contains(item.CustomID))
+                {
+                    await item.OnSelected.Invoke(args, sender, item);
+                }
+            }
+            _option.CurrentValues = new List<string>(args.Values);
+
+        };
+
+    }
+}
diff --git a/TomatenMusicCore/Services/SpotifyService.cs b/TomatenMusicCore/Services/SpotifyService.cs
new file mode 100644
index 0000000..bf9814e
--- /dev/null
+++ b/TomatenMusicCore/Services/SpotifyService.cs
@@ -0,0 +1,196 @@
+using SpotifyAPI.Web;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using DSharpPlus;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Services;
+using TomatenMusic.Music;
+using Lavalink4NET;
+using Lavalink4NET.Player;
+using System.Runtime.Caching;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Services
+{
+
+    public interface ISpotifyService
+    {
+        public Task<MusicActionResponse> ConvertURL(string url);
+        public Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist, FullPlaylist spotifyPlaylist = null);
+        public Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist, FullAlbum spotifyAlbum = null);
+        public Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track, FullTrack spotifyFullTrack = null);
+
+    }
+
+    public class SpotifyService : SpotifyClient, ISpotifyService
+    {
+        public ILogger _logger { get; set; }
+        public IAudioService _audioService { get; set; }
+
+        public ObjectCache Cache { get; set; }
+
+        public SpotifyService(SpotifyClientConfig config, ILogger<SpotifyService> logger, IAudioService audioService) : base(config)
+        {
+            _logger = logger;
+            _audioService = audioService;
+            Cache = MemoryCache.Default;
+        }
+
+        public async Task<MusicActionResponse> ConvertURL(string url)
+        {
+            string trackId = url
+                .Replace("https://open.spotify.com/track/", "")
+                .Replace("https://open.spotify.com/album/", "")
+                .Replace("https://open.spotify.com/playlist/", "")
+                .Substring(0, 22);
+
+            _logger.LogDebug($"Starting spotify conversion for: {url}");
+
+            if (url.StartsWith("https://open.spotify.com/track"))
+            {
+                FullTrack sTrack = Cache.Contains(trackId) ? Cache.Get(trackId) as FullTrack : await Tracks.Get(trackId);
+
+                _logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists)}");
+
+                var track = new TomatenMusicTrack(
+                    await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}"
+                    , Lavalink4NET.Rest.SearchMode.YouTube));
+
+                if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+
+                Cache.Add(trackId, sTrack, DateTimeOffset.MaxValue);
+
+                return new MusicActionResponse(await FullTrackContext.PopulateAsync(track, sTrack));
+
+            }
+            else if (url.StartsWith("https://open.spotify.com/album"))
+            {
+                TrackList tracks = new TrackList();
+
+                FullAlbum album = Cache.Contains(trackId) ? Cache.Get(trackId) as FullAlbum : await Albums.Get(trackId);
+
+                foreach (var sTrack in await PaginateAll(album.Tracks))
+                {
+                    _logger.LogInformation($"Searching youtube from spotify with query: {sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}");
+                    var track = new TomatenMusicTrack(
+                        await _audioService.GetTrackAsync($"{sTrack.Name} {String.Join(" ", sTrack.Artists.ConvertAll(artist => artist.Name))}"
+                        , Lavalink4NET.Rest.SearchMode.YouTube));
+
+                    if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+                    
+                    tracks.Add(await FullTrackContext.PopulateAsync(track, spotifyId: sTrack.Uri.Replace("spotify:track:", "")));
+                }
+                Uri uri;
+                Uri.TryCreate(url, UriKind.Absolute, out uri);
+
+                SpotifyPlaylist playlist = new SpotifyPlaylist(album.Name, album.Id, tracks, uri);
+                await PopulateSpotifyAlbumAsync(playlist, album);
+
+                Cache.Add(trackId, album, DateTimeOffset.MaxValue);
+
+                return new MusicActionResponse(playlist: playlist);
+
+            }
+            else if (url.StartsWith("https://open.spotify.com/playlist"))
+            {
+
+                TrackList tracks = new TrackList();
+
+                FullPlaylist spotifyPlaylist = Cache.Contains(trackId) ? Cache.Get(trackId) as FullPlaylist : await Playlists.Get(trackId);
+                
+                foreach (var sTrack in await PaginateAll(spotifyPlaylist.Tracks))
+                {
+                    if (sTrack.Track is FullTrack)
+                    {
+                        FullTrack fullTrack = (FullTrack)sTrack.Track;
+                        _logger.LogInformation($"Searching youtube from spotify with query: {fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}");
+
+                        var track = new TomatenMusicTrack(
+                            await _audioService.GetTrackAsync($"{fullTrack.Name} {String.Join(" ", fullTrack.Artists.ConvertAll(artist => artist.Name))}"
+                            , Lavalink4NET.Rest.SearchMode.YouTube));
+
+                        if (track == null) throw new ArgumentException("This Spotify Track was not found on Youtube");
+
+                        tracks.Add(await FullTrackContext.PopulateAsync(track, fullTrack));
+                    }
+
+                }
+                Uri uri;
+                Uri.TryCreate(url, UriKind.Absolute, out uri);
+                SpotifyPlaylist playlist = new SpotifyPlaylist(spotifyPlaylist.Name, spotifyPlaylist.Id, tracks, uri);
+                await PopulateSpotifyPlaylistAsync(playlist, spotifyPlaylist);
+
+                Cache.Add(trackId, spotifyPlaylist, DateTimeOffset.MaxValue);
+
+                return new MusicActionResponse(playlist: playlist);
+            }
+            return null;
+        }
+   
+        public async Task<SpotifyPlaylist> PopulateSpotifyPlaylistAsync(SpotifyPlaylist playlist, FullPlaylist spotifyPlaylist = null)
+        {
+            FullPlaylist list = spotifyPlaylist;
+            if (list == null) 
+                list = await this.Playlists.Get(playlist.Identifier);
+
+            string desc = list.Description;
+
+            playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
+            if (playlist.Description.Length < 2)
+                playlist.Description = "None";
+
+            playlist.AuthorUri = new Uri($"https://open.spotify.com/user/{list.Owner.Id}");
+            playlist.AuthorName = list.Owner.DisplayName;
+            playlist.Followers = list.Followers.Total;
+            playlist.Url = new Uri($"https://open.spotify.com/playlist/{playlist.Identifier}");
+            try
+            {
+                playlist.AuthorThumbnail = new Uri(list.Owner.Images.First().Url);
+            }
+            catch (Exception ex) { }
+
+            return playlist;
+        }
+
+        public async Task<SpotifyPlaylist> PopulateSpotifyAlbumAsync(SpotifyPlaylist playlist, FullAlbum spotifyAlbum = null)
+        {
+            FullAlbum list = spotifyAlbum;
+            if (list == null)
+                list = await this.Albums.Get(playlist.Identifier);
+
+            string desc = list.Label;
+
+            playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
+            playlist.AuthorUri = new Uri($"https://open.spotify.com/user/{list.Artists.First().Uri}");
+            playlist.AuthorName = list.Artists.First().Name;
+            playlist.Followers = list.Popularity;
+            playlist.Url = new Uri($"https://open.spotify.com/album/{playlist.Identifier}");
+
+            return playlist;
+        }
+
+        public async Task<LavalinkTrack> PopulateTrackAsync(LavalinkTrack track, FullTrack spotifyFullTrack)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+            if (context.SpotifyIdentifier == null)
+                return track;
+
+            FullTrack spotifyTrack = spotifyFullTrack;
+            if (spotifyTrack == null)
+                spotifyTrack = await Tracks.Get(context.SpotifyIdentifier);
+
+            context.SpotifyAlbum = spotifyTrack.Album;
+            context.SpotifyArtists = spotifyTrack.Artists;
+            context.SpotifyPopularity = spotifyTrack.Popularity;
+            context.SpotifyUri = new Uri($"https://open.spotify.com/track/{context.SpotifyIdentifier}");
+            track.Context = context;
+
+            return track;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Services/TrackProvider.cs b/TomatenMusicCore/Services/TrackProvider.cs
new file mode 100644
index 0000000..cdba996
--- /dev/null
+++ b/TomatenMusicCore/Services/TrackProvider.cs
@@ -0,0 +1,135 @@
+using Lavalink4NET;
+using Lavalink4NET.Rest;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Services;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Music
+{
+    public class TrackProvider
+    {
+        public ISpotifyService _spotifyService { get; set; }
+        public IAudioService _audioService { get; set; }
+        public YoutubeService _youtubeService { get; set; }
+
+        public TrackProvider(ISpotifyService spotify, IAudioService audioService, YoutubeService youtubeService)
+        {
+            _audioService = audioService;
+            _spotifyService = spotify;
+            _youtubeService = youtubeService;
+        }
+
+        public async Task<MusicActionResponse> SearchAsync(string query, bool withSearchResults = false)
+        {
+
+            Uri uri;
+            TrackLoadResponsePayload loadResult;
+            bool isSearch = true;
+
+            if (query.StartsWith("https://open.spotify.com"))
+            {
+                return await _spotifyService.ConvertURL(query);
+            }
+
+            if (Uri.TryCreate(query, UriKind.Absolute, out uri))
+            {
+                loadResult = await _audioService.LoadTracksAsync(uri.ToString());
+                isSearch = false;
+            }
+            else
+                loadResult = await _audioService.LoadTracksAsync(query, SearchMode.YouTube);
+
+            if (uri != null && uri.AbsolutePath.Contains("."))
+                return await SearchAsync(uri);
+
+            if (loadResult.LoadType == TrackLoadType.LoadFailed) throw new ArgumentException("Track loading failed");
+
+            if (loadResult.LoadType == TrackLoadType.NoMatches) throw new FileNotFoundException("Query resulted in no Matches");
+
+            if (ParseTimestamp(query) != null)
+                loadResult.Tracks[0] = loadResult.Tracks[0].WithPosition((TimeSpan)ParseTimestamp(query));
+
+            if (withSearchResults && loadResult.LoadType == TrackLoadType.SearchResult)
+            {
+                return new MusicActionResponse(tracks: await FullTrackContext.PopulateTracksAsync(new TrackList(loadResult.Tracks)));
+            }
+
+            if (loadResult.LoadType == TrackLoadType.PlaylistLoaded && !isSearch)
+                return new MusicActionResponse(
+                    playlist: await _youtubeService.PopulatePlaylistAsync(
+                        new YoutubePlaylist(loadResult.PlaylistInfo.Name, await FullTrackContext.PopulateTracksAsync(new TrackList(loadResult.Tracks)), ParseListId(query))));
+            else
+                return new MusicActionResponse(await FullTrackContext.PopulateAsync(new TomatenMusicTrack(loadResult.Tracks.First())));
+
+        }
+
+        public async Task<MusicActionResponse> SearchAsync(Uri fileUri)
+        {
+
+            var loadResult = new TomatenMusicTrack(await _audioService.GetTrackAsync(fileUri.ToString()));
+            loadResult.Context = new FullTrackContext
+            {
+                IsFile = true
+            };
+
+            if (loadResult == null)
+                throw new FileNotFoundException("The file was not found");
+
+            return new MusicActionResponse(loadResult);
+
+        }
+
+        public string ParseListId(string url)
+        {
+            var uri = new Uri(url, UriKind.Absolute);
+
+            var query = HttpUtility.ParseQueryString(uri.Query);
+
+            var videoId = string.Empty;
+
+            if (query.AllKeys.Contains("list"))
+            {
+                videoId = query["list"];
+            }
+            else
+            {
+                videoId = uri.Segments.Last();
+            }
+
+            return videoId;
+        }
+
+        public TimeSpan? ParseTimestamp(string url)
+        {
+            Uri uri;
+            try
+            {
+                uri = new Uri(url, UriKind.Absolute);
+            }catch (UriFormatException)
+            {
+                return null;
+            }
+
+            var query = HttpUtility.ParseQueryString(uri.Query);
+
+            int seconds;
+
+            if (query.AllKeys.Contains("t"))
+            {
+                seconds = int.Parse(query["t"]);
+            }
+            else
+                return null;
+
+            return TimeSpan.FromSeconds(seconds);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/Services/YoutubeService.cs b/TomatenMusicCore/Services/YoutubeService.cs
new file mode 100644
index 0000000..8f8c78b
--- /dev/null
+++ b/TomatenMusicCore/Services/YoutubeService.cs
@@ -0,0 +1,150 @@
+using Google.Apis.Services;
+using Google.Apis.YouTube.v3;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TomatenMusic.Music.Entitites;
+using System.Linq;
+using Google.Apis.YouTube.v3.Data;
+using Microsoft.Extensions.Logging;
+using static TomatenMusic.TomatenMusicBot;
+using Lavalink4NET.Player;
+using Microsoft.Extensions.DependencyInjection;
+using Lavalink4NET;
+using TomatenMusicCore.Music;
+using TomatenMusicCore.Music.Entities;
+
+namespace TomatenMusic.Services
+{
+    public class YoutubeService
+    {
+        public YouTubeService Service { get; }
+        public ILogger<YoutubeService> _logger { get; set; }
+        public YoutubeService(ILogger<YoutubeService> logger, ConfigJson config)
+        {
+            Service = new YouTubeService(new BaseClientService.Initializer()
+            {
+                ApiKey = config.YoutubeAPIKey,
+                ApplicationName = "TomatenMusic"
+            });
+            _logger = logger;
+        }
+
+        public async Task<LavalinkTrack> PopulateTrackInfoAsync(LavalinkTrack track)
+        {
+            var video = await GetVideoAsync(track.TrackIdentifier);
+            var channel = await GetChannelAsync(video.Snippet.ChannelId);
+            FullTrackContext context = track.Context == null ? new FullTrackContext() : (FullTrackContext)track.Context;
+
+            if (channel.Statistics.SubscriberCount != null)
+                context.YoutubeAuthorSubs = (ulong) channel.Statistics.SubscriberCount;
+            context.YoutubeAuthorThumbnail = new Uri(channel.Snippet.Thumbnails.Default__.Url);
+            context.YoutubeAuthorUri = new Uri($"https://www.youtube.com/channel/{channel.Id}");
+            string desc = video.Snippet.Description;
+
+            context.YoutubeDescription = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
+            if (video.Statistics.LikeCount != null)
+                context.YoutubeLikes = (ulong) video.Statistics.LikeCount;
+            context.YoutubeTags = video.Snippet.Tags;
+
+            try
+            {
+                context.YoutubeThumbnail = new Uri(video.Snippet.Thumbnails.High.Url);
+            }catch (Exception ex) { }
+
+            context.YoutubeUploadDate = (DateTime)video.Snippet.PublishedAt;
+            context.YoutubeViews = (ulong)video.Statistics.ViewCount;
+            context.YoutubeCommentCount = video.Statistics.CommentCount;
+            track.Context = context;
+            return track;
+        }
+
+        public async Task<List<LavalinkTrack>> PopulateTrackListAsync(IEnumerable<LavalinkTrack> tracks)
+        {
+            List<LavalinkTrack> newTracks = new List<LavalinkTrack>();
+            foreach (var track in tracks)
+                newTracks.Add(await PopulateTrackInfoAsync(track));
+
+            return newTracks;
+        }
+        public async Task<ILavalinkPlaylist> PopulatePlaylistAsync(YoutubePlaylist playlist)
+        {
+            var list = await GetPlaylistAsync(playlist.Identifier);
+            var channel = await GetChannelAsync(list.Snippet.ChannelId);
+
+            string desc = list.Snippet.Description;
+
+            playlist.Description = desc.Substring(0, Math.Min(desc.Length, 1024)) + (desc.Length > 1020 ? "..." : " ");
+            if (playlist.Description.Length < 2)
+                playlist.Description = "None";
+
+            try
+            {
+                playlist.Thumbnail = new Uri(list.Snippet.Thumbnails.Maxres.Url);
+            }catch (Exception ex) { }
+            playlist.AuthorName = channel.Snippet.Title;
+            playlist.CreationTime = (DateTime)list.Snippet.PublishedAt;
+            playlist.YoutubeItem = list;
+            playlist.AuthorThumbnail = new Uri(channel.Snippet.Thumbnails.Default__.Url);
+            playlist.AuthorUri = new Uri($"https://www.youtube.com/channels/{channel.Id}");
+
+            return playlist;
+        }
+
+        public async Task<Video> GetVideoAsync(string id)
+        {
+            var search = Service.Videos.List("contentDetails,id,liveStreamingDetails,localizations,player,recordingDetails,snippet,statistics,status,topicDetails");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+            return response.Items.First();
+        }
+
+        public async Task<Channel> GetChannelAsync(string id)
+        {
+            var search = Service.Channels.List("brandingSettings,contentDetails,contentOwnerDetails,id,localizations,snippet,statistics,status,topicDetails");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+
+            return response.Items.First();
+        }
+        public async Task<Playlist> GetPlaylistAsync(string id)
+        {
+            var search = Service.Playlists.List("snippet,contentDetails,status");
+            search.Id = id;
+            var response = await search.ExecuteAsync();
+
+            return response.Items.First();
+        }
+
+        public async Task<IEnumerable<SearchResult>> GetRelatedVideosAsync(string id)
+        {
+            var search = Service.Search.List("snippet");
+            search.RelatedToVideoId = id;
+            search.Type = "video";
+            var response = await search.ExecuteAsync();
+            return response.Items.Where(x => x.Snippet != null);
+        }
+
+        public async Task<TomatenMusicTrack> GetRelatedTrackAsync(string id, List<string> excludeIds)
+        {
+            var audioService = TomatenMusicBot.ServiceProvider.GetRequiredService<IAudioService>();
+
+            var videos = await GetRelatedVideosAsync(id);
+            SearchResult video = null;
+            foreach (var vid in videos)
+                video = videos.First(x => !excludeIds.Contains(x.Id.VideoId));
+            
+            if (video == null)
+                video = videos.FirstOrDefault();
+
+            var loadResult = new TomatenMusicTrack(await audioService.GetTrackAsync($"https://youtu.be/{video.Id.VideoId}"));
+
+            if (loadResult == null)
+                throw new Exception("An Error occurred while processing the Request");
+
+            return await FullTrackContext.PopulateAsync(loadResult);
+        }
+
+    }
+}
diff --git a/TomatenMusicCore/TomatenMusicBot.cs b/TomatenMusicCore/TomatenMusicBot.cs
new file mode 100644
index 0000000..32ac443
--- /dev/null
+++ b/TomatenMusicCore/TomatenMusicBot.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using DSharpPlus;
+using DSharpPlus.EventArgs;
+using DSharpPlus.Entities;
+using System.Linq;
+using DSharpPlus.SlashCommands;
+using DSharpPlus.SlashCommands.EventArgs;
+using TomatenMusic.Commands;
+using Newtonsoft.Json;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using TomatenMusic.Music;
+using SpotifyAPI.Web.Auth;
+using SpotifyAPI.Web;
+using DSharpPlus.Exceptions;
+using Lavalink4NET;
+using Lavalink4NET.DSharpPlus;
+using Lavalink4NET.MemoryCache;
+using TomatenMusic.Services;
+using Lavalink4NET.Tracking;
+using Lavalink4NET.Lyrics;
+using Microsoft.Extensions.Hosting;
+using Lavalink4NET.Logging;
+using Lavalink4NET.Logging.Microsoft;
+using TomatenMusic.Music.Entitites;
+
+namespace TomatenMusic
+{
+    public class TomatenMusicBot
+    {
+
+        public class ConfigJson
+        {
+            [JsonProperty("TOKEN")]
+            public string Token { get; private set; }
+            [JsonProperty("LavaLinkPassword")]
+            public string LavaLinkPassword { get; private set; }
+            [JsonProperty("SpotifyClientId")]
+            public string SpotifyClientId { get; private set; }
+            [JsonProperty("SpotifyClientSecret")]
+            public string SpotifyClientSecret { get; private set; }
+            [JsonProperty("YoutubeApiKey")]
+            public string YoutubeAPIKey { get; private set; }
+
+        }
+
+        public static IServiceProvider ServiceProvider { get; private set; }
+
+        public IHost _host { get; set; }
+
+        private async Task<IServiceProvider> BuildServiceProvider()
+        {
+            var json = "";
+            using (var fs = File.OpenRead("config.json"))
+            using (var sr = new StreamReader(fs, new UTF8Encoding(false)))
+                json = await sr.ReadToEndAsync();
+            ConfigJson config = JsonConvert.DeserializeObject<ConfigJson>(json);
+
+
+
+            _host = Host.CreateDefaultBuilder()
+                .ConfigureServices((_, services) =>
+                services
+                    .AddSingleton(config)
+                    .AddMicrosoftExtensionsLavalinkLogging()
+                    .AddSingleton<TrackProvider>()
+                    .AddSingleton<DiscordShardedClient>()
+                    .AddSingleton( s => new DiscordConfiguration
+                    {
+                        Token = config.Token,
+                        Intents = DiscordIntents.All,
+                        LoggerFactory = s.GetRequiredService<ILoggerFactory>()
+
+                    })
+
+                    .AddSingleton<IDiscordClientWrapper, DiscordShardedClientWrapper>()
+                    .AddSingleton<IAudioService, LavalinkNode>()
+                    .AddSingleton(new InactivityTrackingOptions
+                    {
+                        TrackInactivity = true
+                    })
+                    .AddSingleton<InactivityTrackingService>()
+
+                    .AddSingleton(
+                          new LavalinkNodeOptions
+                          {
+                              RestUri = "http://116.202.92.16:2333",
+                              Password = config.LavaLinkPassword,
+                              WebSocketUri = "ws://116.202.92.16:2333",
+                              AllowResuming = true
+
+                          })
+
+                    .AddSingleton<ILavalinkCache, LavalinkCache>()
+                    .AddSingleton<ISpotifyService, SpotifyService>()
+                    .AddSingleton<YoutubeService>()
+                    .AddSingleton<LyricsOptions>()
+                    .AddSingleton<LyricsService>()
+                    .AddSingleton(
+                    SpotifyClientConfig.CreateDefault().WithAuthenticator(new ClientCredentialsAuthenticator(config.SpotifyClientId, config.SpotifyClientSecret))))
+                .Build();
+
+            ServiceProvider = _host.Services;
+            return ServiceProvider;
+        }
+
+
+
+        public async Task InitBotAsync()
+        {
+            await BuildServiceProvider();
+
+
+            _host.Start();
+            var client = ServiceProvider.GetRequiredService<DiscordShardedClient>();
+            var audioService = ServiceProvider.GetRequiredService<IAudioService>();
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            client.ClientErrored += Discord_ClientErrored;
+            var slash = await client.UseSlashCommandsAsync(new SlashCommandsConfiguration
+            {
+                Services = ServiceProvider
+            });
+            
+            //slash.RegisterCommands<MusicCommands>(888493810554900491);
+            //slash.RegisterCommands<PlayQueueGroup>(888493810554900491);
+            //slash.RegisterCommands<PlayNowGroup>(888493810554900491);
+
+            slash.RegisterCommands<MusicCommands>();
+            slash.RegisterCommands<PlayQueueGroup>();
+            slash.RegisterCommands<PlayNowGroup>();
+
+            await client.StartAsync();
+            client.Ready += Client_Ready;
+            await audioService.InitializeAsync();
+
+            var trackingService = ServiceProvider.GetRequiredService<InactivityTrackingService>();
+            trackingService.ClearTrackers();
+            trackingService.AddTracker(DefaultInactivityTrackers.UsersInactivityTracker);
+            trackingService.AddTracker(DefaultInactivityTrackers.ChannelInactivityTracker);
+
+        }
+
+        private Task Client_Ready(DiscordClient sender, ReadyEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+            var slash = sender.GetSlashCommands();
+            slash.SlashCommandInvoked += Slash_SlashCommandInvoked;
+            slash.SlashCommandErrored += Slash_SlashCommandErrored;
+            sender.UpdateStatusAsync(new DiscordActivity($"/ commands! Shard {sender.ShardId}", ActivityType.Watching));
+
+
+            return Task.CompletedTask;
+        }
+
+        public async Task ShutdownBotAsync()
+        {
+            var audioService = ServiceProvider.GetRequiredService<IAudioService>();
+            var client = ServiceProvider.GetRequiredService<DiscordShardedClient>();
+
+            
+            audioService.Dispose();
+            await client.StopAsync();
+        }
+
+
+        private Task Discord_ClientErrored(DiscordClient sender, ClientErrorEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogDebug("Event {0} errored with Exception {3}", e.EventName, e.Exception);
+            if (e.Exception is NotFoundException)
+                logger.LogDebug($"{ ((NotFoundException)e.Exception).JsonMessage }");
+            if (e.Exception is BadRequestException)
+                logger.LogDebug($"{ ((BadRequestException)e.Exception).Errors }");
+            return Task.CompletedTask;
+        }
+
+        private Task Slash_SlashCommandErrored(SlashCommandsExtension sender, SlashCommandErrorEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogInformation("Command {0} invoked by {1} on Guild {2} with Exception {3}", e.Context.CommandName, e.Context.Member, e.Context.Guild, e.Exception);
+            if (e.Exception is NotFoundException)
+                logger.LogDebug($"{ ((NotFoundException)e.Exception).JsonMessage }");
+            if (e.Exception is BadRequestException)
+                logger.LogInformation($"{ ((BadRequestException)e.Exception).Errors }");
+            return Task.CompletedTask;
+
+        }
+
+        private Task Slash_SlashCommandInvoked(SlashCommandsExtension sender, DSharpPlus.SlashCommands.EventArgs.SlashCommandInvokedEventArgs e)
+        {
+            var logger = ServiceProvider.GetRequiredService<ILogger<TomatenMusicBot>>();
+
+            logger.LogInformation("Command {0} invoked by {1} on Guild {2}", e.Context.CommandName, e.Context.Member, e.Context.Guild);
+
+            return Task.CompletedTask;
+        }
+    }
+}
diff --git a/TomatenMusicCore/TomatenMusicCore.csproj b/TomatenMusicCore/TomatenMusicCore.csproj
new file mode 100644
index 0000000..620cb46
--- /dev/null
+++ b/TomatenMusicCore/TomatenMusicCore.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RestoreAdditionalProjectSources>
+      https://api.nuget.org/v3/index.json;
+      https://nuget.emzi0767.com/api/v3/index.json
+    </RestoreAdditionalProjectSources>
+  </PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="DSharpPlus" Version="4.2.0-nightly-01101" />
+		<PackageReference Include="DSharpPlus.Interactivity" Version="4.2.0-nightly-01101" />
+		<PackageReference Include="DSharpPlus.SlashCommands" Version="4.2.0-nightly-01101" />
+		<PackageReference Include="Google.Apis.YouTube.v3" Version="1.56.0.2630" />
+		<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
+		<PackageReference Include="Lavalink4NET" Version="2.1.1" />
+		<PackageReference Include="Lavalink4NET.DSharpPlus" Version="2.1.1" />
+		<PackageReference Include="Lavalink4NET.Logging.Microsoft" Version="2.1.1-preview.6" />
+		<PackageReference Include="Lavalink4NET.MemoryCache" Version="2.1.1" />
+		<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.7" />
+		<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0-preview.2.22152.2" />
+		<PackageReference Include="SpotifyAPI.Web" Version="6.2.2" />
+		<PackageReference Include="SpotifyAPI.Web.Auth" Version="6.2.2" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<None Update="config.json">
+			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
+		</None>
+	</ItemGroup>
+
+</Project>
diff --git a/TomatenMusicCore/Util/CollectionUtil.cs b/TomatenMusicCore/Util/CollectionUtil.cs
new file mode 100644
index 0000000..1511b14
--- /dev/null
+++ b/TomatenMusicCore/Util/CollectionUtil.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Security.Cryptography;
+using System.Linq;
+
+namespace TomatenMusic.Util
+{
+    static class CollectionUtil
+    {
+        public static void Shuffle<T>(this IList<T> list)
+        {
+            RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
+            int n = list.Count;
+            while (n > 1)
+            {
+                byte[] box = new byte[1];
+                do provider.GetBytes(box);
+                while (!(box[0] < n * (Byte.MaxValue / n)));
+                int k = (box[0] % n);
+                n--;
+                T value = list[k];
+                list[k] = list[n];
+                list[n] = value;
+            }
+        }
+
+        /* public static void Remove<T>(this IList<T> list, T item)
+         {
+             List<T> newList = new List<T>();
+             bool done = false;
+             foreach (var i in list)
+             {
+                 if (i.Equals(item) && !done)
+                 {
+                     done = true;
+                     continue;
+                 }
+
+                 newList.Add(i);
+             }
+
+             list = newList;
+         }*/
+
+
+
+        public static class Arrays
+        {
+            public static IList<T> AsList<T>(params T[] source)
+            {
+                return source;
+            }
+        }
+    }
+}
diff --git a/TomatenMusicCore/Util/Common.cs b/TomatenMusicCore/Util/Common.cs
new file mode 100644
index 0000000..050d5cc
--- /dev/null
+++ b/TomatenMusicCore/Util/Common.cs
@@ -0,0 +1,272 @@
+using DSharpPlus.Entities;
+using HtmlAgilityPack;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using TomatenMusic.Music.Entitites;
+using TomatenMusic.Util;
+using Microsoft.Extensions.Logging;
+using TomatenMusic.Music;
+using System.Threading.Tasks;
+using System.Linq;
+using Lavalink4NET.Player;
+
+namespace TomatenMusic.Util
+{
+    class Common
+    {
+
+        public static DiscordEmbed AsEmbed(LavalinkTrack track, LoopType loopType, int position = -1)
+        {
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder(AsEmbed(track, position));
+            builder.AddField("Current Queue Loop", loopType.ToString(), true);
+            return builder;
+        }
+
+        public static DiscordEmbed AsEmbed(LavalinkTrack track, int position = -1)
+        {
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
+                .WithTitle(track.Title)
+                .WithUrl(track.Source)
+                .WithImageUrl(context.YoutubeThumbnail)
+                .WithDescription(context.YoutubeDescription)
+                .AddField("Length", Common.GetTimestamp(track.Duration), true);
+
+            if (context.IsFile)
+            {
+                builder.WithAuthor(track.Author);
+                builder.WithUrl(track.Source);
+
+            }
+            else
+                builder
+                    .WithAuthor(track.Author, context.YoutubeAuthorUri.ToString(), context.YoutubeAuthorThumbnail.ToString())
+                    .WithUrl(track.Source);
+
+            if (position != -1)
+            {
+                builder.AddField("Queue Position", (position == 0 ? "Now Playing" : position.ToString()), true);
+            }
+
+            if (!context.IsFile)
+            {
+                if (track.Position.Seconds > 0)
+                    builder.AddField("Starting Position", GetTimestamp(track.Position), true);
+
+                builder.AddField("Views", $"{context.YoutubeViews:N0} Views", true);
+                builder.AddField("Rating", $"{context.YoutubeLikes:N0} 👍", true);
+                builder.AddField("Upload Date", $"{context.YoutubeUploadDate.ToString("dd. MMMM, yyyy")}", true);
+                builder.AddField("Comments", context.YoutubeCommentCount == null ? "Comments Disabled" : $"{context.YoutubeCommentCount:N0} Comments", true);
+                builder.AddField("Channel Subscriptions", $"{context.YoutubeAuthorSubs:N0} Subscribers", true);
+            }
+
+            return builder;
+        }
+
+        public static DiscordEmbed AsEmbed(ILavalinkPlaylist playlist)
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+
+            if (playlist is YoutubePlaylist)
+            {
+                YoutubePlaylist youtubePlaylist = (YoutubePlaylist)playlist;
+                Console.WriteLine($"{playlist.AuthorName}, {playlist.AuthorUri.ToString()}, {playlist.AuthorThumbnail.ToString()}");
+                builder.WithAuthor(playlist.AuthorName, playlist.AuthorUri.ToString(), youtubePlaylist.AuthorThumbnail.ToString());
+                builder.WithTitle(playlist.Title);
+                builder.WithUrl(playlist.Url);
+                builder.WithDescription(TrackListString(playlist.Tracks, 4000));
+                builder.WithImageUrl(youtubePlaylist.Thumbnail);
+                builder.AddField("Description", playlist.Description, false);
+                builder.AddField("Track Count", $"{playlist.Tracks.Count()} Tracks", true);
+                builder.AddField("Length", $"{Common.GetTimestamp(playlist.GetLength())}", true);
+                builder.AddField("Create Date", $"{youtubePlaylist.CreationTime:dd. MMMM, yyyy}", true);
+                
+            }else if (playlist is SpotifyPlaylist)
+            {
+                SpotifyPlaylist spotifyPlaylist = (SpotifyPlaylist)playlist;
+
+                builder.WithTitle(playlist.Title);
+                builder.WithUrl(playlist.Url);
+                builder.WithDescription(TrackListString(playlist.Tracks, 4000));
+                builder.AddField("Description", playlist.Description, false);
+                builder.AddField("Track Count", $"{playlist.Tracks.Count()} Tracks", true);
+                builder.AddField("Length", $"{Common.GetTimestamp(playlist.GetLength())}", true);
+                builder.AddField("Spotify Followers", $"{spotifyPlaylist.Followers:N0}", true);
+                if (spotifyPlaylist.AuthorThumbnail != null)
+                {
+                    builder.WithAuthor(playlist.AuthorName, playlist.AuthorUri.ToString(), spotifyPlaylist.AuthorThumbnail.ToString());
+                }else
+                    builder.WithAuthor(playlist.AuthorName, playlist.AuthorUri.ToString());
+            }
+
+            return builder.Build();
+        }
+
+        public static DiscordEmbed GetQueueEmbed(GuildPlayer player)
+        {
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+
+            builder.WithDescription(TrackListString(player.PlayerQueue.Queue, 4000));
+            builder.WithTitle("Current Queue");
+            builder.WithAuthor($"{player.PlayerQueue.Queue.Count} Songs");
+
+            TimeSpan timeSpan = TimeSpan.FromTicks(0);
+
+            foreach (var track in player.PlayerQueue.Queue)
+            {
+                timeSpan = timeSpan.Add(track.Duration);
+            }
+
+            builder.AddField("Length", GetTimestamp(timeSpan), true);
+            builder.AddField("Loop Type", player.PlayerQueue.LoopType.ToString(), true);
+            builder.AddField("Autoplay", player.Autoplay ? "✅" : "❌", true);
+            if (player.PlayerQueue.CurrentPlaylist != null)
+                builder.AddField("Current Playlist", $"[{player.PlayerQueue.CurrentPlaylist.Title}]({player.PlayerQueue.CurrentPlaylist.Url})", true);
+
+            if (player.PlayerQueue.PlayedTracks.Any())
+                builder.AddField("History", TrackListString(player.PlayerQueue.PlayedTracks, 1000), true);
+
+            return builder;
+        }
+
+        public static string TrackListString(IEnumerable<LavalinkTrack> tracks, int maxCharacters)
+        {
+            StringBuilder builder = new StringBuilder();
+            string lastString = " ";
+            int count = 1;
+            foreach (LavalinkTrack track in tracks)
+            {
+                if (builder.ToString().Length > maxCharacters)
+                {
+                    builder = new StringBuilder(lastString);
+                    builder.Append(String.Format("***And {0} more...***", tracks.Count() - count));
+                    break;
+                }
+
+                FullTrackContext context = (FullTrackContext)track.Context;
+
+                lastString = builder.ToString();
+                builder.Append(count).Append(": ").Append($"[{track.Title}]({track.Source})").Append(" [").Append(Common.GetTimestamp(track.Duration)).Append("] | ");
+                builder.Append($"[{track.Author}]({context.YoutubeAuthorUri})");
+                if (track.Position.Seconds != 0)
+                    builder.Append($" | 🕑");
+                builder.Append("\n\n");
+                count++;
+            }
+            builder.Append(" ");
+            return builder.ToString();
+        }
+
+        public static string GetTimestamp(TimeSpan timeSpan)
+        {
+            if (timeSpan.Hours > 0)
+                return String.Format("{0:hh\\:mm\\:ss}", timeSpan);
+            else
+                return String.Format("{0:mm\\:ss}", timeSpan);
+        }
+
+        public static TimeSpan ToTimeSpan(string text)
+        {
+            string[] input = text.Split(" ");
+            TimeSpan timeSpan = TimeSpan.FromMilliseconds(0);
+
+            foreach (var item in input)
+            {
+                var l = item.Length - 1;
+                var value = item.Substring(0, l);
+                var type = item.Substring(l, 1);
+
+                switch (type)
+                {
+                    case "d": 
+                        timeSpan = timeSpan.Add(TimeSpan.FromDays(double.Parse(value)));
+                        break;
+                    case "h":
+                        timeSpan = timeSpan.Add(TimeSpan.FromHours(double.Parse(value))); 
+                        break;
+                    case "m":
+                        timeSpan = timeSpan.Add(TimeSpan.FromMinutes(double.Parse(value)));
+                        break;
+                    case "s":
+                        timeSpan = timeSpan.Add(TimeSpan.FromSeconds(double.Parse(value)));
+                        break;
+                    case "f":
+                        timeSpan = timeSpan.Add(TimeSpan.FromMilliseconds(double.Parse(value))); 
+                        break;
+                    case "z":
+                        timeSpan = timeSpan.Add(TimeSpan.FromTicks(long.Parse(value)));
+                        break;
+                    default:
+                        timeSpan = timeSpan.Add(TimeSpan.FromDays(double.Parse(value)));
+                        break;
+                }
+            }
+
+            return timeSpan;
+        }
+
+       public static string ProgressBar(int current, int max)
+        {
+            int percentage = (current * 100) / max;
+            int rounded = (int) Math.Round(((double) percentage / 10));
+
+            StringBuilder builder = new StringBuilder();
+
+            for (int i = 0; i <= 10; i++)
+            {
+                if (i == rounded)
+                    builder.Append("🔘");
+                else
+                    builder.Append("─");
+            }
+
+            return builder.ToString();
+        }
+
+        public async static Task<DiscordEmbed> CurrentSongEmbedAsync(GuildPlayer player)
+        {
+
+            DiscordEmbedBuilder builder = new DiscordEmbedBuilder();
+            LavalinkTrack track = player.CurrentTrack;
+
+            if (track == null)
+            {
+                builder.WithColor(DiscordColor.Red);
+                builder.WithTitle("Nothing Playing");
+                builder.WithImageUrl("https://media.tomatentum.net/TMBanner.gif");
+                return builder;
+            }
+
+            FullTrackContext context = (FullTrackContext)track.Context;
+
+            string progressBar = $"|{ProgressBar((int)player.Position.Position.TotalSeconds, (int)track.Duration.TotalSeconds)}|\n [{Common.GetTimestamp(player.Position.Position)}/{Common.GetTimestamp(track.Duration)}]";
+            
+            builder.WithAuthor(track.Author);
+            builder.WithTitle(track.Title);
+            builder.WithUrl(track.Source);
+            builder.WithColor(player.State == PlayerState.Paused ? DiscordColor.Orange : DiscordColor.Green);
+            builder.AddField("Length", Common.GetTimestamp(track.Duration), true);
+            builder.AddField("Loop", player.PlayerQueue.LoopType.ToString(), true);
+            builder.AddField("Progress", progressBar, true);
+            if (!context.IsFile)
+            {
+                builder.WithAuthor(track.Author, context.YoutubeAuthorUri.ToString(), context.YoutubeAuthorThumbnail.ToString());
+                builder.WithImageUrl(context.YoutubeThumbnail);
+                builder.AddField("Views", $"{context.YoutubeViews:N0} Views", true);
+                builder.AddField("Rating", $"{context.YoutubeLikes:N0} 👍", true);
+                builder.AddField("Upload Date", $"{context.YoutubeUploadDate.ToString("dd. MMMM, yyyy")}", true);
+                builder.AddField("Comments", $"{context.YoutubeCommentCount:N0} Comments", true);
+                builder.AddField("Channel Subscriptions", $"{context.YoutubeAuthorSubs:N0} Subscribers", true);
+
+            }
+
+
+            return builder;
+        }
+    }
+}
diff --git a/TomatenMusicCore/Util/PageManager.cs b/TomatenMusicCore/Util/PageManager.cs
new file mode 100644
index 0000000..b78849a
--- /dev/null
+++ b/TomatenMusicCore/Util/PageManager.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+
+
+namespace TomatenMusic.Util
+{
+    class PageManager<T>
+    {
+
+		private List<T> Items;
+		private int PageSize;
+
+		public PageManager(List<T> allItems, int pageSize)
+		{
+			this.Items = allItems;
+			this.PageSize = pageSize;
+		}
+
+		public List<T> GetPage(int page)
+		{
+			if (page <= GetTotalPages() && page > 0)
+			{
+				List<T> onPage = new List<T>();
+				page--;
+
+				int lowerBound = page * PageSize;
+				int upperBound = Math.Min(lowerBound + PageSize, Items.Count);
+
+				for (int i = lowerBound; i < upperBound; i++)
+				{
+					onPage.Add(Items[i]);
+				}
+
+				return onPage;
+			}
+			else
+				return new List<T>();
+		}
+
+		public void AddItem(T Item)
+		{
+			if (Items.Contains(Item))
+			{
+				return;
+			}
+			Items.Add(Item);
+		}
+
+		public void RemoveItem(T Item)
+		{
+
+		if (Items.Contains(Item))
+			Items.Remove(Item);
+		}
+
+		public int GetTotalPages()
+		{
+			int totalPages = (int)Math.Ceiling((double)Items.Count / PageSize);
+
+			return totalPages;
+		}
+
+		public List<T> GetContents()
+		{
+			return Items;
+		}
+    }
+}
diff --git a/TomatenMusicCore/Util/RandomUtil.cs b/TomatenMusicCore/Util/RandomUtil.cs
new file mode 100644
index 0000000..e776853
--- /dev/null
+++ b/TomatenMusicCore/Util/RandomUtil.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+
+namespace TomatenMusic.Util
+{
+    class RandomUtil
+    {
+
+        public static string GenerateGuid()
+        {
+            return String.Concat(Guid.NewGuid().ToString("N").Select(c => (char)(c + 17))).ToLower();
+        }
+    }
+}
-- 
2.45.2


From fa9aa36c946a03d2778ca2f397ed289a8860e3bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 29 Mar 2022 22:34:01 +0200
Subject: [PATCH 10/21] fix history always showing same track when using
 autoplay

fix tracks in history still having timestamps
---
 TomatenMusicCore/Music/GuildPlayer.cs | 3 ++-
 TomatenMusicCore/Music/PlayerQueue.cs | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index f0510ea..d90147e 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -109,7 +109,7 @@ namespace TomatenMusic.Music
             MusicActionResponse response;
             try
             {
-                response = PlayerQueue.NextTrack(true);
+                response = PlayerQueue.(true);
             }catch (Exception ex)
             {
                 if (Autoplay)
@@ -249,6 +249,7 @@ namespace TomatenMusic.Music
                     TomatenMusicTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
                     _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
                     await base.OnTrackEndAsync(eventArgs);
+                    PlayerQueue.LastTrack = newTrack;
                     await newTrack.Play(this);
                     QueuePrompt.UpdateFor(GuildId);
 
diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
index b705f8a..b8ca3cf 100644
--- a/TomatenMusicCore/Music/PlayerQueue.cs
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -95,7 +95,7 @@ namespace TomatenMusic.Music
         public MusicActionResponse NextTrack(bool ignoreLoop = false)
         {
             if (LastTrack != null)
-                PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(LastTrack));
+                PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(new TomatenMusicTrack(LastTrack.WithPosition(TimeSpan.Zero))));
 
             switch (LoopType)
             {
-- 
2.45.2


From e5d3f6935db62efeb1c797f93683139ae080fd0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Tue, 29 Mar 2022 22:36:46 +0200
Subject: [PATCH 11/21] fix errors

---
 TomatenMusicCore/Music/GuildPlayer.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index d90147e..4160dc2 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -109,7 +109,7 @@ namespace TomatenMusic.Music
             MusicActionResponse response;
             try
             {
-                response = PlayerQueue.(true);
+                response = PlayerQueue.NextTrack(true);
             }catch (Exception ex)
             {
                 if (Autoplay)
-- 
2.45.2


From 85dbbd8bfcb701194d8112bb966876fdd131991a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Thu, 31 Mar 2022 18:37:35 +0200
Subject: [PATCH 12/21] fix file playback

---
 TomatenMusicCore/Music/Entitites/FullTrackContext.cs | 2 +-
 TomatenMusicCore/Util/Common.cs                      | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/TomatenMusicCore/Music/Entitites/FullTrackContext.cs b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
index 03e6b19..4250914 100644
--- a/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
+++ b/TomatenMusicCore/Music/Entitites/FullTrackContext.cs
@@ -20,7 +20,7 @@ namespace TomatenMusic.Music.Entitites
         public IEnumerable<string> YoutubeTags { get; set; }
         public ulong YoutubeViews { get; set; }
         public ulong YoutubeLikes { get; set; }
-        public Uri YoutubeThumbnail { get; set; }
+        public Uri? YoutubeThumbnail { get; set; } = null;
         public DateTime YoutubeUploadDate { get; set; }
         //
         // Summary:
diff --git a/TomatenMusicCore/Util/Common.cs b/TomatenMusicCore/Util/Common.cs
index 050d5cc..478e9af 100644
--- a/TomatenMusicCore/Util/Common.cs
+++ b/TomatenMusicCore/Util/Common.cs
@@ -32,10 +32,12 @@ namespace TomatenMusic.Util
             DiscordEmbedBuilder builder = new DiscordEmbedBuilder()
                 .WithTitle(track.Title)
                 .WithUrl(track.Source)
-                .WithImageUrl(context.YoutubeThumbnail)
                 .WithDescription(context.YoutubeDescription)
                 .AddField("Length", Common.GetTimestamp(track.Duration), true);
 
+            if (context.YoutubeThumbnail != null)
+                builder.WithImageUrl(context.YoutubeThumbnail);
+
             if (context.IsFile)
             {
                 builder.WithAuthor(track.Author);
-- 
2.45.2


From bc7e493e71a6d11a860d485c72ba93812f1be030 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Fri, 1 Apr 2022 18:46:03 +0200
Subject: [PATCH 13/21] fix playlist playback not working

---
 TomatenMusicCore/Music/GuildPlayer.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index 4160dc2..3de4f45 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -69,6 +69,7 @@ namespace TomatenMusic.Music
             EnsureNotDestroyed();
             EnsureConnected();
 
+            _ = playlist.Play(this);
             _logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Title, (await GetGuildAsync()).Name);
 
 
-- 
2.45.2


From 3fd19eb149158983ae93aff11efd4d204582bb16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Fri, 1 Apr 2022 18:53:07 +0200
Subject: [PATCH 14/21] fix playlist playback nicely

---
 TomatenMusic/Services/TomatenMusicService.cs  |  4 ++--
 TomatenMusicCore/Commands/PlayCommandGroup.cs |  4 ++--
 TomatenMusicCore/Music/GuildPlayer.cs         | 21 -------------------
 3 files changed, 4 insertions(+), 25 deletions(-)

diff --git a/TomatenMusic/Services/TomatenMusicService.cs b/TomatenMusic/Services/TomatenMusicService.cs
index a6780ee..1ca8fde 100644
--- a/TomatenMusic/Services/TomatenMusicService.cs
+++ b/TomatenMusic/Services/TomatenMusicService.cs
@@ -46,9 +46,9 @@ namespace TomatenMusic_Api
 			if (e.Response.IsPlaylist)
             {
 				if (e.Now)
-					await player.PlayPlaylistNowAsync(e.Response.Playlist);
+					await player.PlayNowAsync(e.Response.Playlist);
 				else
-					await player.PlayPlaylistAsync(e.Response.Playlist);
+					await player.PlayItemAsync(e.Response.Playlist);
 			}else
             {
 				if (e.Now)
diff --git a/TomatenMusicCore/Commands/PlayCommandGroup.cs b/TomatenMusicCore/Commands/PlayCommandGroup.cs
index 736594d..b7095f8 100644
--- a/TomatenMusicCore/Commands/PlayCommandGroup.cs
+++ b/TomatenMusicCore/Commands/PlayCommandGroup.cs
@@ -84,7 +84,7 @@ namespace TomatenMusic.Commands
                     if (response.IsPlaylist)
                     {
                         ILavalinkPlaylist playlist = response.Playlist;
-                        await player.PlayPlaylistNowAsync(playlist);
+                        await player.PlayNowAsync(playlist);
 
                         _ = ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
                         Common.AsEmbed(playlist)
@@ -237,7 +237,7 @@ namespace TomatenMusic.Commands
                     if (response.IsPlaylist)
                     {
                         ILavalinkPlaylist playlist = response.Playlist;
-                        await player.PlayPlaylistAsync(playlist);
+                        await player.PlayItemAsync(playlist);
 
                         await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Now Playing:").AddEmbed(
                         Common.AsEmbed(playlist)
diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index 3de4f45..c877454 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -64,27 +64,6 @@ namespace TomatenMusic.Music
             QueuePrompt.UpdateFor(GuildId);
         }
 
-        public async Task PlayPlaylistAsync(ILavalinkPlaylist playlist)
-        {
-            EnsureNotDestroyed();
-            EnsureConnected();
-
-            _ = playlist.Play(this);
-            _logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Title, (await GetGuildAsync()).Name);
-
-
-            QueuePrompt.UpdateFor(GuildId);
-        }
-
-        public async Task PlayPlaylistNowAsync(ILavalinkPlaylist playlist)
-        {
-            EnsureConnected();
-            EnsureNotDestroyed();
-
-
-            QueuePrompt.UpdateFor(GuildId);
-        }
-
         public async Task RewindAsync()
         {
             EnsureNotDestroyed();
-- 
2.45.2


From a71f65de55b2a6bcd6e18f23f1c77ff499f98091 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Wed, 6 Apr 2022 21:32:43 +0200
Subject: [PATCH 15/21] update package sources

---
 TomatenMusic/TomatenMusic.csproj         |  4 ----
 TomatenMusicCore/TomatenMusicCore.csproj | 12 ++++--------
 2 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/TomatenMusic/TomatenMusic.csproj b/TomatenMusic/TomatenMusic.csproj
index 453b8d7..d793b10 100644
--- a/TomatenMusic/TomatenMusic.csproj
+++ b/TomatenMusic/TomatenMusic.csproj
@@ -5,10 +5,6 @@
     <Nullable>enable</Nullable>
     <ImplicitUsings>enable</ImplicitUsings>
     <RootNamespace>TomatenMusic_Api</RootNamespace>
-    <RestoreAdditionalProjectSources>
-      https://api.nuget.org/v3/index.json;
-      https://nuget.emzi0767.com/api/v3/index.json
-    </RestoreAdditionalProjectSources>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/TomatenMusicCore/TomatenMusicCore.csproj b/TomatenMusicCore/TomatenMusicCore.csproj
index 620cb46..36d5b2e 100644
--- a/TomatenMusicCore/TomatenMusicCore.csproj
+++ b/TomatenMusicCore/TomatenMusicCore.csproj
@@ -4,17 +4,13 @@
     <TargetFramework>net6.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
-    <RestoreAdditionalProjectSources>
-      https://api.nuget.org/v3/index.json;
-      https://nuget.emzi0767.com/api/v3/index.json
-    </RestoreAdditionalProjectSources>
   </PropertyGroup>
 
 	<ItemGroup>
-		<PackageReference Include="DSharpPlus" Version="4.2.0-nightly-01101" />
-		<PackageReference Include="DSharpPlus.Interactivity" Version="4.2.0-nightly-01101" />
-		<PackageReference Include="DSharpPlus.SlashCommands" Version="4.2.0-nightly-01101" />
-		<PackageReference Include="Google.Apis.YouTube.v3" Version="1.56.0.2630" />
+		<PackageReference Include="DSharpPlus" Version="4.2.0-nightly-01107" />
+		<PackageReference Include="DSharpPlus.Interactivity" Version="4.2.0-nightly-01107" />
+		<PackageReference Include="DSharpPlus.SlashCommands" Version="4.2.0-nightly-01107" />
+		<PackageReference Include="Google.Apis.YouTube.v3" Version="1.57.0.2637" />
 		<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
 		<PackageReference Include="Lavalink4NET" Version="2.1.1" />
 		<PackageReference Include="Lavalink4NET.DSharpPlus" Version="2.1.1" />
-- 
2.45.2


From fcd376bdcbe6a2f10be6f084531a5bb6717f2a10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Wed, 13 Apr 2022 11:24:59 +0200
Subject: [PATCH 16/21] fix Queue Loop Current track not prepending on enable
 but appending

---
 TomatenMusicCore/Music/PlayerQueue.cs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
index b8ca3cf..b3802f8 100644
--- a/TomatenMusicCore/Music/PlayerQueue.cs
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -157,8 +157,7 @@ namespace TomatenMusic.Music
 
             if (type == LoopType.QUEUE)
             {
-                QueueLoopList = new List<TomatenMusicTrack>(Queue);
-                QueueLoopList.Add(LastTrack);
+                QueueLoopList = new List<TomatenMusicTrack>(QueueLoopList.Prepend(LastTrack));
             }
         }
     }
-- 
2.45.2


From 47bffbff7f59f9a1e54773c42d7f5f976b66abf1 Mon Sep 17 00:00:00 2001
From: EkiciLP <timmueldk@gmx.de>
Date: Thu, 14 Apr 2022 12:13:39 +0200
Subject: [PATCH 17/21] fix Bot throwing an error when tryimg to autoplay
 tracks from non youtube sources

---
 TomatenMusicCore/Music/GuildPlayer.cs | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index c877454..9687114 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -203,7 +203,6 @@ namespace TomatenMusic.Music
         public async override Task OnTrackEndAsync(TrackEndEventArgs eventArgs)
         {
             DisconnectOnStop = false;
-            YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
             var oldTrack = CurrentTrack;
 
             if (eventArgs.Reason != TrackEndReason.Finished)
@@ -226,12 +225,8 @@ namespace TomatenMusic.Music
                         return;
                     }
 
-                    TomatenMusicTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
-                    _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
                     await base.OnTrackEndAsync(eventArgs);
-                    PlayerQueue.LastTrack = newTrack;
-                    await newTrack.Play(this);
-                    QueuePrompt.UpdateFor(GuildId);
+                    _ = OnAutoPlay(oldTrack);
 
                 }
             }
@@ -239,6 +234,21 @@ namespace TomatenMusic.Music
             
         }
 
+        public async Task OnAutoPlay(LavalinkTrack oldTrack)
+        {
+            YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
+
+            TomatenMusicTrack newTrack;
+            if (oldTrack.Source != "YouTube" )
+                newTrack = await youtube.GetRelatedTrackAsync(PlayerQueue.PlayedTracks.First(x => x.Source == "YouTube").TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
+            else
+                newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
+            _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
+            PlayerQueue.LastTrack = newTrack;
+            await newTrack.Play(this);
+            QueuePrompt.UpdateFor(GuildId);
+        }
+
         public async Task<DiscordChannel> GetChannelAsync()
         {
             EnsureConnected();
-- 
2.45.2


From ac0f6e18ed36468414bc86724ce41d7803b5b0c0 Mon Sep 17 00:00:00 2001
From: EkiciLP <timmueldk@gmx.de>
Date: Fri, 15 Apr 2022 02:28:20 +0200
Subject: [PATCH 18/21] fixed stage channel speaking requests

fixed several bugs regarding autoplay

fixed skip autoplay not working
---
 TomatenMusicCore/Music/GuildPlayer.cs         | 25 ++++++++++---------
 TomatenMusicCore/Music/PlayerQueue.cs         |  5 ++--
 .../Prompt/Implementation/QueuePrompt.cs      | 17 +++++++++++--
 3 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index 9687114..d2db34a 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -89,17 +89,12 @@ namespace TomatenMusic.Music
             MusicActionResponse response;
             try
             {
-                response = PlayerQueue.NextTrack(true);
+                response = PlayerQueue.NextTrack(true, Autoplay);
             }catch (Exception ex)
             {
                 if (Autoplay)
                 {
-                    YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
-                    LavalinkTrack newTrack = await youtube.GetRelatedTrackAsync(CurrentTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
-
-                    _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Autoplayed Track {newTrack.Title}");
-                    await PlayAsync(newTrack);
-                    QueuePrompt.UpdateFor(GuildId);
+                    _ = OnAutoPlay(CurrentTrack);
                     return;
                 }
                 throw ex;
@@ -164,10 +159,16 @@ namespace TomatenMusic.Music
 
             if (channel.Type == ChannelType.Stage)
             {
-                DiscordStageInstance stageInstance = await channel.GetStageInstanceAsync();
+                DiscordStageInstance stageInstance;
+                try
+                {
+                    stageInstance = await channel.GetStageInstanceAsync();
 
-                if (stageInstance == null)
+                }catch (Exception ex)
+                {
                     stageInstance = await channel.CreateStageInstanceAsync("Music");
+                }
+                
                 await stageInstance.Channel.UpdateCurrentUserVoiceStateAsync(false);
             }
 
@@ -239,13 +240,13 @@ namespace TomatenMusic.Music
             YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
 
             TomatenMusicTrack newTrack;
-            if (oldTrack.Source != "YouTube" )
-                newTrack = await youtube.GetRelatedTrackAsync(PlayerQueue.PlayedTracks.First(x => x.Source == "YouTube").TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
+            if (oldTrack.Provider != StreamProvider.YouTube)
+                newTrack = await youtube.GetRelatedTrackAsync(PlayerQueue.PlayedTracks.First(x => x.Provider == StreamProvider.YouTube).TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
             else
                 newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier, PlayerQueue.PlayedTracks.Take(5).ToList().ConvertAll(x => x.TrackIdentifier));
             _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
             PlayerQueue.LastTrack = newTrack;
-            await newTrack.Play(this);
+            await newTrack.PlayNow(this, withoutQueuePrepend: true);
             QueuePrompt.UpdateFor(GuildId);
         }
 
diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
index b3802f8..0a3d646 100644
--- a/TomatenMusicCore/Music/PlayerQueue.cs
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -92,10 +92,11 @@ namespace TomatenMusic.Music
 
         }
 
-        public MusicActionResponse NextTrack(bool ignoreLoop = false)
+        public MusicActionResponse NextTrack(bool ignoreLoop = false, bool autoplay = false)
         {
             if (LastTrack != null)
-                PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(new TomatenMusicTrack(LastTrack.WithPosition(TimeSpan.Zero))));
+                if (LoopType != LoopType.NONE && Queue.Count == 0 || autoplay)
+                    PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(new TomatenMusicTrack(LastTrack.WithPosition(TimeSpan.Zero))));
 
             switch (LoopType)
             {
diff --git a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
index fb087d8..0677813 100644
--- a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
+++ b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
@@ -98,7 +98,10 @@ namespace TomatenMusic.Prompt.Implementation
                         await Player.RewindAsync();
                     }catch (Exception ex)
                     {
-
+                        _ = args.Interaction.CreateResponseAsync(
+                            DSharpPlus.InteractionResponseType.ChannelMessageWithSource,
+                            new DiscordInteractionResponseBuilder()
+                            .WithContent($"An Error occurred during this Interaction {ex.Message}"));
                     }
                 }
             }
@@ -131,8 +134,18 @@ namespace TomatenMusic.Prompt.Implementation
                         _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
                         return;
                     }
+                    try
+                    {
+                        await Player.SkipAsync();
 
-                    await Player.SkipAsync();
+                    }
+                    catch (Exception ex)
+                    {
+                        _ = args.Interaction.CreateResponseAsync(
+                            DSharpPlus.InteractionResponseType.ChannelMessageWithSource,
+                            new DiscordInteractionResponseBuilder()
+                            .WithContent($"An Error occurred during this Interaction {ex.Message}"));
+                    }
 
                     System.Timers.Timer timer = new System.Timers.Timer(800);
                     timer.Elapsed += (s, args) =>
-- 
2.45.2


From 1fc3fae278baa12d3be74d2037d7c689c4e05936 Mon Sep 17 00:00:00 2001
From: Tueem <72763011+EkiciLP@users.noreply.github.com>
Date: Tue, 24 May 2022 18:40:19 +0200
Subject: [PATCH 19/21] change Ip of new Lavalink

---
 TomatenMusicCore/TomatenMusicBot.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/TomatenMusicCore/TomatenMusicBot.cs b/TomatenMusicCore/TomatenMusicBot.cs
index 32ac443..a68e715 100644
--- a/TomatenMusicCore/TomatenMusicBot.cs
+++ b/TomatenMusicCore/TomatenMusicBot.cs
@@ -87,9 +87,9 @@ namespace TomatenMusic
                     .AddSingleton(
                           new LavalinkNodeOptions
                           {
-                              RestUri = "http://116.202.92.16:2333",
+                              RestUri = "http://127.0.0.1:2333",
                               Password = config.LavaLinkPassword,
-                              WebSocketUri = "ws://116.202.92.16:2333",
+                              WebSocketUri = "ws://127.0.0.1:2333",
                               AllowResuming = true
 
                           })
-- 
2.45.2


From 5a9d978b5e6a3ddafbca14a4d192f0cb4b307b67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Sun, 9 Oct 2022 16:44:00 +0200
Subject: [PATCH 20/21] - fixed Song History not working anymore

---
 TomatenMusicCore/Music/GuildPlayer.cs                 |  2 +-
 TomatenMusicCore/Music/PlayerQueue.cs                 |  6 +++---
 TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs | 11 ++++++-----
 3 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/TomatenMusicCore/Music/GuildPlayer.cs b/TomatenMusicCore/Music/GuildPlayer.cs
index d2db34a..1b6e22a 100644
--- a/TomatenMusicCore/Music/GuildPlayer.cs
+++ b/TomatenMusicCore/Music/GuildPlayer.cs
@@ -101,7 +101,7 @@ namespace TomatenMusic.Music
             }
 
             _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Track {response.Track.Title}");
-            await base.PlayAsync(response.Track);
+            await PlayNowAsync(response.Track, withoutQueuePrepend: true);
             QueuePrompt.UpdateFor(GuildId);
         }
 
diff --git a/TomatenMusicCore/Music/PlayerQueue.cs b/TomatenMusicCore/Music/PlayerQueue.cs
index 0a3d646..8cff94a 100644
--- a/TomatenMusicCore/Music/PlayerQueue.cs
+++ b/TomatenMusicCore/Music/PlayerQueue.cs
@@ -26,7 +26,7 @@ namespace TomatenMusic.Music
 
         public TomatenMusicTrack LastTrack { get; set; }
 
-        public List<TomatenMusicTrack> QueueLoopList { get; private set; }
+        public List<TomatenMusicTrack> QueueLoopList { get; private set; } = new List<TomatenMusicTrack>();
 
         public void QueueTrack(TomatenMusicTrack track)
         {
@@ -95,7 +95,7 @@ namespace TomatenMusic.Music
         public MusicActionResponse NextTrack(bool ignoreLoop = false, bool autoplay = false)
         {
             if (LastTrack != null)
-                if (LoopType != LoopType.NONE && Queue.Count == 0 || autoplay)
+                if (LoopType != LoopType.TRACK || (ignoreLoop && (Queue.Any() || autoplay)))
                     PlayedTracks = new Queue<TomatenMusicTrack>(PlayedTracks.Prepend(new TomatenMusicTrack(LastTrack.WithPosition(TimeSpan.Zero))));
 
             switch (LoopType)
@@ -158,7 +158,7 @@ namespace TomatenMusic.Music
 
             if (type == LoopType.QUEUE)
             {
-                QueueLoopList = new List<TomatenMusicTrack>(QueueLoopList.Prepend(LastTrack));
+                QueueLoopList = new List<TomatenMusicTrack>(Queue.Prepend(LastTrack));
             }
         }
     }
diff --git a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
index 0677813..014caff 100644
--- a/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
+++ b/TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
@@ -93,15 +93,16 @@ namespace TomatenMusic.Prompt.Implementation
                         _ = args.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Please connect to the bots Channel to use this Interaction"));
                         return;
                     }
+
+
                     try
                     {
                         await Player.RewindAsync();
-                    }catch (Exception ex)
+                    }
+                    catch (Exception ex)
                     {
-                        _ = args.Interaction.CreateResponseAsync(
-                            DSharpPlus.InteractionResponseType.ChannelMessageWithSource,
-                            new DiscordInteractionResponseBuilder()
-                            .WithContent($"An Error occurred during this Interaction {ex.Message}"));
+                        Console.WriteLine(ex);
+
                     }
                 }
             }
-- 
2.45.2


From 0024325f4f590adc4a4aabfaa29e6cd37feb1471 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20M=C3=BCller?= <ekicilp@gmail.com>
Date: Sun, 9 Oct 2022 16:59:48 +0200
Subject: [PATCH 21/21] - fixed PlayNow issue

---
 TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs   | 4 +++-
 TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs | 2 +-
 TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs   | 4 +++-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
index c2296c0..59a5b26 100644
--- a/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
+++ b/TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
@@ -44,7 +44,9 @@ namespace TomatenMusic.Music.Entitites
             if (!player.PlayerQueue.Queue.Any())
                 player.PlayerQueue.CurrentPlaylist = this;
 
-            player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+            if (!withoutQueuePrepend && player.State == PlayerState.Playing)
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
 
             Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
 
diff --git a/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs b/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
index 658e0a6..04bc5e3 100644
--- a/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
+++ b/TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
@@ -38,7 +38,7 @@ namespace TomatenMusicCore.Music.Entities
 
         public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
         {
-            if (!withoutQueuePrepend)
+            if (!withoutQueuePrepend && player.State == PlayerState.Playing)
                 player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
 
 
diff --git a/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
index 641a21e..0da7007 100644
--- a/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
+++ b/TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
@@ -57,7 +57,9 @@ namespace TomatenMusic.Music.Entitites
             if (!player.PlayerQueue.Queue.Any())
                 player.PlayerQueue.CurrentPlaylist = this;
 
-            player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+            if (!withoutQueuePrepend && player.State == PlayerState.Playing)
+                player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
+
 
 
             Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(Tracks);
-- 
2.45.2