├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .vsconfig ├── Assets ├── Icon.ico ├── Icon.png ├── Legacy1_CommandLine.md ├── Legacy1_Configuration.md ├── Legacy1_Home.md ├── Legacy1_Interface.md ├── Legacy2_Usage.md ├── SmartImage icon.psd ├── Usage.md └── Wiki_TOC_Old.md ├── Directory.Build.props ├── Examples ├── Config.png ├── Demo 1.gif ├── Main menu 2.png ├── Main menu diagram.png ├── Main menu.png ├── Main menu.psd ├── Old │ ├── Config (2).png │ ├── Config.png │ ├── Context menu integration.png │ ├── Demo 1-old.gif │ ├── Demo 1.gif │ ├── Demo 2.gif │ ├── Demo 3.gif │ ├── Example search results.png │ ├── Main menu (2).png │ ├── Main menu.png │ ├── Rdx help.png │ ├── Results 1.png │ └── Results 2.png ├── Rdx context menu.png ├── Rdx help.png ├── Results 1.png └── ShareX.png ├── LICENSE ├── NuGet.config ├── README.md ├── SmartImage.Lib ├── Clients │ ├── AnilistClient.cs │ ├── Booru │ │ ├── BaseBooruClient.cs │ │ ├── BaseGelbooruClient.cs │ │ └── Rule34Booru.cs │ ├── FlareSolverrClient.cs │ └── HydrusClient.cs ├── Cookies │ ├── BrowserCookiesProvider.cs │ ├── DefaultCookiesProvider.cs │ ├── ICookiesEngine.cs │ └── ICookiesProvider.cs ├── Engines │ ├── BaseSearchEngine.cs │ ├── IEndpointEngine.cs │ ├── Impl │ │ ├── Search │ │ │ ├── ArchiveMoeEngine.cs │ │ │ ├── Ascii2DEngine.cs │ │ │ ├── EHentaiEngine.cs │ │ │ ├── FluffleEngine.cs │ │ │ ├── GoogleLensEngine.cs │ │ │ ├── Iqdb3DEngine.cs │ │ │ ├── IqdbEngine.cs │ │ │ ├── Other │ │ │ │ ├── BingEngine.cs │ │ │ │ ├── GoogleImagesEngine.cs │ │ │ │ ├── ImgOpsEngine.cs │ │ │ │ └── KarmaDecayEngine.cs │ │ │ ├── RepostSleuthEngine.cs │ │ │ ├── SauceNaoEngine.cs │ │ │ ├── TinEyeEngine.cs │ │ │ ├── TraceMoeEngine.cs │ │ │ └── YandexEngine.cs │ │ └── Upload │ │ │ ├── BaseUploadEngine.cs │ │ │ ├── CatboxEngine.cs │ │ │ ├── LitterboxEngine.cs │ │ │ ├── PomfEngine.cs │ │ │ └── UploadEngineOptions.cs │ ├── SearchEngineOptions.cs │ └── WebSearchEngine.cs ├── Images │ ├── ImageScanner.cs │ └── Uni │ │ ├── UniImage.cs │ │ ├── UniImageFile.cs │ │ ├── UniImageStream.cs │ │ └── UniImageUri.cs ├── Resources.Designer.cs ├── Resources.resx ├── Results │ ├── Data │ │ ├── IHashable.cs │ │ ├── IItemConvertable.cs │ │ ├── IItemConverter.cs │ │ ├── ISearchConfigReceiver.cs │ │ ├── ISimilarity.cs │ │ └── ISize.cs │ ├── SearchResult.cs │ ├── SearchResultItem.cs │ └── UploadResult.cs ├── SearchClient.cs ├── SearchConfig.cs ├── SearchQuery.cs ├── Serialization.Designer.cs ├── Serialization.resx ├── SmartImage.Lib.csproj ├── SmartImage.Lib.csproj.DotSettings └── Utilities │ ├── BaseSearchEngineTypeConverter.cs │ ├── Diagnostics │ ├── AppSupport.cs │ ├── HttpLoggingHandler.cs │ └── SmartImageException.cs │ ├── FieldValueMap.cs │ ├── Integration │ ├── BaseOSIntegration.cs │ ├── LinuxOSIntegration.cs │ └── WindowsOSIntegration.cs │ ├── NodeHelper.cs │ ├── SearchResultTypeConverter.cs │ └── SearchUtil.cs ├── SmartImage.Rdx ├── Commands │ ├── CommonCommandSettings.cs │ ├── IntegrationCommand.cs │ ├── IntegrationCommandSettings.cs │ ├── SearchCommand.cs │ ├── SearchCommandSettings.cs │ ├── SearchServerResponse.cs │ ├── ServerCommand.cs │ └── ServerCommandSettings.cs ├── Icon.ico ├── Program.cs ├── Resources.Designer.cs ├── Resources.resx ├── Resources │ ├── Cybermedium.flf │ ├── Santa Clara.flf │ └── larry3d.flf ├── Shell │ ├── ColorUtil.cs │ ├── ConsoleFormat.cs │ ├── ConsoleUtil.cs │ └── CustomHelpProvider.cs ├── SmartImage.Rdx.csproj └── Utilities │ ├── TypeRegistrar.cs │ └── TypeResolver.cs ├── SmartImage.UI ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Controls │ ├── AppComponents.cs │ ├── ControlsHelper.cs │ ├── Converters │ │ ├── BoolToValConverter.cs │ │ ├── BooleanToBrushConverter.cs │ │ ├── ImageDimensionConverter.cs │ │ ├── InvertableBooleanToVisibilityConverter.cs │ │ ├── UnitStringConverter.cs │ │ └── UrlConverter.cs │ ├── GridViewSort.cs │ ├── SharedImageControl.xaml │ └── SharedImageControl.xaml.cs ├── HydrusWindow.xaml ├── HydrusWindow.xaml.cs ├── Icon.ico ├── MainWindow.Handlers.cs ├── MainWindow.State.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Model │ ├── IBitmapImageSource.cs │ ├── INamed.cs │ ├── LazyProperty.cs │ ├── LogEntry.cs │ ├── QueryModel.cs │ ├── ResultItem.cs │ ├── SharedInfo.cs │ └── UniResultItem.cs ├── ResourceDict.xaml ├── Resources.Designer.cs ├── Resources.resx ├── Resources │ ├── accept.png │ ├── arrow_down.png │ ├── arrow_redo.png │ ├── arrow_refresh.png │ ├── arrow_rotate_anticlockwise.png │ ├── arrow_undo.png │ ├── artwork.png │ ├── asterisk_yellow.png │ ├── clipboard_invoice.png │ ├── clipboard_sign.png │ ├── emotion_question.png │ ├── exclamation.png │ ├── help.png │ ├── image.png │ ├── image_link.png │ ├── information.png │ ├── link.png │ ├── picture.png │ ├── picture_add.png │ ├── picture_delete.png │ ├── picture_empty.png │ ├── picture_error.png │ ├── picture_go.png │ ├── picture_insert.png │ ├── picture_link.png │ ├── picture_save.png │ └── pictures.png ├── ResultWindow.xaml ├── ResultWindow.xaml.cs └── SmartImage.UI.csproj ├── SmartImage.UI2 ├── Program.cs └── SmartImage.UI2.csproj └── SmartImage.sln /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{cs,vb}] 2 | 3 | # IDE0049: Simplify Names 4 | dotnet_diagnostic.IDE0049.severity = none 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | 14 | custom: ['https://paypal.me/decimation001'] 15 | -------------------------------------------------------------------------------- /.vsconfig: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "components": [ 4 | "Microsoft.VisualStudio.Component.CoreEditor", 5 | "Microsoft.VisualStudio.Workload.CoreEditor", 6 | "Microsoft.NetCore.Component.Runtime.3.1", 7 | "Microsoft.NetCore.Component.SDK", 8 | "Microsoft.VisualStudio.Component.NuGet", 9 | "Microsoft.VisualStudio.Component.Roslyn.Compiler", 10 | "Microsoft.VisualStudio.Component.Roslyn.LanguageServices", 11 | "Microsoft.NetCore.Component.DevelopmentTools", 12 | "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions", 13 | "Microsoft.VisualStudio.Component.DockerTools", 14 | "Microsoft.NetCore.Component.Web", 15 | "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites", 16 | "Microsoft.VisualStudio.Component.TypeScript.4.0", 17 | "Microsoft.VisualStudio.Component.JavaScript.TypeScript", 18 | "Microsoft.VisualStudio.Component.JavaScript.Diagnostics", 19 | "Microsoft.Component.MSBuild", 20 | "Microsoft.VisualStudio.Component.TextTemplating", 21 | "Component.Microsoft.VisualStudio.RazorExtension", 22 | "Microsoft.VisualStudio.Component.IISExpress", 23 | "Microsoft.VisualStudio.Component.SQL.ADAL", 24 | "Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime", 25 | "Microsoft.VisualStudio.Component.Common.Azure.Tools", 26 | "Microsoft.VisualStudio.Component.SQL.CLR", 27 | "Microsoft.VisualStudio.Component.MSODBC.SQL", 28 | "Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils", 29 | "Microsoft.VisualStudio.Component.ManagedDesktop.Core", 30 | "Microsoft.VisualStudio.Component.SQL.SSDT", 31 | "Microsoft.VisualStudio.Component.SQL.DataSources", 32 | "Component.Microsoft.Web.LibraryManager", 33 | "Microsoft.VisualStudio.ComponentGroup.Web", 34 | "Microsoft.VisualStudio.Component.Web", 35 | "Microsoft.VisualStudio.Component.IntelliCode", 36 | "Component.Microsoft.VisualStudio.LiveShare", 37 | "Microsoft.VisualStudio.ComponentGroup.Web.Client", 38 | "Microsoft.Net.ComponentGroup.TargetingPacks.Common", 39 | "Component.Microsoft.VisualStudio.Web.AzureFunctions", 40 | "Microsoft.VisualStudio.ComponentGroup.AzureFunctions", 41 | "Microsoft.VisualStudio.Component.Azure.Compute.Emulator", 42 | "Microsoft.VisualStudio.Component.Azure.Storage.Emulator", 43 | "Microsoft.VisualStudio.Component.Azure.ClientLibs", 44 | "Microsoft.VisualStudio.Component.Azure.AuthoringTools", 45 | "Microsoft.VisualStudio.Component.CloudExplorer", 46 | "Microsoft.VisualStudio.ComponentGroup.Web.CloudTools", 47 | "Microsoft.VisualStudio.Component.DiagnosticTools", 48 | "Microsoft.VisualStudio.Component.EntityFramework", 49 | "Microsoft.VisualStudio.Component.AspNet45", 50 | "Microsoft.VisualStudio.Component.AppInsights.Tools", 51 | "Microsoft.VisualStudio.Component.WebDeploy", 52 | "Microsoft.VisualStudio.Workload.NetWeb", 53 | "Microsoft.VisualStudio.ComponentGroup.Azure.Prerequisites", 54 | "Microsoft.VisualStudio.Component.Azure.Waverton.BuildTools", 55 | "Microsoft.VisualStudio.Component.Azure.Waverton", 56 | "Microsoft.Component.Azure.DataLake.Tools", 57 | "Microsoft.VisualStudio.Component.Azure.Kubernetes.Tools", 58 | "Microsoft.VisualStudio.Component.Azure.ResourceManager.Tools", 59 | "Microsoft.VisualStudio.ComponentGroup.Azure.ResourceManager.Tools", 60 | "Microsoft.VisualStudio.ComponentGroup.Azure.CloudServices", 61 | "Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools", 62 | "Microsoft.VisualStudio.Workload.Azure", 63 | "Microsoft.VisualStudio.Component.VC.CoreIde", 64 | "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", 65 | "Microsoft.VisualStudio.Component.Graphics.Tools", 66 | "Microsoft.VisualStudio.Component.Windows10SDK.19041", 67 | "Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites", 68 | "Microsoft.ComponentGroup.Blend", 69 | "Microsoft.VisualStudio.Component.Debugger.JustInTime", 70 | "Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging", 71 | "Microsoft.VisualStudio.Workload.ManagedDesktop", 72 | "Microsoft.VisualStudio.Component.Windows10SDK.18362", 73 | "Microsoft.Component.NetFX.Native", 74 | "Microsoft.VisualStudio.ComponentGroup.UWP.NetCoreAndStandard", 75 | "Microsoft.VisualStudio.Component.Graphics", 76 | "Microsoft.VisualStudio.ComponentGroup.UWP.Xamarin", 77 | "Microsoft.VisualStudio.ComponentGroup.UWP.Support", 78 | "Microsoft.VisualStudio.Component.VC.Tools.ARM64", 79 | "Microsoft.VisualStudio.Component.UWP.VC.ARM64", 80 | "Microsoft.VisualStudio.Component.VC.Tools.ARM", 81 | "Microsoft.VisualStudio.ComponentGroup.UWP.VC", 82 | "Microsoft.VisualStudio.Workload.Universal", 83 | "Component.OpenJDK", 84 | "Microsoft.VisualStudio.Component.MonoDebugger", 85 | "Microsoft.VisualStudio.Component.Merq", 86 | "Component.Xamarin.RemotedSimulator", 87 | "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.TemplateEngine", 88 | "Component.Xamarin", 89 | "Component.Android.SDK28", 90 | "Microsoft.VisualStudio.Workload.NetCrossPlat", 91 | "Microsoft.VisualStudio.Workload.NetCoreTools", 92 | "Microsoft.VisualStudio.ComponentGroup.Maui.All", 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /Assets/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Assets/Icon.ico -------------------------------------------------------------------------------- /Assets/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Assets/Icon.png -------------------------------------------------------------------------------- /Assets/Legacy1_CommandLine.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## Search Engines 4 | 5 | `-se ` _(parameter)_ 6 | 7 | Specifies engines with a comma-separated list of search engine names 8 | 9 | ## Priority Engines 10 | 11 | `-pe ` _(parameter)_ 12 | 13 | Specifies priority engines with a comma-separated list of search engine names 14 | 15 | ## Filtering 16 | 17 | `-f` _(switch)_ 18 | 19 | Enables filtering of results (omit to disable) 20 | 21 | # Input 22 | 23 | The last parameter should be the image query. 24 | 25 | # Examples 26 | 27 | `smartimage -se All -pe SauceNao 'https://litter.catbox.moe/x8jfkj.jpg'` 28 | 29 | Runs a search of `https://litter.catbox.moe/x8jfkj.jpg` with `All` search engines, and `SauceNao` as a priority engine. 30 | 31 |

