05 Feb 2016
In this tutorial I show you how to create your own custom twitter feed for your website. You will have full control over the styling of the feed, like the one I have on my about page. It also pulls through the follower count.
A lot of clients ask for this as they don't like the default styling of the twitter feed plugin.
This tutorial is meant for an MVC website with .NET 4.5 which makes it suitable for Umbraco too.
I've included some functions to help with formatting the tweets and a function to get the relative time since the tweet.
If you already have your twitter API keys you can skip to number 4.
Before you start, you need to have the twitter API keys. To get these, you need to:
PM> Install-Package Skybrud.SocialIf you don't use package manager, you can visit the Skybrud.Social site here http://social.skybrud.dk/
@using Skybrud.Social.Twitter
@using Skybrud.Social.Twitter.Entities
@using Skybrud.Social.Twitter.OAuth
@using Skybrud.Social.Twitter.Objects
@using System.Web.Configuration
@using System.Runtime.Caching
@using System.Text.RegularExpressions
@{
//get the twitter api keys from the web.config file.
string consumerKey = WebConfigurationManager.AppSettings["TwitterAPIConsumerKey"];
string consumerSecret = WebConfigurationManager.AppSettings["TwitterAPIConsumerSecret"];
string twitterUserName = WebConfigurationManager.AppSettings["TwitterUsername"];
const int TWITTER_FEED_CACHING_TIME_IN_MINUTES = 10;
const int NUMBER_OF_TWEETS_TO_SHOW = 3;
const string TWITTER_DOMAIN_URL = "https://twitter.com/";
var tweets = GetTweets(consumerKey, consumerSecret, twitterUserName, TWITTER_FEED_CACHING_TIME_IN_MINUTES).Take(NUMBER_OF_TWEETS_TO_SHOW);
}
@*These are the styles I used for mine, you can create your own*@
<style>
.tweets { background: #f1f1f1; display: block; }
.tweet { display: block; padding: 10px; }
.tweets .alt { background: #fff; }
.tweet-banner { color: #fff; text-align: center; }
.tweet-banner a { color: #fff; }
.tweet-banner img { border-radius: 50%; margin: 0 auto; }
.tweet-banner .shadow {
padding: 50px;
content: "";
position: relative;
width: 100%;
bottom: 0;
height: 100%;
background: -moz-linear-gradient(top, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
background: -webkit-linear-gradient(top, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#a6000000', GradientType=0);
}
span.pretty-date { font-size: 0.6em; }
</style>
@if (tweets != null && tweets.Any() && tweets.Count() > 0)
{
var user = @tweets.First().User;
int tweetCount = 0;
<div class="tweet-banner" style="background:url('@user.ProfileBannerUrl') center / cover">
<div class="shadow">
<h2><a href="@(TWITTER_DOMAIN_URL + user.ScreenName)" target="_blank">@user.Name (@@@(user.ScreenName))</a></h2>
<h3>@user.FollowersCount followers</h3>
<img src="@user.ProfileImageUrl" />
<h3>Latest tweets</h3>
</div>
</div>
<div class="tweets">
@foreach (TwitterStatusMessage tweet in tweets)
{
<div class="tweet @(tweetCount % 2 == 0 ? "" : "alt")">
<a href="@(TWITTER_DOMAIN_URL + tweet.User.ScreenName)"><img src="@tweet.User.ProfileImageUrl" /></a> <a href="@(TWITTER_DOMAIN_URL + tweet.User.ScreenName)" target="_blank">@@@(user.ScreenName)</a> <span class="pretty-date">@GetRelativeTimeBetweenDates(tweet.CreatedAt, DateTime.UtcNow)</span>
<p>@Html.Raw(FormatAsHtml(tweet))</p>
</div> tweetCount++;
}
</div>
}
else
{
@Html.Raw("Tweets currently unavailable")
}
@functions{
//These functions are for getting the tweets using Skybrud.Social and for formatting the tweet text.
/// <summary>
/// Gets a the last 20 Tweets from a user's timeline. Uses caching to reduce calls to the API.
/// </summary>
/// <param name="consumerKey">The consumerKey from your twitter API details</param>
/// <param name="consumerSecret">The consumerSecret from your twitter API details</param>
/// <param name="twitterUserName">The twitter name of the user whose timeline you want to get</param>
/// <param name="cacheTimeInMinutes">Number of minutes before the cache expires</param>
/// <returns>A collection of tweets from a user's timeline. Adds it to the cache for next time.</returns>
public static IEnumerable<TwitterStatusMessage> GetTweets(string consumerKey, string consumerSecret, string twitterUserName, int cacheTimeInMinutes)
{
ObjectCache cache = MemoryCache.Default;
var tweets = cache["tweets"] as IEnumerable<TwitterStatusMessage>;
if (tweets == null)
{
var policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(cacheTimeInMinutes);
Skybrud.Social.Twitter.OAuth.TwitterOAuthClient client = new Skybrud.Social.Twitter.OAuth.TwitterOAuthClient
{
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret
};
TwitterService service = TwitterService.CreateFromOAuthClient(client);
tweets = service.Statuses.GetUserTimeline(twitterUserName).Body.ToList();
cache.Set("tweets", tweets, policy);
}
return tweets;
}
/// <summary>
/// Calls the relevant methods to format a tweet as HTML
/// </summary>
/// <param name="status">The status object as a TwitterStatusMessage</param>
/// <returns>string to output as HTML</returns>
public static string FormatAsHtml(TwitterStatusMessage status)
{
var tweetText = ReplaceHashTagsAndUsernamesWithUrls(status.Text);
foreach (var url in status.Entities.Urls)
{
tweetText = ReplaceFlatUrlWithHTMLUrl(tweetText, url.Url);
}
foreach (var mediaEntity in status.Entities.Media)
{
tweetText = ReplaceMediaUrlWithImgTag(tweetText, mediaEntity);
}
return tweetText;
}
/// <summary>
/// Calls the methods for replacing hash tag text and username text with Urls to the hashtag or user on twitter.
/// </summary>
/// <param name="tweetText">Twitter status text</param>
/// <param name="domainUrl">The twitter domain url in case it changes at a later date</param>
/// <returns>The tweet text with the new formatted text in it</returns>
public static string ReplaceHashTagsAndUsernamesWithUrls(string tweetText, string domainUrl = "https://twitter.com")
{
string newText = ReplaceHashtagsWithUrls(tweetText);
newText = ReplaceTwitterUsernamesWithUrls(newText, domainUrl);
return newText;
}
/// <summary>
/// Looks for patterns in the tweet text which look like usernames and replaces them with some HTML markup for linking to the user
/// </summary>
/// <param name="tweetText">Twitter status text</param>
/// <param name="domainUrl">The twitter domain url in case it changes at a later date</param>
/// <returns>The tweet text with the new formatted text in it</returns>
public static string ReplaceTwitterUsernamesWithUrls(string tweetText, string domainUrl = "https://twitter.com")
{
return Regex.Replace(tweetText, @"@(\w+)", delegate (Match match)
{
string userName = match.ToString().Trim();
return String.Format("<a href=\"{0}/{1}\" target=\"_blank\">{2}</a>", domainUrl.TrimEnd('/'), userName.Substring(1), userName);
});
}
/// <summary>
/// Looks for patterns in the tweet text which look like hashtags and replaces them with some HTML markup for linking to the hashtag
/// </summary>
/// <param name="tweetText">Twitter status text</param>
/// <returns>The tweet text with the new formatted text in it</returns>
public static string ReplaceHashtagsWithUrls(string tweetText)
{
return Regex.Replace(tweetText, @"#(\w+)", delegate (Match match)
{
string hashTag = match.ToString().Trim().Substring(1);
return String.Format("<a href=\"https://twitter.com/hashtag/{0}?src=hash\" target=\"_blank\">#{0}</a>", hashTag);
});
}
/// <summary>
/// Looks a url in the tweet and replaces it with HTML markup for the url
/// </summary>
/// <param name="tweetText">Twitter status text</param>
/// <param name="urlText">The Url to replace</param>
/// <param name="newWindow">Decide whether to open in a new window or not</param>
/// <returns>The tweet text with the new formatted text in it</returns>
public static string ReplaceFlatUrlWithHTMLUrl(string tweetText, string urlText, bool newWindow = true)
{
return tweetText.Replace(urlText, String.Format("<a href=\"{0}\" {1}>{0}</a>", urlText, newWindow ? "target=\"_blank\"" : ""));
}
/// <summary>
/// Looks for images in a tweet and replaces the url text in the tweet text with the markup for displaying the image
/// </summary>
/// <param name="tweetText">Twitter status text</param>
/// <param name="mediaEntity">Media item containing the details of the image</param>
/// <param name="newWindow">Decide whether to open in a new window or not</param>
/// <returns>The tweet text with the new formatted text in it</returns>
public static string ReplaceMediaUrlWithImgTag(string tweetText, TwitterMediaEntity mediaEntity, bool newWindow = true)
{
return tweetText.Replace(mediaEntity.Url, String.Format("<a href=\"{0}\" {1}><img src=\"{0}\" /></a>", mediaEntity.MediaUrl, newWindow ? "target=\"_blank\"" : ""));
}
/// <summary>
/// Returns a string representing the relative time for when the tweet was tweeted.
/// I started out using this as a base, but rewrote some of it: http://www.dotnetperls.com/pretty-date
/// </summary>
/// <param name="startDate">The start date</param>
/// <param name="endDate">The end date</param>
/// <returns>A string representing the relative time between the two dates</returns>
public static string GetRelativeTimeBetweenDates(DateTime startDate, DateTime endDate)
{
const int MAXIMUM_DAYS_IN_MONTH = 31;
const int ONE_MINUTE = 60;
const int TWO_MINUTES = 120;
const int ONE_HOUR = 3600;
const int TWO_HOURS = 7200;
const int ONE_DAY = 86400;
const int ONE_WEEK = 7;
TimeSpan interval = endDate.Subtract(startDate);
int differenceInDays = (int)interval.TotalDays;
int differenceInSeconds = (int)interval.TotalSeconds;
string relativeTime = "";
if (differenceInDays >= 0 && differenceInDays < MAXIMUM_DAYS_IN_MONTH)
{
if (differenceInDays == 0)
{
if (differenceInSeconds < ONE_MINUTE)
{
relativeTime = "just now";
}
else if (differenceInSeconds < TWO_MINUTES)
{
relativeTime = "1 minute ago";
}
else if (differenceInSeconds < ONE_HOUR)
{
relativeTime = string.Format("{0} minutes ago", Math.Floor((double)differenceInSeconds / ONE_MINUTE));
}
else if (differenceInSeconds < TWO_HOURS)
{
relativeTime = "1 hour ago";
}
else if (differenceInSeconds < ONE_DAY)
{
relativeTime = string.Format("{0} hours ago", Math.Floor((double)differenceInSeconds / ONE_HOUR));
}
}
else if (differenceInDays == 1)
{
DateTime yesterday = endDate.AddDays(-1);
if (startDate.Date == yesterday.Date)
{
relativeTime = "yesterday";
}
else
{
relativeTime = "2 days ago";
}
}
else if (differenceInDays < ONE_WEEK)
{
relativeTime = string.Format("{0} days ago", differenceInDays);
}
else if (differenceInDays < MAXIMUM_DAYS_IN_MONTH)
{
double numberOfWeeks = Math.Ceiling((double)differenceInDays / ONE_WEEK);
relativeTime = string.Format("{0} week{1} ago", numberOfWeeks, numberOfWeeks > 1 ? "s" : "");
}
else
{
DateTime oneMonthAgo = endDate.AddMonths(-1);
if (startDate.Year == oneMonthAgo.Year && startDate.Month == oneMonthAgo.Month)
{
relativeTime = "last month";
}
else
{
relativeTime = startDate.ToString("dd MMM yyyy");
}
}
}
return relativeTime;
}
}
@{ Html.RenderPartial("_Tweets"); }
That's it, you can now tweak the markup and styling for how you want it and split the code off into separate classes etc. If you have any problems, please put comments in the bottom of the page and I will try to help you.
Please share on Twitter, Facebook, LinkedIn Google+ etc to help others.