Example #1
0
        public static IMessageActivity NowPlayingStation(ConversationInfo info, Station station)
        {
            var heroCard = NewHeroCard();

            var images = station.Album?.Images ?? station.Playlist?.Images;

            if (images != null && images.Any())
            {
                heroCard.Images.Add(
                    new CardImage
                {
                    Url = images[0].Url,
                    Alt = station.Name
                });
            }

            string messageText = info.IsGroup
                ? $"@{info.FromName} is now playing \"{station.Name}\" {station.HashtagHandle} 📢"
                : $"Now playing \"{station.Name}\". Friends can type `\"join @{RingoBotHelper.ToHashtag(info.FromName)}\"` to join in! 🎉";

            if (info.IsGroup)
            {
                heroCard.Buttons.Add(
                    new CardAction
                {
                    Title = $"Join {station.HashtagHandle}",
                    Value = $"join {station.HashtagHandle}",
                    Type  = ActionTypes.ImBack,
                });
            }

            return(MessageAttachment(heroCard, messageText));
        }
Example #2
0
        public async Task <TokenResponse> ValidateMagicNumber(
            ITurnContext turnContext,
            string text,
            CancellationToken cancellationToken)
        {
            string channelUserId = await _userStateData.GetUserIdFromStateToken(text);

            if (channelUserId == RingoBotHelper.ChannelUserId(turnContext))
            {
                await turnContext.SendActivityAsync(
                    $"Magic Number OK. Ringo is authorized to play Spotify. Ready to rock! 😎",
                    cancellationToken : cancellationToken);

                await _userData.SetTokenValidated(channelUserId, text);

                return(await GetAccessToken(channelUserId));
            }

            _logger.LogWarning($"Invalid Magic Number \"{text}\" for channelUserId {RingoBotHelper.ChannelUserId(turnContext)}");
            await turnContext.SendActivityAsync(
                $"Magic Number is invalid or has expired. Please try again 🤔",
                cancellationToken : cancellationToken);

            return(null);
        }
Example #3
0
        public async Task <Station> GetChannelStation(ConversationInfo info, string conversationName = null)
        {
            if (!info.IsGroup && string.IsNullOrEmpty(conversationName))
            {
                throw new ArgumentException("Must provide conversationName if not in group chat", nameof(conversationName));
            }

            return(await _stationData.GetStation(Station.EncodeIds(info, RingoBotHelper.ToHashtag(conversationName))));
        }
Example #4
0
 private static async Task SendWelcomeMessageAsync(ITurnContext turnContext, CancellationToken cancellationToken)
 {
     foreach (var member in turnContext.Activity.MembersAdded)
     {
         if (member.Id != turnContext.Activity.Recipient.Id)
         {
             await turnContext.SendActivityAsync(
                 RingoBotMessages.Welcome(RingoBotHelper.NormalizedConversationInfo(turnContext), member.Name),
                 cancellationToken : cancellationToken);
         }
     }
 }
Example #5
0
        private async Task CreateChannelUsers(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            var info = RingoBotHelper.NormalizedConversationInfo(turnContext);

            foreach (var member in turnContext.Activity.MembersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await _ringoService.CreateUserIfNotExists(
                        info,
                        member.Id,
                        member.Name);
                }
            }
        }
Example #6
0
        public static IMessageActivity SpotifyError(ConversationInfo info, SpotifyApiErrorException ex, string command)
        {
            var heroCard = NewHeroCard();

            heroCard.Buttons.Add(
                new CardAction
            {
                Title = $"Try again",
                Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}{command}",
                Type  = ActionTypes.ImBack,
            });

            return(MessageAttachment(
                       heroCard,
                       $"Ringo can't talk to Spotify right now 🤔 Please try again in a minute. Spotify says: \"{ex.Message}\""));
        }
