コード例 #1
0
 private HeroCard RenderLinkHeroCard(RedditLinkModel post)
 {
     return(new HeroCard
     {
         Title = post.Title,
         Text = post.Subreddit,
         Images = new List <CardImage>
         {
             new CardImage(post.Thumbnail),
         },
     });
 }
コード例 #2
0
        private async Task <MessagingExtensionResponse> GetRedditPostAsync(string postLink)
        {
            try
            {
                // Execute the domain logic to get the reddit link
                RedditLinkModel redditLink = await this.redditHttpClient.GetLinkAsync(postLink);

                var preview = new MessagingExtensionAttachment(
                    contentType: HeroCard.ContentType,
                    contentUrl: null,
                    content: this.RenderLinkHeroCard(redditLink));

                return(new MessagingExtensionResponse
                {
                    ComposeExtension = new MessagingExtensionResult
                    {
                        Type = "result",
                        AttachmentLayout = AttachmentLayoutTypes.List,
                        Attachments = new List <MessagingExtensionAttachment>()
                        {
                            new MessagingExtensionAttachment
                            {
                                ContentType = AdaptiveCard.ContentType,
                                Content = this.RenderLinkAdaptiveCard(redditLink),
                                Preview = preview,
                            },
                        },
                    },
                });
            }
#pragma warning disable CA1031 // This is a top-level handler and should avoid throwing exceptions.
            catch (Exception ex)
#pragma warning restore CA1031
            {
                this.logger.LogError(ex, "Failed to get reddit post");
                return(null);
            }
        }
コード例 #3
0
        private AdaptiveCard RenderLinkAdaptiveCard(RedditLinkModel post)
        {
            var titleBlock = new AdaptiveTextBlock
            {
                Text     = $"[{post.Title}]({post.Link})",
                Size     = AdaptiveTextSize.Large,
                Wrap     = true,
                MaxLines = 2,
            };

            var upvoteColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Auto,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = this.localizer.GetString("↑ {0}", post.Score),
                    },
                },
            };

            var commentColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Auto,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = this.localizer.GetString(
                            "🗨️ [{0}](https://www.reddit.com/r/{1}/comments/{2})",
                            post.NumComments,
                            post.Subreddit,
                            post.Id),
                    },
                },
            };

            var subredditColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Stretch,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = $"[/r/{post.Subreddit}](https://www.reddit.com/r/{post.Subreddit})",
                        HorizontalAlignment = AdaptiveHorizontalAlignment.Right,
                        Size   = AdaptiveTextSize.Default,
                        Weight = AdaptiveTextWeight.Bolder,
                    },
                },
            };

            var infoColumns = new AdaptiveColumnSet
            {
                Columns = new List <AdaptiveColumn>
                {
                    upvoteColumn,
                    commentColumn,
                    subredditColumn,
                },
            };

            AdaptiveElement preview;

            if (post.Thumbnail != null)
            {
                preview = new AdaptiveImage
                {
                    Url = new Uri(post.Thumbnail),
                    HorizontalAlignment = AdaptiveHorizontalAlignment.Center,
                    Separator           = true,
                };
            }
            else
            {
                preview = new AdaptiveTextBlock
                {
                    Text      = post.SelfText ?? this.localizer.GetString("Preview Not Available"),
                    Wrap      = true,
                    Separator = true,
                };
            }

            var bottomLeftColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Auto,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text   = this.localizer.GetString("Posted by [/u/{0}](https://www.reddit.com/u/{0})", post.Author),
                        Size   = AdaptiveTextSize.Small,
                        Weight = AdaptiveTextWeight.Lighter,
                    },
                },
            };

            var createdText       = $"{{{{DATE({post.Created.DateTime.ToString("yyyy-MM-ddThh:mm:ssZ", CultureInfo.InvariantCulture)})}}}}";
            var bottomRightColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Stretch,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = createdText,
                        HorizontalAlignment = AdaptiveHorizontalAlignment.Right,
                        Size   = AdaptiveTextSize.Small,
                        Weight = AdaptiveTextWeight.Lighter,
                    },
                },
            };

            var bottomColumns = new AdaptiveColumnSet
            {
                Columns = new List <AdaptiveColumn>
                {
                    bottomLeftColumn,
                    bottomRightColumn,
                },
            };

            var card = new AdaptiveCard("1.0")
            {
                Body = new List <AdaptiveElement>
                {
                    titleBlock,
                    infoColumns,
                    preview,
                    bottomColumns,
                },
                Actions = new List <AdaptiveAction>
                {
                    new AdaptiveOpenUrlAction
                    {
                        Title = this.localizer.GetString("Open in Reddit"),
                        Url   = new Uri(post.Link),
                    },
                },
            };

            return(card);
        }
