Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Plugin Endorsements #1403

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Dalamud/Configuration/Internal/DalamudConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,11 @@ public string EffectiveLanguage
/// </summary>
public PluginInstallerOpenKind PluginInstallerOpen { get; set; } = PluginInstallerOpenKind.AllPlugins;

/// <summary>
/// Gets or sets a list of endorsed plugins.
/// </summary>
public List<string> EndorsedPluginInternalNames { get; set; } = new();

/// <summary>
/// Load a configuration from the provided path.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -200,6 +202,7 @@ private enum PluginSortKind
{
Alphabetical,
DownloadCount,
EndorsementCount,
LastUpdate,
NewOrNot,
NotInstalled,
Expand Down Expand Up @@ -556,6 +559,7 @@ private void DrawHeader()
{
(Locs.SortBy_Alphabetical, PluginSortKind.Alphabetical),
(Locs.SortBy_DownloadCounts, PluginSortKind.DownloadCount),
(Locs.SortBy_EndorsementCounts, PluginSortKind.EndorsementCount),
(Locs.SortBy_LastUpdate, PluginSortKind.LastUpdate),
(Locs.SortBy_NewOrNot, PluginSortKind.NewOrNot),
(Locs.SortBy_NotInstalled, PluginSortKind.NotInstalled),
Expand Down Expand Up @@ -1545,11 +1549,13 @@ private void DrawImageTester()
// Name
ImGui.Text("My Cool Plugin");

// Download count
var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420);
// Author, Downloads, Endorsements
var authorText = Locs.PluginBody_Author("Plugin Enjoyer");
var downloadCountText = Locs.PluginBody_DownloadCount(69420);
var endorsementCountText = Locs.PluginBody_EndorsementCount(1337);

ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText);
ImGui.TextColored(ImGuiColors.DalamudGrey3, authorText + downloadCountText + endorsementCountText);

cursor.Y += ImGui.GetTextLineHeightWithSpacing();
ImGui.SetCursorPos(cursor);
Expand Down Expand Up @@ -1908,13 +1914,16 @@ private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, IPlug
}
}

// Download count
var downloadCountText = manifest.DownloadCount > 0
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
// Author, Downloads, Endorsements
var authorText = Locs.PluginBody_Author(manifest.Author);
if (manifest.DownloadCount > 0)
authorText += Locs.PluginBody_DownloadCount(manifest.DownloadCount);

if (manifest.EndorsementCount > 0)
authorText += Locs.PluginBody_EndorsementCount(manifest.EndorsementCount);

ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText);
ImGui.TextColored(ImGuiColors.DalamudGrey3, authorText);

if (isNew)
{
Expand Down Expand Up @@ -2062,7 +2071,7 @@ private void DrawChangelog(IChangelogEntry log)
if (log.Author != null)
{
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, Locs.PluginBody_AuthorWithoutDownloadCount(log.Author));
ImGui.TextColored(ImGuiColors.DalamudGrey3, Locs.PluginBody_Author(log.Author));
}

cursor.Y += ImGui.GetTextLineHeightWithSpacing();
Expand Down Expand Up @@ -2395,14 +2404,15 @@ private void DrawInstalledPlugin(LocalPlugin plugin, int index, RemotePluginMani
ImGui.TextUnformatted(manifest.Name);

// Download count
var downloadText = plugin.IsDev
? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author)
: manifest.DownloadCount > 0
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
var authorText = Locs.PluginBody_Author(manifest.Author);
if (manifest.DownloadCount > 0)
authorText += Locs.PluginBody_DownloadCount(manifest.DownloadCount);

if (manifest.EndorsementCount > 0)
authorText += Locs.PluginBody_EndorsementCount(manifest.EndorsementCount);

ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText);
ImGui.TextColored(ImGuiColors.DalamudGrey3, authorText);

var acceptsFeedback =
this.pluginListAvailable.Any(x => x.InternalName == plugin.InternalName && x.AcceptsFeedback);
Expand Down Expand Up @@ -2474,6 +2484,7 @@ private void DrawInstalledPlugin(LocalPlugin plugin, int index, RemotePluginMani
this.DrawDevPluginButtons(plugin);
this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl, false);
this.DrawDeletePluginButton(plugin);
this.DrawEndorsePluginButton(plugin);