32 | 33 | `smartimage 'C:\Users\\Downloads\image.jpg'` 34 | 35 | Runs a search of `C:\Users\\Downloads\image.jpg` with configuration from the config file. 36 | -------------------------------------------------------------------------------- /Assets/Legacy1_Configuration.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | **SmartImage** is highly customizable. The following table lists config options. 4 | 5 | | Setting | Description | 6 | |--|--| 7 | | Search engines | Engines to use when searching. | 8 | | Priority engines | Engines whose results are opened in the browser.| 9 | | Filtering | Hides low-quality and unsuccessful results. | 10 | | Notification | Displays a toast notification with result information once the search is completed.| 11 | | Notification image | Includes a preview image in the toast notification. | 12 | | Context menu | Integrates **SmartImage** into the context menu (right-click menu). | 13 | 14 | These settings can be configured in different ways: 15 | 16 | - The main menu 17 | - The command line 18 | 19 | 20 | # Engines 21 | 22 | ## Options 23 | 24 | Search engine names and configuration: 25 | 26 | | Real Name | Option Name | 27 | | --------------- | --------------- | 28 | | (All) | `All` | 29 | | (None) | `None` | 30 | | (Auto) | `Auto` | 31 | | (Artwork) | `Artwork` | 32 | | SauceNao | `SauceNao` | 33 | | ImgOps | `ImgOps` | 34 | | Google Images | `GoogleImages` | 35 | | TinEye | `TinEye` | 36 | | IQDB | `Iqdb` | 37 | | trace.moe | `TraceMoe` | 38 | | Karma Decay | `KarmaDecay` | 39 | | Yandex | `Yandex` | 40 | | Bing | `Bing` | 41 | | Tidder | `Tidder` | 42 | | Ascii2D | `Ascii2D` | 43 | 44 | Special options: 45 | - `All`: Use all available engines 46 | - `None`: Use no engines1 47 | - `Auto`: Use the best engine result1 48 | - `Artwork`: The engines *SauceNao*, *IQDB*, *Ascii2D* 49 | 50 | 1 This option can only be used with priority engine options. They cannot be used for the main search engine options. 51 | 52 | ## Search Engines 53 | 54 | These options are the engines used to perform searches. 55 | 56 | ## Priority Search Engines 57 | 58 | These options are the engines whose results will be opened in your browser. If `Auto` is used, the best result is opened. If `None` is used, 59 | no results will be opened in the browser. 60 | 61 | 62 | # Behavior 63 | 64 | Configuration is loaded in this order: 65 | 1. Configuration file (`SmartImage.cfg`) 66 | 2. Command line parameters (if specified) 67 | 68 | Therefore, command line parameters have precedence over the config file. For example, if the config file designates `SauceNao` as a priority engine but the command line argument `-pe Iqdb` is specified, `Iqdb` will take precedence. 69 | 70 | This can be useful when you want to run a search with certain settings but want to keep your personalized configuration intact. -------------------------------------------------------------------------------- /Assets/Legacy1_Home.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ____ _ ___ 3 | / ___| _ __ ___ __ _ _ __| |_|_ _|_ __ ___ __ _ __ _ ___ 4 | \___ \| '_ ` _ \ / _` | '__| __|| || '_ ` _ \ / _` |/ _` |/ _ \ 5 | ___) | | | | | | (_| | | | |_ | || | | | | | (_| | (_| | __/ 6 | |____/|_| |_| |_|\__,_|_| \__|___|_| |_| |_|\__,_|\__, |\___| 7 | |___/ 8 | ``` 9 | 10 | Welcome to the **SmartImage** wiki! 11 | 12 | # Installation 13 | 14 | ## Requirements 15 | 16 | The only requirements are .NET 6 and Windows. You can check if .NET 6 is installed by running 17 | one of the following commands: 18 | 19 | `dotnet --list-runtimes`:
20 | _Output:_ `Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]` 21 | 22 | `dotnet --list-sdks`:
23 | _Output:_ `6.0.100 [C:\Program Files\dotnet\sdk]` 24 | 25 | If the version major number is 6 (i.e., first number in the version), then .NET 6 is installed. 26 | 27 | *** 28 | 29 | **SmartImage** must be added to the system PATH (*`%PATH%`*) environment variable, otherwise context menu integration will not work. **SmartImage** will automatically do this for you. Otherwise, you can read about how to manually do this [here](https://superuser.com/questions/949560/how-do-i-set-system-environment-variables-in-windows-10). 30 | 31 | If the IME (system language) is a non-Romance language (e.g., Japanese or Chinese), some features may not work correctly (i.e., keyboard input, context menu integration). To resolve this, set the IME to English. 32 | 33 | # Engines 34 | 35 | Supported search engines and notes: 36 | 37 | - [SauceNao](https://saucenao.com/) 38 | - Multi-service image search 39 | - **Use case:** Finding sauce, usually artwork 40 | - [IQDB](https://iqdb.org/) 41 | - Multi-service image search 42 | - Similar to *SauceNao* 43 | - **Use case:** Finding sauce, usually artwork 44 | - [trace.moe](https://trace.moe/) 45 | - Multi-database image search 46 | - **Use case:** Identifying anime from a screenshot 47 | - [Karma Decay](http://karmadecay.com/) 48 | - Reddit image search 49 | - **Disclaimer:** Very slow 50 | - [ImgOps](http://imgops.com/) 51 | - Multi-service image search 52 | - **Use case:** Performing multiple image operations 53 | - **Restrictions:** Max upload size is 5MB 54 | - [Google Images](https://images.google.com/) 55 | - General-purpose image search 56 | - [TinEye](https://tineye.com/) 57 | - General-purpose image search 58 | - Generally better than *Google Images* 59 | - [Yandex](https://yandex.com/images/) 60 | - General-purpose image search 61 | - **Disclaimer:** Russian 62 | - [Bing](https://www.bing.com/images/) 63 | - General-purpose image search 64 | - [Tidder](http://tidder.xyz/) 65 | - Reddit image search 66 | - Generally better than *Karma Decay* 67 | - [Ascii2D](https://ascii2d.net/) 68 | - Multi-service image search 69 | - Similar to *SauceNao* and *IQDB* 70 | - **Use case:** Finding sauce, usually artwork 71 | 72 | *** 73 | 74 | 75 | # Usage 76 | 77 | SmartImage can be used in multiple ways: 78 | 79 | - Open the program normally (double click) and you can use the program in a user-friendly way. You can then drag and drop your image into the command prompt and run a search. 80 | 81 |

82 | 83 |

84 | 85 | - Right-click on an image (once the context menu integration is set up) and select the SmartImage option to immediately perform a search. 86 | 87 |

88 | 89 |

90 | 91 | - Drag and drop an image over the executable to immediately perform a search (functionally the same as right-clicking on an image and using the SmartImage option). 92 | 93 |

94 | 95 |

96 | 97 | - Use the command line. 98 | -------------------------------------------------------------------------------- /Assets/Legacy1_Interface.md: -------------------------------------------------------------------------------- 1 | # Main menu 2 | 3 | 4 | 5 |

6 | 7 | 8 | - **Run**: Runs a search 9 | - **Engines**: Configures engines 10 | - **Priority engines**: Configures priority engines 11 | - **Filter**: Toggles filtering 12 | - **Notification**: Toggles toast notification 13 | - **Notification image**: Toggles toast notification image 14 | - **Context menu**: Toggles context menu integration 15 | - **Config**: Displays current configuration 16 | - **Info**: Displays program info 17 | - **Update**: Checks and installs new versions 18 | - **Help**: Opens help 19 | 20 | 21 | 23 | 24 | 25 |

26 | 27 |

28 | 29 | # Results 30 | 31 | ## Result 32 | 33 | Once the search is complete, the UI shows extensive information about the search results. 34 | 35 | 36 | 37 | 38 |

39 | 40 | - **Result**: Most accurate and specific result URL 41 | - **Raw**: Undifferentiated result URL 42 | - **Direct**: Direct image URL 43 | - **Similarity**: Image similarity (delta) 44 | - **Description**: Image description, caption, etc. 45 | - **Site**: Result site1 46 | - **Artist**: Image artist1 47 | - **Characters**: Character(s) in the image1 48 | - **Source**: Image source1 49 | - **Resolution**: Image resolution 50 | - **Detail score**: Number of detail fields 51 | - **Other image results**: Other results 52 | 53 |

54 | 55 | 56 | 1 This metadata is usually only for anime or related image results (i.e. *SauceNao*, *IQDB*, *TraceMoe*, etc.) 57 | 58 | # Interaction 59 | 60 | ## General 61 | 62 | - Press the option character on the keyboard to open it (i.e., press **0** to open **[0]** or **A** to open **[A]**). 63 | - Press `Escape` to return to the previous interface. 64 | - Press `F1` to show filtered results. 65 | - Press `F5` to refresh the console buffer. 66 | 67 | ## Results 68 | 69 | - Press the result character to open the **Result** URL in your browser. 70 | - Hold down `Ctrl` to search for a direct image link. 71 | - Hold down `Alt` to show more info and results. Certain engines will return multiple results; those can be viewed using this option. 72 | - Hold down `Shift` to open the raw URL. 73 | - Hold down `Ctrl` and `Alt` to download the image. -------------------------------------------------------------------------------- /Assets/Legacy2_Usage.md: -------------------------------------------------------------------------------- 1 | # Input 2 | 3 | **SmartImage** can be used in a variety of ways. 4 | - Input text field in the main menu 5 | - Copy/paste text 6 | - Use the *Browse* button to open the file picker dialog 7 | - Context menu 8 | - Use the *Config* button to open the [configuration dialog](https://github.com/Decimation/SmartImage/wiki/Interface#configuration) to toggle context menu integration 9 | - Clipboard 10 | - copying an _image_ or _URI_ outside of the program will automatically populate the input field 11 | - Command line 12 | - Use the `--i` parameter to specify input 13 | 14 | # Queries 15 | 16 | The value given as [input](#Input) is referred to hereafter as _search query_ or _query_. 17 | 18 | * Search queries may be either a _file_ or _URI_. 19 | * All queries must be a recognized image type. 20 | * If query is a _URI_, it must be a direct link (i.e., the payload returned is a binary image). For example, `https://i.imgur.com/zoBIh8t.jpg` returns 21 | an image payload with `Content-Type` as `image/jpeg`. 22 | 23 | -------------------------------------------------------------------------------- /Assets/SmartImage icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Assets/SmartImage icon.psd -------------------------------------------------------------------------------- /Assets/Usage.md: -------------------------------------------------------------------------------- 1 | # Main Menu 2 | 3 | ![](https://github.com/Decimation/SmartImage/blob/v3/Examples/Main%20menu%20diagram.png?raw=true) 4 | 5 | | | | 6 | | ---- | ------------------------ | 7 | | `1` | Input field | 8 | | `2` | Upload URL | 9 | | `3` | Input image type | 10 | | `4` | Input image size | 11 | | `5` | Input image dimensions | 12 | | `6` | Preview image URL | 13 | | `7` | Preview result URL | 14 | | `8` | Preview image info | 15 | | `9` | Preview image size | 16 | | `10` | Preview image dimensions | 17 | | `11` | Functions... | 18 | | `12` | Status... | 19 | | `13` | Search status... | 20 | | `14` | Input queue | 21 | | `15` | Preview | 22 | | `16` | Results | 23 | 24 | # Results 25 | 26 | ![](https://github.com/Decimation/SmartImage/blob/v3/Examples/Results%201.png?raw=true) 27 | 28 | # Configuration 29 | 30 | ![](https://github.com/Decimation/SmartImage/blob/v3/Examples/Config.png?raw=true) 31 | 32 | # Command-Line 33 | 34 | | Parameter | Definition | Type | 35 | | --------- | ----------------------------------------------------- | ---------- | 36 | | `-i` | Input | _`value`_ | 37 | | `-auto` | Start search immediately | _`switch`_ | 38 | | `-h` | Hide GUI | _`switch`_ | 39 | | `-s` | Switch current queue input to value specified by `-i` | _`switch`_ | 40 | 41 | # Integration 42 | 43 | **SmartImage** is compatible with **ShareX** actions. 44 | 45 | ### About 46 | 47 | **Last updated:** `2024-01-24 v4.0.4` 48 | -------------------------------------------------------------------------------- /Assets/Wiki_TOC_Old.md: -------------------------------------------------------------------------------- 1 | ## [Home](https://github.com/Decimation/SmartImage/wiki) 2 | - [Engines](https://github.com/Decimation/SmartImage/wiki/Engines) 3 | - Miscellaneous 4 | - [Reviews](https://github.com/Decimation/SmartImage/wiki/Reviews) 5 | 6 | *** 7 | 8 | ## GUI 9 | - [Usage](https://github.com/Decimation/SmartImage/wiki/Usage) 10 | - [Main Menu](https://github.com/Decimation/SmartImage/wiki/Usage#main-menu) 11 | - [Results](https://github.com/Decimation/SmartImage/wiki/Usage#results) 12 | - [Configuration](https://github.com/Decimation/SmartImage/wiki/Usage#configuration) 13 | 14 | 15 | *** 16 | 17 | ### Legacy² 18 | - [Interface](https://github.com/Decimation/SmartImage/wiki/Interface-(Legacy²)) 19 | - [Main Menu](https://github.com/Decimation/SmartImage/wiki/Interface-(Legacy²)#main-menu) 20 | - [Configuration](https://github.com/Decimation/SmartImage/wiki/Interface-(Legacy²)#configuration) 21 | - [Results](https://github.com/Decimation/SmartImage/wiki/Interface-(Legacy²)#results) 22 | - [Usage](https://github.com/Decimation/SmartImage/wiki/Usage-(Legacy²)) 23 | - [Input](https://github.com/Decimation/SmartImage/wiki/Usage-(Legacy²)#Input) 24 | 25 | *** 26 | 27 | ### [Legacy](https://github.com/Decimation/SmartImage/wiki/Home-(Legacy))¹ 28 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 11.1.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Config.png -------------------------------------------------------------------------------- /Examples/Demo 1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Demo 1.gif -------------------------------------------------------------------------------- /Examples/Main menu 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Main menu 2.png -------------------------------------------------------------------------------- /Examples/Main menu diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Main menu diagram.png -------------------------------------------------------------------------------- /Examples/Main menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Main menu.png -------------------------------------------------------------------------------- /Examples/Main menu.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Main menu.psd -------------------------------------------------------------------------------- /Examples/Old/Config (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Config (2).png -------------------------------------------------------------------------------- /Examples/Old/Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Config.png -------------------------------------------------------------------------------- /Examples/Old/Context menu integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Context menu integration.png -------------------------------------------------------------------------------- /Examples/Old/Demo 1-old.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Demo 1-old.gif -------------------------------------------------------------------------------- /Examples/Old/Demo 1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Demo 1.gif -------------------------------------------------------------------------------- /Examples/Old/Demo 2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Demo 2.gif -------------------------------------------------------------------------------- /Examples/Old/Demo 3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Demo 3.gif -------------------------------------------------------------------------------- /Examples/Old/Example search results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Example search results.png -------------------------------------------------------------------------------- /Examples/Old/Main menu (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Main menu (2).png -------------------------------------------------------------------------------- /Examples/Old/Main menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Main menu.png -------------------------------------------------------------------------------- /Examples/Old/Rdx help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Rdx help.png -------------------------------------------------------------------------------- /Examples/Old/Results 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Results 1.png -------------------------------------------------------------------------------- /Examples/Old/Results 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Old/Results 2.png -------------------------------------------------------------------------------- /Examples/Rdx context menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Rdx context menu.png -------------------------------------------------------------------------------- /Examples/Rdx help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Rdx help.png -------------------------------------------------------------------------------- /Examples/Results 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/Results 1.png -------------------------------------------------------------------------------- /Examples/ShareX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/Examples/ShareX.png -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 | Logo 6 | 7 | 8 |

SmartImage

9 | 10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 |

23 | Find the source image in one click! 24 |
25 | Releases » 26 |
27 | Wiki » 28 |
29 | Issues » 30 | 31 |

32 |

33 | 34 | 35 | 36 |

37 | 38 | 39 | 40 | 41 | 42 |

SmartImage is a powerful reverse image search tool for Windows. SmartImage will open the best match found returned from various image search engines (see the supported sites) right in your web browser. This behavior can be configured to the user's preferences.

43 | 44 | 48 | 49 |

SmartImage has two forms: GUI (Windows) and Rdx (cross-platform).

50 | 51 |

52 | 53 | 54 | 55 | ## Supported Engines 🔎 56 | 57 | Supported search engines: 58 | 59 | **See the [Engines Index](https://docs.google.com/spreadsheets/d/1BdBqGzEQ7x6XKcDSE_w_KrfBzTaYB48CCPJyeSNtbyg/edit?usp=sharing) spreadsheet.** 60 | 61 | 73 | 74 | 75 | 76 | ## Getting Started (GUI) 1️⃣ 77 | 78 | **SmartImage** is designed to be intuitive and agile. There are multiple ways of running the program, and you can use 2 different forms of input: direct image links or files. 79 | 80 | See [Usage »](https://github.com/Decimation/SmartImage/wiki/(GUI)-Usage) 81 | 82 | ### 1. Input Field 83 | 84 | #### 1a. Text 📲 85 | 86 | Simply use the text input field in the shell UI. You are able to copy/paste text and use common text manipulation functions such as undo/redo. 87 | 88 | #### 1b. Drag-and-drop ↕ 89 | 90 | You are able to drag-and-drop images into the text field, in which case the file path populates the input field. 91 | 92 | ### 2. Clipboard 📋 93 | 94 | **SmartImage** automatically detects clipboard changes 95 | 96 | #### 2a. Text 🔗 97 | 98 | Copy a link or file path, and the input field will update with its value. 99 | 100 | #### 2b. Image file 🖼 101 | 102 | Copy an image file, and the input field will update with its file path. 103 | 104 | ### 3. Context menu 🖱 105 | 106 | Once the context menu integration is enabled, right-click on an image to open **SmartImage** with the selected 107 | file used as the input. 108 | 109 | ### 4. Command-line 🖥 110 | 111 | Use the documented arguments to invoke **SmartImage** from various system contexts such as scripts or 112 | applications. 113 | 114 | ## Wiki 📕 115 | 116 | **See the _[Wiki »](https://github.com/Decimation/SmartImage/wiki)_ for documentation.** -------------------------------------------------------------------------------- /SmartImage.Lib/Clients/AnilistClient.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using Kantan.Net; 4 | 5 | // ReSharper disable PossibleNullReferenceException 6 | 7 | // ReSharper disable UnusedMember.Global 8 | 9 | namespace SmartImage.Lib.Clients; 10 | 11 | public sealed class AnilistClient : IDisposable 12 | { 13 | private readonly GraphQLClient m_client; 14 | 15 | public AnilistClient() 16 | { 17 | m_client = new GraphQLClient("https://graphql.anilist.co"); 18 | } 19 | 20 | public async Task GetTitleAsync(int anilistId) 21 | { 22 | /* 23 | * https://anilist.gitbook.io/anilist-apiv2-docs/overview/graphql 24 | * https://anilist.gitbook.io/anilist-apiv2-docs/overview/graphql/getting-started 25 | * https://graphql.org/learn/queries/ 26 | */ 27 | 28 | const string GRAPH_QUERY = @"query ($id: Int) { # Define which variables will be used in the query (id) 29 | Media(id: $id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query) 30 | id 31 | title { 32 | romaji 33 | english 34 | native 35 | } 36 | } 37 | }"; 38 | 39 | var response = await m_client.ExecuteAsync(GRAPH_QUERY, new 40 | { 41 | query = GRAPH_QUERY, 42 | id = anilistId 43 | }); 44 | 45 | var value = response["data"]; 46 | var title = value?["Media"]?["title"]; 47 | return title?["english"]?.ToString() ?? title?["romaji"]?.ToString(); 48 | } 49 | 50 | #region IDisposable 51 | 52 | public void Dispose() 53 | { 54 | 55 | m_client.Dispose(); 56 | } 57 | 58 | #endregion 59 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Clients/Booru/BaseBooruClient.cs: -------------------------------------------------------------------------------- 1 |  2 | using SmartImage.Lib.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using SmartImage.Lib.Utilities.Diagnostics; 10 | 11 | namespace SmartImage.Lib.Clients.Booru; 12 | 13 | // TODO 14 | [Experimental(AppSupport.DIAG_SMRTIMG_EXP001)] 15 | public abstract class BaseBooruClient : IDisposable 16 | { 17 | 18 | public Url BaseUrl { get; } 19 | 20 | public abstract string Name { get; } 21 | 22 | protected BaseBooruClient(Url baseUrl) 23 | { 24 | BaseUrl = baseUrl; 25 | } 26 | 27 | public virtual void Dispose() { } 28 | 29 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Clients/Booru/BaseGelbooruClient.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Reflection; 3 | using Flurl.Http; 4 | using SmartImage.Lib.Utilities; 5 | using SmartImage.Lib.Utilities.Diagnostics; 6 | 7 | namespace SmartImage.Lib.Clients.Booru; 8 | 9 | // TODO 10 | 11 | [Experimental(AppSupport.DIAG_SMRTIMG_EXP001)] 12 | public abstract class BaseGelbooruClient : BaseBooruClient 13 | { 14 | 15 | public FlurlClient Client { get; } 16 | 17 | [CBN] 18 | public string Key { get; set; } 19 | 20 | [CBN] 21 | public string Id { get; set; } 22 | 23 | protected BaseGelbooruClient(Url baseUrl) : base(baseUrl) 24 | { 25 | 26 | Client = new FlurlClient() 27 | { 28 | BaseUrl = baseUrl, 29 | Settings = 30 | { 31 | JsonSerializer = { } 32 | } 33 | }; 34 | } 35 | 36 | public class PostsRequest 37 | { 38 | 39 | public int Limit { get; set; } 40 | 41 | public int Pid { get; set; } 42 | 43 | public string Tags { get; set; } 44 | 45 | public long Cid { get; set; } 46 | 47 | public int Id { get; set; } 48 | 49 | public int Json { get; set; } = 1; 50 | 51 | public PostsRequest() { } 52 | 53 | } 54 | 55 | protected int PostMax { get; set; } = 100; 56 | 57 | protected virtual bool Verify(PostsRequest r) 58 | { 59 | r.Limit = Math.Clamp(r.Limit, 1, PostMax); 60 | 61 | return true; 62 | } 63 | 64 | public virtual async Task GetPostsAsync(PostsRequest r) 65 | { 66 | if (!Verify(r)) { 67 | throw new ArgumentException(); 68 | } 69 | 70 | var properties = new List(); 71 | 72 | foreach (PropertyInfo p in r.GetType().GetProperties()) { 73 | var o = p.GetValue(r); 74 | 75 | if (o == null || o is string s && string.IsNullOrWhiteSpace(s) || o.Equals(0)) { 76 | continue; 77 | } 78 | 79 | var sss = o.ToString(); 80 | var h = p.Name.ToLower() + "=" + Url.Encode(sss, true); 81 | properties.Add(h); 82 | } 83 | 84 | var ss = string.Join('&', properties); 85 | 86 | return await Client.Request("/index.php?page=post&s=list", ss) 87 | .GetAsync(); 88 | } 89 | 90 | public override void Dispose() 91 | { 92 | Client?.Dispose(); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /SmartImage.Lib/Clients/Booru/Rule34Booru.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: Rule34Booru.cs 2 | // Date: 2024/06/18 @ 14:06:04 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | using SmartImage.Lib.Utilities; 6 | using SmartImage.Lib.Utilities.Diagnostics; 7 | 8 | namespace SmartImage.Lib.Clients.Booru; 9 | // TODO 10 | 11 | [Experimental(AppSupport.DIAG_SMRTIMG_EXP001)] 12 | public class Rule34Booru : BaseGelbooruClient 13 | { 14 | 15 | public override string Name => "Rule34"; 16 | 17 | public Rule34Booru() : base("https://rule34.xxx/") { } 18 | 19 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Clients/FlareSolverrClient.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: FlareSolverrClient.cs 2 | // Date: 2024/10/25 @ 12:10:45 3 | 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | using CliWrap; 7 | using FlareSolverrSharp; 8 | using SmartImage.Lib.Results.Data; 9 | 10 | namespace SmartImage.Lib.Clients; 11 | 12 | public sealed class FlareSolverrClient : IDisposable, ISearchConfigReceiver 13 | { 14 | 15 | [MNNW(true, nameof(Client))] 16 | public bool HasClient => Client != null; 17 | 18 | [MNNW(true, nameof(Clearance))] 19 | public bool HasClearance => Clearance != null; 20 | 21 | [MNNW(true, nameof(Clearance), nameof(Client))] 22 | public bool IsInitialized => HasClearance && HasClient; 23 | 24 | public ClearanceHandler Clearance { get; private set; } 25 | 26 | public HttpClient Client { get; private set; } 27 | 28 | public bool Configure(string api) 29 | { 30 | Dispose(); 31 | 32 | Clearance = new ClearanceHandler(api) 33 | { 34 | EnsureResponseIntegrity = false 35 | }; 36 | 37 | Client = new HttpClient(Clearance); 38 | 39 | Trace.WriteLine($"{nameof(FlareSolverrClient)}: init {api}"); 40 | 41 | return HasClient; 42 | } 43 | 44 | private FlareSolverrClient() { } 45 | 46 | static FlareSolverrClient() { } 47 | 48 | public static FlareSolverrClient Value { get; private set; } = new(); 49 | 50 | public void Dispose() 51 | { 52 | Debug.WriteLine($"Disposing {nameof(FlareSolverrClient)}"); 53 | Clearance?.Dispose(); 54 | Client?.Dispose(); 55 | Clearance = null; 56 | Client = null; 57 | } 58 | 59 | #region Implementation of ISearchConfigReceiver 60 | 61 | public ValueTask ApplyConfigAsync(SearchConfig cfg, CancellationToken ct = default) 62 | { 63 | var ok = false; 64 | 65 | if (cfg.FlareSolverr) { 66 | ok = Configure(cfg.FlareSolverrApiUrl); 67 | } 68 | 69 | return ValueTask.FromResult(ok); 70 | } 71 | 72 | #endregion 73 | 74 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Cookies/BrowserCookiesProvider.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: CookiesManager.cs 2 | 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Runtime.Caching; 6 | using Kantan.Net.Web; 7 | 8 | namespace SmartImage.Lib.Cookies; 9 | 10 | public class BrowserCookiesProvider : ICookiesProvider 11 | { 12 | 13 | private const string CH_NAME = "cookies"; 14 | 15 | public BaseCookieReader Reader { get; } 16 | 17 | public MemoryCache Cache { get; } 18 | 19 | public BrowserCookiesProvider(BaseCookieReader reader) 20 | { 21 | Reader = reader; 22 | Cache = new MemoryCache($"{nameof(BrowserCookiesProvider)}_Cache"); 23 | } 24 | 25 | public async ValueTask OpenAsync() 26 | { 27 | // Opening is idempotent 28 | await Reader.Connection.OpenAsync(); 29 | } 30 | 31 | public async ValueTask CloseAsync() 32 | { 33 | await Reader.Connection.CloseAsync(); 34 | } 35 | 36 | public async ValueTask> GetOrLoadCookiesAsync(CancellationToken ct = default) 37 | { 38 | if (!IsOpen) { 39 | await OpenAsync(); 40 | } 41 | 42 | /*if (IsClosedOrBroken) { 43 | throw new InvalidOperationException(); 44 | }*/ 45 | 46 | 47 | var itemPolicy = new CacheItemPolicy() 48 | { 49 | 50 | // AbsoluteExpiration = 51 | }; 52 | 53 | var chi = (IList) Cache.Get(CH_NAME); 54 | 55 | if (chi == null) { 56 | 57 | var cookies = await Reader.ReadCookiesAsync(); 58 | var addOk = Cache.Add(CH_NAME, cookies, itemPolicy); 59 | 60 | if (addOk) { 61 | chi = (IList) Cache.Get(CH_NAME); 62 | } 63 | 64 | } 65 | else { 66 | Trace.WriteLine($"Found {CH_NAME} in cache"); 67 | } 68 | 69 | return chi; 70 | } 71 | 72 | public bool IsOpen => Reader.Connection.State is ConnectionState.Open; 73 | 74 | public bool IsOpenOrInUse => Reader.Connection.State is < ConnectionState.Broken and >= ConnectionState.Open; 75 | 76 | public bool IsClosedOrBroken => Reader.Connection.State is ConnectionState.Broken or ConnectionState.Closed; 77 | 78 | public void Dispose() 79 | { 80 | Debug.WriteLine($"Disposing {nameof(BrowserCookiesProvider)}"); 81 | Reader.Dispose(); 82 | Cache.Dispose(); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Cookies/DefaultCookiesProvider.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: DefaultCookiesProvider.cs 2 | // Date: 2025/02/04 @ 12:02:26 3 | 4 | using Kantan.Net.Web; 5 | 6 | namespace SmartImage.Lib.Cookies; 7 | 8 | public class DefaultCookiesProvider : ICookiesProvider 9 | { 10 | 11 | public List Cookies { get; } 12 | 13 | public DefaultCookiesProvider() 14 | { 15 | Cookies = new List(); 16 | } 17 | 18 | public ValueTask> GetOrLoadCookiesAsync(CancellationToken ct = default) 19 | { 20 | return ValueTask.FromResult>(Cookies); 21 | } 22 | 23 | public void Dispose() 24 | { 25 | Cookies.Clear(); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Cookies/ICookiesEngine.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: ICookiesReceiver.cs 2 | // Date: 2024/06/06 @ 17:06:56 3 | 4 | using Flurl.Http; 5 | 6 | namespace SmartImage.Lib.Cookies; 7 | 8 | public interface ICookiesEngine 9 | { 10 | public CookieJar Jar { get; } 11 | 12 | public ICookiesProvider Provider { get; set; } 13 | 14 | public ValueTask ApplyCookiesAsync(CancellationToken token = default); 15 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Cookies/ICookiesProvider.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: ICookieProvider.cs 2 | // Date: 2024/10/15 @ 12:10:00 3 | 4 | using Kantan.Net.Web; 5 | using SmartImage.Lib.Utilities.Integration; 6 | 7 | namespace SmartImage.Lib.Cookies; 8 | 9 | public interface ICookiesProvider : IDisposable 10 | { 11 | 12 | public ValueTask> GetOrLoadCookiesAsync(CancellationToken ct = default); 13 | 14 | 15 | public static ICookiesProvider GetProvider() 16 | { 17 | if (BaseOSIntegration.Integration.IsFirefoxInstalled) { 18 | var cookieFile = FirefoxCookieReader.FindCookieFile(); 19 | if (cookieFile != null) { 20 | return new BrowserCookiesProvider(new FirefoxCookieReader(cookieFile.FullName)); 21 | 22 | } 23 | } 24 | 25 | return new DefaultCookiesProvider(); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/IEndpointEngine.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: IEndpointEngine.cs 2 | // Date: 2025/03/27 @ 12:03:18 3 | 4 | using Flurl.Http; 5 | 6 | namespace SmartImage.Lib.Engines; 7 | 8 | #pragma warning disable CS0649 9 | 10 | public interface IEndpointEngine 11 | { 12 | 13 | public Url EndpointUrl { get; } 14 | 15 | // public IFlurlClient Client { get; } 16 | 17 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/ArchiveMoeEngine.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: ArchiveMoeEngine.cs 2 | // Date: 2024/06/06 @ 14:06:00 3 | 4 | using System.Security.Cryptography; 5 | using System.Text.RegularExpressions; 6 | using AngleSharp.Dom; 7 | using AngleSharp.Html.Dom; 8 | using Novus.Streams; 9 | using SmartImage.Lib.Results; 10 | using SmartImage.Lib.Results.Data; 11 | 12 | namespace SmartImage.Lib.Engines.Impl.Search; 13 | 14 | public class ArchiveMoeEngine : WebSearchEngine 15 | { 16 | 17 | public override SearchEngineOptions EngineOption => SearchEngineOptions.ArchiveMoe; 18 | 19 | protected string Base64Hash { get; set; } 20 | 21 | protected override string NodesSelector => "//article[contains(@class,'post')]"; 22 | 23 | public ArchiveMoeEngine() : this("https://archived.moe/_/search/") { } 24 | 25 | protected ArchiveMoeEngine(string baseUrl) : base(baseUrl) { } 26 | 27 | protected override Url GetRawUrl(SearchQuery query) 28 | { 29 | Base64Hash = GetHash(query); 30 | 31 | var r = Url.Combine(BaseUrl, "image", Base64Hash); 32 | return r; 33 | 34 | // return (BaseUrl.AppendPathSegments("image").AppendPathSegment(Base64Hash)); 35 | } 36 | 37 | protected override ValueTask ParseResultItem(INode n, SearchResult r) 38 | { 39 | // ReSharper disable PossibleNullReferenceException 40 | 41 | var p = ChanPost.Parse(n); 42 | return ValueTask.FromResult(p.ToItem(r)); 43 | 44 | // ReSharper restore PossibleNullReferenceException 45 | } 46 | 47 | protected static string GetHash(SearchQuery q) 48 | { 49 | //var digestBase64URL = digestBase64.replace('==', '').replace(/\//g, '_').replace(/\+/g, '-'); 50 | var data = MD5.HashData(q.Source.Stream); 51 | var b64 = Convert.ToBase64String(data).Replace("==", ""); 52 | b64 = Regex.Replace(b64, @"\//", "_"); 53 | b64 = Regex.Replace(b64, @"\+", "-"); 54 | 55 | q.Source.Stream.TrySeek(); 56 | 57 | return b64; 58 | } 59 | 60 | public override void Dispose() 61 | { 62 | GC.SuppressFinalize(this); 63 | } 64 | 65 | } 66 | 67 | public record ChanPost : ISearchResultItemConverter 68 | { 69 | 70 | public string Author; 71 | public string Board; 72 | public Url File; 73 | public string Filename; 74 | public int Height; 75 | 76 | public long Id; 77 | public string Size; 78 | public string Text; 79 | public string Time1; 80 | public DateTime Time2; 81 | public string Title; 82 | public string Tripcode; 83 | public int Width; 84 | 85 | public static ChanPost Parse(INode n) 86 | { 87 | var e = n as HtmlElement; 88 | 89 | var pff = e.QuerySelector(".post_file_filename"); 90 | var pfm = e.QuerySelector(".post_file_metadata").TextContent.Split(", "); 91 | var pd = e.QuerySelector(".post_data"); 92 | var pt = pd.QuerySelector(".post_title").TextContent; 93 | var pa = pd.QuerySelector(".post_author").TextContent; 94 | var ptc = pd.QuerySelector(".post_tripcode").TextContent; 95 | var tw = pd.QuerySelector(".time_wrap").Children[0]; 96 | var time2Ok = DateTime.TryParse(tw.GetAttribute("datetime"), out var time2); 97 | var time = tw.TextContent; 98 | var text = e.QuerySelector(".text").TextContent; 99 | 100 | var wh = pfm[1].Split('x'); 101 | 102 | var p = new ChanPost 103 | { 104 | Id = Int64.Parse(e.GetAttribute("id")), 105 | Board = e.GetAttribute("data-board"), 106 | Filename = pff.TextContent, 107 | File = pff.GetAttribute("href"), 108 | Width = Int32.Parse(wh[0]), 109 | Height = Int32.Parse(wh[1]), 110 | Size = pfm[0], 111 | Title = pt, 112 | Author = pa, 113 | Tripcode = ptc, 114 | Time1 = time, 115 | Time2 = time2, 116 | Text = text 117 | }; 118 | 119 | return p; 120 | } 121 | 122 | public SearchResultItem ToItem(SearchResult sr) 123 | { 124 | 125 | var sri = new SearchResultItem(sr) 126 | { 127 | Url = File, 128 | Width = Width, 129 | Height = Height, 130 | Artist = Author, 131 | Description = Title, 132 | Time = Time2, 133 | Site = File.Host, 134 | Metadata = this 135 | }; 136 | 137 | return sri; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/FluffleEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading.Tasks; 9 | using Flurl.Http; 10 | using JetBrains.Annotations; 11 | using Novus.Streams; 12 | using SixLabors.ImageSharp; 13 | using SixLabors.ImageSharp.Formats; 14 | using SixLabors.ImageSharp.Formats.Png; 15 | using SixLabors.ImageSharp.Processing; 16 | using SmartImage.Lib.Results; 17 | using SmartImage.Lib.Results.Data; 18 | using SmartImage.Lib.Utilities.Diagnostics; 19 | 20 | namespace SmartImage.Lib.Engines.Impl.Search; 21 | 22 | public class FluffleEngine : BaseSearchEngine, IEndpointEngine, IDisposable 23 | { 24 | 25 | public const string URL_ENDPOINT = "https://api.fluffle.xyz/v1/"; 26 | public const string URL_BASE = "https://fluffle.xyz/"; 27 | 28 | public FluffleEngine() : base(URL_BASE) 29 | { 30 | MaxSize = 4_194_304; // MiB 31 | 32 | // Timeout = TimeSpan.FromSeconds(10); 33 | } 34 | 35 | 36 | public Url EndpointUrl => URL_ENDPOINT; 37 | 38 | 39 | public override async Task GetResultAsync(SearchQuery query, CancellationToken token = default) 40 | { 41 | 42 | var sr = await base.GetResultAsync(query, token); 43 | 44 | IFlurlResponse response = null; 45 | 46 | if (sr.Status == SearchResultStatus.IllegalInput) { 47 | // return sr; 48 | goto ret; 49 | } 50 | 51 | var hdr = $"{R1.Name}/{AppSupport.Version} (by {R1.Author} on GitHub)"; 52 | 53 | 54 | response = await Client.Request(EndpointUrl, "search") 55 | .WithHeaders(new 56 | { 57 | User_Agent = hdr 58 | }) 59 | .WithTimeout(Timeout) 60 | .OnError(e => { e.ExceptionHandled = true; }) 61 | .PostMultipartAsync(c => 62 | { 63 | // var tmp = query.WriteImageToFile(); 64 | query.Source.Stream.TrySeek(); 65 | 66 | c.AddFile("file", query.Source.Stream, "file"); 67 | query.Source.Stream.TrySeek(); 68 | 69 | c.AddString("includeNsfw", true.ToString()); 70 | c.AddString("limit", 32.ToString()); 71 | 72 | // c.AddString("platforms", null) 73 | // c.AddString("createLink", false) 74 | }, cancellationToken: token); 75 | 76 | if (response is { ResponseMessage: { IsSuccessStatusCode: false } }) { 77 | var er = await response.GetJsonAsync(); 78 | 79 | sr.ErrorMessage = $"{er.Message}: {er.Code}"; 80 | sr.Status = SearchResultStatus.UnknownError; 81 | 82 | // return sr; 83 | goto ret; 84 | } 85 | 86 | if (response == null) { 87 | sr.Status = SearchResultStatus.UnknownError; 88 | goto ret; 89 | } 90 | 91 | var fr = await response.GetJsonAsync(); 92 | 93 | foreach (FluffleResult result in fr.Results) { 94 | var item = result.ToItem(sr); 95 | sr.Results.Add(item); 96 | } 97 | 98 | sr.Status = SearchResultStatus.Success; 99 | ret: 100 | sr.Update(); 101 | response?.Dispose(); 102 | return sr; 103 | } 104 | 105 | protected override Url GetRawUrl(SearchQuery query) 106 | { 107 | return base.GetRawUrl(query); 108 | } 109 | 110 | public override SearchEngineOptions EngineOption => SearchEngineOptions.Fluffle; 111 | 112 | public override void Dispose() { } 113 | 114 | } 115 | 116 | #region API Objects 117 | 118 | public class FluffleErrorCode 119 | { 120 | 121 | [JsonPropertyName("code")] 122 | public string Code { get; set; } 123 | 124 | [JsonPropertyName("message")] 125 | public string Message { get; set; } 126 | 127 | [JsonPropertyName("traceId")] 128 | public string TraceId { get; set; } 129 | 130 | } 131 | 132 | public class FluffleResultCredit 133 | { 134 | 135 | [JsonPropertyName("id")] 136 | public int Id { get; set; } 137 | 138 | [JsonPropertyName("name")] 139 | public string Name { get; set; } 140 | 141 | } 142 | 143 | public class FluffleResult : ISearchResultItemConvertable 144 | { 145 | 146 | [JsonPropertyName("id")] 147 | public int Id { get; set; } 148 | 149 | [JsonPropertyName("score")] 150 | public double Score { get; set; } 151 | 152 | [JsonPropertyName("match")] 153 | public string Match { get; set; } 154 | 155 | [JsonPropertyName("platform")] 156 | public string Platform { get; set; } 157 | 158 | [JsonPropertyName("location")] 159 | public string Location { get; set; } 160 | 161 | [JsonPropertyName("isSfw")] 162 | public bool IsSfw { get; set; } 163 | 164 | [JsonPropertyName("thumbnail")] 165 | public FluffleResultThumbnail Thumbnail { get; set; } 166 | 167 | [JsonPropertyName("credits")] 168 | public List Credits { get; set; } 169 | 170 | public SearchResultItem ToItem(SearchResult sr) 171 | { 172 | 173 | var sri = new SearchResultItem(sr) 174 | { 175 | Artist = Credits.FirstOrDefault()?.Name, 176 | Url = Location, 177 | Similarity = Math.Round(Score * 100.0d, 2), 178 | Metadata = this, 179 | Thumbnail = Thumbnail?.Location, 180 | Site = Platform 181 | }; 182 | return sri; 183 | } 184 | 185 | } 186 | 187 | public class FluffleResponse 188 | { 189 | 190 | [JsonPropertyName("id")] 191 | public string Id { get; set; } 192 | 193 | [JsonPropertyName("stats")] 194 | public FluffleResultStats Stats { get; set; } 195 | 196 | [JsonPropertyName("results")] 197 | public List Results { get; set; } 198 | 199 | } 200 | 201 | public class FluffleResultStats 202 | { 203 | 204 | [JsonPropertyName("count")] 205 | public int Count { get; set; } 206 | 207 | [JsonPropertyName("elapsedMilliseconds")] 208 | public int ElapsedMilliseconds { get; set; } 209 | 210 | } 211 | 212 | public class FluffleResultThumbnail 213 | { 214 | 215 | [JsonPropertyName("width")] 216 | public int Width { get; set; } 217 | 218 | [JsonPropertyName("centerX")] 219 | public int CenterX { get; set; } 220 | 221 | [JsonPropertyName("height")] 222 | public int Height { get; set; } 223 | 224 | [JsonPropertyName("centerY")] 225 | public int CenterY { get; set; } 226 | 227 | [JsonPropertyName("location")] 228 | public string Location { get; set; } 229 | 230 | } 231 | 232 | #endregion -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/Iqdb3DEngine.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.Lib IqdbEngine.cs 2 | // 2023-01-13 @ 11:21 PM 3 | 4 | // ReSharper disable UnusedMember.Global 5 | 6 | #region 7 | 8 | using System.Diagnostics; 9 | using System.Net; 10 | using AngleSharp.Dom; 11 | using AngleSharp.Html.Dom; 12 | using AngleSharp.Html.Parser; 13 | using AngleSharp.XPath; 14 | using Flurl.Http; 15 | using Kantan.Net.Utilities; 16 | using Kantan.Text; 17 | using SmartImage.Lib.Results; 18 | using SmartImage.Lib.Utilities; 19 | 20 | #endregion 21 | 22 | // ReSharper disable StringLiteralTypo 23 | 24 | namespace SmartImage.Lib.Engines.Impl.Search; 25 | 26 | #nullable disable 27 | 28 | public sealed class Iqdb3DEngine : IqdbEngine 29 | { 30 | 31 | private const string URL_BASE = "https://3d.iqdb.org/"; 32 | private const string URL_QUERY = "https://3d.iqdb.org/?url="; 33 | 34 | public override SearchEngineOptions EngineOption => SearchEngineOptions.Iqdb3D; 35 | 36 | public Iqdb3DEngine() : base(URL_QUERY) { } 37 | 38 | #region Overrides of IqdbEngine 39 | 40 | public override Url EndpointUrl => URL_BASE; 41 | 42 | #endregion 43 | 44 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/Other/BingEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using AngleSharp.Dom; 4 | using AngleSharp.Html.Parser; 5 | using Flurl; 6 | using Flurl.Http; 7 | using Kantan.Net.Utilities; 8 | using Kantan.Text; 9 | using SmartImage.Lib.Results; 10 | 11 | namespace SmartImage.Lib.Engines.Impl.Search.Other; 12 | 13 | public sealed class BingEngine : BaseSearchEngine 14 | { 15 | 16 | public BingEngine() : base("https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=") 17 | { } 18 | 19 | public override SearchEngineOptions EngineOption => SearchEngineOptions.Bing; 20 | 21 | public override void Dispose() { } 22 | 23 | // Parsing does not seem feasible ATM 24 | 25 | public async Task SearchAltQueryAsync(string query) 26 | { 27 | var sr = new SearchResult(this) 28 | { 29 | RawUrl = GetAltQueryUrl(query), 30 | }; 31 | 32 | var req = await sr.RawUrl.WithHeaders(new 33 | { 34 | User_Agent = HttpUtilities.UserAgent 35 | }).GetAsync(); 36 | 37 | var parser = new HtmlParser(); 38 | var s = await req.GetStringAsync(); 39 | var doc = await parser.ParseDocumentAsync(s); 40 | 41 | var elem = doc.QuerySelectorAll(".iuscp"); 42 | 43 | foreach (IElement e in elem) { 44 | var imgpt = e.FirstChild; 45 | 46 | if (imgpt is IElement { ClassName: "tit" }) { 47 | continue; 48 | } 49 | 50 | var iusc = imgpt.FirstChild; 51 | var attr = iusc.TryGetAttribute("m"); 52 | var j = JsonValue.Parse(attr); 53 | 54 | var infopt = e.ChildNodes[1]; 55 | 56 | sr.Results.Add(new SearchResultItem(sr) 57 | { 58 | Url = j["murl"].ToString().CleanString(), 59 | Description = infopt.TextContent 60 | }); 61 | } 62 | 63 | return sr; 64 | } 65 | 66 | private const string ALT_QUERY_URL = "https://www.bing.com/images/async"; 67 | 68 | private static Url GetAltQueryUrl(string query, int cnt = 35) 69 | { 70 | var url = ALT_QUERY_URL.SetQueryParams(new 71 | { 72 | q = query, 73 | first = 0, 74 | count = cnt, 75 | // qft = @"""" 76 | }); 77 | return url; 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/Other/GoogleImagesEngine.cs: -------------------------------------------------------------------------------- 1 | namespace SmartImage.Lib.Engines.Impl.Search.Other; 2 | 3 | public sealed class GoogleImagesEngine : BaseSearchEngine 4 | { 5 | 6 | public GoogleImagesEngine() : base("https://lens.google.com/uploadbyurl?url=") { } 7 | 8 | public override string Name => "Google Images"; 9 | 10 | public override SearchEngineOptions EngineOption => SearchEngineOptions.GoogleImages; 11 | 12 | #region Overrides of BaseSearchEngine 13 | 14 | public override void Dispose() { } 15 | 16 | #endregion 17 | 18 | // https://html-agility-pack.net/knowledge-base/2113924/how-can-i-use-html-agility-pack-to-retrieve-all-the-images-from-a-website- 19 | 20 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/Other/ImgOpsEngine.cs: -------------------------------------------------------------------------------- 1 | namespace SmartImage.Lib.Engines.Impl.Search.Other; 2 | 3 | public sealed class ImgOpsEngine : BaseSearchEngine 4 | { 5 | 6 | public ImgOpsEngine() : base("https://imgops.com/") { } 7 | 8 | public override SearchEngineOptions EngineOption => SearchEngineOptions.ImgOps; 9 | 10 | public override void Dispose() { } 11 | 12 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/Other/KarmaDecayEngine.cs: -------------------------------------------------------------------------------- 1 |  2 | //todo 3 | namespace SmartImage.Lib.Engines.Impl.Search.Other; 4 | 5 | public sealed class KarmaDecayEngine : BaseSearchEngine 6 | { 7 | 8 | public KarmaDecayEngine() : base("http://karmadecay.com/search/?q=") 9 | { } 10 | 11 | public override SearchEngineOptions EngineOption => SearchEngineOptions.KarmaDecay; 12 | 13 | public override void Dispose() { } 14 | 15 | /*protected override async Task> GetNodesAsync(IDocument doc) 16 | { 17 | var results = doc.QuerySelectorAll(NodesSelector).Cast().ToList(); 18 | 19 | return await Task.FromResult(results); 20 | }*/ 21 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Search/YandexEngine.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: YandexEngine.cs 2 | // Date: 2024/06/06 @ 14:06:00 3 | 4 | using System.Diagnostics; 5 | using System.Text.Json; 6 | using System.Text.Json.Nodes; 7 | using System.Text.Json.Serialization; 8 | using AngleSharp.Dom; 9 | using AngleSharp.Html.Dom; 10 | using AngleSharp.Html.Parser; 11 | using AngleSharp.XPath; 12 | using Flurl; 13 | using Flurl.Http; 14 | using Kantan.Net.Utilities; 15 | using Kantan.Text; 16 | using Microsoft.Extensions.Logging; 17 | using SmartImage.Lib.Images.Uni; 18 | using SmartImage.Lib.Results; 19 | using SmartImage.Lib.Results.Data; 20 | 21 | // ReSharper disable SuggestVarOrType_SimpleTypes 22 | 23 | #pragma warning disable 8602 24 | 25 | namespace SmartImage.Lib.Engines.Impl.Search; 26 | 27 | public sealed class YandexEngine : BaseSearchEngine 28 | { 29 | 30 | public const string URL_YANDEX = "https://yandex.com/"; 31 | 32 | public override SearchEngineOptions EngineOption => SearchEngineOptions.Yandex; 33 | 34 | protected override string[] ErrorBodyMessages 35 | => 36 | [ 37 | "Please confirm that you and not a robot are sending requests", 38 | "Изображение не загрузилось, попробуйте загрузить другое." 39 | 40 | // "No matching images found" 41 | ]; 42 | 43 | public YandexEngine() : base("https://yandex.com/images/search?rpt=imageview&url=") 44 | { 45 | Timeout = TimeSpan.FromSeconds(30); 46 | } 47 | 48 | private static (int? w, int? h) ParseResolution(string resText) 49 | { 50 | string[] resFull = resText.Split(Strings.Constants.MUL_SIGN); 51 | 52 | int? w = null, h = null; 53 | 54 | if (resFull.Length == 1 && resFull[0] == resText) { 55 | const string TIMES_DELIM = "×"; 56 | 57 | if (resText.Contains(TIMES_DELIM)) { 58 | resFull = resText.Split(TIMES_DELIM); 59 | } 60 | } 61 | 62 | if (resFull.Length == 2) { 63 | w = Int32.Parse(resFull[0]); 64 | h = Int32.Parse(resFull[1]); 65 | } 66 | 67 | return (w, h); 68 | } 69 | 70 | #region Overrides of BaseSearchEngine 71 | 72 | protected override Url GetRawUrl(SearchQuery query) 73 | { 74 | var url = BaseUrl.Clone(); 75 | url.QueryParams.AddOrReplace("url", query.Upload); 76 | url.QueryParams.AddOrReplace("cbir_page", "sites"); 77 | return url; 78 | } 79 | 80 | #endregion 81 | 82 | 83 | public override async Task GetResultAsync(SearchQuery query, CancellationToken token = default) 84 | { 85 | // var sr = await base.GetResultAsync(query, token); 86 | 87 | var url = GetRawUrl(query); 88 | 89 | var sr = new SearchResult(this) 90 | { 91 | RawUrl = url 92 | }; 93 | 94 | lock (sr.Results) { 95 | sr.Results.Add(sr.RawResultItem); 96 | } 97 | 98 | IDocument doc = null; 99 | 100 | IFlurlResponse res = null; 101 | 102 | Stream str = null; 103 | 104 | try { 105 | res = await Client.Request(sr.RawUrl) 106 | .WithTimeout(Timeout) 107 | .GetAsync(cancellationToken: token); 108 | 109 | str = await res.GetStreamAsync(); 110 | 111 | var parser = new HtmlParser(); 112 | doc = await parser.ParseDocumentAsync(str); 113 | 114 | var imagesAppNode = doc.Body.SelectSingleNode(Serialization.S_Yandex_Json); 115 | var json = imagesAppNode.TryGetAttribute("data-state"); 116 | 117 | var jsonNode = JsonNode.Parse(json); 118 | var sites = jsonNode["initialState"]["cbirSites"]["sites"]; 119 | var sitesObj = sites.Deserialize(); 120 | var sri = sitesObj.AsParallel().Select(e => e.ToItem(sr)); 121 | sr.Results.AddRange(sri); 122 | 123 | 124 | } 125 | catch (Exception e) { 126 | // Console.WriteLine(e); 127 | // throw; 128 | doc = null; 129 | Logger.LogError(e, "{Name} error", Name); 130 | 131 | sr.Status = SearchResultStatus.UnknownError; 132 | } 133 | finally { } 134 | 135 | 136 | sr.Status = SearchResultStatus.Success; 137 | ret: 138 | sr.Update(); 139 | res?.Dispose(); 140 | str?.Dispose(); 141 | doc?.Dispose(); 142 | return sr; 143 | } 144 | 145 | 146 | public override void Dispose() { } 147 | 148 | } 149 | 150 | public record YandexImage 151 | { 152 | 153 | [JsonPropertyName("url")] 154 | public string Url { get; set; } 155 | 156 | [JsonPropertyName("height")] 157 | public int Height { get; set; } 158 | 159 | [JsonPropertyName("width")] 160 | public int Width { get; set; } 161 | 162 | } 163 | 164 | public record YandexSite : ISearchResultItemConvertable 165 | { 166 | 167 | [JsonPropertyName("title")] 168 | public string Title { get; set; } 169 | 170 | [JsonPropertyName("description")] 171 | public string Description { get; set; } 172 | 173 | [JsonPropertyName("url")] 174 | public string Url { get; set; } 175 | 176 | [JsonPropertyName("domain")] 177 | public string Domain { get; set; } 178 | 179 | [JsonPropertyName("thumb")] 180 | public YandexImage Thumb { get; set; } 181 | 182 | [JsonPropertyName("originalImage")] 183 | public YandexImage OriginalImage { get; set; } 184 | 185 | public SearchResultItem ToItem(SearchResult sr) 186 | { 187 | return new SearchResultItem(sr) 188 | { 189 | Title = Title, 190 | Description = Description, 191 | Url = OriginalImage.Url, 192 | Site = Domain, 193 | Thumbnail = Thumb.Url.StartsWith("//") ? "https:" + Thumb.Url : Thumb.Url 194 | }; 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Upload/CatboxEngine.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Flurl.Http; 3 | using Flurl.Http.Content; 4 | using Kantan.Net.Utilities; 5 | using SmartImage.Lib.Results; 6 | 7 | namespace SmartImage.Lib.Engines.Impl.Upload; 8 | 9 | public abstract class BaseCatboxEngine : BaseUploadEngine 10 | { 11 | 12 | public override UploadEngineOptions UploadOption => UploadEngineOptions.Catbox; 13 | 14 | public override async Task UploadFileAsync(string file, CancellationToken ct = default) 15 | { 16 | Verify(file); 17 | 18 | var response = await Client.Request(EndpointUrl) 19 | .WithSettings(r => { r.Timeout = Timeout; }) 20 | .WithHeaders(new 21 | { 22 | User_Agent = HttpUtilities.UserAgent 23 | }) 24 | .PostMultipartAsync(mp => 25 | { 26 | mp.AddFile("fileToUpload", file) 27 | .AddString("reqtype", "fileupload") 28 | .AddString("time", "1h") 29 | .AddString("userhash", string.Empty); 30 | }, cancellationToken: ct, completionOption: HttpCompletionOption.ResponseHeadersRead); 31 | 32 | return await ProcessResultAsync(response, ct).ConfigureAwait(false); 33 | } 34 | 35 | /*public async Task UploadFileAsync(Stream file, CancellationToken ct = default) 36 | { 37 | 38 | var response = await Client.Request(EndpointUrl) 39 | .WithSettings(r => { r.Timeout = Timeout; }) 40 | .WithHeaders(new 41 | { 42 | User_Agent = HttpUtilities.UserAgent 43 | }) 44 | .PostMultipartAsync(mp => 45 | { 46 | mp.AddFile("fileToUpload", file, Path.GetTempFileName()) 47 | .AddString("reqtype", "fileupload") 48 | .AddString("time", "1h") 49 | .AddString("userhash", string.Empty); 50 | }, cancellationToken: ct, completionOption: HttpCompletionOption.ResponseHeadersRead); 51 | 52 | return await ProcessResultAsync(response, ct).ConfigureAwait(false); 53 | }*/ 54 | 55 | protected BaseCatboxEngine(string s) : base(s) { } 56 | 57 | } 58 | 59 | public sealed class CatboxEngine : BaseCatboxEngine 60 | { 61 | 62 | public override long? MaxSize => 200_000_000L; 63 | 64 | public CatboxEngine() : base("https://catbox.moe/user/api.php") { } 65 | 66 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Upload/LitterboxEngine.cs: -------------------------------------------------------------------------------- 1 | using Flurl.Http; 2 | 3 | // ReSharper disable StringLiteralTypo 4 | 5 | // ReSharper disable UnusedMember.Global 6 | 7 | namespace SmartImage.Lib.Engines.Impl.Upload; 8 | 9 | public sealed class LitterboxEngine : BaseCatboxEngine 10 | { 11 | 12 | public override UploadEngineOptions UploadOption => UploadEngineOptions.Litterbox; 13 | 14 | 15 | public override long? MaxSize => 1_000_000_000L; 16 | 17 | public LitterboxEngine() : base("https://litterbox.catbox.moe/resources/internals/api.php") { } 18 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Upload/PomfEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Flurl.Http; 8 | using Kantan.Net.Utilities; 9 | using SmartImage.Lib.Results; 10 | 11 | namespace SmartImage.Lib.Engines.Impl.Upload; 12 | 13 | public sealed class PomfEngine : BaseUploadEngine 14 | { 15 | 16 | public override UploadEngineOptions UploadOption => UploadEngineOptions.Pomf; 17 | 18 | public PomfEngine() : base("https://pomf.lain.la/upload.php") { } 19 | 20 | public override long? MaxSize => 1_000_000_000; 21 | 22 | 23 | public override async Task UploadFileAsync(string file, CancellationToken ct = default) 24 | { 25 | Verify(file); 26 | 27 | var response = await Client.Request(EndpointUrl) 28 | .WithSettings(r => 29 | { 30 | r.Timeout = Timeout; 31 | }).OnError(r => 32 | { 33 | r.ExceptionHandled = true; 34 | Trace.WriteLine($"{r.Exception.Message}: {file} {Name}"); 35 | }) 36 | .PostMultipartAsync(mp => 37 | { 38 | mp.AddFile("files[]", file); 39 | }, cancellationToken: ct); 40 | 41 | if (response == null) { 42 | Debugger.Break(); 43 | return new UploadResult() 44 | { 45 | IsValid = false 46 | }; 47 | } 48 | 49 | var pr = await response.GetJsonAsync(); 50 | 51 | var bur = new UploadResult() 52 | { 53 | Value = pr, 54 | Size = pr.Files[0].Size, 55 | Url = pr.Files[0].Url, 56 | IsValid = pr.Success, 57 | Response = response 58 | }; 59 | 60 | return bur; 61 | } 62 | 63 | } 64 | 65 | public sealed class PomfResult 66 | { 67 | 68 | public bool Success { get; set; } 69 | 70 | public PomfFileResult[] Files { get; set; } 71 | 72 | } 73 | 74 | public sealed class PomfFileResult 75 | { 76 | 77 | public string Hash { get; set; } 78 | 79 | public string Name { get; set; } 80 | 81 | public string Url { get; set; } 82 | 83 | public long Size { get; set; } 84 | 85 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/Impl/Upload/UploadEngineOptions.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: UploadEngineOptions.cs 2 | // Date: 2024/12/06 @ 03:12:53 3 | 4 | namespace SmartImage.Lib.Engines.Impl.Upload; 5 | 6 | public enum UploadEngineOptions 7 | { 8 | 9 | None = 0, 10 | 11 | /// 12 | /// 13 | /// 14 | Catbox, 15 | 16 | /// 17 | /// 18 | /// 19 | Litterbox, 20 | 21 | /// 22 | /// 23 | /// 24 | Pomf, 25 | 26 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/SearchEngineOptions.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedMember.Global 2 | 3 | using SmartImage.Lib.Engines.Impl.Search; 4 | using SmartImage.Lib.Engines.Impl.Search.Other; 5 | 6 | namespace SmartImage.Lib.Engines; 7 | 8 | /// 9 | /// Search engine options 10 | /// 11 | [Flags] 12 | public enum SearchEngineOptions 13 | { 14 | 15 | /// 16 | /// No engines 17 | /// 18 | None = 0, 19 | 20 | /// 21 | /// Automatic (use best result) 22 | /// 23 | Auto = 1, 24 | 25 | /// 26 | /// 27 | /// 28 | SauceNao = 1 << 1, 29 | 30 | /// 31 | /// 32 | /// 33 | ImgOps = 1 << 2, 34 | 35 | /// 36 | /// 37 | /// 38 | GoogleImages = 1 << 3, 39 | 40 | /// 41 | /// 42 | /// 43 | TinEye = 1 << 4, 44 | 45 | /// 46 | /// 47 | /// 48 | Iqdb = 1 << 5, 49 | 50 | /// 51 | /// 52 | /// 53 | TraceMoe = 1 << 6, 54 | 55 | /// 56 | /// 57 | /// 58 | KarmaDecay = 1 << 7, 59 | 60 | /// 61 | /// 62 | /// 63 | Yandex = 1 << 8, 64 | 65 | /// 66 | /// 67 | /// 68 | Bing = 1 << 9, 69 | 70 | /// 71 | /// 72 | /// 73 | Ascii2D = 1 << 10, 74 | 75 | /// 76 | /// 77 | /// 78 | RepostSleuth = 1 << 11, 79 | 80 | /// 81 | /// 82 | /// 83 | EHentai = 1 << 12, 84 | 85 | /// 86 | /// 87 | /// 88 | ArchiveMoe = 1 << 13, 89 | 90 | /// 91 | /// 92 | /// 93 | Iqdb3D = 1 << 14, 94 | 95 | /// 96 | /// 97 | /// 98 | Fluffle = 1 << 15, 99 | 100 | /// 101 | /// 102 | /// 103 | GoogleLens = 1 << 16, 104 | 105 | 106 | #region 107 | 108 | /// 109 | /// All engines 110 | /// 111 | All = SauceNao | ImgOps | GoogleImages | TinEye | Iqdb | TraceMoe | KarmaDecay | Yandex | Bing | 112 | Ascii2D | RepostSleuth | EHentai | ArchiveMoe | Iqdb3D | Fluffle | GoogleLens, 113 | 114 | Artwork = SauceNao | Iqdb | Ascii2D | EHentai, 115 | 116 | Advanced = All & ~ (Bing | GoogleImages | ImgOps | KarmaDecay) 117 | 118 | #endregion 119 | 120 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Engines/WebSearchEngine.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: WebSearchEngine.cs 2 | // Date: 2024/06/06 @ 14:06:00 3 | 4 | using System.Diagnostics; 5 | using AngleSharp.Dom; 6 | using AngleSharp.Html.Parser; 7 | using AngleSharp.XPath; 8 | using Flurl.Http; 9 | using Kantan.Diagnostics; 10 | using Kantan.Net.Utilities; 11 | using Microsoft.Extensions.Logging; 12 | using SmartImage.Lib.Results; 13 | using SmartImage.Lib.Results.Data; 14 | 15 | namespace SmartImage.Lib.Engines; 16 | 17 | abstract class ParsedSearchEngine : BaseSearchEngine 18 | { 19 | protected ParsedSearchEngine([NN] Url baseUrl) : base(baseUrl) { } 20 | 21 | protected abstract ValueTask> GetRawItems(); 22 | 23 | } 24 | 25 | public abstract class WebSearchEngine : BaseSearchEngine 26 | { 27 | 28 | protected abstract string NodesSelector { get; } 29 | 30 | protected WebSearchEngine([NN] Url baseUrl) : base(baseUrl) { } 31 | 32 | 33 | public override async Task GetResultAsync(SearchQuery query, CancellationToken token = default) 34 | { 35 | var res = await base.GetResultAsync(query, token); 36 | 37 | IDocument doc = null; 38 | 39 | if (res.Status == SearchResultStatus.IllegalInput) { 40 | goto ret; 41 | } 42 | 43 | try { 44 | doc = await GetDocumentAsync(res, query: query, token: token); 45 | } 46 | catch (Exception e) { 47 | Logger.LogError(e, "{Name} error", e); 48 | 49 | } 50 | 51 | if (!Validate(doc, res)) { 52 | goto ret; 53 | } 54 | 55 | var nodes = (await GetNodes(doc)); 56 | 57 | foreach (INode node in nodes) { 58 | if (token.IsCancellationRequested) { 59 | break; 60 | } 61 | 62 | var sri = await ParseResultItem(node, res); 63 | 64 | if (sri is { }) { 65 | res.Results.Add(sri); 66 | } 67 | } 68 | 69 | Logger.LogInformation("{Name} :: {RawUrl} document {DocLength}", Name, res.RawUrl, doc?.TextContent?.Length); 70 | 71 | res.Status = SearchResultStatus.Success; 72 | 73 | ret: 74 | res.Update(); 75 | doc?.Dispose(); 76 | Logger.LogDebug("Disposing {Name} doc", Name); 77 | return res; 78 | } 79 | 80 | [ICBN] 81 | [MURV] 82 | protected virtual async Task GetDocumentAsync(SearchResult sr, SearchQuery query, 83 | CancellationToken token = default) 84 | { 85 | 86 | var parser = new HtmlParser(); 87 | 88 | try { 89 | 90 | var res = await Client.Request(sr.RawUrl) 91 | .WithCookies(out var cj) 92 | .WithTimeout(Timeout) 93 | .WithHeaders(new 94 | { 95 | User_Agent = HttpUtilities.UserAgent 96 | }) 97 | /*.OnError(s => 98 | { 99 | s.ExceptionHandled = true; 100 | })*/ 101 | .GetAsync(cancellationToken: token); 102 | 103 | var str = await res.GetStreamAsync(); 104 | 105 | var document = await parser.ParseDocumentAsync(str, token); 106 | 107 | return document; 108 | 109 | } 110 | catch (Exception e) { 111 | // return await Task.FromException(e); 112 | Logger.LogError(e, "{Name} failed to get doc", Name); 113 | return null; 114 | } 115 | } 116 | 117 | protected abstract ValueTask ParseResultItem(INode n, SearchResult r); 118 | 119 | protected virtual ValueTask> GetNodes(IDocument d) 120 | { 121 | return ValueTask.FromResult>(d.Body.SelectNodes(NodesSelector)); 122 | } 123 | 124 | #region Overrides of BaseSearchEngine 125 | 126 | protected override Url GetRawUrl(SearchQuery query) 127 | { 128 | return base.GetRawUrl(query); 129 | } 130 | 131 | #endregion 132 | 133 | protected bool Validate([CBN] IDocument doc, SearchResult sr) 134 | { 135 | if (doc is null or { Body: null }) { 136 | return false; 137 | } 138 | 139 | foreach (string s in ErrorBodyMessages) { 140 | if (doc.Body.TextContent.Contains(s)) { 141 | return false; 142 | } 143 | } 144 | 145 | return true; 146 | 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Images/Uni/UniImageFile.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: UniImageFile.cs 2 | // Date: 2024/07/17 @ 02:07:16 3 | 4 | using System.IO.MemoryMappedFiles; 5 | using Microsoft; 6 | 7 | namespace SmartImage.Lib.Images.Uni; 8 | 9 | public class UniImageFile : UniImage 10 | { 11 | 12 | internal UniImageFile(object value, FileInfo fi) 13 | : base(value, UniImageType.File) 14 | { 15 | FileInfo = fi; 16 | FilePath = ValueString; 17 | } 18 | 19 | public FileInfo FileInfo { get; } 20 | 21 | public override string WriteToFile(string fn = null) 22 | { 23 | if (!HasFile) { 24 | throw new FileNotFoundException(ValueString); 25 | } 26 | 27 | return ValueString; 28 | } 29 | 30 | 31 | public override async ValueTask AllocAsync(CancellationToken ct = default) 32 | { 33 | if (!HasStream) { 34 | var fullName = FileInfo.FullName; 35 | 36 | Stream = File.OpenRead(fullName); 37 | } 38 | return HasStream; 39 | } 40 | 41 | public static bool IsFileType(object o, out FileInfo f) 42 | { 43 | f = null; 44 | 45 | if (o is string { } s && File.Exists(s)) { 46 | f = new FileInfo(s); 47 | } 48 | 49 | return f != null; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Images/Uni/UniImageStream.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: UniImageStream.cs 2 | // Date: 2024/07/17 @ 02:07:31 3 | 4 | using Novus.Streams; 5 | 6 | namespace SmartImage.Lib.Images.Uni; 7 | 8 | public class UniImageStream : UniImage 9 | { 10 | 11 | 12 | internal UniImageStream(object value, Stream str) 13 | : base(value, str, UniImageType.Stream) { } 14 | 15 | 16 | public static bool IsStreamType(object o, out Stream t2) 17 | { 18 | t2 = Stream.Null; 19 | 20 | if (o is Stream sz) { 21 | t2 = sz; 22 | } 23 | 24 | return t2 != Stream.Null; 25 | } 26 | 27 | public override ValueTask AllocAsync(CancellationToken ct = default) 28 | { 29 | return ValueTask.FromResult(HasStream); 30 | } 31 | 32 | public override string WriteToFile(string fn = null) 33 | => WriteStreamToFile(fn); 34 | 35 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Images/Uni/UniImageUri.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: UniImageUri.cs 2 | // Date: 2024/07/17 @ 02:07:26 3 | 4 | using System.Collections.Immutable; 5 | using System.Net; 6 | using Flurl.Http; 7 | 8 | namespace SmartImage.Lib.Images.Uni; 9 | 10 | public class UniImageUri : UniImage 11 | { 12 | 13 | public IFlurlResponse Response { get; private set; } 14 | 15 | [MNNW(true, nameof(Response))] 16 | public bool HasResponse => Response != null; 17 | 18 | public Url Url { get; } 19 | 20 | internal UniImageUri(object value, Url url, IFlurlResponse response = null) 21 | : base(value, Stream.Null, UniImageType.Uri) 22 | { 23 | Url = url; 24 | Response = response; 25 | } 26 | 27 | public override string WriteToFile(string fn = null) 28 | => WriteStreamToFile(fn); 29 | 30 | public static bool IsUriType(object o, out Url u) 31 | { 32 | u = o switch 33 | { 34 | Url u2 => u2, 35 | string s when Url.IsValid(s) => s, 36 | _ => null 37 | }; 38 | 39 | if (u == null) { 40 | return false; 41 | } 42 | 43 | var scheme = u.Scheme; 44 | 45 | return LegalSchemes.Contains(scheme); 46 | } 47 | 48 | public override async ValueTask AllocAsync(CancellationToken ct = default) 49 | { 50 | if (!HasResponse) { 51 | Response = await GetResponseAsync(Url, ct); 52 | } 53 | 54 | if (!HasStream) { 55 | Stream = await Response.GetStreamAsync(); 56 | } 57 | 58 | return HasResponse && HasStream; 59 | } 60 | 61 | public async ValueTask AllocResponseAsync(CancellationToken ct = default) 62 | { 63 | Response = await GetResponseAsync(Url, ct); 64 | 65 | return HasResponse; 66 | } 67 | 68 | public static readonly ImmutableArray RestrictedSchemes = ["file", "javascript", "cpu"]; 69 | 70 | public static readonly ImmutableArray LegalSchemes = ["http", "https"]; 71 | 72 | public static async ValueTask GetResponseAsync(Url value, CancellationToken ct) 73 | { 74 | // value = value.CleanString(); 75 | /*if (value.Scheme == "javascript") { 76 | throw new ArgumentException($"{value}"); 77 | }*/ 78 | 79 | var req1 = ImageScanner.Client.Request(value); 80 | var req = await ValueTask.FromResult(req1); 81 | 82 | /*.AllowAnyHttpStatus() 83 | .WithHeaders(new 84 | { 85 | // todo 86 | User_Agent = R1.UserAgent1, 87 | });*/ 88 | 89 | var res = await req.GetAsync(cancellationToken: ct); 90 | 91 | if (res.ResponseMessage.StatusCode == HttpStatusCode.NotFound) { 92 | throw new ArgumentException($"{value} returned {HttpStatusCode.NotFound}"); 93 | 94 | } 95 | 96 | return res; 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/IHashable.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: IHashable.cs 2 | // Date: 2024/11/13 @ 16:11:29 3 | 4 | using System.Security.Cryptography; 5 | using SmartImage.Lib.Utilities; 6 | 7 | namespace SmartImage.Lib.Results.Data; 8 | 9 | public interface IHashable 10 | { 11 | public Lazy Hash { get; } 12 | 13 | public const ulong HASH_ERROR = UInt64.MaxValue; 14 | 15 | public bool HasHash => Hash is { IsValueCreated: true, Value: not HASH_ERROR }; 16 | 17 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/IItemConvertable.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.Lib IResultParsable.cs 2 | // 2023-07-04 @ 1:27 PM 3 | 4 | 5 | // Read S SmartImage.Lib IResultParsable.cs 6 | // 2023-07-04 @ 1:27 PM 7 | 8 | using SmartImage.Lib.Results; 9 | 10 | namespace SmartImage.Lib.Results.Data; 11 | 12 | public interface IItemConvertable 13 | { 14 | 15 | public TItem ToItem(SearchResult sr); 16 | 17 | } 18 | 19 | public interface ISearchResultItemConvertable 20 | : IItemConvertable { } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/IItemConverter.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: IResultConverter.cs 2 | // Date: 2025/04/16 @ 01:04:44 3 | 4 | using AngleSharp.Dom; 5 | 6 | namespace SmartImage.Lib.Results.Data; 7 | 8 | public interface IItemConverter 9 | : IItemConvertable 10 | /*where TResult : IResultConvertable*/ 11 | { 12 | 13 | // public TResult2 ToResultItem(SearchResult sr); 14 | 15 | public static abstract TResult Parse(INode n); 16 | 17 | } 18 | 19 | public interface ISearchResultItemConverter 20 | : IItemConverter { } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/ISearchConfigReceiver.cs: -------------------------------------------------------------------------------- 1 | // Read Stanton SmartImage.Lib ISearchConfigReceiver.cs 2 | // 2023-01-13 @ 11:09 PM 3 | 4 | namespace SmartImage.Lib.Results.Data; 5 | 6 | public interface ISearchConfigReceiver 7 | { 8 | public ValueTask ApplyConfigAsync(SearchConfig cfg, CancellationToken ct = default); 9 | 10 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/ISimilarity.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: ISimilarity.cs 2 | // Date: 2024/11/13 @ 16:11:26 3 | 4 | namespace SmartImage.Lib.Results.Data; 5 | 6 | #pragma warning disable CS0168 7 | public interface ISimilarity 8 | { 9 | 10 | // todo 11 | public double? Similarity { get; } 12 | 13 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/Data/ISize.cs: -------------------------------------------------------------------------------- 1 | // Deci SmartImage.Lib IItemSize.cs 2 | // $File.CreatedYear-$File.CreatedMonth-24 @ 15:40 3 | 4 | namespace SmartImage.Lib.Results.Data; 5 | 6 | // TODO: delete 7 | 8 | public interface ISize 9 | { 10 | public long Size { get; } 11 | 12 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Results/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Net; 5 | using System.Runtime.CompilerServices; 6 | using System.Text.Json.Serialization; 7 | using System.Threading.Channels; 8 | using AngleSharp.Html.Parser; 9 | using Flurl.Http; 10 | using Kantan.Diagnostics; 11 | using Kantan.Net.Utilities; 12 | using SmartImage.Lib.Engines; 13 | using SmartImage.Lib.Images; 14 | using SmartImage.Lib.Utilities; 15 | using static SmartImage.Lib.Engines.BaseSearchEngine; 16 | 17 | namespace SmartImage.Lib.Results; 18 | 19 | #nullable disable 20 | 21 | public enum SearchResultStatus 22 | { 23 | 24 | /// 25 | /// N/A 26 | /// 27 | None = 0, 28 | 29 | /// 30 | /// Result obtained successfully 31 | /// 32 | Success, 33 | 34 | /// 35 | /// Engine is on cooldown due to too many requests 36 | /// 37 | Cooldown, 38 | 39 | /// 40 | /// Obtaining results failed due to an engine error 41 | /// 42 | UnknownError, 43 | 44 | IllegalInput, 45 | 46 | /// 47 | /// Engine is unavailable 48 | /// 49 | Unavailable 50 | 51 | } 52 | 53 | [Flags] 54 | public enum SearchResultFlags 55 | { 56 | 57 | None = 0, 58 | 59 | /// 60 | /// Engine returned no results 61 | /// 62 | NoResults = 1 << 0, 63 | 64 | /// 65 | /// Result is extraneous 66 | /// 67 | Extraneous = 1 << 1, 68 | 69 | } 70 | 71 | /// 72 | /// Root search result returned by a 73 | /// 74 | public class SearchResult : IDisposable, INotifyPropertyChanged 75 | { 76 | 77 | /// 78 | /// Engine which returned this result 79 | /// 80 | [JI] 81 | public BaseSearchEngine Engine { get; } 82 | 83 | // todo: make the engine reference weak 84 | 85 | /// 86 | /// Undifferentiated result URL 87 | /// 88 | public Url RawUrl { get; internal set; } 89 | 90 | [JI] 91 | public bool HasResults => !Flags.HasFlagFast(SearchResultFlags.NoResults); 92 | 93 | public bool IsSuccessful => Status.IsSuccessful(); 94 | 95 | /// 96 | /// Results; first element should be 97 | /// 98 | [NN] 99 | public List Results { get; } 100 | 101 | [CBN] 102 | public string ErrorMessage { get; internal set; } 103 | 104 | public SearchResultStatus Status { get; internal set; } 105 | 106 | public SearchResultFlags Flags { get; internal set; } 107 | 108 | [CBN] 109 | public string Overview { get; internal set; } 110 | 111 | internal SearchResult(BaseSearchEngine bse) 112 | { 113 | Engine = bse; 114 | Results = []; 115 | 116 | // Results = [GetRawResultItem()]; 117 | } 118 | 119 | public void Update() 120 | { 121 | if (Status.IsUnknown()) { 122 | 123 | } 124 | if (Status.IsError()) { 125 | return; 126 | } 127 | 128 | } 129 | 130 | public event PropertyChangedEventHandler PropertyChanged; 131 | 132 | private void OnPropertyChanged([CMN] string propertyName = null) 133 | { 134 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 135 | } 136 | 137 | private bool SetField(ref T field, T value, [CMN] string propertyName = null) 138 | { 139 | if (EqualityComparer.Default.Equals(field, value)) 140 | return false; 141 | 142 | field = value; 143 | OnPropertyChanged(propertyName); 144 | return true; 145 | } 146 | 147 | [CBN] 148 | public SearchResultItem GetBestResult() 149 | { 150 | if (Results.Count == 0) { 151 | // This should never happen so long as results contains the raw item 152 | Debugger.Break(); 153 | return null; 154 | } 155 | 156 | return Results.OrderByDescending(static r => r.Similarity) 157 | .FirstOrDefault(static r => Url.IsValid(r.Url)); 158 | } 159 | 160 | [JI] 161 | public SearchResultItem RawResultItem 162 | { 163 | get 164 | { 165 | var rawCache = new SearchResultItem(this, true) 166 | { 167 | Url = RawUrl 168 | }; 169 | return rawCache; 170 | } 171 | } 172 | 173 | public override string ToString() 174 | { 175 | return $"[{Engine.Name}] {RawUrl} | {Results.Count} | {Status} {ErrorMessage}"; 176 | } 177 | 178 | public void Dispose() 179 | { 180 | Debug.WriteLine($"Disposing {Engine.Name} with {Results.Count}", LogCategories.C_VERBOSE); 181 | 182 | foreach (SearchResultItem item in Results) { 183 | item.Dispose(); 184 | } 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /SmartImage.Lib/Results/UploadResult.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.Lib BaseUploadResponse.cs 2 | // 2023-05-28 @ 7:49 PM 3 | 4 | using Flurl.Http; 5 | 6 | namespace SmartImage.Lib.Results; 7 | 8 | public sealed class UploadResult : IDisposable 9 | { 10 | 11 | public Url Url { get; init; } 12 | 13 | [MN] 14 | public IFlurlResponse Response { get; init; } 15 | 16 | public long? Size { get; init; } 17 | 18 | public bool IsValid { get; init; } 19 | 20 | [CBN] 21 | public object Value { get; init; } 22 | 23 | /*public static implicit operator Url(UploadResult result) 24 | { 25 | if (!result.IsValid) { 26 | throw new Exception(); 27 | } 28 | 29 | return result.Url; 30 | }*/ 31 | 32 | public void Dispose() 33 | { 34 | Response?.Dispose(); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /SmartImage.Lib/SearchQuery.cs: -------------------------------------------------------------------------------- 1 | global using MN = System.Diagnostics.CodeAnalysis.MaybeNullAttribute; 2 | global using CBN = JetBrains.Annotations.CanBeNullAttribute; 3 | global using NN = System.Diagnostics.CodeAnalysis.NotNullAttribute; 4 | global using MNNW = System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute; 5 | global using ISImage = SixLabors.ImageSharp.Image; 6 | using System.Diagnostics; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Drawing; 9 | using System.Runtime.CompilerServices; 10 | using System.Security.Cryptography; 11 | using JetBrains.Annotations; 12 | using Microsoft; 13 | using Novus.FileTypes.Uni; 14 | using Novus.Streams; 15 | using Novus.Win32; 16 | using SixLabors.ImageSharp; 17 | using SixLabors.ImageSharp.Formats.Png; 18 | using SixLabors.ImageSharp.Processing; 19 | using SmartImage.Lib.Engines; 20 | using SmartImage.Lib.Engines.Impl.Upload; 21 | using SmartImage.Lib.Results.Data; 22 | using SmartImage.Lib.Results; 23 | using SmartImage.Lib.Utilities; 24 | using SixLabors.ImageSharp.Formats; 25 | using SmartImage.Lib.Images.Uni; 26 | 27 | [assembly: InternalsVisibleTo("SmartImage")] 28 | [assembly: InternalsVisibleTo("SmartImage.UI")] 29 | [assembly: InternalsVisibleTo("SmartImage.Rdx")] 30 | [assembly: InternalsVisibleTo("Test")] 31 | [assembly: InternalsVisibleTo("SmartImage.Lib.UnitTest")] 32 | 33 | namespace SmartImage.Lib; 34 | 35 | public sealed class SearchQuery : IDisposable, IEquatable 36 | { 37 | 38 | [MN] 39 | public Url Upload { get; private set; } 40 | 41 | [MNNW(true, nameof(Upload))] 42 | public bool IsUploaded => Url.IsValid(Upload); 43 | 44 | public UniImage Source { get; } 45 | 46 | internal SearchQuery(UniImage img, Url upload) 47 | { 48 | Source = img; 49 | Upload = upload; 50 | 51 | // Size = Uni == null ? default : Uni.Stream.Length; 52 | } 53 | 54 | internal SearchQuery(UniImage img) : this(img, null) { } 55 | 56 | static SearchQuery() { } 57 | 58 | public static readonly SearchQuery Null = new(UniImage.Null); 59 | 60 | public static async Task TryCreateAsync(object o, CancellationToken t = default) 61 | { 62 | var task = await UniImage.TryCreateAsync(o, ct: t); 63 | 64 | if (task != UniImage.Null) { 65 | return new SearchQuery(task); 66 | 67 | } 68 | else { 69 | return Null; 70 | } 71 | } 72 | 73 | public async Task UploadAsync(BaseUploadEngine engine = null, CancellationToken ct = default) 74 | { 75 | if (IsUploaded) { 76 | return Upload; 77 | } 78 | 79 | 80 | if (Source.IsUri) { 81 | Upload = Source.ValueString; 82 | 83 | // Size = BaseSearchEngine.NA_SIZE; 84 | // var fmt = await ISImage.DetectFormatAsync(Stream); 85 | 86 | Debug.WriteLine($"Skipping upload for {Source.ValueString}", nameof(UploadAsync)); 87 | } 88 | else { 89 | // fu = await test(fu); 90 | 91 | string fu; 92 | 93 | if (Source.IsFile) { 94 | fu = Source.ValueString; 95 | } 96 | else { 97 | // fu = Source.WriteToFile(); 98 | fu = null; 99 | if (Source.TryWriteToFile()) { 100 | fu = Source.FilePath; 101 | } 102 | Trace.WriteLine($"Wrote to file {fu}"); 103 | } 104 | 105 | engine ??= BaseUploadEngine.Default; 106 | 107 | UploadResult u = await engine.UploadFileAsync(fu, ct); 108 | Url url; 109 | 110 | if (!u.IsValid) { 111 | url = null; 112 | Debug.WriteLine($"{u} is invalid!"); 113 | 114 | // Debugger.Break(); 115 | } 116 | else { 117 | url = u.Url; 118 | 119 | } 120 | 121 | // TODO: AUTO-RETRY 122 | /* 123 | UploadResult u = await UploadAutoAsync(engine, fu, ct); 124 | Url url = u?.Url; 125 | */ 126 | 127 | /*if (!u.IsValid) { 128 | engine = BaseUploadEngine.All[Array.IndexOf(BaseUploadEngine.All, engine) + 1]; 129 | Debug.WriteLine($"{u.Response.ResponseMessage} failed, retrying with {engine.Name}"); 130 | u = await engine.UploadFileAsync(Uni.Value.ToString(), ct); 131 | }*/ 132 | 133 | Upload = url; 134 | 135 | /*if (u.Response is { }) { 136 | Size = NetHelper.GetContentLength(u.Response) ?? Size; 137 | }*/ 138 | // Size = u.Size ?? Size; 139 | u.Dispose(); 140 | } 141 | 142 | return Upload; 143 | } 144 | 145 | public void Dispose() 146 | { 147 | Trace.WriteLine($"Disposing {Source}"); 148 | Source?.Dispose(); 149 | } 150 | 151 | public override string ToString() 152 | { 153 | return $"{Source}: {IsUploaded}"; 154 | } 155 | 156 | #region Equality members 157 | 158 | public bool Equals(SearchQuery other) 159 | { 160 | if (other is null) return false; 161 | if (ReferenceEquals(this, other)) return true; 162 | 163 | return Equals(Source, other.Source) && Equals(Upload, other.Upload); 164 | } 165 | 166 | public override bool Equals(object obj) 167 | { 168 | return ReferenceEquals(this, obj) || (obj is SearchQuery other && Equals(other)); 169 | } 170 | 171 | public override int GetHashCode() 172 | { 173 | // return HashCode.Combine(Uni, Upload, Size); 174 | return HashCode.Combine(Source); 175 | 176 | // return Uni.GetHashCode(); 177 | } 178 | 179 | public static bool operator ==(SearchQuery left, SearchQuery right) 180 | { 181 | return Equals(left, right); 182 | } 183 | 184 | public static bool operator !=(SearchQuery left, SearchQuery right) 185 | { 186 | return !Equals(left, right); 187 | } 188 | 189 | #endregion 190 | 191 | } 192 | -------------------------------------------------------------------------------- /SmartImage.Lib/SmartImage.Lib.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  5 | UI -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/BaseSearchEngineTypeConverter.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: BaseSearchEngineTypeConverter.cs 2 | // Date: 2024/12/11 @ 23:12:21 3 | 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using SmartImage.Lib.Engines; 7 | 8 | namespace SmartImage.Lib.Utilities; 9 | 10 | public sealed class BaseSearchEngineTypeConverter : JsonConverter 11 | { 12 | 13 | #region Overrides of JsonConverter 14 | 15 | public override BaseSearchEngine Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | public override void Write(Utf8JsonWriter writer, BaseSearchEngine value, JsonSerializerOptions options) 21 | { 22 | writer.WriteString(nameof(BaseSearchEngine.Name), value.Name); 23 | } 24 | 25 | #endregion 26 | 27 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Diagnostics/AppSupport.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: AppSupport.cs 2 | // Date: 2024/12/04 @ 22:12:34 3 | 4 | #pragma warning disable IDE1006 5 | global using USI = JetBrains.Annotations.UsedImplicitlyAttribute; 6 | using System.Reflection; 7 | using System.Text.Json.Serialization; 8 | using Flurl.Http; 9 | using JetBrains.Annotations; 10 | using Kantan.Net.Utilities; 11 | using Microsoft.Extensions.Logging; 12 | 13 | // ReSharper disable InconsistentNaming 14 | 15 | namespace SmartImage.Lib.Utilities.Diagnostics; 16 | 17 | public static class AppSupport 18 | { 19 | 20 | internal static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); 21 | 22 | internal static readonly Version Version = Assembly.GetName().Version; 23 | 24 | internal static readonly ILoggerFactory Factory = 25 | LoggerFactory.Create(builder => builder.AddDebug().SetMinimumLevel(LogLevel.Trace)); 26 | 27 | public static async Task GetRepoReleasesAsync() 28 | { 29 | var r = await R1.Url_GitHubApi 30 | .WithAutoRedirect(true) 31 | .AllowAnyHttpStatus() 32 | .WithHeaders(new 33 | { 34 | User_Agent = HttpUtilities.UserAgent 35 | }) 36 | .OnError(e => { e.ExceptionHandled = true; }) 37 | .GetJsonAsync(); 38 | 39 | if (r == null) { 40 | return []; 41 | } 42 | 43 | foreach (var x in r) { 44 | var s = x.tag_name[1..].Split('-')[0]; 45 | x.IsRdx = x.name.Contains("Rdx", StringComparison.CurrentCultureIgnoreCase); 46 | 47 | if (Version.TryParse(s, out var xv)) { 48 | x.Version = xv; 49 | } 50 | } 51 | 52 | return r; 53 | } 54 | 55 | /* 56 | * HKEY_CLASSES_ROOT is an alias, a merging, of two other locations: 57 | * HKEY_CURRENT_USER\Software\Classes 58 | * HKEY_LOCAL_MACHINE\Software\Classes 59 | */ 60 | 61 | public const string DIAG_SMRTIMG_EXP001 = "SMRTIMG_EXP001"; 62 | 63 | } 64 | 65 | [USI(ImplicitUseTargetFlags.WithMembers)] 66 | public class GitHubReleaseAsset 67 | { 68 | 69 | public string url { get; set; } 70 | 71 | public int id { get; set; } 72 | 73 | public string node_id { get; set; } 74 | 75 | public string name { get; set; } 76 | 77 | public object label { get; set; } 78 | 79 | public GitHubUploader uploader { get; set; } 80 | 81 | public string content_type { get; set; } 82 | 83 | public string state { get; set; } 84 | 85 | public int size { get; set; } 86 | 87 | public int download_count { get; set; } 88 | 89 | public DateTime created_at { get; set; } 90 | 91 | public DateTime updated_at { get; set; } 92 | 93 | public string browser_download_url { get; set; } 94 | 95 | } 96 | 97 | [USI(ImplicitUseTargetFlags.WithMembers)] 98 | public class GitHubAuthor 99 | { 100 | 101 | public string login { get; set; } 102 | 103 | public int id { get; set; } 104 | 105 | public string node_id { get; set; } 106 | 107 | public string avatar_url { get; set; } 108 | 109 | public string gravatar_id { get; set; } 110 | 111 | public string url { get; set; } 112 | 113 | public string html_url { get; set; } 114 | 115 | public string followers_url { get; set; } 116 | 117 | public string following_url { get; set; } 118 | 119 | public string gists_url { get; set; } 120 | 121 | public string starred_url { get; set; } 122 | 123 | public string subscriptions_url { get; set; } 124 | 125 | public string organizations_url { get; set; } 126 | 127 | public string repos_url { get; set; } 128 | 129 | public string events_url { get; set; } 130 | 131 | public string received_events_url { get; set; } 132 | 133 | public string type { get; set; } 134 | 135 | public bool site_admin { get; set; } 136 | 137 | } 138 | 139 | [USI(ImplicitUseTargetFlags.WithMembers)] 140 | public class GitHubReactions 141 | { 142 | 143 | public string url { get; set; } 144 | 145 | public int total_count { get; set; } 146 | 147 | [JsonPropertyName("+1")] 148 | public int Plus1 { get; set; } 149 | 150 | [JsonPropertyName("-1")] 151 | public int Minus1 { get; set; } 152 | 153 | public int laugh { get; set; } 154 | 155 | public int hooray { get; set; } 156 | 157 | public int confused { get; set; } 158 | 159 | public int heart { get; set; } 160 | 161 | public int rocket { get; set; } 162 | 163 | public int eyes { get; set; } 164 | 165 | } 166 | 167 | [USI(ImplicitUseTargetFlags.WithMembers)] 168 | public class GitHubUploader 169 | { 170 | 171 | public string login { get; set; } 172 | 173 | public int id { get; set; } 174 | 175 | public string node_id { get; set; } 176 | 177 | public string avatar_url { get; set; } 178 | 179 | public string gravatar_id { get; set; } 180 | 181 | public string url { get; set; } 182 | 183 | public string html_url { get; set; } 184 | 185 | public string followers_url { get; set; } 186 | 187 | public string following_url { get; set; } 188 | 189 | public string gists_url { get; set; } 190 | 191 | public string starred_url { get; set; } 192 | 193 | public string subscriptions_url { get; set; } 194 | 195 | public string organizations_url { get; set; } 196 | 197 | public string repos_url { get; set; } 198 | 199 | public string events_url { get; set; } 200 | 201 | public string received_events_url { get; set; } 202 | 203 | public string type { get; set; } 204 | 205 | public bool site_admin { get; set; } 206 | 207 | } 208 | 209 | [USI(ImplicitUseTargetFlags.WithMembers)] 210 | public class GitHubRelease 211 | { 212 | 213 | [JI] 214 | [field: NonSerialized] 215 | public Version Version { get; set; } 216 | 217 | [JI] 218 | [field: NonSerialized] 219 | public bool IsRdx { get; set; } 220 | 221 | 222 | public string url { get; set; } 223 | 224 | public string assets_url { get; set; } 225 | 226 | public string upload_url { get; set; } 227 | 228 | public string html_url { get; set; } 229 | 230 | public int id { get; set; } 231 | 232 | public GitHubAuthor author { get; set; } 233 | 234 | public string node_id { get; set; } 235 | 236 | public string tag_name { get; set; } 237 | 238 | public string target_commitish { get; set; } 239 | 240 | public string name { get; set; } 241 | 242 | public bool draft { get; set; } 243 | 244 | public bool prerelease { get; set; } 245 | 246 | public DateTime created_at { get; set; } 247 | 248 | public DateTime published_at { get; set; } 249 | 250 | public List assets { get; set; } 251 | 252 | public string tarball_url { get; set; } 253 | 254 | public string zipball_url { get; set; } 255 | 256 | public string body { get; set; } 257 | 258 | public string discussion_url { get; set; } 259 | 260 | public GitHubReactions reactions { get; set; } 261 | 262 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Diagnostics/HttpLoggingHandler.cs: -------------------------------------------------------------------------------- 1 | // Read Stanton SmartImage.Lib LoggingHandler.cs 2 | // 2023-02-14 @ 12:17 AM 3 | 4 | using JetBrains.Annotations; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace SmartImage.Lib.Utilities.Diagnostics; 8 | 9 | internal class HttpLoggingHandler : DelegatingHandler 10 | { 11 | 12 | public HttpLoggingHandler(ILogger l) 13 | { 14 | m_logger = l; 15 | } 16 | 17 | public HttpLoggingHandler([NotNull] HttpMessageHandler innerHandler) : base(innerHandler) { } 18 | 19 | private readonly ILogger m_logger; 20 | 21 | protected override Task SendAsync(HttpRequestMessage request, 22 | CancellationToken cancellationToken) 23 | { 24 | m_logger.LogDebug("Request {Request}", request.RequestUri); 25 | 26 | return base.SendAsync(request, cancellationToken); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Diagnostics/SmartImageException.cs: -------------------------------------------------------------------------------- 1 | namespace SmartImage.Lib.Utilities.Diagnostics; 2 | 3 | public sealed class SmartImageException : Exception 4 | { 5 | 6 | public SmartImageException() { } 7 | 8 | public SmartImageException([CBN] string message) : base(message) { } 9 | 10 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/FieldValueMap.cs: -------------------------------------------------------------------------------- 1 | using Kantan.Utilities; 2 | using Novus.Utilities; 3 | 4 | namespace SmartImage.Lib.Utilities; 5 | 6 | // todo 7 | 8 | public class FieldValueMap 9 | { 10 | 11 | public string Name { get; } 12 | 13 | public object Value { get; } 14 | 15 | public string FieldName { get; } 16 | 17 | public static FieldValueMap Find(T inst, string name) 18 | { 19 | var type = inst?.GetType() ?? typeof(T); 20 | return Find(inst, name, type); 21 | } 22 | 23 | private static FieldValueMap Find(T inst, string name, Type type) 24 | { 25 | var fld = type.GetAnyResolvedField(name); 26 | 27 | if (fld is not null) { 28 | return new FieldValueMap(name, fld.GetValue(inst), fld.Name); 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public static FieldValueMap[] Find(T inst, string[] names) 35 | { 36 | var type = inst?.GetType() ?? typeof(T); 37 | 38 | var values = new FieldValueMap[names.Length]; 39 | int i = 0; 40 | 41 | foreach (string name in names) { 42 | var val = Find(inst, name, type); 43 | 44 | if (val is not null) { 45 | values[i++] = val; 46 | } 47 | } 48 | 49 | Array.Resize(ref values, i); 50 | 51 | return values; 52 | } 53 | 54 | public static FieldValueMap[] Find(T inst, T2 names) where T2 : struct, Enum 55 | { 56 | var names2 = names.GetSetFlags().Select(x => x.ToString()).ToArray(); 57 | return Find(inst, names2); 58 | } 59 | 60 | public FieldValueMap(string name, object value, string fieldName) 61 | { 62 | Name = name; 63 | Value = value; 64 | FieldName = fieldName; 65 | } 66 | 67 | public override string ToString() 68 | { 69 | return $"{nameof(Name)}: {Name} | {nameof(FieldName)}: {FieldName} | {nameof(Value)}: {Value}"; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Integration/BaseOSIntegration.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: BaseOSIntegration.cs 2 | // Date: 2024/12/05 @ 21:12:49 3 | 4 | using System.Diagnostics; 5 | using System.Runtime.Versioning; 6 | using Novus.OS; 7 | 8 | // ReSharper disable InconsistentNaming 9 | 10 | namespace SmartImage.Lib.Utilities.Integration; 11 | 12 | public abstract class BaseOSIntegration 13 | { 14 | 15 | #region 16 | 17 | public virtual bool IsRoot => FileSystem.IsRoot; 18 | 19 | public abstract bool IsContextMenuAdded { get; } 20 | 21 | public abstract string ProgramFilesPath { get; } 22 | 23 | public abstract string AppDataPath { get; } 24 | 25 | public virtual string PersonalPath { get; } = Environment.GetFolderPath(Environment.SpecialFolder.Personal); 26 | 27 | public abstract string LaunchArgs { get; } 28 | 29 | [CBN] 30 | public abstract string ChromePath { get; } 31 | 32 | [CBN] 33 | public abstract string FirefoxPath { get; } 34 | 35 | 36 | [MNNW(true, nameof(ChromePath))] 37 | public bool IsChromeInstalled => Path.Exists(ChromePath); 38 | 39 | [MNNW(true, nameof(FirefoxPath))] 40 | public bool IsFirefoxInstalled => Path.Exists(FirefoxPath); 41 | 42 | #endregion 43 | 44 | static BaseOSIntegration() 45 | { 46 | Executable = GetProcessMainModuleFileName(); 47 | ExecutableDirectory = Path.GetDirectoryName(Executable); 48 | 49 | if (IsWindows) { 50 | Integration = new WindowsOSIntegration(); 51 | } 52 | else if (IsLinux) { 53 | Integration = new LinuxOSIntegration(); 54 | } 55 | else { 56 | Integration = null; 57 | throw new NotSupportedException("OS not supported"); 58 | } 59 | } 60 | 61 | #region 62 | 63 | internal const string OS_WIN = "windows"; 64 | 65 | internal const string OS_LINUX = "linux"; 66 | 67 | public const int EC_ERROR = -1; 68 | 69 | public const int EC_OK = 0; 70 | 71 | [SupportedOSPlatformGuard(OS_LINUX)] 72 | public static readonly bool IsLinux = OperatingSystem.IsLinux(); 73 | 74 | [SupportedOSPlatformGuard(OS_WIN)] 75 | public static readonly bool IsWindows = OperatingSystem.IsWindows(); 76 | 77 | 78 | public static BaseOSIntegration Integration { get; } 79 | 80 | public static string ExecutableDirectory { get; } 81 | 82 | public static bool IsExecutableInPath 83 | => FileSystem.IsFolderInPath(ExecutableDirectory); 84 | 85 | public static string Executable { get; } 86 | 87 | #endregion 88 | 89 | /// true if operation succeeded; false otherwise 90 | public abstract bool? AddToPath(bool option); 91 | 92 | /// true if operation succeeded; false otherwise 93 | public abstract bool? HandleContextMenu(bool option, string args); 94 | 95 | public abstract void FlashNotify(nint fd); 96 | 97 | public static string GetProcessMainModuleFileName() 98 | { 99 | // TODO: vs Directory.GetCurrentDirectory 100 | 101 | ProcessModule module = Process.GetCurrentProcess().MainModule; 102 | 103 | // Require.NotNull(module); 104 | Trace.Assert(module != null); 105 | return module.FileName; 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Integration/LinuxOSIntegration.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: LinuxOSIntegration.cs 2 | // Date: 2024/12/04 @ 22:12:30 3 | 4 | using System.Runtime.Versioning; 5 | using Novus.OS; 6 | using SmartImage.Lib.Utilities.Diagnostics; 7 | 8 | // ReSharper disable InconsistentNaming 9 | 10 | namespace SmartImage.Lib.Utilities.Integration; 11 | 12 | [SupportedOSPlatform(OS_LINUX)] 13 | public sealed class LinuxOSIntegration : BaseOSIntegration 14 | { 15 | 16 | public override string LaunchArgs { get; } = R1.Linux_Launch_Args; 17 | 18 | public override string ProgramFilesPath => null; 19 | 20 | public override string AppDataPath => null; 21 | 22 | 23 | public override bool IsContextMenuAdded => File.Exists(DesktopFile); 24 | 25 | public override string ChromePath => null; 26 | 27 | public override string FirefoxPath => null; 28 | 29 | public static readonly string DesktopFile = Path.Combine(R1.Linux_Applications_Dir, R1.Linux_Desktop_File); 30 | 31 | public override bool? AddToPath(bool option) 32 | { 33 | return null; 34 | } 35 | 36 | public override bool? HandleContextMenu(bool option, string args) 37 | { 38 | if (!FileSystem.IsRoot) { 39 | throw new SmartImageException("Root permissions required"); 40 | 41 | } 42 | 43 | args ??= R1.Linux_Launch_Args; 44 | 45 | if (option) { 46 | string dsk = $""" 47 | [Desktop Entry] 48 | 49 | Type=Application 50 | Version=1.0 51 | Name=SmartImage 52 | Terminal=true 53 | Exec={Executable} {args} 54 | """; 55 | File.WriteAllText(DesktopFile, dsk); 56 | 57 | } 58 | else { 59 | if (IsContextMenuAdded) { 60 | File.Delete(DesktopFile); 61 | } 62 | } 63 | 64 | // Console.WriteLine(Path.GetFullPath(s)); 65 | // Console.ReadLine(); 66 | 67 | // File.WriteAllText("~/.local/share/nautilus/scripts/smartimage.desktop", dsk); 68 | 69 | return true; 70 | } 71 | 72 | public override void FlashNotify(nint fd) { } 73 | 74 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/Integration/WindowsOSIntegration.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: WindowsOSIntegration.cs 2 | // Date: 2024/12/05 @ 21:12:18 3 | 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Versioning; 7 | using JetBrains.Annotations; 8 | using Microsoft.Win32; 9 | using Novus.OS; 10 | using Novus.Win32; 11 | using Novus.Win32.Structures.User32; 12 | 13 | // ReSharper disable InconsistentNaming 14 | 15 | namespace SmartImage.Lib.Utilities.Integration; 16 | 17 | [SupportedOSPlatform(OS_WIN)] 18 | public sealed class WindowsOSIntegration : BaseOSIntegration 19 | { 20 | [NN] 21 | public override string ChromePath => Path.Combine(ProgramFilesPath, @"Google\Chrome\Application\chrome.exe"); 22 | 23 | [NN] 24 | public override string FirefoxPath => Path.Combine(AppDataPath, @"Mozilla"); 25 | 26 | public override string LaunchArgs { get; } = R1.Reg_Launch_Args; 27 | 28 | public override string ProgramFilesPath { get; } = 29 | Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); 30 | 31 | public override string AppDataPath { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 32 | 33 | 34 | public override bool IsContextMenuAdded 35 | { 36 | get 37 | { 38 | using var reg = Registry.CurrentUser.OpenSubKey(R1.Reg_Shell_Cmd); 39 | return reg != null; 40 | 41 | } 42 | } 43 | 44 | public override bool? AddToPath(bool option) 45 | { 46 | if (option) { 47 | var p = FileSystem.GetEnvironmentPath(); 48 | FileSystem.SetEnvironmentPath(p + $";{ExecutableDirectory}"); 49 | 50 | } 51 | else { 52 | FileSystem.RemoveFromPath(ExecutableDirectory); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | public override bool? HandleContextMenu(bool option, string args) 59 | { 60 | /* 61 | * New context menu 62 | */ 63 | bool ok = false; 64 | 65 | switch (option) { 66 | case true: 67 | 68 | args ??= R1.Reg_Launch_Args; 69 | 70 | RegistryKey regMenu = null; 71 | RegistryKey regCmd = null; 72 | 73 | string fullPath = Executable; 74 | 75 | try { 76 | regMenu = Registry.CurrentUser.CreateSubKey(R1.Reg_Shell); 77 | regMenu?.SetValue(String.Empty, R1.Name); 78 | regMenu?.SetValue("Icon", $"\"{fullPath}\""); 79 | 80 | regCmd = Registry.CurrentUser.CreateSubKey(R1.Reg_Shell_Cmd); 81 | 82 | regCmd?.SetValue(String.Empty, $"\"{fullPath}\" {args}"); 83 | 84 | // regCmd?.SetValue(String.Empty, $"\"{fullPath}\" \"%1\""); 85 | // regCmd?.SetValue(String.Empty, $"\"{fullPath}\" -i \"%1\" -auto -s"); 86 | ok = true; 87 | } 88 | catch (Exception ex) { 89 | Trace.WriteLine($"{ex.Message}"); 90 | 91 | // return false; 92 | ok = false; 93 | } 94 | finally { 95 | regMenu?.Close(); 96 | regCmd?.Close(); 97 | } 98 | 99 | break; 100 | 101 | case false: 102 | 103 | try { 104 | var reg = Registry.CurrentUser.OpenSubKey(R1.Reg_Shell_Cmd); 105 | 106 | if (reg != null) { 107 | reg.Close(); 108 | Registry.CurrentUser.DeleteSubKey(R1.Reg_Shell_Cmd); 109 | } 110 | 111 | reg = Registry.CurrentUser.OpenSubKey(R1.Reg_Shell); 112 | 113 | if (reg != null) { 114 | reg.Close(); 115 | Registry.CurrentUser.DeleteSubKey(R1.Reg_Shell); 116 | } 117 | 118 | // return true; 119 | ok = true; 120 | } 121 | catch (Exception ex) { 122 | Trace.WriteLine($"{ex.Message}"); 123 | ok = false; 124 | 125 | // return false; 126 | } 127 | 128 | break; 129 | 130 | } 131 | 132 | return ok; 133 | } 134 | 135 | public override void FlashNotify(nint hwnd) 136 | { 137 | var pwfi = new FLASHWINFO 138 | { 139 | cbSize = (uint) Marshal.SizeOf(), 140 | hwnd = hwnd, 141 | dwFlags = FlashWindowType.FLASHW_TRAY, 142 | uCount = 8, 143 | dwTimeout = 75 144 | }; 145 | 146 | Native.FlashWindowEx(ref pwfi); 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/NodeHelper.cs: -------------------------------------------------------------------------------- 1 | // Read Stanton SmartImage.Lib NodeHelper.cs 2 | // 2023-01-13 @ 11:37 PM 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using System.Net.Mime; 7 | using System.Text.Json; 8 | using System.Text.Json.Nodes; 9 | using System.Text.Json.Serialization; 10 | using AngleSharp.Dom; 11 | using Flurl.Http; 12 | using Flurl.Http.Configuration; 13 | using JetBrains.Annotations; 14 | using Novus.Win32; 15 | 16 | // ReSharper disable AnnotateNotNullParameter 17 | 18 | namespace SmartImage.Lib.Utilities; 19 | 20 | public static class NodeHelper 21 | { 22 | 23 | public static JsonNode TryGetKeyValue(this JsonObject v, string k) 24 | => v.ContainsKey(k) ? v[k] : null; 25 | 26 | [CBN] 27 | internal static INode FirstOrDefaultElement(this INodeList nodes, Func predicate) 28 | => ApplyFunctorInnerPredicate(nodes.FirstOrDefault, predicate); 29 | 30 | /*[CBN] 31 | internal static INode ElemFunctor(Func, INode> functor, Predicate elemPredicate) 32 | => ApplyFunctorInnerPredicate(functor, elemPredicate);*/ 33 | 34 | 35 | [CBN] 36 | internal static INode FirstOrDefaultElementByClassName(this INodeList nodes, string className) 37 | => ApplyFunctorInnerPredicate(nodes.FirstOrDefault, e => e.ClassName == className); 38 | 39 | [CBN] 40 | [LinqTunnel] 41 | internal static T2 ApplyFunctorInnerPredicate(Func, T2> functor, 42 | Func predicate) 43 | => functor(f => f is T e && predicate(e)); 44 | 45 | 46 | /* 47 | * IEnumerable bound 48 | * 49 | */ 50 | 51 | 52 | // Why am I recreating LINQ 53 | 54 | 55 | [return: NN] 56 | internal static IEnumerable TryFindElementsByClassName(Func, IEnumerable> where, 57 | Func predicate) 58 | => where(predicate); 59 | 60 | 61 | public static IEnumerable QueryAllAttribute(this IParentNode doc, string sel, string attr) 62 | => doc.QuerySelectorAll(sel) 63 | .Select(e => e.GetAttribute(attr)); 64 | 65 | public static INode RecurseChildren(this INode node, int childNodeIndex, int level) 66 | { 67 | /*if (level <= 0) { 68 | return n; 69 | } 70 | 71 | return RecurseChildren(n.ChildNodes[childNodeIndex], childNodeIndex, --level);*/ 72 | // return level <= 0 ? n : RecurseChildren(n.ChildNodes[childNodeIndex], childNodeIndex, --level); 73 | 74 | while (true) { 75 | if (level <= 0) 76 | return node; 77 | 78 | node = node.ChildNodes[childNodeIndex]; 79 | level = --level; 80 | } 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/SearchResultTypeConverter.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Lib | Name: SearchResultTypeConverter.cs 2 | // Date: 2024/11/21 @ 13:11:05 3 | 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using System.Text.Json.Serialization.Metadata; 7 | using SmartImage.Lib.Results; 8 | 9 | namespace SmartImage.Lib.Utilities; 10 | 11 | 12 | public sealed class SearchResultTypeConverter : JsonConverter 13 | { 14 | 15 | #region Overrides of JsonConverter 16 | 17 | public override SearchResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public override void Write(Utf8JsonWriter writer, SearchResult value, JsonSerializerOptions options) 23 | { 24 | writer.WriteStartObject(); 25 | writer.WriteString(nameof(SearchResult.Engine.Name), value.Engine.EngineOption.ToString()); 26 | writer.WriteString(nameof(SearchResult.Status), $"{value.Status}"); 27 | writer.WriteStartArray(nameof(SearchResult.Results)); 28 | 29 | foreach (SearchResultItem result in value.Results) { 30 | writer.WriteStartObject(); 31 | writer.WriteRawValue(JsonSerializer.Serialize(result, options)); 32 | writer.WriteEndObject(); 33 | } 34 | 35 | writer.WriteEndArray(); 36 | writer.WriteEndObject(); 37 | } 38 | 39 | #endregion 40 | 41 | } -------------------------------------------------------------------------------- /SmartImage.Lib/Utilities/SearchUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using SmartImage.Lib.Engines; 7 | using SmartImage.Lib.Results; 8 | 9 | namespace SmartImage.Lib.Utilities; 10 | 11 | public static class SearchUtil 12 | { 13 | 14 | /*public static bool IsSuccessful(this SearchResultStatus s) 15 | => s is SearchResultStatus.Success || (!s.IsError() && !s.IsUnknown());*/ 16 | 17 | public static bool IsSuccessful(this SearchResultStatus s) 18 | => s is SearchResultStatus.Success; 19 | 20 | public static bool IsUnknown(this SearchResultStatus s) 21 | => s is SearchResultStatus.None; 22 | 23 | public static bool IsError(this SearchResultStatus s) 24 | => s is SearchResultStatus.UnknownError or SearchResultStatus.IllegalInput 25 | or SearchResultStatus.Unavailable or SearchResultStatus.Cooldown; 26 | 27 | public const SearchResultFlags ALT_STATUS = 28 | SearchResultFlags.NoResults | SearchResultFlags.Extraneous; 29 | 30 | public static bool HasFlagFast(this SearchResultFlags value, SearchResultFlags status) 31 | => (value & status) != 0; 32 | 33 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/CommonCommandSettings.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: CommonCommandSettings.cs 2 | // Date: 2025/02/25 @ 10:02:23 3 | 4 | using System.ComponentModel; 5 | using SmartImage.Lib; 6 | using SmartImage.Lib.Engines; 7 | using Spectre.Console.Cli; 8 | 9 | namespace SmartImage.Rdx.Commands; 10 | 11 | public class CommonCommandSettings : CommandSettings 12 | { 13 | 14 | [CommandOption("-e|--search-engines")] 15 | [DefaultValue(SearchConfig.SE_DEFAULT)] 16 | [Description("Search engines (comma-delimited)")] 17 | public SearchEngineOptions SearchEngines { get; internal set; } 18 | 19 | [CommandOption("-p|--priority-engines")] 20 | [DefaultValue(SearchConfig.PE_DEFAULT)] 21 | [Description("Engines whose results to open (comma-delimited)")] 22 | public SearchEngineOptions PriorityEngines { get; internal set; } 23 | 24 | [CommandOption("--read-cookies")] 25 | [DefaultValue(SearchConfig.READCOOKIES_DEFAULT)] 26 | [Description("Read cookies from browser")] 27 | public bool ReadCookies { get; internal set; } 28 | 29 | [CommandOption("--flaresolverr")] 30 | [DefaultValue(SearchConfig.FLARESOLVERR_DEFAULT)] 31 | [Description("Use FlareSolverr")] 32 | public bool FlareSolverr { get; internal set; } 33 | 34 | [CommandOption("--flaresolverr-api")] 35 | [DefaultValue(SearchConfig.FLARE_SOLVERR_API_URL_DEFAULT)] 36 | [Description("FlareSolverr API URL")] 37 | public string? FlareSolverrApiUrl { get; internal set; } 38 | 39 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/IntegrationCommand.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: IntegrationCommand.cs 2 | // Date: 2024/05/22 @ 16:05:51 3 | 4 | using System.Diagnostics; 5 | using SmartImage.Lib.Utilities.Integration; 6 | using SmartImage.Rdx.Shell; 7 | using Spectre.Console; 8 | using Spectre.Console.Cli; 9 | 10 | namespace SmartImage.Rdx.Commands; 11 | 12 | internal class IntegrationCommand : Command 13 | { 14 | 15 | public override int Execute(CommandContext context, IntegrationCommandSettings settings) 16 | { 17 | try { 18 | // AnsiConsole.WriteLine($"{AppSupport.IsContextMenuAdded}"); 19 | 20 | if (settings.ContextMenu.HasValue) { 21 | var rv = BaseOSIntegration.Integration.HandleContextMenu(settings.ContextMenu.Value, settings.ContextMenuArguments); 22 | AnsiConsole.WriteLine($"Context menu change: {rv}"); 23 | } 24 | 25 | AnsiConsole.WriteLine($"Context menu enabled: {BaseOSIntegration.Integration.IsContextMenuAdded}"); 26 | } 27 | catch (Exception e) { 28 | AnsiConsole.WriteException(e); 29 | } 30 | 31 | return BaseOSIntegration.EC_OK; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/IntegrationCommandSettings.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: IntegrationCommandSettings.cs 2 | // Date: 2024/05/22 @ 16:05:47 3 | 4 | using SmartImage.Lib.Utilities.Integration; 5 | using Spectre.Console; 6 | using Spectre.Console.Cli; 7 | 8 | namespace SmartImage.Rdx.Commands; 9 | 10 | internal class IntegrationCommandSettings : CommandSettings 11 | { 12 | 13 | [CommandOption("--ctx-menu")] 14 | public bool? ContextMenu { get; internal set; } 15 | 16 | [CommandOption("--ctx-menu-args")] 17 | public string? ContextMenuArguments { get; internal set; } 18 | 19 | public override ValidationResult Validate() 20 | { 21 | ContextMenuArguments ??= BaseOSIntegration.Integration.LaunchArgs; 22 | 23 | return base.Validate(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/SearchCommandSettings.cs: -------------------------------------------------------------------------------- 1 | // Deci SmartImage.Rdx SearchCommandSettings.cs 2 | // $File.CreatedYear-$File.CreatedMonth-26 @ 0:56 3 | 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Text; 7 | using Novus.Win32; 8 | using SmartImage.Lib.Images.Uni; 9 | using SmartImage.Rdx.Shell; 10 | using Spectre.Console.Cli; 11 | using ValidationResult = Spectre.Console.ValidationResult; 12 | 13 | namespace SmartImage.Rdx.Commands; 14 | 15 | public sealed class SearchCommandSettings : CommonCommandSettings 16 | { 17 | 18 | [CommandArgument(0, "")] 19 | [Description("Query: file or URL; see wiki")] 20 | public string? Query { get; internal set; } 21 | 22 | #region 23 | 24 | [CommandOption("--keep-open")] 25 | [DefaultValue(false)] 26 | [Description("Waits for input before terminating")] 27 | public bool KeepOpen { get; internal set; } 28 | 29 | #endregion 30 | 31 | #region 32 | 33 | [CommandOption("-f|--output-format")] 34 | [DefaultValue(OutputFileFormat.None)] 35 | [Description("Output file format")] 36 | public OutputFileFormat OutputFileFormat { get; internal set; } 37 | 38 | [CommandOption("-o|--output-file")] 39 | [Description("Output file name")] 40 | public string? OutputFile { get; internal set; } 41 | 42 | [CommandOption("-d|--output-delim")] 43 | [DefaultValue(",")] 44 | [Description("Output file delimiter")] 45 | public string? OutputFileDelimiter { get; internal set; } 46 | 47 | [CommandOption("--output-fields")] 48 | [DefaultValue(OUTPUT_FIELDS_DEFAULT)] 49 | [Description("Output fields (comma-delimited)")] 50 | public OutputFields OutputFields { get; internal set; } 51 | 52 | public const OutputFields OUTPUT_FIELDS_DEFAULT = 53 | OutputFields.Name | OutputFields.Similarity | OutputFields.Url; 54 | 55 | #endregion 56 | 57 | #region 58 | 59 | [CommandOption("-x|--command-exe")] 60 | [Description($"Command/executable to invoke upon completion")] 61 | public string? Command { get; internal set; } 62 | 63 | [CommandOption("-c|--command-args")] 64 | [Description($"Arguments to pass to command")] 65 | public string? CommandArguments { get; internal set; } 66 | 67 | #endregion 68 | 69 | // public bool? Silent { get; internal set; } //todo 70 | 71 | // public const string PROP_ARG_RESULTS = "$all_results"; 72 | 73 | [CommandOption("--interactive")] 74 | [DefaultValue(false)] 75 | [Description("Interactive results")] 76 | public bool Interactive { get; internal set; } 77 | 78 | public override ValidationResult Validate() 79 | { 80 | var result = base.Validate(); 81 | 82 | if (!UniImage.IsValidSourceType(Query, false)) { 83 | return ValidationResult.Error("Invalid query"); 84 | } 85 | 86 | var hasOutputFile = !string.IsNullOrWhiteSpace(OutputFile); 87 | var hasOutputFileDelim = !string.IsNullOrEmpty(OutputFileDelimiter); 88 | bool isOutputFormatDelim = OutputFileFormat == OutputFileFormat.Delimited; 89 | 90 | if (!isOutputFormatDelim && hasOutputFile) { 91 | OutputFileFormat = OutputFileFormat.Delimited; 92 | isOutputFormatDelim = true; 93 | } 94 | 95 | if (isOutputFormatDelim) { 96 | if (!hasOutputFile) { 97 | return ValidationResult.Error( 98 | $"{nameof(OutputFile)} must be set if {nameof(OutputFileFormat)} == {nameof(OutputFileFormat.Delimited)}"); 99 | } 100 | 101 | if (!hasOutputFileDelim) { 102 | return ValidationResult.Error( 103 | $"{nameof(OutputFileDelimiter)} must be set if {nameof(OutputFileFormat)} == {nameof(OutputFileFormat.Delimited)}"); 104 | } 105 | } 106 | 107 | return result; 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/SearchServerResponse.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: SearchServerResponse.cs 2 | // Date: 2025/02/04 @ 12:02:39 3 | 4 | using System.Text.Json.Serialization; 5 | using SmartImage.Lib.Results; 6 | 7 | namespace SmartImage.Rdx.Commands; 8 | 9 | public class SearchServerResponse 10 | { 11 | 12 | [MN] 13 | [JsonPropertyOrder(0)] 14 | public SearchResultItem Best { get; internal set; } 15 | 16 | [JsonPropertyOrder(1)] 17 | public SearchResult[] Results { get; internal set; } 18 | 19 | [MN] 20 | [JsonPropertyOrder(2)] 21 | public string Message { get; internal set; } 22 | 23 | public SearchServerResponse() { } 24 | 25 | public SearchServerResponse(SearchResult[] results, SearchResultItem best) 26 | { 27 | Best = best; 28 | Results = results; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Commands/ServerCommandSettings.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: ServerCommandSettings.cs 2 | // Date: 2024/12/03 @ 10:12:10 3 | 4 | using System.ComponentModel; 5 | using SmartImage.Lib.Utilities.Diagnostics; 6 | using SmartImage.Lib.Utilities.Integration; 7 | using Spectre.Console; 8 | using Spectre.Console.Cli; 9 | 10 | namespace SmartImage.Rdx.Commands; 11 | 12 | public sealed class ServerCommandSettings : CommonCommandSettings 13 | { 14 | 15 | [CommandOption("--port")] 16 | [DefaultValue(25565)] 17 | public int Port { get; set; } 18 | 19 | public override ValidationResult Validate() 20 | { 21 | /*if (!BaseOSIntegration.Integration.IsRoot) { 22 | throw new SmartImageException("Must be admin"); 23 | }*/ 24 | 25 | return base.Validate(); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Decimation/SmartImage/49b6e8266eb5c288bf7ff7685d7e54d0a5b17442/SmartImage.Rdx/Icon.ico -------------------------------------------------------------------------------- /SmartImage.Rdx/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Threading.Channels; 9 | using Flurl.Http; 10 | using Flurl.Http.Configuration; 11 | using Kantan.Text; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Microsoft.Extensions.Hosting; 15 | using Novus.Streams; 16 | using Novus.Win32; 17 | using SixLabors.ImageSharp; 18 | using SixLabors.ImageSharp.Formats; 19 | using SmartImage.Lib; 20 | using SmartImage.Lib.Images; 21 | using SmartImage.Lib.Images.Uni; 22 | using Spectre.Console; 23 | using Spectre.Console.Cli; 24 | using SmartImage.Rdx.Shell; 25 | using SmartImage.Rdx.Utilities; 26 | using SmartImage.Rdx.Commands; 27 | using SmartImage.Lib.Utilities.Integration; 28 | 29 | namespace SmartImage.Rdx; 30 | 31 | public static class Program 32 | { 33 | 34 | public static async Task Main(string[] args) 35 | { 36 | /*AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => 37 | { 38 | Trace.WriteLine($"{sender} -> {eventArgs}"); 39 | };*/ 40 | 41 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 42 | 43 | #if DEBUG 44 | 45 | // Debugger.Launch(); 46 | #endif 47 | HandleArgs(ref args); 48 | 49 | await DisplayHeaderAsync(); 50 | 51 | DisplayInfoGrid(); 52 | 53 | var app = new CommandApp(); 54 | 55 | app.Configure(c => 56 | { 57 | #if DEBUG 58 | c.PropagateExceptions(); 59 | c.ValidateExamples(); 60 | #endif 61 | 62 | var helpProvider = new CustomHelpProvider(c.Settings); 63 | c.SetHelpProvider(helpProvider); 64 | 65 | c.AddCommand("integrate") 66 | .WithDescription("Configure system integration such as context menu"); 67 | 68 | c.AddCommand("server") 69 | .WithDescription("Start listen server"); 70 | }); 71 | 72 | int x = BaseOSIntegration.EC_OK; 73 | 74 | try { 75 | x = await app.RunAsync(args); 76 | 77 | } 78 | catch (Exception e) { 79 | AnsiConsole.WriteException(e); 80 | x = BaseOSIntegration.EC_ERROR; 81 | } 82 | finally { 83 | 84 | if (x != BaseOSIntegration.EC_OK) { 85 | AnsiConsole.Confirm("Press any key to continue"); 86 | } 87 | } 88 | 89 | return x; 90 | } 91 | 92 | private static void DisplayInfoGrid() 93 | { 94 | Grid grd = ConsoleFormat.MapToGrid(ConsoleFormat.InfoMap); 95 | AnsiConsole.Write(grd); 96 | } 97 | 98 | private static async Task DisplayHeaderAsync() 99 | { 100 | var ff = ConsoleFormat.LoadFigletFontFromResource(nameof(R2.Fg_larry3d), out var ms); 101 | 102 | var fg = new FigletText(ff, R1.Name) 103 | .LeftJustified() 104 | .Color(ConsoleFormat.Clr_Misc1); 105 | await ms.DisposeAsync(); 106 | 107 | AnsiConsole.Write(fg); 108 | } 109 | 110 | private static void HandleArgs(ref string[] args) 111 | { 112 | if (args.Length == 0) { 113 | 114 | // todo 115 | 116 | /*if (Clipboard.Open()) { 117 | /*var hasBmp = Clipboard.IsFormatAvailable((uint) ClipboardFormat.CF_BITMAP); 118 | 119 | if (hasBmp) { 120 | var data = (nint) Clipboard.GetData((uint) ClipboardFormat.CF_BITMAP); 121 | var sz = Native.GlobalSize(data); 122 | var buf = new byte[sz]; 123 | Marshal.Copy(data, buf, 0, (int) sz); 124 | var mg = await Image.LoadAsync(new MemoryStream(buf)); 125 | 126 | }#1# 127 | 128 | var hasFileName = Clipboard.IsFormatAvailable((uint) ClipboardFormat.FileNameW); 129 | 130 | }*/ 131 | 132 | // var s = AnsiConsole.Ask("..."); 133 | 134 | } 135 | 136 | /*if (args.Length == 0) { 137 | var prompt = new TextPrompt("Input") 138 | { 139 | Converter = s => 140 | { 141 | /* 142 | var task = SearchQuery.TryCreateAsync(s); 143 | task.Wait(); 144 | var res = task.Result; 145 | #1# 146 | 147 | if (UniImage.IsValidSourceType(s)) { 148 | // var sq = SearchQuery.TryCreateAsync(s).Result; 149 | 150 | return s; 151 | } 152 | 153 | else { 154 | return null; 155 | } 156 | } 157 | }; 158 | var sz = AnsiConsole.Prompt(prompt); 159 | 160 | args = [sz]; 161 | }*/ 162 | if (Console.IsInputRedirected) { 163 | Trace.WriteLine("Input redirected"); 164 | var pipeInput = ConsoleUtil.ParseInputStream(); 165 | 166 | var newArgs = new string[args.Length + 1]; 167 | newArgs[0] = pipeInput; 168 | args.CopyTo(newArgs, 1); 169 | 170 | args = newArgs; 171 | 172 | AnsiConsole.WriteLine($"Received input from stdin"); 173 | } 174 | } 175 | 176 | 177 | private static IConfigurationRoot GetConfig() 178 | { 179 | /*var bldr2 = new ConfigurationBuilder(); 180 | var host = Host.CreateDefaultBuilder(); 181 | var bldr = host.ConfigureServices((ctx, svc) => { svc.AddSingleton(); }); 182 | 183 | bldr2.SetBasePath(Directory.GetCurrentDirectory()) 184 | .AddJsonFile("smartimage.json", optional: false, reloadOnChange: true); 185 | */ 186 | 187 | // TODO 188 | 189 | var currentDirectory = Directory.GetCurrentDirectory(); 190 | var configFileName = $"{R1.Name}.json"; 191 | var configFilePath = Path.Combine(currentDirectory, configFileName); 192 | 193 | var cfg = new ConfigurationBuilder() 194 | .AddJsonFile(configFileName) 195 | .Build(); 196 | 197 | return cfg; 198 | } 199 | 200 | public static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); 201 | 202 | public static readonly Version Version = Assembly.GetName().Version; 203 | 204 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SmartImage.Rdx { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SmartImage.Rdx.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to exit. 65 | /// 66 | internal static string Chc_Exit { 67 | get { 68 | return ResourceManager.GetString("Chc_Exit", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to open. 74 | /// 75 | internal static string Chc_Open { 76 | get { 77 | return ResourceManager.GetString("Chc_Open", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to scan. 83 | /// 84 | internal static string Chc_Scan { 85 | get { 86 | return ResourceManager.GetString("Chc_Scan", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized resource of type System.Byte[]. 92 | /// 93 | internal static byte[] Fg_Cybermedium { 94 | get { 95 | object obj = ResourceManager.GetObject("Fg_Cybermedium", resourceCulture); 96 | return ((byte[])(obj)); 97 | } 98 | } 99 | 100 | /// 101 | /// Looks up a localized resource of type System.Byte[]. 102 | /// 103 | internal static byte[] Fg_larry3d { 104 | get { 105 | object obj = ResourceManager.GetObject("Fg_larry3d", resourceCulture); 106 | return ((byte[])(obj)); 107 | } 108 | } 109 | 110 | /// 111 | /// Looks up a localized resource of type System.Byte[]. 112 | /// 113 | internal static byte[] Fg_SantaClara { 114 | get { 115 | object obj = ResourceManager.GetObject("Fg_SantaClara", resourceCulture); 116 | return ((byte[])(obj)); 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /SmartImage.Rdx/Resources/Cybermedium.flf: -------------------------------------------------------------------------------- 1 | flf2a$ 4 3 8 -1 20 2 | Cyberfont - medium 3 | Figlet conversion by Kent Nassen, kentn@cyberspace.org, 8-11-94 4 | From: stock@fwi.uva.nl (Lennert Stock) 5 | Date: 15 Jul 1994 00:04:25 GMT 6 | 7 | Here are some fonts. Non-figlet I'm afraid, if you wanna convert them, be 8 | my guest. I posted the isometric fonts before. 9 | 10 | ------------------------------------------------------------------------------ 11 | 12 | .x%%%%%%x. .x%%%%%%x. 13 | ,%%%%%%%%%%. .%%%%%%%%%%. 14 | ,%%%' )' \) :( `( `%%%. 15 | ,%x%)________) --------- L e n n e r t S t o c k ( _ __ (%x%. 16 | (%%%~^88P~88P| |~=> .=-~ %%%) 17 | (%%::. .:,\ .' `. /,:. .::%%) 18 | `;%:`\. `-' | | `-' ./':%:' 19 | ``x`. -===.' stock@fwi.uva.nl -------- `.===- .'x'' 20 | / `:`.__.; :.__.':' \ 21 | .d8b. ..`. .'.. .d8b. 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 | @ 56 | @ 57 | @@ 58 | @ 59 | @ 60 | @ 61 | @@ 62 | @ 63 | @ 64 | @ 65 | @@ 66 | @ 67 | @ 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 | @ 99 | @ 100 | @ 101 | @@ 102 | @ 103 | @ 104 | @ 105 | @@ 106 | @ 107 | @ 108 | @ 109 | @@ 110 | @ 111 | @ 112 | @ 113 | @@ 114 | @ 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 | @ 140 | @ 141 | @@ 142 | @ 143 | @ 144 | @ 145 | @@ 146 | __.@ 147 | _]@ 148 | . @ 149 | @@ 150 | @ 151 | @ 152 | @ 153 | @@ 154 | ____ @ 155 | |__| @ 156 | | | @ 157 | @@ 158 | ___ @ 159 | |__] @ 160 | |__] @ 161 | @@ 162 | ____ @ 163 | | @ 164 | |___ @ 165 | @@ 166 | ___ @ 167 | | \ @ 168 | |__/ @ 169 | @@ 170 | ____ @ 171 | |___ @ 172 | |___ @ 173 | @@ 174 | ____ @ 175 | |___ @ 176 | | @ 177 | @@ 178 | ____ @ 179 | | __ @ 180 | |__] @ 181 | @@ 182 | _ _ @ 183 | |__| @ 184 | | | @ 185 | @@ 186 | _ @ 187 | | @ 188 | | @ 189 | @@ 190 | _ @ 191 | | @ 192 | _| @ 193 | @@ 194 | _ _ @ 195 | |_/ @ 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 | @ 269 | @@ 270 | @ 271 | @ 272 | @ 273 | @@ 274 | @ 275 | @ 276 | ___ @ 277 | @@ 278 | . @ 279 | ` @ 280 | @ 281 | @@ 282 | ____ @ 283 | |__| @ 284 | | | @ 285 | @@ 286 | ___ @ 287 | |__] @ 288 | |__] @ 289 | @@ 290 | ____ @ 291 | | @ 292 | |___ @ 293 | @@ 294 | ___ @ 295 | | \ @ 296 | |__/ @ 297 | @@ 298 | ____ @ 299 | |___ @ 300 | |___ @ 301 | @@ 302 | ____ @ 303 | |___ @ 304 | | @ 305 | @@ 306 | ____ @ 307 | | __ @ 308 | |__] @ 309 | @@ 310 | _ _ @ 311 | |__| @ 312 | | | @ 313 | @@ 314 | _ @ 315 | | @ 316 | | @ 317 | @@ 318 | _ @ 319 | | @ 320 | _| @ 321 | @@ 322 | _ _ @ 323 | |_/ @ 324 | | \_ @ 325 | @@ 326 | _ @ 327 | | @ 328 | |___ @ 329 | @@ 330 | _ _ @ 331 | |\/| @ 332 | | | @ 333 | @@ 334 | _ _ @ 335 | |\ | @ 336 | | \| @ 337 | @@ 338 | ____ @ 339 | | | @ 340 | |__| @ 341 | @@ 342 | ___ @ 343 | |__] @ 344 | | @ 345 | @@ 346 | ____ @ 347 | | | @ 348 | |_\| @ 349 | @@ 350 | ____ @ 351 | |__/ @ 352 | | \ @ 353 | @@ 354 | ____ @ 355 | [__ @ 356 | ___] @ 357 | @@ 358 | ___ @ 359 | | @ 360 | | @ 361 | @@ 362 | _ _ @ 363 | | | @ 364 | |__| @ 365 | @@ 366 | _ _ @ 367 | | | @ 368 | \/ @ 369 | @@ 370 | _ _ _ @ 371 | | | | @ 372 | |_|_| @ 373 | @@ 374 | _ _ @ 375 | \/ @ 376 | _/\_ @ 377 | @@ 378 | _ _ @ 379 | \_/ @ 380 | | @ 381 | @@ 382 | ___ @ 383 | / @ 384 | /__ @ 385 | @@ 386 | @ 387 | @ 388 | @ 389 | @@ 390 | | @ 391 | | @ 392 | | @ 393 | | @@ 394 | @ 395 | @ 396 | @ 397 | @@ 398 | @ 399 | @ 400 | @ 401 | @@ 402 | @ 403 | @ 404 | @ 405 | @@ 406 | @ 407 | @ 408 | @ 409 | @@ 410 | @ 411 | @ 412 | @ 413 | @@ 414 | @ 415 | @ 416 | @ 417 | @@ 418 | @ 419 | @ 420 | @ 421 | @@ 422 | @ 423 | @ 424 | @ 425 | @@ 426 | @ 427 | @ 428 | @ 429 | @@ 430 | -------------------------------------------------------------------------------- /SmartImage.Rdx/Shell/ColorUtil.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: ColorUtil.cs 2 | // Date: 2024/04/24 @ 21:04:10 3 | 4 | using Spectre.Console; 5 | 6 | namespace SmartImage.Rdx.Shell; 7 | 8 | internal static class ColorUtil 9 | { 10 | 11 | public const double BYTE_D = 255.0; 12 | 13 | private const double LUM_DELTA = 0.05; 14 | 15 | public static double GetLuminance(this Color c) 16 | { 17 | return 0.2126 * c.R / BYTE_D + 0.7152 * c.G / BYTE_D + 0.0722 * c.B / BYTE_D; 18 | } 19 | 20 | public static double GetContrastRatio(this Color color1, Color color2) 21 | { 22 | double luminance1 = color1.GetLuminance(); 23 | double luminance2 = color2.GetLuminance(); 24 | 25 | if (luminance1 > luminance2) 26 | return (luminance1 + LUM_DELTA) / (luminance2 + LUM_DELTA); 27 | else 28 | return (luminance2 + LUM_DELTA) / (luminance1 + LUM_DELTA); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Shell/ConsoleUtil.cs: -------------------------------------------------------------------------------- 1 | global using STable = Spectre.Console.Table; 2 | global using DTable = System.Data.DataTable; 3 | using System.Data; 4 | using Kantan.Utilities; 5 | using Spectre.Console; 6 | using Spectre.Console.Cli; 7 | using Spectre.Console.Rendering; 8 | 9 | // $User.Name $File.ProjectName $File.FileName 10 | // $File.CreatedYear-$File.CreatedMonth-$File.CreatedDay @ $File.CreatedHour:$File.CreatedMinute 11 | 12 | namespace SmartImage.Rdx.Shell; 13 | 14 | [Flags] 15 | public enum OutputFields 16 | { 17 | 18 | None = 0, 19 | 20 | Name = 1 << 0, 21 | Url = 1 << 1, 22 | Similarity = 1 << 2, 23 | Artist = 1 << 3, 24 | Site = 1 << 4, 25 | 26 | // Default = Name | Url | Similarity 27 | 28 | } 29 | 30 | public enum OutputFileFormat 31 | { 32 | 33 | None = 0, 34 | Delimited, 35 | 36 | } 37 | 38 | internal static class ConsoleUtil 39 | { 40 | 41 | private static readonly byte[] s_utf8BomSig = 42 | [ 43 | 0xEF, 0xBB, 0xBF 44 | ]; 45 | 46 | public static string ParseInputStream(int bufSize = 4096, int maxSize = 10_000_000) 47 | { 48 | string path = null; 49 | 50 | using Stream stdin = Console.OpenStandardInput(); 51 | 52 | var buffer = new byte[bufSize]; 53 | var buffer2 = new byte[maxSize]; 54 | int bytesRead; 55 | int iter = 0; 56 | int b2pos = 0; 57 | 58 | while ((bytesRead = stdin.Read(buffer, 0, buffer.Length)) > 0) { 59 | if (iter == 0) { 60 | 61 | if (buffer[0] == s_utf8BomSig[0] 62 | && buffer[1] == s_utf8BomSig[1] 63 | && buffer[2] == s_utf8BomSig[2]) { 64 | 65 | buffer = buffer[3..]; 66 | bytesRead -= s_utf8BomSig.Length; 67 | } 68 | } 69 | 70 | Array.Copy(buffer, 0, buffer2, b2pos, bytesRead); 71 | b2pos += bytesRead; 72 | 73 | iter++; 74 | 75 | // prog?.Report(b2pos); 76 | } 77 | 78 | if (buffer2[(b2pos - 1)] == '\n' && buffer2[(b2pos - 2)] == '\r') { 79 | b2pos -= 2; 80 | } 81 | 82 | Array.Resize(ref buffer2, b2pos); 83 | 84 | var s = Console.InputEncoding.GetString(buffer2); 85 | 86 | if (File.Exists(s)) { 87 | path = s; 88 | } 89 | else { 90 | path = Path.GetTempFileName(); 91 | File.WriteAllBytes(path, buffer2); 92 | } 93 | 94 | return path; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/Shell/CustomHelpProvider.cs: -------------------------------------------------------------------------------- 1 | // Author: Deci | Project: SmartImage.Rdx | Name: CustomHelpProvider.cs 2 | // Date: 2024/04/10 @ 18:04:50 3 | 4 | using Spectre.Console; 5 | using Spectre.Console.Cli; 6 | using Spectre.Console.Cli.Help; 7 | using Spectre.Console.Rendering; 8 | 9 | namespace SmartImage.Rdx.Shell; 10 | 11 | internal class CustomHelpProvider : HelpProvider 12 | { 13 | 14 | public CustomHelpProvider(ICommandAppSettings settings) 15 | : base(settings) { } 16 | 17 | public override IEnumerable GetUsage(ICommandModel model, ICommandInfo? command) 18 | { 19 | var usage = base.GetUsage(model, command); 20 | return usage; 21 | } 22 | 23 | /*public override IEnumerable GetExamples(ICommandModel model, ICommandInfo? command) 24 | { 25 | return 26 | [ 27 | new Text( 28 | "smartimage \"C:\\Users\\Deci\\Pictures\\Epic anime\\Kallen_FINAL_1-3.png\" --search-engines All --output-format \"Delimited\" --output-file \"output.csv\" --read-cookies") 29 | ]; 30 | 31 | return base.GetExamples(model, command); 32 | }*/ 33 | 34 | public override IEnumerable GetDescription(ICommandModel model, ICommandInfo? command) 35 | { 36 | return new[] 37 | { 38 | Text.NewLine, 39 | new Text("DESCRIPTION:", new Style(Color.Yellow, decoration: Decoration.Bold)), Text.NewLine, 40 | new Text($" Homepage: {R1.Url_Repo}", new Style(link: R1.Url_Repo)), Text.NewLine, 41 | new Text($" Wiki: {R1.Url_Wiki}", new Style(link: R1.Url_Wiki)), Text.NewLine, 42 | Text.NewLine, 43 | Text.NewLine, 44 | }; 45 | } 46 | 47 | public override IEnumerable GetFooter(ICommandModel model, ICommandInfo? command) 48 | { 49 | return base.GetFooter(model, command); 50 | } 51 | 52 | /*public override IEnumerable GetHeader(ICommandModel model, ICommandInfo? command) 53 | { 54 | 55 | switch (command) { 56 | case null: 57 | 58 | break; 59 | } 60 | 61 | return [Text.Empty]; 62 | }*/ 63 | 64 | } -------------------------------------------------------------------------------- /SmartImage.Rdx/SmartImage.Rdx.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | JETBRAINS_ANNOTATIONS;TRACE 9 | True 10 | True 11 | Read Stanton 12 | https://github.com/Decimation/SmartImage 13 | https://github.com/Decimation/SmartImage 14 | SmartImage 15 | true 16 | Debug;Release;Test;UnitTest 17 | 18 | 19 | 20 | 21 | en-US;en 22 | 1.0.9 23 | Icon.ico 24 | 25 | 26 | DEBUG;TRACE;JETBRAINS_ANNOTATIONS 27 | 28 | 29 | 30 | DEBUG;TRACE;JETBRAINS_ANNOTATIONS;TEST 31 | 32 | 33 | 34 | TRACE;JETBRAINS_ANNOTATIONS 35 | 36 | 37 | 38 | 39 | 40 | DEBUG;TRACE;JETBRAINS_ANNOTATIONS;TEST 41 | 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 | all 73 | runtime; build; native; contentfiles; analyzers; buildtransitive 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | ..\..\..\VSProjects\FlareSolverrSharp\src\FlareSolverrSharp\bin\Release\net9.0\FlareSolverrSharp.dll 91 | 92 | 93 | ..\..\..\VSProjects\Kantan\Kantan\bin\Release\net9.0\Kantan.dll 94 | 95 | 96 | ..\..\..\VSProjects\Kantan\Kantan.Net\bin\Release\net9.0\Kantan.Net.dll 97 | 98 | 99 | ..\..\..\VSProjects\Novus\Novus\bin\Release\net9.0\Novus.dll 100 | 101 | 106 | 107 | 108 | 109 | 110 | True 111 | True 112 | Resources.resx 113 | 114 | 115 | 116 | 117 | 118 | ResXFileCodeGenerator 119 | Resources.Designer.cs 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /SmartImage.Rdx/Utilities/TypeRegistrar.cs: -------------------------------------------------------------------------------- 1 | // Deci SmartImage.Rdx TypeRegistrar.cs 2 | // $File.CreatedYear-$File.CreatedMonth-26 @ 1:46 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Spectre.Console.Cli; 6 | 7 | namespace SmartImage.Rdx.Utilities; 8 | 9 | public sealed class TypeRegistrar : ITypeRegistrar 10 | { 11 | 12 | private readonly IServiceCollection _builder; 13 | 14 | public TypeRegistrar(IServiceCollection builder) 15 | { 16 | _builder = builder; 17 | } 18 | 19 | public ITypeResolver Build() 20 | { 21 | return new TypeResolver(_builder.BuildServiceProvider()); 22 | } 23 | 24 | public void Register(Type service, Type implementation) 25 | { 26 | _builder.AddSingleton(service, implementation); 27 | } 28 | 29 | public void RegisterInstance(Type service, object implementation) 30 | { 31 | _builder.AddSingleton(service, implementation); 32 | } 33 | 34 | public void RegisterLazy(Type service, Func func) 35 | { 36 | if (func is null) { 37 | throw new ArgumentNullException(nameof(func)); 38 | } 39 | 40 | _builder.AddSingleton(service, (provider) => func()); 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /SmartImage.Rdx/Utilities/TypeResolver.cs: -------------------------------------------------------------------------------- 1 | // Deci SmartImage.Rdx TypeResolver.cs 2 | // $File.CreatedYear-$File.CreatedMonth-26 @ 1:46 3 | 4 | using Spectre.Console.Cli; 5 | 6 | namespace SmartImage.Rdx.Utilities; 7 | 8 | public sealed class TypeResolver : ITypeResolver, IDisposable 9 | { 10 | 11 | private readonly IServiceProvider _provider; 12 | 13 | public TypeResolver(IServiceProvider provider) 14 | { 15 | _provider = provider ?? throw new ArgumentNullException(nameof(provider)); 16 | } 17 | 18 | public object? Resolve(Type? type) 19 | { 20 | if (type == null) { 21 | return null; 22 | } 23 | 24 | return _provider.GetService(type); 25 | } 26 | 27 | public void Dispose() 28 | { 29 | if (_provider is IDisposable disposable) { 30 | disposable.Dispose(); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /SmartImage.UI/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 38 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /SmartImage.UI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.IO.Pipes; 7 | using System.Threading; 8 | using System.Windows; 9 | using System.Windows.Interop; 10 | using JetBrains.Annotations; 11 | using Novus.Utilities; 12 | using Novus.Win32; 13 | 14 | #nullable disable 15 | namespace SmartImage.UI; 16 | 17 | /// 18 | /// Interaction logic for App.xaml 19 | /// 20 | public partial class App : Application 21 | { 22 | 23 | /// 24 | /// This identifier must be unique for each application. 25 | /// 26 | public const string SINGLE_GUID = "{910e8c27-ab31-4043-9c5d-1382707e6c93}"; 27 | 28 | public const string IPC_PIPE_NAME = "SIPC"; 29 | 30 | public const char ARGS_DELIM = '\0'; 31 | 32 | private static Mutex _singleMutex; 33 | 34 | public NamedPipeServerStream PipeServer { get; private set; } 35 | 36 | public Thread PipeThread { get; private set; } 37 | 38 | private void Application_Startup(object sender, StartupEventArgs startupArgs) 39 | { 40 | bool multipleInstances = false, pipeServer = true; 41 | 42 | var enumerator = startupArgs.Args.GetEnumerator(); 43 | using var unknown = enumerator as IDisposable; 44 | 45 | while (enumerator.MoveNext()) { 46 | var el = enumerator.Current; 47 | var els = el?.ToString(); 48 | 49 | switch (els) { 50 | case "-mi": 51 | multipleInstances = true; 52 | break; 53 | 54 | case "-nms": 55 | pipeServer = false; 56 | break; 57 | 58 | default: 59 | break; 60 | } 61 | } 62 | 63 | _singleMutex = new Mutex(true, SINGLE_GUID); 64 | var isOnlyInstance = _singleMutex.WaitOne(TimeSpan.Zero, true); 65 | 66 | if (multipleInstances || isOnlyInstance) { 67 | // Show main window 68 | StartupUri = new Uri("MainWindow.xaml", UriKind.Relative); 69 | 70 | // Release SingleInstance Mutex 71 | _singleMutex.ReleaseMutex(); 72 | 73 | if (pipeServer) { 74 | StartServer(); 75 | 76 | } 77 | } 78 | else { 79 | // Bring the already running application into the foreground 80 | // Native.PostMessage(0xffff, AppUtil.m_registerWindowMessage, 0, 0); 81 | SendMessage(startupArgs); 82 | 83 | Shutdown(); 84 | 85 | } 86 | 87 | } 88 | 89 | private static void SendMessage(StartupEventArgs e) 90 | { 91 | using var pipe = new NamedPipeClientStream(".", IPC_PIPE_NAME, PipeDirection.Out); 92 | 93 | using var stream = new StreamWriter(pipe); 94 | 95 | pipe.Connect(); 96 | 97 | foreach (var s in e.Args) { 98 | stream.WriteLine(s); 99 | } 100 | 101 | stream.Write($"{ARGS_DELIM}{ProcessHelper.GetParent().Id}"); 102 | 103 | } 104 | 105 | public delegate void PipeMessageCallback(string s); 106 | 107 | public event PipeMessageCallback OnPipeMessage; 108 | 109 | [DebuggerHidden] 110 | private void StartServer() 111 | { 112 | PipeServer = new NamedPipeServerStream(IPC_PIPE_NAME, PipeDirection.In); 113 | 114 | PipeThread = new Thread(Start) 115 | { 116 | IsBackground = true 117 | }; 118 | PipeThread.Start(); 119 | } 120 | 121 | [DebuggerHidden] 122 | private void Start() 123 | { 124 | while (true) { 125 | PipeServer.WaitForConnection(); 126 | var sr = new StreamReader(PipeServer); 127 | 128 | while (!sr.EndOfStream) { 129 | var v = sr.ReadLine(); 130 | OnPipeMessage?.Invoke(v); 131 | } 132 | 133 | PipeServer.Disconnect(); 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /SmartImage.UI/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /SmartImage.UI/Controls/AppComponents.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.UI AppControls.cs 2 | // 2023-07-23 @ 4:16 PM 3 | global using R4 = SmartImage.Lib.Serialization; 4 | global using R2 = SmartImage.UI.Resources; 5 | global using R1 = SmartImage.Lib.Resources; 6 | using System; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Reflection; 10 | using System.Runtime.CompilerServices; 11 | using System.Windows; 12 | using System.Windows.Media.Imaging; 13 | using JetBrains.Annotations; 14 | using SmartImage.Lib.Utilities; 15 | 16 | // ReSharper disable NullableWarningSuppressionIsUsed 17 | 18 | // ReSharper disable InconsistentNaming 19 | 20 | namespace SmartImage.UI.Controls; 21 | #nullable enable 22 | public static class AppComponents 23 | { 24 | 25 | public const int WIDTH = 20; 26 | public const int HEIGHT = 20; 27 | 28 | static AppComponents() { } 29 | 30 | public static BitmapImage LoadInline([CallerMemberName] string? name = null, int w = WIDTH, int h = HEIGHT, 31 | string? ext = "png") 32 | => Load(Path.ChangeExtension(name, ext)!, w, h); 33 | 34 | public static BitmapImage Load(string name, int w = WIDTH, int h = HEIGHT) 35 | { 36 | var bmp = new BitmapImage() 37 | { }; 38 | bmp.BeginInit(); 39 | bmp.CacheOption = BitmapCacheOption.OnLoad; 40 | bmp.UriSource = GetComponentUri(name); 41 | bmp.EndInit(); 42 | bmp = bmp.ResizeBitmap(w, h); 43 | 44 | if (bmp.CanFreeze) { 45 | bmp.Freeze(); 46 | } 47 | 48 | return bmp; 49 | } 50 | 51 | public static Uri GetComponentUri(string n, string resources = "Resources") 52 | { 53 | return new Uri($"pack://application:,,,/{Assembly.GetExecutingAssembly().GetName().Name};component/{resources}/{n}"); 54 | } 55 | 56 | #region 57 | 58 | public static readonly BitmapImage accept = LoadInline(); 59 | 60 | public static readonly BitmapImage exclamation = LoadInline(); 61 | 62 | public static readonly BitmapImage help = LoadInline(); 63 | 64 | public static readonly BitmapImage information = LoadInline(); 65 | 66 | public static readonly BitmapImage picture = LoadInline(); 67 | public static readonly BitmapImage pictures = LoadInline(); 68 | 69 | public static readonly BitmapImage picture_save = LoadInline(); 70 | public static readonly BitmapImage picture_error = LoadInline(); 71 | public static readonly BitmapImage picture_empty = LoadInline(); 72 | public static readonly BitmapImage picture_link = LoadInline(); 73 | 74 | public static readonly BitmapImage artwork = LoadInline(); 75 | 76 | public static readonly BitmapImage image = LoadInline(); 77 | 78 | public static readonly BitmapImage image_link = LoadInline(); 79 | 80 | public static readonly BitmapImage link = LoadInline(); 81 | 82 | public static readonly BitmapImage arrow_refresh = LoadInline(); 83 | 84 | public static readonly BitmapImage clipboard_invoice = LoadInline(); 85 | 86 | public static readonly BitmapImage asterisk_yellow = LoadInline(); 87 | 88 | #endregion 89 | 90 | public static Bitmap BitmapImage2Bitmap(this BitmapImage bitmapImage) 91 | { 92 | // BitmapImage bitmapImage = new BitmapImage(new Uri("../Images/test.png", UriKind.Relative)); 93 | 94 | using (MemoryStream outStream = new MemoryStream()) { 95 | BitmapEncoder enc = new BmpBitmapEncoder(); 96 | enc.Frames.Add(BitmapFrame.Create(bitmapImage)); 97 | enc.Save(outStream); 98 | Bitmap bitmap = new Bitmap(outStream); 99 | 100 | return new Bitmap(bitmap); 101 | } 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/ControlsHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Numerics; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Input; 7 | using System.Windows.Media; 8 | using System.Windows.Media.Imaging; 9 | using Kantan.Utilities; 10 | using SmartImage.Lib.Engines; 11 | using SmartImage.Lib.Engines.Impl.Upload; 12 | using SmartImage.Lib.Images; 13 | using SmartImage.Lib.Images.Uni; 14 | 15 | namespace SmartImage.UI.Controls; 16 | 17 | public static class ControlsHelper 18 | { 19 | 20 | public static bool IsDoubleClick(this MouseButtonEventArgs e) 21 | { 22 | return e.ClickCount == 2; 23 | } 24 | 25 | public static BitmapImage ResizeBitmap(this BitmapImage originalBitmap, int newWidth, int newHeight) 26 | { 27 | // Calculate the scale factors for width and height 28 | double scaleX = (double) newWidth / originalBitmap.PixelWidth; 29 | double scaleY = (double) newHeight / originalBitmap.PixelHeight; 30 | 31 | // Create a new Transform to apply the scale factors 32 | Transform transform = new ScaleTransform(scaleX, scaleY); 33 | 34 | // Create a new TransformedBitmap with the original BitmapImage and the scale Transform 35 | var resizedBitmap = new TransformedBitmap(originalBitmap, transform); 36 | 37 | // Create a new BitmapImage and set it as the source of the resized image 38 | var bitmapImage = new BitmapImage(); 39 | bitmapImage.BeginInit(); 40 | 41 | if (originalBitmap.UriSource != null) { 42 | bitmapImage.UriSource = originalBitmap.UriSource; 43 | } 44 | 45 | else if (originalBitmap.StreamSource != null) { 46 | bitmapImage.StreamSource = originalBitmap.StreamSource; 47 | } 48 | 49 | bitmapImage.DecodePixelWidth = newWidth; 50 | bitmapImage.DecodePixelHeight = newHeight; 51 | bitmapImage.CacheOption = BitmapCacheOption.OnLoad; 52 | bitmapImage.EndInit(); 53 | 54 | return bitmapImage; 55 | } 56 | 57 | public static bool IsLoaded(this RoutedEventArgs e) 58 | => e is { Source: FrameworkElement { IsLoaded: true } fx }; 59 | 60 | public static void HandleEnum(this ListBox lb, T src) where T : struct, Enum 61 | { 62 | foreach (T t in lb.ItemsSource.OfType()) { 63 | if (src.HasFlag(t)) { 64 | lb.SelectedItems.Add(t); 65 | } 66 | else { 67 | lb.SelectedItems.Remove(t); 68 | } 69 | } 70 | } 71 | 72 | public static SearchEngineOptions HandleEnum(this ListBox lb, SelectionChangedEventArgs e, 73 | SearchEngineOptions orig) 74 | { 75 | // var rg = lb.ItemsSource.OfType().ToArray(); 76 | 77 | var ai = e.AddedItems.OfType() 78 | .Aggregate(default(SearchEngineOptions), Or); 79 | 80 | var ri = e.RemovedItems.OfType() 81 | .Aggregate(default(SearchEngineOptions), Or); 82 | 83 | var si = lb.SelectedItems.OfType().ToArray(); 84 | 85 | var siv = si.Aggregate(default(SearchEngineOptions), Or); 86 | 87 | orig &= siv; 88 | orig &= (~ri); 89 | orig |= ai; 90 | 91 | return orig; 92 | 93 | static SearchEngineOptions Or(SearchEngineOptions n, SearchEngineOptions l) => n | l; 94 | } 95 | 96 | 97 | public static string[] GetFilesFromDrop(this DragEventArgs e) 98 | { 99 | if (e.Data.GetDataPresent(DataFormats.FileDrop)) { 100 | 101 | if (e.Data.GetData(DataFormats.FileDrop, true) is string[] files 102 | && files.Any()) { 103 | 104 | return files; 105 | 106 | } 107 | } 108 | 109 | return []; 110 | } 111 | 112 | public static string FormatDescription(string name, UniImage uni, int? w, int? h) 113 | { 114 | string bytes = FormatSize(uni); 115 | 116 | string i; 117 | 118 | i = FormatDimensions(w, h); 119 | 120 | return $"{name} ⇉ [{(uni.HasImageFormat ? uni.ImageFormat : "?")}] [{bytes}] • {i}"; 121 | } 122 | 123 | public static string FormatSize(UniImage uni) 124 | { 125 | string bytes; 126 | 127 | if (!uni.Stream.CanRead) { 128 | bytes = "???"; 129 | } 130 | else 131 | bytes = FormatHelper.FormatBytes(uni.Stream.Length); 132 | 133 | return bytes; 134 | } 135 | 136 | public static (bool ctrl, bool alt, bool shift) GetModifiers() 137 | { 138 | var ctrl = Keyboard.Modifiers.HasFlag(ModifierKeys.Control); 139 | var alt = Keyboard.Modifiers.HasFlag(ModifierKeys.Alt); 140 | var shift = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift); 141 | return (ctrl, alt, shift); 142 | } 143 | 144 | public static string FormatDimensions(int? w, int? h) 145 | { 146 | if (w.HasValue && h.HasValue) { 147 | return $"{w}\u00d7{h}"; 148 | } 149 | 150 | return String.Empty; 151 | } 152 | 153 | internal const string STR_NA = "-"; 154 | 155 | public static readonly string[] UploadEngineNames = Enum.GetNames(); 156 | 157 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/BoolToValConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Data; 7 | using SmartImage.Lib.Results.Data; 8 | 9 | namespace SmartImage.UI.Controls.Converters; 10 | 11 | public class BoolToValueConverter : IValueConverter 12 | { 13 | 14 | public T FalseValue { get; set; } 15 | 16 | public T TrueValue { get; set; } 17 | 18 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 19 | { 20 | if (value == null) 21 | return FalseValue; 22 | 23 | return (bool) value ? TrueValue : FalseValue; 24 | } 25 | 26 | public object ConvertBack(object? value, Type targetType, object? parameter, 27 | CultureInfo culture) 28 | { 29 | return value != null ? value.Equals(TrueValue) : false; 30 | } 31 | 32 | } 33 | 34 | [ValueConversion(typeof(bool), typeof(string))] 35 | public class BoolToStringConverter : BoolToValueConverter { } 36 | 37 | public class BoolToBrushConverter : BoolToValueConverter { } 38 | 39 | public class BoolToVisibilityConverter : BoolToValueConverter { } 40 | 41 | [ValueConversion(typeof(bool), typeof(object))] 42 | public class BoolToObjectConverter : BoolToValueConverter { } 43 | 44 | public class EnumFlagsToStringConverter : IValueConverter 45 | { 46 | 47 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 48 | { 49 | string enumString; 50 | 51 | try { 52 | var x = value.ToString().Split(", "); 53 | StringBuilder sb = new(x.Length); 54 | 55 | foreach (var v in x) { 56 | sb.Append(v[0]); 57 | } 58 | 59 | return sb.ToString(); 60 | } 61 | catch { 62 | return string.Empty; 63 | } 64 | } 65 | 66 | // No need to implement converting back on a one-way binding 67 | public object ConvertBack(object? value, Type targetType, object? parameter, 68 | CultureInfo culture) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | } 74 | 75 | public class EnumToStringConverter : IValueConverter 76 | { 77 | 78 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 79 | { 80 | string enumString; 81 | 82 | try { 83 | enumString = Enum.GetName(value.GetType(), value); 84 | return enumString; 85 | } 86 | catch { 87 | return string.Empty; 88 | } 89 | } 90 | 91 | // No need to implement converting back on a one-way binding 92 | public object ConvertBack(object? value, Type targetType, object? parameter, 93 | CultureInfo culture) 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/BooleanToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.UI BooleanToBrushConverter.cs 2 | // 2023-08-20 @ 11:38 AM 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Windows.Data; 7 | using System.Windows.Media; 8 | 9 | namespace SmartImage.UI.Controls.Converters; 10 | 11 | #pragma warning disable CS8618 12 | [ValueConversion(typeof(bool), typeof(Brush))] 13 | public class BooleanToBrushConverter : IValueConverter 14 | { 15 | 16 | public Brush TrueBrush { get; set; } 17 | 18 | public Brush FalseBrush { get; set; } 19 | 20 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | if (value is bool boolValue) { 23 | return boolValue ? TrueBrush : FalseBrush; 24 | } 25 | 26 | return FalseBrush; 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/ImageDimensionConverter.cs: -------------------------------------------------------------------------------- 1 | using Kantan.Utilities; 2 | using Novus.Win32; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using SmartImage.Lib.Results.Data; 12 | using SmartImage.UI.Model; 13 | 14 | namespace SmartImage.UI.Controls.Converters; 15 | 16 | [ValueConversion(typeof(IBitmapImageSource), typeof(string))] 17 | public class ImageDimensionConverter : IValueConverter 18 | { 19 | 20 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 21 | { 22 | var val = (IBitmapImageSource) value; 23 | 24 | if (val == null) { 25 | return null; 26 | } 27 | 28 | string dim; 29 | 30 | int? w = null, h = null; 31 | 32 | if (val is { } ri) { 33 | if (ri is { IsThumbnail: true, HasImage: true, Image: { } i }) { 34 | w = i.PixelWidth; 35 | h = i.PixelHeight; 36 | } 37 | 38 | } 39 | else { 40 | w = val.Width; 41 | h = val.Height; 42 | 43 | } 44 | 45 | // w ??= val.Width; 46 | // h ??= val.Height; 47 | 48 | dim = ControlsHelper.FormatDimensions(w, h); 49 | 50 | return dim; 51 | } 52 | 53 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 54 | { 55 | return null; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/InvertableBooleanToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.UI InvertableBooleanToVisibilityConverter.cs 2 | // 2023-09-02 @ 3:46 AM 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Windows; 7 | using System.Windows.Data; 8 | 9 | namespace SmartImage.UI.Controls.Converters; 10 | 11 | [ValueConversion(typeof(bool), typeof(Visibility))] 12 | public class InvertableBooleanToVisibilityConverter : IValueConverter 13 | { 14 | public enum Parameters 15 | { 16 | Normal, Inverted 17 | } 18 | 19 | public object Convert(object value, Type targetType, 20 | object parameter, CultureInfo culture) 21 | { 22 | var boolValue = (bool)value; 23 | var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter); 24 | 25 | if (direction == Parameters.Inverted) 26 | return !boolValue ? Visibility.Visible : Visibility.Hidden; 27 | 28 | return boolValue ? Visibility.Visible : Visibility.Hidden; 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, 32 | object parameter, CultureInfo culture) 33 | { 34 | return null; 35 | } 36 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/UnitStringConverter.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 Kantan.Utilities; 9 | using Novus.Win32; 10 | 11 | namespace SmartImage.UI.Controls.Converters; 12 | 13 | [ValueConversion(typeof(long), typeof(string))] 14 | internal class UnitStringConverter : IValueConverter 15 | { 16 | 17 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 18 | { 19 | var val = (long) value; 20 | 21 | string bytes; 22 | 23 | if (val == Native.ERROR_SV) { 24 | bytes = "N/A"; 25 | } 26 | else bytes = FormatHelper.FormatBytes(val); 27 | 28 | return bytes; 29 | } 30 | 31 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 32 | { 33 | return null; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/Converters/UrlConverter.cs: -------------------------------------------------------------------------------- 1 | // Read S SmartImage.UI UrlConverter.cs 2 | // 2023-09-27 @ 3:20 AM 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Windows.Data; 7 | using Flurl; 8 | 9 | namespace SmartImage.UI.Controls.Converters; 10 | 11 | [ValueConversion(typeof(Url), typeof(string))] 12 | public class UrlConverter : IValueConverter 13 | { 14 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 15 | { 16 | if (value == null) 17 | { 18 | return null; 19 | } 20 | 21 | var date = (Url)value; 22 | return date.ToString(); 23 | } 24 | 25 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 26 | { 27 | if (value == null) 28 | { 29 | return null; 30 | } 31 | 32 | string strValue = value as string; 33 | Url resultDateTime; 34 | 35 | return (Url)strValue; 36 | } 37 | } -------------------------------------------------------------------------------- /SmartImage.UI/Controls/SharedImageControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SmartImage.UI/Controls/SharedImageControl.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 SmartImage.UI; 17 | 18 | /// 19 | /// Interaction logic for SharedImageControl.xaml 20 | /// 21 | public partial class SharedImageControl : UserControl 22 | { 23 | public SharedImageControl() 24 | { 25 | InitializeComponent(); 26 | } 27 | 28 | public static readonly DependencyProperty ImageSourceProperty = 29 | DependencyProperty.Register(nameof(ImageSource), typeof(ImageSource), typeof(SharedImageControl), 30 | new PropertyMetadata(null)); 31 | 32 | public ImageSource ImageSource 33 | { 34 | get { return (ImageSource) GetValue(ImageSourceProperty); } 35 | set { SetValue(ImageSourceProperty, value); } 36 | } 37 | } -------------------------------------------------------------------------------- /SmartImage.UI/HydrusWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 19 | 20 | 23 | 24 | 26 | 27 |