Пример #1
0
        public void OnApplicationStart()
        {
            TwitchWebSocketClient.Initialize();
            _ = TwitchCore.Instance;
            _ = AlphaTwitchManager.Instance;

            HarmonyInstance.Create("com.auros.BeatSaber.AlphaTwitch").PatchAll(Assembly.GetExecutingAssembly());
        }
        public void CheckStatusCommands(TwitchMessage message)
        {
            if (message.user.isBroadcaster || message.user.isMod)
            {
                if (message.message.ToLower().Contains("!gm reset"))
                {
                    Plugin.cooldowns.ResetCooldowns();
                    TwitchPowers.ResetPowers(true);
                    Plugin.twitchPowers.StopAllCoroutines();
                    Plugin.charges = ChatConfig.chargesPerLevel;
                    TwitchWebSocketClient.SendMessage("Resetting non Permanent Powers");
                }
            }


            if (message.message.ToLower().Contains("!gm pp"))
            {
                if (Plugin.currentpp != 0)
                {
                    TwitchWebSocketClient.SendMessage("Streamer Rank: #" + Plugin.currentRank + ". Streamer pp: " + Plugin.currentpp + "pp");
                }
                else
                {
                    TwitchWebSocketClient.SendMessage("Currently do not have streamer info");
                }
            }
            if (message.message.ToLower().Contains("!gm status"))
            {
                string scopeMessage = "";
                int    scope        = CheckCommandScope();
                switch (scope)
                {
                case 0:
                    scopeMessage = "Everyone has access to commands";
                    break;

                case 1:
                    scopeMessage = "Subscribers have access to commands";
                    break;

                case 2:
                    scopeMessage = "Moderators have access to commands";
                    break;
                }

                Plugin.beepSound.Play();
                if (GMPUI.chatIntegration)
                {
                    TwitchWebSocketClient.SendMessage("Chat Integration Enabled. " + scopeMessage);
                }
                else
                {
                    TwitchWebSocketClient.SendMessage("Chat Integration Not Enabled. " + scopeMessage);
                }
            }
        }
Пример #3
0
 public void QueueChatMessage(string message)
 {
     if (TwitchWebSocketClient.Connected)
     {
         TwitchWebSocketClient.SendCommand($"{RequestBotConfig.Instance.BotPrefix}\uFEFF{message}");
     }
     //else
     //{
     //    Plugin.Log($"Message sent before twitch connected! \"{message}\"");
     //}
 }
Пример #4
0
 /// <summary>
 /// Sends a message to all connected chat clients
 /// </summary>
 /// <param name="message">The message to be sent.</param>
 public static void SendMessage(string message)
 {
     if (YouTubeConnection.initialized && YouTubeLiveBroadcast.currentBroadcast != null)
     {
         YouTubeLiveChat.SendMessage(message);
     }
     if (TwitchWebSocketClient.Initialized && TwitchWebSocketClient.IsChannelValid)
     {
         TwitchWebSocketClient.SendMessage(message);
     }
 }