if (canFeedback)
{
Expand Down Expand Up @@ -3200,6 +3211,53 @@ private void DrawDeletePluginButton(LocalPlugin plugin)
}
}

private void DrawEndorsePluginButton(LocalPlugin plugin)
{
var showButton = plugin.IsLoaded && !plugin.IsThirdParty && !plugin.IsDev;

if (!showButton)
return;

ImGui.SameLine();
if (plugin.Endorsed)
{
ImGui.PushFont(InterfaceManager.IconFont);
ImGuiComponents.DisabledButton(FontAwesomeIcon.ThumbsUp.ToIconString());
ImGui.PopFont();

if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_PluginAlreadyEndorsed);
}
}
else
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.ThumbsUp))
{
var notifications = Service<NotificationManager>.Get();
plugin.EndorsePluginAsync()
.ContinueWith(task =>
{
if (task.Exception != null)
{
Log.Error(task.Exception, $"An exception occurred while trying to endorse {plugin.Name}");

var message = Locs.Notifications_EndorsementFailed;
if (task.Exception.InnerExceptions.Any(e => e is HttpRequestException { StatusCode: HttpStatusCode.TooManyRequests }))
message = Locs.Notifications_TooManyEndorsements;
notifications.AddNotification(message, Locs.Notifications_EndorsementFailedTitle(plugin.Name), NotificationType.Error);
}
});
}

if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_EndorsePlugin);
}
}
}


private bool DrawVisitRepoUrlButton(string? repoUrl, bool big)
{
if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://"))
Expand Down Expand Up @@ -3384,6 +3442,10 @@ private void ResortPlugins()
this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount));
this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount));
break;
case PluginSortKind.EndorsementCount:
this.pluginListAvailable.Sort((p1, p2) => p2.EndorsementCount.CompareTo(p1.EndorsementCount));
this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.EndorsementCount.CompareTo(p1.Manifest.EndorsementCount));
break;
case PluginSortKind.LastUpdate:
this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate));
this.pluginListInstalled.Sort((p1, p2) =>
Expand Down Expand Up @@ -3576,6 +3638,8 @@ internal static class Locs
public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical");

public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count");

public static string SortBy_EndorsementCounts => Loc.Localize("InstallerEndorsementCount", "Endorsement Count");

public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update");

Expand Down Expand Up @@ -3671,11 +3735,11 @@ internal static class Locs

#region Plugin body

public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author);
public static string PluginBody_Author(string author) => Loc.Localize("InstallerHeaderAuthor", " by {0}").Format(author);

public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0} ({1} downloads)").Format(author, count.ToString("N0"));
public static string PluginBody_DownloadCount(long count) => Loc.Localize("InstallerHeaderDownloadCount", ", {0} downloads").Format(count.ToString("N0"));

public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}").Format(author);
public static string PluginBody_EndorsementCount(long count) => Loc.Localize("InstallerHeaderEndorsementCount", ", {0} endorsements").Format(count.ToString("N0"));

public static string PluginBody_CurrentChangeLog(Version version) => Loc.Localize("InstallerCurrentChangeLog", "Changelog (v{0})").Format(version);

Expand Down Expand Up @@ -3764,6 +3828,10 @@ public static string PluginBody_BannedReason(string message) =>

public static string PluginButtonToolTip_SingleProfileDisabled(string name) => Loc.Localize("InstallerSingleProfileDisabled", "The collection '{0}' which contains this plugin is disabled.\nPlease enable it in the collections manager to toggle the plugin individually.").Format(name);

public static string PluginButtonToolTip_EndorsePlugin => Loc.Localize("InstallerEndorsePlugin", "Show your appreciation of this plugin by endorsing it");

public static string PluginButtonToolTip_PluginAlreadyEndorsed => Loc.Localize("InstallerPluginAlreadyEndorsed", "You have already endorsed this plugin");

#endregion

#region Notifications
Expand Down Expand Up @@ -3792,6 +3860,12 @@ public static string PluginBody_BannedReason(string message) =>

