PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/SauceHunt/Program.cs

https://gitlab.com/SirCmpwn/SauceHunt
C# | 291 lines | 283 code | 5 blank | 3 comment | 60 complexity | 505c7b337fab16572dfc77b4fff88cb7 MD5 | raw file
  1. using System;
  2. using System.IO;
  3. using Newtonsoft.Json;
  4. using RedditSharp;
  5. using System.Threading;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Net;
  9. using Newtonsoft.Json.Linq;
  10. using SauceHunt.Database;
  11. using NHibernate.Linq;
  12. using Database;
  13. namespace SauceHunt
  14. {
  15. class MainClass
  16. {
  17. public static Configuration Config;
  18. public static SauceDatabase SauceDatabase;
  19. public static void Main(string[] args)
  20. {
  21. Config = new Configuration();
  22. var configPath = "config.json";
  23. if (args.Length != 0)
  24. configPath = args[0];
  25. if (File.Exists(configPath))
  26. JsonConvert.PopulateObject(File.ReadAllText(configPath), Config);
  27. else
  28. {
  29. File.WriteAllText(configPath, JsonConvert.SerializeObject(Config, Formatting.Indented));
  30. Console.WriteLine(configPath);
  31. return;
  32. }
  33. File.WriteAllText(configPath, JsonConvert.SerializeObject(Config, Formatting.Indented));
  34. if (string.IsNullOrEmpty(Config.PostgreConnectionString))
  35. {
  36. Console.WriteLine("Needs a connection string.");
  37. return;
  38. }
  39. SauceDatabase = new SauceDatabase(Config.PostgreConnectionString);
  40. var reddit = new Reddit();
  41. reddit.LogIn(Config.RedditUsername, Config.RedditPassword);
  42. var subreddits = new List<Subreddit>();
  43. foreach (var sr in Config.Subreddits)
  44. subreddits.Add(reddit.GetSubreddit(sr.Name));
  45. while (true)
  46. {
  47. Console.WriteLine("Running update at {0}", DateTime.Now.ToShortTimeString());
  48. foreach (var _message in reddit.User.GetUnreadMessages())
  49. {
  50. if (_message is PrivateMessage)
  51. {
  52. var message = (PrivateMessage)_message;
  53. using (var session = SauceDatabase.SessionFactory.OpenSession())
  54. {
  55. var user = session.Query<User>().SingleOrDefault(u => u.Name == message.Author);
  56. if (user == null)
  57. {
  58. user = new User
  59. {
  60. Name = message.Author,
  61. SkipWaitPeriod = false,
  62. IgnoreUser = false,
  63. WarnIfNoSauce = false,
  64. Posts = new List<RedditPost>()
  65. };
  66. using (var transaction = session.BeginTransaction())
  67. {
  68. session.SaveOrUpdate(user);
  69. transaction.Commit();
  70. }
  71. }
  72. message.SetAsRead();
  73. if (message.Body.ToUpper().Contains("RESPOND IMMEDIATELY"))
  74. {
  75. user.SkipWaitPeriod = true;
  76. message.Reply("Okay, from now on, I will find the source for your posts as soon as I see them.");
  77. }
  78. else if (message.Body.ToUpper().Contains("SLOW DOWN"))
  79. {
  80. user.SkipWaitPeriod = false;
  81. message.Reply("I'll take things a little slower now.");
  82. }
  83. else if (message.Body.ToUpper().Contains("HELP ME FIND SAUCE"))
  84. {
  85. user.IgnoreUser = true;
  86. message.Reply("Sorry, I won't bother you any more.");
  87. }
  88. else if (message.Body.ToUpper().Contains("NOTICE ME"))
  89. {
  90. user.IgnoreUser = false;
  91. message.Reply("I'll try to pay more attention to you now.");
  92. }
  93. if (message.Body.ToUpper().Contains("WATCH OUT"))
  94. {
  95. user.WarnIfNoSauce = true;
  96. message.Reply("I've got your back, babe.");
  97. }
  98. if (message.Body.ToUpper().Contains("WARN ME"))
  99. {
  100. user.WarnIfNoSauce = false;
  101. message.Reply("Sorry, I won't look out for you anymore.");
  102. }
  103. using (var transaction = session.BeginTransaction())
  104. {
  105. session.SaveOrUpdate(user);
  106. transaction.Commit();
  107. }
  108. }
  109. }
  110. }
  111. foreach (var subreddit in subreddits)
  112. {
  113. var sr = Config.Subreddits.Single(s => s.Name.ToUpper().Replace("/R/", "") == subreddit.Name.ToUpper());
  114. var posts = subreddit.GetNew().Take(50).ToArray();
  115. foreach (var post in posts)
  116. {
  117. using (var session = SauceDatabase.SessionFactory.OpenSession())
  118. {
  119. if (post.IsSelfPost)
  120. continue;
  121. if (session.Query<RedditPost>().Any(p => p.RedditId == post.Id))
  122. continue;
  123. var user = session.Query<User>().SingleOrDefault(u => u.Name == post.AuthorName);
  124. if (user == null)
  125. {
  126. user = new User
  127. {
  128. Name = post.AuthorName,
  129. SkipWaitPeriod = false,
  130. IgnoreUser = false,
  131. Posts = new List<RedditPost>()
  132. };
  133. using (var transaction = session.BeginTransaction())
  134. {
  135. session.Save(user);
  136. transaction.Commit();
  137. }
  138. }
  139. try
  140. {
  141. var diff = DateTime.Now - post.Created;
  142. Console.WriteLine("Seeing post {1} from {0} minutes ago at {2}", Math.Ceiling(diff.TotalMinutes), post.Id, post.Created.ToString());
  143. bool sauceFound = false;
  144. if (user.SkipWaitPeriod || sr.SauceImmediately || (diff.TotalMinutes > 30 && diff.TotalMinutes < 60))
  145. {
  146. if (!user.IgnoreUser)
  147. {
  148. sauceFound = HandlePost(post);
  149. if (!sauceFound && user.WarnIfNoSauce)
  150. {
  151. reddit.ComposePrivateMessage("Warning: unable to locate sauce", "I was not able to locate sauce for [this post](" + post.Shortlink + ").", post.AuthorName);
  152. }
  153. var dbPost = new RedditPost
  154. {
  155. SauceFound = sauceFound,
  156. RedditId = post.Id,
  157. Submitter = user
  158. };
  159. user.Posts.Add(dbPost);
  160. using (var transaction = session.BeginTransaction())
  161. {
  162. session.SaveOrUpdate(dbPost);
  163. session.SaveOrUpdate(user);
  164. transaction.Commit();
  165. }
  166. }
  167. }
  168. }
  169. catch (Exception e)
  170. {
  171. Console.WriteLine("Error handling {0}", post.Id);
  172. if (!session.Query<RedditPost>().Any(p => p.RedditId == post.Id))
  173. {
  174. var dbPost = new RedditPost
  175. {
  176. SauceFound = false,
  177. RedditId = post.Id,
  178. Submitter = user
  179. };
  180. user.Posts.Add(dbPost);
  181. using (var transaction = session.BeginTransaction())
  182. {
  183. session.Save(dbPost);
  184. session.Save(user);
  185. transaction.Commit();
  186. }
  187. }
  188. }
  189. }
  190. }
  191. }
  192. Console.WriteLine("Done.");
  193. Thread.Sleep(Config.Interval * 60 * 1000);
  194. }
  195. }
  196. public static bool HandlePost(Post post)
  197. {
  198. // Make sure there's an image there
  199. var url = post.Url;
  200. var request = (HttpWebRequest)WebRequest.Create(url);
  201. request.Method = "HEAD";
  202. var response = request.GetResponse();
  203. if (!(response.ContentType == "image/png" || response.ContentType == "image/jpeg" || response.ContentType == "image/bmp"))
  204. {
  205. if (!DomainMaps(ref url))
  206. return false;
  207. }
  208. // Check to see if someone already posted some sauce
  209. var comments = post.GetComments();
  210. foreach (var comment in comments)
  211. {
  212. var text = comment.Body.ToUpper();
  213. if (text.Contains("SOURCE") || text.Contains("SAUCE") || text.Contains("ARTIST") ||
  214. text.Contains("PIXIV"))
  215. {
  216. return false;
  217. }
  218. }
  219. // I need me some sauce
  220. var sauce = GetSauce(url);
  221. if (sauce == null)
  222. return false;
  223. Console.WriteLine("Found sauce for {0}", post.Id);
  224. var check = (HttpWebRequest)WebRequest.Create(sauce.Item2);
  225. check.Method = "HEAD";
  226. try
  227. {
  228. check.GetResponse();
  229. }
  230. catch
  231. {
  232. Console.WriteLine("But it was borked.");
  233. return false;
  234. }
  235. var reply = string.Format(Config.CommentTemplate, sauce.Item1, sauce.Item2);
  236. while (true)
  237. {
  238. try
  239. {
  240. post.Comment(reply);
  241. break;
  242. }
  243. catch (RateLimitException e)
  244. {
  245. Console.WriteLine("Rate limited, waiting {0} seconds", Math.Ceiling(e.TimeToReset.TotalSeconds));
  246. Thread.Sleep(e.TimeToReset);
  247. Console.WriteLine("Attempting again.");
  248. }
  249. }
  250. return true;
  251. }
  252. public static System.Tuple<string, string> GetSauce(string url)
  253. {
  254. var request = (HttpWebRequest)WebRequest.Create("https://saucenao.com/search.php?db=999&output_type=2&numres=16&url=" +
  255. Uri.EscapeUriString(url) + "&api_key=" + Uri.EscapeUriString(Config.SauceNaoAPIKey));
  256. var response = request.GetResponse();
  257. var json = JToken.Parse(new StreamReader(response.GetResponseStream()).ReadToEnd());
  258. response.Close();
  259. if (json["results"] != null)
  260. {
  261. var top = json["results"][0];
  262. if (top["header"]["similarity"].Value<double>() >= 70 && top["header"]["index_id"].Value<int>() == 5 /* aka Pixiv */)
  263. return new System.Tuple<string, string>(top["data"]["member_name"].Value<string>(),
  264. "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=" + top["data"]["pixiv_id"].Value<string>());
  265. }
  266. return null;
  267. }
  268. public static bool DomainMaps(ref string url)
  269. {
  270. var uri = new Uri(url);
  271. if (uri.Host == "mediacru.sh")
  272. {
  273. var request = (HttpWebRequest)WebRequest.Create(url + ".json");
  274. var response = request.GetResponse();
  275. var json = JToken.Parse(new StreamReader(response.GetResponseStream()).ReadToEnd());
  276. if (json["blob_type"].Value<string>() != "image")
  277. return false;
  278. url = json["files"].Where(f => f["type"].Value<string>() == "image/png" ||
  279. f["type"].Value<string>() == "image/jpeg" || f["type"].Value<string>() == "image/svg+xml")
  280. .Select(f => f["url"].Value<string>()).FirstOrDefault();
  281. return true;
  282. }
  283. return false;
  284. }
  285. }
  286. }