diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 74954cf4..c51d164b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -24,7 +24,7 @@ Exact steps which reproduce the bug, if possible. For example: 4. Error occurs. **Log file** -Upload your SMAPI log to https://log.smapi.io and post a link here. +Upload your SMAPI log to https://smapi.io/log and post a link here. **Screenshots** If applicable, add screenshots to help explain your problem. diff --git a/.gitignore b/.gitignore index 7080a8fc..65695211 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,7 @@ _ReSharper*/ appsettings.Development.json # AWS generated files -src/SMAPI.Web/aws-beanstalk-tools-defaults.json +src/SMAPI.Web.LegacyRedirects/aws-beanstalk-tools-defaults.json + +# Azure generated files +src/SMAPI.Web/Properties/PublishProfiles/smapi-web-release - Web Deploy.pubxml diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index e5286bf5..4297756d 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -63,7 +63,7 @@ - + diff --git a/docs/release-notes.md b/docs/release-notes.md index c3840148..dcce70c9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -110,7 +110,7 @@ For modders: * Clicking a mod link now automatically adds it to the visible mods if the list is filtered. * JSON validator: - * Added JSON validator at [json.smapi.io](https://json.smapi.io), which lets you validate a JSON file against predefined mod formats. + * Added JSON validator at [smapi.io/json](https://smapi.io/json), which lets you validate a JSON file against predefined mod formats. * Added support for the `manifest.json` format. * Added support for the Content Patcher format (thanks to TehPers!). * Added support for referencing a schema in a JSON Schema-compatible text editor. @@ -373,7 +373,7 @@ Released 19 November 2018 for Stardew Valley 1.3.32. * Updated compatibility list. * For the web UI: - * Added a [mod compatibility page](https://mods.smapi.io) and [privacy page](https://smapi.io/privacy). + * Added a [mod compatibility page](https://smapi.io/mods) and [privacy page](https://smapi.io/privacy). * The log parser now has a separate filter for game messages. * The log parser now shows content pack authors (thanks to danvolchek!). * Tweaked log parser UI (thanks to danvolchek!). @@ -557,7 +557,7 @@ Released 11 April 2018 for Stardew Valley 1.2.30–1.2.33. * Fixed error when two content packs use different capitalization for the same required mod ID. * Fixed rare crash if the game duplicates an item. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Tweaked UI. ## 2.5.4 @@ -569,7 +569,7 @@ Released 26 March 2018 for Stardew Valley 1.2.30–1.2.33. * Fixed error when mods remove an asset editor/loader. * Fixed minimum game version incorrectly increased in SMAPI 2.5.3. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Fixed error when log text contains certain tokens. * For modders: @@ -591,7 +591,7 @@ Released 13 March 2018 for Stardew Valley ~~1.2.30~~–1.2.33. * Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order. * Updated compatibility list and added update checks for more mods. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Fixed incorrect filtering in some cases. * Fixed error if mods have duplicate names. * Fixed parse bugs if a mod has no author name. @@ -605,7 +605,7 @@ Released 25 February 2018 for Stardew Valley 1.2.30–1.2.33. * For modders: * Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Fixed blank page after uploading a log in some cases. ## 2.5.1 @@ -636,7 +636,7 @@ Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33. * Fixed unhelpful error when a translation file has duplicate keys due to case-insensitivity. * Fixed some JSON field names being case-sensitive. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Added support for SMAPI 2.5 content packs. * Reduced download size when viewing a parsed log with repeated errors. * Improved parse error handling. @@ -657,7 +657,7 @@ Released 24 January 2018 for Stardew Valley 1.2.30–1.2.33. * Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save. * Fixed compatibility with Linux Terminator terminal. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Fixed error parsing logs with zero installed mods. * For modders: @@ -692,7 +692,7 @@ Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33. * Fixed issue where a mod could change the cursor position reported to other mods. * Updated compatibility list. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Fixed broken favicon. ## 2.2 @@ -706,7 +706,7 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33. * Improved error when a mod has an invalid `EntryDLL` filename format. * Updated compatibility list. -* For the [log parser](https://log.smapi.io): +* For the [log parser](https://smapi.io/log): * Logs no longer expire after a week. * Fixed error when uploading very large logs. * Slightly improved the UI. @@ -721,7 +721,7 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33. Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33. * For players: - * Added a [log parser](https://log.smapi.io) site. + * Added a [log parser](https://smapi.io/log) site. * Added better Steam instructions to the SMAPI installer. * Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer. * Removed the game's test messages from the console log. diff --git a/docs/technical/web.md b/docs/technical/web.md index 78d93625..97e0704a 100644 --- a/docs/technical/web.md +++ b/docs/technical/web.md @@ -14,13 +14,13 @@ and update check API. ## Log parser The log parser provides a web UI for uploading, parsing, and sharing SMAPI logs. The logs are -persisted in a compressed form to Pastebin. The log parser lives at https://log.smapi.io. +persisted in a compressed form to Pastebin. The log parser lives at https://smapi.io/log. ## JSON validator ### Overview The JSON validator provides a web UI for uploading and sharing JSON files, and validating them as plain JSON or against a predefined format like `manifest.json` or Content Patcher's `content.json`. -The JSON validator lives at https://json.smapi.io. +The JSON validator lives at https://smapi.io/json. ### Schema file format Schema files are defined in `wwwroot/schemas` using the [JSON Schema](https://json-schema.org/) @@ -111,7 +111,7 @@ format | schema URL ## Web API ### Overview -SMAPI provides a web API at `api.smapi.io` for use by SMAPI and external tools. The URL includes a +SMAPI provides a web API at `smapi.io/api` for use by SMAPI and external tools. The URL includes a `{version}` token, which is the SMAPI version for backwards compatibility. This API is publicly accessible but not officially released; it may change at any time. @@ -184,7 +184,7 @@ may be useful to external tools. Example request: ```js -POST https://api.smapi.io/v3.0/mods +POST https://smapi.io/api/v3.0/mods { "mods": [ { @@ -350,8 +350,7 @@ To launch the environment: mongod --dbpath C:\dev\smapi-cache ``` 2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site. - (Local URLs will use HTTP instead of HTTPS, and subdomains will become routes, like - `log.smapi.io` → `localhost:59482/log`.) + (Local URLs will use HTTP instead of HTTPS.) ### Production environment A production environment includes the web servers and cache database hosted online for public @@ -367,7 +366,6 @@ Initial setup: ------------------------------- | ----------------- `LogParser:PastebinDevKey` | The [Pastebin developer key](https://pastebin.com/api#1) used to authenticate with the Pastebin API. `LogParser:PastebinUserKey` | The [Pastebin user key](https://pastebin.com/api#8) used to authenticate with the Pastebin API. Can be left blank to post anonymously. - `LogParser:SectionUrl` | The root URL of the log page, like `https://log.smapi.io/`. `ModUpdateCheck:GitHubPassword` | The password with which to authenticate to GitHub when fetching release info. `ModUpdateCheck:GitHubUsername` | The username with which to authenticate to GitHub when fetching release info. `MongoDB:Host` | The hostname for the MongoDB instance. diff --git a/src/SMAPI.Web.LegacyRedirects/Controllers/ModsApiController.cs b/src/SMAPI.Web.LegacyRedirects/Controllers/ModsApiController.cs new file mode 100644 index 00000000..44ed0b6b --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/Controllers/ModsApiController.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Pathoschild.Http.Client; +using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; + +namespace SMAPI.Web.LegacyRedirects.Controllers +{ + /// Provides an API to perform mod update checks. + [ApiController] + [Produces("application/json")] + [Route("api/v{version}/mods")] + public class ModsApiController : Controller + { + /********* + ** Public methods + *********/ + /// Fetch version metadata for the given mods. + /// The mod search criteria. + [HttpPost] + public async Task> PostAsync([FromBody] ModSearchModel model) + { + using IClient client = new FluentClient("https://smapi.io/api"); + + Startup.ConfigureJsonNet(client.Formatters.JsonFormatter.SerializerSettings); + + return await client + .PostAsync(this.Request.Path) + .WithBody(model) + .AsArray(); + } + } +} diff --git a/src/SMAPI.Web.LegacyRedirects/Framework/LambdaRewriteRule.cs b/src/SMAPI.Web.LegacyRedirects/Framework/LambdaRewriteRule.cs new file mode 100644 index 00000000..e5138e5c --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/Framework/LambdaRewriteRule.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Rewrite; + +namespace SMAPI.Web.LegacyRedirects.Framework +{ + /// Rewrite requests to prepend the subdomain portion (if any) to the path. + /// Derived from . + internal class LambdaRewriteRule : IRule + { + /********* + ** Accessors + *********/ + /// Rewrite an HTTP request if needed. + private readonly Action Rewrite; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Rewrite an HTTP request if needed. + public LambdaRewriteRule(Action rewrite) + { + this.Rewrite = rewrite ?? throw new ArgumentNullException(nameof(rewrite)); + } + + /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). + /// The rewrite context. + public void ApplyRule(RewriteContext context) + { + HttpRequest request = context.HttpContext.Request; + HttpResponse response = context.HttpContext.Response; + this.Rewrite(context, request, response); + } + } +} diff --git a/src/SMAPI.Web.LegacyRedirects/Program.cs b/src/SMAPI.Web.LegacyRedirects/Program.cs new file mode 100644 index 00000000..6adee877 --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/Program.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace SMAPI.Web.LegacyRedirects +{ + /// The main app entry point. + public class Program + { + /********* + ** Public methods + *********/ + /// The main app entry point. + /// The command-line arguments. + public static void Main(string[] args) + { + Host + .CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(builder => builder.UseStartup()) + .Build() + .Run(); + } + } +} diff --git a/src/SMAPI.Web.LegacyRedirects/Properties/launchSettings.json b/src/SMAPI.Web.LegacyRedirects/Properties/launchSettings.json new file mode 100644 index 00000000..e9a1b210 --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:52756", + "sslPort": 0 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SMAPI.Web.LegacyRedirects": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/src/SMAPI.Web.LegacyRedirects/SMAPI.Web.LegacyRedirects.csproj b/src/SMAPI.Web.LegacyRedirects/SMAPI.Web.LegacyRedirects.csproj new file mode 100644 index 00000000..a3d5c2b6 --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/SMAPI.Web.LegacyRedirects.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.0 + + + + + + + + + + + + + + + + + diff --git a/src/SMAPI.Web.LegacyRedirects/Startup.cs b/src/SMAPI.Web.LegacyRedirects/Startup.cs new file mode 100644 index 00000000..4af51575 --- /dev/null +++ b/src/SMAPI.Web.LegacyRedirects/Startup.cs @@ -0,0 +1,94 @@ +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Rewrite; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using SMAPI.Web.LegacyRedirects.Framework; +using StardewModdingAPI.Toolkit.Serialization; + +namespace SMAPI.Web.LegacyRedirects +{ + /// The web app startup configuration. + public class Startup + { + /********* + ** Public methods + *********/ + /// The method called by the runtime to add services to the container. + /// The service injection container. + public void ConfigureServices(IServiceCollection services) + { + services + .AddControllers() + .AddNewtonsoftJson(options => Startup.ConfigureJsonNet(options.SerializerSettings)); + } + + /// The method called by the runtime to configure the HTTP request pipeline. + /// The application builder. + /// The hosting environment. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + + app + .UseRewriter(this.GetRedirectRules()) + .UseRouting() + .UseAuthorization() + .UseEndpoints(endpoints => endpoints.MapControllers()); + } + + /// Configure a Json.NET serializer. + /// The serializer settings to edit. + internal static void ConfigureJsonNet(JsonSerializerSettings settings) + { + foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters) + settings.Converters.Add(converter); + + settings.Formatting = Formatting.Indented; + settings.NullValueHandling = NullValueHandling.Ignore; + } + + + /********* + ** Private methods + *********/ + /// Get the redirect rules to apply. + private RewriteOptions GetRedirectRules() + { + var redirects = new RewriteOptions(); + + redirects.Add( + new LambdaRewriteRule((context, request, response) => + { + string host = request.Host.Host; + + // map API requests to proxy + // This is needed because the low-level HTTP client SMAPI uses for Linux/Mac compatibility doesn't support redirects. + if (host == "api.smapi.io") + { + request.Path = $"/api{request.Path}"; + return; + } + + // redirect other requests to Azure + string newRoot = host switch + { + "api.smapi.io" => "smapi.io/api", + "json.smapi.io" => "smapi.io/json", + "log.smapi.io" => "smapi.io/log", + "mods.smapi.io" => "smapi.io/mods", + _ => "smapi.io" + }; + response.StatusCode = (int)HttpStatusCode.PermanentRedirect; + response.Headers["Location"] = $"{(request.IsHttps ? "https" : "http")}://{newRoot}{request.PathBase}{request.Path}{request.QueryString}"; + context.Result = RuleResult.EndResponse; + }) + ); + + return redirects; + } + } +} diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index b2eb9a87..c32fb084 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -5,14 +5,12 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; using StardewModdingAPI.Web.Framework; using StardewModdingAPI.Web.Framework.Clients.Pastebin; using StardewModdingAPI.Web.Framework.Compression; -using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.ViewModels.JsonValidator; namespace StardewModdingAPI.Web.Controllers @@ -23,18 +21,12 @@ namespace StardewModdingAPI.Web.Controllers /********* ** Fields *********/ - /// The site config settings. - private readonly SiteConfig Config; - /// The underlying Pastebin client. private readonly IPastebinClient Pastebin; /// The underlying text compression helper. private readonly IGzipHelper GzipHelper; - /// The section URL for the schema validator. - private string SectionUrl => this.Config.JsonValidatorUrl; - /// The supported JSON schemas (names indexed by ID). private readonly IDictionary SchemaFormats = new Dictionary { @@ -57,12 +49,10 @@ namespace StardewModdingAPI.Web.Controllers ** Constructor ***/ /// Construct an instance. - /// The context config settings. /// The Pastebin API client. /// The underlying text compression helper. - public JsonValidatorController(IOptions siteConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) + public JsonValidatorController(IPastebinClient pastebin, IGzipHelper gzipHelper) { - this.Config = siteConfig.Value; this.Pastebin = pastebin; this.GzipHelper = gzipHelper; } @@ -81,7 +71,7 @@ namespace StardewModdingAPI.Web.Controllers { schemaName = this.NormalizeSchemaName(schemaName); - var result = new JsonValidatorModel(this.SectionUrl, id, schemaName, this.SchemaFormats); + var result = new JsonValidatorModel(id, schemaName, this.SchemaFormats); if (string.IsNullOrWhiteSpace(id)) return this.View("Index", result); @@ -142,7 +132,7 @@ namespace StardewModdingAPI.Web.Controllers public async Task PostAsync(JsonValidatorRequestModel request) { if (request == null) - return this.View("Index", new JsonValidatorModel(this.SectionUrl, null, null, this.SchemaFormats).SetUploadError("The request seems to be invalid.")); + return this.View("Index", new JsonValidatorModel(null, null, this.SchemaFormats).SetUploadError("The request seems to be invalid.")); // normalize schema name string schemaName = this.NormalizeSchemaName(request.SchemaName); @@ -150,7 +140,7 @@ namespace StardewModdingAPI.Web.Controllers // get raw log text string input = request.Content; if (string.IsNullOrWhiteSpace(input)) - return this.View("Index", new JsonValidatorModel(this.SectionUrl, null, schemaName, this.SchemaFormats).SetUploadError("The JSON file seems to be empty.")); + return this.View("Index", new JsonValidatorModel(null, schemaName, this.SchemaFormats).SetUploadError("The JSON file seems to be empty.")); // upload log input = this.GzipHelper.CompressString(input); @@ -158,12 +148,10 @@ namespace StardewModdingAPI.Web.Controllers // handle errors if (!result.Success) - return this.View("Index", new JsonValidatorModel(this.SectionUrl, result.ID, schemaName, this.SchemaFormats).SetUploadError($"Pastebin error: {result.Error ?? "unknown error"}")); + return this.View("Index", new JsonValidatorModel(result.ID, schemaName, this.SchemaFormats).SetUploadError($"Pastebin error: {result.Error ?? "unknown error"}")); // redirect to view - UriBuilder uri = new UriBuilder(new Uri(this.SectionUrl)); - uri.Path = $"{uri.Path.TrimEnd('/')}/{schemaName}/{result.ID}"; - return this.Redirect(uri.Uri.ToString()); + return this.Redirect(this.Url.Action("Index", "LogParser", new { schemaName = schemaName, id = result.ID })); } diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs index 32c45038..2ced5a05 100644 --- a/src/SMAPI.Web/Controllers/LogParserController.cs +++ b/src/SMAPI.Web/Controllers/LogParserController.cs @@ -27,9 +27,6 @@ namespace StardewModdingAPI.Web.Controllers /********* ** Fields *********/ - /// The site config settings. - private readonly SiteConfig SiteConfig; - /// The API client settings. private readonly ApiClientsConfig ClientsConfig; @@ -47,13 +44,11 @@ namespace StardewModdingAPI.Web.Controllers ** Constructor ***/ /// Construct an instance. - /// The context config settings. /// The API client settings. /// The Pastebin API client. /// The underlying text compression helper. - public LogParserController(IOptions siteConfig, IOptions clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) + public LogParserController(IOptions clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper) { - this.SiteConfig = siteConfig.Value; this.ClientsConfig = clientsConfig.Value; this.Pastebin = pastebin; this.GzipHelper = gzipHelper; @@ -103,9 +98,7 @@ namespace StardewModdingAPI.Web.Controllers return this.View("Index", this.GetModel(null, uploadError: uploadResult.UploadError)); // redirect to view - UriBuilder uri = new UriBuilder(new Uri(this.SiteConfig.LogParserUrl)); - uri.Path = $"{uri.Path.TrimEnd('/')}/{uploadResult.ID}"; - return this.Redirect(uri.Uri.ToString()); + return this.Redirect(this.Url.Action("Index", "LogParser", new { id = uploadResult.ID })); } @@ -217,10 +210,9 @@ namespace StardewModdingAPI.Web.Controllers /// An error which occurred while uploading the log. private LogParserModel GetModel(string pasteID, DateTime? expiry = null, string uploadWarning = null, string uploadError = null) { - string sectionUrl = this.SiteConfig.LogParserUrl; Platform? platform = this.DetectClientPlatform(); - return new LogParserModel(sectionUrl, pasteID, platform) + return new LogParserModel(pasteID, platform) { UploadWarning = uploadWarning, UploadError = uploadError, diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index fe220eb5..f10e0067 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -49,9 +49,6 @@ namespace StardewModdingAPI.Web.Controllers /// The internal mod metadata list. private readonly ModDatabase ModDatabase; - /// The web URL for the compatibility list. - private readonly string CompatibilityPageUrl; - /********* ** Public methods @@ -70,7 +67,6 @@ namespace StardewModdingAPI.Web.Controllers { this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json")); ModUpdateCheckConfig config = configProvider.Value; - this.CompatibilityPageUrl = config.CompatibilityPageUrl; this.WikiCache = wikiCache; this.ModCache = modCache; @@ -205,7 +201,7 @@ namespace StardewModdingAPI.Web.Controllers // get unofficial version if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, optional?.Version)) - unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}"); + unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.Url.Action("Index", "Mods")}#{wikiEntry.Anchor}"); // get unofficial version for beta if (wikiEntry?.HasBetaInfo == true) @@ -215,7 +211,7 @@ namespace StardewModdingAPI.Web.Controllers if (wikiEntry.BetaCompatibility.UnofficialVersion != null) { unofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, optional?.Version)) - ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}") + ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.Url.Action("Index", "Mods")}#{wikiEntry.Anchor}") : null; } else diff --git a/src/SMAPI.Web/Framework/BeanstalkEnvPropsConfigProvider.cs b/src/SMAPI.Web/Framework/BeanstalkEnvPropsConfigProvider.cs deleted file mode 100644 index fe27fe2f..00000000 --- a/src/SMAPI.Web/Framework/BeanstalkEnvPropsConfigProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace StardewModdingAPI.Web.Framework -{ - /// Reads configuration values from the AWS Beanstalk environment properties file (if present). - /// This is a workaround for AWS Beanstalk injection not working with .NET Core apps. - internal class BeanstalkEnvPropsConfigProvider : ConfigurationProvider, IConfigurationSource - { - /********* - ** Fields - *********/ - /// The absolute path to the container configuration file on an Amazon EC2 instance. - private const string ContainerConfigPath = @"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration"; - - - /********* - ** Public methods - *********/ - /// Build the configuration provider for this source. - /// The configuration builder. - public IConfigurationProvider Build(IConfigurationBuilder builder) - { - return new BeanstalkEnvPropsConfigProvider(); - } - - /// Load the environment properties. - public override void Load() - { - this.Data = new Dictionary(StringComparer.OrdinalIgnoreCase); - - // get Beanstalk config file - FileInfo file = new FileInfo(BeanstalkEnvPropsConfigProvider.ContainerConfigPath); - if (!file.Exists) - return; - - // parse JSON - JObject jsonRoot = (JObject)JsonConvert.DeserializeObject(File.ReadAllText(file.FullName)); - if (jsonRoot["iis"]?["env"] is JArray jsonProps) - { - foreach (string prop in jsonProps.Values()) - { - string[] parts = prop.Split('=', 2); // key=value - if (parts.Length == 2) - this.Data[parts[0]] = parts[1]; - } - } - } - } -} diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index ab935bb3..46073eb8 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -11,8 +11,5 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The number of minutes failed update checks should be cached before refetching them. public int ErrorCacheMinutes { get; set; } - - /// The web URL for the wiki compatibility list. - public string CompatibilityPageUrl { get; set; } } } diff --git a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs index bc6e868a..d379c423 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs @@ -6,18 +6,6 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /********* ** Accessors *********/ - /// The root URL for the app. - public string RootUrl { get; set; } - - /// The root URL for the log parser. - public string LogParserUrl { get; set; } - - /// The root URL for the JSON validator. - public string JsonValidatorUrl { get; set; } - - /// The root URL for the mod list. - public string ModListUrl { get; set; } - /// Whether to show SMAPI beta versions on the main page, if any. public bool BetaEnabled { get; set; } diff --git a/src/SMAPI.Web/Framework/RewriteRules/ConditionalRewriteSubdomainRule.cs b/src/SMAPI.Web/Framework/RewriteRules/ConditionalRewriteSubdomainRule.cs deleted file mode 100644 index 920632ab..00000000 --- a/src/SMAPI.Web/Framework/RewriteRules/ConditionalRewriteSubdomainRule.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Rewrite; - -namespace StardewModdingAPI.Web.Framework.RewriteRules -{ - /// Rewrite requests to prepend the subdomain portion (if any) to the path. - /// Derived from . - internal class ConditionalRewriteSubdomainRule : IRule - { - /********* - ** Accessors - *********/ - /// A predicate which indicates when the rule should be applied. - private readonly Func ShouldRewrite; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// A predicate which indicates when the rule should be applied. - public ConditionalRewriteSubdomainRule(Func shouldRewrite = null) - { - this.ShouldRewrite = shouldRewrite ?? (req => true); - } - - /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). - /// The rewrite context. - public void ApplyRule(RewriteContext context) - { - HttpRequest request = context.HttpContext.Request; - - // check condition - if (!this.ShouldRewrite(request)) - return; - - // get host parts - string host = request.Host.Host; - string[] parts = host.Split('.'); - if (parts.Length < 2) - return; - - // prepend to path - request.Path = $"/{parts[0]}{request.Path}"; - } - } -} diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index fc6161b5..53823771 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -48,7 +48,7 @@ namespace StardewModdingAPI.Web .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .Add(new BeanstalkEnvPropsConfigProvider()) + .AddEnvironmentVariables() .Build(); } @@ -173,8 +173,7 @@ namespace StardewModdingAPI.Web .UseCors(policy => policy .AllowAnyHeader() .AllowAnyMethod() - .WithOrigins("https://smapi.io", "https://*.smapi.io", "https://*.edge.smapi.io") - .SetIsOriginAllowedToAllowWildcardSubdomains() + .WithOrigins("https://smapi.io") ) .UseRewriter(this.GetRedirectRules()) .UseStaticFiles() // wwwroot folder @@ -202,23 +201,13 @@ namespace StardewModdingAPI.Web shouldRewrite: req => req.Host.Host != "localhost" && !req.Path.StartsWithSegments("/api") - && !req.Host.Host.StartsWith("api.") - )); - - // convert subdomain.smapi.io => smapi.io/subdomain for routing - redirects.Add(new ConditionalRewriteSubdomainRule( - shouldRewrite: req => - req.Host.Host != "localhost" - && (req.Host.Host.StartsWith("api.") || req.Host.Host.StartsWith("json.") || req.Host.Host.StartsWith("log.") || req.Host.Host.StartsWith("mods.")) - && !req.Path.StartsWithSegments("/content") - && !req.Path.StartsWithSegments("/favicon.ico") )); // shortcut redirects redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0")); redirects.Add(new RedirectToUrlRule(@"^/(?:buildmsg|package)(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md#$1")); // buildmsg deprecated, remove when SDV 1.4 is released redirects.Add(new RedirectToUrlRule(@"^/community\.?$", "https://stardewvalleywiki.com/Modding:Community")); - redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://mods.smapi.io")); + redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://smapi.io/mods")); redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index")); redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI")); redirects.Add(new RedirectToUrlRule(@"^/troubleshoot(.*)$", "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1")); diff --git a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs index 2d13bf23..5b18331f 100644 --- a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs +++ b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs @@ -9,9 +9,6 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator /********* ** Accessors *********/ - /// The root URL for the log parser controller. - public string SectionUrl { get; set; } - /// The paste ID. public string PasteID { get; set; } @@ -44,13 +41,11 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator public JsonValidatorModel() { } /// Construct an instance. - /// The root URL for the log parser controller. /// The paste ID. /// The schema name with which the JSON was validated. /// The supported JSON schemas (names indexed by ID). - public JsonValidatorModel(string sectionUrl, string pasteID, string schemaName, IDictionary schemaFormats) + public JsonValidatorModel(string pasteID, string schemaName, IDictionary schemaFormats) { - this.SectionUrl = sectionUrl; this.PasteID = pasteID; this.SchemaName = schemaName; this.SchemaFormats = schemaFormats; diff --git a/src/SMAPI.Web/ViewModels/LogParserModel.cs b/src/SMAPI.Web/ViewModels/LogParserModel.cs index b06b5b2d..bea79eae 100644 --- a/src/SMAPI.Web/ViewModels/LogParserModel.cs +++ b/src/SMAPI.Web/ViewModels/LogParserModel.cs @@ -20,9 +20,6 @@ namespace StardewModdingAPI.Web.ViewModels /********* ** Accessors *********/ - /// The root URL for the log parser controller. - public string SectionUrl { get; set; } - /// The paste ID. public string PasteID { get; set; } @@ -55,12 +52,10 @@ namespace StardewModdingAPI.Web.ViewModels public LogParserModel() { } /// Construct an instance. - /// The root URL for the log parser controller. /// The paste ID. /// The viewer's detected OS, if known. - public LogParserModel(string sectionUrl, string pasteID, Platform? platform) + public LogParserModel(string pasteID, Platform? platform) { - this.SectionUrl = sectionUrl; this.PasteID = pasteID; this.DetectedPlatform = platform; this.ParsedLog = null; diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml index f42dde3b..a79a08f5 100644 --- a/src/SMAPI.Web/Views/Index/Index.cshtml +++ b/src/SMAPI.Web/Views/Index/Index.cshtml @@ -44,14 +44,14 @@ }

Get help

@@ -61,7 +61,7 @@
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
-

See the release notes and mod compatibility list for more info.

+

See the release notes and mod compatibility list for more info.

} else { @@ -70,13 +70,13 @@ else
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
-

See the release notes and mod compatibility list for more info.

+

See the release notes and mod compatibility list for more info.

SMAPI @Model.BetaVersion.Version?

@Html.Raw(Markdig.Markdown.ToHtml(Model.BetaVersion.Description))
-

See the release notes and mod compatibility list for more info.

+

See the release notes and mod compatibility list for more info.

} diff --git a/src/SMAPI.Web/Views/Index/Privacy.cshtml b/src/SMAPI.Web/Views/Index/Privacy.cshtml index 914384a8..fe4d773a 100644 --- a/src/SMAPI.Web/Views/Index/Privacy.cshtml +++ b/src/SMAPI.Web/Views/Index/Privacy.cshtml @@ -8,7 +8,7 @@ } -← back to SMAPI page +← back to SMAPI page

SMAPI is an open-source and non-profit project. Your privacy is important, so this page explains what information SMAPI uses and transmits. This page is informational only, it's not a legal document.

@@ -34,7 +34,7 @@

Log parser

-

The log parser page lets you store a log file for analysis and sharing. The log data is stored indefinitely in an obfuscated form as unlisted pastes in Pastebin. No personal information is stored by the log parser beyond what you choose to upload, but see web logging and the Pastebin Privacy Statement.