Example #7
0
        public static IMessageActivity NotPlayingAnything(ConversationInfo info)
        {
            var heroCard = NewHeroCard();

            heroCard.Buttons.Add(
                new CardAction
            {
                Title = "Spotify is playing - Try again!",
                Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}play",
                Type  = ActionTypes.ImBack,
            });

            return(MessageAttachment(
                       heroCard,
                       "You are not currently playing anything 🤔 Play some music in Spotify and try again."));
        }
Example #8
0
        public static IMessageActivity NowPlayingNoContext(ConversationInfo info)
        {
            var heroCard = NewHeroCard();

            heroCard.Buttons.Add(
                new CardAction
            {
                Title = "Try again!",
                Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}play",
                Type  = ActionTypes.ImBack,
            });

            return(MessageAttachment(
                       heroCard,
                       "Unfortunately Ringo does not support playing single Tracks or endless Playlists (including Daily Mixes) 😢 Play a Playlist or an Album in Spotify and try again."));
        }
Example #9
0
        public static IMessageActivity StationNoLongerPlaying(ConversationInfo info, Station station)
        {
            var    heroCard = NewHeroCard();
            string uri      = station.Album?.Uri ?? station.Playlist?.Uri;

            heroCard.Buttons.Add(
                new CardAction
            {
                Title = $"Play {station.Name}",
                Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}play {uri}",
                Type  = ActionTypes.ImBack,
            });

            return(MessageAttachment(
                       heroCard,
                       $"{station.HashtagHandle} is no longer playing. Would you like to play \"{station.Name}\"?"));
        }
Example #10
0
        public static IMessageActivity NowPlayingNotSupported(ConversationInfo info, string type)
        {
            var heroCard = NewHeroCard();

            heroCard.Buttons.Add(
                new CardAction
            {
                Title = "Try again!",
                Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}play",
                Type  = ActionTypes.ImBack,
            });

            return(MessageAttachment(
                       heroCard,
                       type == "artist"
                    ? "Unfortunately Ringo does not currently support playing Artists in Spotify 😢 Play a Playlist or an Album and try again."
                    : $"Ringo does not currently support playing a {type} in Spotify 😢 Play a Playlist or an Album and try again."));
        }
Example #11
0
        /// <summary>
        /// Encodes the Id and Partition Key into a format suitable for a <see cref="CosmosEntity"/>
        /// </summary>
        /// <param name="hashtag">Hashtag</param>
        /// <param name="info">Conversation Info</param>
        /// <param name="username">Username for User Station. Must be null for Conversation Station</param>
        internal static (string id, string pk) EncodeIds(ConversationInfo info, string hashtag, string username = null)
        {
            if (!string.IsNullOrEmpty(username))
            {
                // User Station
                // ringo:{channel_id}:{channel_team_id.ToLower()}:station:user:{lower_word(user_name)}
                string id = $"{RingoBotHelper.RingoBotName}:{info.ChannelId}:{info.ChannelTeamId}:station:user:{RingoBotHelper.LowerWord(username)}"
                            .ToLower();
                return(id, id);
            }

            // Conversation Station
            // ringo:{channel_id}:{channel_team_id.ToLower()}:station:conversation:{lower_word(conversation_name)}[:hashtag:{lower_word(hashtag)}]
            string pk = $"{RingoBotHelper.RingoBotName}:{info.ChannelId}:{info.ChannelTeamId}:station:conversation:{RingoBotHelper.LowerWord(info.ConversationName)}"
                        .ToLower();

            return($"{pk}:hashtag:{RingoBotHelper.LowerWord(hashtag)}", pk);
        }
