Skip to content

Commit

Permalink
spotifytube can convert song url
Browse files Browse the repository at this point in the history
  • Loading branch information
100nandoo committed Oct 15, 2024
1 parent 517c5b2 commit cb3082a
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 1 deletion.
5 changes: 4 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const SupabaseUrl, SupabaseKey string = "SUPABASE_URL", "SUPABASE_KEY"

const TelegramBot string = "TELEGRAM_BOT"
const AntamTelegramBot string = "ANTAM_TELEGRAM_BOT"
const SpotifytubeBot string = "SPOTIFYTUBE_BOT"

const TelegramDebug string = "TELEGRAM_CHANNEL_DEBUG"
const TelegramFreeGames string = "TELEGRAM_CHANNEL_FREE_GAMES"
const TelegramRemoteOk string = "TELEGRAM_CHANNEL_REMOTE_OK"
const TelegramWarta string = "TELEGRAM_CHANNEL_WARTA"
const ChannelAntam string = "TELEGRAM_CHANNEL_ANTAM"
const ChannelAntam string = "TELEGRAM_CHANNEL_ANTAM"

const MusicAPItoken string = "MUSIC_API_TOKEN"
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"gobot/freegames"
"gobot/pkg"
"gobot/remoteok"
"gobot/spotifytube"
"gobot/warta"
)

Expand All @@ -21,6 +22,7 @@ func main() {
antam.Scouting(false)

go antam.Run()
go spotifytube.Run()

pkg.StartBlocking()
}
172 changes: 172 additions & 0 deletions spotifytube/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package spotifytube

import (
"encoding/json"
"fmt"
"gobot/config"
"io"
"net/http"
"os"
"strings"
)

type InspectResponse struct {
Status string `json:"status"`
Source string `json:"source"`
Type string `json:"type"`
Data struct {
ExternalID string `json:"externalId"`
PreviewURL *string `json:"previewUrl"`
Name string `json:"name"`
ArtistNames []string `json:"artistNames"`
AlbumName string `json:"albumName"`
ImageURL string `json:"imageUrl"`
ISRC *string `json:"isrc"`
Duration int `json:"duration"`
URL string `json:"url"`
} `json:"data"`
}

type SearchResponse struct {
Tracks []Track `json:"tracks"`
}

type Track struct {
Source string `json:"source"`
Status string `json:"status"`
Data SearchData `json:"data"`
Type string `json:"type"`
}

type SearchData struct {
ExternalID string `json:"externalId"`
PreviewURL *string `json:"previewUrl"`
Name string `json:"name"`
ArtistNames []string `json:"artistNames"`
AlbumName string `json:"albumName"`
ImageURL string `json:"imageUrl"`
ISRC *string `json:"isrc"`
Duration int `json:"duration"`
URL *string `json:"url"`
}

const (
baseUrl = "https://api.musicapi.com/public/"
inspectUrlPath = "inspect/url"
searchUrlPath = "search"

POST = "POST"
YOUTUBE_MUSIC_PREFIX = "https://music.youtube.com/watch?v="
)

// makeAPIRequest is a reusable function to handle API requests
func makeAPIRequest(endpoint, payload string) ([]byte, error) {
client := &http.Client{}
url := baseUrl + endpoint

req, err := http.NewRequest(POST, url, strings.NewReader(payload))
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}

// Add necessary headers
token := "Token " + os.Getenv(config.MusicAPItoken)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", token)

// Make the request
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %v", err)
}
defer res.Body.Close()

// Read the response body
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %v", err)
}

return body, nil
}

// Search performs a track search on the API and prints the parsed response
// Define a custom type for source selection
type SourceType int

// Define constants for the possible source options
const (
SPOTIFY_ONLY SourceType = iota
YOUTUBE_ONLY
BOTH
)

func Search(track string, artist string, sourceType SourceType) (*SearchResponse, error) {
var sources string

// Determine the sources based on the sourceType
switch sourceType {
case SPOTIFY_ONLY:
sources = `["spotify"]`
case YOUTUBE_ONLY:
sources = `["youtubeMusic"]`
case BOTH:
sources = `["youtubeMusic", "spotify"]`
default:
return nil, fmt.Errorf("invalid source type")
}

// Build the payload with the selected sources
payload := fmt.Sprintf(`{
"track": "%s",
"artist": "%s",
"type": "track",
"sources": %s
}`, track, artist, sources)

// Make the API request
body, err := makeAPIRequest(searchUrlPath, payload)
if err != nil {
return nil, fmt.Errorf("API request error: %w", err)
}

// Parse the response into SearchResponse struct
var searchResp SearchResponse
err = json.Unmarshal(body, &searchResp)
if err != nil {
return nil, fmt.Errorf("error unmarshalling JSON: %w", err)
}

// formattedResp, err := json.MarshalIndent(searchResp, "", " ")
// if err != nil {
// log.Println("Error marshalling JSON:", err)
// return nil, err
// }
// fmt.Println(string(formattedResp))
return &searchResp, nil
}