Пример #5
0
        internal static IEnumerator InitGlobalChatHandlers()
        {
            Twitch  = new ChatIntegration <ITwitchIntegration>();
            YouTube = new ChatIntegration <IYouTubeIntegration>();
            Mixer   = new ChatIntegration <IMixerIntegration>();
            Global  = new ChatIntegration <IGlobalChatIntegration>();

            bool initTwitch = false, initYouTube = false, initMixer = false;

            // Iterate through all the message handlers that were registered
            foreach (var instance in registeredInstances)
            {
                var instanceType = instance.Value.GetType();
                var typeName     = instanceType.Name;

                // Wait for all the registered handlers to be ready
                if (!instance.Value.IsPluginReady)
                {
                    Plugin.Log($"Instance of type {typeName} wasn't ready! Waiting until it is...");
                    yield return(new WaitUntil(() => instance.Value.IsPluginReady));

                    Plugin.Log($"Instance of type {typeName} is ready!");
                }

                bool isGlobalIntegration = typeof(IGlobalChatIntegration).IsAssignableFrom(instanceType);
                // Mark the correct services for initialization based on type
                if (typeof(ITwitchIntegration).IsAssignableFrom(instanceType) || isGlobalIntegration)
                {
                    initTwitch = true;
                }
                if (typeof(IYouTubeIntegration).IsAssignableFrom(instanceType) || isGlobalIntegration)
                {
                    initYouTube = true;
                }
                if (typeof(IMixerIntegration).IsAssignableFrom(instanceType) || isGlobalIntegration)
                {
                    initMixer = true;
                }
            }

            // Initialize the appropriate streaming services
            if (initTwitch)
            {
                TwitchWebSocketClient.Initialize_Internal();
            }
            if (initYouTube)
            {
                YouTubeConnection.Initialize_Internal();
            }
            if (initMixer)
            {
                MixerClient.Initialize_Internal();
            }
        }
Пример #6
0
        public void OnApplicationStart()
        {
            if (Instance != null)
            {
                return;
            }
            Instance   = this;
            ChatConfig = new ChatConfig();

            TwitchWebSocketClient.Initialize();

            SharedCoroutineStarter.instance.StartCoroutine(DelayedStartup());
        }
Пример #7
0
        internal static IEnumerator CreateGlobalMessageHandlers()
        {
            // Attempt to initialize message handlers for each of our chat services
            TwitchMessageHandler.Instance.InitializeMessageHandlers();
            YouTubeMessageHandler.Instance.InitializeMessageHandlers();
            GlobalMessageHandler.Instance.InitializeMessageHandlers();

            bool initTwitch = false, initYouTube = false;

            // Iterate through all the message handlers that were registered
            foreach (var instance in GlobalMessageHandler.registeredInstances)
            {
                var instanceType = instance.Value.GetType();
                var typeName     = instanceType.Name;

                // Wait for all the registered handlers to be ready
                if (!instance.Value.ChatCallbacksReady)
                {
                    Plugin.Log($"Instance of type {typeName} wasn't ready! Waiting until it is...");
                    yield return(new WaitUntil(() => instance.Value.ChatCallbacksReady));

                    Plugin.Log($"Instance of type {typeName} is ready!");
                }

                // Mark the correct services for initialization based on type
                if (typeof(ITwitchMessageHandler).IsAssignableFrom(instanceType))
                {
                    initTwitch = true;
                }
                if (typeof(IYouTubeMessageHandler).IsAssignableFrom(instanceType))
                {
                    initYouTube = true;
                }
                if (typeof(IGlobalMessageHandler).IsAssignableFrom(instanceType))
                {
                    initTwitch  = true;
                    initYouTube = true;
                }
            }

            // Initialize the appropriate streaming services
            if (initTwitch)
            {
                TwitchWebSocketClient.Initialize_Internal();
            }
            if (initYouTube)
            {
                YouTubeConnection.Initialize_Internal();
            }
        }
Пример #8
0
        public void OnApplicationQuit()
        {
            SceneManager.activeSceneChanged -= SceneManager_activeSceneChanged;
            SceneManager.sceneLoaded        -= SceneManager_sceneLoaded;

            Globals.IsApplicationExiting = true;

            // Cancel all running tasks
            TaskHelper.CancelAllTasks();

            // Shutdown our twitch client if it's initialized
            TwitchWebSocketClient.Shutdown();
            YouTubeConnection.Stop();
        }
        public void OnApplicationStart()
        {
            if (Instance != null)
            {
                return;
            }
            Instance = this;
            ChatHandler.OnLoad();
            Task.Run(() => TwitchWebSocketClient.Initialize());

            SceneManager.activeSceneChanged += SceneManager_activeSceneChanged;
            SceneManager.sceneLoaded        += SceneManager_sceneLoaded;

            SharedCoroutineStarter.instance.StartCoroutine(CheckIfUserHasEnteredChannelName());
        }
 private void SendChatMessage(string message)
 {
     try
     {
         Plugin.Log($"Sending message: \"{message}\"");
         TwitchWebSocketClient.SendMessage($"PRIVMSG #{Config.Instance.TwitchChannelName} :{message}");
         TwitchMessage tmpMessage = new TwitchMessage();
         tmpMessage.user = TwitchWebSocketClient.OurTwitchUser;
         MessageParser.Parse(new ChatMessage(message, tmpMessage));
     }
     catch (Exception e)
     {
         Plugin.Log($"Exception was caught when trying to send bot message. {e.ToString()}");
     }
 }
Пример #11
0
// if (!silence) QueueChatMessage($"{request.Key.song["songName"].Value}/{request.Key.song["authorName"].Value} ({songId}) added to the blacklist.");
        private void SendChatMessage(string message)
        {
            try
            {
                Plugin.Log($"Sending message: \"{message}\"");
                //TwitchWebSocketClient.SendMessage($"PRIVMSG #{TwitchLoginConfig.Instance.TwitchChannelName} :{message}");
                TwitchWebSocketClient.SendMessage(message);
                TwitchMessage tmpMessage = new TwitchMessage();
                tmpMessage.user = TwitchWebSocketClient.OurTwitchUser;
                //MessageParser.Parse(new ChatMessage(message, tmpMessage)); // This call is obsolete, when sending a message through TwitchWebSocketClient, the message should automatically appear in chat.
            }
            catch (Exception e)
            {
                Plugin.Log($"Exception was caught when trying to send bot message. {e.ToString()}");
            }
        }
 public void CheckChargeMessage(TwitchMessage message)
 {
     if (message.bits >= ChatConfig.bitsPerCharge && ChatConfig.bitsPerCharge > 0)
     {
         Plugin.charges += (message.bits / ChatConfig.bitsPerCharge);
         TwitchWebSocketClient.SendMessage("Current Charges: " + Plugin.charges);
     }
     if (message.user.displayName.ToLower() == "kyle1413k" && message.message.ToLower().Contains("!charge"))
     {
         Plugin.charges += (ChatConfig.chargesForSuperCharge / 2 + 5);
         TwitchWebSocketClient.SendMessage("Current Charges: " + Plugin.charges);
     }
     if (message.message.ToLower().Contains("!gm") && message.message.ToLower().Contains("super"))
     {
         Plugin.trySuper = true;
     }
 }
