Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c7245f7
Organize Requests and Clients into subfolders; Rename CreateAlbumRequ…
ChaseDRedmon May 6, 2023
b27fca7
Condense error handling error by creating a Throw helper class for ex…
ChaseDRedmon May 6, 2023
00edc26
Organized request types by File, Url, or Album. Wrote some wrapper me…
ChaseDRedmon May 7, 2023
1ab3952
A little bit of work on the error handling using a HttpClient Delegat…
ChaseDRedmon May 9, 2023
2ec4198
Sealed record requests
ChaseDRedmon May 29, 2023
4f6f8e7
Implementation for uploading multiple files and creating an album tha…
ChaseDRedmon May 29, 2023
6885bbc
Added upload images to album method. Bump PolySharp version. Code for…
ChaseDRedmon Jun 10, 2023
002d110
Code organization, renaming classes for readability improvements. Twe…
ChaseDRedmon Jul 1, 2023
f1ea172
Const string
ChaseDRedmon Jul 1, 2023
4a64296
Add BetterEnum source generator to remove duplicated API String Param…
ChaseDRedmon Jul 2, 2023
8772547
Moved files
ChaseDRedmon Aug 23, 2023
392c111
Code organization, remove throw new and replace with Throw helper
ChaseDRedmon Aug 23, 2023
8e251c1
Remove CreateAlbum methods and consolidate request methods using AnyO…
ChaseDRedmon Aug 23, 2023
abf51a8
Remove UploadHost because I don't think I'll ever actually use this
ChaseDRedmon Aug 24, 2023
f5ab896
Documentation and exception messaging improvements
ChaseDRedmon Aug 24, 2023
9bdab1c
Consolidate null checks
ChaseDRedmon Aug 24, 2023
95a909d
refactor: consolidate base class naming conventions
ChaseDRedmon Nov 24, 2025
957c0a8
refactor: remove client interfaces and adopt concrete implementations
ChaseDRedmon Nov 24, 2025
ca1ebbd
refactor: reorganize Throw helper to Client namespace
ChaseDRedmon Nov 24, 2025
4a3c87c
refactor: update client implementations and request handling
ChaseDRedmon Nov 24, 2025
30a171e
refactor: modernize enum implementations with Intellenum
ChaseDRedmon Nov 24, 2025
af66012
refactor: improve exception handling and messaging
ChaseDRedmon Nov 24, 2025
cbdab31
refactor: update request models with enhanced validation
ChaseDRedmon Nov 24, 2025
4a4a1ea
chore: update project configuration and dependencies
ChaseDRedmon Nov 24, 2025
4ccac92
test: add comprehensive unit and integration tests
ChaseDRedmon Nov 24, 2025
c416d16
chore: update sample application with new API patterns
ChaseDRedmon Nov 24, 2025
00c012f
chore: migrate to XML-based solution file
ChaseDRedmon Nov 24, 2025
63a7b2a
chore: add IDE and workspace configuration
ChaseDRedmon Nov 24, 2025
4591c6a
docs: update README with API changes and migration guide
ChaseDRedmon Nov 24, 2025
861b666
Add qodana CI checks (#6)
qodana-cloud[bot] Nov 24, 2025
19a9a3e
Merge branch 'refs/heads/main' into feature/Albums
ChaseDRedmon Nov 24, 2025
9667abe
Update runner to run on version 5
ChaseDRedmon Nov 24, 2025
9f8b7d1
Use Intellenum TypeConverter
ChaseDRedmon Nov 24, 2025
9ae5c3e
Update qodana.yaml
ChaseDRedmon Nov 24, 2025
cd6e36a
Merge branch 'main' into feature/Albums
ChaseDRedmon Nov 24, 2025
346106e
Add slnx file
ChaseDRedmon Nov 24, 2025
4ca785d
Reformatting / re-run qodana
ChaseDRedmon Nov 24, 2025
69a5ba5
Add sln file back to satisfy qodana solution?
ChaseDRedmon Nov 24, 2025
06606b6
Re-run
ChaseDRedmon Nov 24, 2025
f18a3ef
fixed???
ChaseDRedmon Nov 24, 2025
23f6438
Test change 2
ChaseDRedmon Nov 24, 2025
428e0ad
fix .net 10
ChaseDRedmon Nov 24, 2025
452ed56
Merge branch 'main' into feature/Albums
ChaseDRedmon Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"permissions": {
"allow": [
"mcp__Rider__list_directory_tree",
"mcp__Rider__find_files_by_name_keyword",
"mcp__Rider__get_run_configurations",
"mcp__Rider__search_in_files_by_text",
"Bash(dotnet build:*)",
"mcp__fetch__fetch",
"Bash(dotnet test:*)",
"mcp__Rider__get_file_text_by_path",
"mcp__sequentialthinking__sequentialthinking",
"mcp__MicrosoftLearn__microsoft_docs_search",
"mcp__MicrosoftLearn__microsoft_docs_fetch",
"mcp__Rider__replace_text_in_file",
"Bash(dir:*)",
"WebSearch",
"Bash(dotnet --version:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:stackoverflow.com)",
"Bash(dotnet add:*)",
"mcp__MicrosoftLearn__microsoft_code_sample_search",
"Bash(del \"C:\\Coding\\CSharp\\Personal\\Libraries\\CatBox.NET\\src\\CatBox.NET\\Client\\Helpers.cs\")",
"Bash(git commit:*)",
"Bash(git reset:*)",
"Bash(git add:*)",
"Bash(dotnet new:*)",
"Bash(cat:*)"
],
"deny": [],
"ask": []
}
}
7 changes: 4 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