// InspectUrl retrieves information about a specific track by URL and prints the parsed response
func InspectUrl(url string) (*InspectResponse, error) {
// Build the payload
payload := fmt.Sprintf(`{
"url": "%s"
}`, url)

// Make the API request
body, err := makeAPIRequest(inspectUrlPath, payload)
if err != nil {
return nil, fmt.Errorf("API request error: %w", err)
}

// Parse the response into InspectResponse struct
var inspectResp InspectResponse
err = json.Unmarshal(body, &inspectResp)
if err != nil {
return nil, fmt.Errorf("error unmarshalling JSON: %w", err)
}

// Return the parsed InspectResponse and nil error
return &inspectResp, nil
}
156 changes: 156 additions & 0 deletions spotifytube/telegram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package spotifytube

import (
"log"
"os"
"regexp"
"strings"
"time"

"gobot/config"

tele "gopkg.in/telebot.v3"
)

// Constants
const (
helpMessage = `Hello this is SpotifyTubeBot
**Features:**
- Convert YouTube music URL to Spotify URL and vice versa
- Search song using title/artist
**2 ways to convert URL:**
I. Send URL to the bot
II. Type @spotifytubebot followed by URL in chat box on any conversation
Example:
@spotifytubebot https://music.youtube.com/watch?v=ezVbN7e-L7Y
SpotifyTubeBot made with ❤️ by @crossix`

SPOTIFY_REGEX = `^(https?://)?((www|open)\.spotify\.com)/.+$`
YOUTUBE_REGEX = `^(https?\:\/\/)?((www|music)\.youtube\.com|youtu\.be)\/.+$`
)

// Compile regex patterns once
var (
spotifyPattern = regexp.MustCompile(SPOTIFY_REGEX)
youtubePattern = regexp.MustCompile(YOUTUBE_REGEX)
)

// Run initializes the bot and starts listening for messages
func Run() {
pref := tele.Settings{
Token: os.Getenv(config.SpotifytubeBot),
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
}

bot, err := tele.NewBot(pref)
if err != nil {
log.Fatal(err)
return
}

// Define a common help handler
helpHandler := func(c tele.Context) error {
return c.Send(helpMessage, &tele.SendOptions{
ParseMode: tele.ModeMarkdown,
})
}

// Register the help handler for both commands
bot.Handle("/help", helpHandler)
bot.Handle("/start", helpHandler)

// Handle incoming text messages
bot.Handle(tele.OnText, handleTextMessage)

// Start the bot
bot.Start()
}

// handleTextMessage processes incoming text messages
func handleTextMessage(c tele.Context) error {
messageText := c.Text()

// Check for Spotify URL
if spotifyPattern.MatchString(messageText) {
return handleSpotifyURL(c, messageText)
}

// Check for YouTube URL
if youtubePattern.MatchString(messageText) {
return handleYoutubeURL(c, messageText)
}

return nil // No action taken for other messages
}

// handleSpotifyURL processes Spotify URLs
func handleSpotifyURL(c tele.Context, messageText string) error {
resp, err := InspectUrl(messageText)
if err != nil {
return sendErrorMessage(c, "Invalid Spotify URL")
}

searchResp, err := Search(resp.Data.Name, resp.Data.ArtistNames[0], YOUTUBE_ONLY)
if err != nil {
return sendErrorMessage(c, "Can't convert")
}
return sendTrackURLs(c, false, searchResp)
}

// handleYoutubeURL processes YouTube URLs
func handleYoutubeURL(c tele.Context, messageText string) error {
resp, err := InspectUrl(messageText)
if err != nil {
return sendErrorMessage(c, "Invalid YouTube URL")
}

searchResp, err := Search(resp.Data.Name, resp.Data.ArtistNames[0], SPOTIFY_ONLY)
if err != nil {
return sendErrorMessage(c, "Can't convert")
}
return sendTrackURLs(c, true, searchResp)
}

// sendErrorMessage sends an error message to the user
func sendErrorMessage(c tele.Context, message string) error {
return c.Send(message)
}

// sendTrackURLs sends the track URLs to the user
func sendTrackURLs(c tele.Context, isSpotify bool, searchResp *SearchResponse) error {
// Check if there are tracks in the response
if len(searchResp.Tracks) == 0 {
return c.Send("No tracks found.")
}

// Create a slice to hold the URLs
var urls []string

// Loop through each track and collect the URLs
for _, track := range searchResp.Tracks {
if isSpotify {
if track.Data.URL != nil {
urls = append(urls, *track.Data.URL)
} else {
log.Println("Warning: track.Data.URL is nil, skipping this entry.")
}
} else {
urls = append(urls, YOUTUBE_MUSIC_PREFIX+ *&track.Data.ExternalID)
}
}

// Check if any URLs were collected
if len(urls) == 0 {
return c.Send("No URLs found in the track data.")
}

// Format the URLs for sending
responseMessage := strings.Join(urls, "\n")

// Send the response message
return c.Send(responseMessage)
}

0 comments on commit cb3082a

Please sign in to comment.