Пример #13
0
        public void Enter(KEY key)
        {
            var typedtext = key.kb.KeyboardText.text;

            if (typedtext != "")
            {
                if (RequestBot.COMMAND.aliaslist.ContainsKey(RequestBot.ParseState.GetCommand(ref typedtext)))
                {
                    RequestBot.COMMAND.Parse(TwitchWebSocketClient.OurTwitchUser, typedtext, RequestBot.CmdFlags.Local);
                }
                else
                {
                    TwitchWebSocketClient.SendCommand(typedtext);
                }

                key.kb.KeyboardText.text = "";
            }
        }
        public void CheckInfoCommands(TwitchMessage message)
        {
            if (message.message.ToLower().Contains("!gm help"))
            {
                TwitchWebSocketClient.SendMessage("Guides: For Regular Users - http://bit.ly/1413ChatUser | For Streamers - http://bit.ly/1413Readme | For moderators also view http://bit.ly/1413Config");
            }
            if (message.message.ToLower().Contains("!currentsong"))
            {
                if (!Plugin.isValidScene)
                {
                    TwitchWebSocketClient.SendMessage("No song is currently being played.");
                }
                else
                {
                    TwitchWebSocketClient.SendMessage("Current Song: " + Plugin.levelData.GameplayCoreSceneSetupData.difficultyBeatmap.level.songName
                                                      + " - " + Plugin.levelData.GameplayCoreSceneSetupData.difficultyBeatmap.level.songSubName + " mapped by " + Plugin.levelData.GameplayCoreSceneSetupData.difficultyBeatmap.level.songAuthorName);
                }
            }

            if (message.message.ToLower().Contains("!gm chargehelp"))
            {
                if (ChatConfig.timeForCharges == 0 || ChatConfig.chargesOverTime == 0)
                {
                    TwitchWebSocketClient.SendMessage("Every " + ChatConfig.bitsPerCharge + " bits sent with a message adds a charge, which are used to activate commands! If you add super at the end of a command, it will cost " + ChatConfig.chargesForSuperCharge + " Charges but will make the effect last much longer! " + ChatConfig.chargesPerLevel + " Charges are generated every song with chat mode on.");
                }
                else
                {
                    TwitchWebSocketClient.SendMessage("Every " + ChatConfig.bitsPerCharge + " bits sent with a message adds a charge, which are used to activate commands! If you add super at the end of a command, it will cost " + ChatConfig.chargesForSuperCharge + " Charges but will make the effect last much longer! " + ChatConfig.chargesPerLevel + " Charges are generated every song with chat mode on. Every " + ChatConfig.timeForCharges + " seconds, " + ChatConfig.chargesOverTime + " are added.");
                }
            }
            if (message.message.ToLower().Contains("!gm commands"))
            {
                TwitchWebSocketClient.SendMessage("Currently supported commands | status: Currrent Status of chat integration | charges: view current charges and costs | chargehelp: Explain charge system");
            }


            if (message.message.ToLower().Contains("!gm charges"))
            {
                TwitchWebSocketClient.SendMessage("Charges: " + Plugin.charges + " | Commands Per Message: " + ChatConfig.commandsPerMessage + " | " + ChatConfig.GetChargeCostString());
            }
        }
        private void OnConfigChanged()
        {
            //if (lastChannel != String.Empty)
            //    TwitchConnection.Instance.PartRoom(lastChannel);
            Plugin.Log("OnConfigChanged");
            if (TwitchWebSocketClient.Initialized)
            {
                if (Config.Instance.TwitchChannelName != lastChannel)
                {
                    if (Config.Instance.TwitchChannelName != String.Empty)
                    {
                        TwitchWebSocketClient.JoinChannel(Config.Instance.TwitchChannelName);
                    }
                    TwitchWebSocketClient.ConnectionTime = DateTime.Now;
                    displayStatusMessage = true;
                }
                lastChannel = Config.Instance.TwitchChannelName;
            }
            if (Config.Instance.FontName != _lastFontName)
            {
                StartCoroutine(Drawing.Initialize(gameObject.transform));
                foreach (CustomText currentMessage in _chatMessages)
                {
                    Font f = currentMessage.font;
                    currentMessage.font  = Drawing.LoadSystemFont(Config.Instance.FontName);
                    currentMessage.color = Config.Instance.TextColor;
                    Destroy(f);
                }
                _lastFontName = Config.Instance.FontName;
            }

            UpdateChatUI();
            _canvasRectTransform.localScale = new Vector3(0.012f * Config.Instance.ChatScale, 0.012f * Config.Instance.ChatScale, 0.012f * Config.Instance.ChatScale);
            _lockButtonSphere.localScale    = new Vector3(0.15f * Config.Instance.ChatScale, 0.15f * Config.Instance.ChatScale, 0.001f * Config.Instance.ChatScale);
            background.color = Config.Instance.BackgroundColor;

            Plugin.Log($"Config updated!");
            _configChanged = false;
        }