Example #12
0
        /// <summary>
        /// Changes the station owner to the current conversation From user
        /// </summary>
        public async Task ChangeStationOwner(Station station, ConversationInfo info)
        {
            var    oldOwner    = station.Owner;
            string ownerUserId = RingoBotHelper.ChannelUserId(info);

            // get user
            var ownerUser = await _userData.GetUser(ownerUserId);

            station.Owner = ownerUser;
            AddOwnerToListeners(station, ownerUser, ownerUserId);

            // TODO: if a user station, change the hashtag
            if (station.IsUserStation)
            {
                station.Hashtag = RingoBotHelper.ToHashtag(info.FromName);
            }

            await _stationData.ReplaceStation(station);

            _logger.LogInformation($"RingoBotNet.Services.RingoService.ChangeStationOwner: Station {station} has changed owner from {oldOwner} to {ownerUser}.");
        }
Example #13
0
        public async Task <Station> CreateUserStation(
            ConversationInfo info,
            Album album       = null,
            Playlist playlist = null)
        {
            string name        = album?.Name ?? playlist?.Name;
            string ownerUserId = RingoBotHelper.ChannelUserId(info);

            // get user
            var ownerUser = await _userData.GetUser(ownerUserId);

            string hashtag = RingoBotHelper.ToHashtag(ownerUser.Username);

            // get station
            var stationIds = Station.EncodeIds(info, hashtag, ownerUser.Username);
            var station    = await _stationData.GetStation(stationIds);

            // save station
            if (station == null)
            {
                // new station
                station = new Station(info, hashtag, album, playlist, ownerUser, ownerUser.Username);
                await _stationData.CreateStation(station);
            }
            else
            {
                // update station context and owner
                station.Name     = name;
                station.Owner    = ownerUser;
                station.Album    = album;
                station.Playlist = playlist;
                station.Hashtag  = hashtag;

                AddOwnerToListeners(station, ownerUser, ownerUserId);

                await _stationData.ReplaceStation(station);
            }

            return(station);
        }
Example #14
0
        public static IMessageActivity CouldNotFindStation(ConversationInfo info, string query)
        {
            var heroCard = NewHeroCard();

            string messageText = string.IsNullOrEmpty(query)
                ? "Could not find a Station to join 🤔"
                : $"Could not find Station {query} 🤔";

            if (info.IsGroup)
            {
                messageText += $" Would you like to start a Station? Play some music in Spotify. Then click/tap **Play**";

                heroCard.Buttons.Add(
                    new CardAction
                {
                    Title = "Play",
                    Value = $"{RingoBotHelper.RingoHandleIfGroupChat(info)}play",
                    Type  = ActionTypes.ImBack,
                });
            }

            return(MessageAttachment(heroCard, messageText));
        }
Example #15
0
        public async Task <TokenResponse> Authorize(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            var    info   = RingoBotHelper.NormalizedConversationInfo(turnContext);
            string userId = RingoBotHelper.ChannelUserId(turnContext);

            TokenResponse token = await GetAccessToken(userId);

            if (token != null)
            {
                return(token);
            }

            // User is not authorized by Spotify
            if (BotHelper.IsGroup(turnContext))
            {
                // Don't start authorisation dance in Group chat
                await turnContext.SendActivityAsync(
                    $"Before you play or join with Ringo you need to authorize Spotify. DM (direct message) the word `\"{RingoBotCommands.AuthCommand[0]}\"` to @{info.BotName} to continue.",
                    cancellationToken : cancellationToken);

                return(null);
            }

            _logger.LogInformation($"Requesting Spotify Authorization for UserId {userId}");

            await _userData.CreateUserIfNotExists(info);

            // create state token
            string state = $"{RingoBotStatePrefix}{Guid.NewGuid().ToString("N")}".ToLower();

            // validate state token
            if (!RingoBotStateRegex.IsMatch(state))
            {
                throw new InvalidOperationException("Generated state token does not match RingoBotStateRegex");
            }

            // save state token
            await _userStateData.SaveStateToken(userId, state);

            await _userData.SaveStateToken(userId, state);

            // get URL
            string url = UserAccountsService.AuthorizeUrl(
                state,
                new[] { "user-read-playback-state", "user-modify-playback-state" },
                _config["SpotifyApiClientId"],
                _config["SpotifyAuthRedirectUri"]);

            var message = MessageFactory.Attachment(
                new Attachment
            {
                ContentType = HeroCard.ContentType,
                Content     = new HeroCard
                {
                    Text    = "Authorize Ringo bot to use your Spotify account",
                    Buttons = new[]
                    {
                        new CardAction
                        {
                            Title = "Authorize",
                            Text  = "Click to Authorize. (Opens in your browser)",
                            Value = url,
                            Type  = ActionTypes.OpenUrl,
                        },
                    },
                },
            },
                text: "To play music, Ringo needs to be authorized to use your Spotify Account.");

            await turnContext.SendActivityAsync(message, cancellationToken);

            return(null);
        }
