Skip to content

Commit

Permalink
Merge pull request #255 from Controllerdestiny/main
Browse files Browse the repository at this point in the history
更新: AutoPluginManager插件
  • Loading branch information
Controllerdestiny authored Jun 23, 2024
2 parents d8d0e58 + 6e6cb4a commit 54d9dc8
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 270 deletions.
File renamed without changes.
339 changes: 339 additions & 0 deletions AutoPluginManager/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
using Newtonsoft.Json;
using System.IO.Compression;
using System.Reflection;
using Terraria;
using TerrariaApi.Server;
using TShockAPI;

namespace AutoPluginManager;

[ApiVersion(2, 1)]
public class Plugin : TerrariaPlugin
{
public override string Name => "AutoUpdatePlugin";

public override Version Version => new(2, 0, 0, 0);

public override string Author => "少司命,Cai";

public override string Description => "自动更新你的插件!";

private const string ReleaseUrl = "https://github.com/Controllerdestiny/TShockPlugin/releases/download/V1.0.0.0/Plugins.zip";

private const string PUrl = "https://github.moeyy.xyz/";

private const string PluginsUrl = "https://raw.githubusercontent.com/Controllerdestiny/TShockPlugin/master/Plugins.json";

private static readonly HttpClient _httpClient = new();

private const string TempSaveDir = "TempFile";

private const string TempZipName = "Plugins.zip";

private readonly System.Timers.Timer _timer = new();

public Plugin(Main game) : base(game)
{

}

public override void Initialize()
{
Commands.ChatCommands.Add(new("AutoUpdatePlugin", PluginManager, "apm"));
ServerApi.Hooks.GamePostInitialize.Register(this, AutoCheckUpdate, int.MinValue); //最低优先级
}



protected override void Dispose(bool disposing)
{
if (disposing)
{

ServerApi.Hooks.GamePostInitialize.Deregister(this, AutoCheckUpdate);
}

base.Dispose(disposing);
}

private void AutoCheckUpdate(EventArgs args)
{
_timer.AutoReset = true;
_timer.Enabled = true;
_timer.Interval = 60 * 30 * 1000;
_timer.Elapsed += (_, _) =>
{
try
{
var updates = GetUpdates();
if (updates.Any())
{
TShock.Log.ConsoleInfo("[以下插件有新的版本更新]\n" + string.Join("\n", updates.Select(i => $"[{i.Name}] V{i.OldVersion} >>> V{i.NewVersion}")));
TShock.Log.ConsoleInfo("你可以使用命令/uplugin更新插件哦~");
}

}
catch (Exception ex)
{
TShock.Log.ConsoleInfo("[AutoUpdate]无法获取更新:" + ex.Message);
return;
}
};
_timer.Start();
}

private void PluginManager(CommandArgs args)
{
if (args.Parameters.Count == 1 && args.Parameters[0].ToLower() == "-c")
{
CheckCmd(args.Player);
}
else if (args.Parameters.Count >= 1 && args.Parameters[0].ToLower() == "-u")
{
var targets = Array.Empty<string>();
if (args.Parameters.Count > 1)
targets = args.Parameters[1].Split(",");
UpdateCmd(args.Player, targets);
}
else if (args.Parameters.Count == 2 && args.Parameters[0].ToLower() == "-i")
{
var indexs = args.Parameters[1].Split(",").Select(x =>
{
if (int.TryParse(x, out var index))
return index;
return -1;
});
InstallCmd(args.Player, indexs);
}
else if (args.Parameters.Count == 1 && args.Parameters[0].ToLower() == "-l")
{
var repo = GetRepoPlugin();
args.Player.SendInfoMessage("可安装插件列表:");
for (int i = 0; i < repo.Count; i++)
args.Player.SendInfoMessage($"{i + 1}.{repo[i].Name} {repo[i].Path} {repo[i].Version}");
}
else
{
args.Player.SendInfoMessage("apm -c 检测已安装插件更新");
args.Player.SendInfoMessage("apm -u [插件名] 更新所有插件或指定插件");
args.Player.SendInfoMessage("apm -i [序号] 安装指定插件");
args.Player.SendInfoMessage("apm -l 查看可安装插件表");
}
}

private void InstallCmd(TSPlayer Player, IEnumerable<int> target)
{
if (!target.Any())
{
Player.SendErrorMessage("无效参数,请附带需要安装插件的选择项!");
return;
}
try
{
var plugins = GetRepoPlugin();
var installs = new List<PluginVersionInfo>();
foreach (var index in target)
{
if (index > 0 && index <= plugins.Count)
{
installs.Add(plugins[index - 1]);
}
}
if (installs.Count == 0)
{
Player.SendErrorMessage("序号无效,请附带需要安装插件的选择项!");
return;
}
Player.SendInfoMessage("正在下载最新插件包...");
DownLoadPlugin();
Player.SendInfoMessage("正在解压插件包...");
ExtractDirectoryZip();
Player.SendInfoMessage("正在安装插件...");
InstallPlugin(installs);
Player.SendSuccessMessage("[安装完成]\n" + string.Join("\n", installs.Select(i => $"[{i.Name}] V{i.Version}")));
Player.SendSuccessMessage("重启服务器后插件生效!");
}
catch (Exception ex)
{
Player.SendErrorMessage("安装插件出现错误:" + ex.Message);
}
}

private void UpdateCmd(TSPlayer Player, string[] target)
{
try
{
var updates = GetUpdates();
if (updates.Count == 0)
{
Player.SendSuccessMessage("你的插件全是最新版本,无需更新哦~");
return;
}
if (target.Length != 0)
{
updates = updates.Where(i => target.Contains(i.Name)).ToList();
if (!updates.Any())
{
Player.SendErrorMessage($"{string.Join(",", target)} 无需更新!");
return;
}
}
Player.SendInfoMessage("正在下载最新插件包...");
DownLoadPlugin();
Player.SendInfoMessage("正在解压插件包...");
ExtractDirectoryZip();
Player.SendInfoMessage("正在升级插件...");
var success = UpdatePlugin(updates);
if (success.Count == 0)
{
Player.SendSuccessMessage("更新了个寂寞~");
return;
}
Player.SendSuccessMessage("[更新完成]\n" + string.Join("\n", success.Select(i => $"[{i.Name}] V{i.OldVersion} >>> V{i.NewVersion}")));
Player.SendSuccessMessage("重启服务器后插件生效!");
}
catch (Exception ex)
{
Player.SendErrorMessage("自动更新出现错误:" + ex.Message);
return;
}
}

private void CheckCmd(TSPlayer Player)
{
try
{
var updates = GetUpdates();
if (updates.Count == 0)
{
Player.SendSuccessMessage("你的插件全是最新版本,无需更新哦~");
return;
}
Player.SendInfoMessage("[以下插件有新的版本更新]\n" + string.Join("\n", updates.Select(i => $"[{i.Name}] V{i.OldVersion} >>> V{i.NewVersion}")));
}
catch (Exception ex)
{
Player.SendErrorMessage("无法获取更新:" + ex.Message);
return;
}
}

#region 工具方法
private static List<PluginUpdateInfo> GetUpdates()
{
var plugins = GetPlugins();
var latestPluginList = GetRepoPlugin();
List<PluginUpdateInfo> pluginUpdateList = new();
foreach (var latestPluginInfo in latestPluginList)
foreach (var plugin in plugins)
if (plugin.Name == latestPluginInfo.Name && plugin.Version != latestPluginInfo.Version)
pluginUpdateList.Add(new PluginUpdateInfo(plugin.Name, plugin.Author, latestPluginInfo.Version, plugin.Version, plugin.Path, latestPluginInfo.Path));
return pluginUpdateList;
}

private static List<PluginUpdateInfo> GetUpdates(List<PluginVersionInfo> latestPluginList)
{
List<PluginUpdateInfo> pluginUpdateList = new();
var plugins = GetPlugins();
foreach (var latestPluginInfo in latestPluginList)
foreach (var plugin in plugins)
if (plugin.Name == latestPluginInfo.Name && plugin.Version != latestPluginInfo.Version)
pluginUpdateList.Add(new PluginUpdateInfo(plugin.Name, plugin.Author, latestPluginInfo.Version, plugin.Version, plugin.Path, latestPluginInfo.Path));
return pluginUpdateList;
}

private static List<PluginVersionInfo> GetRepoPlugin()
{
var plugins = GetPlugins();
HttpClient httpClient = new();
var response = httpClient.GetAsync(PUrl + PluginsUrl).Result;

if (!response.IsSuccessStatusCode)
throw new Exception("无法连接服务器");
var json = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<List<PluginVersionInfo>>(json) ?? new();
}

private static List<PluginVersionInfo> GetPlugins()
{
List<PluginVersionInfo> plugins = new();
//获取已安装的插件,并且读取插件信息和AssemblyName
foreach (var plugin in ServerApi.Plugins)
{
plugins.Add(new PluginVersionInfo()
{
AssemblyName = plugin.Plugin.GetType().Assembly.GetName().Name!,
Author = plugin.Plugin.Author,
Name = plugin.Plugin.Name,
Description = plugin.Plugin.Description,
Version = plugin.Plugin.Version.ToString()
});
}
//反射拯救了TSAPI
var type = typeof(ServerApi);
var field = type.GetField("loadedAssemblies", BindingFlags.NonPublic | BindingFlags.Static)!;
if (field.GetValue(null) is Dictionary<string, Assembly> loadedAssemblies)
foreach (var (fileName, assembly) in loadedAssemblies)
for (int i = 0; i < plugins.Count; i++)
if (plugins[i].AssemblyName == assembly.GetName().Name)
plugins[i].Path = fileName + ".dll";
return plugins;
}


private static void DownLoadPlugin()
{
DirectoryInfo directoryInfo = new(TempSaveDir);
if (!directoryInfo.Exists)
directoryInfo.Create();
HttpClient httpClient = new();
var zipBytes = httpClient.GetByteArrayAsync(PUrl + ReleaseUrl).Result;
File.WriteAllBytes(Path.Combine(directoryInfo.FullName, TempZipName), zipBytes);
}

private static void ExtractDirectoryZip()
{
DirectoryInfo directoryInfo = new(TempSaveDir);
ZipFile.ExtractToDirectory(Path.Combine(directoryInfo.FullName, TempZipName), Path.Combine(directoryInfo.FullName, "Plugins"), true);
}

private static void InstallPlugin(List<PluginVersionInfo> plugininfos)
{
foreach (var info in plugininfos)
{
string sourcePath = Path.Combine(TempSaveDir, "Plugins", info.Path);
string destinationPath = Path.Combine(ServerApi.ServerPluginsDirectoryPath, info.Path);
File.Copy(sourcePath, destinationPath, true);
//热添加插件emmm
//var ass = Assembly.Load(File.ReadAllBytes(destinationPath));
}
if (Directory.Exists(TempSaveDir))
Directory.Delete(TempSaveDir, true);
}

private static List<PluginUpdateInfo> UpdatePlugin(List<PluginUpdateInfo> pluginUpdateInfos)
{
for (int i = pluginUpdateInfos.Count - 1; i >= 0; i--)
{
var pluginUpdateInfo = pluginUpdateInfos[i];
string sourcePath = Path.Combine(TempSaveDir, "Plugins", pluginUpdateInfo.RemotePath);
string destinationPath = Path.Combine(ServerApi.ServerPluginsDirectoryPath, pluginUpdateInfo.LocalPath);
// 确保目标目录存在
string destinationDirectory = Path.GetDirectoryName(destinationPath)!;
if (File.Exists(destinationPath))
{
File.Copy(sourcePath, destinationPath, true);
}
else
{
TShock.Log.ConsoleWarn($"[跳过更新]无法在本地找到插件{pluginUpdateInfo.Name}({destinationPath}),可能是云加载或使用-additionalplugins加载");
pluginUpdateInfos.RemoveAt(i); // 移除元素
}
}
if (Directory.Exists(TempSaveDir))
Directory.Delete(TempSaveDir, true);

return pluginUpdateInfos;
}
#endregion
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace AutoUpdatePlugin;
namespace AutoPluginManager;

public class PluginUpdateInfo
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace AutoUpdatePlugin;
namespace AutoPluginManager;

public class PluginVersionInfo
{
Expand Down
31 changes: 31 additions & 0 deletions AutoPluginManager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# PluginManager 自动更新插件

- 作者: 少司命,Cai
- 出处: 本仓库
- 使用指令自动更新服务器的插件(仅本仓库)

## 更新日志

```
V2.0.0.0
1.正式更名为AutoPluginManager
2.添加安装插件功能
3.更改指令使用方式
```

## 指令

| 语法 | 权限 | 说明 |
| -------------- | :-----------------: | :------: |
| /apm -c | AutoUpdatePlugin | 检查插件更新|
| /apm -u [插件名] | AutoUpdatePlugin | 一键升级插件,需要重启服务器,插件名可多选`英文逗号隔开`|
| /apm -l | AutoUpdatePlugin | 查看仓库插件列表 |
| /apm -i [插件序号] | AutoUpdatePlugin | 安装插件,需重启服务器,插件序号多选`英文逗号隔开`配合`/apm -i`指令使用 |
## 配置

```json
暂无
```
## 反馈
- 共同维护的插件库:https://github.com/Controllerdestiny/TShockPlugin
- 国内社区trhub.cn 或 TShock官方群等
Loading

0 comments on commit 54d9dc8

Please sign in to comment.