public static string Notifications_PluginEnabled(string name) => Loc.Localize("NotificationsPluginEnabled", "'{0}' was enabled.").Format(name);

public static string Notifications_EndorsementFailedTitle(string name) => Loc.Localize("NotificationsPluginEndorsementFailedTitle", "Failed to endorse '{0}'!").Format(name);

public static string Notifications_EndorsementFailed => Loc.Localize("NotificationsPluginEndorsementFailed", "Please try again later.");

public static string Notifications_TooManyEndorsements => Loc.Localize("NotificationsPluginTooManyEndorsements", "You have sent too many endorsement requests.");

#endregion

#region Footer
Expand Down
31 changes: 30 additions & 1 deletion Dalamud/Plugin/Internal/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

using System.Web;
using CheapLoc;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
Expand Down Expand Up @@ -1713,6 +1713,35 @@ private async Task<LocalPlugin> LoadPluginAsync(FileInfo dllFile, LocalPluginMan
return plugin;
}

/// <summary>
/// Determine if a plugin has been endorsed already.
/// </summary>
/// <param name="plugin">The local plugin.</param>
/// <returns>A value indicating whether the plugin has been endorsed already.</returns>
public bool IsPluginEndorsed(LocalPlugin plugin)
{
var config = Service<DalamudConfiguration>.Get();

return config.EndorsedPluginInternalNames.Contains(plugin.InternalName);
}

/// <summary>
/// Endorse a plugin.
/// </summary>
/// <param name="localPlugin">The plugin to endorse.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EndorsePluginAsync(LocalPlugin localPlugin)
{
Log.Debug($"Endorsing plugin {localPlugin.Name}");

var response = await this.happyHttpClient.SharedHttpClient.PostAsync("https://kamori.goats.dev/Plugin/Endorse/" + HttpUtility.UrlEncode(localPlugin.InternalName), null);
response.EnsureSuccessStatusCode();

var config = Service<DalamudConfiguration>.Get();
config.EndorsedPluginInternalNames.Add(localPlugin.InternalName);
config.QueueSave();
}

private void DetectAvailablePluginUpdates()
{
Log.Debug("Starting plugin update check...");
Expand Down
23 changes: 22 additions & 1 deletion Dalamud/Plugin/Internal/Types/LocalPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ public LocalPlugin(FileInfo dllFile, LocalPluginManifest manifest)
/// Gets the effective version of this plugin.
/// </summary>
public Version EffectiveVersion => this.manifest.EffectiveVersion;

/// <summary>
/// Gets a value indicating whether the user has endorsed this plugin.
/// </summary>
public bool Endorsed { get; private set; }

/// <summary>
/// Gets the effective working plugin ID for this plugin.
Expand Down Expand Up @@ -432,7 +437,8 @@ public async Task LoadAsync(PluginLoadReason reason, bool reloading = false)
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
return;
}


this.Endorsed = pluginManager.IsPluginEndorsed(this);
this.State = PluginState.Loaded;
Log.Information($"Finished loading {this.DllFile.Name}");
}
Expand Down Expand Up @@ -599,6 +605,21 @@ public void ScheduleDeletion(bool status = true)
});
}

public async Task EndorsePluginAsync()
{
this.Endorsed = true;
try
{
var pluginManager = await Service<PluginManager>.GetAsync();
await pluginManager.EndorsePluginAsync(this);
}
catch
{
this.Endorsed = false;
throw;
}
}

/// <summary>
/// Save this plugin manifest.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions Dalamud/Plugin/Internal/Types/Manifest/IPluginManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public interface IPluginManifest
/// </summary>
public long DownloadCount { get; }

/// <summary>
/// Gets the number of endorsements this plugin has.
/// </summary>
public long EndorsementCount { get; }

/// <summary>
/// Gets a value indicating whether the plugin supports profiles.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions Dalamud/Plugin/Internal/Types/PluginManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ internal record PluginManifest : IPluginManifest
[JsonProperty]
public long DownloadCount { get; init; }

/// <inheritdoc/>
[JsonProperty]
public long EndorsementCount { get; init; }

/// <inheritdoc/>
[JsonProperty]
public long LastUpdate { get; set; }
Expand Down