Пример #16
0
        private static async void ProcessSongRequest(int index, bool fromHistory = false)
        {
            if ((RequestQueue.Songs.Count > 0 && !fromHistory) || (RequestHistory.Songs.Count > 0 && fromHistory))
            {
                SongRequest request = null;
                if (!fromHistory)
                {
                    SetRequestStatus(index, RequestStatus.Played);
                    request = DequeueRequest(index);
                }
                else
                {
                    request = RequestHistory.Songs.ElementAt(index);
                }

                if (request == null)
                {
                    Plugin.Log("Can't process a null request! Aborting!");
                    return;
                }
                else
                {
                    Plugin.Log($"Processing song request {request.song["songName"].Value}");
                }


                string songName  = request.song["songName"].Value;
                string songIndex = $"{request.song["id"].Value} ({request.song["songName"].Value} - {request.song["levelAuthor"].Value})";
                songIndex = normalize.RemoveDirectorySymbols(ref songIndex); // Remove invalid characters.

                string currentSongDirectory = Path.Combine(Environment.CurrentDirectory, "Beat Saber_Data\\CustomLevels", songIndex);
                string songHash             = request.song["hash"].Value.ToUpper();


                // Check to see if level exists, download if not.

                // Replace with level check.
                //CustomLevel[] levels = SongLoader.CustomLevels.Where(l => l.levelID.StartsWith(songHash)).ToArray();
                //if (levels.Length == 0)

                var  rat       = SongCore.Collections.levelIDsForHash(songHash);
                bool mapexists = (rat.Count > 0) && (rat[0] != "");


                if (!SongCore.Loader.CustomLevels.ContainsKey(currentSongDirectory) && !mapexists)
                {
                    Utilities.EmptyDirectory(".requestcache", false);


                    //SongMap map;
                    //if (MapDatabase.MapLibrary.TryGetValue(songIndex, out map))
                    //{
                    //    if (map.path != "")
                    //    {
                    //        songIndex = map.song["version"].Value;
                    //        songName = map.song["songName"].Value;
                    //        currentSongDirectory = Path.Combine(Environment.CurrentDirectory, "CustomSongs", songIndex);
                    //        songHash = map.song["hashMd5"].Value.ToUpper();

                    //        Directory.CreateDirectory(currentSongDirectory);
                    //        // HACK to allow playing alternate songs not in custom song directory
                    //        CopyFilesRecursively(new DirectoryInfo(map.path),new DirectoryInfo( currentSongDirectory));

                    //        goto here;
                    //    }
                    //}

                    //Plugin.Log("Downloading");

                    if (Directory.Exists(currentSongDirectory))
                    {
                        Utilities.EmptyDirectory(currentSongDirectory, true);
                        Plugin.Log($"Deleting {currentSongDirectory}");
                    }

                    string localPath = Path.Combine(Environment.CurrentDirectory, ".requestcache", $"{request.song["id"].Value}.zip");
                    //string dl = $"https://beatsaver.com {request.song["downloadURL"].Value}";
                    //Instance.QueueChatMessage($"Download url: {dl}, {request.song}");



                    // Insert code to replace local path with ZIP path here
                    //SongMap map;
                    //if (MapDatabase.MapLibrary.TryGetValue(songIndex, out map))
                    //{
                    //    if (map.path != "")
                    //    {
                    //        songIndex = map.song["version"].Value;
                    //        songName = map.song["songName"].Value;
                    //        currentSongDirectory = Path.Combine(Environment.CurrentDirectory, "CustomSongs", songIndex);
                    //        songHash = map.song["hashMd5"].Value.ToUpper();

                    //        Directory.CreateDirectory(currentSongDirectory);
                    //        // HACK to allow playing alternate songs not in custom song directory
                    //        CopyFilesRecursively(new DirectoryInfo(map.path),new DirectoryInfo( currentSongDirectory));

                    //        goto here;
                    //    }
                    //}


#if UNRELEASED
                    // Direct download hack
                    var ext = Path.GetExtension(request.song["coverURL"].Value);
                    var k   = request.song["coverURL"].Value.Replace(ext, ".zip");

                    var songZip = await Plugin.WebClient.DownloadSong($"https://beatsaver.com{k}", System.Threading.CancellationToken.None);
#else
                    var songZip = await Plugin.WebClient.DownloadSong($"https://beatsaver.com{request.song["downloadURL"].Value}", System.Threading.CancellationToken.None);
#endif

                    Stream zipStream = new MemoryStream(songZip);
                    try
                    {
                        // open zip archive from memory stream
                        ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
                        archive.ExtractToDirectory(currentSongDirectory);
                        archive.Dispose();
                    }
                    catch (Exception e)
                    {
                        Plugin.Log($"Unable to extract ZIP! Exception: {e}");
                        return;
                    }
                    zipStream.Close();

here:

                    await Task.Run(async() =>
                    {
                        while (!SongCore.Loader.AreSongsLoaded && SongCore.Loader.AreSongsLoading)
                        {
                            await Task.Delay(25);
                        }
                    });

                    Loader.Instance.RefreshSongs();

                    await Task.Run(async() =>
                    {
                        while (!SongCore.Loader.AreSongsLoaded && SongCore.Loader.AreSongsLoading)
                        {
                            await Task.Delay(25);
                        }
                    });

                    Utilities.EmptyDirectory(".requestcache", true);
                    //levels = SongLoader.CustomLevels.Where(l => l.levelID.StartsWith(songHash)).ToArray();
                }
                else
                {
                    //Instance.QueueChatMessage($"Directory exists: {currentSongDirectory}");

                    Plugin.Log($"Song {songName} already exists!");
                }

                // Dismiss the song request viewcontroller now
                //_songRequestMenu.Dismiss();
                _flowCoordinator.Dismiss();

                if (true)
                {
                    //Plugin.Log($"Scrolling to level {levels[0].levelID}");

                    bool success = false;

                    Dispatcher.RunCoroutine(SongListUtils.ScrollToLevel(request.song["hash"].Value.ToUpper(), (s) => success = s, false));

                    // Redownload the song if we failed to scroll to it
                }
                else
                {
                    Plugin.Log("Failed to find new level!");
                }

                if (!request.song.IsNull && RequestBotConfig.Instance.SendNextSongBeingPlayedtoChat)
                {
                    new DynamicText().AddUser(ref request.requestor).AddSong(request.song).QueueMessage(NextSonglink.ToString()); // Display next song message
                }

                #if UNRELEASED
                if (!request.song.IsNull) // Experimental!
                {
                    TwitchWebSocketClient.SendCommand("/marker " + new DynamicText().AddUser(ref request.requestor).AddSong(request.song).Parse(NextSonglink.ToString()));
                }
                #endif
            }
        }
