Статьи

Создание клиента Twitter для Windows Phone — получение общедоступной временной шкалы

Если вы пропустили их, предыдущие статьи о том, как собрать MangoTree, клиент для Windows Phone в Твиттере, доступны здесь:

MangoTree может твитнуть, но в настоящее время не может видеть, что пишут другие люди. Однако существует метод API, который может получить текущий локальный канал для аутентифицированного пользователя. Это называется statuses / home_timeline . У меня уже есть ядро ​​OAuth, работающее довольно хорошо, поэтому все, что мне нужно сделать, это выполнить метод по URL-адресу, который Twitter дает разработчику, предоставляя правильные учетные данные. Вот фрагмент:

public static void GetHomeTimeline()
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add("oauth_consumer_key", AuthConstants.ConsumerKey);
    parameters.Add("oauth_signature_method", "HMAC-SHA1");
    parameters.Add("oauth_timestamp", StringHelper.UNIXTimestamp);
    parameters.Add("oauth_nonce", Guid.NewGuid().ToString().Replace("-", ""));
    parameters.Add("oauth_version", "1.0");
    parameters.Add("oauth_token", App.Token);

    OAuthClient client = new OAuthClient();
    client.PerformRequest(parameters,
        "http://api.twitter.com/1/statuses/home_timeline.json", "GET", AuthConstants.ConsumerSecret, App.TokenSecret, string.Empty, data =>
        {
            string returned = (string)data[0];
            Debug.WriteLine(returned);
        }, RequestType.Read);
}

Этот метод является частью статического класса Methods , который предназначен для доступа к различным возможностям Twitter. Когда этот метод успешно выполняется, я получаю необработанные данные JSON — для целей тестирования я помещаю их в консоль вывода. Количество данных, подключенных к одному твиту, довольно мало — посмотрите, что это такое:

{
      "in_reply_to_user_id_str":"14307150",
      "truncated":false,
      "created_at":"Mon Apr 09 04:44:12 +0000 2012",
      "coordinates":null,
      "retweeted":false,
      "place":null,
      "in_reply_to_screen_name":"tylerjameslee",
      "possibly_sensitive":false,
      "contributors":null,
      "user":{
         "id":1312381,
         "listed_count":57,
         "contributors_enabled":false,
         "profile_sidebar_border_color":"8f0000",
         "geo_enabled":true,
         "profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/75507540\/250px-Porkins.jpg",
         "friends_count":921,
         "profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2048185458\/IMG_20120405_155554_normal.jpg",
         "profile_background_tile":true,
         "followers_count":746,
         "is_translator":false,
         "show_all_inline_media":true,
         "follow_request_sent":false,
         "statuses_count":47764,
         "utc_offset":-25200,
         "profile_sidebar_fill_color":"cccccc",
         "name":"Joshua Rivera",
         "default_profile_image":false,
         "protected":false,
         "profile_background_color":"961313",
         "favourites_count":275,
         "lang":"en",
         "url":"http:\/\/www.thejoshuarivera.com\/",
         "verified":false,
         "created_at":"Fri Mar 16 20:54:27 +0000 2007",
         "profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/75507540\/250px-Porkins.jpg",
         "description":"Older white women say I'm very articulate. I'm also a huge dick.",
         "profile_link_color":"424240",
         "profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/2048185458\/IMG_20120405_155554_normal.jpg",
         "default_profile":false,
         "following":true,
         "profile_use_background_image":true,
         "location":"burque",
         "notifications":false,
         "id_str":"1312381",
         "profile_text_color":"7a7a7a",
         "time_zone":"Mountain Time (US & Canada)",
         "screen_name":"supersloth"
      },
      "retweet_count":0,
      "favorited":false,
      "in_reply_to_user_id":14307150,
      "source":"\u003Ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003ETweetDeck\u003C\/a\u003E",
      "in_reply_to_status_id_str":"189211447128104960",
      "geo":null,
      "in_reply_to_status_id":189211447128104960,
      "id":189212091205419008,
      "id_str":"189212091205419008",
      "text":"@tylerjameslee http:\/\/t.co\/sDmfpuXq and of course http:\/\/t.co\/7W24zRMc"
   }

Это, очевидно, требует реализации двух пользовательских моделей — Tweet и User . Я создал новую папку с именем Models и добавил два исходных файла cs для каждой из упомянутых моделей. 

Tweet.cs:

namespace MangoTree.Models
{
    public class Tweet
    {
        public string InReplyToUserIdStr { get; set; }
        public bool Truncated { get; set; }
        public string CreatedAt { get; set; }
        public string Coordinates { get; set; }
        public bool Retweeted { get; set; }
        public string Place { get; set; }
        public string InReplyToScreenName { get; set; }
        public string Contributors { get; set; }
        public User Author { get; set; }
        public int RetweetCount { get; set; }
        public bool Favorited { get; set; }
        public int InReplyToUserId { get; set; }
        public string Source { get; set; }
        public string InReplyToStatusIdStr { get; set; }
        public ulong InReplyToStatusId { get; set; }
        public ulong Id { get; set; }
        public string IdStr { get; set; }
        public string Text { get; set; }
    }
}

User.cs:

namespace MangoTree.Models
{
    public class User
    {
        public int ID { get; set; }
        public int ListedCount { get; set; }
        public bool ContributorsEnabled { get; set; }
        public string ProfileBackgroundImageUrlHTTPS { get; set; }
        public int FriendsCount { get; set; }
        public string ProfileSidebarColor { get; set; } // better to keep the raw color code
        public bool GeoEnabled { get; set; }
        public string ProfileImageUrl { get; set; }
        public bool ProfileBackgroundTile { get; set; }
        public int FollowersCount { get; set; }
        public bool IsTranslator { get; set; }
        public bool ShowAllInlineMedia { get; set; }
        public bool FollowRequestSent { get; set; }
        public int StatusesCount { get; set; }
        public int UTCOffset { get; set; }
        public string ProfileSidebarFillColor { get; set; } // again, better to keep the raw color
        public string Name { get; set; }
        public bool DefaultProfileImage { get; set; }
        public bool Protected { get; set; }
        public string ProfileBackgroundColor { get; set; }
        public int FavoritesCount { get; set; }
        public string Language { get; set; }
        public string Url { get; set; }
        public bool Verified { get; set; }
        public string CreatedAt { get; set; }
        public string ProfileBackgroundImageUrl { get; set; }
        public string Description { get; set; }
        public string ProfileLinkColor { get; set; }
        public string ProfileImageUrlHTTPS { get; set; }
        public bool DefaultProfile { get; set; }
        public bool Following { get; set; }
        public bool ProfileUseBackgroundImage { get; set; }
        public string Location { get; set; }
        public bool Notifications { get; set; }
        public string IdString { get; set; }
        public string ProfileTextColor { get; set; }
        public string TimeZone { get; set; }
        public string ScreenName { get; set; }
    }
}

Синтаксический анализ Json можно выполнить с помощью стандартной Json DLL — System.Json.dll (обычно находится в C: \ Program Files (x86) \ Microsoft SDKs \ Silverlight \ v4.0 \ Libraries \ Client ). В папке Utility я создал новый класс под названием TweetParser . Он будет использоваться для анализа отдельных твитов из необработанного ответа. Вот как выглядит конвертер «сырье-модель»:

