Powiadomienia z dobreprogramy.pl w C# — z dziennika dewelopera
Prace ku stworzeniu uniwersalnej aplikacji Windows 10 (+ Mobile) obsługującej powiadomienia z portalu dobreprogramy.pl posuwają się na przódu. We wcześniejszym poście przedstawiłem kod (plus projekt w VS), który służy do logowania się na swoje konto z poziomu C#. Został nam zatem ostatni etap w przygotowaniu serca naszej aplikacji - zarządzanie powiadomieniami. Zatem do dzieła!
Pobieramy powiadomienia z portalu w formacie JSON
Analiza sposobu działania powiadomień na portalu została przedstawiona w poście: Analizujemy kod portalu dobreprogramy.pl — czyli jak działa system pow.... Dziś przejdziemy już jednak do kodowania.
Zacznijmy zatem od pobrania JSONa z listą powiadomień dla zalogowanego użytkownika. Zakładamy oczywiście, że posiadamy już ciasteczko (w kodzie jest to zmienna cookie), które identyfikuje zalogowanego użytkownika. Opis w jaki sposób jest to zrobione znajduje się w ostatnim wpisie (Logujemy się do dobreprogramy.pl z poziomu kodu C# ).
request = WebRequest.Create(Const.NotifyUrlWithTimeStamp); request.Headers["Cookie"] = cookie; response = await request.GetResponseAsync(); string pageSource; using (StreamReader sr = new StreamReader(response.GetResponseStream())) { pageSource = sr.ReadToEnd(); }
Podobnie jak przy logowaniu, tworzymy zapytanie do serwera poprzez użycie metody z klasy abstrakcyjnej WebRequest. Naszym adresem docelowym jest:
http://www.dobreprogramy.pl/Providers/NotifyHelper.ashx?ping=ping&_=znacznik_czasu
Oczywiście zmienna znacznik_czasu będzie generowana przez nasz kod przy każdym zapytaniu. Jest to nic innego jak aktualna data JavaScript jako int (ilość milisekund od 1 stycznia 1970 roku).
Ważnym elementem jest tutaj uzupełnienie nagłówka o ciasteczko, jakie pozyskaliśmy na etapie logowania. W kolejnych krokach pobieramy zapytanie z serwera, czyli string posiadający odpowiedź w JSONie:
Praca z JSONem - Json.NET na ratunek!
Zostaje nam zatem przerobienie JSONa na coś bardziej zjadliwego. Celem jest stworzenie obiektów nowej klasy, które będą reprezentować powiadomienia z portalu. Chcąc ułatwić pracę z JSONem, nie trzeba tworzyć klas pośrednich lub ręcznie parsować stringa. Posłużymy się tutaj deserializatorem z frameworku Json.NET. W tym celu do projektu dodajemy przez NuGeta pakiet Newtonsoft.Json. Nasz kod uzupełniamy o linijkę:
var respList = (JObject)JsonConvert.DeserializeObject(pageSource);
Pozwoli to nam na operowanie na danych w znacznie wygodniejszy sposób:
Jak wygląda powiadomienie?
W celu przechowywania powiadomień w aplikacji (bazka SQLite, a może coś bardziej trywialnego, jak plik XML ładowany, tylko częściowo, na wejście do aplikacji - to jeszcze kwestia otwarta). W tym celu stworzyłem klasę, która będzie przetrzymywała dane:
public class Notification { public string Id { get; set; } public string PublicationId { get; set; } public string Avatar { get; set; } public string CustomText { get; set; } public string Title { get; set; } public string TargetUrl { get; set; } public DateTime AddedDate { get; set; } public NotificationType TypeValue { get; set; } public NotificationStatus Status { get; set; } public string UserName { get; set; } }
Pola są odwzorowaniem danych z JSONa, dodatkowo uzupełnione o bardziej strawne formaty odnośnie statusu powiadomienia (enum NotificationStatus), typu powiadomienia (enum NotificationType), a także daty przekonwertowanej na format DateTime.
Statusy powiadomień mamy dwa (w tym jeden własny, gdyby coś nowego się pojawiło: Unknown). Typów notyfikacji jest znacznie więcej. Całość przedstawia się następująco:
public enum NotificationType { Unknown = -1, Comment = 0, CommentBlog = 1, ProgramUpdate = 2, Contest = 3, FriendsAccept = 4, FriendsInvite = 5, BlogAnnotation = 6, PrivateMsg = 7, Mention = 8, License = 9, Badges = 10, } public enum NotificationStatus { Unknown = -1, New = 0, Old = 1 }
Typy powiadomień w JSONie są w formie tekstu, więc parsujemy je wg następującego schematu:
public static NotificationType ParseToNotificationType(string typeString) { if (string.IsNullOrWhiteSpace(typeString)) { return NotificationType.Unknown; } typeString = typeString.ToLower(); switch (typeString) { case "comment": return NotificationType.Comment; case "comment_blog": return NotificationType.CommentBlog; case "program_update": return NotificationType.ProgramUpdate; case "contest": return NotificationType.Contest; case "friends_accept": return NotificationType.FriendsAccept; case "friends_invite": return NotificationType.FriendsInvite; case "blog_annotation": return NotificationType.BlogAnnotation; case "private_msg": return NotificationType.PrivateMsg; case "mention": return NotificationType.Mention; case "license": return NotificationType.License; case "badges": return NotificationType.Badges; default: return NotificationType.Unknown; } }
JSON => Notification
Samo parsowanie z JSONa na nasz obiekt Notification jest trywialnie proste dzięki Json.NET:
if (respList.HasValues) { var c = respList.First.First; for (int i = 0; i < c.Count(); i++) { var ele = (JProperty)c.ElementAt(i); Notification n = JsonConvert.DeserializeObject<Notification>(ele.Value.ToString()); n.AddedDate = new DateTime(1970, 1, 1).AddMilliseconds((long)(((JValue)ele.Value["Data"]).Value)); n.TypeValue = Enum.ParseToNotificationType(((JValue)ele.Value["Type"]).Value.ToString()); n.PublicationId = ele.Name.Split(':')[0]; n.Id = ele.Name.Split(':')[1]; notList.Add(n); } }
Głównym rdzeniem jest tutaj:
Notification n = JsonConvert.DeserializeObject<Notification>(ele.Value.ToString());
Nie chciałem się już bawić w jakieś convertery, więc ręcznie zamieniłem pola, które nie mogą być automatycznie zmapowane: AddedDate (rzutowanie daty z JS na DateTime), TypeValue (rzutowanie na enuma) oraz Id i PublicationId (trzeba rozdzielić oryginalne pole Name).
W taki oto sposób otrzymujemy piękną listę powiadomień w C#:
Na takiej liście można już spokojnie pracować
Powiadomienie - odczytywanie/usuwanie
Zostało jeszcze dodanie metody, które oznaczy powiadomienie jako odczytane i usunięte. Wystarczy tutaj dodać mały kawałek kodu:
var request = WebRequest.Create(Const.NotifyUrlRaw); request.Headers["Cookie"] = cookie; request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; request.Method = "POST"; byte[] form = Encoding.UTF8.GetBytes(string.Format("{1}%5B%5D={0}", id, method)); using (Stream os = await request.GetRequestStreamAsync()) { os.Write(form, 0, form.Length); } var resesponse = await request.GetResponseAsync();
Wysyłamy zapytanie pod adres: (pamiętając o ciasteczku)
http://www.dobreprogramy.pl/Providers/NotifyHelper.ashx
. Tym razem jednak dodajemy prostego forma, który zawiera id powiadomienia i typ akcji (method = markAsRead/deleteNotify) do wykonania na powiadomieniu (odczytanie/usunięcie). Oczywiście request uzupełniony jest o typ zawartości i metodę wysyłania zapytania.
W ten sposób stworzyliśmy pełnoprawny mechanizm do zarządzania powiadomieniami z portalu dobreprogramy.pl z poziomu kodu w C#.
Kolejne kroki?
Główny mechanizm do logowania i zarządzania powiadomieniami już mamy. Myślę, że w następnym tygodniu uda mi się złożyć coś, co nie będzie wyglądać dobrze :P , ale będzie działać w tle i pokazywać powiadomienia w Windows 10. Zobaczymy, czy uda się ten plan osiągnąć przed Wielkanocą i/lub maratonem w Dębnie ;)
Zapraszam do kolejnych odcinków z serii :)
Aktualne źródła można znaleźć na GitHub pod adresem: https://github.com/djfoxer/dp.notification