public static void FeedTimer( [TimerTrigger("0 */30 * * * *")] TimerInfo timer, [CosmosDB("FeedStateDatabase", "FeedItemsCollection", ConnectionStringSetting = "slackstackfeed_CosmosDB", Id = "state")] SlackStackState state, [Queue("slackfeed-items", Connection = "slackstackfeed_QueueStorage")] ICollector <QueuedJob> triggers, ILogger log) { if (state == null) { log.LogDebug("no state object."); } if (state.Feeds == null) { log.LogDebug("no state feeds."); return; } if (!state.Feeds.Any()) { log.LogDebug("state feeds empty."); } // NOTE: KeyValuePair has no built-in deconstructor, but tuple does. We could either write an extension method, or convert the Dictionary items. foreach ((var key, var feed) in state.Feeds.Select(x => (x.Key, x.Value))) { if (key == null || feed == null || feed.Teams == null || !feed.Teams.Any()) { log.LogDebug("no feed data."); continue; } if (TimerFunctions.IsFeedUpdated(feed, log)) { foreach (var team in feed.Teams) { triggers.Add(new QueuedJob { Feed = key, Team = team, }); } } feed.Processed = DateTime.Now; } state.Processed = DateTime.Now; }
public static void StackFeeder( [QueueTrigger("slackfeed-items", Connection = "slackstackfeed_QueueStorage")] QueuedJob job, [CosmosDB("FeedStateDatabase", "FeedItemsCollection", ConnectionStringSetting = "slackstackfeed_CosmosDB", Id = "state")] SlackStackState state, [CosmosDB("FeedStateDatabase", "FeedItemsCollection", ConnectionStringSetting = "slackstackfeed_CosmosDB", Id = "{team}")] SlackTeam team, DateTime processed, ILogger log) { if (!team.Active || !team.Subscriptions.ContainsKey(job.Feed)) { return; } var feed = state.Feeds[job.Feed]; try { var atom = XDocument.Load(feed.SourceUri.AbsoluteUri).Root; XNamespace bs = "http://www.w3.org/2005/Atom"; DateTime.TryParse(atom.Element(bs + "updated").Value, out var updated); if (updated > processed) { log.LogDebug("Feed recently updated, scanning for entries..."); using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })) { foreach (var entry in atom.Elements(bs + "entry")) { var id = entry.Element(bs + "id")?.Value ?? string.Empty; if (string.IsNullOrEmpty(id) || team.Posted.Contains(id)) { continue; } if (!DateTime.TryParse(entry.Element(bs + "published").Value, out var published)) { log.LogDebug("Invalid entry data -- missing published date."); continue; } var raw = entry.Element(bs + "summary").Value; var post = StackFunctions.Slackify(raw); var userLink = entry.Element(bs + "author").Element(bs + "uri").Value ?? string.Empty; var match = Regex.Match(userLink, $"https://{feed.Site}.stackexchange.com/users/([0-9]+)"); var user = default(StackUser); if (match.Success) { var userData = client.GetStringAsync($"https://api.stackexchange.com/2.2/users/{match.Groups[1].Value}?site={feed.Site}&key=MYe90O9jVj1YJI12XqK0BA((&filter=!)RwdAtHo34gjVfkkY.BZV4L(").Result; var stackData = JsonConvert.DeserializeObject <StackApiResponse <StackUser> >(userData); if (string.IsNullOrEmpty(stackData.ErrorMessage) && stackData.Items.Any()) { user = stackData.Items[0]; } } var attachments = new[] { new { mrkdwn_in = new string[] { "text", "fields" }, title = entry.Element(bs + "title")?.Value ?? string.Empty, title_link = id, text = post, thumb_url = feed.LogoUri.AbsoluteUri, author_name = user?.DisplayName ?? string.Empty, author_link = user?.Link?.AbsoluteUri ?? string.Empty, author_icon = user?.ProfileImage?.AbsoluteUri ?? string.Empty, fields = entry.Elements(bs + "category").Select(tag => new { value = $"`{tag.Attribute("term").Value}`", @short = true }), ts = new DateTimeOffset(published).ToUnixTimeSeconds() } }; foreach (var channel in team.Subscriptions[job.Feed]) { var data = new FormUrlEncodedContent(new Dictionary <string, string> { { "as_user", "false" }, { "username", "Slack Stack Feed" }, { "token", team.BotToken }, { "channel", channel }, { "text", $"New Question Posted to: <{feed.Uri}| *{feed.Name}*>." }, { "unfurl_links", "false" }, { "unfurl_media", "false" }, { "attachments", JsonConvert.SerializeObject(attachments, new JsonSerializerSettings { Formatting = Formatting.Indented }) } }); var response = client.PostAsync("https://slack.com/api/chat.postMessage", data).Result; response.EnsureSuccessStatusCode(); } team.Posted.Add(id); } } } // Only keep the last 30 posts we sent, to avoid filling up DocumentDB storage. team.Posted = team.Posted.Skip(team.Posted.Count - 30).ToList(); } catch (Exception ex) { log.LogDebug(ex.ToString()); } }