eorzea-votes/client/EorzeaVotes/QuestionManager.cs

195 lines
6.4 KiB
C#

using System.Collections;
using System.Net.Http.Headers;
using Dalamud.Game.ClientState.Objects.Enums;
using EorzeaVotes.Model;
using Newtonsoft.Json;
namespace EorzeaVotes;
internal class QuestionManager : IDisposable, IReadOnlyList<IQuestion> {
private Plugin Plugin { get; }
private HttpClient Http { get; }
private QuestionsResponse _questions = new() {
Page = [],
};
private Guid _lastSeenActive = Guid.Empty;
private int _page = 1;
internal QuestionManager(Plugin plugin) {
this.Plugin = plugin;
this.Http = new HttpClient {
BaseAddress = new Uri("https://ev.anna.lgbt/"),
DefaultRequestHeaders = {
UserAgent = {
new ProductInfoHeaderValue("EorzeaVotes", typeof(QuestionManager).Assembly.GetName().Version?.ToString(3) ?? "???"),
},
},
};
}
public void Dispose() {
this.Http.Dispose();
}
internal IQuestion? Current => this._questions.Current;
internal bool HasNext => this._questions.HasNext;
internal bool Loading { get; private set; }
internal int Page {
get => this._page;
set {
if (this.Loading) {
throw new InvalidOperationException("cannot set page while still loading");
}
var old = this._page;
this.Loading = true;
this._page = Math.Max(1, value);
Task.Run(async () => {
try {
await this.Check();
} catch {
this._page = old;
throw;
} finally {
this.Loading = false;
}
});
}
}
public int Count => this._questions.Page.Count;
public IQuestion this[int index] => this._questions.Page[index];
public IEnumerator<IQuestion> GetEnumerator() {
return this._questions.Page.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
private HttpRequestMessage MakeMessage(HttpMethod method, string url) {
return new HttpRequestMessage(method, url) {
Headers = {
{ "X-Api-Key", this.Plugin.Config.ApiKey },
},
};
}
internal async Task<bool> Vote(Guid questionId, ushort answer) {
var player = await this.Plugin.Framework.RunOnFrameworkThread(() => this.Plugin.ClientState.LocalPlayer);
var voteReq = new VoteRequest {
QuestionId = questionId,
Answer = answer,
Stats = new VoteStats {
Gender = this.Plugin.Config.Gender,
BirthDate = this.Plugin.Config.BirthDate,
CharacterRace = this.Plugin.Config.RaceClanGender
? player?.Customize[(int) CustomizeIndex.Race]
: null,
CharacterClan = this.Plugin.Config.RaceClanGender
? player?.Customize[(int) CustomizeIndex.Tribe]
: null,
CharacterGender = this.Plugin.Config.RaceClanGender
? player?.Customize[(int) CustomizeIndex.Gender]
: null,
CharacterHomeWorld = this.Plugin.Config.HomeWorld
? player?.HomeWorld.Id
: null,
YearStartedPlaying = (ushort?) this.Plugin.Config.YearStartedPlaying,
MyersBriggs = this.Plugin.Config.MyersBriggs,
MainJob = this.Plugin.Config.MainJob,
},
};
var req = this.MakeMessage(HttpMethod.Post, "/vote");
req.Content = new StringContent(JsonConvert.SerializeObject(voteReq, Plugin.SerializerSettings)) {
Headers = {
ContentType = new MediaTypeHeaderValue("application/json"),
},
};
var resp = await this.Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
return resp.IsSuccessStatusCode;
}
private async Task RegisterIfNecessary() {
if (this.Plugin.Config is { UserId: not null, ApiKey: not null }) {
return;
}
var req = this.MakeMessage(HttpMethod.Post, "/user");
var resp = await this.Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
var json = await resp.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<RegisterResponse>(json, Plugin.SerializerSettings)!;
this.Plugin.Config.UserId = data.UserId;
this.Plugin.Config.ApiKey = data.ApiKey;
this.Plugin.SaveConfig();
}
[Serializable]
private class QuestionsResponse {
public IQuestion? Current { get; init; }
public required List<IQuestion> Page { get; init; }
public bool HasNext { get; init; }
}
internal async Task Check() {
await this.RegisterIfNecessary();
var req = this.MakeMessage(HttpMethod.Get, $"/questions?v=2&page={this._page}");
var resp = await this.Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
var json = await resp.Content.ReadAsStringAsync();
this._questions = JsonConvert.DeserializeObject<QuestionsResponse>(json, Plugin.SerializerSettings) ?? new QuestionsResponse {
Current = null,
Page = [],
HasNext = false,
};
await this.OpenIfNew();
}
internal async Task OpenIfNew() {
if (!this.Plugin.Config.OnBoarded) {
return;
}
var isLoggedIn = await this.Plugin.Framework.RunOnFrameworkThread(() => this.Plugin.ClientState.IsLoggedIn);
if (!isLoggedIn) {
return;
}
var active = this.Current;
if (active is not BasicQuestion) {
return;
}
if (this._lastSeenActive != active.Id) {
this.Plugin.Ui.Visible = true;
}
this._lastSeenActive = active.Id;
}
internal async Task<Dictionary<string, ulong[]>> GetBreakdown(Guid question, Breakdown breakdown) {
var req = this.MakeMessage(
HttpMethod.Get,
$"/question/{question}/breakdown/{breakdown.ToSnakeCase()}"
);
var resp = await this.Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
var json = await resp.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<Dictionary<string, ulong[]>>(json)!;
return data;
}
}