using System;
using MangoTree.Models;
using System.Collections.ObjectModel;
using System.Json;
using System.Diagnostics;
namespace MangoTree.Utility
{
    public static class TweetParser
    {
        public static ObservableCollection<Tweet> Parse(string raw)
        {
            ObservableCollection<Tweet> returnable = new ObservableCollection<Tweet>();
            try
            {
                JsonArray array = JsonObject.Parse(raw) as JsonArray;
                foreach (JsonObject tweet in array)
                {
                    Tweet timelineItem = new Tweet()
                    {
                        InReplyToUserIdStr = (tweet["in_reply_to_user_id_str"] ?? string.Empty),
                        Truncated = (bool)tweet["truncated"],
                        CreatedAt = tweet["created_at"],
                        Retweeted = tweet["retweeted"],
                        Place = (tweet["place"] ?? string.Empty),
                        InReplyToScreenName = (tweet["in_reply_to_screen_name"] ?? string.Empty),
                        Contributors = (tweet["contributors"] ?? string.Empty),
                        RetweetCount = tweet["retweet_count"],
                        Favorited = tweet["favorited"],
                        InReplyToUserId = (tweet["in_reply_to_user_id"] ?? 0),
                        Source = (tweet["source"] ?? string.Empty),
                        InReplyToStatusIdStr = (tweet["in_reply_to_status_id_str"] ?? string.Empty),
                        InReplyToStatusId = (tweet["in_reply_to_status_id"] ?? 0),
                        Id = tweet["id"],
                        IdStr = tweet["id_str"],
                        Text = tweet["text"],
                        Author = GetUser(tweet["user"])
                    };

                    returnable.Add(timelineItem);
                }
            }
            catch (Exception ex)
            {
                // The collection failed parsing.
                Debug.WriteLine("Collection failed parsing.\n" + ex.InnerException + Environment.NewLine + ex.Data);
            }
            return returnable;
        }

        private static User GetUser(JsonValue rawUser)
        {
            User returnable = new User()
            {
                ID = rawUser["id"],
                ListedCount = rawUser["listed_count"],
                ContributorsEnabled = rawUser["contributors_enabled"],
                ProfileSidebarColor = rawUser["profile_sidebar_border_color"],
                GeoEnabled = rawUser["geo_enabled"],
                ProfileBackgroundImageUrlHTTPS = (rawUser["profile_background_image_url_https"] ?? string.Empty),
                FriendsCount = rawUser["friends_count"],
                ProfileImageUrl = rawUser["profile_image_url"],
                ProfileBackgroundTile = rawUser["profile_background_tile"],
                FollowersCount = rawUser["followers_count"],
                IsTranslator = rawUser["is_translator"],
                ShowAllInlineMedia = rawUser["show_all_inline_media"],
                FollowRequestSent = rawUser["follow_request_sent"],
                StatusesCount = rawUser["statuses_count"],
                UTCOffset = rawUser["utc_offset"],
                ProfileSidebarFillColor = rawUser["profile_sidebar_fill_color"],
                Name = rawUser["name"],
                DefaultProfileImage = rawUser["default_profile_image"],
                Protected = rawUser["protected"],
                ProfileBackgroundColor = rawUser["profile_background_color"],
                FavoritesCount = rawUser["favourites_count"],
                Language = rawUser["lang"],
                Url = (rawUser["url"] != null ? rawUser["url"].ToString() : string.Empty),
                Verified = rawUser["verified"],
                CreatedAt = rawUser["created_at"],
                ProfileBackgroundImageUrl = (rawUser["profile_background_image_url"] ?? string.Empty),
                Description = (rawUser["description"] ?? string.Empty),
                ProfileLinkColor = rawUser["profile_link_color"],
                ProfileImageUrlHTTPS = rawUser["profile_image_url_https"],
                DefaultProfile = rawUser["default_profile"],
                Following = rawUser["following"],
                ProfileUseBackgroundImage = rawUser["profile_use_background_image"],
                Location = (rawUser["location"] ?? string.Empty),
                Notifications = rawUser["notifications"],
                IdString = rawUser["id_str"],
                ProfileTextColor = rawUser["profile_text_color"],
                TimeZone = rawUser["time_zone"],
                ScreenName = rawUser["screen_name"]
            };
            return returnable;
        }
    }
}

Я активно использую оператор объединения нулей, чтобы проверить, пытаюсь ли я прочитать нулевое значение. В целом, процесс синтаксического анализа может быть выполнен со всеми необходимыми вам сантехническими операциями — просто используйте JSON.NET для десериализации модели, учитывая, что у вас есть правильное связывание атрибутов. Я отказался от этого, поскольку он добавит дополнительную ссылку на сборку, которая не будет интенсивно использоваться. System.Json довольно легок для этой конкретной ситуации и работает достаточно хорошо.

Возвращаясь к методу GetHomeTimeline , я теперь получаю ObservableCollection <Tweet> — теперь я могу легко связать его с чем угодно, включая ListBox, который будет помещен в MainPage.xaml .

Вы можете скачать текущий прогресс по проекту здесь (полный исходный код — без ключей API).