Advertisement
Guest User

Untitled

a guest
Jan 20th, 2018
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.13 KB | None | 0 0
  1. [Group("scheduler")]
  2. public class Scheduler : ModuleBase<SocketCommandContext>
  3. {
  4. private readonly ScheduleService _service;
  5. private readonly string _urlPrefix = @"https://www.twitch.tv/";
  6. private readonly string _usage = @"!scheduler add https://www.twitch.tv/channelname timenwhere *time* is either hours (XXh) or minutes (XXM)n**Examples:**n!scheduler add https://www.twitch.tv/coolguy 1h --- will anmounce stream in 1h from now on";
  7.  
  8. public Scheduler(ScheduleService service)
  9. {
  10. _service = service;
  11. }
  12.  
  13. [Command("stop")]
  14. [RequireOwner]
  15. public async Task StopCmd()
  16. {
  17. _service.Stop($"Manual request by {Context.User.Username}");
  18. }
  19.  
  20. [Command("start")]
  21. [RequireOwner]
  22. public async Task RestartCmd()
  23. {
  24. _service.Start($"Manual request by {Context.User.Username}");
  25. }
  26.  
  27. [Command("add")]
  28. public async Task Schedule(string url, string date)
  29. {
  30. var finalURL = ParseURL(url);
  31. if (string.IsNullOrEmpty(finalURL))
  32. {
  33. await ReplyAsync($"{Context.User.Mention} Incorrect syntax. Usage:n{_usage}");
  34. return;
  35. }
  36. TimeSpan time;
  37. if (string.IsNullOrEmpty(date) || !TimeSpan.TryParse(date, out time) || time.TotalMinutes > 720 || time.TotalMinutes < 30)
  38. {
  39. await ReplyAsync($"{Context.User.Mention} Incorrect timespan specified. Usage:n{_usage}");
  40. return;
  41. }
  42. bool canAdd = Context.Guild.Owner.Username == Context.User.Username || await _service.CanAdd(Context.User);
  43. if (!canAdd)
  44. {
  45. await ReplyAsync($"{Context.User.Mention} You can add 1 entry per 10 minutes, max 5 entries per 24hrs.");
  46. return;
  47. }
  48. var contains = await _service.CheckEntry(finalURL);
  49. if (contains.user != null)
  50. {
  51. await ReplyAsync($"{Context.User.Mention} Entry for {finalURL} already exist.nAdded by {contains.user.Username}, target time {contains.date.ToUniversalTime().ToString()} UTC");
  52. return;
  53. }
  54. await _service.AddEntry(finalURL, DateTime.Now.AddMinutes(time.TotalMinutes), Context.User);
  55. await ReplyAsync($"{Context.User.Mention} New entry added: {finalURL}, announce in: {time.TotalMinutes} minutes");
  56. }
  57.  
  58.  
  59.  
  60. [Command("remove")]
  61. public async Task Remove(string url)
  62. {
  63. var finalURL = ParseURL(url);
  64. if (string.IsNullOrEmpty(finalURL))
  65. {
  66. await ReplyAsync($"{Context.User.Mention} Incorrect syntax. Usage:n!scheduler remove {_urlPrefix}channel");
  67. return;
  68. }
  69. var result = await _service.RemoveEntry(finalURL, Context.User, Context.Guild.Owner.Username == Context.User.Username);
  70. await ReplyAsync($"{Context.User.Mention} Result: {result}");
  71. }
  72.  
  73. [Command("list")]
  74. [RequireOwner]
  75. public async Task GetList()
  76. {
  77. await ReplyAsync($"{await _service.GetList()}");
  78. }
  79.  
  80. [Command("status")]
  81. public async Task Status()
  82. {
  83. await ReplyAsync($"{await _service.Status()}");
  84. }
  85.  
  86.  
  87. // Helpers
  88. private string ParseURL(string url)
  89. {
  90. if (string.IsNullOrEmpty(url)) { return string.Empty; }
  91. int i = url.IndexOf(_urlPrefix);
  92. string name = (i < 0) ? url : url.Remove(i, _urlPrefix.Length);
  93. name = string.IsNullOrEmpty(name) ? string.Empty : name.RemoveSpecialCharacters();
  94. if (string.IsNullOrEmpty(name) || name.Length < 4 || name.Length > 25)
  95. {
  96. return string.Empty;
  97. }
  98. else
  99. {
  100. return _urlPrefix + name;
  101. }
  102. }
  103. }
  104.  
  105. public class ScheduleService
  106. {
  107. private readonly DiscordSocketClient _client;
  108. private readonly Timer _timer;
  109.  
  110. private List<ScheduleData> _queue;
  111. private ConcurrentDictionary<SocketUser, UserStamp> _bouncer;
  112.  
  113. private DateTime startTime;
  114. private object busy = new object();
  115. private StringBuilder SBuilder;
  116. private string streamChannelmask = "bot";
  117. private SocketTextChannel streamChannel;
  118.  
  119. public int Count => _queue.Count;
  120.  
  121. public ScheduleService(DiscordSocketClient client)
  122. {
  123. //Client stuff
  124. _client = client;
  125. _client.Ready += OnClientReady;
  126. // Initializing collections
  127. _queue = new List<ScheduleData>();
  128. _bouncer = new ConcurrentDictionary<SocketUser, UserStamp>();
  129. startTime = DateTime.Now;
  130. SBuilder = new StringBuilder(); // lol optimization
  131. // setting up Timer, currently hardcoded
  132. _timer = new Timer();
  133. _timer.Interval = 30000;
  134. _timer.AutoReset = true;
  135. _timer.Elapsed += OnTimedEvent;
  136.  
  137.  
  138. }
  139.  
  140. private async Task FindChannel()
  141. {
  142. foreach (var guild in _client.Guilds)
  143. {
  144. if (guild.Name.ContainsIC("iconoclasts"))
  145. {
  146. foreach (var channel in guild.TextChannels)
  147. {
  148. if (channel.Name.ContainsIC(streamChannelmask)) { streamChannel = channel; break; }
  149. }
  150. }
  151. }
  152. Logger.LogConsoleInfo($"Default Scheduler channel: {streamChannelmask}");
  153. }
  154.  
  155. // Initializing some post-constructor stuff when client is ready and connected
  156. private async Task OnClientReady()
  157. {
  158. Start("OnReady event");
  159. FindChannel();
  160. }
  161.  
  162. // Timer Event fired every XXms that does most of thw work - going through list
  163. // finding stuff that needs to be announced, etc
  164. // using soft lock because timer resolution is big enough to allow any new requests fall through
  165. private async void OnTimedEvent(object sender, ElapsedEventArgs e)
  166. {
  167. if (System.Threading.Monitor.TryEnter(busy, 1000))
  168. {
  169. try
  170. {
  171. if (_queue.Count > 0)
  172. {
  173. if (_client.ConnectionState == ConnectionState.Connected)
  174. {
  175. if (streamChannel != null)
  176. {
  177. StringBuilder announce = new StringBuilder();
  178. int count = -1;
  179. for (int i = 0; i < _queue.Count; i++)
  180. {
  181. if ((_queue[i].date - DateTime.Now).TotalSeconds < 5)
  182. {
  183. announce.Append($"Scheduled stream starting: {_queue[0].URL} (added by {_queue[0].user.Username})n");
  184. count++;
  185. }
  186. else { break; }
  187. }
  188. if (count > -1)
  189. {
  190. await streamChannel.SendMessageAsync(announce.ToString());
  191. _queue.RemoveRange(0, count + 1);
  192. Logger.LogConsoleInfo($"Scheduler pass finished, removed entries {count + 1}");
  193. }
  194. }
  195. }
  196. else
  197. {
  198. int count = -1;
  199. for (int i = 0; i < _queue.Count; i++)
  200. {
  201. if ((_queue[i].date - DateTime.Now).TotalSeconds < 5) { count++; }
  202. else { break; }
  203. }
  204. if (count > -1)
  205. {
  206. _queue.RemoveRange(0, count + 1);
  207. Logger.LogConsoleInfo($"Timer check passed but not connected. Removed expired entries {count + 1}");
  208. }
  209.  
  210. }
  211. }
  212. else { Stop("Nothing in queue"); }
  213. }
  214. finally { System.Threading.Monitor.Exit(busy); }
  215. }
  216. // 24hrs check, gonna need to figure out something else for this
  217. if ((DateTime.Now - startTime).TotalHours > 23)
  218. {
  219. startTime = DateTime.Now;
  220. ResetUsers();
  221. }
  222. }
  223.  
  224. //Synchronous start of the timer
  225. public void Stop(string reason)
  226. {
  227. if (_timer.Enabled)
  228. {
  229. _timer.Stop();
  230. Logger.LogConsoleInfo($"Scheduler has been stopped: {reason}");
  231. }
  232. }
  233.  
  234. //Synchronous stop of the timer
  235. public void Start(string reason)
  236. {
  237. if (!_timer.Enabled)
  238. {
  239. _timer.Start();
  240. Logger.LogConsoleInfo($"Scheduler has been restarted: {reason}");
  241. }
  242. }
  243.  
  244. //Adding new entry to the queue List
  245. // using explicit lock because every request needs to be processed
  246. public async Task AddEntry(string url, DateTime time, SocketUser user)
  247. {
  248. UpdateUser(user);
  249. lock (busy)
  250. {
  251. int count = -1;
  252. for (int i = 0; i < _queue.Count; i++)
  253. {
  254. if (_queue[i].date < time) { count++; }
  255. else { break; }
  256. }
  257. if (count > -1)
  258. {
  259. _queue.Insert(count + 1, new ScheduleData(url, time, user));
  260. }
  261. else
  262. {
  263. _queue.Add(new ScheduleData(url, time, user));
  264. }
  265. Start("New entry added to empty queue");
  266. }
  267. }
  268.  
  269. //Removing entry from the queue List
  270. // using explicit lock because every request needs to be processed
  271. public async Task<string> RemoveEntry(string url, SocketUser user, bool owner)
  272. {
  273. lock (busy)
  274. {
  275. var data = new ScheduleData(url, DateTime.Now, null);
  276. int indx = -1;
  277. indx = _queue.IndexOf(data);
  278. if (indx > -1)
  279. {
  280. var usr = _queue[indx].user.Username;
  281. if (usr == user.Username || owner)
  282. {
  283. _queue.RemoveAt(indx);
  284. return $"Removed entry {url} (by {usr})";
  285. }
  286. else
  287. {
  288. return $"Entry {url} exist, but it must be removed by the original user: {usr}";
  289. }
  290. }
  291. else
  292. {
  293. busy = false;
  294. return $"Entry {url} not found in queue.";
  295. }
  296. }
  297. }
  298.  
  299. // Checking if specific entry exist in the List, using first result
  300. // Comparer only cares about URL so dummy SheduleData made
  301. public async Task<ScheduleData> CheckEntry(string url)
  302. {
  303. // Need to fix the Socketuser null part since it is bad way to check if no results returned
  304. // SU can be null if user left server?
  305. lock (busy)
  306. {
  307. var data = new ScheduleData(url, DateTime.Now, null);
  308. int indx = -1;
  309. indx = _queue.IndexOf(data);
  310. if (indx > -1) { return _queue[indx]; }
  311. else { return data; }
  312. }
  313.  
  314. }
  315.  
  316.  
  317. // using soft lock because this request can be ignored if collection is busy
  318. public async Task<string> GetList()
  319. {
  320. if (System.Threading.Monitor.TryEnter(busy, 1000))
  321. {
  322. try
  323. {
  324. SBuilder.Clear();
  325. foreach (var item in _queue)
  326. {
  327. SBuilder.Append($"[{item.URL}] [{item.date}] [{item.user}]n");
  328. }
  329. return SBuilder.ToString();
  330. }
  331. finally { System.Threading.Monitor.Exit(busy); }
  332. }
  333. return "Queue is busy";
  334. }
  335.  
  336. public async Task<string> Status()
  337. {
  338. return $"Entries: {Count} | Timer enabled: {_timer.Enabled} | Resolution {_timer.Interval}ms";
  339. }
  340.  
  341. //
  342. // User-bouncer checks
  343. // Using ConcurrentDic for this so dont care for locks and stuff
  344.  
  345. public async Task<bool> CanAdd(SocketUser user)
  346. {
  347. return !_bouncer.ContainsKey(user) || _bouncer[user].CanAdd();
  348. }
  349.  
  350. public async Task UpdateUser(SocketUser user)
  351. {
  352. if (!_bouncer.ContainsKey(user))
  353. {
  354. var result = _bouncer.TryAdd(user, new UserStamp(DateTime.Now, 1));
  355. if (!result) { Logger.LogConsoleInfo("Unable to add user to timestamp DB"); }
  356. }
  357. else
  358. {
  359. _bouncer[user].lastTimestamp = DateTime.Now;
  360. _bouncer[user].count = _bouncer[user].count + 1;
  361. }
  362. }
  363.  
  364. public async Task ResetUsers() { _bouncer.Clear(); }
  365.  
  366.  
  367. }
  368.  
  369. // Struct for List of queue
  370. public struct ScheduleData : IEquatable<ScheduleData>
  371. {
  372. public string URL;
  373. public DateTime date;
  374. public SocketUser user;
  375.  
  376. public ScheduleData(string url, DateTime DT, SocketUser usr)
  377. {
  378. URL = url;
  379. date = DT;
  380. user = usr;
  381. }
  382.  
  383. public override bool Equals(object obj)
  384. {
  385. if (!(obj is ScheduleData))
  386. return false;
  387.  
  388. ScheduleData SD = (ScheduleData)obj;
  389. return this.Equals(SD);
  390. }
  391.  
  392. public bool Equals(ScheduleData other)
  393. {
  394. return URL.Equals(other.URL);
  395. }
  396.  
  397. public override int GetHashCode()
  398. {
  399. unchecked
  400. {
  401. return 17 * 23 + URL.GetHashCode();
  402. }
  403. }
  404. }
  405.  
  406. public class UserStamp
  407. {
  408. public DateTime lastTimestamp;
  409. public int count;
  410.  
  411. public UserStamp(DateTime timestamp, int Count)
  412. {
  413. lastTimestamp = timestamp;
  414. count = Count;
  415. }
  416.  
  417. public bool CanAdd() { return count < 5 && (DateTime.Now - lastTimestamp).TotalSeconds > 600; }
  418.  
  419. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement