INIT
This commit is contained in:
		
							
								
								
									
										63
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										364
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										33
									
								
								GitVersion.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								GitVersion.yml
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										6
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					Dev 
 | 
				
			||||||
 | 
					Master 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TomatenMusic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Project CI can be found [here](https://ci.tomatentum.net/project/TomatenMusicV2 "Tomatentum CI")
 | 
				
			||||||
							
								
								
									
										37
									
								
								TomatenMusic V2.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								TomatenMusic V2.sln
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										12
									
								
								TomatenMusic/.config/dotnet-tools.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								TomatenMusic/.config/dotnet-tools.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "version": 1,
 | 
				
			||||||
 | 
					  "isRoot": true,
 | 
				
			||||||
 | 
					  "tools": {
 | 
				
			||||||
 | 
					    "dotnet-ef": {
 | 
				
			||||||
 | 
					      "version": "6.0.3",
 | 
				
			||||||
 | 
					      "commands": [
 | 
				
			||||||
 | 
					        "dotnet-ef"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								TomatenMusic/Auth/Controllers/UsersController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								TomatenMusic/Auth/Controllers/UsersController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								TomatenMusic/Auth/Entities/User.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								TomatenMusic/Auth/Entities/User.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								TomatenMusic/Auth/Helpers/AppSettings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								TomatenMusic/Auth/Helpers/AppSettings.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					namespace TomatenMusic_Api.Auth.Helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class AppSettings
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string Secret { get; set; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								TomatenMusic/Auth/Helpers/AuthorizeAttribute.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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 };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								TomatenMusic/Auth/Helpers/JwtMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								TomatenMusic/Auth/Helpers/JwtMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								TomatenMusic/Auth/Models/AuthenticateRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								TomatenMusic/Auth/Models/AuthenticateRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								TomatenMusic/Auth/Models/AuthenticateResponse.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								TomatenMusic/Auth/Models/AuthenticateResponse.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								TomatenMusic/Auth/Services/UserService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								TomatenMusic/Auth/Services/UserService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										146
									
								
								TomatenMusic/Controllers/PlayerController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								TomatenMusic/Controllers/PlayerController.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								TomatenMusic/Models/BasicTrackInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								TomatenMusic/Models/BasicTrackInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								TomatenMusic/Models/ChannelConnectRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								TomatenMusic/Models/ChannelConnectRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								TomatenMusic/Models/ChannelDisconnectRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								TomatenMusic/Models/ChannelDisconnectRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					namespace TomatenMusic_Api.Models.EventArgs
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class ChannelDisconnectRequest
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public ulong GuildId { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								TomatenMusic/Models/EventArgs/ChannelConnectArgs.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								TomatenMusic/Models/EventArgs/ChannelDisconnectArgs.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								TomatenMusic/Models/EventArgs/TrackPlayArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								TomatenMusic/Models/EventArgs/TrackPlayArgs.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								TomatenMusic/Models/PlayerConnectionInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								TomatenMusic/Models/PlayerConnectionInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								TomatenMusic/Models/TrackPlayRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								TomatenMusic/Models/TrackPlayRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								TomatenMusic/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								TomatenMusic/Program.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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();
 | 
				
			||||||
							
								
								
									
										31
									
								
								TomatenMusic/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								TomatenMusic/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								TomatenMusic/Services/EventBus.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								TomatenMusic/Services/EventBus.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										91
									
								
								TomatenMusic/Services/TomatenMusicDataService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								TomatenMusic/Services/TomatenMusicDataService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								TomatenMusic/Services/TomatenMusicService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								TomatenMusic/Services/TomatenMusicService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								TomatenMusic/TomatenMusic.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								TomatenMusic/TomatenMusic.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
				
			||||||
							
								
								
									
										8
									
								
								TomatenMusic/appsettings.Development.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								TomatenMusic/appsettings.Development.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "Logging": {
 | 
				
			||||||
 | 
					    "LogLevel": {
 | 
				
			||||||
 | 
					      "Default": "Information",
 | 
				
			||||||
 | 
					      "Microsoft.AspNetCore": "Warning"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								TomatenMusic/appsettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								TomatenMusic/appsettings.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "AppSettings": {
 | 
				
			||||||
 | 
					    "Secret": "WWT9uwYzMkhOnUrZD7CSeT9forbwpbci"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					    "Logging": {
 | 
				
			||||||
 | 
					      "LogLevel": {
 | 
				
			||||||
 | 
					        "Default": "Debug",
 | 
				
			||||||
 | 
					        "Microsoft.AspNetCore": "Debug"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								TomatenMusic/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								TomatenMusic/config.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "TOKEN": "TOKEN",
 | 
				
			||||||
 | 
					  "LavaLinkPassword": " ",
 | 
				
			||||||
 | 
					  "SpotifyClientId": " ",
 | 
				
			||||||
 | 
					  "SpotifyClientSecret": " ",
 | 
				
			||||||
 | 
					  "YoutubeApiKey": " "
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										24
									
								
								TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								TomatenMusicCore/Commands/Checks/OnlyGuildCheck.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								TomatenMusicCore/Commands/Checks/UserInMusicChannelCheck.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								TomatenMusicCore/Commands/Checks/UserInVoiceChannelCheck.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										289
									
								
								TomatenMusicCore/Commands/MusicCommands.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								TomatenMusicCore/Commands/MusicCommands.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										327
									
								
								TomatenMusicCore/Commands/PlayCommandGroup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								TomatenMusicCore/Commands/PlayCommandGroup.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										72
									
								
								TomatenMusicCore/Music/Entitites/FullTrackContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								TomatenMusicCore/Music/Entitites/FullTrackContext.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								TomatenMusicCore/Music/Entitites/ILavalinkPlaylist.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								TomatenMusicCore/Music/Entitites/IPlayableItem.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								TomatenMusicCore/Music/Entitites/IPlayableItem.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										63
									
								
								TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								TomatenMusicCore/Music/Entitites/SpotifyPlaylist.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								TomatenMusicCore/Music/Entitites/TomatenMusicTrack.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										567
									
								
								TomatenMusicCore/Music/Entitites/TrackList.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										567
									
								
								TomatenMusicCore/Music/Entitites/TrackList.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								TomatenMusicCore/Music/Entitites/YoutubePlaylist.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										290
									
								
								TomatenMusicCore/Music/GuildPlayer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								TomatenMusicCore/Music/GuildPlayer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								TomatenMusicCore/Music/LoopType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								TomatenMusicCore/Music/LoopType.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								TomatenMusicCore/Music/MusicActionResponse.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								TomatenMusicCore/Music/MusicActionResponse.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										165
									
								
								TomatenMusicCore/Music/PlayerQueue.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								TomatenMusicCore/Music/PlayerQueue.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								TomatenMusicCore/Prompt/Buttons/AddToQueueButton.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								TomatenMusicCore/Prompt/Buttons/PlayNowButton.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										278
									
								
								TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								TomatenMusicCore/Prompt/Implementation/QueuePrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								TomatenMusicCore/Prompt/Implementation/SongActionPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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()));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										103
									
								
								TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								TomatenMusicCore/Prompt/Implementation/SongSelectorPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								TomatenMusicCore/Prompt/Model/ButtonPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								TomatenMusicCore/Prompt/Model/CombinedPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										499
									
								
								TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										499
									
								
								TomatenMusicCore/Prompt/Model/DiscordPromptBase.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										138
									
								
								TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								TomatenMusicCore/Prompt/Model/PaginatedButtonPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										160
									
								
								TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								TomatenMusicCore/Prompt/Model/PaginatedSelectPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								TomatenMusicCore/Prompt/Model/PromptState.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								TomatenMusicCore/Prompt/Model/PromptState.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace TomatenMusic.Prompt.Model
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    enum PromptState
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PREPARED,
 | 
				
			||||||
 | 
					        OPEN,
 | 
				
			||||||
 | 
					        INVALID,
 | 
				
			||||||
 | 
					        RESPONDED
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								TomatenMusicCore/Prompt/Model/SelectPrompt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								TomatenMusicCore/Prompt/Model/SelectPrompt.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								TomatenMusicCore/Prompt/Option/ButtonPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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) =>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								TomatenMusicCore/Prompt/Option/IPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								TomatenMusicCore/Prompt/Option/IPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								TomatenMusicCore/Prompt/Option/SelectMenuOption.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								TomatenMusicCore/Prompt/Option/SelectMenuPromptOption.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										196
									
								
								TomatenMusicCore/Services/SpotifyService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								TomatenMusicCore/Services/SpotifyService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										135
									
								
								TomatenMusicCore/Services/TrackProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								TomatenMusicCore/Services/TrackProvider.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										150
									
								
								TomatenMusicCore/Services/YoutubeService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								TomatenMusicCore/Services/YoutubeService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										204
									
								
								TomatenMusicCore/TomatenMusicBot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								TomatenMusicCore/TomatenMusicBot.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								TomatenMusicCore/TomatenMusicCore.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								TomatenMusicCore/TomatenMusicCore.csproj
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
				
			||||||
							
								
								
									
										56
									
								
								TomatenMusicCore/Util/CollectionUtil.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								TomatenMusicCore/Util/CollectionUtil.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										272
									
								
								TomatenMusicCore/Util/Common.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								TomatenMusicCore/Util/Common.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										68
									
								
								TomatenMusicCore/Util/PageManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								TomatenMusicCore/Util/PageManager.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								TomatenMusicCore/Util/RandomUtil.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								TomatenMusicCore/Util/RandomUtil.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user