Skip to content

Commit

Permalink
Common: マスタデータをスプレッドシートから読み込めるように (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
2RiniaR authored Nov 2, 2024
1 parent 71e8073 commit 1290ea6
Show file tree
Hide file tree
Showing 32 changed files with 598 additions and 144 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# .NET Core build folders
bin/
obj/
Environment/

# Common node modules locations
/node_modules
Expand Down
2 changes: 1 addition & 1 deletion Common/DiscordManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class DiscordManager
public static async Task InitializeAsync()
{
Client.Log += OnLog;
await Client.LoginAsync(TokenType.Bot, SettingManager.DiscordSecret);
await Client.LoginAsync(TokenType.Bot, EnvironmentManager.DiscordSecret);
await Client.StartAsync();
await EventUtility.WaitAsync(h => Client.Ready += h, h => Client.Ready -= h);
}
Expand Down
8 changes: 5 additions & 3 deletions Common/SettingManager.cs → Common/EnvironmentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

namespace Approvers.King.Common;

public class SettingManager
public class EnvironmentManager
{
private static IConfigurationRoot Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true)
.AddJsonFile("./Environment/appsettings.json", true)
.AddEnvironmentVariables()
.AddUserSecrets<SettingManager>(true)
.AddUserSecrets<EnvironmentManager>(true)
.Build();

public static string DiscordSecret => Get("DiscordSecret");
public static ulong DiscordTargetGuildId => ulong.Parse(Get("DiscordTargetGuildId"));
public static ulong DiscordMainChannelId => ulong.Parse(Get("DiscordMainChannelId"));
public static string GoogleCredentialFilePath => Get("GoogleCredentialFilePath");
public static string GoogleMasterSheetId => Get("GoogleMasterSheetId");

private static string Get(string name)
{
Expand Down
17 changes: 17 additions & 0 deletions Common/Extensions/EnumrableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Approvers.King.Common;

public static class EnumrableExtensions
{
public static TDist? FirstTypeOrDefault<T, TDist>(this IEnumerable<T> source)
{
foreach (var item in source)
{
if (item is TDist dist)
{
return dist;
}
}

return default;
}
}
File renamed without changes.
10 changes: 7 additions & 3 deletions Common/GachaManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public class GachaManager
public void Initialize()
{
_replyMessageTable.Clear();
_replyMessageTable.AddRange(MasterManager.ReplyMessages.Select(x => new ReplyMessage
_replyMessageTable.AddRange(MasterManager.RandomMessageMaster.GetAll(x => x.Type == RandomMessageType.GeneralReply).Select(x => new ReplyMessage
{
Rate = 1f,
Message = x,
Message = x.Content,
}));
}

Expand All @@ -48,7 +48,11 @@ public string PickMessage()

public void ShuffleRareReplyRate()
{
RareReplyRate = MasterManager.RareReplyRateTable.PickRandom();
var step = MasterManager.SettingMaster.RareReplyProbabilityStepPermillage;
var max = MasterManager.SettingMaster.MaxRareReplyProbabilityPermillage;
RareReplyRate = Enumerable.Range(0, max / step)
.Select(i => NumberUtility.GetPercentFromPermillage((i + 1) * step))
.PickRandom();
}

public void ShuffleMessageRates()
Expand Down
22 changes: 22 additions & 0 deletions Common/Google/GoogleSheet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Google.Apis.Sheets.v4.Data;

namespace Approvers.King.Common;

public class GoogleSheet
{
public string SheetName { get; }
public IList<IList<object>> Values { get; }
public int RowCount { get; }
public int ColumnCount { get; }
public GoogleSheet(ValueRange valueRange)
{
SheetName = valueRange.Range.Split('!')[0].Replace("'", "");
Values = valueRange.Values;
RowCount = Values.Count;
ColumnCount = Values.Count > 0 ? Values[0].Count : 0;
}
public string Get(int row, int column)
{
return (string)Values[row][column];
}
}
29 changes: 29 additions & 0 deletions Common/Google/GoogleSheetManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Net.Mime;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Sheets.v4;

namespace Approvers.King.Common;

public static class GoogleSheetManager
{
private static readonly string[] Scopes = [SheetsService.Scope.SpreadsheetsReadonly];
private static ICredential? _credential;

public static async Task<IReadOnlyDictionary<string, GoogleSheet>> GetAllSheetsAsync(IEnumerable<string> sheetNames)
{
if (_credential == null)
{
await using var stream = new FileStream(EnvironmentManager.GoogleCredentialFilePath, FileMode.Open, FileAccess.Read);
_credential = (await GoogleCredential.FromStreamAsync(stream, CancellationToken.None)).CreateScoped(Scopes).UnderlyingCredential;
}
var sheetService = new SheetsService(new BaseClientService.Initializer
{
HttpClientInitializer = _credential
});
var request = sheetService.Spreadsheets.Values.BatchGet(EnvironmentManager.GoogleMasterSheetId);
request.Ranges = sheetNames.ToList();
var sheet = await request.ExecuteAsync();
return sheet.ValueRanges.Select(x => new GoogleSheet(x)).ToDictionary(x => x.SheetName, x => x);
}
}
25 changes: 25 additions & 0 deletions Common/LogManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Approvers.King.Common;

public static class LogManager
{
private static string CreateTimestamp()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}

public static void Log(string message)
{
Console.WriteLine($"{CreateTimestamp()}: [INFO] {message}");
}

public static void LogError(string message)
{
Console.Error.WriteLine($"{CreateTimestamp()}: [ERROR] {message}");
}

public static void LogException(Exception exception)
{
Console.Error.WriteLine($"{CreateTimestamp()}: [EXCEPTION] {exception.Message}");
Console.Error.Write(exception);
}
}
218 changes: 218 additions & 0 deletions Common/Master/MasterManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
using System.Reflection;

namespace Approvers.King.Common;

public class MasterManager : Singleton<MasterManager>
{
[MasterTable("random_message")] private RandomMessageMaster _randomMessageMaster;

Check warning on line 7 in Common/Master/MasterManager.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_randomMessageMaster' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
public static RandomMessageMaster RandomMessageMaster => Instance._randomMessageMaster;

[MasterTable("trigger_phrase")] private TriggerPhraseMaster _triggerPhraseMaster;

Check warning on line 10 in Common/Master/MasterManager.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_triggerPhraseMaster' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
public static TriggerPhraseMaster TriggerPhraseMaster => Instance._triggerPhraseMaster;

[field: MasterTable("setting")] private SettingMaster _settingMaster;

Check warning on line 13 in Common/Master/MasterManager.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_settingMaster' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
public static SettingMaster SettingMaster => Instance._settingMaster;

public static async Task FetchAsync()
{
var dict = new Dictionary<FieldInfo, string>();
var masterFields = Instance.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in masterFields)
{
var tableAttributes = field.GetCustomAttributes().FirstTypeOrDefault<Attribute, MasterTableAttribute>();
if (tableAttributes == null) continue;
dict.Add(field, tableAttributes.KeyName);
}
var sheets = await GoogleSheetManager.GetAllSheetsAsync(dict.Values);
foreach (var (field, sheetName) in dict)
{
var masterType = field.FieldType;
var masterGenericArguments = masterType.BaseType!.GetGenericArguments();
var keyType = masterGenericArguments[0];
var recordType = masterGenericArguments[1];
var master = typeof(MasterManager)
.GetMethod(nameof(BuildMaster), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(masterType, keyType, recordType)
.Invoke(null, [sheets[sheetName]]);
field.SetValue(Instance, master);
}
}

private static TMaster BuildMaster<TMaster, TKey, TRecord>(GoogleSheet sheet)
where TMaster : MasterTable<TKey, TRecord>, new()
where TRecord : MasterRecord<TKey>, new()
where TKey : notnull
{
var records = Enumerable.Range(0, sheet.RowCount - 1).Select(_ => new TRecord()).ToList();
var keyDictionary = Enumerable.Range(0, sheet.ColumnCount).ToDictionary(i => sheet.Get(0, i), i => i);
var fields = typeof(TRecord).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
var propertyAttributes = field.GetCustomAttributes();
foreach (var propertyAttribute in propertyAttributes)
{
switch (propertyAttribute)
{
case MasterIntValueAttribute integerValueAttribute:
{
if (keyDictionary.TryGetValue(integerValueAttribute.KeyName, out var column) == false)
{
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + integerValueAttribute.KeyName);
continue;
}
for (var row = 1; row < sheet.RowCount; row++)
{
var value = sheet.Get(row, column);
if (int.TryParse(value, out var intValue) == false)
{
LogManager.LogError("Invalid value type (integer): " + sheet.SheetName + "/" + integerValueAttribute.KeyName + $"[{row}] = {value}");
continue;
}
field.SetValue(records[row - 1], intValue);
}
break;
}
case MasterFloatValueAttribute floatValueAttribute:
{
if (keyDictionary.TryGetValue(floatValueAttribute.KeyName, out var column) == false)
{
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + floatValueAttribute.KeyName);
continue;
}
for (var row = 1; row < sheet.RowCount; row++)
{
var value = sheet.Get(row, column);
if (float.TryParse(value, out var floatValue) == false)
{
LogManager.LogError("Invalid value type (float): " + sheet.SheetName + "/" + floatValueAttribute.KeyName + $"[{row}] = {value}");
continue;
}
field.SetValue(records[row - 1], floatValue);
}
break;
}
case MasterStringValueAttribute stringValueAttribute:
{
if (keyDictionary.TryGetValue(stringValueAttribute.KeyName, out var column) == false)
{
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + stringValueAttribute.KeyName);
continue;
}
for (var row = 1; row < sheet.RowCount; row++)
{
var value = sheet.Get(row, column);
field.SetValue(records[row - 1], value);
}
break;
}
case MasterBoolValueAttribute booleanValueAttribute:
{
if (keyDictionary.TryGetValue(booleanValueAttribute.KeyName, out var column) == false)
{
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + booleanValueAttribute.KeyName);
continue;
}
for (var row = 1; row < sheet.RowCount; row++)
{
var value = sheet.Get(row, column);
if (bool.TryParse(value, out var boolValue) == false)
{
LogManager.LogError("Invalid value type (bool): " + sheet.SheetName + "/" + booleanValueAttribute.KeyName + $"[{row}] = {value}");
continue;
}
field.SetValue(records[row - 1], boolValue);
}
break;
}
case MasterEnumValueAttribute enumValueAttribute:
{
if (keyDictionary.TryGetValue(enumValueAttribute.KeyName, out var column) == false)
{
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + enumValueAttribute.KeyName);
continue;
}
for (var row = 1; row < sheet.RowCount; row++)
{
var value = sheet.Get(row, column);
if (Enum.TryParse(enumValueAttribute.EnumType, value, out var enumValue) == false)
{
LogManager.LogError($"Invalid value type ({enumValueAttribute.EnumType.Name}): " + sheet.SheetName + "/" + enumValueAttribute.KeyName + $"[{row}] = {value}");
continue;
}
field.SetValue(records[row - 1], enumValue);
}
break;
}
}
}
}
var master = new TMaster();
master.Set(records);
return master;
}
}