Example #16
0
        public async Task Join(
            ITurnContext turnContext,
            UserProfile userProfile,
            string query,
            CancellationToken cancellationToken,
            TokenResponse token = null)
        {
            ConversationInfo info = RingoBotHelper.NormalizedConversationInfo(turnContext);

            if (string.IsNullOrEmpty(query) || (!query.StartsWith('#') && !query.StartsWith('@')))
            {
                // must specify #channel or @username in DM to join a Station
                await turnContext.SendActivityAsync(
                    RingoBotMessages.JoinWhat(),
                    cancellationToken : cancellationToken);

                return;
            }

            Station station = null;

            //if (string.IsNullOrEmpty(query))
            //{
            //    // just `join` in group chat: Play the current channel station
            //    station = await _ringoService.GetChannelStation(info);
            //}
            //else if (query.StartsWith('@'))
            if (query.StartsWith('@'))
            {
                // user station
                station = await _ringoService.GetUserStation(info, query.Substring(1));
            }
            else if (query.StartsWith('#'))
            {
                // could either be a channel station or hashtag station
                station = await _ringoService.GetChannelStation(info, query.Substring(1));
            }

            //if (station == null && query.StartsWith('@'))
            //{
            //    await CreateAndJoinUserStation(turnContext, query, cancellationToken);
            //    return;
            //}

            if (station == null)
            {
                await turnContext.SendActivityAsync(
                    RingoBotMessages.CouldNotFindStation(info, query),
                    cancellationToken : cancellationToken);

                return;
            }

            TokenResponse stationToken = await _authService.GetAccessToken(station.Owner.Id);

            // get user and their token
            token = token ?? await _authService.Authorize(turnContext, cancellationToken);

            if (token == null)
            {
                // resume after auth
                userProfile.ResumeAfterAuthorizationWith = (JoinCommand[0], query);
                return;
            }

            if (!await IsDeviceActive(
                    turnContext,
                    token.Token,
                    $"{RingoBotHelper.RingoHandleIfGroupChat(turnContext)}join {query}",
                    cancellationToken,
                    station.Playlist))
            {
                return;
            }

            // Join
            try
            {
                if (await _spotifyService.JoinPlaylist(
                        query,
                        token.Token,
                        station,
                        stationToken.Token,
                        cancellationToken))
                {
                    await turnContext.SendActivityAsync(
                        RingoBotMessages.UserHasJoined(info, station),
                        cancellationToken : cancellationToken);
                }
                else
                {
                    // station no longer playing. Start it up!
                    //await turnContext.SendActivityAsync(
                    //    RingoBotMessages.StationNoLongerPlaying(info, station),
                    //    cancellationToken: cancellationToken);

                    _logger.LogInformation($"RingobotNet.RingoBotCommands: User {station.Owner.Username} is no longer playing station \"{station.Name}\". Starting station up again with {info.FromName} as owner.");

                    switch (station.SpotifyContextType)
                    {
                    case Station.SpotifyContextTypeAlbum:
                        await _spotifyService.PlayAlbum(station.SpotifyUri, token.Token, cancellationToken);

                        break;

                    case Station.SpotifyContextTypePlaylist:
                        await _spotifyService.PlayPlaylist(station.SpotifyUri, token.Token, cancellationToken);

                        break;

                    default:
                        throw new NotSupportedException($"{station.SpotifyContextType} is not a supported SpotifyContextType");
                    }

                    await _ringoService.ChangeStationOwner(station, info);

                    await turnContext.SendActivityAsync(RingoBotMessages.NowPlayingStation(info, station), cancellationToken);
                }
            }
            catch (SpotifyApi.NetCore.SpotifyApiErrorException ex)
            {
                await turnContext.SendActivityAsync(RingoBotMessages.SpotifyError(info, ex, $"join {query}"), cancellationToken : cancellationToken);
            }
        }
