diff --git a/SnapX.Avalonia/Views/Settings/Views/BuiltInUploaderSettingsView.axaml.cs b/SnapX.Avalonia/Views/Settings/Views/BuiltInUploaderSettingsView.axaml.cs
index 51ff54058..014bcf1ae 100644
--- a/SnapX.Avalonia/Views/Settings/Views/BuiltInUploaderSettingsView.axaml.cs
+++ b/SnapX.Avalonia/Views/Settings/Views/BuiltInUploaderSettingsView.axaml.cs
@@ -83,6 +83,9 @@ private void UpdateSettingsView(object? instance)
GitHubGist => new GithubGistUploaderSettingsView() { DataContext = instance },
Hastebin => new HastebinUploaderSettingsView() { DataContext = instance },
OneTimeSecret => new OneTimeSecretUploadSettingsView() { DataContext = instance },
+ VoidedHostUploader => new ImageUploaders.VoidedHostUploaderSettingsView { DataContext = instance },
+ VoidedHostTextUploader => new ImageUploaders.VoidedHostUploaderSettingsView { DataContext = instance },
+ VoidedHostFileUploader => new ImageUploaders.VoidedHostUploaderSettingsView { DataContext = instance },
FTP or SFTP => new FTPSettingsView() { DataContext = instance },
// Add other mappings here:
// AmazonS3 => new S3SettingsView { DataContext = instance },
diff --git a/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml b/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml
new file mode 100644
index 000000000..dc5bb0fa9
--- /dev/null
+++ b/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml.cs b/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml.cs
new file mode 100644
index 000000000..0b1a3bf28
--- /dev/null
+++ b/SnapX.Avalonia/Views/Settings/Views/ImageUploaders/VoidedHostUploaderSettingsView.axaml.cs
@@ -0,0 +1,65 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using SnapX.Core.Upload.Img;
+using SnapX.Core.Utils;
+
+namespace SnapX.Avalonia.Views.Settings.Views.ImageUploaders;
+
+public partial class VoidedHostUploaderSettingsView : UserControl
+{
+ public VoidedHostUploaderSettingsView()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ if (DataContext is not (VoidedHostUploader or SnapX.Core.Upload.Text.VoidedHostTextUploader or SnapX.Core.Upload.File.VoidedHostFileUploader)) return;
+ var config = SnapX.Core.SnapXL.UploadersConfig;
+
+ bool guestKeyOk = VoidedHostUploader.IsGuestUploadKeyConfigured();
+ if (!guestKeyOk && config.VoidedHostUseGuest)
+ {
+ config.VoidedHostUseGuest = false;
+ }
+
+ cbUseGuest.IsChecked = guestKeyOk && config.VoidedHostUseGuest;
+ cbUseGuest.IsEnabled = guestKeyOk;
+ txtUploadKey.Text = config.VoidedHostUploadKey ?? "";
+
+ UpdateControlStates();
+
+ cbUseGuest.IsCheckedChanged += (_, _) =>
+ {
+ config.VoidedHostUseGuest = cbUseGuest.IsChecked == true;
+ UpdateControlStates();
+ };
+
+ txtUploadKey.TextChanged += (_, _) =>
+ {
+ config.VoidedHostUploadKey = txtUploadKey.Text ?? "";
+ };
+ }
+
+ private void UpdateControlStates()
+ {
+ bool guestKeyOk = VoidedHostUploader.IsGuestUploadKeyConfigured();
+ bool guest = guestKeyOk && cbUseGuest.IsChecked == true;
+
+ txtUploadKey.IsEnabled = !guest;
+ UploadKeyItem.IsEnabled = !guest;
+ ManageKeysItem.IsEnabled = !guest;
+ }
+
+ private void RegisterButton_OnClick(object? sender, RoutedEventArgs e)
+ {
+ URLHelpers.OpenURL(VoidedHostUploader.SnapXSetupRegisterUrl);
+ }
+
+ private void ManageKeysButton_OnClick(object? sender, RoutedEventArgs e)
+ {
+ URLHelpers.OpenURL(VoidedHostUploader.UploadKeySettingsUrl);
+ }
+}
diff --git a/SnapX.Avalonia/Views/Settings/Views/SettingsMainView.axaml b/SnapX.Avalonia/Views/Settings/Views/SettingsMainView.axaml
index 872a1184a..02981ed00 100644
--- a/SnapX.Avalonia/Views/Settings/Views/SettingsMainView.axaml
+++ b/SnapX.Avalonia/Views/Settings/Views/SettingsMainView.axaml
@@ -353,8 +353,6 @@
-
-
@@ -374,6 +372,17 @@
+
+
+
+
+
+
+
+
FileDestination.VoidedHost;
+
+ public override bool CheckConfig(UploadersConfig config) => VoidedHostUploader.IsUploadConfigured(config);
+
+ public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo)
+ {
+ bool useGuest = VoidedHostUploader.ShouldUseGuestMode(config);
+ string key = VoidedHostUploader.GetEffectiveUploadKey(config);
+
+ return new VoidedHostFileUploader(key, useGuest);
+ }
+}
+
+public sealed class VoidedHostFileUploader : FileUploader
+{
+ private readonly VoidedHostMultipartUploader _multipart;
+
+ public VoidedHostFileUploader(string uploadKey, bool useGuestMode)
+ {
+ _multipart = new VoidedHostMultipartUploader(uploadKey, useGuestMode, VoidedHostUploader.FileUploadApiUrl);
+ _multipart.ProgressChanged += OnProgressChanged;
+ _multipart.EarlyURLCopyRequested += OnEarlyURLCopyRequested;
+ }
+
+ [RequiresDynamicCode("Uploader")]
+ [RequiresUnreferencedCode("Uploader")]
+ public override UploadResult Upload(Stream stream, string? fileName)
+ {
+ Errors.Errors.Clear();
+ _multipart.BufferSize = BufferSize;
+ UploadResult result = _multipart.Upload(stream, fileName);
+ Errors.Add(_multipart.Errors);
+ return result;
+ }
+
+ public override void StopUpload()
+ {
+ _multipart.StopUpload();
+ base.StopUpload();
+ }
+}
diff --git a/SnapX.Core/Upload/Img/VoidedHostUploader.cs b/SnapX.Core/Upload/Img/VoidedHostUploader.cs
new file mode 100644
index 000000000..382bef86c
--- /dev/null
+++ b/SnapX.Core/Upload/Img/VoidedHostUploader.cs
@@ -0,0 +1,426 @@
+
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+
+using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
+using System.Net;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using SnapX.Core.Upload.BaseServices;
+using SnapX.Core.Upload.BaseUploaders;
+using SnapX.Core.Upload.Utils;
+
+namespace SnapX.Core.Upload.Img;
+
+public class VoidedHostImageUploaderService : ImageUploaderService
+{
+ public override ImageDestination EnumValue => ImageDestination.VoidedHost;
+
+ public override bool CheckConfig(UploadersConfig config) => VoidedHostUploader.IsUploadConfigured(config);
+
+ public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo)
+ {
+ bool useGuest = VoidedHostUploader.ShouldUseGuestMode(config);
+ string key = VoidedHostUploader.GetEffectiveUploadKey(config);
+
+ return new VoidedHostUploader(key, useGuest);
+ }
+}
+
+internal sealed class VoidedHostMultipartUploader : FileUploader
+{
+ private readonly string _uploadKey;
+ private readonly bool _useGuestMode;
+ private readonly string _apiUrl;
+
+ public VoidedHostMultipartUploader(string uploadKey, bool useGuestMode, string apiUrl)
+ {
+ _uploadKey = uploadKey ?? "";
+ _useGuestMode = useGuestMode;
+ _apiUrl = apiUrl ?? "";
+ }
+
+ [RequiresDynamicCode("Uploader")]
+ [RequiresUnreferencedCode("Uploader")]
+ public override UploadResult Upload(Stream stream, string? fileName)
+ {
+ var result = new UploadResult();
+ Errors.Errors.Clear();
+
+ DebugHelper.WriteLine($"[voided.host] Upload starting. ApiUrl={_apiUrl}, FileName={fileName}, GuestMode={_useGuestMode}, StreamLength={stream?.Length}, StreamPosition={stream?.Position}");
+
+ if (string.IsNullOrWhiteSpace(_uploadKey))
+ {
+ DebugHelper.WriteLine("[voided.host] Upload key is empty, aborting.");
+ if (_useGuestMode)
+ {
+ Errors.Add("Guest uploads aren't enabled in this build of SnapX. Turn off guest upload, then paste your own voided.host upload key (or use a build that includes guest uploads).");
+ }
+ else
+ {
+ Errors.Add("Paste your voided.host upload key in SnapX destinations, or turn on guest upload if your build supports it.");
+ }
+
+ return result;
+ }
+
+ var headers = new NameValueCollection
+ {
+ ["Authorization"] = _uploadKey.Trim()
+ };
+
+ var args = new Dictionary
+ {
+ ["p"] = "snapx",
+ ["v"] = "1",
+ ["t"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()
+ };
+
+ ReturnResponseOnError = true;
+ DebugHelper.WriteLine($"[voided.host] Sending request to {_apiUrl}");
+ result = SendRequestFile(_apiUrl, stream, fileName, "file", args, headers);
+
+ var statusCode = result.ResponseInfo?.StatusCode;
+ DebugHelper.WriteLine($"[voided.host] Response received. IsSuccess={result.IsSuccess}, StatusCode={statusCode}, ResponseLength={result.Response?.Length ?? 0}");
+
+ if (!string.IsNullOrEmpty(result.Response))
+ {
+ var preview = result.Response.Length > 500 ? result.Response[..500] + "..." : result.Response;
+ DebugHelper.WriteLine($"[voided.host] Response body: {preview}");
+ }
+
+ if (VoidedHostResponseParser.TryBuildUserFacingError(result.Response, out var structuredError))
+ {
+ DebugHelper.WriteLine($"[voided.host] API returned structured error: {structuredError}");
+ Errors.Errors.Clear();
+ Errors.Add(structuredError);
+ result.IsSuccess = false;
+ return result;
+ }
+
+ ApplyUnauthorizedRotationHint(result);
+
+ if (!result.IsSuccess || string.IsNullOrEmpty(result.Response))
+ {
+ DebugHelper.WriteLine($"[voided.host] Upload failed or empty response. IsSuccess={result.IsSuccess}, HasResponse={!string.IsNullOrEmpty(result.Response)}, ErrorCount={Errors.Count}");
+ return result;
+ }
+
+ if (!VoidedHostResponseParser.TryParseUploadResponse(result.Response, out var shareLink, out var deletionUrl, out var apiUserError))
+ {
+ DebugHelper.WriteLine($"[voided.host] Failed to parse upload response. ApiError={apiUserError}");
+ if (!string.IsNullOrEmpty(apiUserError))
+ {
+ Errors.Add(apiUserError);
+ }
+ else
+ {
+ Errors.Add("voided.host replied in an unexpected format. Try updating SnapX; if it keeps happening, forward the upload log to voided.host support.");
+ }
+
+ result.IsSuccess = false;
+ return result;
+ }
+
+ DebugHelper.WriteLine($"[voided.host] Upload successful. URL={shareLink}, DeletionURL={deletionUrl}");
+ result.URL = shareLink;
+ if (!string.IsNullOrWhiteSpace(deletionUrl))
+ {
+ result.DeletionURL = deletionUrl;
+ }
+
+ result.IsSuccess = true;
+ return result;
+ }
+
+ private void ApplyUnauthorizedRotationHint(UploadResult result)
+ {
+ var info = LastResponseInfo ?? result.ResponseInfo;
+ if (info?.StatusCode != HttpStatusCode.Unauthorized)
+ {
+ return;
+ }
+
+ DebugHelper.WriteLine($"[voided.host] 401 Unauthorized. GuestMode={_useGuestMode}");
+ Errors.Errors.Clear();
+
+ if (_useGuestMode)
+ {
+ Errors.Add("voided.host blocked guest uploads (access expired or changed). Turn off guest upload and paste your personal upload key, or install the latest SnapX. More help: " + VoidedHostUploader.UploadKeySettingsUrl);
+ }
+ else
+ {
+ Errors.Add("voided.host didn't accept your upload key—it may have been reset. Open your upload-key page, copy the current key, and paste it in SnapX: " + VoidedHostUploader.UploadKeySettingsUrl);
+ }
+ }
+}
+
+public sealed class VoidedHostUploader : ImageUploader
+{
+ ///
+ /// Upload key used when the user enables "Upload as guest". Must be provisioned by voided.host
+ /// for the shared guest account. Guest mode stays off in the UI and in CheckConfig until this is non-empty.
+ /// Omit from public repos — set at release build time only.
+ ///
+ public const string GuestUploadApiKey = "MTA3NQ.MTc3ODMzMzE1MDI3NA.wIeJFNAwaYymgvgMIhLCxRamvUXWtZMtSGmjQNZDfGDKGVqx";
+
+ public const string ImageUploadApiUrl = "https://api.voided.host/v2/images";
+ public const string PasteUploadApiUrl = "https://api.voided.host/v2/pastes";
+ public const string FileUploadApiUrl = "https://api.voided.host/v2/files";
+ public const string UploadKeySettingsUrl = "https://voided.host/settings/security";
+ public const string SnapXSetupRegisterUrl = "https://voided.host/register?inviter=Lixqa&next=/flows/sharex-uploader-setup/";
+ public const string ImageDeletionUrlFormat = "https://voided.host/settings/image/{0}";
+
+ private readonly VoidedHostMultipartUploader _multipart;
+
+ public string UploadKey { get; private set; }
+
+ public bool UseGuestMode { get; private set; }
+
+ public VoidedHostUploader(string uploadKey, bool useGuestMode)
+ {
+ UploadKey = uploadKey ?? "";
+ UseGuestMode = useGuestMode;
+ _multipart = new VoidedHostMultipartUploader(uploadKey, useGuestMode, ImageUploadApiUrl);
+ _multipart.ProgressChanged += OnProgressChanged;
+ _multipart.EarlyURLCopyRequested += OnEarlyURLCopyRequested;
+ }
+
+ public static bool IsUploadConfigured(UploadersConfig config)
+ {
+ if (config == null)
+ {
+ return false;
+ }
+
+ if (ShouldUseGuestMode(config))
+ {
+ return true;
+ }
+
+ return !string.IsNullOrWhiteSpace(config.VoidedHostUploadKey);
+ }
+
+ public static bool ShouldUseGuestMode(UploadersConfig config)
+ {
+ return config != null && config.VoidedHostUseGuest && IsGuestUploadKeyConfigured();
+ }
+
+ public static bool IsGuestUploadKeyConfigured()
+ {
+ return !string.IsNullOrWhiteSpace(GuestUploadApiKey);
+ }
+
+ public static string GetEffectiveUploadKey(UploadersConfig config)
+ {
+ if (ShouldUseGuestMode(config))
+ {
+ return GuestUploadApiKey.Trim();
+ }
+
+ return config?.VoidedHostUploadKey?.Trim() ?? "";
+ }
+
+ [RequiresDynamicCode("Uploader")]
+ [RequiresUnreferencedCode("Uploader")]
+ public override UploadResult Upload(Stream stream, string? fileName)
+ {
+ Errors.Errors.Clear();
+ _multipart.BufferSize = BufferSize;
+ UploadResult result = _multipart.Upload(stream, fileName);
+ Errors.Add(_multipart.Errors);
+ return result;
+ }
+
+ public override void StopUpload()
+ {
+ _multipart.StopUpload();
+ base.StopUpload();
+ }
+}
+
+[JsonSerializable(typeof(VoidedHostApiResponse))]
+internal partial class VoidedHostJsonContext : JsonSerializerContext;
+
+internal sealed class VoidedHostApiResponse
+{
+ [JsonPropertyName("error")]
+ public JsonElement? Error { get; set; }
+
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+
+ [JsonPropertyName("_errors")]
+ public List? ErrorDetails { get; set; }
+
+ [JsonPropertyName("data")]
+ public VoidedHostApiData? Data { get; set; }
+}
+
+internal sealed class VoidedHostApiErrorDetail
+{
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+}
+
+internal sealed class VoidedHostApiData
+{
+ [JsonPropertyName("shareLink")]
+ public string? ShareLink { get; set; }
+
+ [JsonPropertyName("id")]
+ public JsonElement? Id { get; set; }
+}
+
+internal static class VoidedHostResponseParser
+{
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ TypeInfoResolver = VoidedHostJsonContext.Default,
+ PropertyNameCaseInsensitive = true
+ };
+
+ public static bool TryBuildUserFacingError(string? json, out string? message)
+ {
+ message = null;
+
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ return false;
+ }
+
+ try
+ {
+ json = json.TrimStart();
+ if (json.Length == 0 || json[0] != '{')
+ {
+ return false;
+ }
+
+ var response = JsonSerializer.Deserialize(json, JsonOptions);
+ if (response == null || !IsTruthyError(response.Error))
+ {
+ return false;
+ }
+
+ var sb = new StringBuilder();
+
+ var main = response.Message?.Trim();
+ if (!string.IsNullOrEmpty(main))
+ {
+ sb.Append("voided.host: ").Append(main);
+ }
+
+ if (response.ErrorDetails != null)
+ {
+ foreach (var item in response.ErrorDetails)
+ {
+ var detail = item.Message?.Trim();
+ if (string.IsNullOrEmpty(detail))
+ {
+ continue;
+ }
+
+ if (string.Equals(detail, main, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (sb.Length > 0)
+ {
+ sb.Append(Environment.NewLine);
+ }
+
+ sb.Append(detail);
+ }
+ }
+
+ message = sb.Length > 0 ? sb.ToString() : "voided.host returned an error.";
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static bool IsTruthyError(JsonElement? errorElement)
+ {
+ if (errorElement == null || !errorElement.HasValue)
+ {
+ return false;
+ }
+
+ var tok = errorElement.Value;
+
+ return tok.ValueKind switch
+ {
+ JsonValueKind.Null or JsonValueKind.Undefined => false,
+ JsonValueKind.True => true,
+ JsonValueKind.False => false,
+ JsonValueKind.Number => tok.TryGetInt64(out var n) && n != 0,
+ JsonValueKind.String => tok.GetString() is { } s &&
+ !string.IsNullOrEmpty(s.Trim()) &&
+ (s.Equals("true", StringComparison.OrdinalIgnoreCase) ||
+ (int.TryParse(s, out var parsed) && parsed != 0)),
+ _ => false
+ };
+ }
+
+ public static bool TryParseUploadResponse(string? json, out string? shareLink, out string? deletionUrl, out string? errorMessage)
+ {
+ shareLink = null;
+ deletionUrl = null;
+ errorMessage = null;
+
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ return false;
+ }
+
+ try
+ {
+ var response = JsonSerializer.Deserialize(json, JsonOptions);
+ if (response == null)
+ {
+ return false;
+ }
+
+ if (IsTruthyError(response.Error))
+ {
+ if (!TryBuildUserFacingError(json, out errorMessage) || string.IsNullOrEmpty(errorMessage))
+ {
+ errorMessage = "voided.host returned an error.";
+ }
+
+ return false;
+ }
+
+ if (response.Data != null)
+ {
+ var sl = response.Data.ShareLink?.Trim();
+ if (!string.IsNullOrEmpty(sl))
+ {
+ shareLink = sl;
+ }
+
+ if (response.Data.Id is { } idElement && idElement.ValueKind != JsonValueKind.Null && idElement.ValueKind != JsonValueKind.Undefined)
+ {
+ var idPart = idElement.ToString();
+ if (!string.IsNullOrEmpty(idPart))
+ {
+ deletionUrl = string.Format(VoidedHostUploader.ImageDeletionUrlFormat, idPart);
+ }
+ }
+ }
+
+ return !string.IsNullOrEmpty(shareLink);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
diff --git a/SnapX.Core/Upload/Text/VoidedHostTextUploader.cs b/SnapX.Core/Upload/Text/VoidedHostTextUploader.cs
new file mode 100644
index 000000000..866174f25
--- /dev/null
+++ b/SnapX.Core/Upload/Text/VoidedHostTextUploader.cs
@@ -0,0 +1,66 @@
+
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using SnapX.Core.Upload.BaseServices;
+using SnapX.Core.Upload.BaseUploaders;
+using SnapX.Core.Upload.Img;
+using SnapX.Core.Upload.Utils;
+
+namespace SnapX.Core.Upload.Text;
+
+public class VoidedHostTextUploaderService : TextUploaderService
+{
+ public override TextDestination EnumValue => TextDestination.VoidedHost;
+
+ public override bool CheckConfig(UploadersConfig config) => VoidedHostUploader.IsUploadConfigured(config);
+
+ public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo)
+ {
+ bool useGuest = VoidedHostUploader.ShouldUseGuestMode(config);
+ string key = VoidedHostUploader.GetEffectiveUploadKey(config);
+
+ return new VoidedHostTextUploader(key, useGuest);
+ }
+}
+
+public sealed class VoidedHostTextUploader : TextUploader
+{
+ private readonly VoidedHostMultipartUploader _multipart;
+
+ public VoidedHostTextUploader(string uploadKey, bool useGuestMode)
+ {
+ _multipart = new VoidedHostMultipartUploader(uploadKey, useGuestMode, VoidedHostUploader.PasteUploadApiUrl);
+ _multipart.ProgressChanged += OnProgressChanged;
+ _multipart.EarlyURLCopyRequested += OnEarlyURLCopyRequested;
+ }
+
+ [RequiresDynamicCode("Uploader")]
+ [RequiresUnreferencedCode("Uploader")]
+ public override UploadResult UploadText(string? text, string? fileName)
+ {
+ Errors.Errors.Clear();
+ if (string.IsNullOrEmpty(fileName))
+ {
+ fileName = "paste.txt";
+ }
+ else if (!Path.HasExtension(fileName))
+ {
+ fileName += ".txt";
+ }
+
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text ?? ""));
+ _multipart.BufferSize = BufferSize;
+ UploadResult result = _multipart.Upload(stream, fileName);
+ Errors.Add(_multipart.Errors);
+ return result;
+ }
+
+ public override void StopUpload()
+ {
+ _multipart.StopUpload();
+ base.StopUpload();
+ }
+}
diff --git a/SnapX.Core/Upload/UploaderCategory.cs b/SnapX.Core/Upload/UploaderCategory.cs
index c51d650f2..32908fa11 100644
--- a/SnapX.Core/Upload/UploaderCategory.cs
+++ b/SnapX.Core/Upload/UploaderCategory.cs
@@ -8,6 +8,8 @@ namespace SnapX.Core.Upload;
///
public enum UploaderCategory
{
+ [Description("Universal uploaders")]
+ UniversalUploaders,
[Description("Image uploaders")]
ImageUploaders,
[Description("Text uploaders")]
diff --git a/SnapX.Core/Upload/UploaderFactory.cs b/SnapX.Core/Upload/UploaderFactory.cs
index 3c4a2e6e6..0904f361b 100644
--- a/SnapX.Core/Upload/UploaderFactory.cs
+++ b/SnapX.Core/Upload/UploaderFactory.cs
@@ -102,6 +102,7 @@ static UploaderFactory()
Register(new SendSpaceFileUploaderService());
Register(new SulFileUploaderService());
Register(new Vault_oooFileUploaderService());
+ Register(new VoidedHostFileUploaderService());
Register(new GooglePhotosImageUploaderService());
Register(new CheveretoImageUploaderService());
Register(new CustomImageUploaderService());
@@ -109,6 +110,7 @@ static UploaderFactory()
Register(new ImageShackImageUploaderService());
Register(new ImgurImageUploaderService());
Register(new VgymeImageUploaderService());
+ Register(new VoidedHostImageUploaderService());
Register(new CustomTextUploaderService());
Register(new GitHubGistTextUploaderService());
Register(new HastebinTextUploaderService());
@@ -116,6 +118,7 @@ static UploaderFactory()
Register(new Paste2TextUploaderService());
Register(new Paste_eeTextUploaderService());
Register(new PastebinTextUploaderService());
+ Register(new VoidedHostTextUploaderService());
Register(new YourlsURLShortenerService());
Register(new ZeroWidthURLShortenerService());
Register(new VURLShortenerService());
diff --git a/SnapX.Core/Upload/UploadersConfig.cs b/SnapX.Core/Upload/UploadersConfig.cs
index 8cb628f19..5e9697d9c 100644
--- a/SnapX.Core/Upload/UploadersConfig.cs
+++ b/SnapX.Core/Upload/UploadersConfig.cs
@@ -65,6 +65,16 @@ public class UploadersConfig : SettingsBase
#endregion vgy.me
+ #region voided.host
+
+ [JsonEncrypt]
+ [YamlEncrypt]
+ public string VoidedHostUploadKey { get; set; } = "";
+
+ public bool VoidedHostUseGuest { get; set; } = true;
+
+ #endregion voided.host
+
#endregion Image uploaders
#region Text uploaders