From 78240b16d36329c9f13da2679a86402e5f675d36 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 4 Feb 2022 23:03:55 -0500 Subject: [PATCH 01/26] Add dependabot support --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..03ec5454 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 1f03daa13c03c399e1011a30b2eee1099a89d2b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Feb 2022 04:04:24 +0000 Subject: [PATCH 02/26] Bump Microsoft.AspNetCore.Mvc.Testing from 6.0.0 to 6.0.1 Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../ProjectLighthouse.Tests.GameApiTests.csproj | 2 +- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 2 +- ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 4a5850d3..912f051b 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 93e88c08..95750c76 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 91fa33ee..25121b6c 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From a27b5671979700932ddff3b2e61a0dd5e926c23c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Feb 2022 04:11:18 +0000 Subject: [PATCH 03/26] Bump coverlet.collector from 3.1.0 to 3.1.1 Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/commits) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../ProjectLighthouse.Tests.GameApiTests.csproj | 2 +- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 2 +- ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 912f051b..d646e3bb 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -20,7 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 95750c76..9b6b4ab1 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 25121b6c..4849cf8f 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 21428c3d17381995d542abef1788d7314c81d1ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Feb 2022 04:12:16 +0000 Subject: [PATCH 04/26] Bump Discord.Net.Webhook from 3.2.0 to 3.2.1 Bumps [Discord.Net.Webhook](https://github.com/Discord-Net/Discord.Net) from 3.2.0 to 3.2.1. - [Release notes](https://github.com/Discord-Net/Discord.Net/releases) - [Changelog](https://github.com/discord-net/Discord.Net/blob/dev/CHANGELOG.md) - [Commits](https://github.com/Discord-Net/Discord.Net/compare/3.2.0...3.2.1) --- updated-dependencies: - dependency-name: Discord.Net.Webhook dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 63c9b307..61e2eb83 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -15,7 +15,7 @@ - + From df257b38c441c5f4418ee1935e907f5143ae9d3d Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 5 Feb 2022 00:42:39 -0500 Subject: [PATCH 05/26] Add support for hcaptcha --- .../.idea/jsLibraryMappings.xml | 2 +- ProjectLighthouse/Helpers/CaptchaHelper.cs | 37 +++++++++++++++++++ ProjectLighthouse/Pages/LoginForm.cshtml | 5 +++ ProjectLighthouse/Pages/LoginForm.cshtml.cs | 19 +++++++++- .../Pages/Partials/CaptchaPartial.cshtml | 6 +++ .../Types/Settings/ServerSettings.cs | 9 ++++- 6 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 ProjectLighthouse/Helpers/CaptchaHelper.cs create mode 100644 ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml diff --git a/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml b/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml index e0f60e14..6f9a9516 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/CaptchaHelper.cs b/ProjectLighthouse/Helpers/CaptchaHelper.cs new file mode 100644 index 00000000..3008b084 --- /dev/null +++ b/ProjectLighthouse/Helpers/CaptchaHelper.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Settings; +using Newtonsoft.Json.Linq; + +namespace LBPUnion.ProjectLighthouse.Helpers; + +public static class CaptchaHelper +{ + private static readonly HttpClient client = new() + { + BaseAddress = new Uri("https://hcaptcha.com"), + }; + + public static async Task Verify(string token) + { + if (!ServerSettings.Instance.HCaptchaEnabled) return true; + + List> payload = new() + { + new("secret", ServerSettings.Instance.HCaptchaSecret), + new("response", token), + }; + + HttpResponseMessage response = await client.PostAsync("/siteverify", new FormUrlEncodedContent(payload)); + + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + + // We only really care about the success result, nothing else that hcaptcha sends us, so lets only parse that. + bool success = bool.Parse(JObject.Parse(responseBody)["success"]?.ToString() ?? "false"); + return success; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml b/ProjectLighthouse/Pages/LoginForm.cshtml index 2e5494a4..f07daa9f 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml +++ b/ProjectLighthouse/Pages/LoginForm.cshtml @@ -50,6 +50,11 @@ + @if (ServerSettings.Instance.HCaptchaEnabled) + { + @await Html.PartialAsync("Partials/CaptchaPartial") + } + @if (ServerSettings.Instance.RegistrationEnabled) { diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index 5e19c5a4..d10d3f7d 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -6,8 +6,10 @@ using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Primitives; namespace LBPUnion.ProjectLighthouse.Pages; @@ -18,8 +20,6 @@ public class LoginForm : BaseLayout public string Error { get; private set; } - public bool WasLoginRequest { get; private set; } - [UsedImplicitly] public async Task OnPost(string username, string password) { @@ -35,6 +35,19 @@ public class LoginForm : BaseLayout return this.Page(); } + if (ServerSettings.Instance.HCaptchaEnabled) + { + // && (!this.Request.Form.TryGetValue("h-captcha-response", out StringValues values) || !await CaptchaHelper.Verify(values[0]))) + bool gotCaptcha = this.Request.Form.TryGetValue("h-captcha-response", out StringValues values); + string? token = gotCaptcha ? values[0] : null; + + if (token == null || !await CaptchaHelper.Verify(token)) + { + this.Error = "You must solve the captcha correctly."; + return this.Page(); + } + } + User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); if (user == null) { @@ -68,6 +81,8 @@ public class LoginForm : BaseLayout this.Response.Cookies.Append("LighthouseToken", webToken.UserToken); + Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LoggerLevelLogin.Instance); + if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); return this.RedirectToPage(nameof(LandingPage)); diff --git a/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml b/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml new file mode 100644 index 00000000..e1891722 --- /dev/null +++ b/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml @@ -0,0 +1,6 @@ +@using LBPUnion.ProjectLighthouse.Types.Settings +@if (ServerSettings.Instance.HCaptchaEnabled) +{ +
+ +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index 53ce334e..fd99f22e 100644 --- a/ProjectLighthouse/Types/Settings/ServerSettings.cs +++ b/ProjectLighthouse/Types/Settings/ServerSettings.cs @@ -5,7 +5,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using JetBrains.Annotations; using Kettu; -using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; namespace LBPUnion.ProjectLighthouse.Types.Settings; @@ -13,7 +12,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings; [Serializable] public class ServerSettings { - public const int CurrentConfigVersion = 18; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! + public const int CurrentConfigVersion = 19; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! private static FileSystemWatcher fileWatcher; static ServerSettings() { @@ -150,6 +149,12 @@ public class ServerSettings public string MissingIconHash { get; set; } = ""; + public bool HCaptchaEnabled { get; set; } + + public string HCaptchaSiteKey { get; set; } = ""; + + public string HCaptchaSecret { get; set; } = ""; + #region Meta [NotNull] From 2c7922ccc1f1d602c9625209d46813736bbd7d2c Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 5 Feb 2022 01:00:35 -0500 Subject: [PATCH 06/26] Add captcha to register form --- .../Helpers/Extensions/RequestExtensions.cs | 17 +++++++++++++++++ ProjectLighthouse/Pages/LoginForm.cshtml.cs | 16 ++++------------ ProjectLighthouse/Pages/RegisterForm.cshtml | 6 ++++++ ProjectLighthouse/Pages/RegisterForm.cshtml.cs | 10 ++++++++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs b/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs index 05208124..b8738c4d 100644 --- a/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs +++ b/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs @@ -1,5 +1,9 @@ +#nullable enable using System.Text.RegularExpressions; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace LBPUnion.ProjectLighthouse.Helpers.Extensions; @@ -11,4 +15,17 @@ public static class RequestExtensions ("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); public static bool IsMobile(this HttpRequest request) => mobileCheck.IsMatch(request.Headers[HeaderNames.UserAgent].ToString()); + + public static async Task CheckCaptchaValidity(this HttpRequest request) + { + if (ServerSettings.Instance.HCaptchaEnabled) + { + bool gotCaptcha = request.Form.TryGetValue("h-captcha-response", out StringValues values); + if (!gotCaptcha) return false; + + if (!await CaptchaHelper.Verify(values[0])) return false; + } + + return true; + } } \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index d10d3f7d..7ceb63d3 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -3,13 +3,12 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Kettu; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; -using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Primitives; namespace LBPUnion.ProjectLighthouse.Pages; @@ -35,17 +34,10 @@ public class LoginForm : BaseLayout return this.Page(); } - if (ServerSettings.Instance.HCaptchaEnabled) + if (!await Request.CheckCaptchaValidity()) { - // && (!this.Request.Form.TryGetValue("h-captcha-response", out StringValues values) || !await CaptchaHelper.Verify(values[0]))) - bool gotCaptcha = this.Request.Form.TryGetValue("h-captcha-response", out StringValues values); - string? token = gotCaptcha ? values[0] : null; - - if (token == null || !await CaptchaHelper.Verify(token)) - { - this.Error = "You must solve the captcha correctly."; - return this.Page(); - } + this.Error = "You must complete the captcha correctly."; + return this.Page(); } User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); diff --git a/ProjectLighthouse/Pages/RegisterForm.cshtml b/ProjectLighthouse/Pages/RegisterForm.cshtml index 67b3a05a..54775d85 100644 --- a/ProjectLighthouse/Pages/RegisterForm.cshtml +++ b/ProjectLighthouse/Pages/RegisterForm.cshtml @@ -1,4 +1,5 @@ @page "/register" +@using LBPUnion.ProjectLighthouse.Types.Settings @model LBPUnion.ProjectLighthouse.Pages.RegisterForm @{ @@ -60,5 +61,10 @@ + @if (ServerSettings.Instance.HCaptchaEnabled) + { + @await Html.PartialAsync("Partials/CaptchaPartial") + } + \ No newline at end of file diff --git a/ProjectLighthouse/Pages/RegisterForm.cshtml.cs b/ProjectLighthouse/Pages/RegisterForm.cshtml.cs index 310884d5..a7945459 100644 --- a/ProjectLighthouse/Pages/RegisterForm.cshtml.cs +++ b/ProjectLighthouse/Pages/RegisterForm.cshtml.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using JetBrains.Annotations; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Settings; @@ -42,13 +43,18 @@ public class RegisterForm : BaseLayout return this.Page(); } - bool userExists = await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null; - if (userExists) + if (await this.Database.Users.FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()) != null) { this.Error = "The username you've chosen is already taken."; return this.Page(); } + if (!await Request.CheckCaptchaValidity()) + { + this.Error = "You must complete the captcha correctly."; + return this.Page(); + } + User user = await this.Database.CreateUser(username, HashHelper.BCryptHash(password)); WebToken webToken = new() From 19a29ca328846b426429d434f0bb0645a5a0a954 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sat, 5 Feb 2022 02:53:03 -0600 Subject: [PATCH 07/26] Add level comments and yays and boos for all comments --- .../Controllers/GameApi/CommentController.cs | 150 ++++++++++++++++-- ProjectLighthouse/Database.cs | 2 + .../20220205082513_CommentRefactor.cs | 48 ++++++ .../Migrations/DatabaseModelSnapshot.cs | 44 +++-- .../Pages/PasswordResetPage.cshtml | 16 +- ProjectLighthouse/Pages/SlotPage.cshtml | 33 ++++ ProjectLighthouse/Pages/SlotPage.cshtml.cs | 11 ++ ProjectLighthouse/Pages/UserPage.cshtml | 12 +- ProjectLighthouse/Pages/UserPage.cshtml.cs | 3 +- ProjectLighthouse/Types/CommentType.cs | 7 + ProjectLighthouse/Types/Levels/Slot.cs | 22 ++- ProjectLighthouse/Types/Profiles/Comment.cs | 40 ++++- ProjectLighthouse/Types/Reaction.cs | 16 ++ .../Types/Settings/ServerSettings.cs | 8 +- ProjectLighthouse/Types/User.cs | 4 +- 15 files changed, 370 insertions(+), 46 deletions(-) create mode 100644 ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs create mode 100644 ProjectLighthouse/Types/CommentType.cs create mode 100644 ProjectLighthouse/Types/Reaction.cs diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index 334b7f04..f1960429 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +8,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -24,37 +26,130 @@ public class CommentController : ControllerBase this.database = database; } - [HttpGet("userComments/{username}")] - public async Task GetComments(string username) + [HttpPost("rateUserComment/{username}")] + [HttpPost("rateComment/user/{slotId:int}")] + public async Task RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId) { - List comments = await this.database.Comments.Include - (c => c.Target) + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + Comment? comment = await this.database.Comments.Include(c => c.Poster).FirstOrDefaultAsync(c => commentId == c.CommentId); + + if (comment == null) return this.BadRequest(); + + Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId); + if (reaction == null) + { + Reaction newReaction = new Reaction() + { + UserId = user.UserId, + TargetId = commentId, + Rating = 0, + }; + this.database.Reactions.Add(newReaction); + await this.database.SaveChangesAsync(); + reaction = newReaction; + } + int oldRating = reaction.Rating; + if (oldRating == rating) return this.Ok(); + + reaction.Rating = rating; + // if rating changed then we count the number of reactions to ensure accuracy + List reactions = await this.database.Reactions + .Where(c => c.TargetId == commentId) + .ToListAsync(); + int yay = 0; + int boo = 0; + foreach (Reaction r in reactions) + { + switch (r.Rating) + { + case -1: + boo++; + break; + case 1: + yay++; + break; + } + } + + comment.ThumbsDown = boo; + comment.ThumbsUp = yay; + await this.database.SaveChangesAsync(); + return this.Ok(); + } + + + [HttpGet("comments/user/{slotId:int}")] + [HttpGet("userComments/{username}")] + public async Task GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId) + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + int targetId = slotId.GetValueOrDefault(); + CommentType type = CommentType.Level; + if (!string.IsNullOrWhiteSpace(username)) + { + targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId; + type = CommentType.Profile; + } + + List comments = await this.database.Comments .Include(c => c.Poster) - .Where(c => c.Target.Username == username) + .Where(c => c.TargetId == targetId && c.Type == type) .OrderByDescending(c => c.Timestamp) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, + 30)) .ToListAsync(); - string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); + string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); return this.Ok(LbpSerializer.StringElement("comments", outputXml)); } - [HttpPost("postUserComment/{username}")] - public async Task PostComment(string username) + public async Task getReaction(int userId, int commentId) { - this.Request.Body.Position = 0; + Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); + if (reaction == null) return 0; + return reaction.Rating; + } + + [HttpPost("postUserComment/{username}")] + [HttpPost("postComment/user/{slotId:int}")] + public async Task PostComment(string? username, int? slotId) + { + this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(Comment)); - Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString)); + Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString)); + + CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); User? poster = await this.database.UserFromGameRequest(this.Request); if (poster == null) return this.StatusCode(403, ""); - User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (comment == null || target == null) return this.BadRequest(); + if (comment == null) return this.BadRequest(); + + int targetId = slotId.GetValueOrDefault(); + + if (type == CommentType.Profile) + { + User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (target == null) return this.BadRequest(); + targetId = target.UserId; + } + else + { + Slot? target = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == slotId); + if (target == null) return this.BadRequest(); + } comment.PosterUserId = poster.UserId; - comment.TargetUserId = target.UserId; + comment.TargetId = targetId; + comment.Type = type; comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); @@ -64,17 +159,40 @@ public class CommentController : ControllerBase } [HttpPost("deleteUserComment/{username}")] - public async Task DeleteComment([FromQuery] int commentId, string username) + [HttpPost("deleteComment/user/{slotId:int}")] + public async Task DeleteComment([FromQuery] int commentId, string? username, int? slotId) { User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); + // if you are not the poster + if (comment.PosterUserId != user.UserId) + { + if (comment.Type == CommentType.Profile) + { + // if you aren't the poster and aren't the profile owner + if (comment.TargetId != user.UserId) + { + return this.StatusCode(403, ""); + } + } + else + { + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId); + // if you aren't the creator of the level + if (slot == null || slot.CreatorId != user.UserId || slotId.GetValueOrDefault() != slot.SlotId) + { + return this.StatusCode(403, ""); + } + } + } - if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, ""); + comment.Deleted = true; + comment.DeletedBy = user.Username; + comment.DeletedType = "user"; - this.database.Comments.Remove(comment); await this.database.SaveChangesAsync(); return this.Ok(); diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 365319fd..bead8124 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -36,6 +36,7 @@ public class Database : DbContext public DbSet RatedReviews { get; set; } public DbSet UserApprovedIpAddresses { get; set; } public DbSet CustomCategories { get; set; } + public DbSet Reactions { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -272,6 +273,7 @@ public class Database : DbContext this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId)); this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId)); this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId)); + this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId)); this.Users.Remove(user); diff --git a/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs b/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs new file mode 100644 index 00000000..a42defb2 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + public partial class CommentRefactor : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Comments_Slots_TargetId", + table: "Comments"); + + migrationBuilder.DropForeignKey( + name: "FK_Comments_Users_TargetId", + table: "Comments"); + + migrationBuilder.DropIndex( + name: "IX_Comments_TargetId", + table: "Comments"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Comments_TargetId", + table: "Comments", + column: "TargetId"); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Slots_TargetId", + table: "Comments", + column: "TargetId", + principalTable: "Slots", + principalColumn: "SlotId", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Users_TargetId", + table: "Comments", + column: "TargetId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 28c95e9d..15bc4226 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -412,13 +412,22 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("DeletedBy") + .HasColumnType("longtext"); + + b.Property("DeletedType") + .HasColumnType("longtext"); + b.Property("Message") .HasColumnType("longtext"); b.Property("PosterUserId") .HasColumnType("int"); - b.Property("TargetUserId") + b.Property("TargetId") .HasColumnType("int"); b.Property("ThumbsDown") @@ -430,12 +439,13 @@ namespace ProjectLighthouse.Migrations b.Property("Timestamp") .HasColumnType("bigint"); + b.Property("Type") + .HasColumnType("int"); + b.HasKey("CommentId"); b.HasIndex("PosterUserId"); - b.HasIndex("TargetUserId"); - b.ToTable("Comments"); }); @@ -473,6 +483,26 @@ namespace ProjectLighthouse.Migrations b.ToTable("Locations"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reaction", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("TargetId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatingId"); + + b.ToTable("Reactions"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => { b.Property("RatedReviewId") @@ -829,15 +859,7 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.Navigation("Poster"); - - b.Navigation("Target"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml b/ProjectLighthouse/Pages/PasswordResetPage.cshtml index 7cc087e1..c6e0f631 100644 --- a/ProjectLighthouse/Pages/PasswordResetPage.cshtml +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml @@ -10,11 +10,13 @@ } - -
diff --git a/ProjectLighthouse/StaticFiles/css/styles.css b/ProjectLighthouse/StaticFiles/css/styles.css index a74b4401..b231c52f 100644 --- a/ProjectLighthouse/StaticFiles/css/styles.css +++ b/ProjectLighthouse/StaticFiles/css/styles.css @@ -12,6 +12,17 @@ div.main { margin-bottom: 1px; } +canvas.photo-subjects { + opacity: 1; + transition: opacity 0.3s; + pointer-events: none; +} + +canvas.hide-subjects { + opacity: 0; + pointer-events: none; +} + /*#region Cards*/ .card { From e96717e5c47ab93ab196532906b79b187881e500 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 6 Feb 2022 17:13:36 -0500 Subject: [PATCH 16/26] Combine args input and execute button in admin panel --- .../Pages/Admin/AdminPanelPage.cshtml | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml index 97228b43..58440e43 100644 --- a/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml +++ b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml @@ -35,19 +35,24 @@ else

@command.Name()

+ @if (command.RequiredArgs() > 0) { -
+
+
-
-
} - - + else + { + + }
} From ebeb2311bab63a524f31e1246671b371f1305e50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:31:09 +0000 Subject: [PATCH 17/26] Bump Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation Bumps [Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation](https://github.com/dotnet/aspnetcore) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index bb81e5d2..1019e2d9 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -19,7 +19,7 @@ - + all From 711b31a50be77c09475ac79fc9545653f43747d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:31:11 +0000 Subject: [PATCH 18/26] Bump Selenium.WebDriver.ChromeDriver Bumps [Selenium.WebDriver.ChromeDriver](https://github.com/jsakamoto/nupkg-selenium-webdriver-chromedriver) from 96.0.4664.4500 to 97.0.4692.7100. - [Release notes](https://github.com/jsakamoto/nupkg-selenium-webdriver-chromedriver/releases) - [Changelog](https://github.com/jsakamoto/nupkg-selenium-webdriver-chromedriver/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/jsakamoto/nupkg-selenium-webdriver-chromedriver/compare/v.96.0.4664.4500...v.97.0.4692.7100) --- updated-dependencies: - dependency-name: Selenium.WebDriver.ChromeDriver dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 9b6b4ab1..5d50548e 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -16,7 +16,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive From 3a41c680bc0e0c1b4d22ab5873273d40447fd869 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:31:16 +0000 Subject: [PATCH 19/26] Bump Kettu from 1.2.1 to 1.2.2 Bumps [Kettu](https://github.com/Beyley/Kettu) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/Beyley/Kettu/releases) - [Commits](https://github.com/Beyley/Kettu/compare/v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: Kettu dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index bb81e5d2..380de389 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -17,7 +17,7 @@ - + From 68358d23700b98f57bf1b8e7d727f71ea5e1b53c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:31:18 +0000 Subject: [PATCH 20/26] Bump coverlet.collector from 3.1.1 to 3.1.2 Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/commits) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../ProjectLighthouse.Tests.GameApiTests.csproj | 2 +- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 2 +- ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index d646e3bb..063ec5bd 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -20,7 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 9b6b4ab1..eee78ce7 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 4849cf8f..a166be43 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 86bcd9b3a5bde69051b0c21304d2ca5019472df3 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 7 Feb 2022 16:16:24 -0500 Subject: [PATCH 21/26] Update all packages in solution --- .../ProjectLighthouse.Tests.GameApiTests.csproj | 2 +- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 4 ++-- .../ProjectLighthouse.Tests.csproj | 2 +- ProjectLighthouse/ProjectLighthouse.csproj | 11 ++++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 063ec5bd..13bb480f 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 44d1b782..1ea2b728 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -10,13 +10,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index a166be43..e839e583 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index e41d3f78..9a761aff 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -15,17 +15,17 @@ - + - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -47,6 +47,7 @@ Always + From b165d5a2ce4577d7ae8edfba072d50ede4013228 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 7 Feb 2022 16:20:15 -0500 Subject: [PATCH 22/26] Add 7 day cookie length Closes #149 --- ProjectLighthouse/Pages/LoginForm.cshtml.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index 7ceb63d3..f2a7ff64 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Threading.Tasks; using JetBrains.Annotations; using Kettu; @@ -7,6 +8,7 @@ using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -71,7 +73,15 @@ public class LoginForm : BaseLayout this.Database.WebTokens.Add(webToken); await this.Database.SaveChangesAsync(); - this.Response.Cookies.Append("LighthouseToken", webToken.UserToken); + this.Response.Cookies.Append + ( + "LighthouseToken", + webToken.UserToken, + new CookieOptions + { + Expires = DateTimeOffset.Now.AddDays(7), + } + ); Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LoggerLevelLogin.Instance); From 9253c332aaf84e0d85fee022fc83bc6c53f2d1ba Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 8 Feb 2022 13:38:18 -0500 Subject: [PATCH 23/26] Update EntityFramework to 6.0.2 --- .../ProjectLighthouse.Tests.GameApiTests.csproj | 4 ++-- .../ProjectLighthouse.Tests.WebsiteTests.csproj | 4 ++-- ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj | 4 ++-- .../Controllers/GameApi/Slots/ReviewController.cs | 6 +++--- ProjectLighthouse/ProjectLighthouse.csproj | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 13bb480f..b41b9102 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 1ea2b728..ff0847fb 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index e839e583..f9693564 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,8 +14,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs index 6389d02f..e7ad7d7f 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs @@ -74,7 +74,7 @@ public class ReviewController : ControllerBase this.database.RatedLevels.Add(ratedLevel); } - ratedLevel.Rating = Math.Max(Math.Min(1, rating), -1); + ratedLevel.Rating = Math.Clamp(rating, -1, 1); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); if (review != null) review.Thumb = ratedLevel.Rating; @@ -161,12 +161,12 @@ public class ReviewController : ControllerBase yourReview = new Review(); yourReview.ReviewerId = user.UserId; yourReview.Reviewer = user; - yourReview.Thumb = ratedLevel?.Rating == null ? 0 : ratedLevel.Rating; + yourReview.Thumb = ratedLevel?.Rating ?? 0; yourReview.Slot = slot; yourReview.SlotId = slotId; yourReview.Deleted = false; yourReview.DeletedBy = DeletedBy.None; - yourReview.Text = "You haven't reviewed this level yet. Edit this blank review to upload one!"; + yourReview.Text = "You haven't reviewed this level yet. Edit this to write one!"; yourReview.LabelCollection = ""; yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds(); } diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 9a761aff..8b36bb8b 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -18,10 +18,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 0d74e146557c12c94d3b24107bbb132ededfd975 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 16:38:27 +0000 Subject: [PATCH 24/26] Bump Discord.Net.Webhook from 3.2.1 to 3.3.0 Bumps [Discord.Net.Webhook](https://github.com/Discord-Net/Discord.Net) from 3.2.1 to 3.3.0. - [Release notes](https://github.com/Discord-Net/Discord.Net/releases) - [Changelog](https://github.com/discord-net/Discord.Net/blob/dev/CHANGELOG.md) - [Commits](https://github.com/Discord-Net/Discord.Net/compare/3.2.1...3.3.0) --- updated-dependencies: - dependency-name: Discord.Net.Webhook dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ProjectLighthouse/ProjectLighthouse.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 8b36bb8b..4ccd6531 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -14,7 +14,7 @@ - + From a9616618cf2a1902dc7daa32cbc3b516b13ea150 Mon Sep 17 00:00:00 2001 From: jvyden Date: Thu, 10 Feb 2022 18:27:09 -0500 Subject: [PATCH 25/26] Add support for motion recorder filetype --- ProjectLighthouse/Helpers/FileHelper.cs | 6 ++++-- ProjectLighthouse/Types/Files/LbpFileType.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index d67384ef..9d8c4f20 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -21,6 +21,7 @@ public static class FileHelper return file.FileType switch { + LbpFileType.MotionRecording => true, LbpFileType.FileArchive => false, LbpFileType.Painting => true, LbpFileType.Unknown => false, @@ -56,17 +57,18 @@ public static class FileHelper return Encoding.ASCII.GetString(header) switch { + "REC" => LbpFileType.MotionRecording, "PTG" => LbpFileType.Painting, "TEX" => LbpFileType.Texture, "FSH" => LbpFileType.Script, "VOP" => LbpFileType.Voice, "LVL" => LbpFileType.Level, "PLN" => LbpFileType.Plan, - _ => determineFileTypePartTwoWeirdName(reader), + _ => readAlternateHeader(reader), }; } - private static LbpFileType determineFileTypePartTwoWeirdName(BinaryReader reader) + private static LbpFileType readAlternateHeader(BinaryReader reader) { reader.BaseStream.Position = 0; diff --git a/ProjectLighthouse/Types/Files/LbpFileType.cs b/ProjectLighthouse/Types/Files/LbpFileType.cs index a6084228..40e3cc87 100644 --- a/ProjectLighthouse/Types/Files/LbpFileType.cs +++ b/ProjectLighthouse/Types/Files/LbpFileType.cs @@ -8,6 +8,7 @@ public enum LbpFileType FileArchive, // .farc, (ends with FARC) Plan, // PLN, uploaded with levels Voice, // VOP, voice data + MotionRecording, // used in LBP2+/V for the motion recorder Painting, // PTG, paintings Jpeg, // JFIF / FIF, used in sticker switches, Png, // used in LBP Vita From 93810b32c5be1d20f550bc5c37788f6197238a32 Mon Sep 17 00:00:00 2001 From: Arcadius2006 <93880348+Arcadius2006@users.noreply.github.com> Date: Fri, 11 Feb 2022 18:47:36 +0100 Subject: [PATCH 26/26] Replaced slot card overlay as in the thing that's put on top of level icons --- .../StaticFiles/assets/slotCardOverlay.png | Bin 20185 -> 12877 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ProjectLighthouse/StaticFiles/assets/slotCardOverlay.png b/ProjectLighthouse/StaticFiles/assets/slotCardOverlay.png index c5ee40aed147b990e08ec13b897bd421ce676bfe..f1a6adb0e7e2593083c5242414672a660049e2ed 100644 GIT binary patch literal 12877 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGFct^7J29*~C-ahlfq^C6 z(btiIVPik{pF~y$1_sUokH}&M2EKzJ%(!D>_FD!9!$+Pjjv*CsZ|`znSQ}b<|08p| zwa1>N3JDik@>d+K@Vp|WTD7JpZ_11I`2O1Ase))g@{H^D2zSn+#x^vCx`n6yG zo>@~lFFpNGW$pdF*WZ6%^ZaJD{6^-6N`JebPj=tU6YH~h)KUMsj8VdskwNH`?v2{T z8VoN37#a>_@&DYX!R`8sp8m7_;0M9YQ&3);K9T_-Zg{$Sv%*(_q$+Wo0<3_(71Vm0{^Z zh6Nlgc1eqP7O;pgq;y|sXPv_Mq>HIxjli!BJw^?40vHnH7WT^ovL(o}GNcLq(O`6C z2n%FL;9A%}DUdCJla=A5;I9>esSGOv87A-?>!gp~US2^S1z&3!x#r;SPh=3EX- zXDs*}^w{NWv58!l!(0sp(WIoU&7I{I^Ul|Oh*`tWxjH+5 z22O)HGHR!7tH0U2EQ_}M`{nZL8R8YOYup4iO{CHM<;UYA~=W{kg9EBKOovi7Vf%7PCxgX9^H#s^2(6 zSlm(GP4DP}w(AP63uHtZoCN;0ADy*;3t5X}48un>0usxOg*J9`8F8QKQD1o2#z>Ju~_TRc^KWJwa zY0T4L+!nylbHeZkXYB)nQx4T4jM5qmY)?e~X~ykfu`hBG%xD*yAjhh(G3L^!NJk|70?aG}h0W8xNvn+9D^f*xT zVR@C|HIY9{*=xg1MVY$}6p1wc&|sJt$y?uK&vR(s{7c@9t_g--4lyDP9Xf6H2jBB2 z%7=9>ZBW`EtEwQ!<)AX{ko{BcHB%n=OMHu;Sm?Sr``ql*J$r8UE}jzPAb6ukNPwMH zfl2$J{|A0y#V_hc`|iEb`}!lrsPfR;*+x>meUr`_p1l~PKdG>8^XqWV)CQ#j2~`Cf zE(Zse`g;fE4L;~kE3DqU`=lM`_vXC)9zjBVQ_dEiy{LA6^2gI>*U4LoGj$zE6lpBb zU}%issBhZ8n_=Pq`zd#>zmfZt@zU}BRBg87=I-q_&d2WOmbIVU$`imMHKoBogMo1g zUwzYl+w+b8<5O%dyfIUG@qJh97loq_T((<(Hj%8~fAg*WcBh23905$H92h_7r2Y{8 z^R|NP?|-f2`pwlRY=wT@y#G33rU=vA&3>7Ee3nti?q`?nKE0CXN*s#@V_hJFjhE;@ zmU?Tsrug$8_+Rv~{N*pg#L+>1^l3cNsBY>%GF#}IX@DFi`Kf8MVcczuUQb_-qTD+S1 z^J>QYsT)g=CMb3}R8MKJ31DDbk$LiT){pNTf8U?{@j0#hxU$fX==+P>yF3&xY?qpM zLFVlbZKHX2->gg9%5!Chlmq9Bex?h-y1d&LJovw^=dO|5vnYc2E_*!9XbE{%Aa8_QA~BPuXwTY?8Uz8Cx6^b zE4R!}U9jvbgO`K3NCTf&{iig!KfiANx_)X$ywm@KFO?hIgV?rkym)mWJLN^W_!<|% z2)!pUUYr^IOc#V6%KNuJ_`j*==Jw}z-9IR^g|mEl=2*I|C){Lrccd266oxa48MXxc z629v7qnzX4>(o1HkMlVHx-aWz;PjBZP%iM|Hb=b1P8#1?R168b3K_C4Q*) zJZ7HyM)5~tr#92CM(6X>KWf$HNNT$-XuHbbbs$TmLDZ`*D2n6%w1f3?d#q>OmAyat zLH(7t%+EXoFRVA2S3B)Mq*2|zn|IGS2`*r{(#y2LWu^bIo^!uUe|+uxw_D}QcVBr% zBc;>_7B^o!=X_z!E+)np#%#8j<%la|gu;=@6E^>I>wd7`wXM$OJ+IIoet&sJBQ;kC z`@KYRKrkZn$k5*;7ny$=5m;{4ZqRODtI)%$~wnyS?Gu?b-X9c02>6(fUwPhC}=Jo2LJbpLpm0 zjN9x2GrlxzKmF|K*^7x=*RcgK{aU$b zi&%KVy*XDb0x4Nz`TDy4>zzm5GJCjm9bnybaW;$j+2+G^#m=X3Gjr_j$S`e4 zGm-kcE2l2%*uu%yVNAcY_Xdc>GI0FQ3uD<|SM%QPko<=8QyU(qzBtaMuE{i|!A2yS zq3zGdwSNxww*0ph`t$gifP>x=j+_(EL?ov)Gws9$8sGQnDtDSv8jU01@? z)r@vt46F*wE%nyghyGV_{y#m-nPKSymH?)0TB{fY{=L=VsQvz>%APzRi`#2aWOo+e{PcTe|?V+@$bGdiuiRMh)(%D>sg1;g-}h^2N!|@dO7~D z%;v0Lp!aFk|0Ox=a{H#8<8?eT=d+mROP;zFN?{DYOm@vWRDZPl_h%!)n1|mOL()v7 z_C}i3@3hQ162*I>R&_y((!^Rug+JlZLjUGZe(-`9Jutq$UAOF_$lr`7rLbx#3Xy&@+X|tH+3)6V_&jQ zWI@}Uv(KZHHpMV1{Fxpt^iN#qAH%H)JDv1dV{XV?-7I9_vTi!lrWnQ-zfP~S`19W- z?#%X`wG1y(Or-V(nC)No+jI@fm2F%T?s73a+<&9#!~T$C=CfiMUbsFNnCrbNf>UCW zxaB&gl&wq-KlHb${K=oN`Tv^fR~eR=OZA$l$EiJ*tvcg%Aj@(si^y-ihOWc^g(O?- z8T9YO6gBhAjgH{d@G#S6h?R7dqUIf?!0LWi-SLEGuD3DTO%O#o=HOE$>hR=+%M}->}dVKo^UdT z^Ma*T1*gZ#CBpv2(-|UuSf}iWsIdwVf_vZTP^>P+ihD zsc`ulws=mDTFnJ5QyGF;7x*q#`onm@ES%}z?swB;n09R{({Gr`l<-xp^T?NFQitRn z`u1rn|LARex5-(k*3UyXH8yNIgO>I0>*D9mw%9kw?A-bK!fs|K$^PT)#oCNvGj?!J zSXzHb-eKl5d-aCdO#GWCWJa!GcyedMts6rBekt0w*f%JhSd^HB)5a!^?%ND z|LSwp+dW}-2uzyNa7T|d;)LC2Z3b2Szrz0*vJAtS^cJz)n6QOmO}2w-`+uhGg8vx2 zo;};)9IqhKG>u{VY?tYGy!LV>l(8yoSG#(38e7fAp1)`H7^a+_+Hl)xEz^oQ@7O&e znI?%g$gKP89`$R#%Q4?Mf06eV8cuQ4v zf?qzb&&29ydW=){eDVLm>~Q#Ot@$aAZ9)@%1vPxN>5gN_iu*PHbA5`yKZaFi;Y=ld zU-Z5%ZO{u!N&XUlX4U_RTXp`Pp8Ribo;ZVo%8L8auYwxR?6*$6=lktl!?uTV*3667 zF+}kH57?%YUwb5%k)gd-<;qfq!Zo+mE}Gp>TF*42Ryu)8aN0k6CEYJ!+xZy^?%a`h z!jfqtXFuum(uNs}{yIhcy8kh=vZ?;sTV@8a2@C4>vV04DcVTtRJy8K|;mPY6LN7EKoaX#JAAfq1ddUPcZU)s$mc~c^ zGCclRvHrI2GGES)sWUyp8NBR%&p$lVo}oni$&pMGsktx9R*V0sI*oIrTOw-q-DG9p5y;Z3Eqo=k!S6*i?e=o?Y9%!#V%hx&XZ?&;MpO(`eQ}S<{ZE5zE_928rX}||Hsd@ zV_^8;_w3f^JL~-Y*M4m~puoD|(B}WlM(ZZGRaF*qFnrKG!nHb@)#COi-o}M`%r23P z8-)HT?%4dlw#A;IMf}m{Ke3z^i!HsBUHz)`85Z8(X3+9}bG`V#s&DnDzc4a56c~Nq zclPbCl1)#&85K8~{@`+8_5L3}Z8NxzamY}77PFn}!<%O-_~N)5o^Gzcf9K|Ze^A+> ze|zS|adZE@uk-)RUdq5w*K_=FelJf=WWbs?JYfuLTIxC8z4@PJ z`TPI*v&sw&H<;Eky(zihH(t`D2v8O%&o zzi7AF>;#v?$@KqDhyQCAeY4NxV`Ny#GOPRlv?&eq3xA(%ITxhQx?t1h|I!@w3>yxA zU)u1o;*Z=-m0IryTKkNy|K@UdnEpRX!|6@{2g3%}aKC69* z&nh!8Xfv&463H!K+_38pKf?is`eGBQxlg}Mjw~s9t6aTzEyI*OU*4blxBtjZdxjgL z38D}7=9%vlkbM1Dj)9x0=0kto<@@TOL?%~%_>h(ZM>E%f9L_~eo9h`I*qCZ8Ce#^B z=qzW+IeTm2bjGe|Mvj7S|6MJAKmYuXae>pjv;+I|Qbi9u=PZBE6maq8{<;tR3<|=G zVT@nAn|$UO@JF(0WU(q-xcOiGt{uYyr##UI;kq??4WCLM*{)y+DfqVj{&#+c6Fv>0 z4aq4g|92(KuM%yzySx0qY4$_;2XFps8MiZ$9H-XlR|M_xMZN^}cn}88_)MFxt)fsCe!lBZKZ9lkcGoYq$?o z*zCLemhaiQfA;rf7#X~T9vB@kPMKp^d!S>TYJ)dJ``?Ij|K=C`W?)E3|Nic-`mVcM zH|a7gzW5^j&bx%7Z}t0f)@AlFFsO2E;40XbGO_bL`;41gv#-Z(-yOD%)uHa^{i5A= zHV1CnGb~s);!Jh;PadMMy37 z9i0@*DA9lX|M|DSKFC`Bu0QjZnc+g`Zk`W0lKS)QCQQ7g&scLkmyto@QO}()#!Xrb zpH9~gy=&9FYVEO+%sp#%JP#26Z~Qa6gcEADK2TF-D`kHd6E zy;(kL{C@H#r~ZHfr)t%0S)YFfHN6H536mYBGuCaqxmCN}o58NW^ZO2628PhpujCp| zrhMwS&C0MKYZ>=}3=^v|8@c*3vV5C1Zu}Utotxo++CDwzGqpM&PR>fqyRY(Eo8edO z-+eX@wlOfYe6x|XGd{CBOOMeDw53+!iON6ekt4FgTR?6-A~^dMetm zz3=$r|8Kvs-@I|7V%BX|hJ=L--x*(|Hf%TD{Vk-B^T4lmLk0$cXw&Zu3=9p2eZ(3r zW|VBn+3}B`;ly1|y~}4d?qd4z?PJBC{dfK;)O@pNSg_XNJL8_cH@CKWGcYiObTBb6 zIJBM-Whh_!x%V3j}Ll3Dv%FRxU)lFi87eZ!>ihcLoNL*A*xC+%Nj=85zlF z5yw{kj+w!S^MfD5-}kcD-z*EgqsnlPiG$%l)g5M#*EJBKFiE`OV#XJkaCGCeQiz|?m7r@}m;wrvxoWeOXDKr!g>7W1=UMD!GoO{B-Zdc#pdQ4CWZjn zvkqqusZ0`WIGIv3Glvfp7a`9VoIa$26kw3_)}_Tz{q4=eWljtY;Y-uFwGnY}MuZ{z z{$w7827NK(K2fx=gaqqRQ3gA128M>Opa4(koaC>iiO@eu43sO^r+%&|KGM#}$Z%@2 z%5AynJb%LT>^Z{_eie;msL)nmNLX=ca|@C!&$t-gvoJ6y*npIB9Qov#yOFg)nvszq z;+zxXkA|M(vtk(-E^*fgt(0$Aesg-rCnkfLK3~KS-n3#^AjTg1P4%cP$SEgM7$3}K zU}(4#d4AKPaE2X%0t^>Ib#ES21^FjsDZ`(IJ+)``7#KLtx-ic0QR_eT%mI|=H-5=| zUv+8roSWVZ`|{>Juw!6wh~8(E?Aj(lSKkql~+Px7Z6?rmUbxO4Zh z(b+j`^cV}|85j=CirKvKRyD&89tMW5O3XRp4f!X26mI5ba40i5Y;xMiC>o@5?WUh) z#U&Ec85tPD)|_0D?>(*d?%UbHZ&ewN3JEY=Y2Ud&aWBIi#pctX*vTl%Y@3<7i@`^2 za&*e(x8g588!`9_FuZzLd@g>nYxxcyZ@uw=K+i#zD>SJ|? z4*L*xT#JF>iS{R%b^oWO1&3|YX1KIr-GsBxXD>b*!OFn!$zyd()yGzMh1om z-P=qGz25f14~owQMyz3CXsF%y{FRvc5L$|cMHw=o38&Fl&rLv}OYg9(_|E7O&ANbv z!J(@2S<=~At@qt0&sO4U$WF4)?Ax~bVV)nu0R!F5j}lMLxsm^$d(zdVYZz8!r~ZlD zEG@U1fnkDQYH`7Y)4WGT7_Og?%jw&eEXu&J!RK_(j+kSnXY*thUwpAQ@A~dZVGN5v zGLO71zS{{D|Flo}vHB9%fwx9m7V3&5i8cr^Fnny8QEWZuZ}u@4L+*M_nKcYs9_F#_ z*>4%uFq3a)!T!@T9yrbad45U-=L0@T?{G$^1W^WtL*5qGvzBFSa}oXYCSEk0 zA;x+4ddsMuPzDBvcAXvTw`{HtedXJ5f7(GU1_lwOk2m+Hc@=zqDF5qCyl5oD6Y=Hc znfGqX=!!uc@vn78v~|jXy?Vh-U6s%5gI{qq{Oapj0ZN^VGTJhxv1~Q{lRW3|>Ll)l z>>T^7J~5D|a=dH4{a<_`{lDh9Kj~g^i~;wjFN_!2BBIK`kmDuuZ+yQQSIw*$u%28I`UAMdP;{CIOh`u~;Z|D-PxZ-_kX8Nd`)*H+vS z%HYb$(D23T%*XA^IsQkQ9h>q+?Z64$7wtwRk?AV~R2dj9^ffN6Kij3SX3o#^Dw+(R zebuk&A2HpPZ{l9Kk&A)hwd4%#{erLF)Q4VMYVkYVGn^q!Y1^Xel$gtJ^o}iMVCc}l zB6Bm*?|ROKo)714&HV4PiD|>uk4Jv6?o2DsF5V(DoryuAUiSH?Xy-Y9*Pq@NlKx-o zC6~jZ{||q?Y`AW=8*ImI$r;k7!eMnqIr7sE$IslsVj!F;nlL|S-K341SPC>57!Db4 z__98-VCw0%RVw-n(b54&gg$J2mSO3|z|fMc@aO7T#Y6Xh-P~`qmB~POF-ynq)S87i zd;5hL7Nog7b2!`Zdsb|{QtFKaHLe4T)I@%XzmU4$d-h_{LQqEhS2|~N{-@e+_tkdm z%=xQ5S)0LFFzSkbO3mJzv3=f*3=jGzF0G&YTRyAo?}?{p>W}#_MqKM~`seiYeochg z^piUhe;weO!oVPL?!=ts#s5?I1SmUB@>MOte%P@t3LDg8~pVjr(bKbi?$l*E_!x(XOg45qcPw&^nm>cEI zvfJQvLWqIk#j?k9))epY`FYv0euLi3l(%o*?pdG7|C$WbZNu>irS<*ci!k7U&_Fcas0xi{pa3#9=iYN=6<8yOcNHd>`1Fv z*%Q5te~kktL&H^`*8S&RPXA@~`~9J;AFJQ!F`nU$VER(muvLSRVZ)4tAHt2cZvL`= z>Pw~z%Pd)cCEF+_zh!4>b>n21pqyMh{oGTl<@{l%F8%NMDcT^SdcgE(cj_Ih+Jx2# zs~Nh47!=eOK3~&UyzbmX@!uA|^>zP*UIPV$7Nhvd9l4M|G!XxH>U8OU2ko;bQv(@p znXPQlX3P#`Vz_c9;YWS&vBtFR`|Wm4I(&2gsWeuFP)>ngESJLfkJGoK@wp7gIY?~l=V-1m|>JAjGd%8`U0uVZQxyFQ%v4P#y4y_qY7$!7rz z!-fMdVwOC#f3fcWzMmIW7weqJ1DRa#ZP#0`h2e}MstgWZhR+tr6=$xtteP5iC|q45a?Ox-b0iu4p}H z!o25C=U($S^I}N)_2HtmU)k(9qOGjcw?ms6d6f9l5KUk==lKmM@IGoNwU-`@83e$8!@57_vbou63vD*w&>!lz#j zHf07*I6L>#{;%hr2`)*o`17?ms?Pt}m$>r3{Jj6?oc|Xpm^g72i^k9GrdOi>%V{!3 z9Gfe;e@R<|yi=Y3`YC5~KUG)lsAN%;yMGlzvgH8hxP|vwPlEYIXU5luw!J6QGtI0uaHmri5&-I z{X9MHdjI&Xy8ry$=)>`K@0ah2{gP10=PCJ5Rr8fj;R*R`?j7YnzbCI@(g7po;{(bKFT@erOKYwFi`eS<0xAk37j3-u#evAF~s%vV)29=1o z7iG&i>SsTBU-VVy&(r*E-fj1ebhGNT*T-A!7yfr!w6e_N3BTdudU4)Q{Dx-ZMs|yqLW7^<NIQ~s=a=N)0=^J99l-JjTP5{595{c7Z!-Mq=x98p;b_XI>-@KVF0^(z`ef$^nMys8 zzm6`FE&m;7w*OyxJHPis{_+>z40230FXaDyPnq%S!~;n^_8hgzd+R>U(c={UH)B@o z{%MC^GPj;o`15ty+4{?Gwz>b<9)2`(LBG1oef6e4w|7nXV{Ozo)9!-PnW+q+pR*c# z-ie=ZId5ctZPN2ofuCYd{>yFIc)I@lhwJOV{H>i?ZvA&3=l{GNoEw(z^Vj*#Qn$Y+ zEM9R<(mIwEQsK-dKT5rSEY{_%ue|r-xInu0vQvF7~PR#CtFO#V@Dp#oo0^YW@?W zre}PVug=uq&sXQ^c3PibB%ghl)w17y5BGuQb^adTUH*6U++NOKV(_8$ns*mxs>bPu z`i>ukwpVxj+tX5S{i*s=fAT$%e^UGM%q4%$l$$a?`SNt|3o%pFA&%i;{z0=q^_({4|c$;3TyprR=-2yh3Ka}ncUfC2;*LOSU?`_Gp z{pV`MckL`#8}RVGHFr_a-`===^Fw0i`+R!;`Zn{|?zAsyEBu0D_yWZAIZrIfe)ReF z)8`icX5pvKU;0%nFEH(o@0TLSE9*0_2`xxHAj>v&)f|4F|M@3=1kcpJ9`~8Q@>+cH zkJYcZ8@N8Qy#91#Qhj_rPtEe4xOBd)_r(Q@H^sXAtF?<(VE$CzRke8DpX!6&nQiHR zz2aAk3;uf<$RMMv`e1tMo{V1(?pxQfolyCmH8W-Z1HQ$bhC7vIe*WGY^snyv?!yUB z?sI;68Z>j>*VWOfF+M+TJ21{jUc&Oi{mMMs+69Y4R2`OTFd6OK=cjf(qrB<(zwZL` z{`fY>%T1_%tn$ul|FzMsaKUO9Q<*CwQ9P z+O+>qbd77l!y=7x$@c%cMtYMs*2n&6=c#l3k?+PBp>SpLhGfAD{9IP?$38FTF9|6U zoN&_W)b}l?_ZQFkV}0jLIOpp6_h-)k`s(+;|MZ>jlb;n>9Cp`NAP`aXa?5&e@PM=^B4Dr_?_wmR+iUUK{ScDD$^X~wb6z5R=sy3U zdc!b=FoP{Z3+8{l_cOOi>#0}6r=3y?!WkEK?|8a@{@Llf6?AkP6}cAtDL+5q|GV#Z z9|mbQ#~|6(5so&z?AnT`1;8U{6BWKe>l%CoX;=j)llS`a3QNgCnO=sx${?}5^I3t670ZWUkpu@`~ zv4)6;^2Q+b^1^Q%HqLwVdftTvS>BKhRis~K-jXW(MU64=C0!B=nlI{wGYj*ZRng8vR19;_1j%(~%S=XTUaXi>i_cnmp`$?@!5>_ z<^5+A|C~PHBJI#6etecv!J?ddI)_hRpVOwtB*JiIl|Ls(!L|D49_Vec8$ z-w-(_HF}Qv0gYcGm3!iO* z5!y`aSb9?C7}p+PmIs@@OQeC};Cit(`-AazYIi(%rZp^1DPG6(w<}TD)j`cXmhG;( zees6xuFThVTz~Y_h;J|7_I-U!Z2Rj<-%tHCt2*gdhllK+t`GgqOag(Y#IMa#+K~C_ zR>%i=k7rR;i zO|pCDeOqb61D@yhC(l=Hzs=tDEUx+egpZeAGCw-O%-(0SqxiXF`HR!OMql3j^FQ0Z zzZK-A7?B1RooNiJ_SgMx#wa|#pz_4K|Me^ph7i}d`s1gbmCs%*YwfrD_wV#OwYMbK z-WR=a=z28}xKXwea43}?Ehmek&Br;mc0U)&&;N7br2W?k@AykL_qeCj6p8&~u@_-gFyz)hqU!p<1Qt{#7|r}T|jU0^~+y^nwEmBjuK&DUb9DI`d*2r z&o5_Bjqr zgILZB&%3Ad3q&pPgFJ#?gzfGH1$98P}$%=bl4qnZh zbp5RBu4L06q7U==Sr2G*>pxkmpYT)izN&OB-`^>>`4%4h@BS%2N0-$>Q00dI-`>C0 zQStXCUBA96ltGu{@>HYBvo#lrK4>4hug29-I8&tY{*+$7F!hrc%K5(>ylKBm!Sw;p z%{?xs4m^GLL+9px=kL!?zV^!J_@gWNYuBucyMKQD{d4|=>!;f}>UbO(Pc%Fg|M`bI zrtVz5#_Y$bFVZ>I=rS$8^Z9V_d&{RGw#v8EA3J}#|LBeU4C8R7G`XqQPs&H~*SY?< z?ap|@#m_gs?pplxvnP$dOg?*S4ac9l+aIQHUb_7B%}amZ39NAATWIh6<9Q>~gqdf$ z8tP6?sGGGQPA(#XrO&3|!GVKwt?d>5*Z1#gXE>wyLy(JA!P(M>Bf6`hZqb6e2@~qh ze7L6c;l0V9r)U1fSZ>~b$#z@spZoKb|9`i>s~E6nPE)+#zn=>kQgo(sCYc$YIA1+4 zCvNF)r|yP(7q+~76W{bNc5krdJ_023l zcbeX@x0nCt&|AdwSMrd3zDR@9lPH#kD8WA-y-WcUV$?;{gs&()zcByd*C;+NhenId z3~w|Pzlg3s`ze5d>*!3j34WRj?GI;f^bu(|RQQeYNMO_p?YQ#P#S9@0n*|T-7hxq)PCB_!#Af>T$2)r~ zMHq4pAK57@ygb(?{rY3ep0yeb5nY~JW!0`#RQQ!0|11nz#x6EF&)#Rx-w!pPo1e>H zTgb4$R4wg)*6byfe68*u7VB~`SS1VjAHVeE+~)&gP4cmU3>Tys8J4(zSgg;*z_yr! z;iKRmi7qCFd<`at3KsjCg$xI*yBHqGALPHU!NAdF#PG%ag|Z44!=eQo3~vShbTl$G ztk7UOP{d-Nw1`1L@)U!^3uRd@hDnP#7Pzt42Q6keAaaVq%k9NsAua}=MH~xMSnPEc zF&wZt#Zct(;;NL^jT%BZYOk~~HLMEcc;)tBaheE20$=Nb92UC*308)PMyCnp zO!5(ZObjJ1nj1PduM1MG6ChU!JFnV@O5Z+q?W5N<%B}7hbwJ zC1lNwgssyIuS^or4qI^}!AZ$8F8ETy5v@?^wKp8DO3%ztPF}P0m}9%etcg9(Jfwp% zj|NKjL>MmGcdXvSAZaSsRH>|9)i7qaHBmEPEcyHQTpauR-$noDY`%W0+BW7reK8UHR?T=QZywnH(zE`Q`phExFyo%+B{GY8R`32q!~8V0PnP6Cu_GazYFV z3B~-MYA4Dt99Cjv;8MKM-+Y8YqnU-_MPq_Iw%8U*XEOkDN zoeYbV7#%cNYJDV6GAJoBI+U{1o)J9BkfqG%V9HY0;KJ~A0z*OwOKn2A3bTYTCxe^g z1NLj~N)3h+7!m?l>K51wG4gb>Ff=QCV17D*BY{JOL8EN}KTm%O!_o;13et`867ro4 zMH3kom^0Z2$agaQQer%CjN^}kjSABZ5l)6{ZV%WWDGMIh<-xE)pUM7$oDk!ib{2-+ zN*|ctDhnRS^I+JZ++=SctHOALlaqnZ@d5j?ZWo5z6B!aZ57=AZ5Bcp^KKpH4*7uir z7ny_@B{&b95&SXx(SpCvv*N>xKg6*X#_wPIt$urKZuNx!eO9&h{Z8{&|9P>|NsB^fBpNvnZlgvBCG$~^K`f*-1A^a>pCFc*1U=N*MHCJ z`73LEb45@5-E1#!T;aa({EP3eKGm+2UHZ)BAZOF$=BsXEp!Px}ZPX>fZhfqLN=K<9a1_+>L*`e%bHEm#=cNd~*~suvcL)c6uP+!u&h@ z%Dz?49=v;QT^ri|YI|kKG&i9+5`Rp$vahl<{PFKf*kut950+=mED3BJe{Q}D`fa@R z_T{Sw>uz3&oEGKLKJjJs!w2t=9O!JA+|0i8H~WgSa-9dhOl0tpIaseO*Z2MQp4-!} zybIVJE4%RitHV(`Vtg_4*yCg0Mws&Vu9umKWS_{sk=^hwM0QsD=hcA|$_wo6OxeE+Uy->J z&RFji#@TpVH_J~buQa%5gH&Qh-@f#DP9I7RHV0RC%$nTdsl+)$PN;!X@x%SI&OhQ~ zOiN?pzwu`WEU*t0XD?;{;l6V1yuE9A-DN)VnyRyzudH=C%X#SeREZxuEiUK0ry$d$hD-&+<&XN!PtZ|TcZI954 z->=V4o!qd0?X#^4zI}WXShjStEO0dD_*1w0|D0L(-`TX=zml%w`cb#o-udmuEWRIS zZf-xA%{7Ok#^bHj*4a{C95G8yoIJq4va;PV*n{PkGt-3`oow~fkCtvIzwm$lE03%* z4|gB74Su`)ieGs4I_6`2d(4g~v%C-A?RL^ZcsJh{+wYR>a-u)1v*c7W79}ZqIz)Rg zNEseH@P6T+{|n1jTk-Dy*srzsY4y8*4Y!1^_yryQQtS0LYwPzFvG)(XXxR5#&*4d6 zqx*`=-x(DuOt}h-83$aN_FG4tfBJvotmb|9f4MJR@ZPnG?}vZT=MQ%IKR-YF#p1_y zZ(&)$$%f_pPTtDQ_2#Djk}a-60`i;=1-I(D+V|DB;WjB z=_@NU?uT!Co+upG{AePhI&-+Dq(rUnDYYx%6;`j=ulj|3u4*jsU`bM9j{leC_h0;q%$Mw|CysBan7wdv^))H0!@(XM?v``&2j9JVe0u+A8JEY7{5+r z2obaRQ7!j>bJqXRv)Vu0=U?A$vi<$`zwsfnZ{6Ow`c%a_@BA6hS5?;S|GKl5y-IvRVZ-+%&v<*$+7}fWzoe^K4zkXe!&6al zE8KqjJc){sw5c2|Yi(BiJARpwKm51g*5AL=O((Eiab^l|JaFLs_Uiqu|HNx0|FT8( zHZ}EMshv3M{;y~myRr@6GfVk?*xkR^ez}F0NpR_t^X>YTJ8p3{-VcfP;j-Z>Sb9tQ zi)6tiaiPuwH4_tBBsY+N`&Nrg#;@v#!4MBsu4?-T$1U-T`r_MzMQbK4jB z-7TJYZ}WTey7E^SEfb42l*|6SdU5k+xnt{H=ga!buMujjpYbkQ!tm4c=jK=LEjsUa zqLfKfuwYSWYA=K5feH@>HzAcDzu$i0|H}IL(Y~yp2lnn~8}D-;QYvnLyWM=g+VjK7 z96uPBwj5wQz1*@&M6=52rRMhd@ZB-_bdA^U!1HxsiANJLyv;Phw=mE z57t}R%w^iY{lngnv#Sp&f0Eh9{BQS_nwmB9FG|#2+mm)6-Tu#-s3(u-?tS)bYF1)7 z--`c5AL@H%Wpnm~xi_dJRH!g8uafz3cjgY0_g{Z0Ri4;Ci;Zdj{bPF~UfaHEsV%cn zbDQs|&Nxr2VpG(SK=u6JdjifXGX)AWPM7?~aoTyhy##0HfdUT(wjjeF)vo`~W?g^T z{rT!sf32Y7ySe^cw2EDSsebh=|2pp7PSQ_b8o!*Y7FYc!dEsJTQ47hh>l#n*K3;KU zyCq|R(u0-+5f#S63XBpCOCQMVeyINOH8ktqZP`}lzq7CGVOagD%>BrM^DEt#*l2z5 z+3GPrbiV0_q*}lAujG;g<)5!)@aL?^N|^GMQHAm8LR)9r!7 zf3dCRAD8v-eX;p9%LC0UxvgDBp-=Cs>0d~{-W_;2u>N^0Q(Vc<4Nv*m+=K$eI2{~C zoA!%;uT}7PP~Kj-aTfQFE6=9>ns>SU!uKY-1m#~g){S3RG`O?xIlREEex81ZRngLY z=kM3A-zc%Cj=|HxQiY+lN9NDogcskxy8dwb9?@^*C4cJi+nV#?tM~HX=Kc}p{3qwS zvsvvv**|;Z4ydlo?$4?$e6adz+{*LY{p)#LYZr+B-qkT-J0IV!=mwPqJVFgjZan|i zUHhI``@{B0{9G&d_fNk^*Pqo5d&jXi;nebthDZ7jzWDr3lS!TF-<|6=y_fVE;^YO2 z7B2YZtk3u5o435Pjl`se8Wo0_Ze0J?T`QkE%lli{eWfLR7qpk#o{!%wvs&Eq@3L9j zeLrsNzxd+w`-yt$jCKn*Jv-z$-}2#C#(iyz>MklX{C9YJ{3@F?uL9=@XQl~>OCG%6 zUd#WzeslF2ciH*(tNt8&AN?mJ2rRZ~u93HUD?<-N$!bxmcOHrMbR$ab>L5)x&f3 zgVoJDi$59a{;I2xI+L9^Z*f?%ZjW|Y)x?AynT%hj`t{WLyltOuFTvpHV6Vbp%OmsW z@3Qxm|D-`iZLBPPpTF{|#nST!@3Al6;iIKmDHtcE`k7;dsbcGIz4`zN&jZgq7|cuxK77Bp=lyl|oxdI`ygD&w>;8bV?qB-$@}7#GTNBX5 zTz{fDsA2Dt_xN5LN#_e|la_;sf9%Z<*4)3Ns6_f_Ma z1LLbU>De#*w)eEWh>Oq$JD~-xpB zynpr`RhC=W@5Ji0Y6iy+w`0bOdf8Uwe|`UWW0qa`0*y%xePsOS^o9f``o|cmK~F^PxHKQU4MN~#D)92lmBcM+_C?o$L-ULRqwn?mtWWI z%_%-tNP+W)H0J?1HjaPuUe`~zn{w6r$1E$w^&h@R|Bqm;joI*A$W*@E>%s9iOI;2u zX4G41do3yY`h^d-7{o7gIsaX|oax`jTYsn7YqY2&l&di8mQeUn-TMFcm&7%8*YjUh z)MWIF|LVQ?@<;AtuC3fxU*6gFhqGb#%ik}onAUB6_>!^z6z|!SKYsqQYg;9~pzb>7 z#Otg!B|n>;JebPdm^L&YI(R>S*Z$@F3;f@$_LJ@XuK9aT9lw3w!QYc&7pOcbT;`zi ztFB7-T=vytRuw1QQ|-g=pEn3)x%z)s$p&}T+6@7QDvaMO9DFzxGRQP7n56MO1<{@=i^Ulo1-d9&=+q-0!QQ5k0#BN?Cn#Nz{OU-{#|{$UCdSgcgCeM z^ZaXe-@0(PW8SL|x0{~}Gx9jxPgnmacl7hSxr#rZKJENv*S=D6f!(dk4DiGzR!;iWb9^% zvD2&7+G9bDkL@qVCm z5%)yvrU_e*HdjJ$HeSK-%gBzdp3TcvLEQ^ES)#Sqvg4 z=S2l8{HS+(tJ~PLe?Lq80<(}0@q548H$C7#GO1}(`%&2=ZBnbgA3EH>WS<(#*ZpxN zf7Q0WsoQa(qo6p=oGIV|f9+hm3VFZpS6_0!{4b}^C6&c-LHo~M-4ll!7?`{lGtOg^ zDO%p(UA5olZuoM$yT`fy2XB27#aJxOIU|&5g5;mQyY939ymQC)+EV>p%=@x`&fom! zpuwKC?`>*tMn2>>ajI=D=c;IYFrl{j{I?H{PKsjetMd1+*R#8SVUxi9}nVfGi+?=}A$pYoq7KJZ;kq5dSJiO1iT_Sb(WJ@{u)BHjl}XC3VCuCLh0fAvqpv6cHjgfuK? zu`xAZk=OiV@l{+gt}jUFgUVKR{rU-se4Yn%#2NDr+>ZN~yx_t61^+#_E^ONW{f}MS z1Ad?8eW&C0{+BS-e2zhi$qzMJJ7^6j(0yzSo$i{uRgUpd!IJ0N|vZH7>R{3qjA&Q3aFVf){&ZT#|tM;(x`a{q2AJ)qXHvUiN+m|1ZnP+>X6}-p#g~ z@N3npp!>#N|n>$ca9dK|c?Hj}|e~E|yTw=6a|)VxGL;=yI`F`#5@bj7Cq;$~Ir`X2C~nemHZ{*#xNzqYOT zSikX?fxK7P++Sa>mK>Nq-TuH`HFKs5pWoU?eJFn*|7eH*wjYcZEx%?fZs&?y^zRw# z%Y9l$+um3)21~QfxWd73u-^arioX&E5!#|c=t?Ryy#Tk~SZd3jCE zAfXQ&v31+3Z^gX!bhymv(Es4SG5=X4fPe;58=RK3Pqz9#&E)?S5UhyUJt zX6_Qm&tN?Dmq@;_j_uNlz$(F2ej5*7J~aQ`JhRKmDpoye!VPiKbv|4(pf z;m@;^_S9d$`uE)%ImQJtUv*Dk{&&SoU#qV7*Ac~YDlZN%Ut70r_g$aWo(HZhXV`Q1 z@msEcfqq}D7j*YO)`_gX-Kslp#=Zp%1|3(9&sqD-Zut`P75O&Gcf%*Ub^J9lojvK` zSycwu0>v0`l9*m-(~WF)@eVRjb#4pJwN{e ze?rNt>o!w=?s>P8qu-!HaI<($_|k~a39&VK2af6IbXlK$!!_fP$bljW^?(oG<)Yr( zX-NKAAE5k$d4=2G^FMb@vH5=Awf)}oUuRZ{*za4>eVxT8%2=5}de7he{`GtOm;7G! z#nCQkI{%8D3*0oPzw9oL+r{3n>dN`4dTGysz4tC&`D>Tj(YcpPZ|?Y?mnyQLQ?P-z z>A(77mj6e%|9P?1pZDGAXU@>Fz~OC^o$7U#IZM8|sfwvy_x}1dacl4ERXGBiSIxR4 z(!d?IBPo>Q*N3+R|GGiH3? zJTPnKl_|#j zx#{3L@B3@^eZBE+qukM%`Y%4sdzUG7NR1(T-{1J<_Fd2at-j2DB|5?@@B8Pt2(3Si z8D6h8%dHQ~vj2ZE>%C8HPH308`KwJA4?0d<&g7)Vkp1tF-Ew=kmxncr_QbuN^3H5F z|NRdeuFKw4X4v9fJN>-sM@FZmuQpbsACTU4`<2bnjTh^~uc$1zs@br9rK|O=?N=Jb z&dsxZbAIAJhFMGP&gdKU2Wy*l{Y?3~b^ok)t1oshUq0{t!f$Jz@=J;32|kdTIqCU~ zbr<9nk8Irj%b{vxxA7sN&;L&UG_Eb=E9+;tux;hHiCv-mSC2;Ty<-sfg$+jq z8@{u1{fnCsRCMuvB}+X+@FKgj!CTM2nzw1^`qGID9`8C{CDLxU{B-Hv;Mq>94B31B z)<^CC;=k?)BnmZ{)*0F@w)9zQU%7JD_dnAMTK6d|-6z8!(D!S{^w&YJRFA7(i_P7twRb|5 zZNv4&YhzV3f`l%d5^2yjtlYnU{Z-A+HqO(7rv9_BajUIoD1I>M;L@`Ey#cj_CyQS1 z)zGv2Va>@A*jXm5D8{&MqTt_kbtmWheh+=g{QRN%duH9h8HchZ{ld~@l6;vNczkPD zWG}7I+U_F%>TMf;_l0(?zRHb{Z;1+sUS6fok(0>sAUUw9{!`jkA^Di_@1L{6n(P~H z2F!o{OKs^k_FWzqZch!F64f#H@N33B1{)-=3OCf=`)B+9|HOCO>}pT!xxfF)p8er} ze&5Mvm?8SLcAZc}R(ivA=8*Z}*SlY3*&et)-TsDq%T|Vo?hJeGfByc(`|DOK*81?D z+NCy6m;Pd^2-RUYGwGMZ{0-%v->1A?`f4%%lHXY(>tke2xHip-;14imyrItgtD|nF z9ODWhGs(Q7(oiwG;Nl9$o%tYC)sji-~^vEDZuz%=zAknDmV42SgOW(ho_bn`2MB$dmfwRAsPPKC0#iIX9d*jpdpZ~3YoyW+~ z9xU|0?$fMyn%(htoP1a{=GD51Z=c}i&+zR;$sFBQ&9AF@E@^w6e>rcH!Fzs&*B`4I zde7au-`dZ&&Q5efg~)-Ax&EeZjEj00biVs*MROU(FXHbDpR{jb^{HnM&hN2jc;j5V zw423tZS|>dw@iAYL%Xu$_yzK^AFSX!ASZe5_*ZYOiC?D%Wxe<>RJ*|Po;bs`C3csy zmwHV(`PHi7ddc=u&3%>b4drZaGT#I?a40jx2X=?NJ->C1H_zYcF;|!viXUALT>n0A zb47Rqe}4CYt&C2+Z2i+1-b}o?C;DdKl&t#GNkUmWS?gmS|B5{GoB6=Ap!+YsGN_*8 zE@)h!e7V-Nt9W(L!RmQg*3UE?-U>GOvq}H@F1TO4WP7CE74{#?O86OWO!##nx>VD! z>($FU)62G(YTm0n@|*c?aP49hhTSP9_g?>B_->nBYR5XIuZ!Mko;LmUVA`sDUWUy} z_k9V9^_s#}xA4~w;mft5UQ9J2nVtybLKR6oP`-&Dz}{TZE;m&DC!Tg$+}y2AhK zp`Fjm{3HE>lJ45Tr z{Mv3k)n!b2LG$DP9&rd3dcf5f8ZW`_%TRVQ!EV9-5BbY}bL_EDDL=oz{hzH5D}zMa z^6=v2Ofk|5N16^C31upu%OzhWlJG{bL0{v?{Tc682dlkMu8Y56n_AgwbVcZRBojl~ zgkKTY*-w16`T93dj3q3V$3BheLG9f4@A3{89NmA%_P^eb&#&HzF&IoSIXkO*)vDh$ z-~TFVaDG_F5YBby;)KNv&8iI6f4t*Y|G%()+3#0V80W1!9eFo6-!JRj<@QNU_6!Dm z`W$E87M~B`u`s^t{vejIT8u;LP=qprUfqU#*(V!c@W1ETaOBW_Z_z*YQ{OQ%FbB_n zt(2{Lj;q4Ai}mWur#F_f)@`YlIkk~3@eXuJ)=)ucRvi^=TNg@d^1RGv|k$;lQr>Jqcn6cxI?SgaX zyh4^TF+BO0=(scgjDAo~L2ZM^y5QeM<<@H!aUMvN^jgtc-^u^`8?|3X`=$ads@~`tlwiC6D}F82@Pd|2v!i>4z-yJZjItq4xFc&eF&jkrS_v9174W4f?$E z-O4>moCmh`99dfDtoz41|NqW(hx-foC2s#?P|)~#@t)X>YQF||#y;=wfj^EMaE`O{ z?q!-H$M|D{(KHXy|8J&WExP|Ne$G0Eh8;z}Uv(^I&=a0B`^K9UDNF^?QtLRJXU^IF z=jpmX_AAbOO4!BDz_N7Ttwy8m76B_{89Uvt?cKU>#T%A{+W{^|6e1tJou6CJaG_6g z9c#o@wjI%(nGHe84Cb%n4X%8%NG;&|qk8WjyJQt3!<6NAx7D}oTU!0vKs;%pFCui3Dh_vk_U@BAn3*fVT7ezdWuEcYktr=NFoHSQ(-U;EB1K8$HX zo5H_rdUWY5ClJH#k3BbMhX=yUYwy6Wtisy_#25T_)wp=e+US!T;~X-`&Y< zxFgt*zshy>_x$vgPxGE8ypw0RHFdYx0dB_m_wF8BsPe!q)<$;1+H$4|M*aM+Hl-jpFj`P65X7>M`P4!zY|NHZ;evMvwng8C8-}o6WKa^#( zaY|2_M=Zl6Q8Chxi(_by=fp53erJ{{W3&q7w0e&FA(df|)HyZ`H?fBP`!%wc#T z|4g}FxvFf}x6+%}u7w@Z?PX}-d38Hh^n>~C<@bt`&rGn<-Cx*xU|%+aQ>2dAy>HLo z*4O?#eDH%kgU|J)S(f^h>RQ*H9GQ7=32USLyLs2lJ6Rlxj5ljfRcy05!@&^H_p8GH zbKFJSuVR%V2QD)_U-Ema$$SP4<R@7iS(C$M9i zW)H&-ugTy4e}8(&Ugj4wgYfcw|I}jn%KZ0MEUbLT{XAg`OU@S60xnL6cN5OkdxSF_ z`117I&xy+!_XNMUds!CmtIp7U^w7;4jqg@Yx4v5RyoG_`?T#Sb^t0DrcG+4kKG$qj z@Zi9$r;X9_j31iX=lqn=|M%N%7dwN`oL@ETXPmG7SbX*7v#s+OY*b@^|9V@(%5cX0 zory!<8_tC{SrRVFo-6SBe&w|9DVFvA-x(TadUP`u|N1X1((H7N<%Y{?Wd^;!vzw3B zGZ@70R6gMEbvfOuZVnkl-D7io7nj6D8pX>YXOK~zj87d#9HoV^b78> z(z1FyI5+I60R;ZAc>OLP4>!Gk>vJc%14WEP+9yc?|l-ZPwmq3m;C z$=k#2%%)pqH%t|5xO1lKVLiivx+3nQrlwpMyqG2!|Np;q?b@}oFWE9MSO=}y5h}iY zUs2N47g3%JMdxDD-rF-I#9A6EtB3XK)i6zX7Si{AURB~hd4>(^=FIr0aCFU_uUU(; z5?K_?7Z5N>QrO0@~ z^6&ZMHcSi~I_KP&D7@KC)QU0Tzdgf(CjO<_@@KCwci1r%cX_=jP_tYTjjL*dTV4Z#wI_mGYO5ST(%hJmC5A7W?o2j109;dla4@S$EOJ zpP^`5aE($dBg2g=?w7arUl!|b?dRar&0$&)Kd<}0%vIa3lQ#DZBBkm_Dp1dHa~{@5gsu-x(ObOf;VK=R^r_ z_9|rtOJ{QihKO@fMs5s7Pu?v46S0el;X?Si(<+<}^4&)tWyJQqomZ8}!oV;=JDux6 z#?ei4EX;PSP-d{4dAWSswrzR7AoCVQ6t(N=aa{?25!j%q#<1oeJHr&Co0$zOI1e1Y zytQBYk|;w%jHRLK7e9~?=gV8idtY)fIINp}d29cLVdJ+0)ea*64I z0?q?T8w-BkPu{gW?gk?RgNCX)qeYORuKGM)tw$UVVVn=vu3dW@6y=eZ{2Jy=I*{nj zsHDbVS**tMpP9ky^v$Sw#!GEibZ|PH)9==8U}(5hw2ZM%A@*?R42CCvHc2otFiiPo ze`#y~25ZJ;W=s=4yE8E)s9Wk!_hHOwFlAt9@F+^+R%Vc{2-x-Qpd#p!! z{Hh^L6Do@)@iQiID=;ir!PL&wD#O}g#&qC=J%hrclfKL@-V8-<3>ClG8F*BeO9c4k zNc(JIQCOa$#L2*rpkb-Zpm>_m-J4+rBLhRn3;#=7`zIW2SZ2cX;b{pg!-6ey0@LJ+ z#LrC;V|=#e0t-WezNKNCDMPk}QS9#oE(QjJDRW#K)-Xmm`w1?XW6#IHP<^uUq>WXq zbLTXMCx0f*U}9k4QB^Pb-2%_v`%E&p&ElbaVq5tpg~69a=p zlpmA8JC6q$-3*KjGn9lHY@hJ-`((wjF$e^m%PEbQSt%5zELR;dO4~=ZB{ljhSGo44hRM7#Pk7a56A37`xyT zcJ*Sgo4>C1kHan|27@W=%d+L;y{;R8;x)n6^6Q`CbAl2qU{gdi8BbWI|6EbR%8;P( z-Rrt%#}tM;)7I6>OkT&p!1MG*$y;UhW4uk83=9ly(~VErc6fjarp|)rI?N1e&tH_x zZ2<|0b^ZOU7(_I^WznIIwv6X>=!TDsT;lq3XbfcIi81o*mVqiEB(C2;K zZr{Xp3qc7-jLC?V0TePK2V`GLd2L})NS}RVNmx1)gVV{GOdQ);FDz1Ju#6OX+Q7)* z3=%09THxx<@J}W7_m4(!0s@6a{hW0ReKJ`T)UBDpDM&57@prS-#x$CGrT=b*n^%MwmN76eEPc8s#)x%8WAvr1{flpMaWH78<}#kq zWSSy;nN3uW@y4xNyMo2H@ALBrXJ9CL`i5bLx1nx|WJ0$EQ^Fe-1_qZ2#((6$y}iFk zjluFYgWSAz3=B_Bb!sMNGelfrd2p=c?c*yYtPBd*Jzw5(_tTNSBeI;~2FQ}9Q%c@G z4t^;WbcIFX`SfSbjS=h&3@3sf2q*|O_~z#3-d*VJ_jSn}MQH{Gopi6Cen*Y2F?^Xa z+464zv~Wzh!Q`MlQMh4Fyqq!vLqklz*LAx*kLw$h)EVY`T(|R!)Dim*EofN_ig&e{ zUKM1}zm;Ilz_4KYmKiq7SqfgTHi(&lJXplKG{3}u?~|On3=C(Y&xL#{zY@Vb;pMH8 zx6Q2$3=RA0Ok#iUh|}L#zV!D7P!-~=v3$8U1H&V#L59M!Ap+Pe6jD^?qIk`_P z4YNaZRT$)buI~e-jWwqYmDB&(o2T`v)i6!)3qEBT_)>nEHUq=&lbw>~8fmTi3|lT8 z;%Io95)yZoli`3>QIeZ+{{L%UOdl4OynTG*Xwt!*3=9pnnqe}_PjlW4*0QwH5#C{pLu;YAZ98mG1gh zn04zGBf|k(4YwVC^3Sc?I7cGX71Y$Y)3v>1&ds|_3)qeZdbdsXtO`T)-J7Q@qjk>tINLBV$mpd{`B8Orh4tlH<0vV{ z4dqL+<=t1$y>^$0;ljn!3!nchK6PxrcI@m+QA`u8k9_(JY6N+`^V_)k(pF7hRfg!; z$OqE9mdDxfP6&Q1$zWiczBF5Y`?hU=^OtS~1$&mITkoX<3=K<7{=DaEs5SlhEnioS zVQ01O{7LH=7;H3Sf1iq+9Tr;5dEnZ$Y0DY%-p;GC?7qvu(D3Bs%Uj2%Us?A)@Al}q9(X~w({Od0&&=P@!EKiTOz2|i`U9oQe*F{O+ubft%a`5hy zcZ>|i3vLwd(W}2=`}NTyd;1yhW?%ip(XdxD_IFj;E+&R&%HQAGr*m2LToZ0MeYNOW z!n{>J6J{|m7zn@m9Lx7_&byV{>u0a?&9(yP6v4LUyTST;ymK~li7;Fc)Bkt+=I{55 z>udFVKS#+kZn*DveP2;~69dEB6SksdLizvAT3HUT8&;?+Sbtfp`=}>qNFy-wW4xd0 z4eP7-F5TU>{wIsV+rwvUdtXk!B+6hAyR-K1)h^5OrN5u~CbAqTTF$sf!XVv`gF#@% zuZr;cv$21sOJ_UHbYWW1b4HzI*Vm_W8P31VV`LC6SvTwS|8J$;Nxqkt*7KWB6=XPl z_RPGsJQH;r84L_x-QN54z4-3s_td_A%MbQqSQqe_sdt|Z>w#$w3^V3!zS8$Puli@( ztk>s!PNjfSxbFW(y|?qKk_{CYe9r4H&yJt2eeBM}ck0Pr$_&$X+kakiG^tPl)X`pU z_ctW=PLWUA`~PK=y%?q}+-Y^$^loszzE?Pd!|%9B>uO_H-!MAq{CJreQ^Njv>uO~^ zB$XK!B)&Qw`$Tv7hyJI>rfg7~&Y%(e`6k2m692u1Tnr3vPtX0m-}mjScV$(}ESM73 z`&{35F6dcB(k^xe6SvwOr~f^B_ok{fPOI2&4sY%%&W3E$=X3ZI^nJq_9I9=;zyJHc zwlaF>_UZoDRD>B)bJkt`Gy86EzQ5mRcZPCPqSOUo0Y+3;q=YLB`5E0e7f`h%{_l_$G9<^`E0c_n=x(?|E-HJwMDydf_Y)sQ&h%vp$PjrTVcNya=pbXa=WK0p-wW;k?44JOr5FwRnVy8P*X%R$ z^JiyZ&)U)s83XD`Ez)mMxD-_YZm5}hT)aAESV2gW6qtE*p~Reh`+{hkle)r!!FV1)W&-t*ues(R> z0-p==9YX33JIxr}*_a&?Uq#23FLn7}weVMscuRN66w&|Ii7XCZ8h7|i&)&UUXZ>~d ztA{NZ8knvuxAT}Tq`YQ+Q1_0v=UeNx9q$di_b>Mi%Y%t8r7jjhrPJhPQd-c(y8=MDvv+S?jJ+_Fy?fa+rOSY+&!IH0wFWE9ISZ=cR z*OzyirNU0S;+qi|Kez;dzYEv z+d{j~DzRRBY%06IbMLw>AoKZOG33q@AHx`u%Eol zpxQR6rta(Qua4zg9T*x`_R!4%anAZMrNv(iE=YfATyDp}P~vs=clD8NF0#{wWPW((B+Gd%O2*Yz$h8eP7Yu{bpYrE@6$%_7;y#cT9NAEiycw2v6TgC6~8Ih;_ zJR83Iw|Ov4Nvz^J;MuvHrKs}YY4$I!3p@lGKy$GB-rvnq6?55r?EmGU`!SQb|JVfQ z?q5`v*tFl(o1yGce8v)$1Ko_V7gw^R_pB2+vB-g8^7LO;;lDm_+b=Y4wcXEYCGp+f zzki*zzjM>ztP;v4BD$z9yFd_{;MV-VC7ea=NiU*-ybqG1YJ44Q|i^{-OF>#S6sDw zaeT4l#H35d_Nm7ze`?x2w-&=lm9PGZ;v|(*Aqx zeUiQEGHcuS|6iPP{$E#lFJ*bu3GqEAlo@vaJrs7FBiugJQ{G`c`-k=gEcJ%>R_Jpb zXJ?+l^EGq%i=bC6_SIoeALRQ7WLy1+_6}ya?drv}=lhnB+J>irGkzV~U@PG8`I1KF zN~8VXioWht^~*f_@p6{^ox9s~KZzVTl^4t?mT7fB{!>u9!B&fS5MUtN||R~y?vjD zovQ%DtOa(bi~q_A+Lr!ot~tFgv^M9g)qlQ!eh$k7jNe~7Rez>#X2=wkgsDrXt@3*M zP)(mlXKMu;1KZ?ZtISu=-S@|FU+UM;uWL=k|9<8C_wfF|wM%oBO;tJ2%@nJ=Ktx{3 zMTlVmt6o;vrv;n2uP%?9`!0J%&WG)uyy+*jI6uf#nf^Kux9D{0-C#jGy~$aP42Ldx zpUN&R^t=o?|7(ZL4$NIMS@hq|Tl>R{H_p@gvh;)3js6vLxBPLGoUWn0 zE|y7{VOr1!{m@6(^Ma~-ep%}OTb(F&@V=V$7R9Ul0$%b7?|C$K&j0_jhlQadq2a;% zeV4TVMRWi2&H8Fzbk|4QN$9~MJNw09pO}4x7#6HzZ2F(IJAQ58?Kuzb-?_bi>zt=K zvsgm3*RSUPvb;o*lfi(KndSfPD|K$QOZ|VYPAofkKPr}a!={s57xxEz{&&S+f4Cbdxz2v$Q^8>5f_E{{=;$KkMT`m7HeEYuIBmX`76JOjvtr{|yrFzD9zQ5Mjblx<{ zU1{I>zH0BR^9S@#sxQ2@>f?OtH~vfk7ngY+c)v1scg-q?c!l*-zLw3L^yg^dMxIFx zok1T;Ts{0#Z9N)4H=maGoAIwY*5lddU-@gdb%?in9lWm}sQUfqZSE`Om9I;9H}n1j zHP+4UYX6GcdjEai@#XrH>`lbW{f}0hUby*1RCoFWPsjHXv3_%$8_qAhKlyaZ{YCeM z=YO5J;{2yRHTM5O?DN*{xN!dT<-a#-7R~SfIyd#h_LV9O+cJe71U6)eF8k7|W!s+i zFTY6WfM!$UQiu93rihbPf}5A!f5{lTPF+(&pY?zF>YXN)o~GMP@~c+ee>jW(pV@Pk z19>6Ut7k1|b2ASRdNAu||7G#hyZHlVGEH6`WOXC{>R*=Ri|_AUR#iDHdBy#w$Nlds z=kEC9XldHKf3oX#85IV`ubxbKVZ}lZ4mmEqEPh6{&Rc&);qvdoznZSvI3!OKDe()s z@NpeK%cA|=_kY+p9k7~dx-$P$;6A(a_dOjv^*;VyQ(|wn|LgO<6d$D@(Wh@SlwW$z z|GB_4U-(Pe!HX}u9~4awFY!P6gZnr8FQ(~on+_+6MDSU@pYuR|@;|-n>zeleOJl!c z5tGfo@_m8mI!Ih0$Coi-+y!wyr^Y>e|RIlDz`^4h; z)PHebrY8p-yuZwhb;G4}tpj=CyJOyJpI$Eb()+o&kU#qr8{h1w*S{>SF5GpzO+;l6 z>w6#8-l*SG44Kr+e@UOO5?uIeVRy;T>vw|#x4xQMk@7vqH2?c)SEdUS_IVz-e)Zmh zZ%3bA?qKs{`omf?ZC}H>9j&z*5a@63z$|5g0GoA2AddBQKb@74bC{}!x? z%KxY8eb8U;$L%S?46kRiR5&j9zNaqjr^%u(t~FDmUfyLa+dFGJ=O*U_@mGH$E&Eh! z#riI%Cw^+}xbnPW-hEN8_6XxIR;J>=B&~l`TU`%c%^zp?bY{wj?JLz8u9Si^I;oop9ym{B`H%G1=^B*HmwPm0lCF^Z9RY{coG2qW{V* zc(&F5d$irtnK>V}Yw9uC#N4-EP;YKO{nm^9%fB3zT;y~utNnn$WuCKv&)zB0d#u?mGC8s z@zz%|JnR0>*_cA z$g|x4y!LLG$l&9VC-lIzF;3;NYt!OO;@1vG+BIf(moat(I?`@5*$=1bid zi1)L-D83T&XwAMO`M>i+POoj*F8tr_TzGs@=68!#^Fw~We7CLRBzyhrV5Wqfo(;|a zQ?ugZ?0P-o?--YSbdtUPRPXHZO;v>tQrZt*O_X2D_xs!zj+D#kuUKlA&05L4(C$e0 zpF8b5hELw#b-nk@{99P${I7*6{#+3-F%RXab{t~cX>{)Wxp z!&mH$iGOs{mvf6x5dal)kAf5Os4|KzUYYe6M208DY;R0$BjzfM8F{``9zxAxzI zrV@L#^{!$UaE|7GB0-i^w*p3wnTq#_pb_*gbr4_XAhpTHtD?+ zENWa}bywi)?XT}2CSKi6HW53YXjY2}(o_IhQ;AL}N0 zI;<^aJpSsFpXv4Qa$Wax;+H*3Jn(8U# zclYNn1qoX>t#}pVAN<_vme+@k|0^Zt{ae=T&;Me6U`=kSN*c0_O^_7C#+|Oalt!{1D|1VNT z(57&$N;x?E_>!FKx5uw}zjgopWT_&M8Mh}eyjhbXl(6+0>yNhwf(7SENlaqixShK% z=&bMDuNu#{yq;gYho*o|8;w_q;7>$f_Ctn_vTIi6FnHp!mT)e%()eP zb>FrcsZOij#ifN87cA6j%>6EAD$iyV@zu8UQuF)oY9G$e{`arhhxoR^X90$ zwyx>XvlG1X`+obg1@DU;kF8r^`J`sQ;Gc2_rVZOKc{Yf%{#$qJ?CIr#D}{fC#>Oo& zd$M6+<+=;eY~A`oA?#K!wDSJG55Mc%VA1Bq^mTvrnt$cjwKlJPzh?jH^TD65?Nb#0 zIdgybyZ1J8&2NgpH@vQHhzpys8{ zxa|Fdta<-kvyy$z&gA~RW6|>!PbVIA|6a9lzW--wp@a3lDh#uwwKxkNE-+u&Tg6xZ zs$tKG7p+;&`T{W@>mRpfg-_sEpnBAuNvzxc($T~Jl0THcEzP=j-~OB4bos#F?pe;? z4O#y4PGIZU+AIGy-a0(p|FF`J{0^1{`xj1P_}paa&shL1Wc)K6mXXbR6r#P8mJ^Kf) z*nCNusa7iUwVqvUSg746`|h2KL;n9zzMqU)5*nNzx?Ar5I<8mvrnSya?DpY*$qGNd zyD&}2KcR9U#___`KgZ;FKX|2Fmys{dJ#fnR)t0tZtb8rO7K|UF_hwM6bT6`ayMVt2D z|F!qT`pnwaMtz|_wOjdD_wMqzaHVkWx^Cgd zUOovWMh3pAlNx4y{!@4Pf9BSO{_k${Hr6MsVU##NL;lItx;yb@Mq` z^c6dWZtmmwccAZ&>?*_a?{_@d>iR#Z_kn$4$)3kj)m{he7ME?k6+X>h`I2d2{`L3a)lIivEN*L8{L$Z`+3@k|kA~mAf7Gw;DSa{V-@zFo zPu>R==YHD1^jUqol=h6qYdwK=w{QPi45bp{Lq2b5@aFE%yC`d2 zvu;w8_pbR27Iz-qbC=ou^{sN-s&~()Hw9mnTOF1heZP$5pO**2tJPCkW(YTYU)39D ze?aWJ?WZ?u*WZ7d)y8m4Q1gTI!Tm}?4H8*ujAff|{l02r#E?*BbN98A-Mwq(Wvu(} z3jEPlX6#s8CX`^ls?sIli!3w4zU;#H%6>P0efyMgKW6oT_ofbUFT`07+A|0>RAi|$ z>B&9mYVu=YTdlEz@0~z?g}-v<;+Yt&ahUR}6L@`9DRp#8FM99vfe?_~M^ zz=PqIpf2Zz^{Z+_-tu3q;d=OBipd{SRo?n(a*dXS3(61t58N8%{&`*ZgZm6Z4c}aR zSoSUbR%oiuEd5p{z(IcVJagU@xe`aQRA*(t`)@ zojdN-*cb-0%$W5)w0g&^=imCnvokyDt(pEGnZV!^?(KP?OPsqd?5)~X`=I0$hP9t& z|Knd-dH(H@tS%wkzn5p^J&Z4SqdKO3|Kn6_4G+!y`%Z{4i+AIfrvCO1E@FH~Xpe5p!l z!zWi$b!Iu)9cS$s+#db>BOg+H!QOH0)~ml?D||T5!g;{PS5?qpy5hH8jQR>C$<>Np zpYH6v|H-!h{j=ZKo_{WPelTBBsNv_VAfXL&m$QeRY!GI?F2``qba}&m*@N|M9t;Lo zT@sEeGx@RA+}kzVou^^yPPRXyO#gc(G9;wF>O9bOo!>(7$K;at#@q(-_vSt4s;Lh? ztNr!4*oSbAKMIToY|p3)Mij1PRA-9w@dz@|z1{rxen9bt`HvRu^%eM0XfDL?CMD1F zz(WtNTh1Hq%O8|)S71C4_iP?V&UP=ZJZRUORqt`BuXKs|QR$@G$=D}k2 zn5%8|kq6RQ_a?naIe7i*=R4apfBF7hQL=actmeP*p5IqHyic-z(SM+S>jZ`c{6dYJ z!aNUtJCg9#*k6CmIzNql%fwp4&s#UlX5PiTeLdfce>Sw;>A?^n)X89A!(`v3 zz<5BwgQejU#~+O@76xZ!Nrqp_FOm&Z7$mqm88+B4*|WJbHEf*7!2t5Gi3-Dt&J+fP z7mJ027+f5b8Z4DQEY=cYaB((j&{Te*%p=6$;;Pi3srW%zMu=fcLkfeg(hFr1A%-n& zDGU<@zg*-IV%XA?!XPN{<)VrZgAeOThGQ-l+&_CT7`Uo1OE5XgPj+HzuvAcVkY}m! zk>X@{)|SFht@t9@%!9$8T!rPc+kxXb6B!uVl@w=~G1*O+z`#(gte7FqD1U>MlVOA9 zq^21h-wv>OFfa2<2qz-}~=f#I;S<%@O&ex42%h85ZGPBJ>mt!QCk=vaSMrSSn@ fOX(58|MEuh%=62RYiBbsFfe$!`njxgN@xNAAmgnG