UserRepo.cs
1 using CodexContractsPlugin; 2 using Discord; 3 using GethPlugin; 4 using Newtonsoft.Json; 5 using Utils; 6 7 namespace BiblioTech 8 { 9 public class UserRepo 10 { 11 private readonly object repoLock = new object(); 12 private readonly Dictionary<ulong, UserData> cache = new Dictionary<ulong, UserData>(); 13 14 public SetAddressResponse AssociateUserWithAddress(IUser user, EthAddress address) 15 { 16 lock (repoLock) 17 { 18 return SetUserAddress(user, address); 19 } 20 } 21 22 public void ClearUserAssociatedAddress(IUser user) 23 { 24 lock (repoLock) 25 { 26 SetUserAddress(user, null); 27 } 28 } 29 30 public void SetUserNotificationPreference(IUser user, bool enableNotifications) 31 { 32 lock (repoLock) 33 { 34 SetUserNotification(user, enableNotifications); 35 } 36 } 37 38 public UserData[] GetAllUserData() 39 { 40 if (cache.Count == 0) LoadAllUserData(); 41 return cache.Values.ToArray(); 42 } 43 44 public UserData GetUser(IUser user) 45 { 46 if (cache.Count == 0) LoadAllUserData(); 47 return GetOrCreate(user); 48 } 49 50 public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction<Ether>? eth, Transaction<TestToken>? tokens) 51 { 52 lock (repoLock) 53 { 54 var userData = GetOrCreate(user); 55 userData.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens)); 56 SaveUserData(userData); 57 } 58 } 59 60 public EthAddress? GetCurrentAddressForUser(IUser user) 61 { 62 lock (repoLock) 63 { 64 return GetOrCreate(user).CurrentAddress; 65 } 66 } 67 68 public string[] GetInteractionReport(IUser user) 69 { 70 var result = new List<string> 71 { 72 $"User report create on {DateTime.UtcNow.ToString("o")}" 73 }; 74 75 lock (repoLock) 76 { 77 var userData = GetUserDataMaybe(user); 78 if (userData == null) 79 { 80 result.Add("User has not interacted with bot."); 81 } 82 else 83 { 84 result.Add("User joined on " + userData.CreatedUtc.ToString("o")); 85 result.Add("Current address: " + userData.CurrentAddress); 86 foreach (var ae in userData.AssociateEvents) 87 { 88 result.Add($"{ae.Utc.ToString("o")} - Address set to: {ae.NewAddress}"); 89 } 90 foreach (var me in userData.MintEvents) 91 { 92 if (me.EthReceived != null) 93 { 94 result.Add($"{me.Utc.ToString("o")} - {me.EthReceived.ToString()} to {me.UsedAddress}. ({me.EthReceived.TransactionHash})"); 95 } 96 if (me.TestTokensMinted != null) 97 { 98 result.Add($"{me.Utc.ToString("o")} - {me.TestTokensMinted.ToString()} to {me.UsedAddress}. ({me.TestTokensMinted.TransactionHash})"); 99 } 100 } 101 } 102 } 103 104 return result.ToArray(); 105 } 106 107 public string[] GetUserReport(IUser user) 108 { 109 var userData = GetUserDataMaybe(user); 110 if (userData == null) return new[] { "User has not joined the test net." }; 111 return userData.CreateOverview(); 112 } 113 114 public string[] GetUserReport(EthAddress ethAddress) 115 { 116 var userData = GetUserDataForAddressMaybe(ethAddress); 117 if (userData == null) return new[] { "No user is using this eth address." }; 118 return userData.CreateOverview(); 119 } 120 121 public UserData? GetUserDataForAddressMaybe(EthAddress? address) 122 { 123 if (address == null) return null; 124 125 var lower = address.Address.ToLowerInvariant(); 126 if (string.IsNullOrEmpty(lower)) return null; 127 128 if (cache.Count == 0) LoadAllUserData(); 129 foreach (var item in cache.Values) 130 { 131 if (item.CurrentAddress != null && 132 item.CurrentAddress.Address.ToLowerInvariant() == lower) 133 { 134 return item; 135 } 136 } 137 138 return null; 139 } 140 141 private SetAddressResponse SetUserAddress(IUser user, EthAddress? address) 142 { 143 if (GetUserDataForAddressMaybe(address) != null) 144 { 145 return SetAddressResponse.AddressAlreadyInUse; 146 } 147 148 var userData = GetOrCreate(user); 149 if (userData == null) return SetAddressResponse.CreateUserFailed; 150 userData.CurrentAddress = address; 151 userData.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address)); 152 SaveUserData(userData); 153 return SetAddressResponse.OK; 154 } 155 156 private void SetUserNotification(IUser user, bool notifyEnabled) 157 { 158 var userData = GetOrCreate(user); 159 userData.NotificationsEnabled = notifyEnabled; 160 SaveUserData(userData); 161 } 162 163 private UserData? GetUserDataMaybe(IUser user) 164 { 165 if (cache.ContainsKey(user.Id)) 166 { 167 return cache[user.Id]; 168 } 169 170 var filename = GetFilename(user); 171 if (!File.Exists(filename)) 172 { 173 return null; 174 } 175 var userData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(filename))!; 176 cache.Add(userData.DiscordId, userData); 177 return userData; 178 } 179 180 private UserData GetOrCreate(IUser user) 181 { 182 var userData = GetUserDataMaybe(user); 183 if (userData == null) 184 { 185 return CreateAndSaveNewUserData(user); 186 } 187 return userData; 188 } 189 190 private UserData CreateAndSaveNewUserData(IUser user) 191 { 192 var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>(), true); 193 SaveUserData(newUser); 194 return newUser; 195 } 196 197 private void SaveUserData(UserData userData) 198 { 199 var filename = GetFilename(userData); 200 if (File.Exists(filename)) File.Delete(filename); 201 File.WriteAllText(filename, JsonConvert.SerializeObject(userData)); 202 203 if (cache.ContainsKey(userData.DiscordId)) 204 { 205 cache[userData.DiscordId] = userData; 206 } 207 else 208 { 209 cache.Add(userData.DiscordId, userData); 210 } 211 } 212 213 private static string GetFilename(IUser user) 214 { 215 return GetFilename(user.Id); 216 } 217 218 private static string GetFilename(UserData userData) 219 { 220 return GetFilename(userData.DiscordId); 221 } 222 223 private static string GetFilename(ulong discordId) 224 { 225 return Path.Combine(Program.Config.UserDataPath, discordId.ToString() + ".json"); 226 } 227 228 private void LoadAllUserData() 229 { 230 try 231 { 232 var files = Directory.GetFiles(Program.Config.UserDataPath); 233 foreach (var file in files) 234 { 235 try 236 { 237 var userData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!; 238 if (userData != null && userData.DiscordId > 0) 239 { 240 cache.Add(userData.DiscordId, userData); 241 } 242 } 243 catch { } 244 } 245 } 246 catch (Exception ex) 247 { 248 Program.Log.Error("Exception while trying to load all user data: " + ex); 249 } 250 } 251 } 252 253 public enum SetAddressResponse 254 { 255 OK, 256 AddressAlreadyInUse, 257 CreateUserFailed 258 } 259 }