+

The log parser page lets you store a log file for analysis and sharing. The log data is stored indefinitely in an obfuscated form as unlisted pastes in Pastebin. No personal information is stored by the log parser beyond what you choose to upload, but see web logging and the Pastebin Privacy Statement.

Multiplayer sync

As part of its multiplayer API, SMAPI transmits basic context to players you connect to (mainly your OS, SMAPI version, game version, and installed mods). This is used to enable multiplayer features like inter-mod messages, compatibility checks, etc. Although this information is normally hidden from players, it may be visible due to mods or configuration changes.

diff --git a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml index 3143fad9..41dec929 100644 --- a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml +++ b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml @@ -3,8 +3,8 @@ @{ // get view data - string curPageUrl = new Uri(new Uri(Model.SectionUrl), $"{Model.SchemaName}/{Model.PasteID}").ToString(); - string newUploadUrl = Model.SchemaName != null ? new Uri(new Uri(Model.SectionUrl), Model.SchemaName).ToString() : Model.SectionUrl; + string curPageUrl = this.Url.Action("Index", "JsonValidator", new { schemaName = Model.SchemaName, id = Model.PasteID }); + string newUploadUrl = this.Url.Action("Index", "JsonValidator", new { schemaName = Model.SchemaName }); string schemaDisplayName = null; bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName != "None"; @@ -34,8 +34,8 @@ } @@ -70,7 +70,7 @@ else if (Model.PasteID != null) @if (Model.Content == null) {

Upload a JSON file

-
+
  1. Choose the JSON format:
    diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index df2ac115..07e18421 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -32,7 +32,7 @@ showSections: @Json.Serialize(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, section => false), noFormatting), showLevels: @Json.Serialize(defaultFilters, noFormatting), enableFilters: @Json.Serialize(!Model.ShowRaw) - }, '@Model.SectionUrl'); + }, '@this.Url.Action("Index", "LogParser")'); }); } @@ -49,8 +49,8 @@ else if (Model.ParseError != null) { @@ -58,8 +58,8 @@ else if (Model.ParseError != null) else if (Model.ParsedLog?.IsValid == true) { } @@ -127,7 +127,7 @@ else if (Model.ParsedLog?.IsValid == true)

    How do I share my log?

    - +
    1. Drag the file onto this textbox (or paste the text in):
      @@ -322,12 +322,12 @@ else if (Model.ParsedLog?.IsValid == true) } - view raw log + view raw log } else {
      @Model.ParsedLog.RawText
      - view parsed log + view parsed log } } diff --git a/src/SMAPI.Web/Views/Shared/_Layout.cshtml b/src/SMAPI.Web/Views/Shared/_Layout.cshtml index 87a22f06..e4b6ca94 100644 --- a/src/SMAPI.Web/Views/Shared/_Layout.cshtml +++ b/src/SMAPI.Web/Views/Shared/_Layout.cshtml @@ -15,15 +15,15 @@
      diff --git a/src/SMAPI.Web/appsettings.Development.json b/src/SMAPI.Web/appsettings.Development.json index 8e863591..6b32f4ab 100644 --- a/src/SMAPI.Web/appsettings.Development.json +++ b/src/SMAPI.Web/appsettings.Development.json @@ -9,10 +9,6 @@ */ { "Site": { - "RootUrl": "http://localhost:59482/", - "ModListUrl": "http://localhost:59482/mods/", - "LogParserUrl": "http://localhost:59482/log/", - "JsonValidatorUrl": "http://localhost:59482/json/", "BetaEnabled": false, "BetaBlurb": null }, diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index 3b6f8fbd..f81587ef 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -16,10 +16,6 @@ }, "Site": { - "RootUrl": null, - "ModListUrl": null, - "LogParserUrl": null, - "JsonValidatorUrl": null, "BetaEnabled": null, "BetaBlurb": null }, @@ -72,7 +68,6 @@ "ModUpdateCheck": { "SuccessCacheMinutes": 60, - "ErrorCacheMinutes": 5, - "CompatibilityPageUrl": "https://mods.smapi.io" + "ErrorCacheMinutes": 5 } } diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 73852d30..82e5cc2f 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Toolkit.CoreInterface EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Web", "SMAPI.Web\SMAPI.Web.csproj", "{80EFD92F-728F-41E0-8A5B-9F6F49A91899}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI.Web.LegacyRedirects", "SMAPI.Web.LegacyRedirects\SMAPI.Web.LegacyRedirects.csproj", "{159AA5A5-35C2-488C-B23F-1613C80594AE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13 @@ -131,6 +133,10 @@ Global {80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Debug|Any CPU.Build.0 = Debug|Any CPU {80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Release|Any CPU.ActiveCfg = Release|Any CPU {80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Release|Any CPU.Build.0 = Release|Any CPU + {159AA5A5-35C2-488C-B23F-1613C80594AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {159AA5A5-35C2-488C-B23F-1613C80594AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {159AA5A5-35C2-488C-B23F-1613C80594AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {159AA5A5-35C2-488C-B23F-1613C80594AE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 5ea21710..e73bc47d 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework.ModLoading updateUrls.Add(mod.DataRecord.AlternativeUrl); // default update URL - updateUrls.Add("https://mods.smapi.io"); + updateUrls.Add("https://smapi.io/mods"); // build error string error = $"{reasonPhrase}. Please check for a "; diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index b778af5d..53939f8c 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.Models #endif [nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(), [nameof(GitHubProjectName)] = "Pathoschild/SMAPI", - [nameof(WebApiBaseUrl)] = "https://api.smapi.io", + [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(DumpMetadata)] = false diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index afb82679..f1873391 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -299,7 +299,7 @@ namespace StardewModdingAPI.Framework if (File.Exists(Constants.FatalCrashMarker)) { this.Monitor.Log("The game crashed last time you played. That can be due to bugs in the game, but if it happens repeatedly you can ask for help here: https://community.playstarbound.com/threads/108375/.", LogLevel.Error); - this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://log.smapi.io.", LogLevel.Error); + this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://smapi.io/log.", LogLevel.Error); this.Monitor.Log("Press any key to delete the crash data and continue playing.", LogLevel.Info); Console.ReadKey(); File.Delete(Constants.FatalCrashLog); @@ -962,7 +962,7 @@ namespace StardewModdingAPI.Framework } catch (IncompatibleInstructionException) // details already in trace logs { - string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://mods.smapi.io" }.Where(p => p != null).ToArray(); + string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://smapi.io/mods" }.Where(p => p != null).ToArray(); errorReasonPhrase = $"it's no longer compatible. Please check for a new version at {string.Join(" or ", updateUrls)}"; return false; } diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index bccac678..a7381b91 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -52,7 +52,7 @@ The default values are mirrored in StardewModdingAPI.Framework.Models.SConfig to * Note: the protocol will be changed to http:// on Linux/Mac due to OpenSSL issues with the * game's bundled Mono. */ - "WebApiBaseUrl": "https://api.smapi.io", + "WebApiBaseUrl": "https://smapi.io/api/", /** * Whether SMAPI should log network traffic (may be very verbose). Best combined with VerboseLogging, which includes network metadata.