├── .gitattributes ├── .gitignore ├── MultiSourceTorrentDownloader.sln ├── MultiSourceTorrentDownloader ├── App.config ├── App.xaml ├── App.xaml.cs ├── Behaviors │ ├── BindHtmlBehavior.cs │ ├── CloseAutoCompleteDropDownBehavior.cs │ ├── PersistanceBehavior.cs │ └── TorrentInfoSizeReductionBehavior.cs ├── Common │ ├── Command.cs │ └── ContainerConfig.cs ├── Constants │ ├── KickassTorrentIndexer.cs │ ├── LeetxTorrentIndexer.cs │ ├── RargbTorrentIndexer.cs │ ├── SizePostfix.cs │ └── ThePirateBayTorrentIndexer.cs ├── Converters │ ├── BooleanToBackgroundColorConverter.cs │ ├── MessageTypeToBackgroundConverter.cs │ ├── MessageTypeToStatusBarVisibilityConverter.cs │ ├── TimeFormatConverter.cs │ └── TorrentCategoryToBooleanConverter.cs ├── Data │ ├── DisplaySource.cs │ ├── KickassSorting.cs │ ├── LeetxSorting.cs │ ├── RargbSorting.cs │ ├── Settings.cs │ ├── SizeEntity.cs │ ├── SourceInformation.cs │ ├── SourceState.cs │ ├── SourceStateUI.cs │ ├── SourceToProcess.cs │ ├── TorrentEntry.cs │ ├── TorrentPaging.cs │ └── TorrentQueryResult.cs ├── Enums │ ├── MessageType.cs │ ├── Sorting.cs │ ├── ThePirateBaySorting.cs │ ├── TorrentCategory.cs │ └── TorrentSource.cs ├── Extensions │ └── ObservableCollectionForEachExtension.cs ├── Interfaces │ ├── IAutoCompleteService.cs │ ├── IKickassParser.cs │ ├── IKickassSource.cs │ ├── ILeetxParser.cs │ ├── ILeetxSource.cs │ ├── ILogService.cs │ ├── IRargbParser.cs │ ├── IRargbSource.cs │ ├── IThePirateBayParser.cs │ ├── IThePirateBaySource.cs │ ├── ITorrentDataSource.cs │ ├── ITorrentParser.cs │ └── IUserConfiguration.cs ├── Mapping │ ├── SortingMapper.cs │ └── TorrentCategoryMapper.cs ├── Models │ ├── DataModelBase.cs │ ├── MainModel.cs │ ├── MainModel.g.cs │ ├── ModelBase.cs │ ├── NotificaitonPropertyChangedExtensions.cs │ ├── SubModels │ │ ├── MainModelSettings.cs │ │ └── MainModelSettings.g.cs │ ├── TorrentInfoDialogModel.cs │ └── TorrentInfoDialogModel.g.cs ├── MultiSourceTorrentDownloader.csproj ├── Properties │ └── AssemblyInfo.cs ├── Services │ ├── AutoCompleteService.cs │ ├── KickassParser.cs │ ├── KickassSource.cs │ ├── LeetxParser.cs │ ├── LeetxSource.cs │ ├── LogService.cs │ ├── ParserBase.cs │ ├── RargbParser.cs │ ├── RargbSource.cs │ ├── SourceBase.cs │ ├── ThePirateBayParser.cs │ ├── ThePirateBaySource.cs │ └── UserConfiguration.cs ├── ViewModels │ ├── MainViewModel.cs │ ├── TorrentInfoDialogViewModel.cs │ └── ViewModelBase.cs ├── Views │ ├── DrawerView.xaml │ ├── DrawerView.xaml.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── StatusBar.xaml │ ├── StatusBar.xaml.cs │ ├── TorrentInfoDialogView.xaml │ └── TorrentInfoDialogView.xaml.cs ├── app.manifest ├── icon.ico ├── packages.config └── splash.png ├── README.md ├── TorrentSourceTemplate.zip ├── demo ├── details.png ├── launched.png ├── menu.png └── searched.png ├── icon.ico ├── icon_big.png ├── license.txt └── splash.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore App.xaml.cs which contains secrets 2 | MultiSourceTorrentDownloader/Secret.cs 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | .vs 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | # Python Tools for Visual Studio (PTVS) 264 | __pycache__/ 265 | *.pyc -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29503.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiSourceTorrentDownloader", "MultiSourceTorrentDownloader\MultiSourceTorrentDownloader.csproj", "{2483D14D-06BF-4533-8FD8-A53CCBDC8522}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release|Any CPU = Release|Any CPU 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Debug|x86.ActiveCfg = Debug|x86 19 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Debug|x86.Build.0 = Debug|x86 20 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Release|x86.ActiveCfg = Release|x86 23 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {AF8DE5A1-B5E6-4206-9F69-0ABAFEED43EF} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/App.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Interfaces; 2 | using Ninject; 3 | using System.Reflection; 4 | using System.Windows; 5 | 6 | namespace MultiSourceTorrentDownloader 7 | { 8 | /// 9 | /// Interaction logic for App.xaml 10 | /// 11 | public partial class App : Application 12 | { 13 | private ILogService _logger; 14 | public App() 15 | { 16 | 17 | var kernel = new StandardKernel(); 18 | kernel.Load(Assembly.GetExecutingAssembly()); 19 | _logger = kernel.Get(); 20 | 21 | Current.DispatcherUnhandledException += HandleDispatcherUnhandledException; 22 | //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 23 | //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 24 | //Secret.SyncFususionLicenseKey is a const string containing the community license for the project. 25 | //Please replace with stringor create a file Secret.cs with a const string referring to a license. 26 | //If Secret.cs is created in same directory as App.xaml.cs, gitignore will not commit the secret. 27 | //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 28 | //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 29 | Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(Secret.SyncFususionLicenseKey); 30 | } 31 | 32 | private void HandleDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) 33 | { 34 | _logger.Error($"Unhandled exception occured: {e.Exception.StackTrace}"); 35 | Current.Shutdown(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Behaviors/BindHtmlBehavior.cs: -------------------------------------------------------------------------------- 1 | using CefSharp; 2 | using CefSharp.Wpf; 3 | using System.Windows; 4 | using System.Windows.Interactivity; 5 | 6 | namespace MultiSourceTorrentDownloader.Behaviors 7 | { 8 | public class BindHtmlBehavior : Behavior 9 | { 10 | private const string _dynamicImageStyle = ""; 11 | 12 | public static readonly DependencyProperty HtmlProperty = 13 | DependencyProperty.Register("Html", typeof(string), typeof(BindHtmlBehavior)); 14 | 15 | public string Html 16 | { 17 | get { return (string)GetValue(HtmlProperty); } 18 | set { SetValue(HtmlProperty, value); } 19 | } 20 | 21 | protected override void OnAttached() 22 | { 23 | AssociatedObject.IsBrowserInitializedChanged += IsBrowserInitializedChanged; 24 | } 25 | 26 | private void IsBrowserInitializedChanged(object sender, DependencyPropertyChangedEventArgs e) 27 | { 28 | if (AssociatedObject.IsBrowserInitialized) 29 | { 30 | var fitToScreenHtml = $"{_dynamicImageStyle}{Html ?? string.Empty}"; 31 | AssociatedObject.LoadHtml(fitToScreenHtml); 32 | } 33 | } 34 | 35 | protected override void OnDetaching() 36 | { 37 | AssociatedObject.IsBrowserInitializedChanged -= IsBrowserInitializedChanged; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Behaviors/CloseAutoCompleteDropDownBehavior.cs: -------------------------------------------------------------------------------- 1 | using Syncfusion.Windows.Controls.Input; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Interactivity; 8 | 9 | namespace MultiSourceTorrentDownloader.Behaviors 10 | { 11 | public class CloseAutoCompleteDropDownBehavior : Behavior 12 | { 13 | protected override void OnAttached() 14 | { 15 | AssociatedObject.LostKeyboardFocus += OnLostFocus; 16 | 17 | base.OnAttached(); 18 | } 19 | 20 | private void OnLostFocus(object sender, System.Windows.RoutedEventArgs e) 21 | { 22 | AssociatedObject.IsSuggestionOpen = false; 23 | } 24 | 25 | protected override void OnDetaching() 26 | { 27 | AssociatedObject.LostKeyboardFocus -= OnLostFocus; 28 | 29 | base.OnDetaching(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Behaviors/PersistanceBehavior.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Interfaces; 3 | using Ninject; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows; 11 | using System.Windows.Interactivity; 12 | 13 | namespace MultiSourceTorrentDownloader.Behaviors 14 | { 15 | public class PersistanceBehavior : Behavior 16 | { 17 | private IUserConfiguration _config; 18 | protected override void OnAttached() 19 | { 20 | AssociatedObject.Loaded += WindowLoaded; 21 | AssociatedObject.Closing += WindowClosing; 22 | } 23 | 24 | 25 | protected override void OnDetaching() 26 | { 27 | AssociatedObject.Loaded -= WindowLoaded; 28 | AssociatedObject.Closing -= WindowClosing; 29 | } 30 | 31 | private void WindowLoaded(object sender, RoutedEventArgs e) 32 | { 33 | var settings = GetUserSettings(); 34 | 35 | if(settings.Window == null) 36 | { 37 | AssociatedObject.SizeToContent = SizeToContent.WidthAndHeight; 38 | return; 39 | } 40 | 41 | AssociatedObject.SizeToContent = SizeToContent.Manual; 42 | AssociatedObject.WindowStartupLocation = WindowStartupLocation.Manual; 43 | 44 | AssociatedObject.Width = settings.Window.Width; 45 | AssociatedObject.Height = settings.Window.Height; 46 | AssociatedObject.Left = settings.Window.PositionLeft; 47 | AssociatedObject.Top = settings.Window.PositionTop; 48 | } 49 | 50 | private Settings GetUserSettings() 51 | { 52 | var kernel = new StandardKernel(); 53 | kernel.Load(Assembly.GetExecutingAssembly()); 54 | _config = kernel.Get(); 55 | return _config.GetConfiguration(); 56 | } 57 | 58 | private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e) 59 | { 60 | var window = new Data.Window 61 | { 62 | Width = AssociatedObject.ActualWidth, 63 | Height = AssociatedObject.ActualHeight, 64 | PositionLeft = AssociatedObject.Left, 65 | PositionTop = AssociatedObject.Top 66 | }; 67 | 68 | _config.SaveSettings(window); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Behaviors/TorrentInfoSizeReductionBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Interactivity; 9 | 10 | namespace MultiSourceTorrentDownloader.Behaviors 11 | { 12 | public class TorrentInfoSizeReductionBehavior : Behavior 13 | { 14 | private bool _sizeChangeInProgress = true; 15 | private const int MaxDialogHeight = 890; 16 | protected override void OnAttached() 17 | { 18 | AssociatedObject.SizeChanged += SizeChanged; 19 | } 20 | 21 | private void SizeChanged(object sender, SizeChangedEventArgs e) 22 | { 23 | _sizeChangeInProgress = !_sizeChangeInProgress; 24 | if (_sizeChangeInProgress) return; 25 | 26 | AssociatedObject.Width = AssociatedObject.ActualWidth * 0.85; 27 | 28 | var newHeight = AssociatedObject.ActualHeight * 0.9; 29 | AssociatedObject.Height = newHeight > MaxDialogHeight ? MaxDialogHeight : newHeight; 30 | } 31 | 32 | protected override void OnDetaching() 33 | { 34 | AssociatedObject.SizeChanged -= SizeChanged; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Common/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace MultiSourceTorrentDownloader.Common 5 | { 6 | public class Command : ICommand 7 | { 8 | private Predicate _canExecute; 9 | private Action _execute; 10 | 11 | public Command(Action execute, Predicate canExecute) 12 | { 13 | _execute = execute; 14 | _canExecute = canExecute; 15 | } 16 | 17 | public Command(Action execute) 18 | { 19 | _execute = execute; 20 | } 21 | 22 | public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs()); 23 | 24 | public event EventHandler CanExecuteChanged; 25 | 26 | public bool CanExecute(object parameter) 27 | { 28 | return _canExecute != null ? _canExecute(parameter) : true; 29 | } 30 | 31 | public void Execute(object parameter) 32 | { 33 | _execute(parameter); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Common/ContainerConfig.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Interfaces; 2 | using MultiSourceTorrentDownloader.Services; 3 | using MultiSourceTorrentDownloader.ViewModels; 4 | using Ninject.Modules; 5 | 6 | namespace MultiSourceTorrentDownloader.Common 7 | { 8 | public class ContainerConfig : NinjectModule 9 | { 10 | public override void Load() 11 | { 12 | Bind().ToSelf(); 13 | Bind().ToSelf(); 14 | 15 | Bind().To().InSingletonScope(); 16 | 17 | Bind().To().InTransientScope(); 18 | Bind().To().InTransientScope(); 19 | 20 | Bind().To().InTransientScope(); 21 | Bind().To().InTransientScope(); 22 | 23 | Bind().To().InTransientScope(); 24 | Bind().To().InTransientScope(); 25 | 26 | Bind().To().InTransientScope(); 27 | Bind().To().InTransientScope(); 28 | 29 | Bind().To().InSingletonScope(); 30 | Bind().To().InSingletonScope(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Constants/KickassTorrentIndexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Constants 8 | { 9 | public static class KickassTorrentIndexer 10 | { 11 | public const int Name = 0; 12 | public const int Date = 3; 13 | public const int Size = 1; 14 | public const int Seeders = 4; 15 | public const int Leechers = 5; 16 | public const int Uploader = 2; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Constants/LeetxTorrentIndexer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Constants.MultiSourceTorrentDownloader 3 | { 4 | public static class LeetxTorrentIndexer 5 | { 6 | public const int Name = 0; 7 | public const int Seeders = 1; 8 | public const int Leechers = 2; 9 | public const int Date = 3; 10 | public const int Size = 4; 11 | public const int Uploader = 5; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Constants/RargbTorrentIndexer.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MultiSourceTorrentDownloader.Constants 9 | { 10 | public static class RargbTorrentIndexer 11 | { 12 | public const int Name = 1; 13 | public const int Date = 3; 14 | public const int Size = 4; 15 | public const int Seeders = 5; 16 | public const int Leechers = 6; 17 | public const int Uploader = 7; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Constants/SizePostfix.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Constants 3 | { 4 | public static class SizePostfix 5 | { 6 | public const string KiloBytes = "KB"; 7 | public const string MegaBytes = "MB"; 8 | public const string GigaBytes = "GB"; 9 | public const string Undefined = "N/A"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Constants/ThePirateBayTorrentIndexer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Constants 3 | { 4 | public static class ThePirateBayTorrentIndexer 5 | { 6 | public const int TitleNode = 0; 7 | public const int Seeders = 1; 8 | public const int Leechers = 2; 9 | 10 | public const int DateIndex = 0; 11 | public const int SizeIndex = 1; 12 | public const int UploaderIndex = 2; 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Converters/BooleanToBackgroundColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | using System.Windows.Media; 9 | 10 | namespace MultiSourceTorrentDownloader.Converters 11 | { 12 | public class BooleanToBackgroundColorConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | return value is bool downloaded && downloaded ? Brushes.DarkGray : Brushes.Green; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Converters/MessageTypeToBackgroundConverter.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace MultiSourceTorrentDownloader.Converters 8 | { 9 | public class MessageTypeToBackgroundConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if(Enum.TryParse(value.ToString(), out MessageType messageType)) 14 | { 15 | switch (messageType) 16 | { 17 | case MessageType.Information: return Brushes.Indigo; 18 | case MessageType.Error: return Brushes.DarkRed; 19 | case MessageType.Empty: return Brushes.White; 20 | } 21 | } 22 | return Brushes.White; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Converters/MessageTypeToStatusBarVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Data; 10 | 11 | namespace MultiSourceTorrentDownloader.Converters 12 | { 13 | class MessageTypeToStatusBarVisibilityConverter : IValueConverter 14 | { 15 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 16 | { 17 | if (value is MessageType message && message != MessageType.Empty) 18 | return Visibility.Visible; 19 | 20 | return Visibility.Collapsed; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Converters/TimeFormatConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace MultiSourceTorrentDownloader.Converters 6 | { 7 | public class TimeFormatConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if(value is DateTime dateTime) 12 | { 13 | if(dateTime.Date == DateTime.Now.Date) 14 | return dateTime.ToString("hh:mm tt"); 15 | else if(dateTime.Year == DateTime.Now.Year) 16 | return dateTime.ToString("MMM. dd"); 17 | else if(dateTime.Year != DateTime.Now.Year) 18 | return dateTime.ToString("yyyy MMM."); 19 | } 20 | 21 | return value; 22 | } 23 | 24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Converters/TorrentCategoryToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | 9 | namespace MultiSourceTorrentDownloader.Converters 10 | { 11 | public class TorrentCategoryToBooleanConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | return value.Equals(parameter); 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | return ((bool)value) ? parameter : Binding.DoNothing; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/DisplaySource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using MultiSourceTorrentDownloader.Models; 3 | using System.Collections.ObjectModel; 4 | using System.Reactive.Subjects; 5 | 6 | namespace MultiSourceTorrentDownloader.Data 7 | { 8 | public class DisplaySource : ModelBase 9 | { 10 | public DisplaySource() 11 | { 12 | SourceStates = new ObservableCollection(); 13 | } 14 | 15 | private bool _selected; 16 | 17 | public bool Selected 18 | { 19 | get => _selected; 20 | set 21 | { 22 | this.MutateVerbose(ref _selected, value, RaisePropertyChanged()); 23 | _selectedSubject.OnNext(value); 24 | } 25 | } 26 | 27 | private ISubject _selectedSubject = new Subject(); 28 | 29 | public ISubject SelectedObservable 30 | { 31 | get => _selectedSubject; 32 | set 33 | { 34 | if (value != _selectedSubject) 35 | _selectedSubject = value; 36 | } 37 | } 38 | 39 | public string SourceName { get; set; } 40 | public TorrentSource Source { get; set; } 41 | 42 | public ObservableCollection SourceStates { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/KickassSorting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Data 8 | { 9 | public class KickassSorting 10 | { 11 | public string SortBy { get; set; } 12 | public string Sort { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/LeetxSorting.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Data 3 | { 4 | public class LeetxSorting 5 | { 6 | public string SortedBy { get; set; } 7 | public string Order { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/RargbSorting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Data 8 | { 9 | public class RargbSorting 10 | { 11 | public string Order { get; set; } 12 | public string By { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/Settings.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System.Collections.Generic; 3 | 4 | namespace MultiSourceTorrentDownloader.Data 5 | { 6 | public class Settings 7 | { 8 | public Window Window { get; set; } 9 | public Search Search { get; set; } 10 | public AutoComplete AutoComplete { get; set; } 11 | } 12 | 13 | public class Window 14 | { 15 | public double Width { get; set; } 16 | public double Height {get;set;} 17 | public double PositionLeft { get; set; } 18 | public double PositionTop { get; set; } 19 | } 20 | 21 | public class Search 22 | { 23 | public int PagesToLoadOnSeach { get; set; } 24 | public IDictionary SelectedSources { get; set; } 25 | public bool SaveSearchOrder { get; set; } 26 | public Sorting SearchSortOrder { get; set; } 27 | } 28 | 29 | public class AutoComplete 30 | { 31 | public IEnumerable Values { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/SizeEntity.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Constants; 2 | using System; 3 | 4 | namespace MultiSourceTorrentDownloader.Data 5 | { 6 | public class SizeEntity : IComparable 7 | { 8 | public double Value { get; set; } 9 | public string Postfix { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | return $"{string.Format("{0:0.##}", Value)} {Postfix}"; 14 | } 15 | 16 | public int CompareTo(object obj) 17 | { 18 | if (obj == null) return 1; 19 | 20 | var that = obj as SizeEntity; 21 | if(that != null) 22 | { 23 | if ((this.Postfix == SizePostfix.KiloBytes && that.Postfix == SizePostfix.KiloBytes) 24 | || (this.Postfix == SizePostfix.MegaBytes && that.Postfix == SizePostfix.MegaBytes) 25 | || (this.Postfix == SizePostfix.GigaBytes && that.Postfix == SizePostfix.GigaBytes)) 26 | { 27 | return this.Value.CompareTo(that.Value); 28 | } 29 | 30 | if (this.Postfix == SizePostfix.KiloBytes 31 | || (this.Postfix == SizePostfix.MegaBytes && that.Postfix == SizePostfix.GigaBytes)) 32 | return -1;//that is bigger 33 | 34 | return 1;//this is bigger when only combination is that.Postfix = KiloBytes 35 | } 36 | else 37 | { 38 | throw new ArgumentException("Object is not a SizeEntity"); 39 | } 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/SourceInformation.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Interfaces; 2 | 3 | namespace MultiSourceTorrentDownloader.Data 4 | { 5 | public class SourceInformation 6 | { 7 | public ITorrentDataSource DataSource { get; set; } 8 | public int CurrentPage { get; set; } 9 | public int StartPage { get; set; } 10 | public bool LastPage { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/SourceState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Data 8 | { 9 | public class SourceState 10 | { 11 | public SourceState(string source, bool isAlive) 12 | { 13 | SourceName = source; 14 | IsAlive = isAlive; 15 | } 16 | 17 | public string SourceName { get; private set; } 18 | public bool IsAlive{ get; private set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/SourceStateUI.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MultiSourceTorrentDownloader.Data 10 | { 11 | public class SourceStateUI : DataModelBase 12 | { 13 | public SourceStateUI(string source, bool isAlive, bool selected) 14 | { 15 | SourceName = source; 16 | _isAlive = isAlive; 17 | _selected = selected; 18 | } 19 | 20 | public string SourceName { get; set; } 21 | public bool _isAlive; 22 | 23 | public bool IsAlive 24 | { 25 | get => _isAlive; 26 | set => this.MutateVerbose(ref _isAlive, value, RaisePropertyChanged()); 27 | } 28 | 29 | private bool _selected; 30 | public bool Selected 31 | { 32 | get => _selected; 33 | set 34 | { 35 | this.MutateVerbose(ref _selected, value, RaisePropertyChanged()); 36 | _selectedSubject.OnNext(value); 37 | } 38 | 39 | } 40 | 41 | private ISubject _selectedSubject = new Subject(); 42 | 43 | public ISubject SelectedObservable 44 | { 45 | get => _selectedSubject; 46 | set 47 | { 48 | if (value != _selectedSubject) 49 | _selectedSubject = value; 50 | } 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/SourceToProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Data 8 | { 9 | public class SourceToProcess 10 | { 11 | public string SourceName { get; set; } 12 | public SourceInformation SourceInformation { get; set; } 13 | public Task TaskToPerform { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/TorrentEntry.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using MultiSourceTorrentDownloader.Models; 3 | using System; 4 | using System.ComponentModel; 5 | 6 | namespace MultiSourceTorrentDownloader.Data 7 | { 8 | public class TorrentEntry : INotifyPropertyChanged 9 | { 10 | public string Title { get; set; } 11 | public DateTime Date { get; set; } 12 | public SizeEntity Size { get; set; } 13 | public string Uploader { get; set; } 14 | public int Seeders { get; set; } 15 | public int Leechers{ get; set; } 16 | public string DescriptionHtml{ get; set; } 17 | 18 | public string TorrentUri { get; set; } 19 | public string TorrentMagnet { get; set; } 20 | 21 | public TorrentSource Source { get; set; } 22 | 23 | private bool _magnetDownloaded; 24 | public bool MagnetDownloaded 25 | { 26 | get => _magnetDownloaded; 27 | set => this.MutateVerbose(ref _magnetDownloaded, value, RaisePropertyChanged()); 28 | } 29 | 30 | public event PropertyChangedEventHandler PropertyChanged; 31 | private Action RaisePropertyChanged() 32 | { 33 | return args => PropertyChanged?.Invoke(this, args); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/TorrentPaging.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Data 3 | { 4 | public class TorrentPaging 5 | { 6 | public int ThePirateBayCurrentPage { get; set; } = 0; 7 | public bool ThePirateBayPagingEnded { get; set; } 8 | 9 | public int LeetxCurrentPage { get; set; } = 1; 10 | public bool LeetxPagingEnded { get; set; } 11 | 12 | public bool AllSourcesReachedEnd() 13 | { 14 | return ThePirateBayPagingEnded && LeetxPagingEnded; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Data/TorrentQueryResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MultiSourceTorrentDownloader.Data 4 | { 5 | public class TorrentQueryResult 6 | { 7 | public TorrentQueryResult() 8 | { 9 | TorrentEntries = new List(); 10 | } 11 | public IEnumerable TorrentEntries { get; set; } 12 | public bool IsLastPage { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Enums/MessageType.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Enums 3 | { 4 | public enum MessageType 5 | { 6 | Information = 0, 7 | Error = 1, 8 | 9 | /// 10 | /// Used to indicate that the message displayer will be reset. Neutral indication. 11 | /// 12 | Empty = 2 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Enums/Sorting.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Enums 3 | { 4 | public enum Sorting 5 | { 6 | TimeDesc = 0, 7 | TimeAsc = 1, 8 | SizeDesc = 2, 9 | SizeAsc = 3, 10 | SeedersDesc = 4, 11 | SeedersAsc = 5, 12 | LeecherssDesc = 6, 13 | LeechersAsc = 7, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Enums/ThePirateBaySorting.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Enums 3 | { 4 | public enum ThePirateBaySorting 5 | { 6 | UploadedDesc = 3, 7 | UploadedAsc = 4, 8 | SizeDesc = 5, 9 | SizeAsc = 6, 10 | SeedersDesc = 7, 11 | SeedersAsc = 8, 12 | LeecherssDesc = 9, 13 | LeechersAsc = 10, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Enums/TorrentCategory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Enums 8 | { 9 | public enum TorrentCategory 10 | { 11 | All, 12 | Movies, 13 | TV, 14 | Games, 15 | Music, 16 | Applications, 17 | XXX 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Enums/TorrentSource.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Enums 3 | { 4 | public enum TorrentSource 5 | { 6 | ThePirateBay, 7 | Leetx, 8 | Rargb, 9 | Kickass 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Extensions/ObservableCollectionForEachExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace MultiSourceTorrentDownloader.Extensions 5 | { 6 | public static class ObservableCollectionForEachExtension 7 | { 8 | public static void ForEach(this ObservableCollection enumerable, Action action) 9 | { 10 | foreach (var item in enumerable) 11 | { 12 | action.Invoke(item); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IAutoCompleteService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MultiSourceTorrentDownloader.Interfaces 4 | { 5 | public interface IAutoCompleteService 6 | { 7 | IReadOnlyCollection AutoCompletes { get; } 8 | 9 | void Init(IEnumerable autoCompletes); 10 | void TryAddAutoCompleteEntry(string entry); 11 | } 12 | } -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IKickassParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Interfaces 8 | { 9 | public interface IKickassParser : ITorrentParser 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IKickassSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Interfaces 8 | { 9 | public interface IKickassSource : ITorrentDataSource 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/ILeetxParser.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Interfaces 3 | { 4 | public interface ILeetxParser : ITorrentParser 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/ILeetxSource.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Interfaces 3 | { 4 | public interface ILeetxSource : ITorrentDataSource 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/ILogService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MultiSourceTorrentDownloader.Interfaces 4 | { 5 | public interface ILogService 6 | { 7 | void Information(string message); 8 | void Information(string message, Exception ex); 9 | 10 | void Warning(string message); 11 | void Warning(string message, Exception ex); 12 | 13 | void Error(string message); 14 | void Error(string message, Exception ex); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IRargbParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Interfaces 8 | { 9 | public interface IRargbParser : ITorrentParser 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IRargbSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Interfaces 8 | { 9 | public interface IRargbSource : ITorrentDataSource 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IThePirateBayParser.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Interfaces 3 | { 4 | public interface IThePirateBayParser : ITorrentParser 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IThePirateBaySource.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiSourceTorrentDownloader.Interfaces 3 | { 4 | public interface IThePirateBaySource : ITorrentDataSource 5 | { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/ITorrentDataSource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace MultiSourceTorrentDownloader.Interfaces 7 | { 8 | public interface ITorrentDataSource 9 | { 10 | Task GetTorrentsAsync(string searchFor, int page, Sorting sorting); 11 | Task GetTorrentsByCategoryAsync(string searchFor, int page, Sorting sorting, TorrentCategory category); 12 | Task GetTorrentMagnetAsync(string detailsUri); 13 | Task GetTorrentDescriptionAsync(string detailsUri); 14 | 15 | IEnumerable GetSources(); 16 | IAsyncEnumerable GetSourceStates(); 17 | void UpdateUsedSource(string newBaseUrl); 18 | 19 | string FullTorrentUrl(string uri); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/ITorrentParser.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using System.Threading.Tasks; 3 | 4 | namespace MultiSourceTorrentDownloader.Interfaces 5 | { 6 | public interface ITorrentParser 7 | { 8 | Task ParsePageForTorrentEntriesAsync(string pageContents); 9 | Task ParsePageForMagnetAsync(string pageContents); 10 | Task ParsePageForDescriptionHtmlAsync(string pageContents); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Interfaces/IUserConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MultiSourceTorrentDownloader.Interfaces 9 | { 10 | public interface IUserConfiguration 11 | { 12 | Settings GetConfiguration(); 13 | void SaveSettings(T settings) where T:class; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Mapping/SortingMapper.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MultiSourceTorrentDownloader.Mapping 10 | { 11 | public static class SortingMapper 12 | { 13 | public static int SortingToThePirateBaySorting(Sorting sorting) 14 | { 15 | return sorting switch 16 | { 17 | Sorting.LeechersAsc => (int)ThePirateBaySorting.LeechersAsc, 18 | Sorting.LeecherssDesc => (int)ThePirateBaySorting.LeecherssDesc, 19 | Sorting.SeedersAsc => (int)ThePirateBaySorting.SeedersAsc, 20 | Sorting.SeedersDesc => (int)ThePirateBaySorting.SeedersDesc, 21 | Sorting.SizeAsc => (int)ThePirateBaySorting.SizeAsc, 22 | Sorting.SizeDesc => (int)ThePirateBaySorting.SizeDesc, 23 | Sorting.TimeAsc => (int)ThePirateBaySorting.UploadedAsc, 24 | Sorting.TimeDesc => (int)ThePirateBaySorting.UploadedDesc, 25 | _ => throw new ArgumentException($"Unrecognized sorting option"), 26 | }; 27 | } 28 | 29 | public static LeetxSorting SortingToLeetxSorting(Sorting sorting) 30 | { 31 | return sorting switch 32 | { 33 | Sorting.LeechersAsc => new LeetxSorting { Order = "asc", SortedBy = "leechers" }, 34 | Sorting.LeecherssDesc => new LeetxSorting { Order = "desc", SortedBy = "leechers" }, 35 | Sorting.SeedersAsc => new LeetxSorting { Order = "asc", SortedBy = "seeders" }, 36 | Sorting.SeedersDesc => new LeetxSorting { Order = "desc", SortedBy = "seeders" }, 37 | Sorting.SizeAsc => new LeetxSorting { Order = "asc", SortedBy = "size" }, 38 | Sorting.SizeDesc => new LeetxSorting { Order = "desc", SortedBy = "size" }, 39 | Sorting.TimeAsc => new LeetxSorting { Order = "asc", SortedBy = "time" }, 40 | Sorting.TimeDesc => new LeetxSorting { Order = "desc", SortedBy = "time" }, 41 | _ => throw new ArgumentException($"Unrecognized sorting option") 42 | }; 43 | } 44 | 45 | public static KickassSorting SortingToKickassSorting(Sorting sorting) 46 | { 47 | return sorting switch 48 | { 49 | Sorting.LeechersAsc => new KickassSorting { Sort = "asc", SortBy = "leechers" }, 50 | Sorting.LeecherssDesc => new KickassSorting { Sort = "desc", SortBy = "leechers" }, 51 | Sorting.SeedersAsc => new KickassSorting { Sort = "asc", SortBy = "seeders" }, 52 | Sorting.SeedersDesc => new KickassSorting { Sort = "desc", SortBy = "seeders" }, 53 | Sorting.SizeAsc => new KickassSorting { Sort = "asc", SortBy = "size" }, 54 | Sorting.SizeDesc => new KickassSorting { Sort = "desc", SortBy = "size" }, 55 | Sorting.TimeAsc => new KickassSorting { Sort = "asc", SortBy = "time" }, 56 | Sorting.TimeDesc => new KickassSorting { Sort = "desc", SortBy = "time" }, 57 | _ => throw new ArgumentException($"Unrecognized sorting option") 58 | }; 59 | } 60 | 61 | public static RargbSorting SortingToRargbSorting(Sorting sorting) 62 | { 63 | return sorting switch 64 | { 65 | Sorting.LeechersAsc => new RargbSorting { By = "asc", Order = "leechers" }, 66 | Sorting.LeecherssDesc => new RargbSorting { By = "desc", Order = "leechers" }, 67 | Sorting.SeedersAsc => new RargbSorting { By = "asc", Order = "seeders" }, 68 | Sorting.SeedersDesc => new RargbSorting { By = "desc", Order = "seeders" }, 69 | Sorting.SizeAsc => new RargbSorting { By = "asc", Order = "size" }, 70 | Sorting.SizeDesc => new RargbSorting { By = "desc", Order = "size" }, 71 | Sorting.TimeAsc => new RargbSorting { By = "asc", Order = "data" }, 72 | Sorting.TimeDesc => new RargbSorting { By = "desc", Order = "data" }, 73 | _ => throw new ArgumentException($"Unrecognized sorting option") 74 | }; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Mapping/TorrentCategoryMapper.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MultiSourceTorrentDownloader.Mapping 9 | { 10 | public static class TorrentCategoryMapper 11 | { 12 | public static string ToThePirateBayCategory(TorrentCategory category) 13 | { 14 | return category switch 15 | { 16 | TorrentCategory.Applications => "300", 17 | TorrentCategory.Games => "400", 18 | TorrentCategory.Movies => "201,202,207", 19 | TorrentCategory.Music => "101", 20 | TorrentCategory.TV => "205,208", 21 | TorrentCategory.XXX => "500", 22 | _ => throw new ArgumentException($"Unexpected torrent category: {category}") 23 | }; 24 | } 25 | 26 | public static string ToLeetxCategory(TorrentCategory category) 27 | { 28 | return DirectMapping(category); 29 | } 30 | 31 | public static string ToRargbCategory(TorrentCategory category) 32 | { 33 | return DirectMapping(category).ToLower(); 34 | } 35 | 36 | private static string DirectMapping(TorrentCategory category) 37 | { 38 | return category switch 39 | { 40 | TorrentCategory.Applications => "Apps", 41 | TorrentCategory.Games => "Games", 42 | TorrentCategory.Movies => "Movies", 43 | TorrentCategory.Music => "Music", 44 | TorrentCategory.TV => "TV", 45 | TorrentCategory.XXX => "XXX", 46 | _ => throw new ArgumentException($"Unexpected torrent category: {category}") 47 | }; 48 | } 49 | 50 | internal static string ToKickassCategory(TorrentCategory category) 51 | { 52 | return DirectMapping(category).ToLower(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/DataModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MultiSourceTorrentDownloader.Models 9 | { 10 | public class DataModelBase : INotifyPropertyChanged 11 | { 12 | public event PropertyChangedEventHandler PropertyChanged; 13 | protected Action RaisePropertyChanged() 14 | { 15 | return args => PropertyChanged?.Invoke(this, args); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/MainModel.cs: -------------------------------------------------------------------------------- 1 | using MaterialDesignThemes.Wpf; 2 | using MultiSourceTorrentDownloader.Common; 3 | using MultiSourceTorrentDownloader.Data; 4 | using MultiSourceTorrentDownloader.Enums; 5 | using MultiSourceTorrentDownloader.Models.SubModels; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Collections.ObjectModel; 9 | 10 | namespace MultiSourceTorrentDownloader.Models 11 | { 12 | public partial class MainModel : ModelBase 13 | { 14 | public MainModel() 15 | { 16 | TorrentEntries = new ObservableCollection(); 17 | AvailableSortOrders = new List>(); 18 | Settings = new MainModelSettings(); 19 | AutoCompleteItems = new List(); 20 | } 21 | 22 | private MainModelSettings _settings; 23 | private IEnumerable _autoCompleteItems; 24 | 25 | private bool _isLoading; 26 | private string _searchValue; 27 | private string _torrentFilter; 28 | 29 | public ObservableCollection TorrentEntries { get; set; } 30 | 31 | private KeyValuePair _selectedSearchSortOrder; 32 | private IEnumerable> _availableSortOrders; 33 | 34 | private TorrentEntry _selectedTorrent; 35 | private TorrentCategory _selectedTorrentCategory; 36 | 37 | public Command LoadMoreCommand { get; set; } 38 | public Command SearchCommand { get; set; } 39 | public Command OpenTorrentInfoCommand { get; set; } 40 | public Command DownloadMagnetCommand { get; set; } 41 | public Command CopyTorrentLinkCommand { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/MainModel.g.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Models.SubModels; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.ComponentModel; 8 | using System.Reactive.Subjects; 9 | 10 | namespace MultiSourceTorrentDownloader.Models 11 | { 12 | public partial class MainModel 13 | { 14 | public bool IsLoading 15 | { 16 | get => _isLoading; 17 | set 18 | { 19 | this.MutateVerbose(ref _isLoading, value, RaisePropertyChanged()); 20 | _isLoadingSubject.OnNext(value); 21 | } 22 | } 23 | 24 | private ISubject _isLoadingSubject = new Subject(); 25 | 26 | public ISubject IsLoadingObservable 27 | { 28 | get => _isLoadingSubject; 29 | set 30 | { 31 | if (value != _isLoadingSubject) 32 | _isLoadingSubject = value; 33 | } 34 | } 35 | 36 | public string SearchValue 37 | { 38 | get => _searchValue; 39 | set 40 | { 41 | this.MutateVerbose(ref _searchValue, value, RaisePropertyChanged()); 42 | _searchValueSubject.OnNext(value); 43 | } 44 | } 45 | 46 | private ISubject _searchValueSubject = new Subject(); 47 | 48 | public ISubject SearchValueObservable 49 | { 50 | get => _searchValueSubject; 51 | set 52 | { 53 | if (value != _searchValueSubject) 54 | _searchValueSubject = value; 55 | } 56 | } 57 | 58 | public string TorrentFilter 59 | { 60 | get => _torrentFilter; 61 | set 62 | { 63 | this.MutateVerbose(ref _torrentFilter, value, RaisePropertyChanged()); 64 | _torrentFilterSubject.OnNext(value); 65 | } 66 | } 67 | 68 | private ISubject _torrentFilterSubject = new Subject(); 69 | 70 | public ISubject TorrentFilterObservable 71 | { 72 | get => _torrentFilterSubject; 73 | set 74 | { 75 | if (value != _torrentFilterSubject) 76 | _torrentFilterSubject = value; 77 | } 78 | } 79 | 80 | private ISubject> _selectedSearchSortOrderSubject = new Subject>(); 81 | 82 | public ISubject> SelectedSearchSortOrderObservable 83 | { 84 | get => _selectedSearchSortOrderSubject; 85 | set 86 | { 87 | if (value != _selectedSearchSortOrderSubject) 88 | _selectedSearchSortOrderSubject = value; 89 | } 90 | } 91 | 92 | public KeyValuePair SelectedSearchSortOrder 93 | { 94 | get => _selectedSearchSortOrder; 95 | set 96 | { 97 | this.MutateVerbose(ref _selectedSearchSortOrder, value, RaisePropertyChanged()); 98 | _selectedSearchSortOrderSubject.OnNext(value); 99 | } 100 | } 101 | 102 | public IEnumerable> AvailableSortOrders 103 | { 104 | get => _availableSortOrders; 105 | set => this.MutateVerbose(ref _availableSortOrders, value, RaisePropertyChanged()); 106 | } 107 | 108 | public TorrentEntry SelectedTorrent 109 | { 110 | get => _selectedTorrent; 111 | set => this.MutateVerbose(ref _selectedTorrent, value, RaisePropertyChanged()); 112 | } 113 | 114 | public TorrentCategory SelectedTorrentCategory 115 | { 116 | get => _selectedTorrentCategory; 117 | set => this.MutateVerbose(ref _selectedTorrentCategory, value, RaisePropertyChanged()); 118 | } 119 | 120 | public MainModelSettings Settings 121 | { 122 | get => _settings; 123 | set => this.MutateVerbose(ref _settings, value, RaisePropertyChanged()); 124 | } 125 | 126 | public IEnumerable AutoCompleteItems 127 | { 128 | get => _autoCompleteItems; 129 | set => this.MutateVerbose(ref _autoCompleteItems, value, RaisePropertyChanged()); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/ModelBase.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Reactive.Subjects; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MultiSourceTorrentDownloader.Models 11 | { 12 | public class ModelBase : INotifyPropertyChanged, IDataErrorInfo 13 | { 14 | private string _statusBarMessage; 15 | private MessageType _messageType; 16 | 17 | public string StatusBarMessage 18 | { 19 | get => _statusBarMessage; 20 | set => this.MutateVerbose(ref _statusBarMessage, value, RaisePropertyChanged()); 21 | } 22 | 23 | public MessageType MessageType 24 | { 25 | get => _messageType; 26 | set 27 | { 28 | this.MutateVerbose(ref _messageType, value, RaisePropertyChanged()); 29 | _messageTypeSubject.OnNext(value); 30 | } 31 | } 32 | 33 | private ISubject _messageTypeSubject = new Subject(); 34 | 35 | public ISubject StatusBarObservable 36 | { 37 | get => _messageTypeSubject; 38 | set 39 | { 40 | if (value != _messageTypeSubject) 41 | _messageTypeSubject = value; 42 | } 43 | } 44 | 45 | public string this[string columnName] => throw new NotImplementedException(); 46 | 47 | public string Error => throw new NotImplementedException(); 48 | 49 | public event PropertyChangedEventHandler PropertyChanged; 50 | protected Action RaisePropertyChanged() 51 | { 52 | return args => PropertyChanged?.Invoke(this, args); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/NotificaitonPropertyChangedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace MultiSourceTorrentDownloader.Models 8 | { 9 | public static class NotifyPropertyChangedExtensions 10 | { 11 | public static void MutateVerbose(this INotifyPropertyChanged instance, ref TField field, TField newValue, Action raise, [CallerMemberName] string propertyName = null) 12 | { 13 | if (EqualityComparer.Default.Equals(field, newValue)) return; 14 | field = newValue; 15 | raise?.Invoke(new PropertyChangedEventArgs(propertyName)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/SubModels/MainModelSettings.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MultiSourceTorrentDownloader.Models.SubModels 11 | { 12 | public partial class MainModelSettings : ModelBase 13 | { 14 | public MainModelSettings() 15 | { 16 | AvailableSources = new ObservableCollection(); 17 | SelectablePages = new ObservableCollection(); 18 | } 19 | 20 | private bool _isLoading; 21 | 22 | public ObservableCollection AvailableSources { get; set; } 23 | public ObservableCollection SelectablePages { get; set; } 24 | 25 | private int _pagesToLoadBySearch; 26 | private bool _saveSearchSortOrder; 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/SubModels/MainModelSettings.g.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MultiSourceTorrentDownloader.Models.SubModels 10 | { 11 | public partial class MainModelSettings 12 | { 13 | public bool IsLoading 14 | { 15 | get => _isLoading; 16 | set => this.MutateVerbose(ref _isLoading, value, RaisePropertyChanged()); 17 | } 18 | 19 | public bool SaveSearchSortOrder 20 | { 21 | get => _saveSearchSortOrder; 22 | set => this.MutateVerbose(ref _saveSearchSortOrder, value, RaisePropertyChanged()); 23 | } 24 | 25 | public int PagesToLoadBySearch 26 | { 27 | get => _pagesToLoadBySearch; 28 | set => this.MutateVerbose(ref _pagesToLoadBySearch, value, RaisePropertyChanged()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/TorrentInfoDialogModel.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Common; 2 | using MultiSourceTorrentDownloader.Data; 3 | using System; 4 | 5 | namespace MultiSourceTorrentDownloader.Models 6 | { 7 | public partial class TorrentInfoDialogModel : ModelBase 8 | { 9 | private bool _isLoading; 10 | 11 | private string _title; 12 | private DateTime _date; 13 | private SizeEntity _size; 14 | private string _uploader; 15 | private int _seeders; 16 | private int _leechers; 17 | private string _torrentMagnet; 18 | private string _torrentLink; 19 | private string _description; 20 | private bool _magnetDownloaded; 21 | 22 | public Command DownloadTorrentCommand { get; set; } 23 | public Command CopyTorrentLinkCommand { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Models/TorrentInfoDialogModel.g.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Common; 2 | using MultiSourceTorrentDownloader.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MultiSourceTorrentDownloader.Models 11 | { 12 | public partial class TorrentInfoDialogModel 13 | { 14 | public bool IsLoading 15 | { 16 | get => _isLoading; 17 | set => this.MutateVerbose(ref _isLoading, value, RaisePropertyChanged()); 18 | } 19 | public string Title 20 | { 21 | get => _title; 22 | set => this.MutateVerbose(ref _title, value, RaisePropertyChanged()); 23 | } 24 | 25 | public DateTime Date 26 | { 27 | get => _date; 28 | set => this.MutateVerbose(ref _date, value, RaisePropertyChanged()); 29 | } 30 | 31 | public SizeEntity Size 32 | { 33 | get => _size; 34 | set => this.MutateVerbose(ref _size, value, RaisePropertyChanged()); 35 | } 36 | 37 | public string Uploader 38 | { 39 | get => _uploader; 40 | set => this.MutateVerbose(ref _uploader, value, RaisePropertyChanged()); 41 | } 42 | 43 | public int Seeders 44 | { 45 | get => _seeders; 46 | set => this.MutateVerbose(ref _seeders, value, RaisePropertyChanged()); 47 | } 48 | 49 | public int Leechers 50 | { 51 | get => _leechers; 52 | set => this.MutateVerbose(ref _leechers, value, RaisePropertyChanged()); 53 | } 54 | 55 | public string TorrentMagnet 56 | { 57 | get => _torrentMagnet; 58 | set => this.MutateVerbose(ref _torrentMagnet, value, RaisePropertyChanged()); 59 | } 60 | 61 | public string TorrentLink 62 | { 63 | get => _torrentLink; 64 | set => this.MutateVerbose(ref _torrentLink, value, RaisePropertyChanged()); 65 | } 66 | public string Description 67 | { 68 | get => _description; 69 | set => this.MutateVerbose(ref _description, value, RaisePropertyChanged()); 70 | } 71 | 72 | public bool MagnetDownloaded 73 | { 74 | get => _magnetDownloaded; 75 | set => this.MutateVerbose(ref _magnetDownloaded, value, RaisePropertyChanged()); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/MultiSourceTorrentDownloader.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Debug 9 | AnyCPU 10 | {2483D14D-06BF-4533-8FD8-A53CCBDC8522} 11 | WinExe 12 | MultiSourceTorrentDownloader 13 | MultiSourceTorrentDownloader 14 | v4.8 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | true 19 | true 20 | 21 | 22 | 23 | 24 | 25 | AnyCPU 26 | true 27 | full 28 | false 29 | bin\Debug\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | x86 36 | pdbonly 37 | true 38 | bin\Release\ 39 | TRACE 40 | prompt 41 | 4 42 | 43 | 44 | true 45 | bin\x86\Debug\ 46 | DEBUG;TRACE 47 | full 48 | x86 49 | latest 50 | prompt 51 | MinimumRecommendedRules.ruleset 52 | true 53 | 54 | 55 | bin\x86\Release\ 56 | TRACE 57 | true 58 | pdbonly 59 | x86 60 | latest 61 | prompt 62 | MinimumRecommendedRules.ruleset 63 | true 64 | 65 | 66 | app.manifest 67 | 68 | 69 | icon.ico 70 | 71 | 72 | 73 | ..\packages\CefSharp.Common.108.4.130\lib\net452\CefSharp.dll 74 | 75 | 76 | ..\packages\CefSharp.Common.108.4.130\lib\net452\CefSharp.Core.dll 77 | 78 | 79 | ..\packages\CefSharp.Wpf.108.4.130\lib\net462\CefSharp.Wpf.dll 80 | 81 | 82 | ..\packages\HtmlAgilityPack.1.11.46\lib\Net45\HtmlAgilityPack.dll 83 | 84 | 85 | ..\packages\MaterialDesignColors.2.1.0\lib\net462\MaterialDesignColors.dll 86 | 87 | 88 | ..\packages\MaterialDesignThemes.4.7.0\lib\net462\MaterialDesignThemes.Wpf.dll 89 | 90 | 91 | ..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll 92 | 93 | 94 | ..\packages\System.Windows.Interactivity.WPF.2.0.20525\lib\net40\Microsoft.Expression.Interactions.dll 95 | 96 | 97 | ..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.39\lib\net45\Microsoft.Xaml.Behaviors.dll 98 | 99 | 100 | ..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll 101 | 102 | 103 | ..\packages\Ninject.3.3.6\lib\net45\Ninject.dll 104 | 105 | 106 | ..\packages\RestSharp.108.0.3\lib\netstandard2.0\RestSharp.dll 107 | 108 | 109 | ..\packages\Serilog.2.12.0\lib\net47\Serilog.dll 110 | 111 | 112 | ..\packages\Serilog.Sinks.File.5.0.0\lib\net45\Serilog.Sinks.File.dll 113 | 114 | 115 | ..\packages\Syncfusion.Licensing.20.4.0.43\lib\net46\Syncfusion.Licensing.dll 116 | 117 | 118 | ..\packages\Syncfusion.SfInput.WPF.20.4.0.43\lib\net46\Syncfusion.SfInput.WPF.dll 119 | 120 | 121 | ..\packages\Syncfusion.SfShared.WPF.18.4.0.49\lib\net46\Syncfusion.SfShared.WPF.dll 122 | 123 | 124 | ..\packages\Syncfusion.Shared.WPF.20.4.0.43\lib\net46\Syncfusion.Shared.WPF.dll 125 | 126 | 127 | ..\packages\Syncfusion.Tools.WPF.20.4.0.43\lib\net46\Syncfusion.Tools.WPF.dll 128 | 129 | 130 | 131 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 132 | 133 | 134 | 135 | 136 | 137 | 138 | ..\packages\System.Interactive.Async.6.0.1\lib\net48\System.Interactive.Async.dll 139 | 140 | 141 | ..\packages\System.Linq.Async.6.0.1\lib\net48\System.Linq.Async.dll 142 | 143 | 144 | ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll 145 | 146 | 147 | 148 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 149 | 150 | 151 | ..\packages\System.Reactive.5.0.0\lib\net472\System.Reactive.dll 152 | 153 | 154 | ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll 155 | 156 | 157 | 158 | ..\packages\System.Text.Encodings.Web.7.0.0\lib\net462\System.Text.Encodings.Web.dll 159 | 160 | 161 | ..\packages\System.Text.Json.7.0.1\lib\net462\System.Text.Json.dll 162 | 163 | 164 | ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll 165 | 166 | 167 | ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll 168 | 169 | 170 | 171 | 172 | 173 | ..\packages\System.Windows.Interactivity.WPF.2.0.20525\lib\net40\System.Windows.Interactivity.dll 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 4.0 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | MSBuild:Compile 191 | Designer 192 | 193 | 194 | App.xaml 195 | Code 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | DrawerView.xaml 269 | 270 | 271 | MainWindow.xaml 272 | 273 | 274 | StatusBar.xaml 275 | 276 | 277 | TorrentInfoDialogView.xaml 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | Code 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | Designer 299 | MSBuild:Compile 300 | 301 | 302 | MSBuild:Compile 303 | Designer 304 | 305 | 306 | Designer 307 | MSBuild:Compile 308 | 309 | 310 | Designer 311 | MSBuild:Compile 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("MultiSourceTorrentDownloader")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("MultiSourceTorrentDownloader")] 15 | [assembly: AssemblyCopyright("Copyright © 2019")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/AutoCompleteService.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace MultiSourceTorrentDownloader.Services 7 | { 8 | public class AutoCompleteService : IAutoCompleteService 9 | { 10 | private const int MaxEntries = 50; 11 | 12 | private List _autoCompletes = new List(); 13 | 14 | public IReadOnlyCollection AutoCompletes { get => _autoCompletes; } 15 | 16 | public void Init(IEnumerable autoCompletes) 17 | { 18 | _autoCompletes = new List(autoCompletes); 19 | } 20 | 21 | /// 22 | /// Adds an entry to the auto complete list if one does not already exist 23 | /// 24 | public void TryAddAutoCompleteEntry(string entry) 25 | { 26 | if (string.IsNullOrWhiteSpace(entry) 27 | || _autoCompletes.Any(a => a.Equals(entry, StringComparison.InvariantCultureIgnoreCase))) 28 | return; 29 | 30 | if (_autoCompletes.Count is MaxEntries) 31 | _autoCompletes.RemoveAt(0); 32 | 33 | _autoCompletes.Add(entry); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/KickassParser.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using MultiSourceTorrentDownloader.Constants; 3 | using MultiSourceTorrentDownloader.Data; 4 | using MultiSourceTorrentDownloader.Enums; 5 | using MultiSourceTorrentDownloader.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class KickassParser : ParserBase, IKickassParser 15 | { 16 | private readonly ILogService _logger; 17 | 18 | public KickassParser(ILogService logger) 19 | { 20 | DataColumnCount = 6; 21 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | } 23 | 24 | public async Task ParsePageForDescriptionHtmlAsync(string pageContents) 25 | { 26 | return await Task.Run(() => 27 | { 28 | var htmlDocument = LoadedHtmlDocument(pageContents); 29 | 30 | var descriptionNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='textcontent']"); 31 | if (descriptionNode == null) 32 | { 33 | _logger.Warning("Could not find description node for Kickass"); 34 | return string.Empty; 35 | } 36 | 37 | return descriptionNode.InnerHtml; 38 | }); 39 | } 40 | 41 | public async Task ParsePageForMagnetAsync(string pageContents) 42 | { 43 | _logger.Information("Kickass magnet parsing parsing"); 44 | return await BaseParseMagnet(pageContents); 45 | } 46 | 47 | public async Task ParsePageForTorrentEntriesAsync(string pageContents) 48 | { 49 | return await Task.Run(() => 50 | { 51 | try 52 | { 53 | _logger.Information("Kickass parsing"); 54 | var htmlAgility = LoadedHtmlDocument(pageContents); 55 | 56 | var tableRows = htmlAgility.DocumentNode.SelectNodes("//tr[@class='odd'] | //tr[@class='even']");//gets table rows that contain torrent data 57 | if (NoTableEntries(tableRows)) 58 | return new TorrentQueryResult { IsLastPage = true }; 59 | 60 | var result = new List(); 61 | foreach (var row in tableRows) 62 | { 63 | var columns = row.SelectNodes("td"); 64 | if (columns == null || columns.Count != DataColumnCount) 65 | { 66 | _logger.Warning($"Could not find all columns for torrent {Environment.NewLine} {row.OuterHtml}"); 67 | continue; 68 | } 69 | 70 | var titleNode = columns[KickassTorrentIndexer.Name] 71 | .SelectSingleNode("div/div/a[@class='cellMainLink']"); 72 | if (titleNode == null) 73 | { 74 | _logger.Warning($"Could not find title node for torrent {Environment.NewLine} {columns[KickassTorrentIndexer.Name].OuterHtml}"); 75 | continue; 76 | } 77 | 78 | var title = titleNode.InnerText.Trim(); 79 | if (string.IsNullOrEmpty(title))//empty title entry makes no sense. log and skip 80 | { 81 | _logger.Warning($"Empty title from {Environment.NewLine}{titleNode.OuterHtml}"); 82 | continue; 83 | } 84 | 85 | var torrentUri = titleNode.Attributes.FirstOrDefault(a => a.Name == "href")?.Value; 86 | if (string.IsNullOrEmpty(torrentUri)) 87 | { 88 | _logger.Warning($"Empty torrent uri from{Environment.NewLine}{titleNode.OuterHtml}"); 89 | continue; 90 | } 91 | 92 | var magnetLink = string.Empty; 93 | 94 | if (!int.TryParse(columns[KickassTorrentIndexer.Seeders].InnerText, out var seeders)) 95 | _logger.Warning($"Could not parse seeders {Environment.NewLine}{columns[KickassTorrentIndexer.Seeders].OuterHtml}"); 96 | 97 | if (!int.TryParse(columns[KickassTorrentIndexer.Leechers].InnerText, out var leechers)) 98 | _logger.Warning($"Could not parse leechers {Environment.NewLine}{columns[KickassTorrentIndexer.Leechers].OuterHtml}"); 99 | 100 | var date = columns[KickassTorrentIndexer.Date].InnerText.Trim(); 101 | 102 | var size = columns[KickassTorrentIndexer.Size].InnerText.Trim(); 103 | var uploader = columns[KickassTorrentIndexer.Uploader].InnerText.Trim(); 104 | 105 | var splitSize = size.Split(' '); 106 | result.Add(new TorrentEntry 107 | { 108 | Title = title, 109 | TorrentUri = TrimUriStart(torrentUri), 110 | TorrentMagnet = magnetLink, 111 | Date = ParseDate(date), 112 | Size = new SizeEntity 113 | { 114 | Value = double.Parse(splitSize[0], CultureInfo.InvariantCulture), 115 | Postfix = splitSize[1] 116 | }, 117 | Uploader = uploader, 118 | Seeders = seeders, 119 | Leechers = leechers, 120 | Source = TorrentSource.Kickass 121 | }); 122 | } 123 | 124 | var pagination = htmlAgility.DocumentNode.SelectSingleNode("//div[@class='pages botmarg5px floatright']"); 125 | 126 | return new TorrentQueryResult 127 | { 128 | TorrentEntries = result, 129 | IsLastPage = IsLastPage(pagination) 130 | }; 131 | 132 | } 133 | catch (Exception ex) 134 | { 135 | _logger.Warning("Kickass parse exception", ex); 136 | throw; 137 | } 138 | }); 139 | } 140 | 141 | private DateTime ParseDate(string date) 142 | { 143 | var digitEndIndex = 0; 144 | foreach(var c in date) 145 | { 146 | if (char.IsLetter(c)) 147 | break; 148 | digitEndIndex++; 149 | } 150 | 151 | int.TryParse(date.Substring(0, digitEndIndex), out var numberToSubtract); 152 | var parsedDate = DateTime.UtcNow; 153 | 154 | if (date.Contains("min.")) parsedDate = parsedDate.AddMinutes(-numberToSubtract); 155 | if (date.Contains("hour")) parsedDate = parsedDate.AddHours(-numberToSubtract); 156 | if (date.Contains("day")) parsedDate = parsedDate.AddDays(-numberToSubtract); 157 | if (date.Contains("month")) parsedDate = parsedDate.AddMonths(-numberToSubtract); 158 | if (date.Contains("year")) parsedDate = parsedDate.AddYears(-numberToSubtract); 159 | 160 | return parsedDate; 161 | } 162 | private bool IsLastPage(HtmlNode pagination) => pagination == null || pagination.SelectNodes("a").All(n => n.InnerText != ">>"); 163 | 164 | private bool NoTableEntries(HtmlNodeCollection tableRows) => tableRows == null; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/KickassSource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Interfaces; 4 | using MultiSourceTorrentDownloader.Mapping; 5 | using RestSharp; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Configuration; 9 | using System.IO; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class KickassSource : SourceBase, IKickassSource 15 | { 16 | private readonly ILogService _logger; 17 | private readonly IKickassParser _parser; 18 | 19 | private RestClient _restClient; 20 | 21 | public KickassSource(ILogService logger, IKickassParser parser) 22 | { 23 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 24 | _parser = parser ?? throw new ArgumentNullException(nameof(parser)); 25 | 26 | _baseUrl = ConfigurationManager.AppSettings["KickassUrl"]; 27 | _searchResource = ConfigurationManager.AppSettings["KickassSearchEndpoint"]; 28 | 29 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 30 | 31 | _restClient = new RestClient(_baseUrl); 32 | } 33 | 34 | public string FullTorrentUrl(string uri) => TorrentUrl(uri); 35 | 36 | public IEnumerable GetSources() 37 | { 38 | return BaseGetSources(); 39 | } 40 | 41 | public async IAsyncEnumerable GetSourceStates() 42 | { 43 | await foreach (var source in BaseGetSourceStates(() => GetTorrentsAsync(searchFor: "demo", page: 1, Sorting.SeedersDesc))) 44 | yield return source; 45 | } 46 | 47 | public async Task GetTorrentDescriptionAsync(string detailsUri) 48 | { 49 | var fullUrl = Path.Combine(_baseUrl, detailsUri); 50 | var response = await HttpGetAsync(fullUrl); 51 | 52 | return await _parser.ParsePageForDescriptionHtmlAsync(response.Content); 53 | } 54 | 55 | public async Task GetTorrentMagnetAsync(string detailsUri) 56 | { 57 | var fullUrl = Path.Combine(_baseUrl, detailsUri); 58 | var response = await HttpGetAsync(fullUrl); 59 | return await _parser.ParsePageForMagnetAsync(response.Content); 60 | } 61 | 62 | public async Task GetTorrentsAsync(string searchFor, int page, Sorting sorting) 63 | { 64 | var mappedSortOption = SortingMapper.SortingToKickassSorting(sorting); 65 | var fullUrl = Path.Combine(_searchEndpoint, searchFor, page.ToString(), $"?sortby={mappedSortOption.SortBy}&sort={mappedSortOption.Sort}"); 66 | 67 | var response = await HttpGetAsync(fullUrl); 68 | 69 | return await _parser.ParsePageForTorrentEntriesAsync(response.Content); 70 | } 71 | 72 | public async Task GetTorrentsByCategoryAsync(string searchFor, int page, Sorting sorting, TorrentCategory category) 73 | { 74 | var mappedSortOption = SortingMapper.SortingToKickassSorting(sorting); 75 | var mappedCategory = TorrentCategoryMapper.ToKickassCategory(category); 76 | 77 | var fullUrl = Path.Combine(_searchEndpoint, searchFor, $"category/{mappedCategory}", page.ToString(), $"?sortby={mappedSortOption.SortBy}&sort={mappedSortOption.Sort}"); 78 | 79 | var response = await HttpGetAsync(fullUrl); 80 | 81 | return await _parser.ParsePageForTorrentEntriesAsync(response.Content); 82 | } 83 | 84 | public void UpdateUsedSource(string newBaseUrl) 85 | { 86 | BaseUpdateUsedSource(newBaseUrl); 87 | } 88 | 89 | private async Task HttpGetAsync(string fullUrl) 90 | { 91 | var request = new RestRequest 92 | { 93 | Method = Method.Get, 94 | Resource = fullUrl, 95 | Timeout = 7 * 1000 96 | }; 97 | 98 | return await _restClient.ExecuteAsync(request); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/LeetxParser.cs: -------------------------------------------------------------------------------- 1 | using Constants.MultiSourceTorrentDownloader; 2 | using HtmlAgilityPack; 3 | using MultiSourceTorrentDownloader.Data; 4 | using MultiSourceTorrentDownloader.Enums; 5 | using MultiSourceTorrentDownloader.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class LeetxParser : ParserBase, ILeetxParser 15 | { 16 | private readonly ILogService _logger; 17 | 18 | private readonly string[] _formats = new string[] 19 | { 20 | "htt MMM. d\\t\\h", //11am Nov. 8th 21 | "htt MMM. d\\s\\t", //11am Nov. 1st 22 | "htt MMM. dn\\d", //11am Nov. 2nd 23 | "htt MMM. dr\\d", //11am Nov. 3rd 24 | 25 | "MMM. d\\t\\h \\'yy", //Oct. 9th '19 26 | "MMM. d\\s\\t \\'yy", //Oct. 1st '19 27 | "MMM. dn\\d \\'yy", //Oct. 2nd '19 28 | "MMM. dr\\d \\'yy", //Oct. 3rd '19 29 | }; 30 | 31 | public LeetxParser(ILogService logger) 32 | { 33 | DataColumnCount = 6; 34 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 35 | } 36 | 37 | public async Task ParsePageForTorrentEntriesAsync(string pageContents) 38 | { 39 | return await Task.Run(() => 40 | { 41 | try 42 | { 43 | _logger.Information("Leetx parsing"); 44 | var htmlAgility = new HtmlDocument(); 45 | htmlAgility.LoadHtml(pageContents); 46 | 47 | var tableRows = htmlAgility.DocumentNode.SelectNodes("//table[@class='table-list table table-responsive table-striped']/tbody/tr");//gets table rows that contain torrent data 48 | if (NoTableEntries(tableRows)) 49 | return new TorrentQueryResult { IsLastPage = true }; 50 | 51 | var result = new List(); 52 | foreach (var dataRow in tableRows) 53 | { 54 | var dataColumns = dataRow.SelectNodes("td"); 55 | if (dataColumns == null || dataColumns.Count != DataColumnCount) 56 | { 57 | _logger.Warning($"Could not find all columns for torrent {Environment.NewLine} {dataRow.OuterHtml}"); 58 | continue; 59 | } 60 | 61 | var titleNode = dataColumns[LeetxTorrentIndexer.Name] 62 | .SelectNodes("a")? 63 | .FirstOrDefault(a => a.Attributes.Any(atr => atr.Name == "href" && atr.Value.Contains("torrent"))); 64 | if (titleNode == null) 65 | { 66 | _logger.Warning($"Could not find title node for torrent {Environment.NewLine} {dataColumns[LeetxTorrentIndexer.Name].OuterHtml}"); 67 | continue; 68 | } 69 | 70 | var title = titleNode.InnerText; 71 | if (string.IsNullOrEmpty(title))//empty title entry makes no sense. log and skip 72 | { 73 | _logger.Warning($"Empty title from {Environment.NewLine}{titleNode.OuterHtml}"); 74 | continue; 75 | } 76 | 77 | var torrentUri = titleNode.Attributes.FirstOrDefault(a => a.Name == "href")?.Value; 78 | if (string.IsNullOrEmpty(torrentUri)) 79 | { 80 | _logger.Warning($"Empty torrent uri from{Environment.NewLine}{titleNode.OuterHtml}"); 81 | continue; 82 | } 83 | 84 | var magnetLink = string.Empty; 85 | 86 | if (!int.TryParse(dataColumns[LeetxTorrentIndexer.Seeders].InnerText, out var seeders)) 87 | _logger.Warning($"Could not parse seeders {Environment.NewLine}{dataColumns[LeetxTorrentIndexer.Seeders].OuterHtml}"); 88 | 89 | if (!int.TryParse(dataColumns[LeetxTorrentIndexer.Leechers].InnerText, out var leechers)) 90 | _logger.Warning($"Could not parse leechers {Environment.NewLine}{dataColumns[LeetxTorrentIndexer.Leechers].OuterHtml}"); 91 | 92 | var date = dataColumns[LeetxTorrentIndexer.Date].InnerText; 93 | 94 | var size = dataColumns[LeetxTorrentIndexer.Size].InnerHtml.Substring(0, dataColumns[LeetxTorrentIndexer.Size].InnerHtml.IndexOf('<')); 95 | var uploader = dataColumns[LeetxTorrentIndexer.Uploader].SelectSingleNode("a")?.InnerText; 96 | 97 | var splitSize = size.Split(' '); 98 | result.Add(new TorrentEntry 99 | { 100 | Title = title, 101 | TorrentUri = TrimUriStart(torrentUri), 102 | TorrentMagnet = magnetLink, 103 | Date = ParseDate(date, _formats), 104 | Size = new SizeEntity 105 | { 106 | Value = double.Parse(splitSize[0], CultureInfo.InvariantCulture), 107 | Postfix = splitSize[1] 108 | }, 109 | Uploader = uploader, 110 | Seeders = seeders, 111 | Leechers = leechers, 112 | Source = TorrentSource.Leetx 113 | }); 114 | } 115 | 116 | var pagination = htmlAgility.DocumentNode.SelectSingleNode("//div[@class='pagination']"); 117 | 118 | return new TorrentQueryResult 119 | { 120 | TorrentEntries = result, 121 | IsLastPage = pagination == null || !pagination.InnerHtml.Contains("href") 122 | }; 123 | } 124 | catch(Exception ex) 125 | { 126 | _logger.Warning("1337X parse exception", ex); 127 | throw; 128 | } 129 | 130 | }); 131 | } 132 | 133 | private bool NoTableEntries(HtmlNodeCollection tableRows) => tableRows == null; 134 | 135 | public async Task ParsePageForMagnetAsync(string pageContents) 136 | { 137 | _logger.Information("Leetx magnet parsing parsing"); 138 | 139 | return await Task.Run(() => 140 | { 141 | var htmlAgility = LoadedHtmlDocument(pageContents); 142 | 143 | var magnetNode = htmlAgility.DocumentNode 144 | .SelectNodes("//a") 145 | .FirstOrDefault(a => a.Attributes.Any(atr => atr.Name == "href" && atr.Value.Contains("magnet")) 146 | && a.InnerText.Equals("Magnet Download", StringComparison.InvariantCultureIgnoreCase)); 147 | 148 | if (magnetNode == null) 149 | throw new Exception($"Magnet node is not found"); 150 | 151 | return magnetNode.Attributes.First(m => m.Name == "href").Value; 152 | }); 153 | } 154 | 155 | public async Task ParsePageForDescriptionHtmlAsync(string pageContents) 156 | { 157 | return await Task.Run(() => 158 | { 159 | var htmlDocument = LoadedHtmlDocument(pageContents); 160 | 161 | var descriptionNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@id='description']"); 162 | if (descriptionNode == null) 163 | { 164 | _logger.Warning("Could not find description node for 1337X"); 165 | return string.Empty; 166 | } 167 | //hack around images. downloaded img src is pointing to empty .svg. need to redirect to data-original 168 | return descriptionNode.InnerHtml.Replace("src", "nothing").Replace("data-original", "src"); 169 | }); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/LeetxSource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Interfaces; 4 | using MultiSourceTorrentDownloader.Mapping; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Configuration; 8 | using System.IO; 9 | using System.Threading.Tasks; 10 | 11 | namespace MultiSourceTorrentDownloader.Services 12 | { 13 | public class LeetxSource : SourceBase, ILeetxSource 14 | { 15 | private readonly ILogService _logger; 16 | private readonly ILeetxParser _parser; 17 | 18 | private string _categorySearchResource; 19 | private string _categorySearchEndpoint; 20 | 21 | public LeetxSource(ILogService logger, ILeetxParser parser) 22 | { 23 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 24 | _parser = parser ?? throw new ArgumentNullException(nameof(parser)); 25 | 26 | _baseUrl = ConfigurationManager.AppSettings["LeetxUrl"]; 27 | _searchResource = ConfigurationManager.AppSettings["LeetxSearchEndpoint"]; 28 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 29 | 30 | _categorySearchResource = ConfigurationManager.AppSettings["LeetxCategorySearchEndpoint"]; 31 | _categorySearchEndpoint = Path.Combine(_baseUrl, _categorySearchResource); 32 | } 33 | 34 | public void UpdateUsedSource(string newBaseUrl) 35 | { 36 | BaseUpdateUsedSource(newBaseUrl); 37 | _categorySearchEndpoint = Path.Combine(_baseUrl, _categorySearchResource); 38 | } 39 | 40 | public IEnumerable GetSources() 41 | { 42 | return BaseGetSources(); 43 | } 44 | 45 | public async Task GetTorrentsAsync(string searchFor, int page, Sorting sorting) 46 | { 47 | var mapperSorting = SortingMapper.SortingToLeetxSorting(sorting); 48 | var fullUrl = Path.Combine(_searchEndpoint, searchFor, mapperSorting.SortedBy, mapperSorting.Order, page.ToString()) + Path.DirectorySeparatorChar; 49 | var contents = await UrlGetResponseString(fullUrl); 50 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 51 | } 52 | 53 | public async Task GetTorrentMagnetAsync(string detailsUri) 54 | { 55 | return await BaseGetTorrentMagnetAsync(detailsUri, _parser); 56 | } 57 | 58 | public async Task GetTorrentDescriptionAsync(string detailsUri) 59 | { 60 | return await BaseGetTorrentDescriptionAsync(Path.Combine(_baseUrl, detailsUri), _parser); 61 | } 62 | 63 | public async Task GetTorrentsByCategoryAsync(string searchFor, int page, Sorting sorting, TorrentCategory category) 64 | { 65 | var mapperSorting = SortingMapper.SortingToLeetxSorting(sorting); 66 | var mappedCategory = TorrentCategoryMapper.ToLeetxCategory(category); 67 | 68 | var fullUrl = Path.Combine(_categorySearchEndpoint, searchFor, mappedCategory, mapperSorting.SortedBy, mapperSorting.Order, page.ToString()) + Path.DirectorySeparatorChar; 69 | var contents = await UrlGetResponseString(fullUrl); 70 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 71 | } 72 | 73 | public string FullTorrentUrl(string uri) => TorrentUrl(uri); 74 | 75 | public async IAsyncEnumerable GetSourceStates() 76 | { 77 | await foreach (var source in BaseGetSourceStates(() => GetTorrentsAsync(searchFor: "demo", page: 1, Sorting.SeedersDesc))) 78 | yield return source; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/LogService.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Interfaces; 2 | using Serilog; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace MultiSourceTorrentDownloader.Services 8 | { 9 | public class LogService : ILogService 10 | { 11 | private ILogger _logger; 12 | 13 | public LogService() 14 | { 15 | _logger = new LoggerConfiguration() 16 | .WriteTo.File("Logs/log.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") 17 | .CreateLogger(); 18 | } 19 | 20 | public void Information(string message) => _logger.Information(message); 21 | 22 | public void Information(string message, Exception ex) => _logger.Information(ex, message); 23 | 24 | public void Warning(string message) => _logger.Warning(message); 25 | public void Warning(string message, Exception ex) => _logger.Warning(ex, message); 26 | 27 | public void Error(string message) => _logger.Error(message); 28 | 29 | public void Error(string message, Exception ex) => _logger.Error(ex, message); 30 | 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/ParserBase.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using System; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace MultiSourceTorrentDownloader.Services 8 | { 9 | public abstract class ParserBase 10 | { 11 | protected int DataColumnCount; 12 | protected virtual DateTime ParseDate(string date, string[] formats) 13 | { 14 | if (!DateTime.TryParse(date, out var parsedDate)) 15 | DateTime.TryParseExact(date, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate); 16 | 17 | return parsedDate; 18 | } 19 | 20 | protected string TrimUriStart(string uri) => uri.TrimStart(new char[] { '\\', '/' }); 21 | 22 | protected HtmlDocument LoadedHtmlDocument(string pageContents) 23 | { 24 | var htmlDocument = new HtmlDocument(); 25 | htmlDocument.LoadHtml(pageContents); 26 | return htmlDocument; 27 | } 28 | 29 | protected async Task BaseParseMagnet(string pageContents) 30 | { 31 | return await Task.Run(() => 32 | { 33 | var htmlAgility = LoadedHtmlDocument(pageContents); 34 | 35 | var magnetNode = htmlAgility.DocumentNode 36 | .SelectNodes("//a") 37 | .FirstOrDefault(a => a.Attributes.Any(atr => atr.Name == "href" && atr.Value.Contains("magnet"))); 38 | 39 | if (magnetNode == null) 40 | throw new Exception($"Magnet node is not found"); 41 | 42 | return magnetNode.Attributes.First(m => m.Name == "href").Value; 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/RargbParser.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using MultiSourceTorrentDownloader.Constants; 3 | using MultiSourceTorrentDownloader.Data; 4 | using MultiSourceTorrentDownloader.Enums; 5 | using MultiSourceTorrentDownloader.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class RargbParser : ParserBase, IRargbParser 15 | { 16 | private readonly ILogService _logger; 17 | 18 | public RargbParser(ILogService logger) 19 | { 20 | DataColumnCount = 8; 21 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | } 23 | 24 | public async Task ParsePageForDescriptionHtmlAsync(string pageContents) 25 | { 26 | return await Task.Run(() => 27 | { 28 | var htmlDocument = LoadedHtmlDocument(pageContents); 29 | 30 | var descriptionNode = htmlDocument.DocumentNode.SelectSingleNode("//td[@id='description']"); 31 | if (descriptionNode == null) 32 | { 33 | _logger.Warning("Could not find description node for RARGB"); 34 | return string.Empty; 35 | } 36 | 37 | return descriptionNode.InnerHtml; 38 | }); 39 | } 40 | 41 | public async Task ParsePageForMagnetAsync(string pageContents) 42 | { 43 | _logger.Information("RARGB magnet parsing parsing"); 44 | return await BaseParseMagnet(pageContents); 45 | } 46 | 47 | public async Task ParsePageForTorrentEntriesAsync(string pageContents) 48 | { 49 | return await Task.Run(() => 50 | { 51 | try 52 | { 53 | _logger.Information("RARGB parsing"); 54 | var htmlAgility = LoadedHtmlDocument(pageContents); 55 | 56 | var tableRows = htmlAgility.DocumentNode.SelectNodes("//tr[@class='lista2']");//gets table rows that contain torrent data 57 | if (NoTableEntries(tableRows)) 58 | return new TorrentQueryResult { IsLastPage = true }; 59 | 60 | var result = new List(); 61 | foreach (var row in tableRows) 62 | { 63 | var columns = row.SelectNodes("td"); 64 | if(columns == null || columns.Count != DataColumnCount) 65 | { 66 | _logger.Warning($"Could not find all columns for torrent {Environment.NewLine} {row.OuterHtml}"); 67 | continue; 68 | } 69 | 70 | var titleNode = columns[RargbTorrentIndexer.Name] 71 | .SelectSingleNode("a"); 72 | if (titleNode == null) 73 | { 74 | _logger.Warning($"Could not find title node for torrent {Environment.NewLine} {columns[RargbTorrentIndexer.Name].OuterHtml}"); 75 | continue; 76 | } 77 | 78 | var title = titleNode.InnerText; 79 | if (string.IsNullOrEmpty(title))//empty title entry makes no sense. log and skip 80 | { 81 | _logger.Warning($"Empty title from {Environment.NewLine}{titleNode.OuterHtml}"); 82 | continue; 83 | } 84 | 85 | var torrentUri = titleNode.Attributes.FirstOrDefault(a => a.Name == "href")?.Value; 86 | if (string.IsNullOrEmpty(torrentUri)) 87 | { 88 | _logger.Warning($"Empty torrent uri from{Environment.NewLine}{titleNode.OuterHtml}"); 89 | continue; 90 | } 91 | 92 | var magnetLink = string.Empty; 93 | 94 | if (!int.TryParse(columns[RargbTorrentIndexer.Seeders].InnerText, out var seeders)) 95 | _logger.Warning($"Could not parse seeders {Environment.NewLine}{columns[RargbTorrentIndexer.Seeders].OuterHtml}"); 96 | 97 | if (!int.TryParse(columns[RargbTorrentIndexer.Leechers].InnerText, out var leechers)) 98 | _logger.Warning($"Could not parse leechers {Environment.NewLine}{columns[RargbTorrentIndexer.Leechers].OuterHtml}"); 99 | 100 | var date = columns[RargbTorrentIndexer.Date].InnerText; 101 | 102 | var size = columns[RargbTorrentIndexer.Size].InnerText; 103 | var uploader = columns[RargbTorrentIndexer.Uploader].InnerText; 104 | 105 | var splitSize = size.Split(' '); 106 | result.Add(new TorrentEntry 107 | { 108 | Title = title, 109 | TorrentUri = TrimUriStart(torrentUri), 110 | TorrentMagnet = magnetLink, 111 | Date = DateTime.Parse(date), 112 | Size = new SizeEntity 113 | { 114 | Value = double.Parse(splitSize[0], CultureInfo.InvariantCulture), 115 | Postfix = splitSize[1] 116 | }, 117 | Uploader = uploader, 118 | Seeders = seeders, 119 | Leechers = leechers, 120 | Source = TorrentSource.Rargb 121 | }); 122 | } 123 | 124 | var pagination = htmlAgility.DocumentNode.SelectSingleNode("//div[@id='pager_links']"); 125 | 126 | return new TorrentQueryResult 127 | { 128 | TorrentEntries = result, 129 | IsLastPage = IsLastPage(pagination) 130 | }; 131 | 132 | } 133 | catch(Exception ex) 134 | { 135 | _logger.Warning("RARGB parse exception", ex); 136 | throw; 137 | } 138 | }); 139 | } 140 | 141 | private bool IsLastPage(HtmlNode pagination) => pagination == null || pagination.SelectNodes("a").All(n => n.InnerText != ">>"); 142 | 143 | private bool NoTableEntries(HtmlNodeCollection tableRows) => tableRows == null; 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/RargbSource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Interfaces; 4 | using MultiSourceTorrentDownloader.Mapping; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Configuration; 8 | using System.IO; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class RargbSource : SourceBase, IRargbSource 15 | { 16 | private readonly ILogService _logger; 17 | private readonly IRargbParser _parser; 18 | public RargbSource(ILogService logger, IRargbParser parser) 19 | { 20 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 21 | _parser = parser ?? throw new ArgumentNullException(nameof(parser)); 22 | 23 | _baseUrl = ConfigurationManager.AppSettings["RargbUrl"]; 24 | 25 | _searchResource = ConfigurationManager.AppSettings["RargbSearchEndpoint"]; 26 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 27 | 28 | //_mirrors = new[] 29 | //{ 30 | // "https://rarbgproxied.org/", 31 | // "https://rarbgget.org/", 32 | // "https://rarbgunblock.com/", 33 | // "https://rarbgmirror.com/", 34 | // "https://rarbg2020.org/" 35 | //}; 36 | } 37 | 38 | public IEnumerable GetSources() 39 | { 40 | return BaseGetSources(); 41 | } 42 | 43 | public void UpdateUsedSource(string newBaseUrl) 44 | { 45 | BaseUpdateUsedSource(newBaseUrl); 46 | } 47 | public string FullTorrentUrl(string uri) => TorrentUrl(uri); 48 | 49 | public async IAsyncEnumerable GetSourceStates() 50 | { 51 | await foreach (var source in BaseGetSourceStates(() => GetTorrentsAsync(searchFor: "demo", page: 1, Sorting.SeedersDesc))) 52 | yield return source; 53 | } 54 | 55 | public async Task GetTorrentDescriptionAsync(string detailsUri) 56 | { 57 | return await BaseGetTorrentDescriptionAsync(Path.Combine(_baseUrl, detailsUri), _parser); 58 | } 59 | 60 | public async Task GetTorrentMagnetAsync(string detailsUri) 61 | { 62 | return await BaseGetTorrentMagnetAsync(detailsUri, _parser); 63 | } 64 | 65 | public async Task GetTorrentsAsync(string searchFor, int page, Sorting sorting) 66 | { 67 | var mappedSortOption = SortingMapper.SortingToRargbSorting(sorting); 68 | var fullUrl = Path.Combine(_searchEndpoint, page.ToString(), $"?search={searchFor}&order={mappedSortOption.Order}&by={mappedSortOption.By}"); 69 | 70 | var contents = await UrlGetResponseString(fullUrl); 71 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 72 | } 73 | 74 | public async Task GetTorrentsByCategoryAsync(string searchFor, int page, Sorting sorting, TorrentCategory category) 75 | { 76 | var mappedSortOption = SortingMapper.SortingToRargbSorting(sorting); 77 | var mappedCategory = TorrentCategoryMapper.ToRargbCategory(category); 78 | 79 | var fullUrl = Path.Combine(_searchEndpoint, page.ToString(), $"?search={searchFor}&category[]={mappedCategory}&order={mappedSortOption.Order}&by={mappedSortOption.By}"); 80 | 81 | var contents = await UrlGetResponseString(fullUrl); 82 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/SourceBase.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Interfaces; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Threading.Tasks; 9 | 10 | namespace MultiSourceTorrentDownloader.Services 11 | { 12 | public abstract class SourceBase 13 | { 14 | protected HttpClient _httpClient; 15 | protected string _baseUrl; 16 | protected string _searchEndpoint; 17 | protected string _searchResource; 18 | 19 | protected IEnumerable _mirrors; 20 | 21 | public SourceBase() 22 | { 23 | _httpClient = new HttpClient(); 24 | _httpClient.Timeout = TimeSpan.FromMilliseconds(7000); 25 | 26 | _mirrors = new List(); 27 | } 28 | 29 | protected async Task UrlGetResponseString(string url) 30 | { 31 | var response = await _httpClient.GetAsync(url); 32 | response.EnsureSuccessStatusCode(); 33 | 34 | return await response.Content.ReadAsStringAsync(); 35 | } 36 | 37 | protected string TorrentUrl(string torrentUri) 38 | { 39 | return Path.Combine(_baseUrl, torrentUri); 40 | } 41 | 42 | protected async Task BaseGetTorrentDescriptionAsync(string fullUrl, ITorrentParser parser) 43 | { 44 | var contents = await UrlGetResponseString(fullUrl); 45 | return await parser.ParsePageForDescriptionHtmlAsync(contents); 46 | } 47 | 48 | protected async Task BaseGetTorrentMagnetAsync(string detailsUri, ITorrentParser parser) 49 | { 50 | var fullUrl = Path.Combine(_baseUrl, detailsUri); 51 | var contents = await UrlGetResponseString(fullUrl); 52 | return await parser.ParsePageForMagnetAsync(contents); 53 | } 54 | 55 | protected IEnumerable BaseGetSources() 56 | { 57 | var allSources = new List(); 58 | allSources.Add(_baseUrl); 59 | allSources.AddRange(_mirrors); 60 | return allSources; 61 | } 62 | 63 | protected void BaseUpdateUsedSource(string newBaseUrl) 64 | { 65 | _baseUrl = newBaseUrl; 66 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 67 | } 68 | 69 | /// 70 | /// Checks if each sources are active or not 71 | /// 72 | /// Type of result the func yields 73 | /// Function that throws an exception if source is dead 74 | /// 75 | protected async IAsyncEnumerable BaseGetSourceStates(Func> func) 76 | { 77 | var sources = BaseGetSources(); 78 | 79 | foreach (var source in sources) 80 | { 81 | _searchEndpoint = Path.Combine(source, _searchResource); 82 | var sourceActivity = await GetSourceActivity(source, func); 83 | yield return new SourceState(source, sourceActivity); 84 | } 85 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 86 | } 87 | 88 | private async Task GetSourceActivity(string source, Func> func) 89 | { 90 | try 91 | { 92 | var res = await func.Invoke(); 93 | if(res.TorrentEntries.Any()) 94 | return true; 95 | } 96 | catch (Exception) 97 | { 98 | return false; 99 | } 100 | 101 | return false; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/ThePirateBayParser.cs: -------------------------------------------------------------------------------- 1 | using HtmlAgilityPack; 2 | using MultiSourceTorrentDownloader.Constants; 3 | using MultiSourceTorrentDownloader.Data; 4 | using MultiSourceTorrentDownloader.Enums; 5 | using MultiSourceTorrentDownloader.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace MultiSourceTorrentDownloader.Services 13 | { 14 | public class ThePirateBayParser : ParserBase, IThePirateBayParser 15 | { 16 | private readonly ILogService _logger; 17 | 18 | private readonly string _dateStringToReplace; 19 | private readonly string _sizeStringToReplace; 20 | private readonly string _uploaderStringToReplace; 21 | 22 | private readonly string[] _formats = new string[] 23 | { 24 | "'Today' HH:mm", 25 | "MM-dd HH:mm" 26 | }; 27 | 28 | public ThePirateBayParser(ILogService logger) 29 | { 30 | DataColumnCount = 3; 31 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 32 | 33 | _dateStringToReplace = "Uploaded"; 34 | _sizeStringToReplace = "Size"; 35 | _uploaderStringToReplace = "ULed by"; 36 | } 37 | 38 | public async Task ParsePageForTorrentEntriesAsync(string pageContents) 39 | { 40 | return await Task.Run(() => 41 | { 42 | try 43 | { 44 | _logger.Information("ThePirateBay parsing"); 45 | var htmlDocument = LoadedHtmlDocument(pageContents); 46 | 47 | var tableRows = htmlDocument.DocumentNode.SelectNodes("//table[@id='searchResult']/tr");//gets table rows that contain torrent data without table header 48 | if (NoTableEntries(tableRows))//probably end of results 49 | return new TorrentQueryResult { IsLastPage = true }; 50 | 51 | var result = new List(); 52 | foreach (var dataRow in tableRows) 53 | { 54 | var dataColumns = dataRow.SelectNodes("td[position()>1]");//skips first column because it does not contain useful info 55 | if (dataColumns == null || dataColumns.Count != DataColumnCount) 56 | { 57 | _logger.Warning($"Could not find all columns for torrent {Environment.NewLine} {dataRow.OuterHtml}"); 58 | continue; 59 | } 60 | 61 | var titleNode = dataColumns[ThePirateBayTorrentIndexer.TitleNode].SelectSingleNode("div[@class='detName']/a[@class='detLink']"); 62 | if (titleNode == null) 63 | { 64 | _logger.Warning($"Could not find title node for torrent {Environment.NewLine} {dataColumns[ThePirateBayTorrentIndexer.TitleNode].OuterHtml}"); 65 | continue; 66 | } 67 | 68 | var title = titleNode.InnerText; 69 | if (string.IsNullOrEmpty(title))//empty title entry makes no sense. log and skip 70 | { 71 | _logger.Warning($"Empty title from {Environment.NewLine}{titleNode.OuterHtml}"); 72 | continue; 73 | } 74 | var torrentUri = titleNode.Attributes?.FirstOrDefault(a => a.Name == "href")?.Value;//this field is not vital, null is acceptable 75 | 76 | var magnetLink = string.Empty; 77 | try 78 | { 79 | magnetLink = dataColumns[ThePirateBayTorrentIndexer.TitleNode].SelectNodes("a") 80 | .First(a => a.Attributes.Any(atr => atr.Name == "href" && atr.Value.Contains("magnet"))) 81 | .Attributes.First(atr => atr.Name == "href") 82 | .Value; 83 | } 84 | catch (Exception ex) 85 | { 86 | _logger.Warning($"Could not find magnet link for {Environment.NewLine}{dataColumns[ThePirateBayTorrentIndexer.TitleNode].OuterHtml}", ex); 87 | continue;//no point in showing non-downloadable entry 88 | } 89 | 90 | var detailsNode = dataColumns[ThePirateBayTorrentIndexer.TitleNode].SelectSingleNode("font[@class='detDesc']"); 91 | if (detailsNode == null) 92 | { 93 | _logger.Warning($"Could not find details node for {Environment.NewLine}{dataColumns[ThePirateBayTorrentIndexer.TitleNode].OuterHtml}"); 94 | continue; 95 | } 96 | 97 | var details = (detailsNode.InnerText + detailsNode.SelectSingleNode("a")?.InnerText).Replace(" ", " ").Split(',');//date, size, uploader 98 | var date = string.Empty; 99 | var size = string.Empty; 100 | var uploader = string.Empty; 101 | 102 | if (details.Length == 3) 103 | { 104 | date = PrunedDetail(details[ThePirateBayTorrentIndexer.DateIndex], _dateStringToReplace); 105 | size = PrunedDetail(details[ThePirateBayTorrentIndexer.SizeIndex], _sizeStringToReplace); 106 | uploader = PrunedDetail(details[ThePirateBayTorrentIndexer.UploaderIndex], _uploaderStringToReplace); 107 | } 108 | 109 | if (!int.TryParse(dataColumns[ThePirateBayTorrentIndexer.Seeders].InnerText, out var seeders)) 110 | _logger.Warning($"Could not parse seeders {Environment.NewLine}{dataColumns[ThePirateBayTorrentIndexer.Seeders].OuterHtml}"); 111 | 112 | if (!int.TryParse(dataColumns[ThePirateBayTorrentIndexer.Leechers].InnerText, out var leechers)) 113 | _logger.Warning($"Could not parse leechers {Environment.NewLine}{dataColumns[ThePirateBayTorrentIndexer.Leechers].OuterHtml}"); 114 | 115 | var splitSize = size.Split(' '); 116 | result.Add(new TorrentEntry 117 | { 118 | Title = title, 119 | TorrentUri = TrimUriStart(torrentUri), 120 | TorrentMagnet = magnetLink, 121 | Date = ParseDate(date, _formats), 122 | Size = new SizeEntity 123 | { 124 | Value = splitSize[1] != "B" ? double.Parse(splitSize[0], CultureInfo.InvariantCulture) : double.Parse(splitSize[0], CultureInfo.InvariantCulture) / 1024, 125 | Postfix = ParseSizePostfix(splitSize[1]) 126 | }, 127 | Uploader = uploader, 128 | Seeders = seeders, 129 | Leechers = leechers, 130 | Source = TorrentSource.ThePirateBay 131 | }); 132 | } 133 | 134 | var pagination = htmlDocument.DocumentNode.SelectNodes("//img[@alt='Next']"); 135 | return new TorrentQueryResult 136 | { 137 | TorrentEntries = result, 138 | IsLastPage = pagination == null 139 | }; 140 | } 141 | catch(Exception ex) 142 | { 143 | _logger.Warning("Pirate bay parse exception", ex); 144 | throw; 145 | } 146 | 147 | }); 148 | } 149 | 150 | private string PrunedDetail(string source, string toRemove) => source.Replace(toRemove, "").Trim(); 151 | 152 | private bool NoTableEntries(HtmlNodeCollection tableRows) => tableRows == null; 153 | 154 | protected override DateTime ParseDate(string date, string[] formats) 155 | { 156 | var yesterdayFormat = "'Y-day' HH:mm"; 157 | if (DateTime.TryParseExact(date, yesterdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDate)) 158 | return parsedDate.AddDays(-1); 159 | 160 | if (!DateTime.TryParse(date, out parsedDate)) 161 | DateTime.TryParseExact(date, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate); 162 | 163 | return parsedDate; 164 | } 165 | 166 | public async Task ParsePageForMagnetAsync(string pageContents)//magnet is parsed from first page 167 | { 168 | throw new NotImplementedException(); 169 | } 170 | 171 | public async Task ParsePageForDescriptionHtmlAsync(string pageContents) 172 | { 173 | return await Task.Run(() => 174 | { 175 | var htmlDocument = LoadedHtmlDocument(pageContents); 176 | 177 | var detailsNode = htmlDocument.DocumentNode.SelectSingleNode("//pre"); 178 | if (detailsNode == null) 179 | { 180 | _logger.Warning($"Could not find details node for thepiratebay"); 181 | return string.Empty; 182 | } 183 | 184 | return detailsNode.OuterHtml; 185 | }); 186 | 187 | } 188 | 189 | private string ParseSizePostfix(string postfix) 190 | { 191 | if (postfix == "B") return SizePostfix.KiloBytes; 192 | if (postfix == "KiB") return SizePostfix.KiloBytes; 193 | if (postfix == "MiB") return SizePostfix.MegaBytes; 194 | if (postfix == "GiB") return SizePostfix.GigaBytes; 195 | 196 | return SizePostfix.Undefined; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/ThePirateBaySource.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Interfaces; 4 | using MultiSourceTorrentDownloader.Mapping; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Configuration; 8 | using System.IO; 9 | using System.Threading.Tasks; 10 | 11 | namespace MultiSourceTorrentDownloader.Services 12 | { 13 | public class ThePirateBaySource : SourceBase, IThePirateBaySource 14 | { 15 | private readonly ILogService _logger; 16 | private readonly IThePirateBayParser _parser; 17 | 18 | public ThePirateBaySource(ILogService logger, IThePirateBayParser parser) 19 | { 20 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 21 | _parser = parser ?? throw new ArgumentNullException(nameof(parser)); 22 | 23 | _baseUrl = ConfigurationManager.AppSettings["ThePirateBayUrl"]; 24 | 25 | _searchResource = ConfigurationManager.AppSettings["ThePirateBaySearchEndpoint"]; 26 | _searchEndpoint = Path.Combine(_baseUrl, _searchResource); 27 | 28 | _mirrors = new[]// TODO: FROM CONFIG 29 | { 30 | "https://tpb.party/", 31 | "https://thepiratebay0.org/", 32 | "https://thepiratebay10.org/", 33 | "https://piratebay1.live/" 34 | }; 35 | } 36 | 37 | public IEnumerable GetSources() 38 | { 39 | return BaseGetSources(); 40 | } 41 | 42 | public void UpdateUsedSource(string newBaseUrl) 43 | { 44 | BaseUpdateUsedSource(newBaseUrl); 45 | } 46 | public async Task GetTorrentsAsync(string searchFor, int page, Sorting sorting) 47 | { 48 | var mappedSortOption = SortingMapper.SortingToThePirateBaySorting(sorting); 49 | var fullUrl = Path.Combine(_searchEndpoint, searchFor, page.ToString(), mappedSortOption.ToString(), "0"); 50 | 51 | var contents = await UrlGetResponseString(fullUrl); 52 | 53 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 54 | } 55 | 56 | public async Task GetTorrentMagnetAsync(string detailsUri)// TPB has magnets on search page 57 | { 58 | throw new NotImplementedException(); 59 | } 60 | 61 | public async Task GetTorrentDescriptionAsync(string detailsUri) 62 | { 63 | var fullUrl = detailsUri.Contains(_baseUrl) ? detailsUri : Path.Combine(_baseUrl, detailsUri);//mirrors have full url while original has it without baseUrl 64 | return await BaseGetTorrentDescriptionAsync(fullUrl, _parser); 65 | } 66 | 67 | public async Task GetTorrentsByCategoryAsync(string searchFor, int page, Sorting sorting, TorrentCategory category) 68 | { 69 | var mappedSortOption = SortingMapper.SortingToThePirateBaySorting(sorting); 70 | var mappedCategorySearch = TorrentCategoryMapper.ToThePirateBayCategory(category); 71 | var fullUrl = Path.Combine(_searchEndpoint, searchFor, page.ToString(), mappedSortOption.ToString(), mappedCategorySearch); 72 | 73 | var contents = await UrlGetResponseString(fullUrl); 74 | 75 | return await _parser.ParsePageForTorrentEntriesAsync(contents); 76 | } 77 | 78 | public string FullTorrentUrl(string uri) => uri.Contains(_baseUrl) ? uri : TorrentUrl(uri); 79 | 80 | public async IAsyncEnumerable GetSourceStates() 81 | { 82 | await foreach (var source in BaseGetSourceStates(() => GetTorrentsAsync(searchFor: "demo", page: 1, Sorting.SeedersDesc))) 83 | yield return source; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Services/UserConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Data; 2 | using MultiSourceTorrentDownloader.Interfaces; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace MultiSourceTorrentDownloader.Services 9 | { 10 | public class UserConfiguration : IUserConfiguration 11 | { 12 | private Settings _settingsSingleton; 13 | 14 | private readonly ILogService _logService; 15 | 16 | private readonly string _windowSettingsPath; 17 | private readonly string _searchSettingsPath; 18 | private readonly string _autoCompleteSettingsPath; 19 | 20 | public UserConfiguration(ILogService logService) 21 | { 22 | _logService = logService ?? throw new ArgumentNullException(nameof(LogService)); 23 | 24 | var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create); 25 | var currAppDir = Path.Combine(localAppData, System.Diagnostics.Process.GetCurrentProcess().ProcessName); 26 | Directory.CreateDirectory(currAppDir); 27 | 28 | _windowSettingsPath = Path.Combine(currAppDir, "windowSettings.json"); 29 | if (!File.Exists(_windowSettingsPath)) 30 | { 31 | using var windowsFile = File.Create(_windowSettingsPath); 32 | } 33 | 34 | _searchSettingsPath = Path.Combine(currAppDir, "searchSettings.json"); 35 | if (!File.Exists(_searchSettingsPath)) 36 | { 37 | using var searchFile = File.Create(_searchSettingsPath); 38 | } 39 | 40 | _autoCompleteSettingsPath = Path.Combine(currAppDir, "autoCompleteSettings.json"); 41 | if (!File.Exists(_autoCompleteSettingsPath)) 42 | { 43 | using var autoCompleteFile = File.Create(_autoCompleteSettingsPath); 44 | } 45 | } 46 | 47 | public Settings GetConfiguration() 48 | { 49 | if (_settingsSingleton != null) 50 | return _settingsSingleton; 51 | 52 | var windowSettings = ReadSettings(_windowSettingsPath); 53 | var searchSettings = ReadSettings(_searchSettingsPath); 54 | 55 | var autoCompleteSettings = ReadSettings(_autoCompleteSettingsPath); 56 | if (autoCompleteSettings is null) 57 | { 58 | autoCompleteSettings = new AutoComplete 59 | { 60 | Values = Enumerable.Empty() 61 | }; 62 | } 63 | 64 | _settingsSingleton = new Settings 65 | { 66 | Window = windowSettings, 67 | Search = searchSettings, 68 | AutoComplete = autoCompleteSettings 69 | }; 70 | 71 | return _settingsSingleton; 72 | } 73 | 74 | private T ReadSettings(string filePath) where T: class 75 | { 76 | try 77 | { 78 | var fileData = File.ReadAllText(filePath); 79 | return JsonConvert.DeserializeObject(fileData); 80 | } 81 | catch (Exception ex) 82 | { 83 | _logService.Information($"{typeof(T)} settings parse exception", ex); 84 | return null; 85 | } 86 | } 87 | 88 | public void SaveSettings(T settings) where T: class 89 | { 90 | try 91 | { 92 | var res = JsonConvert.SerializeObject(settings, Formatting.Indented); 93 | 94 | var path = settings switch 95 | { 96 | Search => _searchSettingsPath, 97 | Window => _windowSettingsPath, 98 | AutoComplete => _autoCompleteSettingsPath, 99 | _ => throw new NotImplementedException("Settings type not found"), 100 | }; 101 | 102 | File.WriteAllText(path, res); 103 | } 104 | catch (Exception ex) 105 | { 106 | _logService.Information($"{typeof(T)} settings save exception", ex); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/ViewModels/TorrentInfoDialogViewModel.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Common; 2 | using MultiSourceTorrentDownloader.Enums; 3 | using MultiSourceTorrentDownloader.Interfaces; 4 | using MultiSourceTorrentDownloader.Models; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows; 12 | 13 | namespace MultiSourceTorrentDownloader.ViewModels 14 | { 15 | public class TorrentInfoDialogViewModel : ViewModelBase 16 | { 17 | private readonly ILogService _logger; 18 | 19 | public TorrentInfoDialogViewModel(ILogService logger) 20 | { 21 | _logger = logger; 22 | 23 | InitializeModel(); 24 | } 25 | 26 | private void InitializeModel() 27 | { 28 | Model.DownloadTorrentCommand = new Command(OnDownloadTorrentCommand); 29 | Model.CopyTorrentLinkCommand = new Command(OnCopyTorrentLinkCommand); 30 | } 31 | 32 | private void OnCopyTorrentLinkCommand(object obj) 33 | { 34 | Clipboard.SetText(Model.TorrentLink); 35 | ShowStatusBarMessage(MessageType.Information, "Copied torrent link to clipboard"); 36 | } 37 | 38 | private void OnDownloadTorrentCommand(object obj) 39 | { 40 | try 41 | { 42 | Model.IsLoading = true; 43 | Process.Start(Model.TorrentMagnet); 44 | Model.MagnetDownloaded = true; 45 | ShowStatusBarMessage(MessageType.Information, "Opening on local torrent downloading app"); 46 | } 47 | catch (Exception ex) 48 | { 49 | _logger.Warning($"Failed to open magnet '{Model.TorrentMagnet}'", ex); 50 | ShowStatusBarMessage(MessageType.Error, $"Failed to open magnet link: {ex.Message}"); 51 | } 52 | finally 53 | { 54 | Model.IsLoading = false; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using MultiSourceTorrentDownloader.Enums; 2 | using MultiSourceTorrentDownloader.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reactive.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MultiSourceTorrentDownloader.ViewModels 11 | { 12 | public class ViewModelBase where T : ModelBase, new() 13 | { 14 | public T Model { get; private set; } 15 | 16 | private TimeSpan _statusBarFadeTime; 17 | private IDisposable _statusBarSubscription; 18 | 19 | public ViewModelBase() 20 | { 21 | Model = new T(); 22 | Initialize(); 23 | } 24 | 25 | private void Initialize() 26 | { 27 | _statusBarFadeTime = TimeSpan.FromSeconds(10); 28 | Model.MessageType = MessageType.Empty; 29 | Model.StatusBarObservable 30 | .Where(x => x != MessageType.Empty) 31 | .Subscribe(OnStatusBarMessageChanged); 32 | } 33 | 34 | private void OnStatusBarMessageChanged(MessageType obj) 35 | { 36 | _statusBarSubscription?.Dispose(); 37 | _statusBarSubscription = Observable 38 | .Timer(_statusBarFadeTime) 39 | .Subscribe(x => 40 | { 41 | Model.StatusBarMessage = string.Empty; 42 | Model.MessageType = MessageType.Empty; 43 | }); 44 | 45 | } 46 | 47 | public void ShowStatusBarMessage(MessageType messageType, string message) 48 | { 49 | Model.MessageType = messageType; 50 | Model.StatusBarMessage = message; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Views/DrawerView.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Views/DrawerView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace MultiSourceTorrentDownloader.Views 17 | { 18 | /// 19 | /// Interaction logic for DrawerView.xaml 20 | /// 21 | public partial class DrawerView : UserControl 22 | { 23 | public DrawerView() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 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 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 145 | 146 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 187 | 189 | 190 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 204 | 205 | 206 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 111 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/Views/TorrentInfoDialogView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CefSharp.Wpf; 2 | using MultiSourceTorrentDownloader.Models; 3 | using System; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | 7 | namespace MultiSourceTorrentDownloader.Views 8 | { 9 | /// 10 | /// Interaction logic for TorrentInfoDialogView.xaml 11 | /// 12 | public partial class TorrentInfoDialogView : UserControl 13 | { 14 | private static UserControl _userControl; 15 | public TorrentInfoDialogView() 16 | { 17 | _userControl = this; 18 | Loaded += TorrentInfoDialogView_Loaded; 19 | InitializeComponent(); 20 | } 21 | 22 | private void TorrentInfoDialogView_Loaded(object sender, RoutedEventArgs e) 23 | { 24 | if (DataContext is TorrentInfoDialogModel context) 25 | { 26 | context.IsLoading = true;// showing loading icon while browser loads up 27 | } 28 | } 29 | 30 | private void IsBrowserInitializedChanged(object sender, DependencyPropertyChangedEventArgs e) 31 | { 32 | if (sender is ChromiumWebBrowser browser && browser.IsBrowserInitialized) 33 | { 34 | TrySetLoadingIndicator(false); 35 | } 36 | } 37 | 38 | private void LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) 39 | { 40 | if(e.IsLoading) 41 | TrySetLoadingIndicator(true); 42 | else 43 | TrySetLoadingIndicator(false); 44 | } 45 | 46 | private void TrySetLoadingIndicator(bool value) 47 | { 48 | App.Current.Dispatcher.Invoke(() => 49 | { 50 | if (DataContext is TorrentInfoDialogModel context) 51 | { 52 | context.IsLoading = value; 53 | } 54 | else 55 | { 56 | MessageBox.Show("Model for torrent info is incorrect! Loss in information may occur"); 57 | } 58 | }); 59 | 60 | } 61 | 62 | public static void ParentWindowSizeChangedHandler(object sender, SizeChangedEventArgs e) 63 | { 64 | if(_userControl != null) 65 | { 66 | _userControl.Width = e.NewSize.Width; 67 | _userControl.Height = e.NewSize.Height; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | Windows 7 22 | 23 | 24 | Windows 8 25 | 26 | 27 | Windows 8.1 28 | 29 | 30 | Windows 10 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/MultiSourceTorrentDownloader/icon.ico -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MultiSourceTorrentDownloader/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/MultiSourceTorrentDownloader/splash.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiSourceTorrentDownloader 2 | An app that gets torrents from multiple sites (like thepiratebay, 133tx.to) and shows them in one place with the ability to download with local app (this app only opens the magnet/torrent, a separate app like uTorrent or BitTorrent is required)
3 | 4 | 5 | ## Compilation 6 | - Target the solution for x86 or x64 (needed for CefCharp chromium browser) 7 | - App.xaml.cs file requires a Secret.cs file for Syncfusion framework which contains a license key. One can replace that code with empty string and use trial license or create an account for Syncfusion and generate a lifetime community license key. Example of the Class 8 | ```C# 9 | public class Secret 10 | { 11 | public static string SyncFususionLicenseKey { get; } = "MySecretLicenseKey"; 12 | } 13 | ``` 14 | 15 | ## Releases 16 | [Download page](https://github.com/aivarasatk/MultiSourceTorrentDownloader/releases)
17 | Unzip the file and launch the MultiSourceTorrentDownloader.exe or create a shortcut for it (installer is planned in future)
18 | Some users have reported that torrents are not launching on their local torrent download app a fix for that is to execute the app as admin 19 | 20 | ## Future 21 | This is an ongoing project. Progress board can be viewed on [Trello](https://trello.com/b/O3gltd5G/multi-source-torrent-downloader) 22 | 23 | ### Screenshots 24 | ![launched](demo/launched.png) 25 | ![searched](demo/searched.png) 26 | ![menu](demo/menu.png) 27 | ![details](demo/details.png) 28 | 29 | ## Contributing 30 | [MIT license](license.txt) 31 | -------------------------------------------------------------------------------- /TorrentSourceTemplate.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/TorrentSourceTemplate.zip -------------------------------------------------------------------------------- /demo/details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/demo/details.png -------------------------------------------------------------------------------- /demo/launched.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/demo/launched.png -------------------------------------------------------------------------------- /demo/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/demo/menu.png -------------------------------------------------------------------------------- /demo/searched.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/demo/searched.png -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/icon.ico -------------------------------------------------------------------------------- /icon_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/icon_big.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2019 Google, Inc. http://angularjs.org 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aivarasatk/MultiSourceTorrentDownloader/890ac09fc1c6a52cbdbcd896744eb0bd1b1c3c58/splash.png --------------------------------------------------------------------------------