private static bool SendTweet(ILogger log, Models.Tweet tweet, Dictionary <string, string> statesDict) { tweet.VotingMembers = GetMemberVotes(log, tweet.MemberPositions, statesDict); // Creates an image of senator voting positions to upload along with the tweet var filePath = CreateImage(log, tweet); if (!string.IsNullOrEmpty(filePath)) { byte[] file1 = File.ReadAllBytes(filePath); log.LogInformation($"Uploading image for tweet..."); var media = Upload.UploadBinary(file1); log.LogInformation($"Image upload complete!"); log.LogInformation($"Publishing tweet..."); var tweetResponse = Tweetinvi.Tweet.PublishTweet(tweet.TweetText, new PublishTweetOptionalParameters { Medias = new List <Tweetinvi.Models.IMedia> { media } }); if (tweetResponse == null) { log.LogError($"Tweet was unable to be published"); return(false); } else { log.LogInformation($"Tweet published! TweetId: {tweetResponse.Id}"); return(true); } } return(false); }
private static string CreateImage(ILogger log, Models.Tweet tweet) { log.LogInformation($"Creating image for tweet..."); // TODO: Move these constants to a constants file var DEFAULT_TWEET_IMAGE_WIDTH = 1230; var DEFAULT_TWEET_IMAGE_HEIGHT = 1625; var DEFAULT_TWEET_IMAGE_FONT_SIZE = 25; var DEFAULT_LINE_HEIGHT = 30; var ESTIMATED_NUMBER_OF_CHARS_PER_LINE = 110; var MEMBER1_NAME_START_X = 225; var MEMBER1_VOTE_START_X = 575; var MEMBER2_NAME_START_X = 725; var MEMBER2_VOTE_START_X = 1075; var VOTE_RESULTS_START_X = 425; var FINAL_RESULT_START_X = 543; var imageWidth = DEFAULT_TWEET_IMAGE_WIDTH; var imageHeight = DEFAULT_TWEET_IMAGE_HEIGHT; var regularFont = SystemFonts.CreateFont("Arial", DEFAULT_TWEET_IMAGE_FONT_SIZE, FontStyle.Regular); var boldFont = SystemFonts.CreateFont("Arial", DEFAULT_TWEET_IMAGE_FONT_SIZE, FontStyle.Bold); var numberOfLines = 0; if (!string.IsNullOrEmpty(tweet.BillSummary)) { // If there is a bill summary, make additional room for the "Bill Summary:" header, the Bill Summary // text, and a trailing new line on the image canvas numberOfLines = tweet.BillSummary.Length / ESTIMATED_NUMBER_OF_CHARS_PER_LINE; imageHeight += numberOfLines * DEFAULT_LINE_HEIGHT + (DEFAULT_LINE_HEIGHT * 2); } else if (!string.IsNullOrEmpty(tweet.VoteDescription)) { // If there is a vote description, make additional room for the "Vote Description:" header, the Vote Description // text, and a trailing new line on the image canvas numberOfLines = tweet.VoteDescription.Length / ESTIMATED_NUMBER_OF_CHARS_PER_LINE; imageHeight += numberOfLines * DEFAULT_LINE_HEIGHT + (DEFAULT_LINE_HEIGHT * 2); } log.LogInformation($"Image width: {imageWidth} Image height: {imageHeight}"); using (Image <Rgba32> img = new Image <Rgba32>(imageWidth, imageHeight)) { // Set the background color of the image to white img.Mutate(ctx => ctx.Fill(Rgba32.White)); // The height to draw on the canvas var y = 5; // The width to draw on the canvas var x = 5; // Populate image with header text if (!string.IsNullOrEmpty(tweet.BillSummary)) { img.Mutate(ctx => ctx.DrawText($"Bill Summary:", boldFont, Rgba32.Black, new PointF(x, y))); var textGraphicOptions = new TextGraphicsOptions(true) { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, WrapTextWidth = imageWidth }; y += DEFAULT_LINE_HEIGHT; img.Mutate(ctx => ctx.DrawText(textGraphicOptions, tweet.BillSummary, regularFont, Rgba32.Black, new PointF(x, y))); y += DEFAULT_LINE_HEIGHT * numberOfLines + DEFAULT_LINE_HEIGHT; } else if (!string.IsNullOrEmpty(tweet.VoteDescription)) { img.Mutate(ctx => ctx.DrawText($"Vote Description:", boldFont, Rgba32.Black, new PointF(x, y))); var textGraphicOptions = new TextGraphicsOptions(true) { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, WrapTextWidth = imageWidth }; y += DEFAULT_LINE_HEIGHT; img.Mutate(ctx => ctx.DrawText(textGraphicOptions, tweet.VoteDescription, regularFont, Rgba32.Black, new PointF(x, y))); y += DEFAULT_LINE_HEIGHT * numberOfLines + DEFAULT_LINE_HEIGHT; } // Populate canvas with senate vote positions var totalYes = 0; var totalNo = 0; var totalNotVoting = 0; foreach (var memberVote in tweet.VotingMembers) { var member1 = memberVote.Item1; var member2 = memberVote.Item2; img.Mutate(ctx => ctx.DrawText($"{member1.State}:", boldFont, Rgba32.Black, new PointF(x, y))); var member1Position = member1.VotePosition.ToLower(); // NOTE: _1 variable is unused. Used here so terinary operand can be used to reduce code size var _1 = member1Position == "yes" ? totalYes += 1 : member1Position == "no" ? totalNo += 1 : totalNotVoting += 1; var member2Position = member2.VotePosition.ToLower(); // NOTE: _2 variable is unused. Used here so terinary operand can be used to reduce code size var _2 = member2Position == "yes" ? totalYes += 1 : member2Position == "no" ? totalNo += 1 : totalNotVoting += 1; // Draw Member 1 results on canvas DrawMemberPositionOnCanvas(img, boldFont, regularFont, member1, MEMBER1_NAME_START_X, MEMBER1_VOTE_START_X, y); // Draw Member 2 results on canvas DrawMemberPositionOnCanvas(img, boldFont, regularFont, member2, MEMBER2_NAME_START_X, MEMBER2_VOTE_START_X, y); y += DEFAULT_LINE_HEIGHT; } // Populate canvas with vote results y += DEFAULT_LINE_HEIGHT; img.Mutate(ctx => ctx.DrawText($"Yes: {totalYes} No: {totalNo} Not Voting: {totalNotVoting}", boldFont, Rgba32.Black, new PointF(VOTE_RESULTS_START_X, y))); y += DEFAULT_LINE_HEIGHT; img.Mutate(ctx => ctx.DrawText($"Majority Position Required: {tweet.VoteType}", boldFont, Rgba32.Black, new PointF(VOTE_RESULTS_START_X, y))); y += DEFAULT_LINE_HEIGHT; var resultColor = Rgba32.Black; switch (tweet.ShortResult) { case "AGREED": case "CONFIRMED": case "SUCCEEDED": case "PASSED": resultColor = Rgba32.Green; break; case "FAILED": case "NOT SUSTAINED": case "VETO SUSTAINED": case "REJECTED": resultColor = Rgba32.Red; break; default: resultColor = Rgba32.Black; break; } img.Mutate(ctx => ctx.DrawText($"{tweet.ShortResult}", boldFont, resultColor, new PointF(FINAL_RESULT_START_X, y))); // Save as image return(SaveImage(log, img)); } }
public static async Task <List <Models.Tweet> > CreateTweets(ILogger log, HttpClient client, List <Vote> votes) { var tweets = new List <Models.Tweet>(); foreach (var vote in votes) { var voteUri = vote.VoteUri; var uri = voteUri.Replace(HelperMethods.GetEnvironmentVariable(PRO_PUBLICA_BASE_URL), ""); var voteResp = await client.GetStringAsync(uri); var voteData = JsonConvert.DeserializeObject <dynamic>(voteResp); JArray positionsArray = null; try { positionsArray = voteData["results"]["votes"]["vote"]["positions"] as JArray; } catch (Exception e) { log.LogError($"Exception when converting senate positions to JArray. Message: {e.Message}"); return(tweets); } var memberPositions = positionsArray.ToObject <IEnumerable <Member> >(); // Create body of tweet var tweetText = CreateTweetText(log, vote, memberPositions); var newTweet = new Models.Tweet { VoteDescription = vote.Description, Result = vote.Result, ShortResult = vote.ShortResult, MemberPositions = memberPositions, TweetText = tweetText, VoteUrl = vote.Url, VoteType = vote.VoteType, VoteDate = vote.Date }; if (!string.IsNullOrEmpty(vote.Bill.ApiUri)) { var billUri = vote.Bill.ApiUri; uri = billUri.Replace(HelperMethods.GetEnvironmentVariable(PRO_PUBLICA_BASE_URL), ""); var billResp = await client.GetStringAsync(uri); var billData = JsonConvert.DeserializeObject <dynamic>(billResp); if (billData != null) { if (billData["results"] is JArray billArray && billArray.Count > 0) { vote.Bill.Summary = billArray[0]["summary"].ToObject <string>(); vote.Bill.ShortSummary = billArray[0]["summary_short"].ToObject <string>(); newTweet.BillSummary = vote.Bill.Summary; } } } tweets.Add(newTweet); } log.LogInformation($"Number of tweets attempting to be published: {tweets.Count}"); return(tweets); }