Skip to content

Commit

Permalink
Updated to work w/ NX API changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Digitalroot committed Dec 8, 2022
1 parent 71e7434 commit 24ee6de
Show file tree
Hide file tree
Showing 36 changed files with 291 additions and 211 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Release
concurrency: ci-${{ github.ref }}

on:
workflow_dispatch:

push:
branches: [ main, develop ] # Default release branch
paths:
Expand Down
17 changes: 9 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# OdinPlus Mod Uploader

[![Release](https://github.com/Digitalroot-Valheim/Digitalroot.OdinPlusModUploader/actions/workflows/release.yml/badge.svg)](https://github.com/Digitalroot-Valheim/Digitalroot.OdinPlusModUploader/actions/workflows/release.yml)

## Introduction

This is a command-line tool that can be used to upload mod files to an existing mod on Thunderstore, ModVault and Nexus Mods.
Expand All @@ -27,7 +25,8 @@ Please use environment variables for the sensitive information.
### Environment Variables
- __NEXUSMOD_API_KEY__
- __NEXUSMOD_COOKIE__
- __NEXUSMOD_COOKIE_NEXUSID__
- __NEXUSMOD_COOKIE_SID_DEVELOP__

All Commands support the `-?, -h, --help` options to show help and usage information

Expand Down Expand Up @@ -79,7 +78,8 @@ Usage:
Options:
-k, --key <key> Api Key, ENV: NEXUSMOD_API_KEY
-c, --cookie <cookie> Session Cookie, ENV: NEXUSMOD_COOKIE
-cnxid, --cookie_nexusid <cookie value> Session Cookie, ENV: NEXUSMOD_COOKIE_NEXUSID
-csid, --cookie_sid_develop <cookie value> Session Cookie, ENV: NEXUSMOD_COOKIE_SID_DEVELOP
-?, -h, --help Show help and usage information
```
---
Expand Down Expand Up @@ -108,13 +108,14 @@ Options:
-dmv, --disable-main-vortex Skips setting file as the main Vortex file. [default: False]
-drpu, --disable-requirements-pop-up Skips informing downloaders of this mod's requirements before they attempt to download this file [default: False]
-k, --key <key> Api Key, ENV: NEXUSMOD_API_KEY
-c, --cookie <cookie> Session Cookie, ENV: NEXUSMOD_COOKIE
-cnxid, --cookie_nexusid <cookie value> Session Cookie, ENV: NEXUSMOD_COOKIE_NEXUSID
-csid, --cookie_sid_develop <cookie value> Session Cookie, ENV: NEXUSMOD_COOKIE_SID_DEVELOP
-?, -h, --help Show help and usage information
```

#### Examples
```bash
nexusmods check -k "7a0e--MyVeryLongNexusApiKey--377" -c "%7B%22mechanism--MyVeryLongNexusSessionCookieValue--%22%7D"
nexusmods check -k "7a0e--MyVeryLongNexusApiKey--377" -cnxid "%7B%22mechanism--MyVeryLongNexusSessionCookieValue--%22%7D" -csid "%7B%22mechanism--MyVeryLongNexusSessionCookieSessIdValue--%22%7D"
```
---
```bash
Expand All @@ -136,9 +137,9 @@ nexusmods upload 1303 "Digitalroot.Valheim.JVL.BT.Fix.v1.0.6.zip" -v "1.0.6" -f
##### Q3: Where do I get my Nexus Mods' API key?
> [My Nexus account page](https://www.nexusmods.com/users/myaccount?tab=api%20access)
##### Q4: Where do I get my Nexus Mods' Session Cookie?
##### Q4: Where do I get my Nexus Mods' Session Cookies?
> From your browser. This [site](https://www.cookieyes.com/how-to-check-cookies-on-your-website-manually/) covers how to do it in most browers.
> The cookie you are looking for is called `nexusid` and starts with `%7B%22mechanism`. This is the html encoded value for `{"mechanism`.
> The cookies you are looking for are called `nexusid` and `sid_develop`. They starts with `%7B%22mechanism`. This is the html encoded value for `{"mechanism`.
> If your browser displays a cookie value starting with `{"mechanism` then you will need to html encode the value before using this tool.

53 changes: 40 additions & 13 deletions src/Digitalroot.OdinPlusModUploader/Clients/AbstractRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Digitalroot.OdinPlusModUploader.Models;
using Digitalroot.OdinPlusModUploader.Protocol;
using Digitalroot.OdinPlusModUploader.Provider.NexusMods;
using Digitalroot.OdinPlusModUploader.Provider.NexusMods.Models;
using Digitalroot.OdinPlusModUploader.Provider.NexusMods.Protocol;
using Digitalroot.OdinPlusModUploader.Serialization;
using Newtonsoft.Json;
using RestSharp;
Expand Down Expand Up @@ -45,7 +47,7 @@ private protected AbstractRestClient(AbstractHostProviderConfiguration modsHostP
/// e.g. [JsonProperty(PropertyName = "country")]
/// </summary>
/// <param name="restClient"></param>
private protected static void SetNewtonsoftJsonSerializerAsHandler(IRestClient restClient)
private protected static void SetNewtonsoftJsonSerializerAsHandler(RestClient restClient)
{
restClient.UseNewtonsoftJson(new JsonSerializerSettings
{
Expand All @@ -54,22 +56,22 @@ private protected static void SetNewtonsoftJsonSerializerAsHandler(IRestClient r
});
}

internal static ErrorResponseModel GetErrorMessage(IRestResponse response)
internal static ErrorResponseModel GetErrorMessage(RestResponse response)
{
return new ErrorResponseModel(response);
}

[Conditional("TRACE")]
private static void TraceRequest(IRestClient restClient, IRestRequest request)
private static void TraceRequest(RestClient restClient, RestRequest request)
{
Trace.WriteLine($"** Request ** - {request.Method}: {restClient.BaseUrl}{request.Resource}");
Trace.WriteLine($"** Request ** - {request.Method}: {restClient.Options.BaseUrl}{request.Resource}");
Trace.WriteLine("[Payload:]");
Trace.WriteLine(NewtonsoftJsonSerializer.Default.Serialize(request.Parameters));
Trace.WriteLine(string.Empty);
}

[Conditional("TRACE")]
private static void TraceResponse(IRestResponse response)
private static void TraceResponse(RestResponse response)
{
Trace.WriteLine($"** Response ** - IsSuccessful: {response.IsSuccessful}, StatusCode: {response.StatusCode}");
Trace.WriteLine("[Headers:]");
Expand All @@ -79,7 +81,7 @@ private static void TraceResponse(IRestResponse response)
switch (response.ContentType)
{
case "application/json; charset=utf-8":
// Print nothing. JSON data is printed via TraceResponse<TRestResponseModel>(IRestResponse response, TRestResponseModel data)
// Print nothing. JSON data is printed via TraceResponse<TRestResponseModel>(RestResponse response, TRestResponseModel data)
break;

case "text/html; charset=utf-8":
Expand Down Expand Up @@ -111,7 +113,7 @@ private static void TraceResponse(IRestResponse response)
}

[Conditional("TRACE")]
private static void TraceResponse<TRestResponseModel>(IRestResponse response, TRestResponseModel data)
private static void TraceResponse<TRestResponseModel>(RestResponse response, TRestResponseModel data)
{
TraceResponse(response);
if (data == null) return;
Expand All @@ -126,22 +128,22 @@ private static void TraceResponse<TRestResponseModel>(IRestResponse response, TR
#region Obsolete

[Obsolete("Use Async Version")]
protected static TRestResponse Get<TRestResponse>(IRestClient restClient, IRestRequest request)
protected static TRestResponse Get<TRestResponse>(RestClient restClient, RestRequest request)
{
var response = restClient.Get(request);
return (TRestResponse)Activator.CreateInstance(typeof(TRestResponse), response);
}

[Obsolete("Use Async Version")]
protected static TRestResponse Get<TRestResponse, TRestResponseModel>(IRestClient restClient, IRestRequest request)
protected static TRestResponse Get<TRestResponse, TRestResponseModel>(RestClient restClient, RestRequest request)
where TRestResponse : AbstractResponse
{
var response = restClient.Get<TRestResponseModel>(request);
return (TRestResponse)Activator.CreateInstance(typeof(TRestResponse), response);
}

[Obsolete("This might be restored")]
protected static async Task<TRestResponse> PostAsync<TRestResponse>(IRestClient restClient, IRestRequest request)
protected static async Task<TRestResponse> PostAsync<TRestResponse>(RestClient restClient, RestRequest request)
{
var response = await restClient.ExecutePostAsync(request);
return (TRestResponse)Activator.CreateInstance(typeof(TRestResponse), response);
Expand All @@ -151,19 +153,35 @@ protected static async Task<TRestResponse> PostAsync<TRestResponse>(IRestClient

#region GetAsync

protected static async Task<TRestResponse> GetAsync<TRestResponse>(IRestClient restClient, IRestRequest request)
//AbstractAuthorizedRequest

protected static async Task<TRestResponse> GetAsync<TRestResponse>(RestClient restClient, RestRequest request)
where TRestResponse : AbstractResponse
{
TraceRequest(restClient, request);

if (request is AbstractAuthorizedRequest { Model: CookieRequestModel model })
{
restClient.AddCookie("nexusid", model.CookieNexusId, "/", ".nexusmods.com");
restClient.AddCookie("sid_develop", model.CookieSidDevelop, "/", ".nexusmods.com");
}

var response = await restClient.ExecuteGetAsync(request);
TraceResponse(response);
return (TRestResponse)Activator.CreateInstance(typeof(TRestResponse), response);
}

protected static async Task<TRestResponse> GetAsync<TRestResponse, TRestResponseModel>(IRestClient restClient, IRestRequest request)
protected static async Task<TRestResponse> GetAsync<TRestResponse, TRestResponseModel>(RestClient restClient, RestRequest request)
where TRestResponse : AbstractResponse
{
TraceRequest(restClient, request);

if (request is AbstractAuthorizedRequest { Model: CookieRequestModel model })
{
restClient.AddCookie("nexusid", model.CookieNexusId, "/", ".nexusmods.com");
restClient.AddCookie("sid_develop", model.CookieSidDevelop, "/", ".nexusmods.com");
}

var response = await restClient.ExecuteGetAsync<TRestResponseModel>(request);
TraceResponse(response, response.Data);

Expand All @@ -174,10 +192,19 @@ protected static async Task<TRestResponse> GetAsync<TRestResponse, TRestResponse

#region PostAsync

protected static async Task<TRestResponse> PostAsync<TRestResponse, TRestResponseModel>(IRestClient restClient, IRestRequest request)
protected static async Task<TRestResponse> PostAsync<TRestResponse, TRestResponseModel>(RestClient restClient, RestRequest request)
where TRestResponse : AbstractResponse
{
TraceRequest(restClient, request);

if (request is AbstractAuthorizedRequest { Model: CookieRequestModel model })
{
restClient.AddCookie("nexusid", model.CookieNexusId, "/", ".nexusmods.com");
restClient.AddCookie("sid_develop", model.CookieSidDevelop, "/", ".nexusmods.com");
}

var response = await restClient.ExecutePostAsync<TRestResponseModel>(request);
TraceResponse(response, response.Data);
return (TRestResponse)Activator.CreateInstance(typeof(TRestResponse), response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<ToolCommandName>opmu</ToolCommandName>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>Copyright © Digitalroot Technologies 2021 - 2022</Copyright>
<Copyright>Copyright © Digitalroot Technologies 2021 - 2023</Copyright>
<PackageProjectUrl>https://github.com/Digitalroot-Valheim/Digitalroot.OdinPlusModUploader</PackageProjectUrl>
<RepositoryUrl>https://github.com/Digitalroot-Valheim/Digitalroot.OdinPlusModUploader</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand All @@ -29,7 +29,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<VersionPrefix>1.0.12</VersionPrefix>
<VersionPrefix>1.1.0</VersionPrefix>
<RestoreAdditionalProjectSources>
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json;
https://digitalroot-valheim-nuget.s3.us-west-2.amazonaws.com/index.json
Expand All @@ -48,13 +48,13 @@

<ItemGroup>
<None Include="..\ico\ValheimRcon_color.png" Pack="true" PackagePath="\" />
<None Include="..\..\docs\README.md" Pack="true" PackagePath="\"/>
<None Include="..\..\docs\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Pastel" Version="3.0.0" />
<PackageReference Include="RestSharp" Version="106.13.0" />
<PackageReference Include="RestSharp.Serializers.NewtonsoftJson" Version="106.13.0" />
<PackageReference Include="Pastel" Version="4.0.2" />
<PackageReference Include="RestSharp" Version="108.0.3" />
<PackageReference Include="RestSharp.Serializers.NewtonsoftJson" Version="108.0.3" />
<PackageReference Include="System.CommandLine" Version="[2.0.0-beta1.21568.1]" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ namespace Digitalroot.OdinPlusModUploader.Models;

public class ErrorResponseModel : AbstractResponseModel
{
private readonly IRestResponse _restResponse;
private readonly RestResponse _restResponse;

public string ErrorMessage => _restResponse.ErrorMessage;
public Exception ErrorException => _restResponse.ErrorException;
public bool IsSet => !string.IsNullOrEmpty(ErrorMessage) || ErrorException != null;

public ErrorResponseModel(IRestResponse restResponse)
public ErrorResponseModel(RestResponse restResponse)
{
_restResponse = restResponse;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Digitalroot.OdinPlusModUploader.Protocol;

public abstract class AbstractResponse
{
private readonly IRestResponse _restResponse;
private readonly RestResponse _restResponse;

internal string Content => _restResponse.Content;
internal bool IsSuccessful => _restResponse.IsSuccessful;
Expand All @@ -16,9 +16,9 @@ public abstract class AbstractResponse
internal HttpStatusCode StatusCode => _restResponse.StatusCode;

[Newtonsoft.Json.JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
internal IRestResponse RawRestResponse => _restResponse;
internal RestResponse RawRestResponse => _restResponse;

private protected AbstractResponse(IRestResponse response)
private protected AbstractResponse(RestResponse response)
{
_restResponse = response;
}
Expand All @@ -30,7 +30,7 @@ internal abstract class AbstractResponse<T> : AbstractResponse
internal T Data { get; }

/// <inheritdoc />
protected AbstractResponse(IRestResponse<T> response)
protected AbstractResponse(RestResponse<T> response)
: base(response)
{
Data = response.Data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Threading.Tasks;

Expand All @@ -21,8 +22,9 @@ internal static ICommand GetCheckCommand()
{
var command = new Command("check", "Check that an API Key and Cookie are valid.")
{
CommandHelper.GetOption(new[] { "--key", "-k" }, "Api Key, ENV: " + "NEXUSMOD_API_KEY".Pastel(ColorOptions.EmColor), CommandUtils.RestClient.GetDefaultConfigValue("NEXUSMOD_API_KEY"), optionValidatorsFactory: Digitalroot.OdinPlusModUploader.Provider.NexusMods.Validators.ValidatorsFactory.Instance) as Option ?? throw new InvalidOperationException()
, CommandHelper.GetOption(new[] { "--cookie", "-c" }, "Session Cookie, ENV: " + "NEXUSMOD_COOKIE".Pastel(ColorOptions.EmColor), CommandUtils.RestClient.GetDefaultConfigValue("NEXUSMOD_COOKIE"), optionValidatorsFactory: Digitalroot.OdinPlusModUploader.Provider.NexusMods.Validators.ValidatorsFactory.Instance) as Option ?? throw new InvalidOperationException()
CommandHelper.GetOption(new[] { "--key", "-k" }, "Api Key, ENV: " + "NEXUSMOD_API_KEY".Pastel(ColorOptions.EmColor), CommandUtils.RestClient.GetDefaultConfigValue("NEXUSMOD_API_KEY"), optionValidatorsFactory: ValidatorsFactory.Instance) as Option ?? throw new InvalidOperationException()
, CommandHelper.GetOption(new[] { "--cookie_nexusid", "-cnxid" }, "Session Cookie, ENV: " + "NEXUSMOD_COOKIE_NEXUSID".Pastel(ColorOptions.EmColor), CommandUtils.RestClient.GetDefaultConfigValue("NEXUSMOD_COOKIE_NEXUSID"), optionValidatorsFactory: ValidatorsFactory.Instance) as Option ?? throw new InvalidOperationException()
, CommandHelper.GetOption(new[] { "--cookie_sid_develop", "-csid" }, "Session Cookie, ENV: " + "NEXUSMOD_COOKIE_SID_DEVELOP".Pastel(ColorOptions.EmColor), CommandUtils.RestClient.GetDefaultConfigValue("NEXUSMOD_COOKIE_SID_DEVELOP"), optionValidatorsFactory: ValidatorsFactory.Instance) as Option ?? throw new InvalidOperationException()
};

command.Handler = GetCommandHandler();
Expand All @@ -33,17 +35,17 @@ internal static ICommand GetCheckCommand()

private static ICommandHandler GetCommandHandler()
{
return CommandHandler.Create<string, string>(async (key, cookie) =>
return CommandHandler.Create<string, string, string>(async (key, cookie_nexusid, cookie_sid_develop) =>
{
var checkApiKeyMessage = await CheckApiKey(key);
Console.WriteLine(checkApiKeyMessage.Response.IsApiKeyValid ? "API key successfully validated!".Pastel(Color.Green) : "API key validation failed!".Pastel(Color.Orange));

var checkCookieMessage = await CheckCookie(cookie);
var checkCookieMessage = await CheckCookie(cookie_nexusid, cookie_sid_develop);
Console.WriteLine(checkCookieMessage.ResponseModel.IsCookieValid ? "Cookies successfully validated!".Pastel(Color.Green) : "Cookie validation failed!".Pastel(Color.Orange));
});
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Consistent Pattern")]
[SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Consistent Pattern")]
private static async Task<Message<
CheckApiKeyRequest, ApiKeyRequestModel,
CheckApiKeyResponse, UserResponseModel
Expand All @@ -57,14 +59,14 @@ private static async Task<Message<
return message;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Consistent Pattern")]
[SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Consistent Pattern")]
private static async Task<Message<
CheckCookieRequest, CookieRequestModel,
CheckCookieResponse, CheckCookieResponseModel
>> CheckCookie(string cookie)
>> CheckCookie(string cookieNexusId, string cookieSidDevelop)
{
var message = new Message<CheckCookieRequest, CookieRequestModel, CheckCookieResponse, CheckCookieResponseModel>();
message.RequestModel = new CookieRequestModel(cookie);
message.RequestModel = new CookieRequestModel(cookieNexusId, cookieSidDevelop);
message.Request = new CheckCookieRequest(message.RequestModel);
message.Response = await CommandUtils.RestClient.ExecuteAsync(message.Request);
message.ResponseModel = new CheckCookieResponseModel(message.Response);
Expand Down
Loading

0 comments on commit 24ee6de

Please sign in to comment.