Example #17
0
        public async Task Play(
            ITurnContext turnContext,
            UserProfile userProfile,
            string query,
            CancellationToken cancellationToken,
            TokenResponse token = null)
        {
            var info = RingoBotHelper.NormalizedConversationInfo(turnContext);

            // User authorised?
            token = token ?? await _authService.Authorize(turnContext, cancellationToken);

            if (token == null)
            {
                // resume after auth
                userProfile.ResumeAfterAuthorizationWith = (PlayCommand[0], query);
                return;
            }

            // Device active?
            if (!await IsDeviceActive(
                    turnContext,
                    token.Token,
                    $"{RingoBotHelper.RingoHandleIfGroupChat(turnContext)}play {query}",
                    cancellationToken))
            {
                return;
            }

            Station station = null;

            if (string.IsNullOrEmpty(query))
            {
                // Play whatever is now playing on Spotify
                station = await PlayNowPlaying(turnContext, token.Token, cancellationToken);

                if (station == null)
                {
                    return;
                }
            }
            else
            {
                // Search for a Playlist and command Spotify to Play it

                // playlist query
                string search = null;

                //if (query.Contains('#'))
                //{
                //    search = query.Substring(0, query.IndexOf('#'));
                //    hashtag = query.Substring(query.IndexOf('#') + 1);
                //}
                //else
                //{
                //    search = query;
                //}

                search = query;

                Playlist[] playlists = await _spotifyService.FindPlaylists(
                    search,
                    token.Token,
                    cancellationToken);

                if (playlists == null || !playlists.Any())
                {
                    await turnContext.SendActivityAsync($"No playlists found!", cancellationToken : cancellationToken);

                    return;
                }

                // TODO: Carousel
                Playlist playlist = playlists[0];

                if (!await IsDeviceActive(
                        turnContext,
                        token.Token,
                        $"{RingoBotHelper.RingoHandleIfGroupChat(turnContext)}play {playlist.Uri}",
                        cancellationToken,
                        playlist: playlist))
                {
                    return;
                }

                // Command Spotify to play the Playlist
                await _spotifyService.PlayPlaylist(
                    playlist.Id,
                    token.Token,
                    cancellationToken);

                if (playlist == null)
                {
                    return;
                }

                // Playlist is playing, now create a station
                station = info.IsGroup
                    ? await _ringoService.CreateConversationStation(info, playlist : playlist)
                    : await _ringoService.CreateUserStation(info, playlist : playlist);
            }

            await turnContext.SendActivityAsync(RingoBotMessages.NowPlayingStation(info, station), cancellationToken);
        }