コード例 #4
0
        /// <summary>
        /// Get the information about a post.
        /// </summary>
        /// <param name="authToken">The reddit auth token.</param>
        /// <param name="postLink">The url to the reddit post.</param>
        /// <returns>A Task resolving to the reddit link model for the post.</returns>
        /// <exception cref="RedditUnauthorizedException"> Thrown when the call to Reddit API was unauthorized.</exception>
        /// <exception cref="RedditRequestException"> Thrown when the call to Reddit API was unsuccessful.</exception>
        /// <exception cref="ArgumentException"> Thrown when post link is malformed or not for a post.</exception>
        /// <remarks>
        /// See: https://www.reddit.com/dev/api#GET_api_info .
        /// </remarks>
        public async Task <RedditLinkModel> GetLinkAsync(string authToken, string postLink)
        {
            if (string.IsNullOrEmpty(authToken))
            {
                // Throw an exception to cause the activity handler to prompt the user for login.
                throw new RedditUnauthorizedException($"{nameof(authToken)} is null");
            }

            Match  m         = RedditHttpClient.ParameterExtrator.Matches(postLink)?[0];
            string subreddit = m?.Groups?["subreddit"]?.Value;
            string id        = m?.Groups?["id"]?.Value;

            if (string.IsNullOrEmpty(subreddit))
            {
                throw new ArgumentException($"Unable to find subreddit in url: ${postLink}", nameof(postLink));
            }

            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentException($"Unable to find 'thing-id' in url: ${postLink}", nameof(postLink));
            }

            this.logger.LogInformation($"Extracted id: {id}, subreddit: {subreddit}");

            string stringContent = string.Empty;

            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"https://oauth.reddit.com/r/{subreddit}/api/info?id=t3_{id}"))
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
                request.Headers.UserAgent.Add(new ProductInfoHeaderValue(this.options.Value.ClientUserAgent, "1.0"));

                HttpResponseMessage result = await this.httpClient.SendAsync(request).ConfigureAwait(false);

                if (result.StatusCode == HttpStatusCode.Unauthorized)
                {
                    // Throw an exception to cause the activity handler to prompt the user for login.
                    throw new RedditUnauthorizedException(result.ReasonPhrase);
                }

                if (!result.IsSuccessStatusCode)
                {
                    throw new RedditRequestException(result.ReasonPhrase);
                }

                stringContent = await result.Content
                                .ReadAsStringAsync()
                                .ConfigureAwait(false);
            }

            JObject root      = JObject.Parse(stringContent);
            JToken  firstPost = root?["data"]?["children"]?[0]?["data"];

            // Preview images are html encoded urls.
            string thumbnailUrl = WebUtility.HtmlDecode((string)firstPost?["preview"]?["images"]?[0]?["source"]?["url"]);

            RedditLinkModel redditLink = new RedditLinkModel
            {
                Id          = (string)firstPost?["id"],
                Title       = (string)firstPost?["title"],
                Score       = (int)firstPost?["score"],
                Subreddit   = (string)firstPost?["subreddit"],
                Thumbnail   = thumbnailUrl,
                SelfText    = (string)firstPost?["selftext"],
                NumComments = (int)firstPost?["num_comments"],
                Link        = "https://www.reddit.com" + (string)firstPost?["permalink"],
                Author      = (string)firstPost?["author"],
                Created     = DateTimeOffset.FromUnixTimeSeconds((long)firstPost?["created_utc"]),
            };

            return(redditLink);
        }
