示例#1
0
    def __init__(self, base: "Base") -> None:
        self.base = base

        self.suggestions = Suggestions(self)

        self.queue = QueuedSong.objects

        self.playback = Playback(self)
        self.controller = Controller(self)
示例#2
0
class Musiq(Stateful):
    """This class provides endpoints for all music related requests."""
    def __init__(self, base: "Base") -> None:
        self.base = base

        self.suggestions = Suggestions(self)

        self.queue = QueuedSong.objects

        self.playback = Playback(self)
        self.controller = Controller(self)

    def start(self) -> None:
        self.controller.start()
        self.playback.start()

    def do_request_music(
        self,
        request_ip: str,
        query: str,
        key: Optional[int],
        playlist: bool,
        platform: str,
        archive: bool = True,
        manually_requested: bool = True,
    ) -> Tuple[bool, str, Optional[int]]:
        """Performs the actual requesting of the music, not an endpoint.
        Enqueues the requested song or playlist into the queue, using appropriate providers.
        Returns a 3-tuple: successful, message, queue_key"""
        providers: List[MusicProvider] = []

        provider: MusicProvider
        music_provider_class: Union[Type[PlaylistProvider], Type[SongProvider]]
        local_provider_class: Type[MusicProvider]
        soundcloud_provider_class: Type[MusicProvider]
        spotify_provider_class: Type[MusicProvider]
        youtube_provider_class: Type[MusicProvider]
        if playlist:
            music_provider_class = PlaylistProvider
            local_provider_class = PlaylistProvider
            soundcloud_provider_class = SoundcloudPlaylistProvider
            spotify_provider_class = SpotifyPlaylistProvider
            youtube_provider_class = YoutubePlaylistProvider
        else:
            music_provider_class = SongProvider
            local_provider_class = LocalSongProvider
            soundcloud_provider_class = SoundcloudSongProvider
            spotify_provider_class = SpotifySongProvider
            youtube_provider_class = YoutubeSongProvider

        if key is not None:
            # an archived entry was requested.
            # The key determines the Provider
            provider = music_provider_class.create(self, query, key)
            if provider is None:
                return False, "No provider found for requested key", None
            providers.append(provider)
        else:
            if platform == "local":
                # local music can only be searched explicitly
                providers.append(local_provider_class(self, query, key))
            if self.base.settings.platforms.soundcloud_enabled:
                try:
                    soundcloud_provider = soundcloud_provider_class(
                        self, query, key)
                    if platform == "soundcloud":
                        providers.insert(0, soundcloud_provider)
                    else:
                        providers.append(soundcloud_provider)
                except WrongUrlError:
                    pass
            if self.base.settings.platforms.spotify_enabled:
                try:
                    spotify_provider = spotify_provider_class(self, query, key)
                    if platform == "spotify":
                        providers.insert(0, spotify_provider)
                    else:
                        providers.append(spotify_provider)
                except WrongUrlError:
                    pass
            if self.base.settings.platforms.youtube_enabled:
                try:
                    youtube_provider = youtube_provider_class(self, query, key)
                    if platform == "youtube":
                        providers.insert(0, youtube_provider)
                    else:
                        providers.append(youtube_provider)
                except WrongUrlError:
                    pass

        if not providers:
            return False, "No backend configured to handle your request.", None

        fallback = False
        for i, provider in enumerate(providers):
            try:
                provider.request(request_ip,
                                 archive=archive,
                                 manually_requested=manually_requested)
                # the current provider could provide the song, don't try the other ones
                break
            except ProviderError:
                # this provider cannot provide this song, use the next provider
                # if this was the last provider, show its error
                # in new music only mode, do not allow fallbacks
                if self.base.settings.basic.new_music_only or i == len(
                        providers) - 1:
                    return False, provider.error, None
                fallback = True
        message = provider.ok_message
        queue_key = None
        if not playlist:
            queued_song = cast(SongProvider, provider).queued_song
            if not queued_song:
                logging.error(
                    "no placeholder was created for query '%s' and key '%s'",
                    query, key)
                return False, "No placeholder was created", None
            queue_key = queued_song.id
        if fallback:
            message += " (used fallback)"
        return True, message, queue_key

    def request_music(self, request: WSGIRequest) -> HttpResponse:
        """Endpoint to request music. Calls internal function."""
        key = request.POST.get("key")
        query = request.POST.get("query")
        playlist = request.POST.get("playlist") == "true"
        platform = request.POST.get("platform")

        if query is None or not platform:
            return HttpResponseBadRequest(
                "query, playlist and platform have to be specified.")
        ikey = None
        if key:
            ikey = int(key)

        # only get ip on user requests
        if self.base.settings.basic.logging_enabled:
            request_ip, _ = ipware.get_client_ip(request)
            if request_ip is None:
                request_ip = ""
        else:
            request_ip = ""

        successful, message, queue_key = self.do_request_music(
            request_ip, query, ikey, playlist, platform)
        if not successful:
            return HttpResponseBadRequest(message)
        return JsonResponse({"message": message, "key": queue_key})

    def request_radio(self, request: WSGIRequest) -> HttpResponse:
        """Endpoint to request radio for the current song."""
        # only get ip on user requests
        if self.base.settings.basic.logging_enabled:
            request_ip, _ = ipware.get_client_ip(request)
            if request_ip is None:
                request_ip = ""
        else:
            request_ip = ""

        try:
            current_song = CurrentSong.objects.get()
        except CurrentSong.DoesNotExist:
            return HttpResponseBadRequest("Need a song to play the radio")
        provider = SongProvider.create(self,
                                       external_url=current_song.external_url)
        return provider.request_radio(request_ip)

    @csrf_exempt
    def post_song(self, request: WSGIRequest) -> HttpResponse:
        """This endpoint is part of the API and exempt from CSRF checks.
        Shareberry uses this endpoint."""
        # only get ip on user requests
        if self.base.settings.basic.logging_enabled:
            request_ip, _ = ipware.get_client_ip(request)
            if request_ip is None:
                request_ip = ""
        else:
            request_ip = ""
        query = request.POST.get("query")
        if not query:
            return HttpResponseBadRequest("No query to share.")
        # Set the requested platform to 'spotify'.
        # It will automatically fall back to Youtube
        # if Spotify is not enabled or a youtube link was requested.
        successful, message, _ = self.do_request_music(request_ip, query, None,
                                                       False, "spotify")
        if not successful:
            return HttpResponseBadRequest(message)
        return HttpResponse(message)

    def index(self, request: WSGIRequest) -> HttpResponse:
        """Renders the /musiq page."""
        context = self.base.context(request)
        context[
            "additional_keywords"] = self.base.settings.basic.additional_keywords
        context[
            "forbidden_keywords"] = self.base.settings.basic.forbidden_keywords
        return render(request, "musiq.html", context)

    def state_dict(self) -> Dict[str, Any]:
        state_dict = self.base.state_dict()
        current_song: Optional[Dict[str, Any]]
        try:
            current_song = model_to_dict(CurrentSong.objects.get())
            current_song["duration_formatted"] = song_utils.format_seconds(
                current_song["duration"])
        except CurrentSong.DoesNotExist:
            current_song = None

        song_queue = []
        total_time = 0
        all_songs = self.queue.all()
        if self.base.settings.basic.voting_system:
            all_songs = all_songs.order_by("-votes", "index")
        for song in all_songs:
            song_dict = model_to_dict(song)
            total_time += song_dict["duration"]
            song_dict["duration_formatted"] = song_utils.format_seconds(
                song_dict["duration"])
            song_queue.append(song_dict)
        state_dict["total_time_formatted"] = song_utils.format_seconds(
            total_time)

        if state_dict["alarm"]:
            state_dict["current_song"] = {
                "queue_key": -1,
                "manually_requested": False,
                "votes": None,
                "internal_url": "",
                "external_url": "",
                "artist": "Raveberry",
                "title": "ALARM!",
                "duration": 10,
                "created": "",
            }
        elif self.playback.backup_playing.is_set():
            state_dict["current_song"] = {
                "queue_key": -1,
                "manually_requested": False,
                "votes": None,
                "internal_url": "",
                "external_url": self.base.settings.sound.backup_stream,
                "artist": "",
                "title": "Backup Stream",
                "duration": 60 * 60 * 24,
                "created": "",
            }
        else:
            state_dict["current_song"] = current_song
        state_dict["paused"] = self.playback.paused()
        state_dict["progress"] = self.playback.progress()
        state_dict["shuffle"] = self.controller.shuffle
        state_dict["repeat"] = self.controller.repeat
        state_dict["autoplay"] = self.controller.autoplay
        state_dict["volume"] = self.controller.volume
        state_dict["song_queue"] = song_queue
        return state_dict