initial commit
This commit is contained in:
commit
56de31d5c5
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
zig-cache
|
||||||
|
zig-out
|
35
build.zig
Normal file
35
build.zig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "artificer",
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
const exe_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
|
}
|
7
build.zig.zon
Normal file
7
build.zig.zon
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.{
|
||||||
|
.name = "artificer",
|
||||||
|
.version = "0.1.0",
|
||||||
|
.minimum_zig_version = "0.12.0",
|
||||||
|
.dependencies = .{ },
|
||||||
|
.paths = .{ "" },
|
||||||
|
}
|
544
src/artifacts.zig
Normal file
544
src/artifacts.zig
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const json = std.json;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
// Specification: https://api.artifactsmmo.com/docs
|
||||||
|
|
||||||
|
// TODO: Convert 'expiration' date time strings into date time objects
|
||||||
|
|
||||||
|
const ArtifactsAPI = @This();
|
||||||
|
|
||||||
|
pub const APIErrors = error {
|
||||||
|
RequestFailed,
|
||||||
|
ParseFailed
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServerStatus = struct {
|
||||||
|
// TODO: Parse the rest of the fields
|
||||||
|
allocator: Allocator,
|
||||||
|
status: []const u8,
|
||||||
|
version: []const u8,
|
||||||
|
|
||||||
|
fn parse(allocator: Allocator, object: json.ObjectMap) !ServerStatus {
|
||||||
|
const status = getJsonString(object, "status") orelse return error.MissingStatus;
|
||||||
|
const version = getJsonString(object, "version") orelse return error.MissingVersion;
|
||||||
|
|
||||||
|
return ServerStatus{
|
||||||
|
.allocator = allocator,
|
||||||
|
.status = try allocator.dupe(u8, status),
|
||||||
|
.version = try allocator.dupe(u8, version)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ServerStatus) void {
|
||||||
|
self.allocator.free(self.status);
|
||||||
|
self.allocator.free(self.version);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Character = struct {
|
||||||
|
pub const SkillStats = struct {
|
||||||
|
level: i64,
|
||||||
|
xp: i64,
|
||||||
|
max_xp: i64,
|
||||||
|
|
||||||
|
fn parse(object: json.ObjectMap, level: []const u8, xp: []const u8, max_xp: []const u8) !SkillStats {
|
||||||
|
return SkillStats{
|
||||||
|
.level = getJsonInteger(object, level) orelse return error.MissingProperty,
|
||||||
|
.xp = getJsonInteger(object, xp) orelse return error.MissingProperty,
|
||||||
|
.max_xp = getJsonInteger(object, max_xp) orelse return error.MissingProperty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CombatStats = struct {
|
||||||
|
attack: i64,
|
||||||
|
damage: i64,
|
||||||
|
resistance: i64,
|
||||||
|
|
||||||
|
fn parse(object: json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !CombatStats {
|
||||||
|
return CombatStats{
|
||||||
|
.attack = getJsonInteger(object, attack) orelse return error.MissingProperty,
|
||||||
|
.damage = getJsonInteger(object, damage) orelse return error.MissingProperty,
|
||||||
|
.resistance = getJsonInteger(object, resistance) orelse return error.MissingProperty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Equipment = struct {
|
||||||
|
pub const Consumable = struct {
|
||||||
|
name: []u8,
|
||||||
|
quantity: i64,
|
||||||
|
|
||||||
|
fn parse(allocator: Allocator, obj: json.ObjectMap, name: []const u8, quantity: []const u8) !Consumable {
|
||||||
|
return Consumable{
|
||||||
|
.name = (try dupeJsonString(allocator, obj, name)) orelse return error.MissingProperty,
|
||||||
|
.quantity = getJsonInteger(obj, quantity) orelse return error.MissingProperty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
weapon: []u8,
|
||||||
|
shield: []u8,
|
||||||
|
helmet: []u8,
|
||||||
|
body_armor: []u8,
|
||||||
|
leg_armor: []u8,
|
||||||
|
boots: []u8,
|
||||||
|
|
||||||
|
ring1: []u8,
|
||||||
|
ring2: []u8,
|
||||||
|
amulet: []u8,
|
||||||
|
|
||||||
|
artifact1: []u8,
|
||||||
|
artifact2: []u8,
|
||||||
|
artifact3: []u8,
|
||||||
|
|
||||||
|
consumable1: Consumable,
|
||||||
|
consumable2: Consumable,
|
||||||
|
|
||||||
|
fn parse(allocator: Allocator, obj: json.ObjectMap) !Equipment {
|
||||||
|
return Equipment{
|
||||||
|
.weapon = (try dupeJsonString(allocator, obj, "weapon_slot")) orelse return error.MissingProperty,
|
||||||
|
.shield = (try dupeJsonString(allocator, obj, "shield_slot")) orelse return error.MissingProperty,
|
||||||
|
.helmet = (try dupeJsonString(allocator, obj, "helmet_slot")) orelse return error.MissingProperty,
|
||||||
|
.body_armor = (try dupeJsonString(allocator, obj, "body_armor_slot")) orelse return error.MissingProperty,
|
||||||
|
.leg_armor = (try dupeJsonString(allocator, obj, "leg_armor_slot")) orelse return error.MissingProperty,
|
||||||
|
.boots = (try dupeJsonString(allocator, obj, "boots_slot")) orelse return error.MissingProperty,
|
||||||
|
.ring1 = (try dupeJsonString(allocator, obj, "ring1_slot")) orelse return error.MissingProperty,
|
||||||
|
.ring2 = (try dupeJsonString(allocator, obj, "ring2_slot")) orelse return error.MissingProperty,
|
||||||
|
.amulet = (try dupeJsonString(allocator, obj, "amulet_slot")) orelse return error.MissingProperty,
|
||||||
|
.artifact1 = (try dupeJsonString(allocator, obj, "artifact1_slot")) orelse return error.MissingProperty,
|
||||||
|
.artifact2 = (try dupeJsonString(allocator, obj, "artifact2_slot")) orelse return error.MissingProperty,
|
||||||
|
.artifact3 = (try dupeJsonString(allocator, obj, "artifact3_slot")) orelse return error.MissingProperty,
|
||||||
|
.consumable1 = try Consumable.parse(allocator, obj, "consumable1_slot", "consumable1_slot_quantity"),
|
||||||
|
.consumable2 = try Consumable.parse(allocator, obj, "consumable2_slot", "consumable2_slot_quantity"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arena: *std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
name: []u8,
|
||||||
|
skin: []u8,
|
||||||
|
account: ?[]u8,
|
||||||
|
total_xp: i64,
|
||||||
|
gold: i64,
|
||||||
|
hp: i64,
|
||||||
|
haste: i64,
|
||||||
|
x: i64,
|
||||||
|
y: i64,
|
||||||
|
cooldown: i64,
|
||||||
|
cooldown_expiration: []u8,
|
||||||
|
|
||||||
|
combat: SkillStats,
|
||||||
|
mining: SkillStats,
|
||||||
|
woodcutting: SkillStats,
|
||||||
|
fishing: SkillStats,
|
||||||
|
weaponcrafting: SkillStats,
|
||||||
|
gearcrafting: SkillStats,
|
||||||
|
jewelrycrafting: SkillStats,
|
||||||
|
cooking: SkillStats,
|
||||||
|
|
||||||
|
water: CombatStats,
|
||||||
|
fire: CombatStats,
|
||||||
|
earth: CombatStats,
|
||||||
|
air: CombatStats,
|
||||||
|
|
||||||
|
equipment: Equipment,
|
||||||
|
|
||||||
|
fn parse(child_allocator: Allocator, obj: json.ObjectMap) !Character {
|
||||||
|
var arena = try child_allocator.create(std.heap.ArenaAllocator);
|
||||||
|
errdefer child_allocator.destroy(arena);
|
||||||
|
|
||||||
|
arena.* = std.heap.ArenaAllocator.init(child_allocator);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
|
return Character{
|
||||||
|
.arena = arena,
|
||||||
|
.account = try dupeJsonString(allocator, obj, "account"),
|
||||||
|
.name = (try dupeJsonString(allocator, obj, "name")) orelse return error.MissingProperty,
|
||||||
|
.skin = (try dupeJsonString(allocator, obj, "skin")) orelse return error.MissingProperty,
|
||||||
|
|
||||||
|
.total_xp = getJsonInteger(obj, "total_xp") orelse return error.MissingProperty,
|
||||||
|
.gold = getJsonInteger(obj, "gold") orelse return error.MissingProperty,
|
||||||
|
.hp = getJsonInteger(obj, "hp") orelse return error.MissingProperty,
|
||||||
|
.haste = getJsonInteger(obj, "haste") orelse return error.MissingProperty,
|
||||||
|
.x = getJsonInteger(obj, "x") orelse return error.MissingProperty,
|
||||||
|
.y = getJsonInteger(obj, "y") orelse return error.MissingProperty,
|
||||||
|
.cooldown = getJsonInteger(obj, "cooldown") orelse return error.MissingProperty,
|
||||||
|
.cooldown_expiration = (try dupeJsonString(allocator, obj, "cooldown_expiration")) orelse return error.MissingProperty,
|
||||||
|
|
||||||
|
.combat = try SkillStats.parse(obj, "level", "xp", "max_xp"),
|
||||||
|
.mining = try SkillStats.parse(obj, "mining_level", "mining_xp", "mining_max_xp"),
|
||||||
|
.woodcutting = try SkillStats.parse(obj, "woodcutting_level", "woodcutting_xp", "woodcutting_max_xp"),
|
||||||
|
.fishing = try SkillStats.parse(obj, "fishing_level", "fishing_xp", "fishing_max_xp"),
|
||||||
|
.weaponcrafting = try SkillStats.parse(obj, "weaponcrafting_level", "weaponcrafting_xp", "weaponcrafting_max_xp"),
|
||||||
|
.gearcrafting = try SkillStats.parse(obj, "gearcrafting_level", "gearcrafting_xp", "gearcrafting_max_xp"),
|
||||||
|
.jewelrycrafting = try SkillStats.parse(obj, "jewelrycrafting_level", "jewelrycrafting_xp", "jewelrycrafting_max_xp"),
|
||||||
|
.cooking = try SkillStats.parse(obj, "cooking_level", "cooking_xp", "cooking_max_xp"),
|
||||||
|
|
||||||
|
.water = try CombatStats.parse(obj, "attack_water", "dmg_water", "res_water"),
|
||||||
|
.fire = try CombatStats.parse(obj, "attack_fire", "dmg_fire", "res_fire"),
|
||||||
|
.earth = try CombatStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"),
|
||||||
|
.air = try CombatStats.parse(obj, "attack_air", "dmg_air", "res_air"),
|
||||||
|
|
||||||
|
.equipment = try Equipment.parse(allocator, obj)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Character) void {
|
||||||
|
var child_allocator = self.arena.child_allocator;
|
||||||
|
self.arena.deinit();
|
||||||
|
child_allocator.destroy(self.arena);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CharacterList = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
items: []Character,
|
||||||
|
|
||||||
|
pub fn deinit(self: CharacterList) void {
|
||||||
|
for (self.items) |*char| {
|
||||||
|
char.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Cooldown = struct {
|
||||||
|
pub const Reason = enum {
|
||||||
|
movement,
|
||||||
|
fight,
|
||||||
|
crafting,
|
||||||
|
gathering,
|
||||||
|
buy_ge,
|
||||||
|
sell_ge,
|
||||||
|
delete_item,
|
||||||
|
deposit_bank,
|
||||||
|
withdraw_bank,
|
||||||
|
equip,
|
||||||
|
unequip,
|
||||||
|
task,
|
||||||
|
recycling,
|
||||||
|
|
||||||
|
fn parse(str: []const u8) ?Reason {
|
||||||
|
const eql = std.mem.eql;
|
||||||
|
if (eql(u8, str, "movement")) {
|
||||||
|
return .movement;
|
||||||
|
} else if (eql(u8, str, "fight")) {
|
||||||
|
return .fight;
|
||||||
|
} else if (eql(u8, str, "crafting")) {
|
||||||
|
return .crafting;
|
||||||
|
} else if (eql(u8, str, "gathering")) {
|
||||||
|
return .gathering;
|
||||||
|
} else if (eql(u8, str, "buy_ge")) {
|
||||||
|
return .buy_ge;
|
||||||
|
} else if (eql(u8, str, "sell_ge")) {
|
||||||
|
return .sell_ge;
|
||||||
|
} else if (eql(u8, str, "delete_item")) {
|
||||||
|
return .delete_item;
|
||||||
|
} else if (eql(u8, str, "deposit_bank")) {
|
||||||
|
return .deposit_bank;
|
||||||
|
} else if (eql(u8, str, "withdraw_bank")) {
|
||||||
|
return .withdraw_bank;
|
||||||
|
} else if (eql(u8, str, "equip")) {
|
||||||
|
return .equip;
|
||||||
|
} else if (eql(u8, str, "unequip")) {
|
||||||
|
return .unequip;
|
||||||
|
} else if (eql(u8, str, "task")) {
|
||||||
|
return .task;
|
||||||
|
} else if (eql(u8, str, "recycling")) {
|
||||||
|
return .recycling;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
total_seconds: i64,
|
||||||
|
remaining_seconds: i64,
|
||||||
|
expiration: []u8,
|
||||||
|
reason: Reason,
|
||||||
|
|
||||||
|
fn parse(allocator: Allocator, obj: json.ObjectMap) !Cooldown {
|
||||||
|
const reason = getJsonString(obj, "reason") orelse return error.MissingProperty;
|
||||||
|
return Cooldown{
|
||||||
|
.allocator = allocator,
|
||||||
|
.total_seconds = getJsonInteger(obj, "totalSeconds") orelse return error.MissingProperty,
|
||||||
|
.remaining_seconds = getJsonInteger(obj, "remainingSeconds") orelse return error.MissingProperty,
|
||||||
|
.expiration = (try dupeJsonString(allocator, obj, "expiration")) orelse return error.MissingProperty,
|
||||||
|
.reason = Reason.parse(reason) orelse return error.UnknownReason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Cooldown) void {
|
||||||
|
self.allocator.free(self.expiration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FightResult = struct {
|
||||||
|
cooldown: Cooldown,
|
||||||
|
|
||||||
|
fn parse(allocator: Allocator, obj: json.ObjectMap) !FightResult {
|
||||||
|
const cooldown = getJsonObject(obj, "cooldown") orelse return error.MissingProperty;
|
||||||
|
|
||||||
|
return FightResult{
|
||||||
|
.cooldown = try Cooldown.parse(allocator, cooldown)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: FightResult) void {
|
||||||
|
self.cooldown.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArtifactsFetchResult = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
status: std.http.Status,
|
||||||
|
body: ?json.Value = null,
|
||||||
|
|
||||||
|
fn deinit(self: ArtifactsFetchResult) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
client: std.http.Client,
|
||||||
|
|
||||||
|
server: []u8,
|
||||||
|
server_uri: std.Uri,
|
||||||
|
|
||||||
|
token: ?[]u8 = null,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) !ArtifactsAPI {
|
||||||
|
const server = try allocator.dupe(u8, "https://api.artifactsmmo.com");
|
||||||
|
const server_uri = std.Uri.parse(server) catch unreachable;
|
||||||
|
|
||||||
|
return ArtifactsAPI{
|
||||||
|
.allocator = allocator,
|
||||||
|
.client = .{ .allocator = allocator },
|
||||||
|
.server = server,
|
||||||
|
.server_uri = server_uri
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch(self: *ArtifactsAPI, method: std.http.Method, path: []const u8) !ArtifactsFetchResult {
|
||||||
|
var uri = self.server_uri;
|
||||||
|
uri.path = .{ .raw = path };
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
|
||||||
|
var response_storage = std.ArrayList(u8).init(arena.allocator());
|
||||||
|
|
||||||
|
var opts = std.http.Client.FetchOptions{
|
||||||
|
.method = method,
|
||||||
|
.location = .{ .uri = uri },
|
||||||
|
.response_storage = .{ .dynamic = &response_storage }
|
||||||
|
};
|
||||||
|
|
||||||
|
var authorization_header: ?[]u8 = null;
|
||||||
|
defer if (authorization_header) |str| self.allocator.free(str);
|
||||||
|
|
||||||
|
if (self.token) |token| {
|
||||||
|
authorization_header = try std.fmt.allocPrint(self.allocator, "Bearer {s}", .{token});
|
||||||
|
opts.headers.authorization = .{ .override = authorization_header.? };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = try self.client.fetch(opts);
|
||||||
|
|
||||||
|
if (result.status != .ok) {
|
||||||
|
return ArtifactsFetchResult{
|
||||||
|
.arena = arena,
|
||||||
|
.status = result.status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response_body = response_storage.items;
|
||||||
|
|
||||||
|
const parsed = try json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed });
|
||||||
|
if (parsed != json.Value.object) {
|
||||||
|
return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArtifactsFetchResult{
|
||||||
|
.status = result.status,
|
||||||
|
.arena = arena,
|
||||||
|
.body = parsed.object.get("data")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
|
||||||
|
const url_dupe = self.allocator.dupe(u8, url);
|
||||||
|
errdefer self.allocator.free(url_dupe);
|
||||||
|
|
||||||
|
const uri = try std.Uri.parse(url_dupe);
|
||||||
|
|
||||||
|
self.allocator.free(self.server);
|
||||||
|
self.server = url_dupe;
|
||||||
|
self.server_uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setToken(self: *ArtifactsAPI, token: ?[]const u8) !void {
|
||||||
|
var new_token: ?[]u8 = null;
|
||||||
|
if (token != null) {
|
||||||
|
new_token = try self.allocator.dupe(u8, token.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.token) |str| self.allocator.free(str);
|
||||||
|
self.token = new_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getJsonString(object: json.ObjectMap, name: []const u8) ?[]const u8 {
|
||||||
|
const value = object.get(name);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.? != json.Value.string) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.?.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dupeJsonString(allocator: Allocator, object: json.ObjectMap, name: []const u8) !?[]u8 {
|
||||||
|
const str = getJsonString(object, name) orelse return null;
|
||||||
|
return try allocator.dupe(u8, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getJsonInteger(object: json.ObjectMap, name: []const u8) ?i64 {
|
||||||
|
const value = object.get(name);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.? != json.Value.integer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.?.integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn asJsonObject(value: json.Value) ?json.ObjectMap {
|
||||||
|
if (value != json.Value.object) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn asJsonArray(value: json.Value) ?json.Array {
|
||||||
|
if (value != json.Value.array) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.array;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getJsonObject(object: json.ObjectMap, name: []const u8) ?json.ObjectMap {
|
||||||
|
const value = object.get(name);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return asJsonObject(value.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getServerStatus(self: *ArtifactsAPI) !ServerStatus {
|
||||||
|
const result = try self.fetch(.GET, "/");
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
if (result.status != .ok) {
|
||||||
|
return APIErrors.RequestFailed;
|
||||||
|
}
|
||||||
|
if (result.body == null) {
|
||||||
|
return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
|
||||||
|
return ServerStatus.parse(self.allocator, body) catch return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCharacter(self: *ArtifactsAPI, name: []const u8) !Character {
|
||||||
|
const path = try std.fmt.allocPrint(self.allocator, "/characters/{s}", .{name});
|
||||||
|
defer self.allocator.free(path);
|
||||||
|
|
||||||
|
const result = try self.fetch(.GET, path);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
if (result.status != .ok) {
|
||||||
|
return APIErrors.RequestFailed;
|
||||||
|
}
|
||||||
|
if (result.body == null) {
|
||||||
|
return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
|
||||||
|
return Character.parse(self.allocator, body) catch return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listMyCharacters(self: *ArtifactsAPI) !CharacterList {
|
||||||
|
const path = try std.fmt.allocPrint(self.allocator, "/my/characters", .{});
|
||||||
|
defer self.allocator.free(path);
|
||||||
|
|
||||||
|
const result = try self.fetch(.GET, path);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
if (result.status != .ok) {
|
||||||
|
return APIErrors.RequestFailed;
|
||||||
|
}
|
||||||
|
if (result.body == null) {
|
||||||
|
return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = asJsonArray(result.body.?) orelse return APIErrors.ParseFailed;
|
||||||
|
|
||||||
|
var characters = try std.ArrayList(Character).initCapacity(self.allocator, body.items.len);
|
||||||
|
errdefer {
|
||||||
|
for (characters.items) |*char| {
|
||||||
|
char.deinit();
|
||||||
|
}
|
||||||
|
characters.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (body.items) |character_json| {
|
||||||
|
const character_obj = asJsonObject(character_json) orelse return APIErrors.ParseFailed;
|
||||||
|
const char = Character.parse(self.allocator, character_obj) catch return APIErrors.ParseFailed;
|
||||||
|
|
||||||
|
characters.appendAssumeCapacity(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return CharacterList{
|
||||||
|
.allocator = self.allocator,
|
||||||
|
.items = characters.items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn actionFight(self: *ArtifactsAPI, name: []const u8) !FightResult {
|
||||||
|
const path = try std.fmt.allocPrint(self.allocator, "/my/{s}/action/fight", .{name});
|
||||||
|
defer self.allocator.free(path);
|
||||||
|
|
||||||
|
const result = try self.fetch(.POST, path);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
if (result.status != .ok) {
|
||||||
|
return APIErrors.RequestFailed;
|
||||||
|
}
|
||||||
|
if (result.body == null) {
|
||||||
|
return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = asJsonObject(result.body.?) orelse return APIErrors.ParseFailed;
|
||||||
|
return FightResult.parse(self.allocator, body) catch return APIErrors.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *ArtifactsAPI) void {
|
||||||
|
self.client.deinit();
|
||||||
|
self.allocator.free(self.server);
|
||||||
|
if (self.token) |str| self.allocator.free(str);
|
||||||
|
}
|
26
src/main.zig
Normal file
26
src/main.zig
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const ArtifactsAPI = @import("artifacts.zig");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
var api = try ArtifactsAPI.init(allocator);
|
||||||
|
defer api.deinit();
|
||||||
|
|
||||||
|
{ // Set auth token from environment variable
|
||||||
|
var env = try std.process.getEnvMap(allocator);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const token = env.get("ARTIFACTS_TOKEN");
|
||||||
|
try api.setToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const result = try api.actionFight("Daisy");
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
std.time.sleep(std.time.ns_per_s * (@as(u64, @intCast(result.cooldown.remaining_seconds)) + 1));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user