コード例 #5
0
        /// <summary>
        /// Get the login or preview response for the given link.
        /// </summary>
        /// <param name="turnContext">The turn context.</param>
        /// <param name="query">The matched url.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A Task resolving to either a login card or </returns>
        /// <remarks>
        /// For more information on Link Unfurling see the documentation
        /// https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=dotnet
        ///
        /// This method also implements messaging extension authentication to get the reddit API token for the user.
        /// https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/add-authentication
        /// </remarks>
        protected override async Task <MessagingExtensionResponse> OnTeamsAppBasedLinkQueryAsync(
            ITurnContext <IInvokeActivity> turnContext,
            AppBasedLinkQuery query,
            CancellationToken cancellationToken = default)
        {
            turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
            query       = query ?? throw new ArgumentNullException(nameof(query));

            IUserTokenProvider tokenProvider = turnContext.Adapter as IUserTokenProvider;

            // Get the magic code out of the request for when the login flow is completed.
            // https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0#securing-the-sign-in-url
            string magicCode = (turnContext.Activity?.Value as JObject)?.Value <string>("state");

            // Get the token from the Azure Bot Framework Token Service to handle token storage
            // https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0#about-the-bot-framework-token-service
            var tokenResponse = await tokenProvider
                                ?.GetUserTokenAsync(
                turnContext : turnContext,
                connectionName : this.options.Value.BotFrameworkConnectionName,
                magicCode : magicCode,
                cancellationToken : cancellationToken);

            try
            {
                // Execute the domain logic to get the reddit link
                RedditLinkModel redditLink = await this.redditHttpClient.GetLinkAsync(tokenResponse?.Token, query.Url);

                var preview = new MessagingExtensionAttachment(
                    contentType: HeroCard.ContentType,
                    contentUrl: null,
                    content: this.RenderLinkHeroCard(redditLink));

                return(new MessagingExtensionResponse
                {
                    ComposeExtension = new MessagingExtensionResult
                    {
                        Type = "result",
                        AttachmentLayout = AttachmentLayoutTypes.List,
                        Attachments = new List <MessagingExtensionAttachment>()
                        {
                            new MessagingExtensionAttachment
                            {
                                ContentType = AdaptiveCard.ContentType,
                                Content = this.RenderLinkAdaptiveCard(redditLink),
                                Preview = preview,
                            },
                        },
                    },
                });
            }
            catch (RedditUnauthorizedException)
            {
                this.logger.LogInformation("Attempt to fetch post resulted in unauthorized, triggering log-in flow");

                // "log out" the user, so log-in gets a new token.
                await tokenProvider.SignOutUserAsync(
                    turnContext : turnContext,
                    connectionName : this.options.Value.BotFrameworkConnectionName,
                    cancellationToken : cancellationToken);

                return(await this
                       .GetAuthenticationMessagingExtensionResponseAsync(turnContext, cancellationToken)
                       .ConfigureAwait(false));
            }
#pragma warning disable CA1031
            catch (Exception ex)
#pragma warning restore CA1031
            {
                this.logger.LogError(ex, "Failed to get reddit post");
                return(null);
            }
        }
コード例 #6
0
        private AdaptiveCard RenderLinkAdaptiveCard(RedditLinkModel post)
        {
            var titleBlock = new AdaptiveTextBlock
            {
                Text     = post.Title,
                Size     = AdaptiveTextSize.Large,
                Wrap     = true,
                MaxLines = 2,
            };

            var upvoteColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Auto,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = $"↑ {post.Score}",
                    },
                },
            };

            var commentColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Auto,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = $"🗨️ {post.NumComments}",
                    },
                },
            };

            var subredditColumn = new AdaptiveColumn
            {
                Width = AdaptiveColumnWidth.Stretch,
                Items = new List <AdaptiveElement>
                {
                    new AdaptiveTextBlock
                    {
                        Text = $"/r/{post.Subreddit}",
                        HorizontalAlignment = AdaptiveHorizontalAlignment.Right,
                        Size   = AdaptiveTextSize.Default,
                        Weight = AdaptiveTextWeight.Bolder,
                    },
                },
            };

            var infoColumns = new AdaptiveColumnSet
            {
                Columns = new List <AdaptiveColumn>
                {
                    upvoteColumn,
                    commentColumn,
                    subredditColumn,
                },
            };

            AdaptiveElement preview;

            if (post.Thumbnail != null)
            {
                preview = new AdaptiveImage
                {
                    Url = new Uri(post.Thumbnail),
                    HorizontalAlignment = AdaptiveHorizontalAlignment.Center,
                    Separator           = true,
                };
            }
            else
            {
                preview = new AdaptiveTextBlock
                {
                    Text      = post.SelfText ?? this.localizer.GetString("Preview not available"),
                    Wrap      = true,
                    Separator = true,
                };
            }

            return(new AdaptiveCard("1.0")
            {
                Body = new List <AdaptiveElement>
                {
                    titleBlock,
                    infoColumns,
                    preview,
                },
                Actions = new List <AdaptiveAction>
                {
                    new AdaptiveOpenUrlAction
                    {
                        Title = this.localizer.GetString("Open in Reddit"),
                        Url = new Uri(post.Link),
                    },
                },
            });
        }