Example #18
0
        private async Task <Station> PlayNowPlaying(ITurnContext turnContext, string token, CancellationToken cancellationToken)
        {
            // no query so start / resume station
            // Play whatever the user is currently playing on Spotify

            var info          = RingoBotHelper.NormalizedConversationInfo(turnContext);
            var channelUserId = RingoBotHelper.ChannelUserId(turnContext);

            SpotifyApi.NetCore.CurrentPlaybackContext nowPlaying = await _spotifyService.GetUserNowPlaying(token);

            if (nowPlaying == null || !nowPlaying.IsPlaying || nowPlaying.Context == null)
            {
                if (nowPlaying.IsPlaying && nowPlaying.Context == null)
                {
                    await turnContext.SendActivityAsync(RingoBotMessages.NowPlayingNoContext(info), cancellationToken);
                }
                else if (nowPlaying.IsPlaying && !new[] { "playlist", "album" }.Contains(nowPlaying.Context.Type))
                {
                    await turnContext.SendActivityAsync(
                        RingoBotMessages.NowPlayingNotSupported(info, nowPlaying.Context.Type), cancellationToken);
                }
                else
                {
                    await turnContext.SendActivityAsync(RingoBotMessages.NotPlayingAnything(info), cancellationToken);
                }

                return(null);
            }

            await _spotifyService.TurnOffShuffleRepeat(token, nowPlaying);

            Station station = null;

            switch (nowPlaying.Context.Type)
            {
            case "playlist":
                var playlist = await _spotifyService.GetPlaylist(token, nowPlaying.Context.Uri);

                station = info.IsGroup
                        ? await _ringoService.CreateConversationStation(info, playlist : playlist)
                        : await _ringoService.CreateUserStation(info, playlist : playlist);

                break;

            case "album":
                var album = await _spotifyService.GetAlbum(token, nowPlaying.Context.Uri);

                station = info.IsGroup
                        ? await _ringoService.CreateConversationStation(info, album : album)
                        : await _ringoService.CreateUserStation(info, album : album);

                break;

            default:
                await turnContext.SendActivityAsync(
                    RingoBotMessages.NowPlayingNotSupported(info, nowPlaying.Context.Type), cancellationToken);

                break;
            }

            return(station);
        }
Example #19
0
        public async Task Auth(ITurnContext turnContext, UserProfile userProfile, string query, CancellationToken cancellationToken)
        {
            var info = RingoBotHelper.NormalizedConversationInfo(turnContext);

            // Don't auth in group chat
            if (BotHelper.IsGroup(turnContext))
            {
                await turnContext.SendActivityAsync(
                    $"Ringo cannot authorize you in Group Chat. DM (direct message) @{info.BotName} instead.",
                    cancellationToken : cancellationToken);

                return;
            }

            // RESET
            if (query.ToLower() == "reset")
            {
                await _authService.ResetAuthorization(turnContext, cancellationToken);

                await turnContext.SendActivityAsync(RingoBotMessages.AuthHasBeenReset(info), cancellationToken);

                return;
            }

            // MAGIC NUMBER
            if (AuthService.RingoBotStateRegex.IsMatch(query))
            {
                var token = await _authService.ValidateMagicNumber(turnContext, query, cancellationToken);

                if (PlayCommand.Contains(userProfile.ResumeAfterAuthorizationWith.command))
                {
                    await Play(
                        turnContext,
                        userProfile,
                        userProfile.ResumeAfterAuthorizationWith.query,
                        cancellationToken,
                        token : token);
                }
                else if (JoinCommand.Contains(userProfile.ResumeAfterAuthorizationWith.command))
                {
                    await Join(
                        turnContext,
                        userProfile,
                        userProfile.ResumeAfterAuthorizationWith.query,
                        cancellationToken,
                        token : token);
                }

                return;
            }

            // AUTH
            var token2 = await _authService.Authorize(turnContext, cancellationToken);

            if (token2 == null)
            {
                // clear resume after auth
                userProfile.ResumeAfterAuthorizationWith = (null, null);
                return;
            }

            await turnContext.SendActivityAsync(
                "Ringo is authorized to play Spotify. Ready to rock! 😎",
                cancellationToken : cancellationToken);
        }
Example #20
0
 public async Task ResetAuthorization(ITurnContext turnContext, CancellationToken cancellationToken)
 {
     await _userData.ResetAuthorization(RingoBotHelper.ChannelUserId(turnContext));
 }
Example #21
0
 public async Task <Station> GetUserStation(ConversationInfo info, string username)
 => await _stationData.GetStation(Station.EncodeIds(info, RingoBotHelper.ToHashtag(username), username));