コード例 #1
0
        public async Task <string> PostComment(string parentFullName, string text)
        {
            Dictionary <string, string> data = new Dictionary <string, string>();

            data.Add("api_type", "json");
            data.Add("thing_id", parentFullName);
            data.Add("text", text);

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/api/comment");

            request.Content = new FormUrlEncodedContent(data);
            HttpResponseMessage response = await SendRequestToRedditApi(request);

            string responseContent = await response.Content.ReadAsStringAsync();

            Log.Verbose($"Reddit PostComment Response:{Environment.NewLine}{JsonConvert.SerializeObject(JsonConvert.DeserializeObject(responseContent), Formatting.Indented)}");

            dynamic deserializedResponse = JsonConvert.DeserializeObject <dynamic>(responseContent);

            if (deserializedResponse == null)
            {
                return(null);
            }

            RedditThing commentThing = ((JObject)deserializedResponse.json.data.things[0].data).ToObject <RedditThing>();

            if (commentThing == null)
            {
                return(null);
            }

            return(commentThing.Name);
        }
コード例 #2
0
        protected virtual void UpsertIntoCollection(RedditThing thing)
        {
            var collection = GetMongoCollection();

            collection.ReplaceOne(
                filter: new BsonDocument("_id", thing.Id),
                options: new ReplaceOptions {
                IsUpsert = true
            },
                replacement: thing.ToBsonDocument());
        }
コード例 #3
0
        public static string GetCommandTextFromMention(this RedditThing mention, string myUsername)
        {
            List <string> messageBodyLines = mention.Body.Split(new string[] { "  \n", "\n\n" }, StringSplitOptions.RemoveEmptyEntries).ToList();

            foreach (string line in messageBodyLines)
            {
                int mentionIndex = line.ToLower().Trim().IndexOf($"u/{myUsername.ToLower()}");
                if (mentionIndex == 0 || mentionIndex == 1)
                {
                    return(line);
                }
            }

            return(null);
        }