[*]
charset = utf-8-bom
end_of_line = crlf
trim_trailing_whitespace = false
insert_final_newline = false
indent_style = space
indent_size = 4

[{*.yml,*.json,*.xml,*.csproj,*.slnx}]
indent_size = 2

[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
Expand Down Expand Up @@ -75,7 +77,6 @@ resharper_web_config_module_not_resolved_highlighting = warning
resharper_web_config_type_not_resolved_highlighting = warning
resharper_web_config_wrong_module_highlighting = warning

[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 4
1 change: 1 addition & 0 deletions .github/workflows/qodana_code_quality.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Test Change 2
name: Qodana
on:
workflow_dispatch:
Expand Down
8 changes: 8 additions & 0 deletions .idea/.idea.CatBox.NET/.idea/projectSettingsUpdater.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "CatBox.NET.sln"
}
1 change: 0 additions & 1 deletion CatBox.NET.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatBox.NET", "src\CatBox.NET\CatBox.NET.csproj", "{CDEF8297-E053-495F-960C-F3EBAEA7E71F}"
EndProject
Expand Down
21 changes: 21 additions & 0 deletions CatBox.NET.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Solution>
<Folder Name="/samples/">
<Project Path="samples/SampleApp/SampleApp.csproj"/>
</Folder>
<Folder Name="/Solution/">
<File Path=".editorconfig"/>
<File Path=".github/workflows/release.yml"/>
<File Path=".github/workflows/qodana_code_quality.yml"/>
<File Path=".gitignore"/>
<File Path="qodana.yaml"/>
<File Path="CLAUDE.md"/>
<File Path="license.txt"/>
<File Path="readme.md"/>
</Folder>
<Folder Name="/src/">
<Project Path="src/CatBox.NET/CatBox.NET.csproj"/>
</Folder>
<Folder Name="/tests/">
<Project Path="tests/CatBox.Tests/CatBox.Tests.csproj"/>
</Folder>
</Solution>
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
}
}
5 changes: 4 additions & 1 deletion qodana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
####################################################################################################################

