├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── conversation-thread.md │ ├── feature-request.md │ └── improvement-request.md ├── .gitignore ├── EspionSpotify.FakeSpotify ├── App.config ├── EspionSpotify.FakeSpotify.csproj ├── EspionSpotify.FakeSpotify.csproj.DotSettings ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── frmSpotify.Designer.cs ├── frmSpotify.cs ├── frmSpotify.resx ├── icon.ico └── packages.config ├── EspionSpotify.Tests ├── EspionSpotify.Tests.csproj ├── ExtensionsTests.cs ├── FileManagerTests.cs ├── LastFMAPITests.cs ├── MapperID3Tests.cs ├── Properties │ └── AssemblyInfo.cs ├── RecorderTests.cs ├── SpotifyAPITests.cs ├── SpotifyHandlerTests.cs ├── SpotifyProcessTests.cs ├── SpotifyStatusTests.cs ├── TagLibTab.cs ├── TrackTests.cs ├── TranslationTests.cs ├── UserSettingTests.cs ├── WatcherTests.cs ├── app.config └── packages.config ├── EspionSpotify.Updater ├── App.config ├── EspionSpotify.Updater.csproj ├── Models │ └── GitHub │ │ ├── Asset.cs │ │ ├── Release.cs │ │ └── User.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Updater.cs ├── Utilities │ ├── AssemblyInfo.cs │ ├── CustomExceptions.cs │ ├── Extensions.cs │ ├── GitHub.cs │ └── Web.cs └── packages.config ├── EspionSpotify.sln ├── EspionSpotify ├── API │ ├── ExternalAPI.cs │ ├── IExternalAPI.cs │ ├── ILastFMAPI.cs │ ├── IMapperID3.cs │ ├── ISpotifyAPI.cs │ ├── LastFMAPI.cs │ ├── MapperID3.cs │ ├── NoneAPI.cs │ └── SpotifyAPI.cs ├── Analytics.cs ├── App.config ├── AudioSessions │ ├── AudioCircularBuffer.cs │ ├── AudioLoopback.cs │ ├── AudioMMDevicesManager.cs │ ├── AudioThrottler.cs │ ├── IAudioCircularBuffer.cs │ ├── IAudioLoopback.cs │ ├── IAudioThrottler.cs │ ├── IMainAudioSession.cs │ └── MainAudioSession.cs ├── Constants.cs ├── Controls │ ├── MetroComboBox.cs │ ├── MetroCredentialsForm.cs │ ├── MetroCredentialsForm.resx │ └── MetroTrackBar.cs ├── Drivers │ ├── AudioVirtualCableDriver.cs │ ├── VBCABLE_ControlPanel.exe │ ├── VBCABLE_Setup.exe │ ├── VBCABLE_Setup_x64.exe │ ├── pin_in.ico │ ├── pin_out.ico │ ├── readme.txt │ ├── vbMmeCable64_2003.inf │ ├── vbMmeCable64_vista.inf │ ├── vbMmeCable64_win7.inf │ ├── vbMmeCable_2003.inf │ ├── vbMmeCable_vista.inf │ ├── vbMmeCable_win7.inf │ ├── vbMmeCable_xp.inf │ ├── vbaudio_cable64_2003.cat │ ├── vbaudio_cable64_2003.sys │ ├── vbaudio_cable64_vista.cat │ ├── vbaudio_cable64_vista.sys │ ├── vbaudio_cable64_win7.cat │ ├── vbaudio_cable64_win7.sys │ ├── vbaudio_cable_2003.cat │ ├── vbaudio_cable_2003.sys │ ├── vbaudio_cable_vista.cat │ ├── vbaudio_cable_vista.sys │ ├── vbaudio_cable_win7.cat │ ├── vbaudio_cable_win7.sys │ ├── vbaudio_cable_xp.cat │ └── vbaudio_cable_xp.sys ├── Enums │ ├── AlbumCoverSize.cs │ ├── ExternalAPIType.cs │ ├── LanguageType.cs │ ├── LastFMNodeStatus.cs │ ├── MediaFormat.cs │ ├── OSVersions.cs │ ├── RecordRecordingsStatus.cs │ ├── SilenceAnalyzer.cs │ ├── TitleSeparatorType.cs │ ├── TranslationKeys.cs │ └── WaveFormatMP3Restriction.cs ├── EspionSpotify.csproj ├── Events │ ├── PlayStateEventArgs.cs │ ├── TrackChangeEventArgs.cs │ └── TrackTimeChangeEventArgs.cs ├── Exceptions │ ├── DestinationPathNotFoundException.cs │ └── SourceFileNotFoundException.cs ├── Extensions │ ├── ClassPropertyExtensions.cs │ ├── ColorExtensions.cs │ ├── ControlExtensions.cs │ ├── DictionaryExtensions.cs │ ├── JsonObjectExtensions.cs │ ├── LinqExtensions.cs │ ├── NAudioExtensions.cs │ ├── OperatingSystemExtensions.cs │ ├── ResourceManagerExtensions.cs │ ├── SettingExtensions.cs │ ├── SpotifyWebAPIExtensions.cs │ ├── StringExtensions.cs │ └── WaveFormatExtensions.cs ├── GitHub.cs ├── IFrmEspionSpotify.cs ├── IRecorder.cs ├── IWatcher.cs ├── Models │ ├── AudioWaveBuffer.cs │ ├── GitHub │ │ ├── Asset.cs │ │ ├── Release.cs │ │ └── User.cs │ ├── LastFMNode.cs │ ├── OutputFile.cs │ ├── RecorderTask.cs │ ├── SpotifyWindowInfo.cs │ ├── Track.cs │ └── UserSettings.cs ├── Native │ ├── Combase.cs │ ├── FileManager.cs │ ├── IProcessManager.cs │ ├── Models │ │ ├── HRESULT.cs │ │ ├── IProcess.cs │ │ └── Process.cs │ ├── NativeMethods.cs │ └── ProcessManager.cs ├── Normalize.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ ├── Settings.settings │ └── app.manifest ├── RESET_APP_SETTINGS.BAT ├── Recorder.cs ├── Resources │ ├── add_device.gif │ ├── clear.gif │ ├── faq.gif │ ├── folder.gif │ ├── heart.gif │ ├── key.gif │ ├── logo_en.gif │ ├── minus.gif │ ├── off.gif │ ├── on.gif │ ├── pause.gif │ ├── play.gif │ ├── plus.gif │ ├── record.gif │ ├── release.gif │ ├── remove_device.gif │ ├── spotify.gif │ ├── spytify-logo.png │ ├── spytify.ico │ ├── voldown.gif │ ├── volmute.gif │ └── volup.gif ├── Router │ ├── AudioRouter.cs │ ├── Helpers │ │ ├── AudioPolicyConfigFactory.cs │ │ ├── AudioPolicyConfigFactoryImplFor21H2.cs │ │ ├── AudioPolicyConfigFactoryImplForDownlevel.cs │ │ └── IAudioPolicyConfigFactory.cs │ ├── IAudioPolicyConfigFactory21H2.cs │ ├── IAudioPolicyConfigFactoryDownlevel.cs │ └── IAudioRouter.cs ├── Settings.cs ├── Spotify │ ├── ISpotifyHandler.cs │ ├── ISpotifyProcess.cs │ ├── ISpotifyStatus.cs │ ├── SpotifyConnect.cs │ ├── SpotifyHandler.cs │ ├── SpotifyProcess.cs │ └── SpotifyStatus.cs ├── Translations │ ├── I18nKeys.cs │ ├── Languages.cs │ ├── README.MD │ ├── en.Designer.cs │ ├── en.resx │ ├── fr.Designer.cs │ └── fr.resx ├── Updater.cs ├── Watcher.cs ├── frmEspionSpotify.Designer.cs ├── frmEspionSpotify.cs ├── frmEspionSpotify.resx ├── libmp3lame.32.dll ├── libmp3lame.64.dll ├── packages.config └── setup-spotify-api.txt ├── LICENSE ├── README.md ├── appveyor.yml └── psd ├── features_icons.psd ├── icons.psd ├── logo-en.png ├── logo-fr.png ├── logo.png ├── logo256px.png ├── logos_sp.psd ├── on-off.psd ├── spytify-espion-spotify-logo-small.png ├── spytify-espion-spotify-logo-small.psd └── spytify-espion-spotify-logo.psd /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS0618: Type or member is obsolete 4 | dotnet_diagnostic.CS0618.severity = silent 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: jwallet 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['jwallet.github.io/spy-spotify/donate'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help fix Spytify 4 | title: '' 5 | labels: "bug \U0001F41E" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | **Track used** 15 | 18 | 19 | **Screenshots** 20 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/conversation-thread.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Conversation thread 3 | about: Create a new conversation thread that will be moved to the Discussions section under QA. 4 | title: '' 5 | labels: "question \U0001F4AC" 6 | assignees: '' 7 | 8 | --- 9 | 10 | You can now use the Discussions section 11 | https://github.com/jwallet/spy-spotify/discussions 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an new feature to improve Spytify that will be moved to the Discussions section under Idea 4 | title: '' 5 | labels: feature ✨ 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/improvement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Improvement request 3 | about: Suggest an enhancement of a current feature that will be moved to the Discussions section under Idea 4 | title: '' 5 | labels: task ⚒ 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The following command works for downloading when using Git for Windows: 2 | # curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 3 | # 4 | # Download this file using PowerShell v3 under Windows with the following comand: 5 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore 6 | # 7 | # or wget: 8 | # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.sln.docstates 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | # build folder is nowadays used for build scripts and should not be ignored 22 | #build/ 23 | 24 | # NuGet Packages 25 | *.nupkg 26 | # The packages folder can be ignored because of Package Restore 27 | **/packages/* 28 | # except build/, which is used as an MSBuild target. 29 | !**/packages/build/ 30 | # Uncomment if necessary however generally it will be regenerated when needed 31 | #!**/packages/repositories.config 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | _site/ 38 | .jekyll-cache/ 39 | 40 | *_i.c 41 | *_p.c 42 | *.ilk 43 | *.meta 44 | *.obj 45 | *.pch 46 | *.pdb 47 | *.pgc 48 | *.pgd 49 | *.rsp 50 | *.sbr 51 | *.tlb 52 | *.tli 53 | *.tlh 54 | *.tmp 55 | *.tmp_proj 56 | *.log 57 | *.vspscc 58 | *.vssscc 59 | .builds 60 | *.pidb 61 | *.log 62 | *.scc 63 | 64 | # OS generated files # 65 | .DS_Store* 66 | Icon? 67 | 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opensdf 73 | *.sdf 74 | *.cachefile 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | *.vspx 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | 88 | # TeamCity is a build add-in 89 | _TeamCity* 90 | 91 | # DotCover is a Code Coverage Tool 92 | *.dotCover 93 | 94 | # NCrunch 95 | *.ncrunch* 96 | .*crunch*.local.xml 97 | 98 | # Installshield output folder 99 | [Ee]xpress/ 100 | 101 | # DocProject is a documentation generator add-in 102 | DocProject/buildhelp/ 103 | DocProject/Help/*.HxT 104 | DocProject/Help/*.HxC 105 | DocProject/Help/*.hhc 106 | DocProject/Help/*.hhk 107 | DocProject/Help/*.hhp 108 | DocProject/Help/Html2 109 | DocProject/Help/html 110 | 111 | # Click-Once directory 112 | publish/ 113 | 114 | # Publish Web Output 115 | *.Publish.xml 116 | 117 | # Windows Azure Build Output 118 | csx 119 | *.build.csdef 120 | 121 | # Windows Store app package directory 122 | AppPackages/ 123 | 124 | # Others 125 | *.Cache 126 | ClientBin/ 127 | [Ss]tyle[Cc]op.* 128 | ~$* 129 | *~ 130 | *.dbmdl 131 | *.[Pp]ublish.xml 132 | *.pfx 133 | *.publishsettings 134 | modulesbin/ 135 | tempbin/ 136 | 137 | # EPiServer Site file (VPP) 138 | AppData/ 139 | 140 | # RIA/Silverlight projects 141 | Generated_Code/ 142 | 143 | # Backup & report files from converting an old project file to a newer 144 | # Visual Studio version. Backup files are not needed, because we have git ;-) 145 | _UpgradeReport_Files/ 146 | Backup*/ 147 | UpgradeLog*.XML 148 | UpgradeLog*.htm 149 | 150 | # vim 151 | *.txt~ 152 | *.swp 153 | *.swo 154 | 155 | # Temp files when opening LibreOffice on ubuntu 156 | .~lock.* 157 | 158 | # svn 159 | .svn 160 | 161 | # CVS - Source Control 162 | **/CVS/ 163 | 164 | # Remainings from resolving conflicts in Source Control 165 | *.orig 166 | 167 | # SQL Server files 168 | **/App_Data/*.mdf 169 | **/App_Data/*.ldf 170 | **/App_Data/*.sdf 171 | 172 | 173 | #LightSwitch generated files 174 | GeneratedArtifacts/ 175 | _Pvt_Extensions/ 176 | ModelManifest.xml 177 | 178 | # ========================= 179 | # Windows detritus 180 | # ========================= 181 | 182 | # Windows image file caches 183 | Thumbs.db 184 | ehthumbs.db 185 | 186 | # Folder config file 187 | Desktop.ini 188 | 189 | # Recycle Bin used on file shares 190 | $RECYCLE.BIN/ 191 | 192 | # Mac desktop service store files 193 | .DS_Store 194 | 195 | # SASS Compiler cache 196 | .sass-cache 197 | 198 | # Visual Studio 2014 CTP 199 | **/*.sln.ide 200 | 201 | # Visual Studio temp something 202 | .vs/ 203 | 204 | # dotnet stuff 205 | project.lock.json 206 | 207 | # VS 2015+ 208 | *.vc.vc.opendb 209 | *.vc.db 210 | 211 | # Rider 212 | .idea/ 213 | 214 | # Visual Studio Code 215 | .vscode/ 216 | 217 | # Output folder used by Webpack or other FE stuff 218 | **/node_modules/* 219 | **/wwwroot/* 220 | 221 | # SpecFlow specific 222 | *.feature.cs 223 | *.feature.xlsx.* 224 | *.Specs_*.html 225 | 226 | ##### 227 | # End of core ignore list, below put you custom 'per project' settings (patterns or path) 228 | ##### 229 | 230 | docs/* -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/EspionSpotify.FakeSpotify.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | D:\dev\spy-spotify\packages\NAudio.1.10.0\lib\net35\NAudio.dll|*D:\dev\spy-spotify\packages\MetroModernUI.1.4.0.0\lib\net\MetroFramework.dll|MetroFramework.Components.MetroStyleExtender;MetroFramework.Components.MetroStyleManager;MetroFramework.Components.MetroToolTip;MetroFramework.Controls.MetroButton;MetroFramework.Controls.MetroCheckBox;MetroFramework.Controls.MetroComboBox;MetroFramework.Controls.MetroContextMenu;MetroFramework.Controls.MetroDateTime;MetroFramework.Controls.MetroGrid;MetroFramework.Controls.MetroLabel;MetroFramework.Controls.MetroLink;MetroFramework.Controls.MetroListView;MetroFramework.Controls.MetroPanel;MetroFramework.Controls.MetroProgressBar;MetroFramework.Controls.MetroProgressSpinner;MetroFramework.Controls.MetroRadioButton;MetroFramework.Controls.MetroScrollBar;MetroFramework.Controls.MetroTabControl;MetroFramework.Controls.MetroTextBox;MetroFramework.Controls.MetroTile;MetroFramework.Controls.MetroToggle;MetroFramework.Controls.MetroTrackBar;MetroFramework.Controls.MetroUserControl;MetroFramework.Drawing.Html.HtmlPanel;MetroFramework.Drawing.Html.HtmlLabel;MetroFramework.Drawing.Html.HtmlToolTip -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Windows.Forms; 4 | 5 | namespace EspionSpotify.FakeSpotify 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main() 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new FrmSpotify()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Spotify")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Spotify")] 13 | [assembly: AssemblyCopyright("Copyright © 2022")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("AC6E7B02-7941-40B2-88D0-C53B50073073")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace EspionSpotify.FakeSpotify.Properties 12 | { 13 | /// 14 | /// A strongly-typed resource class, for looking up localized strings, etc. 15 | /// 16 | // This class was auto-generated by the StronglyTypedResourceBuilder 17 | // class via a tool like ResGen or Visual Studio. 18 | // To add or remove a member, edit your .ResX file then rerun ResGen 19 | // with the /str option, or rebuild your VS project. 20 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", 21 | "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources 25 | { 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", 31 | "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() 33 | { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState 40 | .Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = 48 | new global::System.Resources.ResourceManager("EspionSpotify.FakeSpotify.Properties.Resources", 49 | typeof(Resources).Assembly); 50 | resourceMan = temp; 51 | } 52 | 53 | return resourceMan; 54 | } 55 | } 56 | 57 | /// 58 | /// Overrides the current thread's CurrentUICulture property for all 59 | /// resource lookups using this strongly typed resource class. 60 | /// 61 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState 62 | .Advanced)] 63 | internal static global::System.Globalization.CultureInfo Culture 64 | { 65 | get { return resourceCulture; } 66 | set { resourceCulture = value; } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace EspionSpotify.FakeSpotify.Properties 12 | { 13 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 14 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute( 15 | "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 17 | { 18 | private static Settings defaultInstance = 19 | ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 20 | 21 | public static Settings Default 22 | { 23 | get { return defaultInstance; } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify.FakeSpotify/icon.ico -------------------------------------------------------------------------------- /EspionSpotify.FakeSpotify/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /EspionSpotify.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("EspionSpotify.Tests")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("EspionSpotify.Tests")] 9 | [assembly: AssemblyCopyright("Copyright © 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | 15 | [assembly: Guid("9a936c14-6665-48a8-a79d-14347f50e215")] 16 | 17 | // [assembly: AssemblyVersion("1.0.*")] 18 | [assembly: AssemblyVersion("1.0.0.0")] 19 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /EspionSpotify.Tests/SpotifyStatusTests.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Extensions; 2 | using EspionSpotify.Models; 3 | using EspionSpotify.Spotify; 4 | using Xunit; 5 | 6 | namespace EspionSpotify.Tests 7 | { 8 | public class SpotifyStatusTests 9 | { 10 | [Theory] 11 | [InlineData(null, true)] 12 | [InlineData("", true)] 13 | [InlineData("Artist Name - Song Title", false)] 14 | [InlineData(Constants.ADVERTISEMENT, false)] 15 | [InlineData(Constants.SPOTIFY, true)] 16 | [InlineData(Constants.SPOTIFYFREE, true)] 17 | [InlineData(Constants.SPOTIFYPREMIUM, true)] 18 | [InlineData("SPOTIFY", true)] 19 | [InlineData("spotify", true)] 20 | internal void SpotifyStatusWindowTitleIsSpotify_ReturnsWhenItMatches(string value, bool expected) 21 | { 22 | var actual = value.IsNullOrSpotifyIdleState(); 23 | 24 | Assert.Equal(expected, actual); 25 | } 26 | 27 | [Fact] 28 | internal void SpotifyStatusSpotifyStandingBy_ReturnsExpectingTrack() 29 | { 30 | var expectedTrack = new Track 31 | { 32 | Artist = Constants.SPOTIFY 33 | }; 34 | var spotifyWindowInfo = new SpotifyWindowInfo 35 | { 36 | WindowTitle = Constants.SPOTIFY, 37 | IsPlaying = false 38 | }; 39 | 40 | var status = new SpotifyStatus(spotifyWindowInfo); 41 | 42 | Assert.Equal(expectedTrack, status.CurrentTrack); 43 | Assert.Equal(Constants.SPOTIFY, status.CurrentTrack.ToString()); 44 | } 45 | 46 | [Theory] 47 | [InlineData("Artist Name - Song Title", false)] 48 | [InlineData("Artist Name - Song Title - Live", true)] 49 | internal void SpotifyStatusTrackPlaying_ReturnsExpectingTrack(string windowTitle, bool isTitleExtended) 50 | { 51 | var expectedTrack = new Track 52 | { 53 | Artist = "Artist Name", 54 | Title = "Song Title", 55 | TitleExtended = isTitleExtended ? "Live" : "", 56 | Playing = true, 57 | Ad = false 58 | }; 59 | var spotifyWindowInfo = new SpotifyWindowInfo 60 | { 61 | WindowTitle = windowTitle, 62 | IsPlaying = true 63 | }; 64 | 65 | var status = new SpotifyStatus(spotifyWindowInfo); 66 | 67 | Assert.Equal(expectedTrack, status.CurrentTrack); 68 | Assert.Equal(windowTitle, status.CurrentTrack.ToString()); 69 | } 70 | 71 | [Theory] 72 | [InlineData(Constants.SPOTIFY)] 73 | [InlineData("Spotify Sponsor")] 74 | [InlineData("#1337: DAILY NEWS")] 75 | internal void SpotifyStatusSpotifyPlayingAdOrUnknown_ReturnsExpectingTrack(string windowTitle) 76 | { 77 | var expectedTrack = new Track 78 | { 79 | Artist = windowTitle, 80 | Title = null, 81 | TitleExtended = null, 82 | Playing = true, 83 | Ad = true 84 | }; 85 | var spotifyWindowInfo = new SpotifyWindowInfo 86 | { 87 | WindowTitle = windowTitle, 88 | IsPlaying = true 89 | }; 90 | 91 | var status = new SpotifyStatus(spotifyWindowInfo); 92 | 93 | Assert.Equal(expectedTrack, status.CurrentTrack); 94 | Assert.Equal(expectedTrack.ToString(), status.CurrentTrack.ToString()); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /EspionSpotify.Tests/TagLibTab.cs: -------------------------------------------------------------------------------- 1 | using TagLib; 2 | 3 | namespace EspionSpotify.Tests 4 | { 5 | public class TagLibTab : Tag 6 | { 7 | public override TagTypes TagTypes { get; } 8 | 9 | public override uint Track { get; set; } 10 | 11 | public override string Title { get; set; } 12 | public override string Subtitle { get; set; } 13 | public override string[] AlbumArtists { get; set; } 14 | public override string[] Performers { get; set; } 15 | 16 | public override string Album { get; set; } 17 | public override string[] Genres { get; set; } 18 | 19 | public override uint Year { get; set; } 20 | public override uint Disc { get; set; } 21 | 22 | public override IPicture[] Pictures { get; set; } 23 | 24 | public override void Clear() 25 | { 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /EspionSpotify.Tests/TrackTests.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Enums; 2 | using EspionSpotify.Models; 3 | using Xunit; 4 | 5 | namespace EspionSpotify.Tests 6 | { 7 | public class TrackTests 8 | { 9 | [Fact] 10 | internal void DefaultTrack_ReturnsEmptyTrack() 11 | { 12 | var track = new Track(); 13 | 14 | Assert.False(track.IsNormalPlaying); 15 | Assert.Equal("Spotify", track.ToString()); 16 | } 17 | 18 | [Fact] 19 | internal void MinimalTrack_ReturnsBasicInfo() 20 | { 21 | var track = new Track 22 | { 23 | Title = "Song Title", 24 | Artist = "Artist Name", 25 | Ad = false, 26 | Playing = true, 27 | TitleExtended = "Live", 28 | TitleExtendedSeparatorType = TitleSeparatorType.Dash 29 | }; 30 | 31 | Assert.True(track.IsNormalPlaying); 32 | Assert.Equal("Artist Name - Song Title - Live", track.ToString()); 33 | Assert.NotEqual(track, new Track()); 34 | } 35 | 36 | [Theory] 37 | [InlineData("A", "B", false, true, true)] 38 | [InlineData("A", "", false, true, false)] 39 | [InlineData("", "B", false, true, false)] 40 | [InlineData("", "", false, true, false)] 41 | [InlineData(null, null, false, true, false)] 42 | [InlineData("A", "B", true, true, false)] 43 | [InlineData("A", "B", false, false, false)] 44 | internal void IsNormal_ReturnsExpectedNormalResults(string artist, string title, bool ad, bool playing, 45 | bool expected) 46 | { 47 | var track = new Track {Artist = artist, Title = title, Ad = ad, Playing = playing}; 48 | 49 | Assert.Equal(expected, track.IsNormalPlaying); 50 | } 51 | 52 | [Fact] 53 | internal void TrackWithInitialTrack_ReturnsUpdatedTrack() 54 | { 55 | var initialTrack = new Track 56 | { 57 | Title = "Song Title", 58 | Artist = "Artist Name", 59 | Ad = false, 60 | Playing = true, 61 | TitleExtended = "Live", 62 | TitleExtendedSeparatorType = TitleSeparatorType.Dash 63 | }; 64 | 65 | var track = new Track(initialTrack) 66 | { 67 | Album = "Album", 68 | AlbumPosition = 1, 69 | AlbumArtUrl = "http://logo.png" 70 | }; 71 | 72 | Assert.Equal(initialTrack.ToString(), track.ToString()); 73 | Assert.NotEqual(initialTrack.Album, track.Album); 74 | Assert.NotEqual(initialTrack.AlbumArtUrl, track.AlbumArtUrl); 75 | Assert.NotEqual(track, new Track()); 76 | } 77 | 78 | [Fact] 79 | internal void ToTitleString_ReturnsTitleAndExtendedTitle() 80 | { 81 | Assert.Equal("", new Track().ToTitleString()); 82 | 83 | Assert.Equal( 84 | "Title", 85 | new Track 86 | { 87 | Title = "Title", 88 | Artist = "Artist", 89 | TitleExtended = "Must Not Return", 90 | TitleExtendedSeparatorType = TitleSeparatorType.None 91 | }.ToTitleString()); 92 | 93 | Assert.Equal( 94 | "Title - Remastered", 95 | new Track 96 | { 97 | Title = "Title", 98 | Artist = "Artist", 99 | TitleExtended = "Remastered", 100 | TitleExtendedSeparatorType = TitleSeparatorType.Dash 101 | }.ToTitleString()); 102 | 103 | Assert.Equal( 104 | "Title (Featuring Other)", 105 | new Track 106 | { 107 | Title = "Title", 108 | Artist = "Artist", 109 | TitleExtended = "Featuring Other", 110 | TitleExtendedSeparatorType = TitleSeparatorType.Parenthesis 111 | }.ToTitleString()); 112 | 113 | Assert.Equal( 114 | "Title (Featuring Other) - Remastered", 115 | new Track 116 | { 117 | Title = "Title (Featuring Other)", 118 | Artist = "Artist", 119 | TitleExtended = "Remastered", 120 | TitleExtendedSeparatorType = TitleSeparatorType.Dash 121 | }.ToTitleString()); 122 | } 123 | 124 | [Fact] 125 | internal void TrackEquals_ReturnsAsExpected() 126 | { 127 | var trackEmpty = new Track(); 128 | var trackDetailed = new Track 129 | { 130 | Title = "Title", 131 | Artist = "Artist", 132 | TitleExtended = "", 133 | Ad = false 134 | }; 135 | 136 | Assert.True(trackEmpty.Equals(new Track())); 137 | Assert.True(trackDetailed.Equals(new Track {Title = "Title", Artist = "Artist"})); 138 | 139 | Assert.False(trackEmpty.Equals(null)); 140 | Assert.False(trackEmpty.Equals(new OutputFile())); 141 | Assert.False(trackEmpty.Equals(new Track {Title = "Title"})); 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /EspionSpotify.Tests/TranslationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Resources; 6 | using EspionSpotify.Enums; 7 | using EspionSpotify.Extensions; 8 | using EspionSpotify.Translations; 9 | using Xunit; 10 | 11 | namespace EspionSpotify.Tests 12 | { 13 | public class TranslationTests 14 | { 15 | private static ResourceManager RM; 16 | private readonly Type _en; 17 | private readonly Type _fr; 18 | private readonly int _keysCount; 19 | 20 | public TranslationTests() 21 | { 22 | _en = Languages.GetResourcesManagerLanguageType(LanguageType.en); 23 | _fr = Languages.GetResourcesManagerLanguageType(LanguageType.fr); 24 | 25 | _keysCount = Enum.GetNames(typeof(TranslationKeys)).Length; 26 | } 27 | 28 | [Fact] 29 | internal void TranslationSetup_ShouldBeReady() 30 | { 31 | Assert.NotNull(_en); 32 | Assert.NotNull(_fr); 33 | } 34 | 35 | private void ShouldGetTranslations(ResourceManager RM) 36 | { 37 | var resourceSet = RM.GetResourceSet(CultureInfo.InvariantCulture, true, true); 38 | var count = 0; 39 | 40 | foreach (DictionaryEntry o in resourceSet) 41 | { 42 | count++; 43 | var actual = (string) o.Key; 44 | var expected = actual.ToEnum(false)?.ToString(); 45 | Assert.Equal(expected, actual); 46 | } 47 | 48 | Assert.Equal(count, _keysCount); 49 | } 50 | 51 | [Fact] 52 | internal void English_ShouldGetTranslations() 53 | { 54 | ShouldGetTranslations(new ResourceManager(_en)); 55 | } 56 | 57 | [Fact] 58 | internal void French_ShouldGetTranslations() 59 | { 60 | ShouldGetTranslations(new ResourceManager(_fr)); 61 | } 62 | 63 | [Fact] 64 | internal void Unsupported_ShouldNotGetTranslations() 65 | { 66 | RM = new ResourceManager(typeof(Languages)); 67 | Assert.Throws(() => 68 | RM.GetResourceSet(CultureInfo.InvariantCulture, true, true)); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /EspionSpotify.Tests/UserSettingTests.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Models; 2 | using Xunit; 3 | 4 | namespace EspionSpotify.Tests 5 | { 6 | public class UserSettingTests 7 | { 8 | [Fact] 9 | internal void DefaultUserSettings_ReturnsDefault() 10 | { 11 | var userSettings = new UserSettings(); 12 | 13 | Assert.Equal(1, userSettings.InternalOrderNumber); 14 | Assert.False(userSettings.HasRecordingTimerEnabled); 15 | Assert.Equal(0.0, userSettings.RecordingTimerMilliseconds); 16 | Assert.Null(userSettings.OrderNumberAsFile); 17 | Assert.Null(userSettings.OrderNumberAsTag); 18 | Assert.False(userSettings.IsSpotifyAPISet); 19 | } 20 | 21 | [Fact] 22 | internal void RecordingTimer_ReturnsRecordingTimerEnabled() 23 | { 24 | Assert.False(new UserSettings {RecordingTimer = ""}.HasRecordingTimerEnabled); 25 | Assert.False(new UserSettings {RecordingTimer = "1337"}.HasRecordingTimerEnabled); 26 | Assert.False(new UserSettings {RecordingTimer = "000000"}.HasRecordingTimerEnabled); 27 | 28 | Assert.True(new UserSettings {RecordingTimer = "001337"}.HasRecordingTimerEnabled); 29 | } 30 | 31 | [Fact] 32 | internal void RecordingTimer_ReturnsRecordingTimerMilliseconds() 33 | { 34 | Assert.Equal(0, new UserSettings {RecordingTimer = "000000"}.RecordingTimerMilliseconds); 35 | Assert.Equal(1000, new UserSettings {RecordingTimer = "000001"}.RecordingTimerMilliseconds); 36 | Assert.Equal(817000, new UserSettings {RecordingTimer = "001337"}.RecordingTimerMilliseconds); 37 | Assert.Equal(43200000, new UserSettings {RecordingTimer = "120000"}.RecordingTimerMilliseconds); 38 | // invalid value to passed 39 | Assert.Equal(362439000, new UserSettings {RecordingTimer = "999999"}.RecordingTimerMilliseconds); 40 | } 41 | 42 | [Fact] 43 | internal void OrderNumberMask_ReturnsMaskAsMax() 44 | { 45 | Assert.Equal(99999, new UserSettings {OrderNumberMask = "00000"}.OrderNumberMax); 46 | } 47 | 48 | [Fact] 49 | internal void HasOrderNumberEnabled_ReturnsAsExpected() 50 | { 51 | Assert.True(new UserSettings {OrderNumberInfrontOfFileEnabled = true}.HasOrderNumberEnabled); 52 | Assert.True(new UserSettings {OrderNumberInMediaTagEnabled = true}.HasOrderNumberEnabled); 53 | Assert.False(new UserSettings().HasOrderNumberEnabled); 54 | } 55 | 56 | [Fact] 57 | internal void InternalOrderNumber_ReturnsOrderNumberAsFile() 58 | { 59 | Assert.Equal(1, new UserSettings {OrderNumberInfrontOfFileEnabled = true}.OrderNumberAsFile); 60 | } 61 | 62 | [Fact] 63 | internal void InternalOrderNumber_ReturnsOrderNumberAsTag() 64 | { 65 | Assert.Equal(1, new UserSettings {OrderNumberInMediaTagEnabled = true}.OrderNumberAsTag); 66 | } 67 | 68 | [Fact] 69 | internal void SpotifyAPISecrets_ReturnsIsSpotifyAPISet() 70 | { 71 | Assert.False(new UserSettings {SpotifyAPIClientId = "", SpotifyAPISecretId = ""}.IsSpotifyAPISet); 72 | Assert.False(new UserSettings {SpotifyAPIClientId = "abc123", SpotifyAPISecretId = ""}.IsSpotifyAPISet); 73 | Assert.False(new UserSettings {SpotifyAPIClientId = "", SpotifyAPISecretId = "abc123"}.IsSpotifyAPISet); 74 | Assert.True(new UserSettings {SpotifyAPIClientId = "abc123", SpotifyAPISecretId = "abc123"} 75 | .IsSpotifyAPISet); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /EspionSpotify.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /EspionSpotify.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /EspionSpotify.Updater/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EspionSpotify.Updater/EspionSpotify.Updater.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F20370C5-7613-452E-8FCD-06F7F5703C35} 8 | Exe 9 | EspionSpotify.Updater 10 | Updater 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | ..\EspionSpotify\bin\Debug\Updater\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | ..\EspionSpotify\bin\Release\Updater\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\DotNetZip.1.11.0\lib\net20\DotNetZip.dll 38 | 39 | 40 | ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /EspionSpotify.Updater/Models/GitHub/Asset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Updater.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class Asset 7 | { 8 | public string url { get; set; } 9 | public int id { get; set; } 10 | public string node_id { get; set; } 11 | public string name { get; set; } 12 | public string lable { get; set; } 13 | public User uploader { get; set; } 14 | public string content_type { get; set; } 15 | public string state { get; set; } 16 | public int? size { get; set; } 17 | public int? download_count { get; set; } 18 | public DateTime? created_at { get; set; } 19 | public DateTime? updated_at { get; set; } 20 | public string browser_download_url { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Models/GitHub/Release.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Updater.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class Release 7 | { 8 | public string url { get; set; } 9 | public string assets_url { get; set; } 10 | public string upload_url { get; set; } 11 | public string html_url { get; set; } 12 | public int id { get; set; } 13 | public string node_id { get; set; } 14 | public string tag_name { get; set; } 15 | public string target_commitish { get; set; } 16 | public bool draft { get; set; } 17 | public User author { get; set; } 18 | public bool prerelease { get; set; } 19 | public DateTime? created_at { get; set; } 20 | public DateTime? published_at { get; set; } 21 | public Asset[] assets { get; set; } 22 | public string tarball_url { get; set; } 23 | public string zipball_url { get; set; } 24 | public string body { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Models/GitHub/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Updater.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class User 7 | { 8 | public string login { get; set; } 9 | public int id { get; set; } 10 | public string node_id { get; set; } 11 | public string avatar_url { get; set; } 12 | public string gravatar_id { get; set; } 13 | public string url { get; set; } 14 | public string html_url { get; set; } 15 | public string followers_url { get; set; } 16 | public string following_url { get; set; } 17 | public string gists_url { get; set; } 18 | public string starred_url { get; set; } 19 | public string subscriptions_url { get; set; } 20 | public string organizations_url { get; set; } 21 | public string repos_url { get; set; } 22 | public string events_url { get; set; } 23 | public string received_events_url { get; set; } 24 | public string type { get; set; } 25 | public bool site_admin { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace EspionSpotify.Updater 4 | { 5 | public class Program 6 | { 7 | internal static void Main() 8 | { 9 | var t = new Thread(Run); 10 | t.SetApartmentState(ApartmentState.STA); 11 | t.IsBackground = true; 12 | t.Start(); 13 | t.Join(); 14 | } 15 | 16 | internal static void Run() 17 | { 18 | Updater.ProcessUpdateAsync().GetAwaiter().GetResult(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die einer Assembly zugeordnet sind. 7 | [assembly: AssemblyTitle("EspionSpotify.Updater")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("EspionSpotify.Updater")] 12 | [assembly: AssemblyCopyright("Copyright © 2021")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 17 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("f20370c5-7613-452e-8fcd-06f7f5703c35")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 32 | // indem Sie "*" wie unten gezeigt eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /EspionSpotify.Updater/Updater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using EspionSpotify.Updater.Models.GitHub; 7 | using EspionSpotify.Updater.Utilities; 8 | using Ionic.Zip; 9 | 10 | namespace EspionSpotify.Updater 11 | { 12 | internal static class Updater 13 | { 14 | private const string UPDATER_DIRECTORY = "Updater"; 15 | private const string UPDATER_TMP_DIRECTORY = "tmp_updater"; 16 | private const string APP = "spytify.exe"; 17 | internal static readonly string ProjectDirectory = AppDomain.CurrentDomain.BaseDirectory + @"..\"; 18 | private static readonly string UpdaterTempFullPath = $"{ProjectDirectory}{UPDATER_TMP_DIRECTORY}"; 19 | internal static readonly string AppFullPath = $"{ProjectDirectory}{APP}"; 20 | 21 | internal static async Task ProcessUpdateAsync() 22 | { 23 | DeleteTempFiles(); 24 | 25 | try 26 | { 27 | var releases = await GitHub.GetReleases(); 28 | 29 | if (!releases.Any()) return; 30 | 31 | foreach (var release in releases) 32 | { 33 | Console.WriteLine("Updating to release {0}...", release.tag_name); 34 | var fileName = await DownloadUpdateAsync(release); 35 | if (!string.IsNullOrEmpty(fileName)) 36 | { 37 | ExtractDownloadedAsset(fileName); 38 | File.Delete(fileName); 39 | } 40 | } 41 | 42 | Console.WriteLine("Successfully updated!"); 43 | Process.Start(new ProcessStartInfo(AppFullPath)); 44 | } 45 | catch 46 | { 47 | Process.Start(new ProcessStartInfo(GitHub.LATEST_RELEASE_LINK)); 48 | LeaveConsole(); 49 | } 50 | finally 51 | { 52 | Environment.Exit(0); 53 | } 54 | } 55 | 56 | private static async Task DownloadUpdateAsync(Release release) 57 | { 58 | try 59 | { 60 | var asset = GitHub.GetZipAssetFromRelease(release); 61 | return await Web.DownloadFileAsync(asset.browser_download_url, release.tag_name); 62 | } 63 | catch (ReleaseAssetNotFoundException ex) 64 | { 65 | Console.WriteLine( 66 | "Something went wrong during the download. Make sure it was not blocked by your Antivirus Software."); 67 | Console.WriteLine("Error Message: {0}", ex.Message); 68 | throw; 69 | } 70 | catch (Exception ex) 71 | { 72 | Console.WriteLine("Something went wrong during the download."); 73 | Console.WriteLine("Error Message: {0}", ex.Message); 74 | throw; 75 | } 76 | } 77 | 78 | private static void ExtractDownloadedAsset(string fileName) 79 | { 80 | ZipFile zip = null; 81 | try 82 | { 83 | zip = ZipFile.Read(fileName); 84 | Console.WriteLine("Extracting..."); 85 | 86 | foreach (var entry in zip.ToList()) 87 | entry.Extract( 88 | (Path.GetDirectoryName(entry.FileName) ?? "").Equals(UPDATER_DIRECTORY) 89 | ? UpdaterTempFullPath 90 | : ProjectDirectory, ExtractExistingFileAction.OverwriteSilently); 91 | } 92 | catch (UnauthorizedAccessException ex) 93 | { 94 | Console.WriteLine( 95 | "An error occurred while extracting. Make sure Spytify is not running or Antivirus Software is interfering."); 96 | Console.WriteLine("You can extract the file manually at: {0}", fileName); 97 | Console.WriteLine("Error Message: {0}", ex.Message); 98 | throw; 99 | } 100 | catch (Exception ex) 101 | { 102 | Console.WriteLine("An error occurred while extracting"); 103 | Console.WriteLine("Error Message: {0}", ex.Message); 104 | throw; 105 | } 106 | finally 107 | { 108 | DeleteTempFiles(); 109 | zip?.Dispose(); 110 | } 111 | } 112 | 113 | internal static void LeaveConsole() 114 | { 115 | Console.WriteLine("Press any key to exit"); 116 | Console.ReadKey(); 117 | } 118 | 119 | private static void DeleteTempFiles() 120 | { 121 | foreach (var filename in Directory 122 | .EnumerateFiles(ProjectDirectory, "*.*", SearchOption.AllDirectories) 123 | .Where(s => new[] {".tmp", ".pending-overwrite"}.Any(ext => s.ToLower().EndsWith(ext)))) 124 | try 125 | { 126 | File.Delete(filename); 127 | } 128 | catch 129 | { 130 | // ignored 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Utilities/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace EspionSpotify.Updater.Utilities 6 | { 7 | internal class AssemblyInfo 8 | { 9 | internal static bool IsNewerVersionThanCurrent(string name) 10 | { 11 | var m = Regex.Match(name, @"^(\d+\.)?(\d+\.)?(\*|\d+\.)?(\*|\d+)$"); 12 | var version = m.Groups[0]; 13 | 14 | if (version.Success) 15 | { 16 | var availableVersion = new Version(version.Value); 17 | Version currentVersion; 18 | try 19 | { 20 | currentVersion = new Version(FileVersionInfo.GetVersionInfo(Updater.AppFullPath).FileVersion); 21 | } 22 | catch 23 | { 24 | return true; 25 | } 26 | 27 | return availableVersion > currentVersion; 28 | } 29 | 30 | return false; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Utilities/CustomExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Updater.Utilities 4 | { 5 | internal class ReleaseAssetNotFoundException : ApplicationException 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Utilities/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Updater.Utilities 4 | { 5 | internal static class LongExtensions 6 | { 7 | private const int BYTES_IN_MEGABYTE = 1_000_000; 8 | 9 | internal static string ToMo(this long bytes) 10 | { 11 | return Math.Round(bytes / (double) BYTES_IN_MEGABYTE, 2).ToString("0.00"); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Utilities/GitHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using System.Threading.Tasks; 5 | using EspionSpotify.Updater.Models.GitHub; 6 | using Newtonsoft.Json; 7 | 8 | namespace EspionSpotify.Updater.Utilities 9 | { 10 | internal class GitHub 11 | { 12 | internal const string API_LATEST_RELEASES_LINK = "https://api.github.com/repos/jwallet/spy-spotify/releases"; 13 | internal const string LATEST_RELEASE_LINK = "https://github.com/jwallet/spy-spotify/releases/latest"; 14 | 15 | internal static async Task GetReleases() 16 | { 17 | Console.WriteLine("Getting missing releases..."); 18 | 19 | var content = await Web.SendWebRequestAndGetContentAsync(API_LATEST_RELEASES_LINK); 20 | var releases = JsonConvert.DeserializeObject(content); 21 | 22 | if (releases == null) 23 | { 24 | Console.WriteLine("Releases were not found."); 25 | return null; 26 | } 27 | 28 | var relatedReleases = releases.Reverse().Where(r => AssemblyInfo.IsNewerVersionThanCurrent(r.tag_name)) 29 | .ToArray(); 30 | 31 | if (!relatedReleases.Any()) 32 | { 33 | Console.WriteLine("You are already using the latest version."); 34 | Updater.LeaveConsole(); 35 | } 36 | 37 | return relatedReleases; 38 | } 39 | 40 | internal static Asset GetZipAssetFromRelease(Release release) 41 | { 42 | var asset = release.assets 43 | .FirstOrDefault(a => Regex.IsMatch(a.name, @"^Spytify(-|\.)(v?)(\.)?\d+(\.\d+)?(\.\d+)?.zip$")); 44 | 45 | if (asset == null) 46 | { 47 | Console.WriteLine("This Release did not have any suitable asset to download. Try again later."); 48 | throw new ReleaseAssetNotFoundException(); 49 | } 50 | 51 | Console.WriteLine("Release asset found: {0}", asset.name); 52 | 53 | return asset; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/Utilities/Web.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace EspionSpotify.Updater.Utilities 9 | { 10 | internal class Web 11 | { 12 | internal const string USER_AGENT = 13 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"; 14 | 15 | internal static async Task DownloadFileAsync(string url, string tag) 16 | { 17 | var uri = new Uri(url); 18 | Console.WriteLine("Downloading update {0}...", tag); 19 | 20 | var fileName = $"{Updater.ProjectDirectory}update-{tag}.zip"; 21 | 22 | using (var wc = new WebClient()) 23 | { 24 | wc.DownloadProgressChanged += WebClient_DownloadProgressChanged; 25 | wc.DownloadFileCompleted += WebClient_DownloadFileCompleted; 26 | 27 | await wc.DownloadFileTaskAsync(uri, fileName); 28 | } 29 | 30 | return fileName; 31 | } 32 | 33 | internal static void WebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 34 | { 35 | lock (e.UserState) 36 | { 37 | Console.SetCursorPosition(0, Console.CursorTop - 1); 38 | Console.WriteLine("{0} % completed ({1} of {2} Mo.)", 39 | e.ProgressPercentage, 40 | e.BytesReceived.ToMo(), 41 | e.TotalBytesToReceive.ToMo()); 42 | } 43 | } 44 | 45 | internal static void WebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 46 | { 47 | if (e.Cancelled) Console.WriteLine("File download cancelled."); 48 | 49 | if (e.Error != null) Console.WriteLine(e.Error.ToString()); 50 | } 51 | 52 | internal static async Task SendWebRequestAndGetContentAsync(string address) 53 | { 54 | var result = string.Empty; 55 | var content = new MemoryStream(); 56 | var myHttpWebRequest = SetHttpWebRequest(address); 57 | myHttpWebRequest.Method = WebRequestMethods.Http.Get; 58 | 59 | try 60 | { 61 | using (var myHttpWebResponse = (HttpWebResponse) await myHttpWebRequest.GetResponseAsync()) 62 | { 63 | if (myHttpWebResponse.StatusCode == HttpStatusCode.OK) 64 | using (var reader = myHttpWebResponse.GetResponseStream()) 65 | { 66 | await reader.CopyToAsync(content); 67 | result = Encoding.UTF8.GetString(content.ToArray()); 68 | } 69 | } 70 | } 71 | catch 72 | { 73 | // ignored 74 | } 75 | finally 76 | { 77 | content.Dispose(); 78 | } 79 | 80 | return result; 81 | } 82 | 83 | private static HttpWebRequest SetHttpWebRequest(string address) 84 | { 85 | if (!Uri.TryCreate(address, UriKind.Absolute, out var uriGitHub)) return null; 86 | 87 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 88 | var request = (HttpWebRequest) WebRequest.Create(uriGitHub); 89 | request.Method = WebRequestMethods.Http.Get; 90 | request.UserAgent = "Spytify"; 91 | 92 | return request; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /EspionSpotify.Updater/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /EspionSpotify.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29609.76 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EspionSpotify", "EspionSpotify\EspionSpotify.csproj", "{A02571B8-7CE5-4865-84F0-87613F895204}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EspionSpotify.Tests", "EspionSpotify.Tests\EspionSpotify.Tests.csproj", "{9A936C14-6665-48A8-A79D-14347F50E215}" 8 | EndProject 9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3CD407CC-3558-4008-B207-D69B7BF19AC4}" 10 | ProjectSection(SolutionItems) = preProject 11 | .editorconfig = .editorconfig 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EspionSpotify.Updater", "EspionSpotify.Updater\EspionSpotify.Updater.csproj", "{F20370C5-7613-452E-8FCD-06F7F5703C35}" 15 | ProjectSection(ProjectDependencies) = postProject 16 | {A02571B8-7CE5-4865-84F0-87613F895204} = {A02571B8-7CE5-4865-84F0-87613F895204} 17 | EndProjectSection 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EspionSpotify.FakeSpotify", "EspionSpotify.FakeSpotify\EspionSpotify.FakeSpotify.csproj", "{AC6E7B02-7941-40B2-88D0-C53B50073073}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {A02571B8-7CE5-4865-84F0-87613F895204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {A02571B8-7CE5-4865-84F0-87613F895204}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {A02571B8-7CE5-4865-84F0-87613F895204}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {A02571B8-7CE5-4865-84F0-87613F895204}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {9A936C14-6665-48A8-A79D-14347F50E215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {9A936C14-6665-48A8-A79D-14347F50E215}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {9A936C14-6665-48A8-A79D-14347F50E215}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {9A936C14-6665-48A8-A79D-14347F50E215}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {F20370C5-7613-452E-8FCD-06F7F5703C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {F20370C5-7613-452E-8FCD-06F7F5703C35}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {F20370C5-7613-452E-8FCD-06F7F5703C35}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {F20370C5-7613-452E-8FCD-06F7F5703C35}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {AC6E7B02-7941-40B2-88D0-C53B50073073}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {AC6E7B02-7941-40B2-88D0-C53B50073073}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {AC6E7B02-7941-40B2-88D0-C53B50073073}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {AC6E7B02-7941-40B2-88D0-C53B50073073}.Release|Any CPU.Build.0 = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(SolutionProperties) = preSolution 45 | HideSolutionNode = FALSE 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {9853ED7B-8ABA-4853-8C7F-35E528FD136D} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /EspionSpotify/API/ExternalAPI.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.API 2 | { 3 | public static class ExternalAPI 4 | { 5 | public static IExternalAPI Instance { get; set; } = new NoneAPI(); 6 | } 7 | } -------------------------------------------------------------------------------- /EspionSpotify/API/IExternalAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EspionSpotify.Enums; 3 | using EspionSpotify.Models; 4 | 5 | namespace EspionSpotify.API 6 | { 7 | public interface IExternalAPI 8 | { 9 | ExternalAPIType GetTypeAPI { get; } 10 | bool IsAuthenticated { get; } 11 | Task Authenticate(); 12 | void Reset(); 13 | 14 | Task UpdateTrack(Track track); 15 | } 16 | } -------------------------------------------------------------------------------- /EspionSpotify/API/ILastFMAPI.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Models; 2 | 3 | namespace EspionSpotify.API 4 | { 5 | public interface ILastFMAPI 6 | { 7 | string[] ApiKeys { get; } 8 | 9 | void MapLastFMTrackToTrack(Track track, LastFMTrack trackExtra); 10 | } 11 | } -------------------------------------------------------------------------------- /EspionSpotify/API/IMapperID3.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EspionSpotify.Models; 3 | 4 | namespace EspionSpotify.API 5 | { 6 | public interface IMapperID3 7 | { 8 | string CurrentFile { get; set; } 9 | int? Count { get; set; } 10 | bool OrderNumberInMediaTagEnabled { get; set; } 11 | Track Track { get; set; } 12 | 13 | Task SaveMediaTags(); 14 | } 15 | } -------------------------------------------------------------------------------- /EspionSpotify/API/ISpotifyAPI.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Models; 2 | using SpotifyAPI.Web.Models; 3 | 4 | namespace EspionSpotify.API 5 | { 6 | public interface ISpotifyAPI 7 | { 8 | void MapSpotifyTrackToTrack(Track track, FullTrack spotifyTrack); 9 | 10 | void MapSpotifyAlbumToTrack(Track track, FullAlbum spotifyAlbum); 11 | } 12 | } -------------------------------------------------------------------------------- /EspionSpotify/API/NoneAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EspionSpotify.Enums; 3 | using EspionSpotify.Models; 4 | 5 | namespace EspionSpotify.API 6 | { 7 | public class NoneAPI: IExternalAPI 8 | { 9 | public ExternalAPIType GetTypeAPI => ExternalAPIType.None; 10 | public bool IsAuthenticated => true; 11 | public Task Authenticate() 12 | { 13 | return Task.CompletedTask; 14 | } 15 | 16 | public void Reset() 17 | { 18 | } 19 | 20 | public Task UpdateTrack(Track track) 21 | { 22 | return Task.FromResult(true); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /EspionSpotify/Analytics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using PCLWebUtility; 8 | 9 | namespace EspionSpotify 10 | { 11 | #region analytics 12 | 13 | public class Analytics 14 | { 15 | private const string ANALYTICS_URL = "https://www.google-analytics.com/collect"; 16 | private const string ANALYTICS_TID = "UA-125662919-1"; 17 | private readonly string _cid; 18 | 19 | private readonly HttpClient _client = new HttpClient(); 20 | private readonly string _cm; 21 | private readonly string _cs; 22 | private readonly string _sr; 23 | private readonly string _ua; 24 | private readonly string _ul; 25 | 26 | public Analytics(string clientId, string version) 27 | { 28 | var osArchitecture = Environment.Is64BitOperatingSystem ? "Win64; x64;" : ""; 29 | var screenBoundaries = Screen.PrimaryScreen.Bounds; 30 | var osPlatform = $"Windows NT {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}"; 31 | 32 | _cid = clientId; 33 | _cm = version; 34 | _cs = Environment.OSVersion.ToString(); 35 | _ul = CultureInfo.CurrentCulture.Name; 36 | _ua = WebUtility.UrlEncode( 37 | $"Mozilla/5.0 ({osPlatform}; {osArchitecture}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"); 38 | _sr = $"{screenBoundaries.Width}x{screenBoundaries.Height}"; 39 | } 40 | 41 | private DateTime LastRequest { get; set; } 42 | private string LastAction { get; set; } = string.Empty; 43 | 44 | public static string GenerateCid() 45 | { 46 | return Guid.NewGuid().ToString(); 47 | } 48 | 49 | public async Task LogAction(string action) 50 | { 51 | if (LastAction.Equals(action) && DateTime.Now - LastRequest < TimeSpan.FromMinutes(5)) return false; 52 | 53 | var data = new Dictionary 54 | { 55 | {"v", "1"}, 56 | {"tid", ANALYTICS_TID}, // App id 57 | {"t", "pageview"}, // Analytics type 58 | {"cid", _cid}, // Client id 59 | {"cm", _cm}, // Campaign medium, App version 60 | {"av", _cm}, // App version 61 | {"cn", Constants.SPYTIFY}, // Campaign name 62 | {"an", Constants.SPYTIFY}, // App name 63 | {"cs", WebUtility.UrlEncode(_cs)}, // Campaign source, OS Version 64 | {"ul", _ul}, // User Language 65 | {"sr", _sr}, // Screen resolution 66 | {"ua", _ua}, // User-Agent overwrite 67 | {"dh", "jwallet.github.io/spy-spotify"}, // Document host 68 | {"dl", $"/{action}"}, // Document link 69 | {"dt", action}, // Document title 70 | {"cd", action} // Screen name 71 | }; 72 | 73 | var content = new FormUrlEncodedContent(data); 74 | var success = false; 75 | 76 | try 77 | { 78 | var resp = await _client.PostAsync(ANALYTICS_URL, content); 79 | success = resp.IsSuccessStatusCode; 80 | } 81 | catch 82 | { 83 | // ignored 84 | } 85 | 86 | LastAction = action; 87 | LastRequest = DateTime.Now; 88 | 89 | return success; 90 | } 91 | } 92 | 93 | #endregion analytics 94 | } -------------------------------------------------------------------------------- /EspionSpotify/AudioSessions/AudioLoopback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NAudio.CoreAudioApi; 5 | using NAudio.Wave; 6 | 7 | namespace EspionSpotify.AudioSessions 8 | { 9 | public class AudioLoopback: IAudioLoopback, IDisposable 10 | { 11 | private readonly bool _canDo = false; 12 | private bool _isDisposed = false; 13 | 14 | private const int BUFFER_TOTAL_SIZE_MS = 10_000; 15 | 16 | private readonly BufferedWaveProvider _bufferedWaveProvider; 17 | private readonly WasapiLoopbackCapture _waveIn; 18 | private readonly WaveOut _waveOut; 19 | 20 | private CancellationTokenSource _cancellationTokenSource; 21 | 22 | public bool Running { get; set; } 23 | 24 | public AudioLoopback(MMDevice currentEndpointDevice, MMDevice defaultEndpointDevice) 25 | { 26 | _canDo = currentEndpointDevice.ID != defaultEndpointDevice.ID; 27 | 28 | _waveIn = new WasapiLoopbackCapture(currentEndpointDevice); 29 | _waveIn.DataAvailable += OnDataAvailable; 30 | 31 | _bufferedWaveProvider = new BufferedWaveProvider(_waveIn.WaveFormat) 32 | { 33 | DiscardOnBufferOverflow = true, 34 | BufferDuration = TimeSpan.FromMilliseconds(BUFFER_TOTAL_SIZE_MS) 35 | }; 36 | 37 | _waveOut = new WaveOut(); 38 | } 39 | 40 | public async Task Run(CancellationTokenSource cancellationTokenSource) 41 | { 42 | if (!_canDo) return; 43 | 44 | _cancellationTokenSource = cancellationTokenSource; 45 | Running = true; 46 | 47 | _waveIn.StartRecording(); 48 | _waveOut.Init(_bufferedWaveProvider); 49 | 50 | _waveOut.Play(); 51 | 52 | while (Running) 53 | { 54 | if (_cancellationTokenSource.IsCancellationRequested) return; 55 | await Task.Delay(100); 56 | } 57 | 58 | _waveOut.Stop(); 59 | _waveIn.StopRecording(); 60 | } 61 | 62 | private void OnDataAvailable(object sender, WaveInEventArgs waveInEventArgs) 63 | { 64 | _bufferedWaveProvider.AddSamples(waveInEventArgs.Buffer,0, waveInEventArgs.BytesRecorded); 65 | } 66 | 67 | public void Dispose() 68 | { 69 | if (!_isDisposed) 70 | { 71 | _isDisposed = true; 72 | _waveIn.Dispose(); 73 | _waveOut.Dispose(); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /EspionSpotify/AudioSessions/IAudioCircularBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.AudioSessions 2 | { 3 | public interface IAudioCircularBuffer 4 | { 5 | int MaxLength { get; } 6 | int Count { get; } 7 | int ReadPosition { get; } 8 | int WritePosition { get; } 9 | 10 | int Write(byte[] data, int offset, int count); 11 | int Read(out byte[] data, int offset, int count); 12 | int Peek(out byte[] data, int offset, int count); 13 | void Advance(int count); 14 | void Reset(); 15 | } 16 | } -------------------------------------------------------------------------------- /EspionSpotify/AudioSessions/IAudioLoopback.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace EspionSpotify.AudioSessions 5 | { 6 | public interface IAudioLoopback 7 | { 8 | bool Running { get; set; } 9 | 10 | Task Run(CancellationTokenSource cancellationTokenSource); 11 | void Dispose(); 12 | } 13 | } -------------------------------------------------------------------------------- /EspionSpotify/AudioSessions/IAudioThrottler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using EspionSpotify.Enums; 4 | using EspionSpotify.Models; 5 | using NAudio.Wave; 6 | 7 | namespace EspionSpotify.AudioSessions 8 | { 9 | public interface IAudioThrottler 10 | { 11 | bool Running { get; set; } 12 | WaveFormat WaveFormat { get; } 13 | bool BufferIsHalfFull { get; } 14 | bool BufferIsReady { get; } 15 | 16 | Task Run(CancellationTokenSource cancellationTokenSource); 17 | Task Read(SilenceAnalyzer silence = SilenceAnalyzer.None); 18 | Task WaitBufferReady(); 19 | void Dispose(); 20 | } 21 | } -------------------------------------------------------------------------------- /EspionSpotify/AudioSessions/IMainAudioSession.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using NAudio.CoreAudioApi; 4 | 5 | namespace EspionSpotify.AudioSessions 6 | { 7 | public interface IMainAudioSession 8 | { 9 | MMDeviceEnumerator AudioMMDevices { get; } 10 | AudioMMDevicesManager AudioMMDevicesManager { get; } 11 | 12 | void SetSpotifyProcesses(); 13 | void RouteSpotifyAudioSessions(bool canRedirectPlayback); 14 | void UnrouteSpotifyAudioSessions(); 15 | ICollection SpotifyAudioSessionControls { get; } 16 | 17 | int AudioDeviceVolume { get; } 18 | bool IsAudioEndPointDeviceIndexAvailable { get; } 19 | void ClearSpotifyAudioSessionControls(); 20 | 21 | void SetAudioDeviceVolume(int volume); 22 | 23 | Task SleepWhileTheSongEnds(); 24 | Task IsSpotifyCurrentlyPlaying(); 25 | void SetSpotifyToMute(bool mute); 26 | Task WaitSpotifyAudioSessionToStart(bool running); 27 | void SetSpotifyVolumeToHighAndOthersToMute(bool mute = false); 28 | 29 | void Dispose(); 30 | } 31 | } -------------------------------------------------------------------------------- /EspionSpotify/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify 2 | { 3 | public static class Constants 4 | { 5 | public const string SPYTIFY = "Spytify"; 6 | 7 | public const string SPOTIFY = "Spotify"; 8 | public const string SPOTIFYFREE = "Spotify Free"; 9 | public const string SPOTIFYPREMIUM = "Spotify Premium"; 10 | public const string ADVERTISEMENT = "Advertisement"; 11 | 12 | public const string UNTITLED_ALBUM = "Untitled"; 13 | } 14 | } -------------------------------------------------------------------------------- /EspionSpotify/Controls/MetroComboBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace EspionSpotify.Controls 5 | { 6 | public class MetroComboBox : MetroFramework.Controls.MetroComboBox 7 | { 8 | protected override void OnMouseWheel(MouseEventArgs e) 9 | { 10 | ((HandledMouseEventArgs) e).Handled = true; 11 | } 12 | 13 | protected override void OnSelectedIndexChanged(EventArgs e) 14 | { 15 | base.OnSelectedIndexChanged(e); 16 | base.OnLostFocus(e); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /EspionSpotify/Controls/MetroTrackBar.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace EspionSpotify.Controls 4 | { 5 | public class MetroTrackBar : MetroFramework.Controls.MetroTrackBar 6 | { 7 | protected override void OnMouseWheel(MouseEventArgs e) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /EspionSpotify/Drivers/AudioVirtualCableDriver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace EspionSpotify.Drivers 8 | { 9 | public static class AudioVirtualCableDriver 10 | { 11 | private const string DRIVER_NAME = "VB-Audio Virtual Cable"; 12 | 13 | private static string Path => 14 | $@"{Environment.CurrentDirectory}\Drivers\VBCABLE_Setup{(Environment.Is64BitOperatingSystem ? "_x64" : "")}.exe"; 15 | 16 | public static bool IsFound => File.Exists(Path); 17 | 18 | public static bool ExistsInAudioEndPointDevices(IDictionary audioEndPointDeviceNames) 19 | { 20 | return audioEndPointDeviceNames.Any(x => x.Value.Contains(DRIVER_NAME)); 21 | } 22 | 23 | public static bool SetupDriver() 24 | { 25 | try 26 | { 27 | var psi = new ProcessStartInfo 28 | { 29 | CreateNoWindow = false, 30 | UseShellExecute = true, 31 | Verb = "runas", 32 | FileName = Path 33 | }; 34 | var process = new Process 35 | { 36 | StartInfo = psi 37 | }; 38 | process.Start(); 39 | process.WaitForExit(); 40 | } 41 | catch 42 | { 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /EspionSpotify/Drivers/VBCABLE_ControlPanel.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/VBCABLE_ControlPanel.exe -------------------------------------------------------------------------------- /EspionSpotify/Drivers/VBCABLE_Setup.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/VBCABLE_Setup.exe -------------------------------------------------------------------------------- /EspionSpotify/Drivers/VBCABLE_Setup_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/VBCABLE_Setup_x64.exe -------------------------------------------------------------------------------- /EspionSpotify/Drivers/pin_in.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/pin_in.ico -------------------------------------------------------------------------------- /EspionSpotify/Drivers/pin_out.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/pin_out.ico -------------------------------------------------------------------------------- /EspionSpotify/Drivers/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/readme.txt -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbMmeCable64_vista.inf: -------------------------------------------------------------------------------- 1 | [Version] 2 | Signature="$CHICAGO$" 3 | Class=MEDIA 4 | Provider=%Provider% 5 | ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} 6 | DriverVer=09/02/2014,1.0.3.5 7 | CatalogFile=vbaudio_cable64_vista.cat 8 | 9 | 10 | [SourceDisksNames] 11 | 222=%DiskName%,"",222 12 | 13 | [SourceDisksFiles.x86] 14 | vbaudio_cable64_vista.sys=222 15 | 16 | 17 | [Manufacturer] 18 | %Provider%=VBCable,NTamd64, NTIA64 19 | 20 | 21 | [VBCable] 22 | %DeviceName%=VBCableInst,%HardwareId% 23 | 24 | 25 | [VBCable.NTamd64] 26 | %DeviceName%=VBCableInst,%HardwareId% 27 | 28 | 29 | [VBCable.NTIA64] 30 | %DeviceName%=VBCableInst,%HardwareId% 31 | 32 | [DestinationDirs] 33 | VBCableInst.CopyList=10,system32\drivers 34 | 35 | 36 | ;====================================================== 37 | ; VB_CABLE 38 | ;====================================================== 39 | [VBCableInst] 40 | AlsoInstall=ks.registration(ks.inf),wdmaudio.registration(wdmaudio.inf) 41 | CopyFiles=VBCableInst.CopyList 42 | AddReg=VBCableInst.AddReg 43 | 44 | [VBCableInst.CopyList] 45 | vbaudio_cable64_vista.sys 46 | 47 | [VBCableInst.Interfaces] 48 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 49 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 50 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 51 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 52 | 53 | [VBCableInst.AddReg] 54 | HKR,,AssociatedFilters,,"wdmaud,redbook" 55 | ;HKR,,SetupPreferredAudioDevices,3,01,00,00,00 56 | HKR,,Driver,,%DriverFile% 57 | HKR,,NTMPDriver,,"%DriverFile%,sbemul.sys" 58 | 59 | HKR,Drivers,SubClasses,,"wave,mixer" 60 | 61 | HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv 62 | HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv 63 | 64 | HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName% 65 | HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName% 66 | 67 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Name,,%VBCABLE.INPUTNAME% 68 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Display,1,00,00,00,00 69 | 70 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Name,,%VBCABLE.OUTPUTNAME% 71 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Display,1,00,00,00,00 72 | 73 | ;====================================================== 74 | ; COMMON 75 | ;====================================================== 76 | [VBCableInst.I.Topo] 77 | AddReg=VBCableInst.I.Topo.AddReg 78 | 79 | [VBCableInst.I.Topo.AddReg] 80 | HKR,,CLSID,,%CLSID_Proxy% 81 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 82 | 83 | [VBCableInst.I.Wave] 84 | AddReg=VBCableInst.I.Wave.AddReg 85 | 86 | [VBCableInst.I.Wave.AddReg] 87 | HKR,,CLSID,,%CLSID_Proxy% 88 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 89 | 90 | 91 | 92 | ;====================================================== 93 | ; VB_CABLE NT 94 | ;====================================================== 95 | [VBCableInst.NT] 96 | Include=ks.inf,wdmaudio.inf 97 | Needs=KS.Registration, WDMAUDIO.Registration 98 | CopyFiles=VBCableInst.CopyList 99 | AddReg=VBCableInst.AddReg 100 | 101 | 102 | 103 | [VBCableInst.NT.Interfaces] 104 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 105 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 106 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 107 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 108 | 109 | 110 | 111 | [VBCableInst.NT.Services] 112 | AddService=%ServiceName%,0x2,SrvInstSection 113 | 114 | [SrvInstSection] 115 | DisplayName=%DeviceName% (WDM) 116 | ServiceType=%SERVICE_KERNEL_DRIVER% 117 | StartType=%SERVICE_DEMAND_START% 118 | ErrorControl=%SERVICE_ERROR_NORMAL% 119 | ServiceBinary=%12%\%DriverFile% 120 | 121 | 122 | ;====================================================== 123 | ;COMMON STRING 124 | ;====================================================== 125 | [Strings] 126 | 127 | Provider="VB-Audio Software" 128 | DeviceName="VB-Audio Virtual Cable" 129 | HardwareId="VBAudioVACWDM" 130 | ServiceName="VBAudioVACMME" 131 | DriverFile="vbaudio_cable64_vista.sys" 132 | DiskName="vbcable Driver Disk" 133 | 134 | KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}" 135 | KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}" 136 | KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}" 137 | 138 | CLSID_Proxy="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" 139 | 140 | SERVICE_KERNEL_DRIVER=1 141 | SERVICE_DEMAND_START=3 142 | SERVICE_ERROR_NORMAL=1 143 | 144 | ;; FriendlyName 145 | VBCABLE.INPUTGUID ="{23208800-570C-4682-BB53-7F12CE497910}" 146 | VBCABLE.OUTPUTGUID ="{23208900-570C-4682-BB53-7F12CE497910}" 147 | 148 | VBCABLE.NAMELine1 ="VB-Audio Point" 149 | VBCABLE.INPUTNAME ="CABLE Output" 150 | VBCABLE.OUTPUTNAME ="CABLE Input" 151 | 152 | MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" 153 | 154 | -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbMmeCable64_win7.inf: -------------------------------------------------------------------------------- 1 | [Version] 2 | Signature="$CHICAGO$" 3 | Class=MEDIA 4 | Provider=%Provider% 5 | ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} 6 | DriverVer=09/02/2014,1.0.3.5 7 | CatalogFile=vbaudio_cable64_win7.cat 8 | 9 | 10 | [SourceDisksNames] 11 | 222=%DiskName%,"",222 12 | 13 | [SourceDisksFiles.x86] 14 | vbaudio_cable64_win7.sys=222 15 | 16 | 17 | [Manufacturer] 18 | %Provider%=VBCable,NTamd64, NTIA64 19 | 20 | 21 | [VBCable] 22 | %DeviceName%=VBCableInst,%HardwareId% 23 | 24 | 25 | [VBCable.NTamd64] 26 | %DeviceName%=VBCableInst,%HardwareId% 27 | 28 | 29 | [VBCable.NTIA64] 30 | %DeviceName%=VBCableInst,%HardwareId% 31 | 32 | [DestinationDirs] 33 | VBCableInst.CopyList=10,system32\drivers 34 | 35 | 36 | ;====================================================== 37 | ; VB_CABLE 38 | ;====================================================== 39 | [VBCableInst] 40 | AlsoInstall=ks.registration(ks.inf),wdmaudio.registration(wdmaudio.inf) 41 | CopyFiles=VBCableInst.CopyList 42 | AddReg=VBCableInst.AddReg 43 | 44 | [VBCableInst.CopyList] 45 | vbaudio_cable64_win7.sys 46 | 47 | [VBCableInst.Interfaces] 48 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 49 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 50 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 51 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 52 | 53 | [VBCableInst.AddReg] 54 | HKR,,AssociatedFilters,,"wdmaud,redbook" 55 | ;HKR,,SetupPreferredAudioDevices,3,01,00,00,00 56 | HKR,,Driver,,%DriverFile% 57 | HKR,,NTMPDriver,,"%DriverFile%,sbemul.sys" 58 | 59 | HKR,Drivers,SubClasses,,"wave,mixer" 60 | 61 | HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv 62 | HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv 63 | 64 | HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName% 65 | HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName% 66 | 67 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Name,,%VBCABLE.INPUTNAME% 68 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Display,1,00,00,00,00 69 | 70 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Name,,%VBCABLE.OUTPUTNAME% 71 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Display,1,00,00,00,00 72 | 73 | ;====================================================== 74 | ; COMMON 75 | ;====================================================== 76 | [VBCableInst.I.Topo] 77 | AddReg=VBCableInst.I.Topo.AddReg 78 | 79 | [VBCableInst.I.Topo.AddReg] 80 | HKR,,CLSID,,%CLSID_Proxy% 81 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 82 | 83 | [VBCableInst.I.Wave] 84 | AddReg=VBCableInst.I.Wave.AddReg 85 | 86 | [VBCableInst.I.Wave.AddReg] 87 | HKR,,CLSID,,%CLSID_Proxy% 88 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 89 | 90 | 91 | 92 | ;====================================================== 93 | ; VB_CABLE NT 94 | ;====================================================== 95 | [VBCableInst.NT] 96 | Include=ks.inf,wdmaudio.inf 97 | Needs=KS.Registration, WDMAUDIO.Registration 98 | CopyFiles=VBCableInst.CopyList 99 | AddReg=VBCableInst.AddReg 100 | 101 | 102 | 103 | [VBCableInst.NT.Interfaces] 104 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 105 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 106 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 107 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 108 | 109 | 110 | 111 | [VBCableInst.NT.Services] 112 | AddService=%ServiceName%,0x2,SrvInstSection 113 | 114 | [SrvInstSection] 115 | DisplayName=%DeviceName% (WDM) 116 | ServiceType=%SERVICE_KERNEL_DRIVER% 117 | StartType=%SERVICE_DEMAND_START% 118 | ErrorControl=%SERVICE_ERROR_NORMAL% 119 | ServiceBinary=%12%\%DriverFile% 120 | 121 | 122 | ;====================================================== 123 | ;COMMON STRING 124 | ;====================================================== 125 | [Strings] 126 | 127 | Provider="VB-Audio Software" 128 | DeviceName="VB-Audio Virtual Cable" 129 | HardwareId="VBAudioVACWDM" 130 | ServiceName="VBAudioVACMME" 131 | DriverFile="vbaudio_cable64_win7.sys" 132 | DiskName="vbcable Driver Disk" 133 | 134 | KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}" 135 | KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}" 136 | KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}" 137 | 138 | CLSID_Proxy="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" 139 | 140 | SERVICE_KERNEL_DRIVER=1 141 | SERVICE_DEMAND_START=3 142 | SERVICE_ERROR_NORMAL=1 143 | 144 | ;; FriendlyName 145 | VBCABLE.INPUTGUID ="{23208800-570C-4682-BB53-7F12CE497910}" 146 | VBCABLE.OUTPUTGUID ="{23208900-570C-4682-BB53-7F12CE497910}" 147 | 148 | VBCABLE.NAMELine1 ="VB-Audio Point" 149 | VBCABLE.INPUTNAME ="CABLE Output" 150 | VBCABLE.OUTPUTNAME ="CABLE Input" 151 | 152 | MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" 153 | 154 | -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbMmeCable_vista.inf: -------------------------------------------------------------------------------- 1 | [Version] 2 | Signature="$CHICAGO$" 3 | Class=MEDIA 4 | Provider=%Provider% 5 | ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} 6 | DriverVer=09/02/2014,1.0.3.5 7 | CatalogFile=vbaudio_cable_vista.cat 8 | 9 | 10 | [SourceDisksNames] 11 | 222=%DiskName%,"",222 12 | 13 | [SourceDisksFiles.x86] 14 | vbaudio_cable_vista.sys=222 15 | 16 | 17 | [Manufacturer] 18 | %Provider%=VBCable,NTamd64, NTIA64 19 | 20 | 21 | [VBCable] 22 | %DeviceName%=VBCableInst,%HardwareId% 23 | 24 | 25 | [VBCable.NTamd64] 26 | %DeviceName%=VBCableInst,%HardwareId% 27 | 28 | 29 | [VBCable.NTIA64] 30 | %DeviceName%=VBCableInst,%HardwareId% 31 | 32 | [DestinationDirs] 33 | VBCableInst.CopyList=10,system32\drivers 34 | 35 | 36 | ;====================================================== 37 | ; VB_CABLE 38 | ;====================================================== 39 | [VBCableInst] 40 | AlsoInstall=ks.registration(ks.inf),wdmaudio.registration(wdmaudio.inf) 41 | CopyFiles=VBCableInst.CopyList 42 | AddReg=VBCableInst.AddReg 43 | 44 | [VBCableInst.CopyList] 45 | vbaudio_cable_vista.sys 46 | 47 | [VBCableInst.Interfaces] 48 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 49 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 50 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 51 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 52 | 53 | [VBCableInst.AddReg] 54 | HKR,,AssociatedFilters,,"wdmaud,redbook" 55 | ;HKR,,SetupPreferredAudioDevices,3,01,00,00,00 56 | HKR,,Driver,,%DriverFile% 57 | HKR,,NTMPDriver,,"%DriverFile%,sbemul.sys" 58 | 59 | HKR,Drivers,SubClasses,,"wave,mixer" 60 | 61 | HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv 62 | HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv 63 | 64 | HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName% 65 | HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName% 66 | 67 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Name,,%VBCABLE.INPUTNAME% 68 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Display,1,00,00,00,00 69 | 70 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Name,,%VBCABLE.OUTPUTNAME% 71 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Display,1,00,00,00,00 72 | 73 | ;====================================================== 74 | ; COMMON 75 | ;====================================================== 76 | [VBCableInst.I.Topo] 77 | AddReg=VBCableInst.I.Topo.AddReg 78 | 79 | [VBCableInst.I.Topo.AddReg] 80 | HKR,,CLSID,,%CLSID_Proxy% 81 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 82 | 83 | [VBCableInst.I.Wave] 84 | AddReg=VBCableInst.I.Wave.AddReg 85 | 86 | [VBCableInst.I.Wave.AddReg] 87 | HKR,,CLSID,,%CLSID_Proxy% 88 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 89 | 90 | 91 | 92 | ;====================================================== 93 | ; VB_CABLE NT 94 | ;====================================================== 95 | [VBCableInst.NT] 96 | Include=ks.inf,wdmaudio.inf 97 | Needs=KS.Registration, WDMAUDIO.Registration 98 | CopyFiles=VBCableInst.CopyList 99 | AddReg=VBCableInst.AddReg 100 | 101 | 102 | 103 | [VBCableInst.NT.Interfaces] 104 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 105 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 106 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 107 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 108 | 109 | 110 | 111 | [VBCableInst.NT.Services] 112 | AddService=%ServiceName%,0x2,SrvInstSection 113 | 114 | [SrvInstSection] 115 | DisplayName=%DeviceName% (WDM) 116 | ServiceType=%SERVICE_KERNEL_DRIVER% 117 | StartType=%SERVICE_DEMAND_START% 118 | ErrorControl=%SERVICE_ERROR_NORMAL% 119 | ServiceBinary=%12%\%DriverFile% 120 | 121 | 122 | ;====================================================== 123 | ;COMMON STRING 124 | ;====================================================== 125 | [Strings] 126 | 127 | Provider="VB-Audio Software" 128 | DeviceName="VB-Audio Virtual Cable" 129 | HardwareId="VBAudioVACWDM" 130 | ServiceName="VBAudioVACMME" 131 | DriverFile="vbaudio_cable_vista.sys" 132 | DiskName="vbcable Driver Disk" 133 | 134 | KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}" 135 | KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}" 136 | KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}" 137 | 138 | CLSID_Proxy="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" 139 | 140 | SERVICE_KERNEL_DRIVER=1 141 | SERVICE_DEMAND_START=3 142 | SERVICE_ERROR_NORMAL=1 143 | 144 | ;; FriendlyName 145 | VBCABLE.INPUTGUID ="{23208800-570C-4682-BB53-7F12CE497910}" 146 | VBCABLE.OUTPUTGUID ="{23208900-570C-4682-BB53-7F12CE497910}" 147 | 148 | VBCABLE.NAMELine1 ="VB-Audio Point" 149 | VBCABLE.INPUTNAME ="CABLE Output" 150 | VBCABLE.OUTPUTNAME ="CABLE Input" 151 | 152 | MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" 153 | 154 | -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbMmeCable_win7.inf: -------------------------------------------------------------------------------- 1 | [Version] 2 | Signature="$CHICAGO$" 3 | Class=MEDIA 4 | Provider=%Provider% 5 | ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} 6 | DriverVer=09/02/2014,1.0.3.5 7 | CatalogFile=vbaudio_cable_win7.cat 8 | 9 | 10 | [SourceDisksNames] 11 | 222=%DiskName%,"",222 12 | 13 | [SourceDisksFiles.x86] 14 | vbaudio_cable_win7.sys=222 15 | 16 | 17 | [Manufacturer] 18 | %Provider%=VBCable,NTamd64, NTIA64 19 | 20 | 21 | [VBCable] 22 | %DeviceName%=VBCableInst,%HardwareId% 23 | 24 | 25 | [VBCable.NTamd64] 26 | %DeviceName%=VBCableInst,%HardwareId% 27 | 28 | 29 | [VBCable.NTIA64] 30 | %DeviceName%=VBCableInst,%HardwareId% 31 | 32 | [DestinationDirs] 33 | VBCableInst.CopyList=10,system32\drivers 34 | 35 | 36 | ;====================================================== 37 | ; VB_CABLE 38 | ;====================================================== 39 | [VBCableInst] 40 | AlsoInstall=ks.registration(ks.inf),wdmaudio.registration(wdmaudio.inf) 41 | CopyFiles=VBCableInst.CopyList 42 | AddReg=VBCableInst.AddReg 43 | 44 | [VBCableInst.CopyList] 45 | vbaudio_cable_win7.sys 46 | 47 | [VBCableInst.Interfaces] 48 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 49 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 50 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 51 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 52 | 53 | [VBCableInst.AddReg] 54 | HKR,,AssociatedFilters,,"wdmaud,redbook" 55 | ;HKR,,SetupPreferredAudioDevices,3,01,00,00,00 56 | HKR,,Driver,,%DriverFile% 57 | HKR,,NTMPDriver,,"%DriverFile%,sbemul.sys" 58 | 59 | HKR,Drivers,SubClasses,,"wave,mixer" 60 | 61 | HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv 62 | HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv 63 | 64 | HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName% 65 | HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName% 66 | 67 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Name,,%VBCABLE.INPUTNAME% 68 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Display,1,00,00,00,00 69 | 70 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Name,,%VBCABLE.OUTPUTNAME% 71 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Display,1,00,00,00,00 72 | 73 | ;====================================================== 74 | ; COMMON 75 | ;====================================================== 76 | [VBCableInst.I.Topo] 77 | AddReg=VBCableInst.I.Topo.AddReg 78 | 79 | [VBCableInst.I.Topo.AddReg] 80 | HKR,,CLSID,,%CLSID_Proxy% 81 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 82 | 83 | [VBCableInst.I.Wave] 84 | AddReg=VBCableInst.I.Wave.AddReg 85 | 86 | [VBCableInst.I.Wave.AddReg] 87 | HKR,,CLSID,,%CLSID_Proxy% 88 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 89 | 90 | 91 | 92 | ;====================================================== 93 | ; VB_CABLE NT 94 | ;====================================================== 95 | [VBCableInst.NT] 96 | Include=ks.inf,wdmaudio.inf 97 | Needs=KS.Registration, WDMAUDIO.Registration 98 | CopyFiles=VBCableInst.CopyList 99 | AddReg=VBCableInst.AddReg 100 | 101 | 102 | 103 | [VBCableInst.NT.Interfaces] 104 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",VBCableInst.I.Topo 105 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",VBCableInst.I.Wave 106 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",VBCableInst.I.Wave 107 | AddInterface=%KSCATEGORY_RENDER%,"Wave",VBCableInst.I.Wave 108 | 109 | 110 | 111 | [VBCableInst.NT.Services] 112 | AddService=%ServiceName%,0x2,SrvInstSection 113 | 114 | [SrvInstSection] 115 | DisplayName=%DeviceName% (WDM) 116 | ServiceType=%SERVICE_KERNEL_DRIVER% 117 | StartType=%SERVICE_DEMAND_START% 118 | ErrorControl=%SERVICE_ERROR_NORMAL% 119 | ServiceBinary=%12%\%DriverFile% 120 | 121 | 122 | ;====================================================== 123 | ;COMMON STRING 124 | ;====================================================== 125 | [Strings] 126 | 127 | Provider="VB-Audio Software" 128 | DeviceName="VB-Audio Virtual Cable" 129 | HardwareId="VBAudioVACWDM" 130 | ServiceName="VBAudioVACMME" 131 | DriverFile="vbaudio_cable_win7.sys" 132 | DiskName="vbcable Driver Disk" 133 | 134 | KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}" 135 | KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}" 136 | KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}" 137 | 138 | CLSID_Proxy="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" 139 | 140 | SERVICE_KERNEL_DRIVER=1 141 | SERVICE_DEMAND_START=3 142 | SERVICE_ERROR_NORMAL=1 143 | 144 | ;; FriendlyName 145 | VBCABLE.INPUTGUID ="{23208800-570C-4682-BB53-7F12CE497910}" 146 | VBCABLE.OUTPUTGUID ="{23208900-570C-4682-BB53-7F12CE497910}" 147 | 148 | VBCABLE.NAMELine1 ="VB-Audio Point" 149 | VBCABLE.INPUTNAME ="CABLE Output" 150 | VBCABLE.OUTPUTNAME ="CABLE Input" 151 | 152 | MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" 153 | 154 | -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbMmeCable_xp.inf: -------------------------------------------------------------------------------- 1 | [Version] 2 | 3 | Signature="$CHICAGO$" 4 | Class=MEDIA 5 | Provider=%Provider% 6 | ClassGUID={4d36e96c-e325-11ce-bfc1-08002be10318} 7 | DriverVer=09/02/2014,1.0.3.5 8 | CatalogFile=vbaudio_cable_xp.cat 9 | 10 | 11 | 12 | [Manufacturer] 13 | 14 | %Provider%=DevSection,NTamd64 15 | 16 | 17 | 18 | [DevSection] 19 | 20 | %DeviceName%=DevInst,%HardwareId% 21 | 22 | 23 | 24 | [DevSection.NTamd64] 25 | 26 | %DeviceName%=DevInst,%HardwareId% 27 | 28 | 29 | 30 | ;[DevSection.NTia64] 31 | 32 | ;%DeviceName%=DevInst,%HardwareId% 33 | 34 | 35 | 36 | ;##################################################################### 37 | ; 38 | ; 2k+ Installation 39 | ; ================ 40 | ; 41 | ;##################################################################### 42 | 43 | 44 | [DevInst.NT] 45 | 46 | Include=ks.inf,wdmaudio.inf 47 | Needs=KS.Registration, WDMAUDIO.Registration 48 | CopyFiles=DevInst.CopyList 49 | AddReg=DevInst.AddReg 50 | 51 | 52 | 53 | [DevInst.NT.Interfaces] 54 | 55 | 56 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",DevInst.I.Topo 57 | 58 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",DevInst.I.Wave 59 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",DevInst.I.Wave 60 | AddInterface=%KSCATEGORY_RENDER%,"Wave",DevInst.I.Wave 61 | 62 | 63 | 64 | [DevInst.NT.Services] 65 | 66 | AddService=%ServiceName%,0x2,SrvInstSection 67 | 68 | [SrvInstSection] 69 | 70 | DisplayName=%DeviceName% (WDM) 71 | ServiceType=%SERVICE_KERNEL_DRIVER% 72 | StartType=%SERVICE_DEMAND_START% 73 | ErrorControl=%SERVICE_ERROR_NORMAL% 74 | ServiceBinary=%12%\%DriverFile% 75 | 76 | 77 | 78 | ;##################################################################### 79 | ; 80 | ; 98SE+ Installation 81 | ; ================== 82 | ; 83 | ;##################################################################### 84 | 85 | 86 | [DevInst] 87 | 88 | AlsoInstall=ks.registration(ks.inf),wdmaudio.registration(wdmaudio.inf) 89 | CopyFiles=DevInst.CopyList 90 | AddReg=DevInst.AddReg 91 | 92 | 93 | 94 | [DevInst.Interfaces] 95 | 96 | 97 | AddInterface=%KSCATEGORY_AUDIO%,"Topo",DevInst.I.Topo 98 | 99 | AddInterface=%KSCATEGORY_AUDIO%,"Wave",DevInst.I.Wave 100 | AddInterface=%KSCATEGORY_CAPTURE%,"Wave",DevInst.I.Wave 101 | AddInterface=%KSCATEGORY_RENDER%,"Wave",DevInst.I.Wave 102 | 103 | 104 | ;##################################################################### 105 | ; 106 | ; Common registry data 107 | ; ==================== 108 | ; 109 | ;##################################################################### 110 | 111 | 112 | [DevInst.AddReg] 113 | 114 | HKR,,AssociatedFilters,,"wdmaud,redbook" 115 | ;HKR,,SetupPreferredAudioDevices,3,01,00,00,00 116 | HKR,,Driver,,%DriverFile% 117 | HKR,,NTMPDriver,,"%DriverFile%,sbemul.sys" 118 | 119 | HKR,Drivers,SubClasses,,"wave,mixer" 120 | 121 | HKR,Drivers\wave\wdmaud.drv,Driver,,wdmaud.drv 122 | HKR,Drivers\mixer\wdmaud.drv,Driver,,wdmaud.drv 123 | 124 | HKR,Drivers\wave\wdmaud.drv,Description,,%DeviceName% 125 | HKR,Drivers\mixer\wdmaud.drv,Description,,%DeviceName% 126 | 127 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Name,,%VBCABLE.INPUTNAME% 128 | HKLM,%MediaCategories%\%VBCABLE.INPUTGUID%,Display,1,00,00,00,00 129 | 130 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Name,,%VBCABLE.OUTPUTNAME% 131 | HKLM,%MediaCategories%\%VBCABLE.OUTPUTGUID%,Display,1,00,00,00,00 132 | 133 | [DevInst.I.Topo] 134 | AddReg=DevInst.I.Topo.AddReg 135 | 136 | [DevInst.I.Topo.AddReg] 137 | HKR,,CLSID,,%CLSID_Proxy% 138 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 139 | 140 | [DevInst.I.Wave] 141 | AddReg=DevInst.I.Wave.AddReg 142 | 143 | [DevInst.I.Wave.AddReg] 144 | HKR,,CLSID,,%CLSID_Proxy% 145 | HKR,,FriendlyName,,%VBCABLE.NAMELine1% 146 | 147 | 148 | ;##################################################################### 149 | ; 150 | ; Files 151 | ; ===== 152 | ; 153 | ;##################################################################### 154 | 155 | 156 | 157 | [SourceDisksNames] 158 | 222=%DiskName%,"",222 159 | 160 | 161 | 162 | [SourceDisksFiles.x86] 163 | vbaudio_cable_xp.sys=222 164 | 165 | 166 | [SourceDisksFiles.amd64] 167 | vbaudio_cable_xp.sys=222 168 | 169 | 170 | [DestinationDirs] 171 | DevInst.CopyList=10,system32\drivers 172 | 173 | 174 | 175 | [DevInst.CopyList] 176 | ;%DriverFile% 177 | vbaudio_cable_xp.sys 178 | 179 | ;##################################################################### 180 | ; 181 | ; Strings 182 | ; ======= 183 | ; 184 | ;##################################################################### 185 | [Strings] 186 | 187 | Provider="VB-Audio Software" 188 | DeviceName="VB-Audio Virtual Cable" 189 | HardwareId=VBAudioVACWDM 190 | ServiceName=VBAudioVACMME 191 | DriverFile=vbaudio_cable_xp.sys 192 | DiskName="vbcable Driver Disk" 193 | 194 | KSCATEGORY_AUDIO="{6994AD04-93EF-11D0-A3CC-00A0C9223196}" 195 | KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}" 196 | KSCATEGORY_RENDER="{65E8773E-8F56-11D0-A3B9-00A0C9223196}" 197 | 198 | 199 | 200 | CLSID_Proxy="{17CCA71B-ECD7-11D0-B908-00A0C9223196}" 201 | 202 | SERVICE_KERNEL_DRIVER=1 203 | SERVICE_DEMAND_START=3 204 | SERVICE_ERROR_NORMAL=1 205 | 206 | ;; FriendlyName 207 | VBCABLE.INPUTGUID ="{23208800-570C-4682-BB53-7F12CE497910}" 208 | VBCABLE.OUTPUTGUID ="{23208900-570C-4682-BB53-7F12CE497910}" 209 | 210 | VBCABLE.NAMELine1 ="VB-Audio Point" 211 | VBCABLE.INPUTNAME ="CABLE Output" 212 | VBCABLE.OUTPUTNAME ="CABLE Input" 213 | 214 | MediaCategories="SYSTEM\CurrentControlSet\Control\MediaCategories" 215 | 216 | -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_2003.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_2003.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_2003.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_2003.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_vista.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_vista.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_vista.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_vista.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_win7.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_win7.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable64_win7.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable64_win7.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_2003.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_2003.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_2003.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_2003.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_vista.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_vista.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_vista.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_vista.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_win7.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_win7.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_win7.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_win7.sys -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_xp.cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_xp.cat -------------------------------------------------------------------------------- /EspionSpotify/Drivers/vbaudio_cable_xp.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Drivers/vbaudio_cable_xp.sys -------------------------------------------------------------------------------- /EspionSpotify/Enums/AlbumCoverSize.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum AlbumCoverSize 4 | { 5 | small, 6 | medium, 7 | large, 8 | extralarge 9 | } 10 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/ExternalAPIType.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum ExternalAPIType 4 | { 5 | None = - 1, 6 | LastFM = 0, 7 | Spotify 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/LanguageType.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum LanguageType 4 | { 5 | en = 0, 6 | fr // French 7 | } 8 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/LastFMNodeStatus.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum LastFMNodeStatus 4 | { 5 | ok, 6 | failed 7 | } 8 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/MediaFormat.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum MediaFormat 4 | { 5 | Mp3, 6 | Wav 7 | } 8 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/OSVersions.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum OSVersions 4 | { 5 | RS3 = 16299, 6 | RS4 = 17134, 7 | RS5_1809 = 17763, 8 | Version21H2 = 21390, 9 | Windows11 = 22000 10 | } 11 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/RecordRecordingsStatus.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum RecordRecordingsStatus 4 | { 5 | Skip = 0, 6 | Overwrite, 7 | Duplicate 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/SilenceAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum SilenceAnalyzer 4 | { 5 | None, 6 | TrimEnd, 7 | TrimStart 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/TitleSeparatorType.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum TitleSeparatorType 4 | { 5 | None, 6 | Dash, 7 | Parenthesis 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/TranslationKeys.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum TranslationKeys 4 | { 5 | cbOptBitRate128, 6 | cbOptBitRate160, 7 | cbOptBitRate256, 8 | cbOptBitRate320, 9 | cbOptBitRateSpotifyFree, 10 | cbOptBitRateSpotifyPremium, 11 | lblAddFolders, 12 | lblAddSeparators, 13 | lblAd, 14 | lblAds, 15 | lblAlbumTrackNumberToFilePrefix, 16 | lblAudioDevice, 17 | lblBitRate, 18 | lblClientId, 19 | lblDuplicate, 20 | lblExtraTitleToSubtitle, 21 | lblForceSpotifyToSkip, 22 | lblFormat, 23 | lblGeneral, 24 | lblID3, 25 | lblLanguage, 26 | lblListenToSpotifyPlayback, 27 | lblMinimizeToSystemTray, 28 | lblMinLength, 29 | lblMuteAds, 30 | lblCounterToFilePrefix, 31 | lblCounterToMediaTag, 32 | lblPath, 33 | lblRecorder, 34 | lblRecordEverything, 35 | lblRecordAds, 36 | lblRecordingNum, 37 | lblRecordingTimer, 38 | lblRecordOverRecordings, 39 | lblRedirectURL, 40 | lblSecretId, 41 | lblSpy, 42 | lblUpdateRecordingsID3Tags, 43 | logAd, 44 | logDeleting, 45 | logException, 46 | logMaxFileSequenceReached, 47 | logMissingDlls, 48 | logOutputPathNotFound, 49 | logPreviousLogs, 50 | logRecorded, 51 | logRecordedFileNotFound, 52 | logRecording, 53 | logRecordingTimerDone, 54 | logSpotifyConnecting, 55 | logSpotifyIsClosed, 56 | logSpotifyNotConnected, 57 | logSpotifyNotFound, 58 | logSpotifyPlayingOutsideOfSelectedAudioEndPoint, 59 | logStarting, 60 | logStoping, 61 | logStopRecordingWhenSongEnds, 62 | logTrackExists, 63 | logUnknownException, 64 | logUnsupportedNumberChannels, 65 | logUnsupportedRate, 66 | msgBodyCantQuit, 67 | msgBodyDriverInstallationFailed, 68 | msgBodyFailedToUseSpotifyAPI, 69 | msgBodyPathNotFound, 70 | msgBodyPathTooLong, 71 | msgFolderDialog, 72 | msgNewVersionContent, 73 | msgNewVersionTitle, 74 | msgTitleCantQuit, 75 | msgTitleFailedToUseSpotifyAPI, 76 | msgTitlePathNotFound, 77 | msgTitlePathTooLong, 78 | tabAdvanced, 79 | tabRecord, 80 | tabSettings, 81 | tipClear, 82 | tipDirectory, 83 | tipDonate, 84 | tipFAQ, 85 | tipFAQSpotifyAPI, 86 | tipInstallVirtualCableDriver, 87 | tipNumModifierHold, 88 | tipPath, 89 | tipRelease, 90 | tipSpotifyAPICredentials, 91 | tipSpotifyAPIDashboard, 92 | tipStartSpying, 93 | tipStopSying, 94 | tipUninstallVirtualCableDriver, 95 | titleSpotifyAPICredentials, 96 | watermarkClientId, 97 | watermarkSecretId, 98 | watermarkRedirectURL 99 | } 100 | } -------------------------------------------------------------------------------- /EspionSpotify/Enums/WaveFormatMP3Restriction.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Enums 2 | { 3 | public enum WaveFormatMP3Restriction 4 | { 5 | Channel, 6 | SampleRate 7 | } 8 | } -------------------------------------------------------------------------------- /EspionSpotify/Events/PlayStateEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Events 2 | { 3 | /// 4 | /// Event gets triggered, when the Playin-state is changed (e.g Play --> Pause) 5 | /// 6 | public class PlayStateEventArgs 7 | { 8 | public bool Playing { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /EspionSpotify/Events/TrackChangeEventArgs.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Models; 2 | 3 | namespace EspionSpotify.Events 4 | { 5 | /// 6 | /// Event gets triggered, when the Track is changed 7 | /// 8 | public class TrackChangeEventArgs 9 | { 10 | public Track OldTrack { get; set; } 11 | public Track NewTrack { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /EspionSpotify/Events/TrackTimeChangeEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Events 2 | { 3 | /// 4 | /// Event gets triggered, when the tracktime changes 5 | /// 6 | public class TrackTimeChangeEventArgs 7 | { 8 | public int TrackTime { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /EspionSpotify/Exceptions/DestinationPathNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Exceptions 4 | { 5 | public class DestinationPathNotFoundException : Exception 6 | { 7 | public DestinationPathNotFoundException() 8 | { 9 | } 10 | 11 | public DestinationPathNotFoundException(string message) 12 | : base(message) 13 | { 14 | } 15 | 16 | public DestinationPathNotFoundException(string message, Exception inner) 17 | : base(message, inner) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /EspionSpotify/Exceptions/SourceFileNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Exceptions 4 | { 5 | public class SourceFileNotFoundException : Exception 6 | { 7 | public SourceFileNotFoundException() 8 | { 9 | } 10 | 11 | public SourceFileNotFoundException(string message) 12 | : base(message) 13 | { 14 | } 15 | 16 | public SourceFileNotFoundException(string message, Exception inner) 17 | : base(message, inner) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/ClassPropertyExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Extensions 2 | { 3 | public static class ClassPropertyExtensions 4 | { 5 | public static void CopyAllTo(this TSource source, TTarget target) 6 | where TSource : class 7 | where TTarget : class 8 | { 9 | var parentProperties = source.GetType().GetProperties(); 10 | var childProperties = target.GetType().GetProperties(); 11 | 12 | foreach (var parentProperty in parentProperties) 13 | foreach (var childProperty in childProperties) 14 | { 15 | if (!childProperty.CanWrite || !parentProperty.CanRead) break; 16 | if (parentProperty.Name == childProperty.Name && 17 | parentProperty.PropertyType == childProperty.PropertyType) 18 | { 19 | childProperty.SetValue(target, parentProperty.GetValue(source)); 20 | break; 21 | } 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/ColorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace EspionSpotify.Extensions 4 | { 5 | public static class ColorExtensions 6 | { 7 | public static Color SpotifyPrimaryText(this Color _) 8 | { 9 | return Color.FromArgb(32, 187, 88); 10 | } 11 | 12 | public static Color SpotifySecondaryText(this Color _) 13 | { 14 | return Color.White; 15 | } 16 | 17 | public static Color SpotifySecondaryTextAlternate(this Color _) 18 | { 19 | return Color.FromArgb(179, 179, 179); 20 | } 21 | 22 | public static Color SpotifyPrimaryBackground(this Color _) 23 | { 24 | return Color.FromArgb(18, 18, 18); 25 | } 26 | 27 | public static Color SpotifySecondaryBackground(this Color _) 28 | { 29 | return Color.FromArgb(24, 24, 24); 30 | } 31 | 32 | public static Color SpotifySecondaryBackgroundAlternate(this Color _) 33 | { 34 | return Color.FromArgb(40, 40, 40); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/ControlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | public static class ControlExtensions 7 | { 8 | public static TResult GetPropertyThreadSafe(this TControl control, 9 | Func getter) 10 | where TControl : Control 11 | { 12 | if (control.IsDisposed()) return default; 13 | if (control.InvokeRequired) 14 | return (TResult) control.Invoke(getter, control); 15 | return getter(control); 16 | } 17 | 18 | public static void SetPropertyThreadSafe(this TControl control, MethodInvoker setter) 19 | where TControl : Control 20 | { 21 | lock (control) 22 | { 23 | if (control.IsDisposed()) return; 24 | if (control.InvokeRequired) 25 | control.Invoke(setter); 26 | else 27 | setter(); 28 | } 29 | } 30 | 31 | public static bool IsDisposed(this TControl control) 32 | where TControl : Control 33 | { 34 | return FrmEspionSpotify.Instance.IsDisposed || control.IsDisposed || control.Parent.IsDisposed; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | 5 | namespace EspionSpotify.Extensions 6 | { 7 | public static class DictionaryExtensions 8 | { 9 | public static bool IncludesKey(this IDictionary dictionary, string key) 10 | { 11 | return dictionary.Any() && dictionary.ContainsKey(key ?? string.Empty); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/JsonObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | public static class JsonObjectExtensions 7 | { 8 | public static IDictionary ToDictionary(this object obj) 9 | { 10 | var json = JsonConvert.SerializeObject(obj ?? new object()); 11 | if (!json.StartsWith("{") && !json.EndsWith("}")) return new Dictionary(); 12 | var dictionary = JsonConvert.DeserializeObject>(json); 13 | return dictionary; 14 | } 15 | 16 | public static KeyValuePair ToKeyValuePair(this object obj) 17 | { 18 | var json = JsonConvert.SerializeObject(obj ?? new object()); 19 | if (!json.StartsWith("{") && !json.EndsWith("}")) return new KeyValuePair(); 20 | var pair = JsonConvert.DeserializeObject>(json); 21 | return pair; 22 | } 23 | 24 | public static object[] ToArrayObject(this object obj) 25 | { 26 | var json = JsonConvert.SerializeObject(obj ?? new object()); 27 | if (!json.StartsWith("[") && !json.EndsWith("]")) return null; 28 | return JsonConvert.DeserializeObject(json); 29 | 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/LinqExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace EspionSpotify.Extensions 6 | { 7 | public static class LinqExtensions 8 | { 9 | public static double Median(this IEnumerable source) 10 | { 11 | var sourceList = source.ToList(); 12 | if (!sourceList.Any()) throw new InvalidOperationException("Cannot compute median for an empty set."); 13 | 14 | var sortedList = (from number in sourceList orderby number select number).ToList(); 15 | 16 | var itemIndex = sortedList.Count() / 2; 17 | 18 | if (sortedList.Count % 2 == 0) 19 | return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2; 20 | return sortedList.ElementAt(itemIndex); 21 | } 22 | 23 | public static double Median(this IEnumerable source) 24 | { 25 | return (from num in source select (double) num).Median(); 26 | } 27 | 28 | public static double Median(this IEnumerable numbers, Func selector) 29 | { 30 | return (from num in numbers select selector(num)).Median(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/NAudioExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NAudio.CoreAudioApi; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | public static class NAudioExtensions 7 | { 8 | public static string GetFriendlyName(this MMDevice device) 9 | { 10 | string result; 11 | try 12 | { 13 | result = device.FriendlyName; 14 | } 15 | catch (Exception ex) 16 | { 17 | Console.WriteLine(ex.Message); 18 | return null; 19 | } 20 | 21 | return result; 22 | } 23 | 24 | public static MMDevice GetDefaultAudioEndpointSafeException(this MMDeviceEnumerator audioMMDevices, 25 | DataFlow dataflow, Role deviceRole, bool safe = true) 26 | { 27 | MMDevice device; 28 | try 29 | { 30 | device = audioMMDevices.GetDefaultAudioEndpoint(dataflow, deviceRole); 31 | } 32 | catch (Exception ex) 33 | { 34 | Console.WriteLine(ex.Message); 35 | if (!safe) 36 | { 37 | var message = 38 | "Failed to use your default audio device as a capture device. This can occur if your device has been unplugged, or the audio hardware have been reconfigured, disabled, removed, or otherwise made unavailable for use."; 39 | var exception = new Exception(message, ex); 40 | Program.ReportException(exception); 41 | } 42 | 43 | return null; 44 | } 45 | 46 | return device; 47 | } 48 | 49 | public static MMDevice GetDeviceSafeException(this MMDeviceEnumerator audioMMDevices, string deviceName, 50 | bool safe = true) 51 | { 52 | MMDevice device; 53 | try 54 | { 55 | device = audioMMDevices.GetDevice(deviceName); 56 | } 57 | catch (Exception ex) 58 | { 59 | Console.WriteLine(ex.Message); 60 | if (!safe) 61 | { 62 | var message = "Failed to use the specified audio device as a capture device."; 63 | var exception = new Exception(message, ex); 64 | Program.ReportException(exception); 65 | } 66 | 67 | return null; 68 | } 69 | 70 | return device; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/OperatingSystemExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Enums; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | public static class OperatingSystemExtensions 7 | { 8 | public static bool IsAtLeast(this OperatingSystem os, OSVersions version) 9 | { 10 | return os.Version.Build >= (int)version; 11 | } 12 | 13 | public static bool IsGreaterThan(this OperatingSystem os, OSVersions version) 14 | { 15 | return os.Version.Build > (int)version; 16 | } 17 | 18 | public static bool IsLessThan(this OperatingSystem os, OSVersions version) 19 | { 20 | return os.Version.Build < (int)version; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/ResourceManagerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using EspionSpotify.Enums; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | public static class ResourceManagerExtensions 7 | { 8 | public static string GetString(this ResourceManager rm, TranslationKeys key) 9 | { 10 | return rm.GetString(key.ToString()); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/SettingExtensions.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Enums; 2 | using EspionSpotify.Properties; 3 | 4 | namespace EspionSpotify.Extensions 5 | { 6 | internal static class SettingExtensions 7 | { 8 | internal static RecordRecordingsStatus GetRecordRecordingsStatus(this Settings settings) 9 | { 10 | if (settings.advanced_record_over_recordings_enabled) 11 | { 12 | if (settings.advanced_record_over_recordings_and_duplicate_enabled) 13 | return RecordRecordingsStatus.Duplicate; 14 | return RecordRecordingsStatus.Overwrite; 15 | } 16 | 17 | return RecordRecordingsStatus.Skip; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/SpotifyWebAPIExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SpotifyAPI.Web; 3 | using SpotifyAPI.Web.Models; 4 | 5 | namespace EspionSpotify.Extensions 6 | { 7 | public static class SpotifyWebAPIExtensions 8 | { 9 | public static async Task GetPlaybackWithoutExceptionAsync(this SpotifyWebAPI api) 10 | { 11 | PlaybackContext playback = null; 12 | try 13 | { 14 | playback = await api.GetPlaybackAsync(); 15 | } 16 | catch 17 | { 18 | // ignored 19 | } 20 | 21 | return playback; 22 | } 23 | 24 | public static async Task GetAlbumWithoutExceptionAsync(this SpotifyWebAPI api, string id) 25 | { 26 | FullAlbum album = null; 27 | try 28 | { 29 | album = await api.GetAlbumAsync(id); 30 | } 31 | catch 32 | { 33 | // ignored 34 | } 35 | 36 | return album; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using EspionSpotify.Enums; 7 | 8 | namespace EspionSpotify.Extensions 9 | { 10 | public static class StringExtensions 11 | { 12 | private static readonly Regex RegexVersion = new Regex(@"(\d+\.)(\d+\.)?(\d+\.)?(\*|\d+)"); 13 | private static readonly Regex RegexTag = new Regex(@"[^\d+\.]"); 14 | 15 | public static AlbumCoverSize? ToAlbumCoverSize(this string value) 16 | { 17 | return value.ToEnum(true); 18 | } 19 | 20 | public static LastFMNodeStatus? ToLastFMNodeStatus(this string value) 21 | { 22 | return value.ToEnum(true); 23 | } 24 | 25 | public static MediaFormat? ToMediaFormat(this string value) 26 | { 27 | return value.ToEnum(true); 28 | } 29 | 30 | public static ExternalAPIType? ToMediaTagsAPI(this string value) 31 | { 32 | return value.ToEnum(true); 33 | } 34 | 35 | public static IEnumerable ToPerformers(this string value) 36 | { 37 | var match = new Regex(@"\((with |feat\. )(?.*)\)").Match(value); 38 | var performers = match.Groups["performers"].ToString().Replace(" & ", ", "); 39 | return Regex.Split(performers, @", ").AsEnumerable(); 40 | } 41 | 42 | public static LanguageType? ToLanguageType(this string value) 43 | { 44 | return value.ToEnum(true); 45 | } 46 | 47 | public static int? ToNullableInt(this string value) 48 | { 49 | if (int.TryParse(value, out var i)) return i; 50 | return null; 51 | } 52 | 53 | public static string TrimEndPath(this string path) 54 | { 55 | return path?.Trim()?.TrimEnd(Path.GetInvalidFileNameChars()); 56 | } 57 | 58 | public static bool IsNullOrAdOrSpotifyIdleState(this string value) 59 | { 60 | return IsNullOrSpotifyIdleState(value) || IsSpotifyPlayingAnAd(value); 61 | } 62 | 63 | public static bool IsSpotifyPlayingAnAd(this string value) 64 | { 65 | return Constants.ADVERTISEMENT.ToLowerInvariant() == value.ToLowerInvariant(); 66 | } 67 | 68 | public static bool IsNullOrSpotifyIdleState(this string value) 69 | { 70 | return string.IsNullOrWhiteSpace(value) || value.IsSpotifyIdleState(); 71 | } 72 | 73 | public static bool IsSpotifyIdleState(this string value) 74 | { 75 | return new[] 76 | { 77 | Constants.SPOTIFY.ToLowerInvariant(), 78 | Constants.SPOTIFYFREE.ToLowerInvariant(), 79 | Constants.SPOTIFYPREMIUM.ToLowerInvariant() 80 | }.Contains(value.ToLowerInvariant()); 81 | } 82 | 83 | public static T? ToEnum(this string value, bool ignoreCase) where T : struct 84 | { 85 | var types = typeof(T); 86 | if (string.IsNullOrEmpty(value) || 87 | Enum.GetNames(types).All(x => x.ToLowerInvariant() != value.ToLowerInvariant())) return null; 88 | 89 | return (T) Enum.Parse(types, value, ignoreCase); 90 | } 91 | 92 | public static string ToVersionAsString(this string tag) 93 | { 94 | return string.IsNullOrEmpty(tag) ? string.Empty : RegexTag.Replace(tag, string.Empty); 95 | } 96 | 97 | public static Version ToVersion(this string value) 98 | { 99 | var versionString = value.ToVersionAsString(); 100 | 101 | if (string.IsNullOrEmpty(versionString) || !RegexVersion.IsMatch(versionString)) return null; 102 | 103 | return new Version(versionString); 104 | } 105 | 106 | public static string Capitalize(this string input) 107 | { 108 | if (string.IsNullOrWhiteSpace(input)) return input; 109 | return input.First().ToString().ToUpper() + input.Substring(1); 110 | } 111 | 112 | public static string ToMaxLength(this string input, int max = -1) 113 | { 114 | if (input.Length <= max || max == -1) return input; 115 | return input.Substring(0, max); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /EspionSpotify/Extensions/WaveFormatExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EspionSpotify.Enums; 3 | using NAudio.Wave; 4 | 5 | namespace EspionSpotify.Extensions 6 | { 7 | public static class WaveFormatExtensions 8 | { 9 | public static IEnumerable GetMP3RestrictionCode(this WaveFormat waveFormat) 10 | { 11 | var restrictions = new List(); 12 | if (waveFormat.Channels > Recorder.MP3_MAX_NUMBER_CHANNELS) 13 | restrictions.Add(WaveFormatMP3Restriction.Channel); 14 | if (waveFormat.SampleRate > Recorder.MP3_MAX_SAMPLE_RATE) 15 | restrictions.Add(WaveFormatMP3Restriction.SampleRate); 16 | 17 | return restrictions; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /EspionSpotify/GitHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using EspionSpotify.Extensions; 11 | using EspionSpotify.Models.GitHub; 12 | using EspionSpotify.Properties; 13 | using EspionSpotify.Translations; 14 | using MetroFramework; 15 | using Newtonsoft.Json; 16 | 17 | namespace EspionSpotify 18 | { 19 | internal static class GitHub 20 | { 21 | private const string API_LATEST_RELEASE_URL = 22 | "https://api.github.com/repos/jwallet/spy-spotify/releases/latest"; 23 | 24 | public const string WEBSITE_FAQ_URL = "https://jwallet.github.io/spy-spotify/faq.html"; 25 | 26 | public const string WEBSITE_FAQ_SPOTIFY_API_URL = 27 | "https://jwallet.github.io/spy-spotify/faq.html#media-tags-not-found"; 28 | 29 | public const string WEBSITE_DONATE_URL = "https://jwallet.github.io/spy-spotify/donate.html"; 30 | // public const string REPO_LATEST_RELEASE_URL = "https://github.com/jwallet/spy-spotify/releases/latest"; 31 | 32 | public static async Task GetVersion() 33 | { 34 | if (!Uri.TryCreate(API_LATEST_RELEASE_URL, UriKind.Absolute, out var uri)) return; 35 | 36 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 37 | var request = (HttpWebRequest) WebRequest.Create(uri); 38 | request.Method = WebRequestMethods.Http.Get; 39 | request.UserAgent = Constants.SPYTIFY; 40 | 41 | var content = new MemoryStream(); 42 | 43 | try 44 | { 45 | using (var response = (HttpWebResponse) await request.GetResponseAsync()) 46 | { 47 | if (response.StatusCode != HttpStatusCode.OK) return; 48 | 49 | using (var reader = response.GetResponseStream()) 50 | { 51 | if (reader != null) await reader.CopyToAsync(content); 52 | } 53 | 54 | var body = Encoding.UTF8.GetString(content.ToArray()); 55 | var release = JsonConvert.DeserializeObject(body); 56 | 57 | if (release == null || release.prerelease || release.draft) return; 58 | 59 | var githubTagVersion = release.tag_name.ToVersion(); 60 | var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version; 61 | 62 | if (githubTagVersion == null || githubTagVersion <= assemblyVersion) return; 63 | if (Settings.Default.app_last_version_prompt.ToVersion() == githubTagVersion) return; 64 | 65 | var dialogTitle = string.Format(FrmEspionSpotify.Instance.Rm.GetString(I18NKeys.MsgNewVersionTitle), 66 | githubTagVersion); 67 | var dialogMessage = FrmEspionSpotify.Instance.Rm.GetString(I18NKeys.MsgNewVersionContent); 68 | 69 | if (!string.IsNullOrEmpty(release.body)) 70 | { 71 | var releaseBodySplit = 72 | release.body.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None); 73 | dialogMessage = 74 | $"{releaseBodySplit.TakeWhile(x => x.StartsWith("- ")).Take(5).Aggregate((current, next) => $"{current}\n{next}")}\r\n\r\n{dialogMessage}"; 75 | } 76 | 77 | var dialogResult = MetroMessageBox.Show( 78 | FrmEspionSpotify.Instance, 79 | dialogMessage, 80 | dialogTitle, 81 | MessageBoxButtons.OKCancel, 82 | MessageBoxIcon.Question, 83 | 260); 84 | 85 | if (dialogResult == DialogResult.OK) Update(); 86 | 87 | Settings.Default.app_last_version_prompt = githubTagVersion.ToString(); 88 | Settings.Default.Save(); 89 | } 90 | } 91 | catch (Exception ex) 92 | { 93 | Console.WriteLine(ex.Message); 94 | } 95 | } 96 | 97 | public static void Update() 98 | { 99 | Process.Start(new ProcessStartInfo(Application.StartupPath + "/Updater/Updater.exe")); 100 | Application.Exit(); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /EspionSpotify/IFrmEspionSpotify.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using EspionSpotify.Enums; 3 | 4 | namespace EspionSpotify 5 | { 6 | public interface IFrmEspionSpotify 7 | { 8 | ResourceManager Rm { get; } 9 | void UpdateIconSpotify(bool isSpotifyPlaying, bool isRecording = false); 10 | void UpdatePlayingTitle(string text); 11 | void UpdateRecordedTime(int? time); 12 | void UpdateStartButton(); 13 | void StopRecording(); 14 | void UpdateNumUp(); 15 | void UpdateNumDown(); 16 | void WriteIntoConsole(TranslationKeys resource, params object[] args); 17 | } 18 | } -------------------------------------------------------------------------------- /EspionSpotify/IRecorder.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using EspionSpotify.Models; 4 | 5 | namespace EspionSpotify 6 | { 7 | public interface IRecorder 8 | { 9 | int CountSeconds { get; set; } 10 | Track Track { get; } 11 | bool Running { get; set; } 12 | 13 | Task Run(CancellationTokenSource token); 14 | 15 | void Dispose(); 16 | } 17 | } -------------------------------------------------------------------------------- /EspionSpotify/IWatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EspionSpotify.Spotify; 3 | 4 | namespace EspionSpotify 5 | { 6 | public interface IWatcher 7 | { 8 | int CountSeconds { get; set; } 9 | ISpotifyHandler Spotify { get; set; } 10 | 11 | bool RecorderUpAndRunning { get; } 12 | bool IsTypeAllowed { get; } 13 | 14 | Task Run(); 15 | } 16 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/AudioWaveBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Models 2 | { 3 | public class AudioWaveBuffer 4 | { 5 | public byte[] Buffer; 6 | public int BytesRecordedCount; 7 | public bool WithSilence; 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/GitHub/Asset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class Asset 7 | { 8 | public string url { get; set; } 9 | public int id { get; set; } 10 | public string node_id { get; set; } 11 | public string name { get; set; } 12 | public string lable { get; set; } 13 | public User uploader { get; set; } 14 | public string content_type { get; set; } 15 | public string state { get; set; } 16 | public int? size { get; set; } 17 | public int? download_count { get; set; } 18 | public DateTime? created_at { get; set; } 19 | public DateTime? updated_at { get; set; } 20 | public string browser_download_url { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/GitHub/Release.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class Release 7 | { 8 | public string url { get; set; } 9 | public string assets_url { get; set; } 10 | public string upload_url { get; set; } 11 | public string html_url { get; set; } 12 | public int id { get; set; } 13 | public string node_id { get; set; } 14 | public string tag_name { get; set; } 15 | public string target_commitish { get; set; } 16 | public bool draft { get; set; } 17 | public User author { get; set; } 18 | public bool prerelease { get; set; } 19 | public DateTime? created_at { get; set; } 20 | public DateTime? published_at { get; set; } 21 | public Asset[] assets { get; set; } 22 | public string tarball_url { get; set; } 23 | public string zipball_url { get; set; } 24 | public string body { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/GitHub/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Models.GitHub 4 | { 5 | [Serializable] 6 | internal class User 7 | { 8 | public string login { get; set; } 9 | public int id { get; set; } 10 | public string node_id { get; set; } 11 | public string avatar_url { get; set; } 12 | public string gravatar_id { get; set; } 13 | public string url { get; set; } 14 | public string html_url { get; set; } 15 | public string followers_url { get; set; } 16 | public string following_url { get; set; } 17 | public string gists_url { get; set; } 18 | public string starred_url { get; set; } 19 | public string subscriptions_url { get; set; } 20 | public string organizations_url { get; set; } 21 | public string repos_url { get; set; } 22 | public string events_url { get; set; } 23 | public string received_events_url { get; set; } 24 | public string type { get; set; } 25 | public bool site_admin { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/OutputFile.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Extensions; 2 | using EspionSpotify.Native; 3 | 4 | namespace EspionSpotify.Models 5 | { 6 | public class OutputFile 7 | { 8 | private const int FIRST_SONG_NAME_COUNT = 1; 9 | 10 | private string _file; 11 | 12 | public OutputFile() 13 | { 14 | Count = FIRST_SONG_NAME_COUNT; 15 | } 16 | 17 | public string MediaFile 18 | { 19 | get => _file; 20 | set => _file = Normalize.RemoveDiacritics(value); 21 | } 22 | 23 | public string BasePath { get; set; } 24 | public string FoldersPath { get; set; } 25 | private int Count { get; set; } 26 | public string Separator { get; set; } 27 | public string Extension { get; set; } 28 | 29 | internal void Increment() 30 | { 31 | Count++; 32 | } 33 | 34 | public string ToMediaFilePath() 35 | { 36 | if (_file.IsNullOrAdOrSpotifyIdleState()) return null; 37 | return FileManager.ConcatPaths(BasePath, FoldersPath, $"{_file}{GetAddedCount()}.{Extension}"); 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return _file.IsNullOrAdOrSpotifyIdleState() 43 | ? string.Empty 44 | : FileManager.ConcatPaths("..", FoldersPath, $"{_file}{GetAddedCount()}.{Extension}"); 45 | } 46 | 47 | private string GetAddedCount() 48 | { 49 | return Count > FIRST_SONG_NAME_COUNT ? $"{Separator}{Count}" : ""; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/RecorderTask.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace EspionSpotify.Models 5 | { 6 | public class RecorderTask 7 | { 8 | public Task Task { get; set; } 9 | public IRecorder Recorder { get; set; } 10 | public CancellationTokenSource Token { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/SpotifyWindowInfo.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Spotify; 2 | 3 | namespace EspionSpotify.Models 4 | { 5 | public class SpotifyWindowInfo 6 | { 7 | public string WindowTitle { get; set; } 8 | public bool IsPlaying { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /EspionSpotify/Models/UserSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Enums; 3 | using NAudio.Lame; 4 | 5 | namespace EspionSpotify.Models 6 | { 7 | public class UserSettings 8 | { 9 | public string OutputPath { get; set; } 10 | public LAMEPreset Bitrate { get; set; } 11 | public MediaFormat MediaFormat { get; set; } 12 | public int MinimumRecordedLengthSeconds { get; set; } 13 | public bool GroupByFoldersEnabled { get; set; } 14 | public string TrackTitleSeparator { get; set; } = " "; 15 | public bool OrderNumberInMediaTagEnabled { get; set; } 16 | public bool OrderNumberInfrontOfFileEnabled { get; set; } 17 | public bool AlbumTrackNumberInfrontOfFileEnabled { get; set; } 18 | public bool ListenToSpotifyPlaybackEnabled { get; set; } 19 | public bool ForceSpotifyToSkipEnabled { get; set; } 20 | public bool MuteAdsEnabled { get; set; } 21 | public bool MinimizeToSystemTrayEnabled { get; set; } 22 | public bool RecordEverythingEnabled { get; set; } 23 | public bool RecordAdsEnabled { get; set; } 24 | public bool ExtraTitleToSubtitleEnabled { get; set; } 25 | public int InternalOrderNumber { get; set; } = 1; 26 | public RecordRecordingsStatus RecordRecordingsStatus { get; set; } 27 | public string AudioEndPointDeviceID { get; set; } 28 | public string RecordingTimer { get; set; } 29 | public string SpotifyAPIClientId { get; set; } 30 | public string SpotifyAPISecretId { get; set; } 31 | public string SpotifyAPIRedirectURL { get; set; } 32 | public bool UpdateRecordingsID3TagsEnabled { get; set; } 33 | public string OrderNumberMask { get; set; } = "000"; 34 | public int OrderNumberMax => Convert.ToInt32(OrderNumberMask.Replace('0', '9')); 35 | 36 | public bool HasRecordingTimerEnabled => !string.IsNullOrEmpty(RecordingTimer) && RecordingTimer.Length == 6 && 37 | RecordingTimer != "000000"; 38 | 39 | public double RecordingTimerMilliseconds => 40 | HasRecordingTimerEnabled 41 | ? new TimeSpan( 42 | int.Parse(RecordingTimer.Substring(0, 2)), 43 | int.Parse(RecordingTimer.Substring(2, 2)), 44 | int.Parse(RecordingTimer.Substring(4, 2))).TotalMilliseconds 45 | : 0.0; 46 | 47 | public bool HasOrderNumberEnabled => OrderNumberInfrontOfFileEnabled || OrderNumberInMediaTagEnabled; 48 | 49 | public int? OrderNumberAsTag => OrderNumberInMediaTagEnabled ? (int?) InternalOrderNumber : null; 50 | 51 | public int? OrderNumberAsFile => OrderNumberInfrontOfFileEnabled 52 | ? (int?) Math.Min(InternalOrderNumber, OrderNumberMax) 53 | : null; 54 | 55 | public bool IsSpotifyAPISet => !string.IsNullOrWhiteSpace(SpotifyAPIClientId) && 56 | !string.IsNullOrWhiteSpace(SpotifyAPISecretId); 57 | } 58 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/Combase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace EspionSpotify.Native 5 | { 6 | /// 7 | /// Provides interoperability for "combase.dll". 8 | /// 9 | public static class Combase 10 | { 11 | /// 12 | /// Gets the activation factory for the specified runtime class. 13 | /// 14 | /// The ID of the activatable class. 15 | /// The reference ID of the interface. 16 | /// The activation factory. 17 | [DllImport("combase.dll", PreserveSig = false)] 18 | public static extern void RoGetActivationFactory( 19 | [MarshalAs(UnmanagedType.HString)] string activatableClassId, 20 | [In] ref Guid iid, 21 | [Out, MarshalAs(UnmanagedType.IInspectable)] out object factory); 22 | 23 | /// 24 | /// Creates a new HSTRING based on the specified source string. 25 | /// 26 | /// A null-terminated string to use as the source for the new HSTRING. To create a new, empty, or NULL string, pass NULL for sourceString and 0 for length. 27 | /// The length of sourceString, in Unicode characters. Must be 0 if sourceString is NULL. 28 | /// A pointer to the newly created HSTRING, or NULL if an error occurs. Any existing content in string is overwritten. The HSTRING is a standard handle type.. 29 | [DllImport("combase.dll", PreserveSig = false)] 30 | public static extern void WindowsCreateString( 31 | [MarshalAs(UnmanagedType.LPWStr)] string src, 32 | [In] uint length, 33 | [Out] out IntPtr hstring); 34 | } 35 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/IProcessManager.cs: -------------------------------------------------------------------------------- 1 | using EspionSpotify.Native.Models; 2 | 3 | namespace EspionSpotify.Native 4 | { 5 | public interface IProcessManager 6 | { 7 | IProcess GetCurrentProcess(); 8 | IProcess[] GetProcesses(); 9 | IProcess[] GetProcessesByName(string processName); 10 | IProcess GetProcessById(int processId); 11 | 12 | IProcess Start(string fileName); 13 | } 14 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/Models/HRESULT.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Native.Models 2 | { 3 | public enum HRESULT: uint 4 | { 5 | S_OK = 0x0, 6 | S_FALSE = 0x1, 7 | AUDCLNT_E_DEVICE_INVALIDATED = 0x88890004, 8 | AUDCLNT_S_NO_SINGLE_PROCESS = 0x889000d, 9 | ERROR_NOT_FOUND = 0x80070490, 10 | } 11 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/Models/IProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Native.Models 4 | { 5 | public interface IProcess 6 | { 7 | int Id { get; } 8 | string MainWindowTitle { get; } 9 | string ProcessName { get; } 10 | IntPtr MainWindowHandle { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/Models/Process.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EspionSpotify.Native.Models 4 | { 5 | public class Process : IProcess 6 | { 7 | public int Id { get; set; } 8 | public string MainWindowTitle { get; set; } 9 | public string ProcessName { get; set; } 10 | public IntPtr MainWindowHandle { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace EspionSpotify.Native 6 | { 7 | internal static class NativeMethods 8 | { 9 | internal static void PreventSleep() 10 | { 11 | SetThreadExecutionState(ExecutionState.EsContinuous | ExecutionState.EsSystemRequired); 12 | } 13 | 14 | internal static void AllowSleep() 15 | { 16 | SetThreadExecutionState(ExecutionState.EsContinuous); 17 | } 18 | 19 | internal static void SendKeyPessNextMedia(IntPtr process) 20 | { 21 | Task.Run(() => 22 | { 23 | SendMessage(process, 0x0319, IntPtr.Zero, new IntPtr((long)720896)); 24 | }); 25 | } 26 | 27 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 28 | private static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags); 29 | 30 | 31 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 32 | internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 33 | 34 | 35 | [FlagsAttribute] 36 | private enum ExecutionState : uint 37 | { 38 | EsAwaymodeRequired = 0x00000040, 39 | EsContinuous = 0x80000000, 40 | EsDisplayRequired = 0x00000002, 41 | EsSystemRequired = 0x00000001 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /EspionSpotify/Native/ProcessManager.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using EspionSpotify.Native.Models; 3 | using NativeProcess = System.Diagnostics.Process; 4 | 5 | namespace EspionSpotify.Native 6 | { 7 | public class ProcessManager : IProcessManager 8 | { 9 | public IProcess GetCurrentProcess() 10 | { 11 | NativeProcess process; 12 | 13 | try 14 | { 15 | process = NativeProcess.GetCurrentProcess(); 16 | } 17 | catch 18 | { 19 | return null; 20 | } 21 | 22 | return new Process 23 | { 24 | Id = process.Id, 25 | MainWindowTitle = process.MainWindowTitle, 26 | ProcessName = process.ProcessName 27 | }; 28 | } 29 | 30 | public IProcess[] GetProcesses() 31 | { 32 | NativeProcess[] processes; 33 | 34 | try 35 | { 36 | processes = NativeProcess.GetProcesses(); 37 | } 38 | catch 39 | { 40 | return new IProcess[] { }; 41 | } 42 | 43 | return processes.Select(x => new Process 44 | { 45 | Id = x.Id, 46 | MainWindowTitle = x.MainWindowTitle, 47 | ProcessName = x.ProcessName, 48 | MainWindowHandle = x.MainWindowHandle 49 | }).ToArray(); 50 | } 51 | 52 | public IProcess[] GetProcessesByName(string processName) 53 | { 54 | NativeProcess[] processes; 55 | 56 | try 57 | { 58 | processes = NativeProcess.GetProcessesByName(processName); 59 | } 60 | catch 61 | { 62 | return new IProcess[] { }; 63 | } 64 | 65 | return processes.Select(x => new Process 66 | { 67 | Id = x.Id, 68 | MainWindowTitle = x.MainWindowTitle, 69 | ProcessName = x.ProcessName 70 | }).ToArray(); 71 | } 72 | 73 | public IProcess GetProcessById(int processId) 74 | { 75 | NativeProcess process; 76 | 77 | try 78 | { 79 | process = NativeProcess.GetProcessById(processId); 80 | } 81 | catch 82 | { 83 | return null; 84 | } 85 | 86 | return new Process 87 | { 88 | Id = process.Id, 89 | MainWindowTitle = process.MainWindowTitle, 90 | ProcessName = process.ProcessName 91 | }; 92 | } 93 | 94 | public IProcess Start(string fileName) 95 | { 96 | NativeProcess process; 97 | 98 | try 99 | { 100 | process = NativeProcess.Start(fileName); 101 | } 102 | catch 103 | { 104 | return null; 105 | } 106 | 107 | if (process == null) return null; 108 | 109 | return new Process 110 | { 111 | Id = process.Id, 112 | MainWindowTitle = process.MainWindowTitle, 113 | ProcessName = process.ProcessName 114 | }; 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /EspionSpotify/Normalize.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace EspionSpotify 6 | { 7 | internal static class Normalize 8 | { 9 | public static string RemoveDiacritics(string text) 10 | { 11 | if (text == null) return string.Empty; 12 | 13 | var normalizedString = text.Trim().Normalize(NormalizationForm.FormD); 14 | var stringBuilder = new StringBuilder(); 15 | 16 | foreach (var c in normalizedString) 17 | if (!Path.GetInvalidFileNameChars().Contains(c)) 18 | stringBuilder.Append(c); 19 | 20 | return stringBuilder.ToString().Normalize(NormalizationForm.FormC); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /EspionSpotify/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Windows.Forms; 6 | using EspionSpotify.Properties; 7 | using ExceptionReporting; 8 | 9 | namespace EspionSpotify 10 | { 11 | internal static class Program 12 | { 13 | [STAThread] 14 | private static void Main() 15 | { 16 | // Add the event handler for handling UI thread exceptions to the event. 17 | Application.ThreadException += Application_ThreadException; 18 | 19 | // Set the unhandled exception mode to force all Windows Forms errors to go through our handler. 20 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 21 | 22 | // Add the event handler for handling non-UI thread exceptions to the event. 23 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 24 | 25 | Application.EnableVisualStyles(); 26 | Application.SetCompatibleTextRenderingDefault(false); 27 | Application.Run(new FrmEspionSpotify()); 28 | } 29 | 30 | // Handle the UI exceptions by showing a dialog box, and asking the user whether 31 | // or not they wish to abort execution. 32 | private static void Application_ThreadException(object sender, ThreadExceptionEventArgs t) 33 | { 34 | ReportException(t.Exception); 35 | } 36 | 37 | // Handle the UI exceptions by showing a dialog box, and asking the user whether 38 | // or not they wish to abort execution. 39 | // NOTE: This exception cannot be kept from terminating the application - it can only 40 | // log the event, and inform the user about it. 41 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 42 | { 43 | var thread = new Thread(() => { ReportException((Exception) e.ExceptionObject); }); 44 | thread.SetApartmentState(ApartmentState.STA); 45 | thread.IsBackground = true; 46 | thread.Start(); 47 | thread.Join(); 48 | } 49 | 50 | // Creates the error message and displays it. 51 | internal static void ReportException(Exception ex) 52 | { 53 | string template = null; 54 | try 55 | { 56 | template = @"# {{App.Title}} 57 | 58 | **Version**: {{App.Version}} 59 | **Region**: {{App.Region}} 60 | {{#if App.User}} 61 | **User**: {{App.User}} 62 | {{/if}} 63 | **Date**: {{Error.Date}} 64 | **Time**: {{Error.Time}} 65 | {{#if Error.Explanation}} 66 | **User Explanation**: {{Error.Explanation}} 67 | {{/if}} 68 | 69 | **Error Message**: {{Error.Message}} 70 | 71 | ## Stack Traces 72 | ```shell 73 | {{Error.FullStackTrace}} 74 | ``` 75 | 76 | ## Logs 77 | ```console 78 | {{Logs}} 79 | ``` 80 | 81 | ## Settings 82 | {{Settings}} 83 | 84 | ## Assembly References 85 | {{#App.AssemblyRefs}} 86 | - {{Name}}, Version={{Version}} 87 | {{/App.AssemblyRefs}} 88 | 89 | ## System Info 90 | ```console 91 | {{SystemInfo}} 92 | ``` 93 | ".Replace("{{Logs}}", string.Join("\n", Settings.Default.app_console_logs.Split(';'))) 94 | .Replace("{{Settings}}", GetSettings()); 95 | } 96 | catch 97 | { 98 | // ignored 99 | } 100 | 101 | var er = new ExceptionReporter 102 | { 103 | Config = 104 | { 105 | AppName = Application.ProductName, 106 | CompanyName = Application.CompanyName, 107 | WebServiceUrl = "https://exception-mailer.herokuapp.com/send", 108 | TitleText = "Exception Report", 109 | TakeScreenshot = true, 110 | SendMethod = ReportSendMethod.WebService, 111 | TopMost = true, 112 | ShowFlatButtons = true, 113 | ShowLessDetailButton = true, 114 | ReportCustomTemplate = !string.IsNullOrWhiteSpace(template) ? template : null, 115 | ReportTemplateFormat = TemplateFormat.Markdown 116 | } 117 | }; 118 | er.Show(ex); 119 | } 120 | 121 | private static string GetSettings() 122 | { 123 | var result = ""; 124 | var settings = Settings.Default.Properties; 125 | 126 | foreach (SettingsProperty setting in settings) 127 | { 128 | if (setting.Name == nameof(Settings.Default.app_console_logs)) continue; 129 | 130 | var isSecret = new[] 131 | { 132 | nameof(Settings.Default.app_spotify_api_client_id), 133 | nameof(Settings.Default.app_spotify_api_client_secret) 134 | }.Contains(setting.Name); 135 | 136 | var value = Settings.Default[setting.Name].ToString(); 137 | var secretValue = isSecret && !string.IsNullOrEmpty(value) 138 | ? value.Substring(0, Math.Min(value.Length, 4)).PadRight(28, '*') 139 | : value; 140 | 141 | result += $"**{setting.Name}**: {secretValue} \n"; 142 | } 143 | 144 | return result; 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /EspionSpotify/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // Allgemeine Informationen über eine Assembly werden über die folgenden 5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 6 | // die einer Assembly zugeordnet sind. 7 | [assembly: AssemblyTitle("Spytify")] 8 | [assembly: AssemblyDescription("Records Spotify while it plays without ads")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Spytify")] 11 | [assembly: AssemblyProduct("Spytify")] 12 | [assembly: AssemblyCopyright("github.com/jwallet")] 13 | [assembly: AssemblyTrademark("Spytify")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 17 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 18 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 19 | [assembly: ComVisible(false)] 20 | 21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 22 | [assembly: Guid("a02571b8-7ce5-4865-84f0-87613f895204")] 23 | 24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 25 | // 26 | // Hauptversion 27 | // Nebenversion 28 | // Buildnummer 29 | // Revision 30 | // 31 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 32 | // übernehmen, indem Sie "*" eingeben: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.12.0.0")] 35 | [assembly: AssemblyFileVersion("1.12.0.0")] 36 | -------------------------------------------------------------------------------- /EspionSpotify/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1 10 | 11 | 12 | 0 13 | 14 | 15 | 30 16 | 17 | 18 | False 19 | 20 | 21 | False 22 | 23 | 24 | False 25 | 26 | 27 | False 28 | 29 | 30 | en 31 | 32 | 33 | 1 34 | 35 | 36 | False 37 | 38 | 39 | True 40 | 41 | 42 | False 43 | 44 | 45 | 46 | 47 | 48 | False 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 0 58 | 59 | 60 | 61 | 62 | 63 | False 64 | 65 | 66 | 000 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | False 76 | 77 | 78 | False 79 | 80 | 81 | False 82 | 83 | 84 | http://localhost:4002 85 | 86 | 87 | False 88 | 89 | 90 | False 91 | 92 | 93 | False 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /EspionSpotify/Properties/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 50 | 51 | 52 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /EspionSpotify/RESET_APP_SETTINGS.BAT: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | ECHO Reset Spytify to its default settings. 3 | ECHO If you continue, it will remove all user settings from any Spytify current or older versions on your system. 4 | PAUSE 5 | DEL /S /q "%userprofile%\AppData\Local\Spytify\*" 6 | PAUSE 7 | -------------------------------------------------------------------------------- /EspionSpotify/Resources/add_device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/add_device.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/clear.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/clear.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/faq.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/faq.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/folder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/folder.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/heart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/heart.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/key.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/key.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/logo_en.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/logo_en.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/minus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/minus.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/off.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/off.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/on.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/on.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/pause.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/pause.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/play.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/play.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/plus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/plus.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/record.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/record.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/release.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/release.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/remove_device.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/remove_device.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/spotify.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/spotify.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/spytify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/spytify-logo.png -------------------------------------------------------------------------------- /EspionSpotify/Resources/spytify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/spytify.ico -------------------------------------------------------------------------------- /EspionSpotify/Resources/voldown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/voldown.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/volmute.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/volmute.gif -------------------------------------------------------------------------------- /EspionSpotify/Resources/volup.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/Resources/volup.gif -------------------------------------------------------------------------------- /EspionSpotify/Router/AudioRouter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Native; 3 | using EspionSpotify.Router.Helpers; 4 | using NAudio.CoreAudioApi; 5 | 6 | namespace EspionSpotify.Router 7 | { 8 | /// 9 | /// Provides a service for controlling and interacting with the audio device of an application. 10 | /// 11 | public class AudioRouter: IAudioRouter 12 | { 13 | /// 14 | /// The device interface string represents audio playback. 15 | /// 16 | private const string DEVINTERFACE_AUDIO_RENDER = "#{e6327cad-dcec-4949-ae8a-991e976a79d2}"; 17 | 18 | /// 19 | /// The device interface string represents audio recording. 20 | /// 21 | private const string DEVINTERFACE_AUDIO_CAPTURE = "#{2eef81be-33fa-4800-9670-1cd474972c3f}"; 22 | 23 | /// 24 | /// The MMDevice API token. 25 | /// 26 | private const string MMDEVAPI_TOKEN = @"\\?\SWD#MMDEVAPI#"; 27 | 28 | private IAudioPolicyConfigFactory _sharedPolicyConfig; 29 | private DataFlow _flow; 30 | 31 | public AudioRouter(DataFlow flow) 32 | { 33 | _flow = flow; 34 | } 35 | 36 | private void EnsurePolicyConfig() 37 | { 38 | if (_sharedPolicyConfig == null) 39 | { 40 | _sharedPolicyConfig = AudioPolicyConfigFactory.Create(); 41 | } 42 | } 43 | 44 | private string GenerateDeviceId(string deviceId) 45 | { 46 | return 47 | $"{MMDEVAPI_TOKEN}{deviceId}{(_flow == DataFlow.Render ? DEVINTERFACE_AUDIO_RENDER : DEVINTERFACE_AUDIO_CAPTURE)}"; 48 | } 49 | 50 | private string UnpackDeviceId(string deviceId) 51 | { 52 | if (deviceId.StartsWith(MMDEVAPI_TOKEN)) deviceId = deviceId.Remove(0, MMDEVAPI_TOKEN.Length); 53 | if (deviceId.EndsWith(DEVINTERFACE_AUDIO_RENDER)) 54 | deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_RENDER.Length); 55 | if (deviceId.EndsWith(DEVINTERFACE_AUDIO_CAPTURE)) 56 | deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_CAPTURE.Length); 57 | return deviceId; 58 | } 59 | 60 | public void SetDefaultEndPoint(string deviceId, int processId) 61 | { 62 | Console.WriteLine($"AudioPolicyConfigService SetDefaultEndPoint {deviceId} {processId}"); 63 | try 64 | { 65 | EnsurePolicyConfig(); 66 | 67 | var hstring = IntPtr.Zero; 68 | 69 | if (!string.IsNullOrWhiteSpace(deviceId)) 70 | { 71 | var str = GenerateDeviceId(deviceId); 72 | Combase.WindowsCreateString(str, (uint) str.Length, out hstring); 73 | } 74 | 75 | _sharedPolicyConfig.SetPersistedDefaultAudioEndpoint((uint) processId, _flow, Role.Multimedia, 76 | hstring); 77 | _sharedPolicyConfig.SetPersistedDefaultAudioEndpoint((uint) processId, _flow, Role.Console, hstring); 78 | } 79 | catch (Exception ex) 80 | { 81 | Console.WriteLine($"{ex}"); 82 | } 83 | } 84 | 85 | public string GetDefaultEndPoint(int processId) 86 | { 87 | try 88 | { 89 | EnsurePolicyConfig(); 90 | 91 | _sharedPolicyConfig.GetPersistedDefaultAudioEndpoint((uint) processId, _flow, 92 | role: Role.Multimedia | Role.Console, out string deviceId); 93 | return UnpackDeviceId(deviceId); 94 | } 95 | catch (Exception ex) 96 | { 97 | Console.WriteLine($"{ex}"); 98 | } 99 | 100 | return null; 101 | } 102 | 103 | public void ResetDefaultEndpoints() 104 | { 105 | try 106 | { 107 | EnsurePolicyConfig(); 108 | 109 | _sharedPolicyConfig.ClearAllPersistedApplicationDefaultEndpoints(); 110 | } 111 | catch (Exception ex) 112 | { 113 | Console.WriteLine($"{ex}"); 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /EspionSpotify/Router/Helpers/AudioPolicyConfigFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Enums; 3 | using EspionSpotify.Extensions; 4 | 5 | namespace EspionSpotify.Router.Helpers 6 | { 7 | public static class AudioPolicyConfigFactory 8 | { 9 | public static IAudioPolicyConfigFactory Create() 10 | { 11 | if (Environment.OSVersion.IsAtLeast(OSVersions.Version21H2)) 12 | { 13 | return new AudioPolicyConfigFactoryImplFor21H2(); 14 | } 15 | 16 | return new AudioPolicyConfigFactoryImplForDownlevel(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /EspionSpotify/Router/Helpers/AudioPolicyConfigFactoryImplFor21H2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Native; 3 | using EspionSpotify.Native.Models; 4 | using NAudio.CoreAudioApi; 5 | 6 | namespace EspionSpotify.Router.Helpers 7 | { 8 | public class AudioPolicyConfigFactoryImplFor21H2: IAudioPolicyConfigFactory 9 | { 10 | private readonly IAudioPolicyConfigFactory21H2 _factory; 11 | 12 | internal AudioPolicyConfigFactoryImplFor21H2() 13 | { 14 | var iid = typeof(IAudioPolicyConfigFactory21H2).GUID; 15 | Combase.RoGetActivationFactory("Windows.Media.Internal.AudioPolicyConfig", ref iid, out object factory); 16 | _factory = (IAudioPolicyConfigFactory21H2)factory; 17 | } 18 | 19 | public HRESULT ClearAllPersistedApplicationDefaultEndpoints() 20 | { 21 | return _factory.ClearAllPersistedApplicationDefaultEndpoints(); 22 | } 23 | 24 | public HRESULT GetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, out string deviceId) 25 | { 26 | return _factory.GetPersistedDefaultAudioEndpoint(processId, flow, role, out deviceId); 27 | } 28 | 29 | public HRESULT SetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, IntPtr deviceId) 30 | { 31 | return _factory.SetPersistedDefaultAudioEndpoint(processId, flow, role, deviceId); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /EspionSpotify/Router/Helpers/AudioPolicyConfigFactoryImplForDownlevel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Native; 3 | using EspionSpotify.Native.Models; 4 | using NAudio.CoreAudioApi; 5 | 6 | namespace EspionSpotify.Router.Helpers 7 | { 8 | public class AudioPolicyConfigFactoryImplForDownlevel: IAudioPolicyConfigFactory 9 | { 10 | private readonly IAudioPolicyConfigFactoryDownlevel _factory; 11 | 12 | internal AudioPolicyConfigFactoryImplForDownlevel() 13 | { 14 | var iid = typeof(IAudioPolicyConfigFactoryDownlevel).GUID; 15 | Combase.RoGetActivationFactory("Windows.Media.Internal.AudioPolicyConfig", ref iid, out object factory); 16 | _factory = (IAudioPolicyConfigFactoryDownlevel)factory; 17 | } 18 | 19 | public HRESULT ClearAllPersistedApplicationDefaultEndpoints() 20 | { 21 | return _factory.ClearAllPersistedApplicationDefaultEndpoints(); 22 | } 23 | 24 | public HRESULT GetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, out string deviceId) 25 | { 26 | return _factory.GetPersistedDefaultAudioEndpoint(processId, flow, role, out deviceId); 27 | } 28 | 29 | public HRESULT SetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, IntPtr deviceId) 30 | { 31 | return _factory.SetPersistedDefaultAudioEndpoint(processId, flow, role, deviceId); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /EspionSpotify/Router/Helpers/IAudioPolicyConfigFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EspionSpotify.Native.Models; 3 | using NAudio.CoreAudioApi; 4 | 5 | namespace EspionSpotify.Router.Helpers 6 | { 7 | /// 8 | /// Provides methods for controlling and interacting with audio endpoints for processes. 9 | /// 10 | public interface IAudioPolicyConfigFactory 11 | { 12 | /// 13 | /// Sets the persisted default audio endpoint for the specified process identifier. 14 | /// 15 | /// The process identifier. 16 | /// The flow. 17 | /// The role. 18 | /// The device identifier. 19 | /// The result of setting the default audio endpoint. 20 | HRESULT SetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, IntPtr deviceId); 21 | 22 | /// 23 | /// Gets the persisted default audio endpoint for the specified process identifier. 24 | /// 25 | /// The process identifier. 26 | /// The flow. 27 | /// The role. 28 | /// The device identifier. 29 | /// The result of getting the default audio endpoint. 30 | HRESULT GetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, out string deviceId); 31 | 32 | /// 33 | /// Clears all persisted application default endpoints. 34 | /// 35 | /// The result of clearing all persisted endpoints. 36 | HRESULT ClearAllPersistedApplicationDefaultEndpoints(); 37 | } 38 | } -------------------------------------------------------------------------------- /EspionSpotify/Router/IAudioPolicyConfigFactory21H2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using EspionSpotify.Native.Models; 4 | using NAudio.CoreAudioApi; 5 | 6 | namespace EspionSpotify.Router 7 | { 8 | /// 9 | /// Provides methods for controlling and interacting with audio endpoints for processes. 10 | /// 11 | [Guid("ab3d4648-e242-459f-b02f-541c70306324")] 12 | [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] 13 | public interface IAudioPolicyConfigFactory21H2 14 | { 15 | int __incomplete__add_CtxVolumeChange(); 16 | int __incomplete__remove_CtxVolumeChanged(); 17 | int __incomplete__add_RingerVibrateStateChanged(); 18 | int __incomplete__remove_RingerVibrateStateChange(); 19 | int __incomplete__SetVolumeGroupGainForId(); 20 | int __incomplete__GetVolumeGroupGainForId(); 21 | int __incomplete__GetActiveVolumeGroupForEndpointId(); 22 | int __incomplete__GetVolumeGroupsForEndpoint(); 23 | int __incomplete__GetCurrentVolumeContext(); 24 | int __incomplete__SetVolumeGroupMuteForId(); 25 | int __incomplete__GetVolumeGroupMuteForId(); 26 | int __incomplete__SetRingerVibrateState(); 27 | int __incomplete__GetRingerVibrateState(); 28 | int __incomplete__SetPreferredChatApplication(); 29 | int __incomplete__ResetPreferredChatApplication(); 30 | int __incomplete__GetPreferredChatApplication(); 31 | int __incomplete__GetCurrentChatApplications(); 32 | int __incomplete__add_ChatContextChanged(); 33 | int __incomplete__remove_ChatContextChanged(); 34 | 35 | /// 36 | /// Sets the persisted default audio endpoint for the specified process identifier. 37 | /// 38 | /// The process identifier. 39 | /// The flow. 40 | /// The role. 41 | /// The device identifier. 42 | /// The result of setting the default audio endpoint. 43 | [PreserveSig] 44 | HRESULT SetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, IntPtr deviceId); 45 | 46 | /// 47 | /// Gets the persisted default audio endpoint for the specified process identifier. 48 | /// 49 | /// The process identifier. 50 | /// The flow. 51 | /// The role. 52 | /// The device identifier. 53 | /// The result of getting the default audio endpoint. 54 | [PreserveSig] 55 | HRESULT GetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId); 56 | 57 | /// 58 | /// Clears all persisted application default endpoints. 59 | /// 60 | /// The result of clearing all persisted endpoints. 61 | [PreserveSig] 62 | HRESULT ClearAllPersistedApplicationDefaultEndpoints(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EspionSpotify/Router/IAudioPolicyConfigFactoryDownlevel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using EspionSpotify.Native.Models; 4 | using NAudio.CoreAudioApi; 5 | 6 | namespace EspionSpotify.Router 7 | { 8 | /// 9 | /// Provides methods for controlling and interacting with audio endpoints for processes. 10 | /// 11 | [Guid("2a59116d-6c4f-45e0-a74f-707e3fef9258")] 12 | [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] 13 | public interface IAudioPolicyConfigFactoryDownlevel 14 | { 15 | int __incomplete__add_CtxVolumeChange(); 16 | int __incomplete__remove_CtxVolumeChanged(); 17 | int __incomplete__add_RingerVibrateStateChanged(); 18 | int __incomplete__remove_RingerVibrateStateChange(); 19 | int __incomplete__SetVolumeGroupGainForId(); 20 | int __incomplete__GetVolumeGroupGainForId(); 21 | int __incomplete__GetActiveVolumeGroupForEndpointId(); 22 | int __incomplete__GetVolumeGroupsForEndpoint(); 23 | int __incomplete__GetCurrentVolumeContext(); 24 | int __incomplete__SetVolumeGroupMuteForId(); 25 | int __incomplete__GetVolumeGroupMuteForId(); 26 | int __incomplete__SetRingerVibrateState(); 27 | int __incomplete__GetRingerVibrateState(); 28 | int __incomplete__SetPreferredChatApplication(); 29 | int __incomplete__ResetPreferredChatApplication(); 30 | int __incomplete__GetPreferredChatApplication(); 31 | int __incomplete__GetCurrentChatApplications(); 32 | int __incomplete__add_ChatContextChanged(); 33 | int __incomplete__remove_ChatContextChanged(); 34 | 35 | /// 36 | /// Sets the persisted default audio endpoint for the specified process identifier. 37 | /// 38 | /// The process identifier. 39 | /// The flow. 40 | /// The role. 41 | /// The device identifier. 42 | /// The result of setting the default audio endpoint. 43 | [PreserveSig] 44 | HRESULT SetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, IntPtr deviceId); 45 | 46 | /// 47 | /// Gets the persisted default audio endpoint for the specified process identifier. 48 | /// 49 | /// The process identifier. 50 | /// The flow. 51 | /// The role. 52 | /// The device identifier. 53 | /// The result of getting the default audio endpoint. 54 | [PreserveSig] 55 | HRESULT GetPersistedDefaultAudioEndpoint(uint processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId); 56 | 57 | /// 58 | /// Clears all persisted application default endpoints. 59 | /// 60 | /// The result of clearing all persisted endpoints. 61 | [PreserveSig] 62 | HRESULT ClearAllPersistedApplicationDefaultEndpoints(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EspionSpotify/Router/IAudioRouter.cs: -------------------------------------------------------------------------------- 1 | namespace EspionSpotify.Router 2 | { 3 | /// 4 | /// Provides a service for controlling and interacting with the audio device of an application. 5 | /// 6 | public interface IAudioRouter 7 | { 8 | /// 9 | /// Gets the default audio device for the specified process. 10 | /// 11 | /// The process id. 12 | /// The audio device; otherwise null. 13 | string GetDefaultEndPoint(int processId); 14 | 15 | /// 16 | /// Sets the default audio device of an application. 17 | /// 18 | /// The device id. 19 | /// The process to update. 20 | void SetDefaultEndPoint(string deviceId, int processId); 21 | 22 | /// 23 | /// Resets the default audio devices previously set. 24 | /// 25 | void ResetDefaultEndpoints(); 26 | } 27 | } -------------------------------------------------------------------------------- /EspionSpotify/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Configuration; 3 | 4 | namespace EspionSpotify.Properties 5 | { 6 | // Cette classe vous permet de gérer des événements spécifiques dans la classe de paramètres : 7 | // L'événement SettingChanging est déclenché avant la modification d'une valeur de paramètre. 8 | // L'événement PropertyChanged est déclenché après la modification d'une valeur de paramètre. 9 | // L'événement SettingsLoaded est déclenché après le chargement des valeurs de paramètre. 10 | // L'événement SettingsSaving est déclenché avant l'enregistrement des valeurs de paramètre. 11 | internal sealed partial class Settings 12 | { 13 | private void SettingChangingEventHandler(object sender, SettingChangingEventArgs e) 14 | { 15 | // Ajouter du code pour gérer l'événement SettingChangingEvent ici. 16 | } 17 | 18 | private void SettingsSavingEventHandler(object sender, CancelEventArgs e) 19 | { 20 | // Ajouter du code pour gérer l'événement SettingsSaving ici. 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/ISpotifyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Timers; 4 | using EspionSpotify.Events; 5 | using EspionSpotify.Models; 6 | 7 | namespace EspionSpotify.Spotify 8 | { 9 | public interface ISpotifyHandler 10 | { 11 | Timer EventTimer { get; } 12 | Timer SongTimer { get; } 13 | ISpotifyProcess SpotifyProcess { get; } 14 | ISpotifyStatus SpotifyLatestStatus { get; } 15 | bool ListenForEvents { get; set; } 16 | 17 | Track Track { get; set; } 18 | 19 | event EventHandler OnTrackChange; 20 | event EventHandler OnPlayStateChange; 21 | event EventHandler OnTrackTimeChange; 22 | 23 | Task GetTrack(); 24 | Task TriggerEvents(); 25 | 26 | void Dispose(); 27 | } 28 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/ISpotifyProcess.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace EspionSpotify.Spotify 4 | { 5 | public interface ISpotifyProcess 6 | { 7 | Task GetSpotifyStatus(); 8 | } 9 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/ISpotifyStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using EspionSpotify.Models; 3 | 4 | namespace EspionSpotify.Spotify 5 | { 6 | public interface ISpotifyStatus 7 | { 8 | Track CurrentTrack { get; set; } 9 | 10 | Task GetTrack(); 11 | } 12 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/SpotifyConnect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.IO.Abstractions; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace EspionSpotify.Spotify 9 | { 10 | public static class SpotifyConnect 11 | { 12 | private static readonly TimeSpan RunSpotifyInterval = TimeSpan.FromSeconds(3); 13 | 14 | private static readonly string[] SpotifyPossiblePaths = 15 | { 16 | // app store 17 | Path.Combine( 18 | Environment.GetFolderPath( 19 | Environment.SpecialFolder.LocalApplicationData), 20 | @"Microsoft\WindowsApps\Spotify.exe"), 21 | // installer 22 | Path.Combine( 23 | Environment.GetFolderPath( 24 | Environment.SpecialFolder.ApplicationData), 25 | @"\Spotify\Spotify.exe"), 26 | // custom install 27 | Path.Combine( 28 | Environment.GetFolderPath( 29 | Environment.SpecialFolder.ProgramFiles), 30 | @"Spotify\Spotify.exe") 31 | }; 32 | 33 | public static async Task Run(IFileSystem fileSystem) 34 | { 35 | if (!IsSpotifyInstalled(fileSystem)) return; 36 | 37 | for (var tries = 5; tries > 0; tries--) 38 | { 39 | if (RunSpotify(fileSystem)) break; 40 | 41 | await Task.Delay(RunSpotifyInterval); 42 | } 43 | } 44 | 45 | private static bool RunSpotify(IFileSystem fileSystem) 46 | { 47 | if (!IsSpotifyRunning()) 48 | try 49 | { 50 | foreach (var path in SpotifyPossiblePaths) 51 | { 52 | if (!fileSystem.File.Exists(path)) continue; 53 | Process.Start(path); 54 | break; 55 | } 56 | } 57 | catch (Exception ex) 58 | { 59 | Console.WriteLine(ex.Message); 60 | return false; 61 | } 62 | 63 | return IsSpotifyRunning(); 64 | } 65 | 66 | public static bool IsSpotifyInstalled(IFileSystem fileSystem) 67 | { 68 | return SpotifyPossiblePaths.Any(path => fileSystem.File.Exists(path)); 69 | } 70 | 71 | public static bool IsSpotifyRunning() 72 | { 73 | try 74 | { 75 | return Process.GetProcessesByName(Constants.SPOTIFY).Length >= 1; 76 | } 77 | catch 78 | { 79 | return false; 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/SpotifyProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using EspionSpotify.AudioSessions; 6 | using EspionSpotify.Extensions; 7 | using EspionSpotify.Models; 8 | using EspionSpotify.Native; 9 | using EspionSpotify.Native.Models; 10 | using Process = System.Diagnostics.Process; 11 | 12 | namespace EspionSpotify.Spotify 13 | { 14 | public class SpotifyProcess : ISpotifyProcess 15 | { 16 | private readonly IMainAudioSession _audioSession; 17 | private readonly IProcessManager _processManager; 18 | private int? _spotifyProcessId; 19 | 20 | internal SpotifyProcess(IMainAudioSession audioSession) : 21 | this(audioSession, new ProcessManager()) 22 | { 23 | } 24 | 25 | public SpotifyProcess(IMainAudioSession audioSession, IProcessManager processManager) 26 | { 27 | _processManager = processManager; 28 | _audioSession = audioSession; 29 | _spotifyProcessId = GetMainSpotifyProcess(_processManager)?.Id; 30 | } 31 | 32 | public async Task GetSpotifyStatus() 33 | { 34 | var (processTitle, isSpotifyAudioPlaying) = await GetSpotifyTitle(); 35 | var isWindowTitledSpotify = processTitle.IsNullOrSpotifyIdleState(); 36 | 37 | if (string.IsNullOrWhiteSpace(processTitle)) return null; 38 | 39 | var spotifyWindowInfo = new SpotifyWindowInfo 40 | { 41 | WindowTitle = processTitle, 42 | IsPlaying = isSpotifyAudioPlaying || !isWindowTitledSpotify 43 | }; 44 | 45 | return new SpotifyStatus(spotifyWindowInfo); 46 | } 47 | 48 | private async Task<(string, bool)> GetSpotifyTitle() 49 | { 50 | string mainWindowTitle = null; 51 | var isSpotifyAudioPlaying = false; 52 | 53 | if (_spotifyProcessId.HasValue) 54 | try 55 | { 56 | isSpotifyAudioPlaying = await _audioSession.IsSpotifyCurrentlyPlaying(); 57 | var process = _processManager.GetProcessById(_spotifyProcessId.Value); 58 | mainWindowTitle = process?.MainWindowTitle ?? ""; 59 | } 60 | catch (Exception ex) 61 | { 62 | Console.WriteLine(ex.Message); 63 | } 64 | else 65 | _spotifyProcessId = GetMainSpotifyProcess(_processManager)?.Id; 66 | 67 | return (mainWindowTitle, isSpotifyAudioPlaying); 68 | } 69 | 70 | internal static ICollection GetSpotifyProcesses(IProcessManager processManager) 71 | { 72 | return processManager.GetProcesses().Where(x => x.ProcessName.IsSpotifyIdleState()).ToList(); 73 | } 74 | 75 | private static IProcess GetMainSpotifyProcess(IProcessManager processManager) 76 | { 77 | return processManager.GetProcesses() 78 | .FirstOrDefault(x => x.ProcessName.IsSpotifyIdleState() && !string.IsNullOrEmpty(x.MainWindowTitle)); 79 | } 80 | 81 | public static IntPtr? GetMainSpotifyHandler(IProcessManager processManager) 82 | { 83 | return GetMainSpotifyProcess(processManager)?.MainWindowHandle; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /EspionSpotify/Spotify/SpotifyStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using EspionSpotify.API; 5 | using EspionSpotify.Enums; 6 | using EspionSpotify.Extensions; 7 | using EspionSpotify.Models; 8 | 9 | namespace EspionSpotify.Spotify 10 | { 11 | public class SpotifyStatus : ISpotifyStatus 12 | { 13 | public SpotifyStatus(SpotifyWindowInfo spotifyWindowInfo) 14 | { 15 | var tags = GetDashTags(spotifyWindowInfo.WindowTitle, 2); 16 | var longTitlePart = GetTitleTag(tags, 2); 17 | var (titleTags, separatorType) = GetTitleTags(longTitlePart ?? "", 2); 18 | 19 | var isPlaying = spotifyWindowInfo.IsPlaying; 20 | var isLookingLikeAnAd = tags.Length < 2; 21 | 22 | CurrentTrack = new Track 23 | { 24 | Ad = spotifyWindowInfo.WindowTitle.IsSpotifyPlayingAnAd() || (isLookingLikeAnAd && isPlaying), 25 | Playing = isPlaying, 26 | Artist = GetTitleTag(tags, 1), 27 | Title = GetTitleTag(titleTags, 1), 28 | TitleExtended = GetTitleTag(titleTags, 2), 29 | TitleExtendedSeparatorType = separatorType 30 | }; 31 | } 32 | 33 | public Track CurrentTrack { get; set; } 34 | 35 | public async Task GetTrack() 36 | { 37 | if (!CurrentTrack.IsNormalPlaying) 38 | { 39 | await ExternalAPI.Instance.Authenticate(); 40 | return CurrentTrack; 41 | } 42 | 43 | _ = Task.Run(async () => 44 | { 45 | await Task.Delay(1000); 46 | CurrentTrack.MetaDataUpdated = await ExternalAPI.Instance.UpdateTrack(CurrentTrack); 47 | }); 48 | 49 | return CurrentTrack; 50 | } 51 | 52 | public static string[] GetDashTags(string title, int maxSize = 3) 53 | { 54 | return title.Split(new[] {" - "}, maxSize, StringSplitOptions.RemoveEmptyEntries); 55 | } 56 | 57 | public static (string[], TitleSeparatorType) GetTitleTags(string title, int maxSize = 2) 58 | { 59 | if (string.IsNullOrWhiteSpace(title)) return (null, TitleSeparatorType.None); 60 | 61 | var byDash = GetDashTags(title, maxSize); 62 | var byParenthesis = title.Split(new[] {" ("}, maxSize, StringSplitOptions.RemoveEmptyEntries); 63 | if (byParenthesis.Length == 2) byParenthesis[1] = byParenthesis[1].Replace(")", ""); 64 | 65 | if (byDash.Length > 1) return (byDash, TitleSeparatorType.Dash); 66 | if (byParenthesis.Length > 1) return (byParenthesis, TitleSeparatorType.Parenthesis); 67 | 68 | return (new[] {title}, TitleSeparatorType.None); 69 | } 70 | 71 | public static string GetTitleTag(string[] tags, int maxValue) 72 | { 73 | return tags != null && tags.Length >= maxValue && maxValue != 0 ? tags[maxValue - 1] ?? null : null; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /EspionSpotify/Translations/Languages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using EspionSpotify.Enums; 5 | 6 | namespace EspionSpotify.Translations 7 | { 8 | public static class Languages 9 | { 10 | private static readonly Dictionary AvailableResourcesManager = 11 | new Dictionary 12 | { 13 | {LanguageType.en, typeof(en)}, 14 | {LanguageType.fr, typeof(fr)} 15 | }; 16 | 17 | internal static readonly Dictionary DropdownListValues = 18 | new Dictionary 19 | { 20 | {LanguageType.en, "English"}, 21 | {LanguageType.fr, "Français"} // French 22 | }; 23 | 24 | public static Type GetResourcesManagerLanguageType(LanguageType? type) 25 | { 26 | return AvailableResourcesManager.Where(x => x.Key.Equals(type ?? LanguageType.en)).Select(x => x.Value) 27 | .Single(); 28 | } 29 | 30 | public static KeyValuePair GetDropdownListItemFromLanguageType(LanguageType? type) 31 | { 32 | return DropdownListValues.Single(x => x.Key == (type ?? LanguageType.en)); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /EspionSpotify/Translations/README.MD: -------------------------------------------------------------------------------- 1 | https://translate.zanata.org/ 2 | spytify - spy-spotify 3 | default.json 4 | http://converter.webtranslateit.com/ 5 | json to resx -------------------------------------------------------------------------------- /EspionSpotify/Updater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | using EspionSpotify.Properties; 6 | 7 | namespace EspionSpotify 8 | { 9 | internal static class Updater 10 | { 11 | private const string UPDATER_DIRECTORY = "Updater"; 12 | private const string UPDATER_TMP_DIRECTORY = "tmp_updater"; 13 | private static readonly string ProjectDirectory = AppDomain.CurrentDomain.BaseDirectory; 14 | 15 | private static readonly string UpdaterExtractedTempDirectoryPath = $"{ProjectDirectory}{UPDATER_TMP_DIRECTORY}"; 16 | 17 | private static readonly string UpdaterExtractedContentDirectoryPath = 18 | $@"{UpdaterExtractedTempDirectoryPath}\{UPDATER_DIRECTORY}"; 19 | 20 | private static readonly string UpdaterDirectoryPath = $"{ProjectDirectory}{UPDATER_DIRECTORY}"; 21 | 22 | internal static void UpgradeSettings() 23 | { 24 | if (!Directory.Exists(UpdaterExtractedContentDirectoryPath)) return; 25 | Settings.Default.Upgrade(); 26 | Settings.Default.Save(); 27 | UpdateDirectoryContent(); 28 | DeleteOlderUserSettings(); 29 | } 30 | 31 | private static void UpdateDirectoryContent() 32 | { 33 | try 34 | { 35 | Directory.Delete(UpdaterDirectoryPath, true); 36 | Directory.Move(UpdaterExtractedContentDirectoryPath, UpdaterDirectoryPath); 37 | Directory.Delete(UpdaterExtractedTempDirectoryPath); 38 | } 39 | catch 40 | { 41 | // ignored 42 | } 43 | } 44 | 45 | private static void DeleteOlderUserSettings() 46 | { 47 | var path = Path.GetFullPath(Path.Combine( 48 | ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath, 49 | @"..\..\")); 50 | var settingPaths = Directory.GetDirectories(path); 51 | foreach (var settingPath in settingPaths) 52 | { 53 | var info = new DirectoryInfo(settingPath); 54 | if (info.Name != Application.ProductVersion) Directory.Delete(settingPath, true); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /EspionSpotify/libmp3lame.32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/libmp3lame.32.dll -------------------------------------------------------------------------------- /EspionSpotify/libmp3lame.64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/EspionSpotify/libmp3lame.64.dll -------------------------------------------------------------------------------- /EspionSpotify/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /EspionSpotify/setup-spotify-api.txt: -------------------------------------------------------------------------------- 1 | Go to https://developer.spotify.com/dashboard/applications/ and register a new application 2 | - (e.g.:) Name: spotify, description: none, building: i don't know 3 | - Accept the terms 4 | Click on the new spotify application that you created and click on Edit the settings 5 | - Set as "Redirect URI" the value "http://localhost:4002", Add and save it. 6 | On the spotify app dashboard, get 7 | - The "Client ID" 8 | - The "Client Secret" (click on Show) 9 | Paste the ids below 10 | - (e.g.:) 11 | 1234567890 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Spytify by jwallet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/appveyor.yml -------------------------------------------------------------------------------- /psd/features_icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/features_icons.psd -------------------------------------------------------------------------------- /psd/icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/icons.psd -------------------------------------------------------------------------------- /psd/logo-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/logo-en.png -------------------------------------------------------------------------------- /psd/logo-fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/logo-fr.png -------------------------------------------------------------------------------- /psd/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/logo.png -------------------------------------------------------------------------------- /psd/logo256px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/logo256px.png -------------------------------------------------------------------------------- /psd/logos_sp.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/logos_sp.psd -------------------------------------------------------------------------------- /psd/on-off.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/on-off.psd -------------------------------------------------------------------------------- /psd/spytify-espion-spotify-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/spytify-espion-spotify-logo-small.png -------------------------------------------------------------------------------- /psd/spytify-espion-spotify-logo-small.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/spytify-espion-spotify-logo-small.psd -------------------------------------------------------------------------------- /psd/spytify-espion-spotify-logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwallet/spy-spotify/5a8f62c4c3144b1eebed9b90a3f544bfa2cea37f/psd/spytify-espion-spotify-logo.psd --------------------------------------------------------------------------------