コード例 #4
0
ファイル: MessageProcessor.cs プロジェクト: AaronV-T/IVAedit
        private static Uri GetMediaUrlFromPost(RedditThing post)
        {
            // Comment or Text Post
            if (post.Kind == "t1" || (post.IsSelf != null && post.IsSelf.Value))
            {
                string text = post.Kind == "t1" ? post.Body : post.Selftext;

                // Return the first link found in the body.
                string[] splitText = text.Split(' ', '(', ')', '\n');
                foreach (string s in splitText)
                {
                    if (Uri.TryCreate(s, UriKind.Absolute, out Uri uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
                    {
                        return(new Uri(s));
                    }
                }

                return(null);
            }
            // Link
            else if (post.Kind == "t3")
            {
                // Temporary override for v.redd.it links until youtube-dl is fixed.
                if (post.Url.ToLower().Contains("v.redd.it") && post.Media != null)
                {
                    dynamic mediaObj = post.Media;
                    if (mediaObj.reddit_video == null)
                    {
                        Console.WriteLine($"Can't find reddit_video object in post's media object.");
                        return(null);
                    }

                    string url = mediaObj.reddit_video.fallback_url;
                    return(new Uri(url));
                }

                if (post.CrosspostParentList != null && post.CrosspostParentList.Count > 0)
                {
                    return(GetMediaUrlFromPost(post.CrosspostParentList[0]));
                }

                return(new Uri(post.Url));
            }
            else
            {
                throw new ArgumentException($"Given post '{post}' is not a valid kind.");
            }
        }
コード例 #5
0
        public async Task CleanupPosts(DateTime earliestTimeToCleanup)
        {
            List <UploadLog> uploadLogs = databaseAccessor.GetAllUploadLogs();

            foreach (UploadLog uploadLog in uploadLogs)
            {
                if ((uploadLog.ReplyDeleted && uploadLog.UploadDeleted) || uploadLog.UploadDatetime < earliestTimeToCleanup)
                {
                    continue;
                }

                List <RedditThing> postAndReply = await redditClient.GetInfoOfCommentsAndLinks("all", new List <string> {
                    uploadLog.PostFullname, uploadLog.ReplyFullname
                });

                RedditThing originalFilePost = postAndReply[0];
                RedditThing replyComment     = postAndReply[1];

                if (!settings.FilterSettings.ProcessNSFWContent && originalFilePost.Over18 != null && originalFilePost.Over18.Value)
                {
                    await DeleteUpload(uploadLog, "Post was NSFW.");
                }
                else if (!settings.FilterSettings.ProcessNSFWContent && originalFilePost.Body != null && (originalFilePost.Body.ToLower().Contains("nsfw") || originalFilePost.Body.ToLower().Contains("nsfl")))
                {
                    await DeleteUpload(uploadLog, $"Post was self-marked as NSFW.");
                }
                else if (replyComment.Score < 0)
                {
                    await DeleteUpload(uploadLog, $"Reply score ({replyComment.Score}) was too low.");
                }
                else if (originalFilePost.Author == "[deleted]")
                {
                    await DeleteUpload(uploadLog, $"Post was deleted or removed.");
                }
                else if (originalFilePost.BannedAtUtc != null)
                {
                    await DeleteUpload(uploadLog, $"Post was removed.");
                }
            }
        }
コード例 #6
0
 protected override void UpsertIntoCollection(RedditThing thing)
 {
     return;
 }
コード例 #7
0
 protected override void UpsertIntoCollection(RedditThing thing)
 {
     throw new Exception("UpsertIntoCollection was hit");
 }
コード例 #8
0
ファイル: MessageProcessor.cs プロジェクト: AaronV-T/IVAedit
        private async Task <Tuple <bool, string> > PostIsSafeToProcess(RedditThing post, bool isRootPost)
        {
            FilterSettings filterSettings = settings.FilterSettings;

            // If this is the post with the media file to manipulate: ...
            if (isRootPost)
            {
                if (post.Score < filterSettings.MinimumPostScore)
                {
                    return(new Tuple <bool, string>(false, $"Post's score is too low (Post Score: {post.Score}; Minimum Required Score: {filterSettings.MinimumPostScore})."));
                }

                if (post.Kind == "t1")
                {
                    if (!filterSettings.ProcessEditedComments && (post.Edited == null || post.Edited.Value))
                    {
                        return(new Tuple <bool, string>(false, $"Post is edited."));
                    }
                    if (!filterSettings.ProcessNSFWContent && (post.Body.ToLower().Contains("nsfw") || post.Body.ToLower().Contains("nsfl")))
                    {
                        return(new Tuple <bool, string>(false, $"Post is self-marked as NSFW/NSFL."));
                    }
                }

                RedditThing user = await redditClient.GetInfoOfUser(post.Author);

                if (user.CommentKarma == null || user.LinkKarma == null || user.CommentKarma + user.LinkKarma < filterSettings.MinimumPostingAccountKarma)
                {
                    return(new Tuple <bool, string>(false, $"Posting user's account doesn't have enough karma (Karma: {user.CommentKarma + user.LinkKarma}; Minimum Required Karma: {filterSettings.MinimumPostingAccountKarma})."));
                }
                if (user.CreatedUtc == null || user.CreatedUtc.Value.UnixTimeToDateTime() > DateTime.Today.AddDays(-filterSettings.MinimumPostingAccountAgeInDays))
                {
                    return(new Tuple <bool, string>(false, $"Posting user's account isn't old enough (must be at least {filterSettings.MinimumPostingAccountAgeInDays} days old)."));
                }
            }

            // If this post is a comment: ...
            if (post.Kind == "t1")
            {
                return(await PostIsSafeToProcess(await redditClient.GetInfoOfCommentOrLink(post.Subreddit, post.LinkId), false)); // Check this comment's parent post.
            }
            // If this post is a link: ...
            else if (post.Kind == "t3")
            {
                if (!filterSettings.ProcessNSFWContent && (post.Over18 == null || post.Over18.Value))
                {
                    return(new Tuple <bool, string>(false, $"Link is NSFW."));
                }
                if (databaseAccessor.GetBlacklistedSubreddit(post.Subreddit) != null)
                {
                    return(new Tuple <bool, string>(false, $"Subreddit is blacklisted."));
                }
                if (post.SubredditSubscribers == null || post.SubredditSubscribers < filterSettings.MinimumSubredditSubscribers)
                {
                    return(new Tuple <bool, string>(false, $"Subreddit is too small (Subscribers: {post.SubredditSubscribers}; Minimum Required Subscribers: {filterSettings.MinimumSubredditSubscribers})."));
                }
                if (!filterSettings.ProcessPostsInNonPublicSubreddits && (post.SubredditType == null || post.SubredditType != "public"))
                {
                    return(new Tuple <bool, string>(false, $"Subreddit is not public."));
                }

                return(new Tuple <bool, string>(true, "Post seems safe."));
            }
            else
            {
                throw new ArgumentException($"Given post '{post.Name}' is not a valid kind '{post.Kind}'.");
            }
        }
コード例 #9
0
ファイル: MessageProcessor.cs プロジェクト: AaronV-T/IVAedit
        private async Task ProcessRequests(List <RedditThing> requests)
        {
            // Get parent of each request.
            Dictionary <RedditThing, RedditThing> requestsWithParents = new Dictionary <RedditThing, RedditThing>();

            foreach (RedditThing request in requests)
            {
                RedditThing immediateParent = await redditClient.GetInfoOfCommentOrLink(request.Subreddit, request.ParentId);

                // Determine whether to use the root link or the request's immediate parent.
                RedditThing   parentPost;
                List <string> splitRequest = request.GetCommandTextFromMention(redditClient.Username).Split().ToList();
                if (immediateParent.Kind == "t3" || (splitRequest.Count > 1 && splitRequest[1].ToLower() == "!immediate"))
                {
                    parentPost = immediateParent;
                }
                else
                {
                    parentPost = await redditClient.GetInfoOfCommentOrLink(request.Subreddit, immediateParent.LinkId);
                }

                requestsWithParents.Add(request, parentPost);
            }

            // Process each request.
            Dictionary <string, List <RedditThing> > processedRequestsByUser = new Dictionary <string, List <RedditThing> >();

            foreach (var kvp in requestsWithParents)
            {
                try
                {
                    RedditThing mentionComment = kvp.Key;
                    RedditThing parentPost     = kvp.Value;

                    // If we have already processed a request from this user in this batch of requests:...
                    if (processedRequestsByUser.ContainsKey(mentionComment.Author))
                    {
                        // If this is a duplicate request: discard it.
                        if (processedRequestsByUser[mentionComment.Author].Any(rt => rt.ParentId == mentionComment.ParentId && rt.Body == mentionComment.Body))
                        {
                            Log.Information($"Skipping {mentionComment.Name}: Post is a duplicate. ({mentionComment.Author}: '{mentionComment.Body}')");
                            await redditClient.MarkMessagesAsRead(new List <string> {
                                mentionComment.Name
                            });

                            continue;
                        }
                        // If we have already processed enough requests from this user in this batch: temporarily skip it.
                        else if (processedRequestsByUser[mentionComment.Author].Count >= 2)
                        {
                            Log.Information($"Temporarily skipping {mentionComment.Name}: Too many recent requests. ({mentionComment.Author}: '{mentionComment.Body}')");
                            continue;
                        }

                        processedRequestsByUser[mentionComment.Author].Add(mentionComment);
                    }
                    else
                    {
                        processedRequestsByUser.Add(mentionComment.Author, new List <RedditThing> {
                            mentionComment
                        });
                    }

                    bool requestorIsAdmin = settings.Administrators.Contains(mentionComment.Author);

                    // Verify that the media post is old enough.
                    if (!requestorIsAdmin && parentPost.CreatedUtc.Value.UnixTimeToDateTime() > DateTime.Now.ToUniversalTime().AddMinutes(-settings.FilterSettings.MinimumPostAgeInMinutes))
                    {
                        Log.Information($"Temporarily skipping {mentionComment.Name}: Post is too recent. ({mentionComment.Author}: '{mentionComment.Body}')");
                        continue;
                    }

                    await redditClient.MarkMessagesAsRead(new List <string> {
                        mentionComment.Name
                    });

                    // Verify that the requestor isn't blacklisted.
                    if (databaseAccessor.GetBlacklistedUser(mentionComment.Author) != null)
                    {
                        Log.Information($"Skipping {mentionComment.Name}: Requestor is blacklisted. ({mentionComment.Author}: '{mentionComment.Body}')");
                        continue;
                    }

                    Func <string, Task> onFailedToProcessPost = async(reason) =>
                    {
                        Log.Information($"Skipping {mentionComment.Name}: {reason} ({mentionComment.Author}: '{mentionComment.Body}')");
                        await PostReplyToFallbackThread($"/u/{mentionComment.Author} I was unable to process your [request](https://reddit.com{mentionComment.Context}).  \nReason: {reason}");
                    };

                    // Verify that the post is safe to process.
                    Tuple <bool, string> postSafetyInfo = await PostIsSafeToProcess(parentPost, true);

                    if (!requestorIsAdmin && !postSafetyInfo.Item1)
                    {
                        await onFailedToProcessPost($"{postSafetyInfo.Item2} See [here](https://www.reddit.com/r/IVAEbot/wiki/index#wiki_limitations) for more information.");

                        continue;
                    }

                    // Get the commands from the mention comment.
                    List <IVAECommand> commands;
                    try
                    {
                        commands = IVAECommandFactory.CreateCommands(mentionComment.GetCommandTextFromMention(redditClient.Username));

                        IVAECommand speedupCommand = commands.FirstOrDefault(c => c.GetType() == typeof(AdjustSpeedCommand) && ((AdjustSpeedCommand)c).FrameRate > 1);
                        if (speedupCommand != null)
                        {
                            commands.Remove(speedupCommand);
                            commands.Insert(0, speedupCommand);
                        }

                        IVAECommand trimCommand = commands.FirstOrDefault(c => c.GetType() == typeof(TrimCommand));
                        if (trimCommand != null)
                        {
                            commands.Remove(trimCommand);
                            commands.Insert(0, trimCommand);
                        }
                    }
                    catch (ArgumentException ex)
                    {
                        await onFailedToProcessPost($"{ex.Message}  \nSee [here](https://www.reddit.com/r/IVAEbot/wiki/index#wiki_commands) for a list of valid commands.");

                        continue;
                    }
                    catch (Exception ex)
                    {
                        Log.Warning(ex.ToString());
                        await onFailedToProcessPost($"An error occurred while trying to parse commands.  \nSee [here](https://www.reddit.com/r/IVAEbot/wiki/index#wiki_commands) for a list of valid commands.");

                        continue;
                    }

                    if (commands != null && commands.Any(command => commands.Count(cmd => cmd.GetType() == command.GetType()) > 1)) // This is O(n^2), consider something more efficient.
                    {
                        await onFailedToProcessPost("Multiple commands of same type.");

                        continue;
                    }

                    // Get url to media file.
                    Uri mediaUrl = GetMediaUrlFromPost(parentPost);
                    if (mediaUrl == null || string.IsNullOrWhiteSpace(mediaUrl.AbsoluteUri))
                    {
                        await onFailedToProcessPost("Invalid media URL.");

                        continue;
                    }

                    // Ensure that the download directory exists.
                    if (!System.IO.Directory.Exists(DOWNLOAD_DIR))
                    {
                        System.IO.Directory.CreateDirectory(DOWNLOAD_DIR);
                    }

                    string mediaFilePath = null;
                    try
                    {
                        // Download the media file.
                        System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
                        string fileNameWithoutExtension        = Guid.NewGuid().ToString();
                        string filePathWithoutExtension        = System.IO.Path.Combine(DOWNLOAD_DIR, fileNameWithoutExtension);
                        string mediaUrlFileExtension           = System.IO.Path.GetExtension(mediaUrl.AbsoluteUri).ToLower();
                        if (mediaUrlFileExtension == ".jpg" || mediaUrlFileExtension == ".png")
                        {
                            // Verify that the file is not too large.
                            if (!TryGetMediaFileSize(mediaUrl.AbsoluteUri, out long fileSize))
                            {
                                await onFailedToProcessPost("Failed to get media file size.");

                                continue;
                            }
                            else if (fileSize > settings.FilterSettings.MaximumDownloadFileSizeInMB * 10000000)
                            {
                                await onFailedToProcessPost("Media file too large.");

                                continue;
                            }

                            mediaFilePath = $"{filePathWithoutExtension}{mediaUrlFileExtension}";

                            using (System.Net.WebClient client = new System.Net.WebClient())
                            {
                                client.DownloadFile(mediaUrl, mediaFilePath);
                            }
                        }
                        else if (mediaUrl.Host == "v.redd.it")
                        {
                            // Temporary override for v.redd.it links until youtube-dl is fixed.
                            mediaFilePath = $"{filePathWithoutExtension}.mp4";

                            using (System.Net.WebClient client = new System.Net.WebClient())
                            {
                                client.DownloadFile(mediaUrl, mediaFilePath);

                                try
                                {
                                    string audioUrl         = $"{mediaUrl.AbsoluteUri.Substring(0, mediaUrl.AbsoluteUri.LastIndexOf("/"))}/audio";
                                    string audioFilePath    = System.IO.Path.Combine(DOWNLOAD_DIR, $"{System.IO.Path.GetFileNameWithoutExtension(mediaFilePath)}_audio");
                                    string combinedFilePath = System.IO.Path.Combine(DOWNLOAD_DIR, "combined.mp4");
                                    client.DownloadFile(audioUrl, audioFilePath);
                                    new MediaManipulation.FFmpegProcessRunner().Run($"-i {mediaFilePath} -i {audioFilePath} -acodec copy -vcodec copy \"{combinedFilePath}\"");
                                    System.IO.File.Delete(audioFilePath);
                                    System.IO.File.Delete(mediaFilePath);
                                    System.IO.File.Move(combinedFilePath, mediaFilePath);
                                }
                                catch (Exception) { }
                            }
                        }
                        else
                        {
                            YoutubedlProcessRunner youtubedlProcessRunner = new YoutubedlProcessRunner();
                            List <string>          downloadOutput         = youtubedlProcessRunner.Run($"\"{mediaUrl.AbsoluteUri}\" --max-filesize {settings.FilterSettings.MaximumDownloadFileSizeInMB}m -o \"{filePathWithoutExtension}.%(ext)s\" -f mp4");
                            mediaFilePath = System.IO.Directory.GetFiles(DOWNLOAD_DIR, $"{fileNameWithoutExtension}*").SingleOrDefault();
                        }

                        if (mediaFilePath == null)
                        {
                            await onFailedToProcessPost("Failed to download media file. (The file may have been too big.)");

                            continue;
                        }

                        // Execute all commands on the media file.
                        long origFileSize = new System.IO.FileInfo(mediaFilePath).Length;
                        foreach (IVAECommand command in commands)
                        {
                            string path = command.Execute(mediaFilePath);
                            System.IO.File.Delete(mediaFilePath);

                            if (System.IO.File.Exists(path))
                            {
                                mediaFilePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(mediaFilePath), $"{System.IO.Path.GetFileNameWithoutExtension(mediaFilePath)}{System.IO.Path.GetExtension(path)}");
                                System.IO.File.Move(path, mediaFilePath);
                            }
                        }

                        if (!System.IO.File.Exists(mediaFilePath))
                        {
                            await onFailedToProcessPost($"Failed to create output file.");

                            continue;
                        }

                        double transformedFileSizeInMB = ((double)new System.IO.FileInfo(mediaFilePath).Length) / 1000000;
                        MediaManipulation.MediaFileInfo transformedMFI = new MediaManipulation.MediaFileInfo(mediaFilePath);
                        if (!transformedMFI.IsValidMediaFile)
                        {
                            await onFailedToProcessPost($"Output file was broken.");

                            continue;
                        }
                        if (transformedFileSizeInMB > settings.FilterSettings.MaximumUploadFileSizeInMB)
                        {
                            await onFailedToProcessPost($"Output file ({transformedFileSizeInMB.ToString("N2")}MB) can not be larger than {settings.FilterSettings.MaximumUploadFileSizeInMB}MB.");

                            continue;
                        }
                        else if (transformedMFI.Duration > settings.FilterSettings.MaximumUploadFileDurationInSeconds)
                        {
                            await onFailedToProcessPost($"Output file ({transformedMFI.Duration.Value.ToString("N2")}s) can not be longer than {settings.FilterSettings.MaximumUploadFileDurationInSeconds} seconds.");

                            continue;
                        }

                        // Upload transformed media file.
                        byte[] mediaFileBytes = System.IO.File.ReadAllBytes(mediaFilePath);
                        string deleteKey, uploadDestination, uploadPath, uploadLink;
                        if (!transformedMFI.HasVideo || transformedMFI.Duration <= 30)
                        {
                            uploadDestination = "imgur";

                            string videoFormat = null;
                            if (System.IO.Path.GetExtension(mediaFilePath) == ".mp4")
                            {
                                videoFormat = "mp4";
                            }
                            ImgurUploadResponse imgurUploadResponse = await imgurClient.Upload(mediaFileBytes, videoFormat);

                            if (imgurUploadResponse == null)
                            {
                                await onFailedToProcessPost("Failed to upload transformed file.");

                                continue;
                            }

                            deleteKey  = imgurUploadResponse.DeleteHash;
                            uploadLink = imgurUploadResponse.Link;
                            uploadPath = imgurUploadResponse.Name;
                        }
                        else
                        {
                            uploadDestination = "gfycat";
                            string gfyname = await gfycatClient.Upload(mediaFileBytes);

                            if (gfyname == null)
                            {
                                await onFailedToProcessPost("Failed to upload transformed file.");

                                continue;
                            }

                            deleteKey  = gfyname;
                            uploadLink = $"https://giant.gfycat.com/{gfyname}.mp4";
                            uploadPath = gfyname;
                        }

                        // Respond with link.
                        stopwatch.Stop();
                        Guid uploadId = Guid.NewGuid();

                        string responseText = $"[Direct File Link]({uploadLink})\n\n" +
                                              $"***\n" +
                                              $"Finished in {(stopwatch.Elapsed.TotalMinutes >= 1 ? $"{stopwatch.Elapsed.ToString("mm")} minutes " : "" )}{stopwatch.Elapsed.ToString("ss")} seconds. {((double)origFileSize / 1000000).ToString("N2")}MB -> {transformedFileSizeInMB.ToString("N2")}MB.  \n" +
                                              $"[How To Use](https://www.reddit.com/r/IVAEbot/wiki/index) | [Submit Feedback](https://www.reddit.com/message/compose/?to=TheTollski&subject=IVAEbot%20Feedback) | [Delete](https://www.reddit.com/message/compose/?to=IVAEbot&subject=Command&message=delete%20{uploadId.ToString()}) (Requestor Only)  \n" +
                                              $"^^I ^^am ^^a ^^bot ^^in ^^beta ^^testing ^^and ^^need ^^more ^^[testers](https://www.reddit.com/r/IVAEbot/comments/bp3aha/testers_needed/). ^^Feel ^^free ^^to ^^learn ^^what ^^I ^^can ^^do ^^and ^^summon ^^me.";
                        string replyCommentName = await redditClient.PostComment(mentionComment.Name, responseText);

                        if (replyCommentName == null)
                        {
                            replyCommentName = await PostReplyToFallbackThread($"/u/{mentionComment.Author} I was unable to repond directly to your [request]({mentionComment.Permalink}) so I have posted my response here.\n\n{responseText}");
                        }

                        databaseAccessor.InsertUploadLog(new UploadLog
                        {
                            Id                = uploadId,
                            PostFullname      = parentPost.Name,
                            ReplyDeleted      = false,
                            ReplyFullname     = replyCommentName,
                            RequestorUsername = mentionComment.Author,
                            UploadDatetime    = DateTime.UtcNow,
                            UploadDeleted     = false,
                            UploadDeleteKey   = deleteKey,
                            UploadDestination = uploadDestination,
                            UploadPath        = uploadPath
                        });
                    }
                    catch (Exception)
                    {
                        if (!string.IsNullOrWhiteSpace(mediaFilePath) && System.IO.File.Exists(mediaFilePath))
                        {
                            System.IO.File.Delete(mediaFilePath);
                        }

                        throw;
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Exception occurred while processing a request.");
                }
            }
        }