public class MasterBoolValueAttribute : Attribute
{
public MasterBoolValueAttribute(string keyName)
{
KeyName = keyName;
}
public string KeyName { get; }
}
public class MasterEnumValueAttribute : Attribute
{
public MasterEnumValueAttribute(string keyName, Type enumType)
{
KeyName = keyName;
EnumType = enumType;
}
public string KeyName { get; }
public Type EnumType { get; }
}

public class MasterFloatValueAttribute : Attribute
{
public MasterFloatValueAttribute(string keyName)
{
KeyName = keyName;
}
public string KeyName { get; }
}

public class MasterIntValueAttribute : Attribute
{
public MasterIntValueAttribute(string keyName)
{
KeyName = keyName;
}
public string KeyName { get; }
}

public class MasterStringValueAttribute : Attribute
{
public MasterStringValueAttribute(string keyName)
{
KeyName = keyName;
}
public string KeyName { get; }
}

public class MasterStringCollectionValueAttribute : Attribute
{
public MasterStringCollectionValueAttribute(string keyName)
{
KeyName = keyName;
}
public string KeyName { get; }
}

public class MasterTableAttribute : Attribute
{
public MasterTableAttribute(string keyName)
{
KeyName = keyName;
}

public string KeyName { get; }
}
6 changes: 6 additions & 0 deletions Common/Master/MasterRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Approvers.King.Common;

public abstract class MasterRecord<TKey> where TKey : notnull
{
public abstract TKey Key { get; }
}
Loading

0 comments on commit 1290ea6

Please sign in to comment.