· 4 years ago · Nov 07, 2020, 11:50 AM
1using System;
2using System.Collections.Generic;
3using Newtonsoft.Json;
4using Oxide.Core;
5using Oxide.Core.Libraries;
6using UnityEngine;
7
8namespace Oxide.Plugins
9{
10 public class ErrorsLogger : Plugin
11 {
12 #region Vars
13
14 private const string patternPluginName = "Oxide.Plugins.";
15 private string prefixConsole => $"[{Name}, Log]";
16 private HashSet<string> history = new HashSet<string>();
17
18 #endregion
19
20 #region Oxide Hooks
21
22 private void OnServerInitialized()
23 {
24 NextTick(() =>
25 {
26 Application.logMessageReceivedThreaded += Handler;
27 });
28 }
29
30 private void Unload()
31 {
32 Application.logMessageReceivedThreaded -= Handler;
33 }
34
35 #endregion
36
37 #region Commands
38
39 [ConsoleCommand("testlogs")]
40 private void cmdControlConsole(ConsoleSystem.Arg arg)
41 {
42 if (arg.IsAdmin == false)
43 {
44 SendReply(arg, "Only for developers!");
45 return;
46 }
47
48 SendReply(arg, $"Testing logs system in 1 tick... History contains {history.Count} values");
49 NextTick(() =>
50 {
51 // ReSharper disable once PossibleNullReferenceException
52 ((BasePlayer) null).Die();
53 });
54 }
55
56 #endregion
57
58 #region Core
59
60 private void Handler(string s1, string s2, LogType type)
61 {
62 var exception = s1 + "\n" + s2;
63 if (exception == "\n" || exception.StartsWith(prefixConsole) == true)
64 {
65 return;
66 }
67
68 if (type != LogType.Error && type != LogType.Exception)
69 {
70 return;
71 }
72
73 if (history.Contains(exception) == true)
74 {
75 return;
76 }
77
78 if (config.sendToLogs == true)
79 {
80 LogToFile("errors", $"[{type}] {exception}", this);
81 }
82
83 if (config.sendToDiscord == true)
84 {
85 SendMessageWebhook($"[{type}] {exception}", config.webhookDiscord);
86 }
87
88 if (config.sendToWebsite == true)
89 {
90 SendLogs(exception);
91 }
92
93 history.Add(exception);
94 }
95
96 private void SendLogs(string exception)
97 {
98 var pluginAuthor = "Null";
99 var pluginName = "Null";
100 var pluginVersion = "0.0.0";
101 var indexStart = exception.IndexOf(patternPluginName, StringComparison.OrdinalIgnoreCase);
102 if (indexStart > 0)
103 {
104 indexStart += patternPluginName.Length;
105 var indexEnd = exception.IndexOf(".", indexStart, StringComparison.OrdinalIgnoreCase);
106 var length = indexEnd - indexStart;
107 if (length > 0)
108 {
109 pluginName = exception.Substring(indexStart, indexEnd - indexStart);
110 var plugin = plugins.Find(pluginName);
111 if (plugin != null)
112 {
113 pluginAuthor = plugin.Author;
114 pluginName = plugin.Name;
115 pluginVersion = plugin.Version.ToString();
116 }
117 }
118 }
119
120 if (pluginAuthor != "Orange" && string.IsNullOrEmpty(config.secretKey) == true)
121 {
122 return;
123 }
124
125 var serverName = ConVar.Server.hostname;
126 if (serverName != null && serverName.Length > 20)
127 {
128 serverName = serverName.Substring(0, 20);
129 }
130
131 var url = $"https://rustworkshop.space/report-exception.php?" +
132 $"author={pluginAuthor}" +
133 $"&plugin={pluginName}" +
134 $"&version={pluginVersion}" +
135 $"&exception={exception}";
136
137 if (config.discordInfo != "Orange#0900")
138 {
139 url += $"&discord={config.discordInfo}";
140 }
141 else
142 {
143 url += $"&server={serverName}";
144 }
145
146 if (string.IsNullOrEmpty(config.secretKey) == false)
147 {
148 url += $"&key={config.secretKey}";
149 }
150
151 url += $"&logger={Version}";
152 url = url.Replace(" ", "+");
153 url = url.Replace("#", "-");
154 webrequest.Enqueue(url, null, (i, s) =>
155 {
156 if (string.IsNullOrEmpty(s) || s.Contains("Success") == false)
157 {
158 PrintWarning($"{prefixConsole} Failed to send logs to developers!\nCode:{i}\nResponse:\n{s}");
159 }
160 }, this);
161 }
162
163 #endregion
164
165 #region Configuration | 24.05.2020
166
167 private static ConfigData config = new ConfigData();
168
169 private class ConfigData
170 {
171 [JsonProperty(PropertyName = "Send plugin errors to developers (if possible)")]
172 public bool sendToWebsite = true;
173
174 [JsonProperty(PropertyName = "Save errors in logs")]
175 public bool sendToLogs = true;
176
177 [JsonProperty(PropertyName = "Send errors in discord")]
178 public bool sendToDiscord = false;
179
180 [JsonProperty(PropertyName = "Your discord contact information")]
181 public string discordInfo = "Orange#0900";
182
183 [JsonProperty(PropertyName = "Discord webhook")]
184 public string webhookDiscord = "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
185
186 [JsonProperty(PropertyName = "Special key")]
187 public string secretKey = "";
188 }
189
190 protected override void LoadConfig()
191 {
192 base.LoadConfig();
193
194 try
195 {
196 config = Config.ReadObject<ConfigData>();
197 if (config == null)
198 {
199 LoadDefaultConfig();
200 }
201 }
202 catch
203 {
204 for (var i = 0; i < 3; i++)
205 {
206 PrintWarning("Configuration file is corrupt! Check your config file at https://jsonlint.com/");
207 }
208
209 LoadDefaultConfig();
210 return;
211 }
212
213 ValidateConfig();
214 SaveConfig();
215 }
216
217 private void ValidateConfig()
218 {
219 if (Interface.Oxide.CallHook("InDebugMode") != null)
220 {
221 PrintWarning("Using default configuration in debug mode");
222 config = new ConfigData();
223 }
224 }
225
226 protected override void LoadDefaultConfig()
227 {
228 config = new ConfigData();
229 }
230
231 protected override void SaveConfig()
232 {
233 Config.WriteObject(config);
234 }
235
236 #endregion
237
238 #region Discord Support 01.06.2020
239
240 private void SendMessageWebhook(string text, string webhook)
241 {
242 if (string.IsNullOrEmpty(webhook) == true || webhook.Contains("/api") == false)
243 {
244 return;
245 }
246
247 var messages = new List<string>();
248
249 if (text.Length > 1500)
250 {
251 var message = string.Empty;
252
253 for (var i = 0; i < text.Length; i++)
254 {
255 var current = text[i];
256 message += current;
257
258 if (i >= 1500)
259 {
260 messages.Add(message);
261 message = string.Empty;
262 }
263 }
264
265 if (message != string.Empty)
266 {
267 messages.Add(message);
268 }
269 }
270 else
271 {
272 messages.Add(text);
273 }
274
275 foreach (var value in messages)
276 {
277 var obj = new ContentType(value);
278 webrequest.Enqueue(webhook, JsonConvert.SerializeObject(obj), PostCallBack, this, RequestMethod.POST, new Dictionary<string, string>{{"Content-Type", "application/json"}});
279 }
280 }
281
282 private void PostCallBack(int code, string response)
283 {
284 if(code != 200 && code != 204)
285 {
286 PrintWarning($"Discord Api responded with {code}: {response}");
287 }
288 }
289
290 private class ContentType
291 {
292 public string content;
293 public string username;
294 public string avatar_url;
295
296 [JsonConstructor]
297 public ContentType(string text, string name = null, string avatar = null)
298 {
299 content = text;
300 username = name;
301 avatar_url = avatar;
302 }
303 }
304
305 #endregion
306 }
307}