version: "1.0"
linter: jetbrains/qodana-cdnet:2025.2
linter: jetbrains/qodana-cdnet:latest
bootstrap: curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --jsonfile /data/project/global.json -i /usr/share/dotnet
dotnet:
solution: CatBox.NET.sln
profile:
name: qodana.recommended
include:
Expand Down
11 changes: 1 addition & 10 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,4 @@ CatBox.NET is a library for uploading images to the [CatBox.moe](https://catbox.

## Getting Started

Head over [to our wiki](https://github.com/ChaseDRedmon/CatBox.NET/wiki) to get started on how to use the library.

## Prerequisites
Change your Language Version to C# 11, by adding the following code to your `.csproj` file. Make sure the `<LangVersion>` tag is added under your `<PropertyGroup>`

```csharp
<PropertyGroup>
<LangVersion>11</LangVersion>
</PropertyGroup>
```
Head over [to our wiki](https://github.com/ChaseDRedmon/CatBox.NET/wiki) to get started on how to use the library.
175 changes: 154 additions & 21 deletions samples/SampleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,109 @@
// See https://aka.ms/new-console-template for more information

using CatBox.NET;
using CatBox.NET.Client;
using CatBox.NET.Enums;
using CatBox.NET.Requests.Album.Create;
using CatBox.NET.Requests.Album.Modify;
using CatBox.NET.Requests.File;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Microsoft.Extensions.Logging;

// Load configuration from user secrets
// To set your UserHash, run: dotnet user-secrets set "CatBox:UserHash" "your-hash-here" --project samples/SampleApp
// Get your UserHash from: https://catbox.moe/user/manage.php
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();

var userHash = configuration["CatBox:UserHash"]
?? throw new InvalidOperationException(
"CatBox UserHash not configured. Run: dotnet user-secrets set \"CatBox:UserHash\" \"your-hash-here\" --project samples/SampleApp");

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console()
.CreateLogger();
// Compute path to test image file within the project directory
string testImagePath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"..", "..", "..", "..", "..", // Navigate up from bin/Debug/net7.0
"tests", "CatBox.Tests", "Images", "test-file.png"
);
testImagePath = Path.GetFullPath(testImagePath); // Normalize the path
string testImageFileName = Path.GetFileName(testImagePath);

// Verify the test image file exists
if (!File.Exists(testImagePath))
{
throw new FileNotFoundException($"Test image not found at: {testImagePath}");
}

var collection = new ServiceCollection()
.AddCatBoxServices(f => f.CatBoxUrl = new Uri("https://catbox.moe/user/api.php"))
.AddLogging(f => f.AddSerilog(dispose: true))
.AddCatBoxServices(f =>
{
f.CatBoxUrl = new Uri("https://catbox.moe/user/api.php");
f.LitterboxUrl = new Uri("https://litterbox.catbox.moe/resources/internals/api.php");
})
.AddLogging(f => f.AddConsole())
.BuildServiceProvider();

// Upload a single image
// Store uploaded file URLs and album URL for cleanup
var uploadedFiles = new List<string>();
string? albumUrl = null;

// Upload a single image via stream
using (var scope = collection.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<ICatBoxClient>();
var responses = client.UploadFilesAsStreamAsync([new StreamUploadRequest
{
Stream = File.OpenRead(testImagePath),
FileName = testImageFileName,
UserHash = userHash
}]);

await foreach (var response in responses)
{
Console.WriteLine(response);
if (!string.IsNullOrWhiteSpace(response))
uploadedFiles.Add(response);
}
}

// Create an album of images already on Catbox
using (var scope = collection.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<ICatBoxClient>();
var response = await client.UploadImage(new StreamUploadRequest
var response = await client.CreateAlbumAsync(new RemoteCreateAlbumRequest
{
Stream = File.OpenRead(@"C:\Users\redmo\Documents\Anime\13c9a4.png"),
FileName = Path.GetFileName(@"C:\Users\redmo\Documents\Anime\13c9a4.png")
Title = "Album Title",
Description = "Album Description",
Files = uploadedFiles, // Use the actual uploaded file(s) from previous step
UserHash = userHash
});

Console.WriteLine(response);
albumUrl = response;
}

// Create an album of images already on Catbox
// Cleanup: Delete the album and uploaded files
using (var scope = collection.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<ICatBoxClient>();
var response = await client.CreateAlbum(new CreateAlbumRequest
await CleanupAsync(client, albumUrl, uploadedFiles, userHash);
}

return;

/*// Upload images to CatBox, then create an album on CatBox, then place the uploaded images into the newly created album
using (var scope = collection.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<ICatBox>();
var response = await client.CreateAlbumFromFilesAsync(new CreateAlbumRequest
{
Title = "Album Title",
Description = "Album Description",
Files = new [] { "1.jpg", }
UserHash = null,
UploadRequest = new FileUploadRequest
{
Files = [new FileInfo(testImagePath)]
}
});

Console.WriteLine(response);
Expand All @@ -45,14 +113,79 @@
using (var scope = collection.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<ILitterboxClient>();
var response = await client.UploadImage(new TemporaryStreamUploadRequest
var response = await client.UploadImageAsync(new TemporaryStreamUploadRequest
{
ExpireAfter = ExpireAfter.OneHour,
FileName = Path.GetFileName(@"C:\Users\redmo\Documents\Anime\13c9a4.png"),
Stream = File.OpenRead(@"C:\Users\redmo\Documents\Anime\13c9a4.png")
Expiry = ExpireAfter.OneHour,
FileName = testImageFileName,
Stream = File.OpenRead(testImagePath)
});

Console.WriteLine(response);

Console.WriteLine();
}

Console.ReadLine();
Console.ReadLine();*/

// Cleanup method to delete album and uploaded files
static async Task CleanupAsync(ICatBoxClient client, string? albumUrl, List<string> uploadedFiles, string userHash)
{
Console.WriteLine("\n--- Starting Cleanup ---");

// Delete the album first
if (!string.IsNullOrWhiteSpace(albumUrl))
{
try
{
// Extract album short ID from URL (e.g., "pd412w" from "https://catbox.moe/c/pd412w")
var albumUri = new Uri(albumUrl);
var albumId = albumUri.AbsolutePath.TrimStart('/').Replace("c/", "");

Console.WriteLine($"Deleting album: {albumId}");

var albumDeleteResponse = await client.ModifyAlbumAsync(new ModifyAlbumImagesRequest
{
Request = RequestType.DeleteAlbum,
UserHash = userHash,
AlbumId = albumId,
Files = [] // Empty for DeleteAlbum operation
});

Console.WriteLine($"Album deletion response: {albumDeleteResponse}");
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting album: {ex.Message}");
}
}

// Delete the uploaded files
if (uploadedFiles.Count > 0)
{
try
{
// Extract filenames from URLs (e.g., "8ce67f.jpg" from "https://files.catbox.moe/8ce67f.jpg")
var fileNames = uploadedFiles.Select(url =>
{
var uri = new Uri(url);
return Path.GetFileName(uri.AbsolutePath);
}).ToList();

Console.WriteLine($"Deleting {fileNames.Count} file(s): {string.Join(", ", fileNames)}");

var fileDeleteResponse = await client.DeleteMultipleFilesAsync(new DeleteFileRequest
{
UserHash = userHash,
FileNames = fileNames
});

Console.WriteLine($"File deletion response: {fileDeleteResponse}");
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting files: {ex.Message}");
}
}

Console.WriteLine("--- Cleanup Complete ---\n");
}
33 changes: 18 additions & 15 deletions samples/SampleApp/SampleApp.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<UserSecretsId>catbox-net-sample-app</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CatBox.NET" Version="0.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\CatBox.NET\CatBox.NET.csproj"/>
</ItemGroup>

</Project>
Loading
Loading