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)); }
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); }
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)))); }
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); } } }
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); } } }
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}\"")); }
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.")); }
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.")); }
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}\"?")); }
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.")); }
/// <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); }
/// <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}."); }
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); }
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)); }
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); }
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); } }
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); }
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); }
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); }
public async Task ResetAuthorization(ITurnContext turnContext, CancellationToken cancellationToken) { await _userData.ResetAuthorization(RingoBotHelper.ChannelUserId(turnContext)); }
public async Task <Station> GetUserStation(ConversationInfo info, string username) => await _stationData.GetStation(Station.EncodeIds(info, RingoBotHelper.ToHashtag(username), username));