Пример #17
0
 public void QueueChatMessage(string message)
 {
     TwitchWebSocketClient.SendCommand($"{RequestBotConfig.Instance.BotPrefix}\uFEFF{message}");
 }
Пример #18
0
 private void InitStreamCore()
 {
     TwitchWebSocketClient.Initialize();
     TwitchAsync();
 }
Пример #19
0
 internal static void SendAsyncMessage(string message)
 {
     TwitchWebSocketClient.SendMessage(message);
 }
Пример #20
0
        private static IEnumerator ProcessSongRequest(int index, bool fromHistory = false)
        {
            if ((RequestQueue.Songs.Count > 0 && !fromHistory) || (RequestHistory.Songs.Count > 0 && fromHistory))
            {
                SongRequest request = null;
                if (!fromHistory)
                {
                    SetRequestStatus(index, RequestStatus.Played);
                    request = DequeueRequest(index);
                }
                else
                {
                    request = RequestHistory.Songs.ElementAt(index);
                }

                if (request == null)
                {
                    Plugin.Log("Can't process a null request! Aborting!");
                    yield break;
                }
                else
                {
                    Plugin.Log($"Processing song request {request.song["songName"].Value}");
                }


                string songName  = request.song["songName"].Value;
                string songIndex = $"{request.song["id"].Value} ({request.song["songName"].Value} - {request.song["levelAuthor"].Value})";
                songIndex = normalize.RemoveDirectorySymbols(ref songIndex); // Remove invalid characters.

                string currentSongDirectory = Path.Combine(Environment.CurrentDirectory, "Beat Saber_Data\\CustomLevels", songIndex);
                string songHash             = request.song["hash"].Value.ToUpper();


                // Check to see if level exists, download if not.

                // Replace with level check.
                //CustomLevel[] levels = SongLoader.CustomLevels.Where(l => l.levelID.StartsWith(songHash)).ToArray();
                //if (levels.Length == 0)

                var  rat       = SongCore.Collections.levelIDsForHash(songHash);
                bool mapexists = (rat.Count > 0) && (rat[0] != "");


                if (!SongCore.Loader.CustomLevels.ContainsKey(currentSongDirectory) && !mapexists)
                {
                    Utilities.EmptyDirectory(".requestcache", false);


                    //SongMap map;
                    //if (MapDatabase.MapLibrary.TryGetValue(songIndex, out map))
                    //{
                    //    if (map.path != "")
                    //    {
                    //        songIndex = map.song["version"].Value;
                    //        songName = map.song["songName"].Value;
                    //        currentSongDirectory = Path.Combine(Environment.CurrentDirectory, "CustomSongs", songIndex);
                    //        songHash = map.song["hashMd5"].Value.ToUpper();

                    //        Directory.CreateDirectory(currentSongDirectory);
                    //        // HACK to allow playing alternate songs not in custom song directory
                    //        CopyFilesRecursively(new DirectoryInfo(map.path),new DirectoryInfo( currentSongDirectory));

                    //        goto here;
                    //    }
                    //}

                    //Plugin.Log("Downloading");

                    if (Directory.Exists(currentSongDirectory))
                    {
                        Utilities.EmptyDirectory(currentSongDirectory, true);
                        Plugin.Log($"Deleting {currentSongDirectory}");
                    }

                    string localPath = Path.Combine(Environment.CurrentDirectory, ".requestcache", $"{request.song["id"].Value}.zip");
                    //string dl = $"https://beatsaver.com {request.song["downloadURL"].Value}";
                    //Instance.QueueChatMessage($"Download url: {dl}, {request.song}");

                    yield return(Utilities.DownloadFile($"https://beatsaver.com{request.song["downloadURL"].Value}", localPath));

                    yield return(Utilities.ExtractZip(localPath, currentSongDirectory));

here:

                    yield return(new WaitUntil(() => SongCore.Loader.AreSongsLoaded && !SongCore.Loader.AreSongsLoading));

                    Loader.Instance.RefreshSongs();
                    yield return(new WaitUntil(() => SongCore.Loader.AreSongsLoaded && !SongCore.Loader.AreSongsLoading));

                    Utilities.EmptyDirectory(".requestcache", true);
                    //levels = SongLoader.CustomLevels.Where(l => l.levelID.StartsWith(songHash)).ToArray();
                }
                else
                {
                    //Instance.QueueChatMessage($"Directory exists: {currentSongDirectory}");

                    Plugin.Log($"Song {songName} already exists!");
                }

                // Dismiss the song request viewcontroller now
                _songRequestMenu.Dismiss();

                if (true)
                {
                    //Plugin.Log($"Scrolling to level {levels[0].levelID}");

                    bool success = false;
                    yield return(SongListUtils.ScrollToLevel(request.song["hash"].Value.ToUpper(), (s) => success = s, false));

                    yield return(null);

                    // Redownload the song if we failed to scroll to it
                }
                else
                {
                    Plugin.Log("Failed to find new level!");
                }

                if (!request.song.IsNull)
                {
                    new DynamicText().AddUser(ref request.requestor).AddSong(request.song).QueueMessage(NextSonglink.ToString());                       // Display next song message
                }
                #if UNRELEASED
                if (!request.song.IsNull) // Experimental!
                {
                    TwitchWebSocketClient.SendCommand("/marker " + new DynamicText().AddUser(ref request.requestor).AddSong(request.song).Parse(NextSonglink.ToString()));
                }
                #endif
            }
        }