Właściwie odkąd założyłem bloga, chodził za mną temat zautomatyzowanych, cotygodniowych postów dotyczących najciekawszych treści, na które natknąłem się danego tygodnia w Internecie. Nadałem temu nazwę “Niedzielnik Kokosa” bo postanowiłem publikować je w niedzielę rano – doskonały moment na spokojne przejrzenie ciekawych treści. Ponieważ na co dzień mam zwyczaj odkładania ciekawych linków do Instapaper – źródło danych mam gotowe. Pozostawała tylko kwestia automatyzacji. Dość długo czaiłem się na serwisy pokroju IFTTT albo Zapier. Jednak z każdą kolejną próbą odbijałem się od nich, ponieważ potrzebna mi automatyzacja (choć prosta) nie wpisywała mi się w to co te serwisy oferują.
To czego potrzebuję to następującą sekwencję zdarzeń (wykonywaną cyklicznie, np. rano co niedzielę):
- Pobierz z mojego konta Instapaper linki zapamiętane przeze mnie w ciągu ostatnich 7 dni – maksymalnie 10
- Opublikuj na blogu post zgodnie z pewnym szablonem, wypisującym te linki
- Opublikuj na Twitterze i stronie Facebooka informację o nowym poście
- Idealnie – poinformuj mnie mailem, że się udało
Funkcjonalność taką udało mi się w bardzo przyjemny i szybki sposób zaimplementować ostatnio za pomocą kombinacji dwóch świetnych usług Azure:
- Azure Logic Apps – pozwala na definiowanie sekwencji akcji (włączając to w pętle, warunki itp.) składających się z wielu dostępnych “z pudełka” komponentów. Są m.in. dostępne komponenty integrujące z pocztą, Facebookiem, Twitterem, Instapaper (z metodami typu “pobierz wszystkie polubione treści”) i wieloma, wieloma innymi. Przepływy (choć angielski workflow dużo lepiej tu pasuje) definiować można za pomocą JSONa lub graficznie za pomocą zaskakująco przyjemnego edytora.
- Azure Functions – pozwala na zdefiniowanie funkcji (napisanych w C#, F#, Node.js), które w duchu “serverless” hostowane są w Azure i możemy ich reużywać za pomocą HTTP. Co dla mnie ważne, jednym z klocków w Azure Logic App może być właśnie stworzona przez nas Azure Function.
Tak doprowadziło mnie to do zdefiniowania w Logic App następującego przepływu:

Jak dla mnie bomba, zwłaszcza że tworzenie go było szybkie i napotkałem zaskakująco mało problemów. Sam diagram jest dość oczywisty. Klikając na każdy z bloczków można podać dodatkowe parametry, tutaj większość zwinąłem dla przejrzystości.
Głównym triggerem jest akcja Recurrence, uruchamiająca przepływ co siedem dni począwszy od najbliższej niedzieli:

Następna akcja to integrator z Instapaper, który po prostu pobiera polubione treści z mojego konta Instapaper. Postanowiłem użyć mechanizmu polubienia by świadomie wybierać co spośród wszystkich zapamiętanych treści ma szansę się pojawić w Niedzielniku. Następnie pierwsza z Azure Functions – ProcessLikes. Filtruje ona otrzymanego z Instapaper JSONa. Cały jej kod wygląda następująco:
#r "System.Net.Http"
#r "Newtonsoft.Json"
open System.Net
open System.Net.Http
open Newtonsoft.Json
type Bookmark = {
hash: string
description: string
bookmark_id: string
private_source: string
title: string
url: string
progress_timestamp: string
time: DateTime
progress: string
starred: bool
``type``:string
}
let Run(req: HttpRequestMessage, log: TraceWriter) =
async {
let days = match req.GetQueryNameValuePairs()
|> Seq.tryPick(fun p -> if p.Key = "days" then Some(p) else None) with
| Some p -> p.Value
| None -> "7"
|> float
let! jsonContent = req.Content.ReadAsStringAsync() |> Async.AwaitTask
try
let bookmarks = JsonConvert.DeserializeObject<Bookmark[]>(jsonContent)
let now = DateTime.Now.AddDays(-1.0*days)
let results = bookmarks
|> Seq.filter (fun b -> b.time > now)
|> Seq.truncate 10
return req.CreateResponse(HttpStatusCode.OK, results)
with _ ->
return req.CreateResponse(HttpStatusCode.BadRequest)
} |> Async.StartAsTask
Kolejna funkcja – PublishLikes – używa paczki MetaWeblogSharp by poprzez API MetaWeblog (o dziwo wspierane nawet przez BlogEngine.NET) stworzyć nowy wpis:
let date = DateTime.Now
let title = sprintf "Niedzielnik Kokosa - %s" (date.ToString("dd-MM-yyyy"))
let data = { Title = title; Bookmarks = bookmarks }
let lines = [
@"<div style=""text-align: center;""><img src=""/image.axd?picture=%2f2017%2f03%2fniedzielnik_logo.jpg""></div>";
@"<p>Internet codziennie zalewa nas informacjami. Jeśli nie masz czasu tego wszystkiego ogarnąć, oto garść wybranych przeze mnie, najciekawszych treści, na które natknąłem się w tracie ostatniego tygodnia:<br /></p>";
sprintf @"<ul>%s</ul>" (Seq.fold (fun s (x : Bookmark) -> (s + sprintf @"<li><a href=""%s"">%s</a> - %s</li>" x.url x.title x.description)) String.Empty data.Bookmarks)
]
let html = String.concat "" lines
let conn = new MetaWeblogSharp.BlogConnectionInfo(
"http://blog.kokosa.net",
"http://blog.kokosa.net/metaweblog.axd",
"BlogKokosa",
user,
password)
let client = new MetaWeblogSharp.Client(conn)
let newPost = new PostInfo(Title = title,
DateCreated = new Nullable<DateTime>(date),
Description = html)
let postId = client.NewPost(newPost, [| "Niedzielnik"; "Ciekawostki" |], true)
let postDesc = client.GetPost(postId)
let result = { PostId = postDesc.PostID; Url = postDesc.Link }
Z braku czasu użyłem dość paskudnego podejścia z użyciem sprintf. Niestety z niewiadomych mi jeszcze przyczyn nie udało się użyć np. DotLiquid – paczka nie chciała się zaimportować z nugeta.
Dalej jest jeszcze prościej. Jeśli odpowiedź z PublishLikes jest 200 OK, to za pomocą integracji z Bufferem kolejkuję publikację informacji o nowym Niedzielniku na Twitterze i na Facebooku (link do opublikowanego posta dostaję z funkcji PublishLikes):

Następnie dla każdego udanej publikacji wysyłam maila podsumowującego całą akcję. Między publikacją posta, a użyciem Buffera, dodałem krótkie opóźnienie rzędu 30 sekund bo nie ufam BlogEngine.NET i wolę dać mu chwilę na faktyczne opublikowanie posta.
I to tyle! Coś na co czaiłem się od miesięcy udało mi się wyklikać w kilka godzin zaczynając od zera ze znajomością obu usług Azure. Najwięcej czasu i tak poświęciłem na fragmenty w F# bo wciąż go dopiero opanowuję. I tak wiem, nadal brzydko ten kod wygląda 
Tak czy inaczej, teraz pozostaje już tylko czekać na niedzielę 26 marca i o godzinie 10 przekonać się czy pierwszy Niedzielnik faktycznie się ukaże!
PS. To czym chciałbym się jeszcze pobawić to lepszym logowaniem i monitorowaniem tego procesu więc CDN.