Skip to content

Commit

Permalink
Add feature: get original tweets from azure service #288
Browse files Browse the repository at this point in the history
Add feature: get original tweets from azure service
  • Loading branch information
pfedotovsky authored Mar 28, 2020
2 parents 65c2ffa + d1528f0 commit 73f98ca
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 3 deletions.
7 changes: 7 additions & 0 deletions DotNetRu.AzureService/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ public class RealmSettings

public string RealmName { get; set; }
}

public class TweetSettings
{
public string ConsumerKey { get; set; }

public string ConsumerSecret { get; set; }
}
}
15 changes: 15 additions & 0 deletions DotNetRu.AzureService/ApplicationLogging.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Logging;

namespace DotNetRu.Azure
{
/// <summary>
/// Shared logger
/// </summary>
internal static class ApplicationLogging
{
internal static ILoggerFactory LoggerFactory { get; set; }// = new LoggerFactory();
internal static ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();
internal static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName);

}
}
51 changes: 51 additions & 0 deletions DotNetRu.AzureService/Controllers/TweetController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
using DotNetRu.AzureService;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace DotNetRu.Azure
{
[Route("tweet")]
public class TweetController : ControllerBase
{
private readonly ILogger logger;

private readonly TweetSettings tweetSettings;

private readonly PushNotificationsManager pushNotificationsManager;

public TweetController(
ILogger<DiagnosticsController> logger,
TweetSettings tweetSettings,
PushNotificationsManager pushNotificationsManager)
{
this.logger = logger;
this.tweetSettings = tweetSettings;
this.pushNotificationsManager = pushNotificationsManager;
}

[HttpGet]
[Route("get_original")]
public async Task<IActionResult> GetOriginalTweets()
{
try
{
var tweets = await TweetService.GetAsync(tweetSettings);
var json = JsonConvert.SerializeObject(tweets);

return new OkObjectResult(json);
}
catch (Exception e)
{
logger.LogCritical(e, "Unhandled error while getting original tweets");
return new ObjectResult(e)
{
StatusCode = StatusCodes.Status500InternalServerError
};
}
}
}
}
5 changes: 3 additions & 2 deletions DotNetRu.AzureService/DotNetRu.AzureService.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="linqtotwitter" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="3.1.3" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.9.10" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NSwag.AspNetCore" Version="13.3.0" />
Expand Down
9 changes: 8 additions & 1 deletion DotNetRu.AzureService/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace DotNetRu.AzureService
{
Expand All @@ -22,10 +23,14 @@ public void ConfigureServices(IServiceCollection services)
var realmSettings = new RealmSettings();
Configuration.Bind("RealmOptions", realmSettings);

var tweetSettings = new TweetSettings();
Configuration.Bind("TweetOptions", tweetSettings);

var pushSettings = new PushSettings();
Configuration.Bind("PushOptions", pushSettings);

services.AddSingleton(realmSettings);
services.AddSingleton(tweetSettings);
services.AddSingleton(pushSettings);

services.AddScoped<PushNotificationsManager>();
Expand All @@ -38,8 +43,10 @@ public void ConfigureServices(IServiceCollection services)
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory logFactory)
{
ApplicationLogging.LoggerFactory = logFactory;

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
Expand Down
20 changes: 20 additions & 0 deletions DotNetRu.AzureService/Tweet/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace DotNetRu.Azure
{
public static class StringExtensions
{
internal static string ConvertToUsualUrl(this string input, List<KeyValuePair<string, string>> replacements)
{
var returnString = new StringBuilder(input);
foreach (var replacement in replacements)
{
returnString.Replace(replacement.Key, replacement.Value);
}

return Regex.Replace(returnString.ToString(), @"https:\/\/t\.co\/.+$", string.Empty);
}
}
}
77 changes: 77 additions & 0 deletions DotNetRu.AzureService/Tweet/Tweet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;

namespace DotNetRu.Azure
{
public class Tweet
{
public Tweet(ulong statusID)
{
StatusID = statusID;
}

private string tweetedImage;

public bool HasImage => !string.IsNullOrWhiteSpace(this.tweetedImage);

public string TweetedImage
{
get => this.tweetedImage;
set => this.tweetedImage = value;
}

public int? NumberOfLikes { get; set; }

public int NumberOfRetweets { get; set; }

public int NumberOfComments { get; set; }

public ulong StatusID { get; }

public string Text { get; set; }

public string Image { get; set; }

public string Url { get; set; }

public string Name { get; set; }

public string ScreenName { get; set; }

public DateTime CreatedDate { get; set; }

public string TitleDisplay => this.Name;

public string SubtitleDisplay => "@" + this.ScreenName;

public string DateDisplay => this.CreatedDate.ToShortDateString();

public Uri TweetedImageUri
{
get
{
try
{
if (string.IsNullOrWhiteSpace(this.TweetedImage))
{
return null;
}

return new Uri(this.TweetedImage);
}
catch
{
// TODO ignored
}

return null;
}
}

public bool HasAttachedImage => !string.IsNullOrWhiteSpace(this.TweetedImage);

public override string ToString()
{
return $"[Name={Name};Text={Text};Retweets={NumberOfRetweets};Likes={NumberOfLikes}";
}
}
}
99 changes: 99 additions & 0 deletions DotNetRu.AzureService/Tweet/TweetService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DotNetRu.AzureService;
using LinqToTwitter;
using Microsoft.Extensions.Logging;

namespace DotNetRu.Azure
{
internal class TweetService
{
private static readonly ILogger Logger = ApplicationLogging.CreateLogger(nameof(TweetService));

/// <summary>
/// Returns tweets by SpdDotNet/DotNetRu (if it's retweet then original tweet is returned instead of retweet)
/// </summary>
/// <returns>
/// Returns a list of tweets.
/// </returns>
internal static async Task<List<Tweet>> GetAsync(TweetSettings tweetSettings)
{
try
{
var auth = new ApplicationOnlyAuthorizer
{
CredentialStore =
new InMemoryCredentialStore
{
ConsumerKey =
tweetSettings.ConsumerKey,
ConsumerSecret =
tweetSettings.ConsumerSecret
},
};
await auth.AuthorizeAsync();

using var twitterContext = new TwitterContext(auth);
var spbDotNetTweets =
await (from tweet in twitterContext.Status
where tweet.Type == StatusType.User && tweet.ScreenName == "spbdotnet"
&& tweet.TweetMode == TweetMode.Extended
select tweet).ToListAsync();

var dotnetRuTweets =
await (from tweet in twitterContext.Status
where tweet.Type == StatusType.User && tweet.ScreenName == "DotNetRu"
&& tweet.TweetMode == TweetMode.Extended
select tweet).ToListAsync();

var unitedTweets = spbDotNetTweets.Union(dotnetRuTweets).Where(tweet => !tweet.PossiblySensitive).Select(GetTweet);

var tweetsWithoutDuplicates = unitedTweets.GroupBy(tw => tw.StatusID).Select(g => g.First());

var sortedTweets = tweetsWithoutDuplicates.OrderByDescending(x => x.CreatedDate).ToList();

return sortedTweets;
}
catch (Exception e)
{
Logger.LogError(e, "Unhandled error while getting original tweets");
}

return new List<Tweet>();
}

private static Tweet GetTweet(Status tweet)
{
var sourceTweet = tweet.RetweetedStatus.StatusID == 0 ? tweet : tweet.RetweetedStatus;

var urlLinks =
sourceTweet.Entities.UrlEntities.Select(t => new KeyValuePair<string, string>(t.Url, t.DisplayUrl)).ToList();

var profileImage = sourceTweet.User?.ProfileImageUrl.Replace("http://", "https://", StringComparison.InvariantCultureIgnoreCase);
if (profileImage != null)
{
//normal image is 48x48, bigger image is 73x73, see https://developer.twitter.com/en/docs/accounts-and-users/user-profile-images-and-banners
profileImage = Regex.Replace(profileImage, @"(.+)_normal(\..+)", "$1_bigger$2");
}

return new Tweet(sourceTweet.StatusID)
{
TweetedImage =
tweet.Entities?.MediaEntities.Count > 0
? tweet.Entities?.MediaEntities?[0].MediaUrlHttps ?? string.Empty
: string.Empty,
NumberOfLikes = sourceTweet.FavoriteCount,
NumberOfRetweets = sourceTweet.RetweetCount,
ScreenName = sourceTweet.User?.ScreenNameResponse ?? string.Empty,
Text = sourceTweet.FullText.ConvertToUsualUrl(urlLinks),
Name = sourceTweet.User?.Name,
CreatedDate = tweet.CreatedAt,
Url = $"https://twitter.com/{sourceTweet.User?.ScreenNameResponse}/status/{tweet.StatusID}",
Image = profileImage
};
}
}
}
4 changes: 4 additions & 0 deletions DotNetRu.AzureService/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"RealmServerUrl": "dotnetru.de1a.cloud.realm.io",
"RealmName": "dotnetru_050919"
},
"TweetOptions": {
"ConsumerKey": "ho0v2B1bimeufLqI1rA8KuLBp",
"ConsumerSecret": "RAzIHxhkzINUxilhdr98TWTtjgFKXYzkEhaGx8WJiBPh96TXNK"
},
"PushOptions": {
"AppType": "beta",
"AppCenterAppNames": [ "DotNetRu-Android-App", "DotNetRu-iOS-App" ],
Expand Down

0 comments on commit 73f98ca

Please sign in to comment.