Initial commit
This commit is contained in:
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Build outputs
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.user
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.userprefs
|
||||||
|
*.csproj.user
|
||||||
|
*.vbproj.user
|
||||||
|
[Test]Results*/
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.appx
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
.nuget/packages/
|
||||||
|
|
||||||
|
# Logs and diagnostics
|
||||||
|
*.log
|
||||||
|
*.trx
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# OS artifacts
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": ".NET Debug",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/net10.0/FsTool.dll",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
.vscode/tasks.json
vendored
Normal file
24
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "shell",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
// Ask dotnet build to generate full paths for file names.
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "silent"
|
||||||
|
},
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
125
AuthHelper.cs
Normal file
125
AuthHelper.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using System.Dynamic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FsTool;
|
||||||
|
|
||||||
|
public static class AuthHelper
|
||||||
|
{
|
||||||
|
private static async Task<string?> LoadCsrfTokenAsync()
|
||||||
|
{
|
||||||
|
if (!File.Exists("csrf_token.json")) return null;
|
||||||
|
|
||||||
|
var json = await File.ReadAllTextAsync("csrf_token.json");
|
||||||
|
var tokenInfo = JsonSerializer.Deserialize<JsonObject>(json);
|
||||||
|
|
||||||
|
if (tokenInfo == null) return null;
|
||||||
|
|
||||||
|
var token = tokenInfo["token"]?.GetValue<string>();
|
||||||
|
var expiresAtString = tokenInfo["expiresAt"]?.GetValue<string>();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(expiresAtString)) return null;
|
||||||
|
|
||||||
|
if (DateTime.TryParse(expiresAtString, out var expiresAt))
|
||||||
|
{
|
||||||
|
if (DateTime.UtcNow < expiresAt.ToUniversalTime())
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task StoreCsrfTokenAsync(string? csrfToken, DateTime expiration)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(csrfToken)) return;
|
||||||
|
|
||||||
|
var tokenInfo = new
|
||||||
|
{
|
||||||
|
token = csrfToken,
|
||||||
|
expiresAt = expiration
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(tokenInfo, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync("csrf_token.json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task EnsureAuthenticationAsync(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
var csrfToken = await LoadCsrfTokenAsync();
|
||||||
|
csrfToken = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(csrfToken))
|
||||||
|
csrfToken = await CallLoginEndpointAsync(httpClient);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(csrfToken))
|
||||||
|
{
|
||||||
|
csrfToken = csrfToken.ReplaceLineEndings(string.Empty);
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Remove("X-CSRF-Token");
|
||||||
|
httpClient.DefaultRequestHeaders.Add("X-CSRF-Token", csrfToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string?> CallLoginEndpointAsync(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
string? csrfToken = null;
|
||||||
|
|
||||||
|
// prompt for 2FA code
|
||||||
|
Console.Write("Enter 2FA code: ");
|
||||||
|
var authCode = Console.ReadLine()?.Trim();
|
||||||
|
|
||||||
|
// validate input
|
||||||
|
if (string.IsNullOrWhiteSpace(authCode))
|
||||||
|
{
|
||||||
|
Console.WriteLine("A valid 2FA code is required.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
email = "foodsharing@beging.de",
|
||||||
|
password = "z+hc@Ox9Zu4~MXzkB:Z@O.-S1AvsT&mc!oyQA?NK1jckl1Dzi2^-)+.H.AJKBLoi",
|
||||||
|
code = authCode,
|
||||||
|
remember_me = true
|
||||||
|
};
|
||||||
|
var response = await httpClient.PostAsJsonAsync(Endpoints.Login, payload);
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
await Console.Error.WriteLineAsync($"Login failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read headers as dictionary
|
||||||
|
var headers = response.Headers.ToDictionary(h => h.Key, h => string.Join(", ", h.Value));
|
||||||
|
if(headers.TryGetValue("Set-Cookie", out var setCookieHeader))
|
||||||
|
{
|
||||||
|
// Split cookies by comma and semicolon
|
||||||
|
var cookies = setCookieHeader.Split(new[] { ';' });
|
||||||
|
var csrfTokenEntry = cookies.FirstOrDefault(c => c.Trim().StartsWith("FS_CSRF_TOKEN="));
|
||||||
|
csrfToken = csrfTokenEntry?.Split('=')[1];
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(csrfToken)) return null;
|
||||||
|
|
||||||
|
var expiryEntry = cookies.FirstOrDefault(c => c.Trim().StartsWith("expires="));
|
||||||
|
var expireString = expiryEntry?.Split('=')[1];
|
||||||
|
if (DateTime.TryParse(expireString, out var expiration))
|
||||||
|
await StoreCsrfTokenAsync(csrfToken, expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return csrfToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
CustomTasks.cs
Normal file
114
CustomTasks.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public class CustomTasks
|
||||||
|
{
|
||||||
|
#region Public Method GetUnconfirmedPickupsLindenbergAsync
|
||||||
|
|
||||||
|
public static async Task ConfirmUnconfirmedPickupsLindenbergAsync(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var toConfirm = new List<(string Name, string Date, int User)>();
|
||||||
|
|
||||||
|
// Collect unconfirmed slots
|
||||||
|
var pickups = await StoreTasks.GetPickupsAsync(httpClient, 56749);
|
||||||
|
foreach (var pickup in pickups)
|
||||||
|
foreach (var slot in pickup.OccupiedSlots)
|
||||||
|
if (!slot.IsConfirmed)
|
||||||
|
{
|
||||||
|
var pickupDate = pickup.Date;
|
||||||
|
var userId = slot.Profile.Id;
|
||||||
|
var userName = slot.Profile.Name;
|
||||||
|
|
||||||
|
toConfirm.Add((userName, pickupDate, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toConfirm.Count != 0)
|
||||||
|
{
|
||||||
|
toConfirm.ForEach(x => Console.WriteLine($"Slot found: {x.Name} on {x.Date}"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Write("No unconfirmed slots found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm question
|
||||||
|
Console.Write("Confirm all unconfirmed slots? (y/n) (Enter for default): ");
|
||||||
|
var input = Console.ReadLine();
|
||||||
|
if (string.IsNullOrEmpty(input) || input.Equals("y", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Confirming unconfirmed slots...");
|
||||||
|
|
||||||
|
// Confirm the unconfirmed slots
|
||||||
|
foreach (var confirmEntry in toConfirm)
|
||||||
|
await StoreTasks.PatchPickupAsync(httpClient, 52170, confirmEntry.Date, confirmEntry.User);
|
||||||
|
|
||||||
|
Console.WriteLine("done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private record AldiMember(RegionTasks.Store Store, StoreTasks.Member Member);
|
||||||
|
|
||||||
|
public static async Task CheckAldiMembershipsAsync(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
// Implementation for checking Aldi memberships would go here.
|
||||||
|
var stores = await RegionTasks.GetStoresInRegionAsync(httpClient, 139);
|
||||||
|
var activeAldis = stores.Where(x => x.CooperationStatus == RegionTasks.CooperationStatus.Cooperating && x.Name.Contains("ALDI", StringComparison.CurrentCultureIgnoreCase)).ToList();
|
||||||
|
|
||||||
|
Console.WriteLine("Found " + activeAldis.Count + " active ALDI stores in region Siegen.");
|
||||||
|
|
||||||
|
var aldiMembers = new List<AldiMember>();
|
||||||
|
|
||||||
|
// Collect members from each active ALDI store
|
||||||
|
foreach (var activeAldi in activeAldis)
|
||||||
|
{
|
||||||
|
Thread.Sleep(200);
|
||||||
|
Console.WriteLine("Checking members for store: " + activeAldi.Name);
|
||||||
|
|
||||||
|
var members = await StoreTasks.GetStoreMembersAsync(httpClient, activeAldi.Id);
|
||||||
|
var activeMembers = members.Where(x => x is { Verified: StoreTasks.VerifiedStatus.Verified, Team_Active: StoreTasks.TeamActiveStatus.Active }).ToList();
|
||||||
|
|
||||||
|
activeMembers.ForEach(x => aldiMembers.Add(new(activeAldi, x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group memberships
|
||||||
|
var grouped = aldiMembers.GroupBy(x => x.Member.Id);
|
||||||
|
|
||||||
|
// Find groups with more than two memberships
|
||||||
|
var multipleMemberships = grouped.Where(g => g.Count() > 2).OrderByDescending(g => g.Count()).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine($"Users with more than two ALDI store memberships: {multipleMemberships.Count}");
|
||||||
|
Console.WriteLine($"Saving data to file.");
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("ALDI Store Memberships Report");
|
||||||
|
sb.AppendLine("Generated: " + DateTime.Now.ToString("dd.MM.yyyy HH:mm"));
|
||||||
|
sb.AppendLine("============================");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
foreach (var group in multipleMemberships)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{group.First().Member.Name} ({group.Key}) - {group.Count()} ALDIs");
|
||||||
|
foreach (var membership in group)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" - {membership.Store.Name}");
|
||||||
|
}
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file with timestamp
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||||
|
var filename = $"AldiMemberships_{timestamp}.txt";
|
||||||
|
await File.WriteAllTextAsync(filename, sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Endpoints.cs
Normal file
17
Endpoints.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public static class Endpoints
|
||||||
|
{
|
||||||
|
public const string Login = "https://beta.foodsharing.de/api/user/login";
|
||||||
|
|
||||||
|
public const string UserCurrent = "https://beta.foodsharing.de/api/user/current";
|
||||||
|
|
||||||
|
public const string StorePickups = "https://beta.foodsharing.de/api/stores/{0}/pickups";
|
||||||
|
|
||||||
|
public const string StoreMembers = "https://beta.foodsharing.de/api/stores/{0}/member";
|
||||||
|
|
||||||
|
public const string StorePickupsSlot = "https://beta.foodsharing.de/api/stores/{0}/pickups/{1}/{2}";
|
||||||
|
|
||||||
|
public const string RegionStores = "https://beta.foodsharing.de/api/region/{0}/stores";
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Extensions.cs
Normal file
41
Extensions.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
#region Public Method FsPostAsync
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a POST request to the specified URI with the provided JSON content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpClient">The instance of <see cref="HttpClient" /> used to send the request.</param>
|
||||||
|
/// <param name="requestUri">The URI to which the request is sent.</param>
|
||||||
|
/// <param name="jsonObject">The JSON object to include in the request body.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the HTTP response.</returns>
|
||||||
|
public static async Task<HttpResponseMessage> FsPostAsync(this HttpClient httpClient, string requestUri, JsonObject jsonObject)
|
||||||
|
{
|
||||||
|
var content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json");
|
||||||
|
content.Headers.ContentType = new("application/json");
|
||||||
|
return await httpClient.PostAsync(requestUri, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Method ToList
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the specified JSON node into a list of non-null <see cref="JsonNode" /> elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The <see cref="JsonNode" /> to be converted. If null, an empty list is returned.</param>
|
||||||
|
/// <returns>A list of <see cref="JsonNode" /> instances containing non-null elements.</returns>
|
||||||
|
public static List<JsonNode> ToList(this JsonNode? node)
|
||||||
|
{
|
||||||
|
var array = node?.AsArray() ?? [];
|
||||||
|
return array.Where(x => x != null).Select(x => x!).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
10
FsTool.csproj
Normal file
10
FsTool.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
24
FsTool.sln
Normal file
24
FsTool.sln
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FsTool", "FsTool.csproj", "{D8D12A22-90E0-56DB-D8E0-629AC330F42B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D8D12A22-90E0-56DB-D8E0-629AC330F42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D8D12A22-90E0-56DB-D8E0-629AC330F42B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D8D12A22-90E0-56DB-D8E0-629AC330F42B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D8D12A22-90E0-56DB-D8E0-629AC330F42B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {4685653D-1D5C-4785-BEF1-83E4C5377643}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
30
Program.cs
Normal file
30
Program.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FsTool;
|
||||||
|
|
||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
|
||||||
|
// Show menu for the user to choose from the two tasks
|
||||||
|
Console.WriteLine("Choose a task to execute:");
|
||||||
|
Console.WriteLine("1. Check Aldi Memberships");
|
||||||
|
Console.WriteLine("2. Confirm all Unconfirmed Pickups for Lindenberg");
|
||||||
|
Console.Write("Enter the number of the task to execute (or any other key to exit): ");
|
||||||
|
var choice = Console.ReadLine();
|
||||||
|
|
||||||
|
switch (choice)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
await CustomTasks.CheckAldiMembershipsAsync(httpClient);
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
await CustomTasks.ConfirmUnconfirmedPickupsLindenbergAsync(httpClient);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Console.WriteLine("Exiting...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
52
RegionTasks.cs
Normal file
52
RegionTasks.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public class RegionTasks
|
||||||
|
{
|
||||||
|
public record Store(int Id, string Name, CooperationStatus CooperationStatus);
|
||||||
|
|
||||||
|
public enum CooperationStatus
|
||||||
|
{
|
||||||
|
NoStatus = 0,
|
||||||
|
NoContact = 1,
|
||||||
|
Negotiating = 2,
|
||||||
|
DoNotWant = 4,
|
||||||
|
Cooperating = 5,
|
||||||
|
DonatingToTafel = 6,
|
||||||
|
NoExisting = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<Store>> GetStoresInRegionAsync(HttpClient httpClient, int regionId)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var uri = string.Format(Endpoints.RegionStores, regionId);
|
||||||
|
var response = await httpClient.GetAsync(uri);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Region stores retrieval failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Stores in region {regionId}:");
|
||||||
|
Console.WriteLine(responseBody);
|
||||||
|
|
||||||
|
var root = JsonNode.Parse(responseBody);
|
||||||
|
if (root == null) return [];
|
||||||
|
|
||||||
|
// Deserialize JsonNode to List<Pickup>
|
||||||
|
var opts = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = null // <── WICHTIG
|
||||||
|
};
|
||||||
|
|
||||||
|
return root["stores"].Deserialize<List<Store>>(opts) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
StoreTasks.cs
Normal file
102
StoreTasks.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public static class StoreTasks
|
||||||
|
{
|
||||||
|
public record Pickup(string Date, List<Slot> OccupiedSlots);
|
||||||
|
public record Slot(bool IsConfirmed, Profile Profile);
|
||||||
|
public record Profile(int Id, string Name);
|
||||||
|
public record Member(int Id, string Name, TeamActiveStatus Team_Active, VerifiedStatus Verified);
|
||||||
|
|
||||||
|
public enum TeamActiveStatus
|
||||||
|
{
|
||||||
|
Jumper = 2,
|
||||||
|
Active = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VerifiedStatus
|
||||||
|
{
|
||||||
|
Unverified = 0,
|
||||||
|
Verified = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<Member>> GetStoreMembersAsync(HttpClient httpClient, int storeId)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var uri = string.Format(Endpoints.StoreMembers, storeId);
|
||||||
|
var response = await httpClient.GetAsync(uri);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Store members retrieval failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = JsonNode.Parse(responseBody);
|
||||||
|
if (root == null) return [];
|
||||||
|
|
||||||
|
// Deserialize JsonNode to List<Pickup>
|
||||||
|
var opts = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = null // <── WICHTIG
|
||||||
|
};
|
||||||
|
|
||||||
|
return root.Deserialize<List<Member>>(opts) ?? [];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<Pickup>> GetPickupsAsync(HttpClient httpClient, int storeId)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var uri = string.Format(Endpoints.StorePickups, storeId);
|
||||||
|
var response = await httpClient.GetAsync(uri);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Pickup retrieval failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = JsonNode.Parse(responseBody);
|
||||||
|
if (root == null) return [];
|
||||||
|
|
||||||
|
// Deserialize JsonNode to List<Pickup>
|
||||||
|
var opts = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
PropertyNamingPolicy = null // <── WICHTIG
|
||||||
|
};
|
||||||
|
|
||||||
|
return root["pickups"].Deserialize<List<Pickup>>(opts) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task PatchPickupAsync(HttpClient httpClient, int storeId, string pickupDate, int fsId)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var uri = string.Format(Endpoints.StorePickupsSlot, storeId, pickupDate, fsId);
|
||||||
|
var payload = new JsonObject
|
||||||
|
{
|
||||||
|
["isConfirmed"] = true
|
||||||
|
};
|
||||||
|
var response = await httpClient.PatchAsync(uri, JsonContent.Create(payload));
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
await Console.Error.WriteLineAsync($"Pickup patch failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
else
|
||||||
|
Console.WriteLine($"Pickup patch succeeded {fsId} on {pickupDate}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
UserTasks.cs
Normal file
24
UserTasks.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace FsTool
|
||||||
|
{
|
||||||
|
public static class UserTasks
|
||||||
|
{
|
||||||
|
public static async Task GetCurrentUserAsync(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
await AuthHelper.EnsureAuthenticationAsync(httpClient);
|
||||||
|
|
||||||
|
var response = await httpClient.GetAsync(Endpoints.UserCurrent);
|
||||||
|
|
||||||
|
// handle unsuccessful response
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
await Console.Error.WriteLineAsync($"Get current user failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Console.WriteLine("Current User Info:");
|
||||||
|
Console.WriteLine(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user