PageSourceSearch

https://shabiba.fm/js/shabiba.js

js shabiba.fm collected 2026-10-06 16:16:42 UTC 20,289 bytes, 528 lines download raw bytes

1const RADIO_NAME = 'Shabiba FM';
2
3// Change Stream URL Here, Supports, ICECAST, ZENO, SHOUTCAST, RADIOJAR and any other stream service.
4const URL_STREAMING = 'https://stream.shabiba.fm:9010/shabiba';
5
6//API URL /
7// const API_URL = 'https://twj.es/free/?url='+URL_STREAMING;
8// const FALLBACK_API_URL = 'https://twj.es/metadata/?url=' + URL_STREAMING;
9
10// Visit https://api.vagalume.com.br/docs/ to get your API key
11// const API_KEY = "18fe07917957c289983464588aabddfb";
12
13let userInteracted = true;
14
15let musicaAtual = null;
16
17// Cache para a API do iTunes
18const cache = {};
19
20window.addEventListener('load', () => { 
21    const page = new Page();
22    page.changeTitlePage();
23    page.setVolume();
24
25    const player = new Player();
26    player.play();
27
28    // Chama a função getStreamingData imediatamente quando a página carrega
29    getStreamingData();
30
31    // Define o intervalo para atualizar os dados de streaming a cada 10 segundos
32    const streamingInterval = setInterval(getStreamingData, 10000);
33
34    // Ajusta a altura da capa do álbum para ser igual à sua largura
35    const coverArt = document.querySelector('.cover-album'); // Use querySelector para selecionar o elemento
36    if (coverArt) { 
37      // Adiciona uma verificação para garantir que o elemento exista
38      coverArt.style.height = `${coverArt.offsetWidth}px`;
39    } else {
40      console.warn("Elemento .cover-album não encontrado.");
41    }
42});
43
44// DOM control
45class Page {
46    constructor() {
47        this.changeTitlePage = function (title = RADIO_NAME) {
48            document.title = title;
49        };
50
51        this.refreshCurrentSong = function(song, artist) {
52            const currentSong = document.getElementById('currentSong');
53            const currentArtist = document.getElementById('currentArtist');
54            const lyricsSong = document.getElementById('lyricsSong');
55        
56            if (song !== currentSong.textContent || artist !== currentArtist.textContent) { 
57                // Esmaecer o conteúdo existente (fade-out)
58                currentSong.classList.add('fade-out');
59                currentArtist.classList.add('fade-out');
60        
61                setTimeout(function() {
62                    // Atualizar o conteúdo após o fade-out
63                    currentSong.textContent = song; 
64                    currentArtist.textContent = artist;
65                    lyricsSong.textContent = song + ' - ' + artist;
66        
67                    // Esmaecer o novo conteúdo (fade-in)
68                    currentSong.classList.remove('fade-out');
69                    currentSong.classList.add('fade-in');
70                    currentArtist.classList.remove('fade-out');
71                    currentArtist.classList.add('fade-in');
72                }, 500); 
73        
74                setTimeout(function() {
75                    // Remover as classes fade-in após a animação
76                    currentSong.classList.remove('fade-in');
77                    currentArtist.classList.remove('fade-in');
78                }, 1000); 
79            }
80        };
81          
82        this.refreshHistoric = async function (info, n) {
83            const historicDiv = document.querySelectorAll("#historicSong article")[n];
84            const songName = document.querySelectorAll("#historicSong article .music-info .song")[n];
85            const artistName = document.querySelectorAll("#historicSong article .music-info .artist")[n];
86            const coverHistoric = document.querySelectorAll("#historicSong article .cover-historic")[n];
87
88            const defaultCoverArt = "img/cover.png";
89
90            // Extrai o título da música e o nome do artista,
91            // tratando a possibilidade de 'song' e 'artist' serem objetos ou strings.
92            const songTitle = typeof info.song === "object" ? info.song.title : info.song;
93            const songArtist = typeof info.artist === "object" ? info.artist.title : info.artist;
94
95            // Define o conteúdo dos elementos HTML,
96            // incluindo uma verificação para evitar erros caso os valores estejam ausentes.
97            songName.innerHTML = songTitle || "Desconhecido";
98            artistName.innerHTML = songArtist || "Desconhecido";
99
100            try {
101                // Utiliza os valores extraídos para buscar a capa do álbum na API do iTunes.
102                const data = await getDataFromITunes(songArtist, songTitle, defaultCoverArt, defaultCoverArt);
103                // Define a imagem de fundo do elemento 'coverHistoric' com a capa encontrada.
104                coverHistoric.style.backgroundImage = "url(" + (data.art || defaultCoverArt) + ")";
105            } catch (error) {
106                // Captura e imprime o erro no console para ajudar na depuração.
107                console.log("Erro ao buscar dados da API do iTunes:");
108                console.error(error);
109                // Define a imagem de fundo como a capa padrão em caso de erro.
110                coverHistoric.style.backgroundImage = "url(" + defaultCoverArt + ")";
111            }
112
113            // Adiciona a classe 'animated' para a animação de slide.
114            historicDiv.classList.add("animated", "slideInRight");
115            // Remove a classe 'animated' após 2 segundos para preparar para a próxima animação.
116            setTimeout(() => historicDiv.classList.remove("animated", "slideInRight"), 2000);
117        };
118                
119        this.refreshCover = async function (song = '', artist) {
120            const coverArt = document.getElementById('currentCoverArt');
121            const coverBackground = document.getElementById('bgCover');
122            const defaultCoverArt = 'img/cover.png'; 
123        
124            try {
125                const data = await getDataFromITunes(artist, song, defaultCoverArt, defaultCoverArt);
126        
127                // Aplica a imagem de capa (sempre, mesmo se for a padrão)
128                coverArt.style.backgroundImage = 'url(' + data.art + ')';
129                coverBackground.style.backgroundImage = 'url(' + data.cover + ')';
130        
131                // Adiciona/remove classes para animação (se necessário)
132                coverArt.classList.add('animated', 'bounceInLeft');
133                setTimeout(() => coverArt.classList.remove('animated', 'bounceInLeft'), 2000);
134              
135                // Atualiza MediaSession (se suportado)
136                if ('mediaSession' in navigator) {
137                    const artwork = [
138                        { src: data.art, sizes: '96x96',   type: 'image/png' },
139                        { src: data.art, sizes: '128x128', type: 'image/png' },
140                        { src: data.art, sizes: '192x192', type: 'image/png' },
141                        { src: data.art, sizes: '256x256', type: 'image/png' },
142                        { src: data.art, sizes: '384x384', type: 'image/png' },
143                        { src: data.art, sizes: '512x512', type: 'image/png' },
144                    ];
145                
146                    navigator.mediaSession.metadata = new MediaMetadata({ 
147                        title: song, 
148                        artist: artist, 
149                        artwork 
150                    });
151                }
152            } catch (error) {
153                console.log("Erro ao buscar dados da API do iTunes:", error);
154                // ... (lógica para lidar com o erro)
155            }
156        };
157
158        this.changeVolumeIndicator = function(volume) {
159            document.getElementById('volIndicator').textContent = volume; // Use textContent em vez de innerHTML
160          
161            if (typeof Storage !== 'undefined') {
162              localStorage.setItem('volume', volume);
163            }
164          };
165          
166        this.setVolume = function() {
167            if (typeof Storage !== 'undefined') {
168              const volumeLocalStorage = localStorage.getItem('volume') || 80; // Operador de coalescência nula (??)
169          
170              document.getElementById('volume').value = volumeLocalStorage;
171              document.getElementById('volIndicator').textContent = volumeLocalStorage;
172            }
173          };
174
175        this.refreshLyric = async function (currentSong, currentArtist) {
176            const openLyric = document.getElementsByClassName('lyrics')[0];
177            const modalLyric = document.getElementById('modalLyrics');
178            
179            try {
180              const response = await fetch('https://api.vagalume.com.br/search.php?apikey=' + API_KEY + '&art=' + currentArtist + '&mus=' + currentSong.toLowerCase());
181              const data = await response.json();
182          
183              if (data.type === 'exact' || data.type === 'aprox') {
184                const lyric = data.mus[0].text;
185          
186                //document.getElementById('lyric').textContent = lyric.replace(/\n/g, '<br />'); Use textContent em vez de innerHTML
187                document.getElementById('lyric').innerHTML = lyric.replace(/\n/g, '<br />');
188                openLyric.style.opacity = "1";
189                openLyric.setAttribute('data-toggle', 'modal');
190          
191                // Esconde o modal caso esteja visível
192                modalLyric.style.display = "none";
193                modalLyric.setAttribute('aria-hidden', 'true');
194                if (document.getElementsByClassName('modal-backdrop')[0]) {
195                  document.getElementsByClassName('modal-backdrop')[0].remove();
196                }
197              } else {
198                openLyric.style.opacity = "0.3";
199                openLyric.removeAttribute('data-toggle');
200              }
201            } catch (error) {
202              console.log("Erro ao buscar a letra da música:", error);
203              openLyric.style.opacity = "0.3";
204              openLyric.removeAttribute('data-toggle');
205            }
206        };
207    }
208}
209
210
211async function getStreamingData() {
212    try {
213        let data = await fetchStreamingData(API_URL);
214        if (!data) {
215            data = await fetchStreamingData(FALLBACK_API_URL);
216        }
217
218        if (data) {
219            const page = new Page();
220            const currentSong = data.songtitle || (typeof data.song === "object" ? data.song.title : data.song);
221            const currentArtist = typeof data.artist === "object" ? data.artist.title : data.artist;
222
223            const safeCurrentSong = (currentSong || "").replace(/'/g, "'").replace(/&/g, "&");
224            const safeCurrentArtist = (currentArtist || "").replace(/'/g, "'").replace(/&/g, "&");
225
226            if (safeCurrentSong !== musicaAtual) {
227                document.title = `${safeCurrentSong} - ${safeCurrentArtist} | ${RADIO_NAME}`;
228
229                page.refreshCover(safeCurrentSong, safeCurrentArtist);
230                page.refreshCurrentSong(safeCurrentSong, safeCurrentArtist);
231                page.refreshLyric(safeCurrentSong, safeCurrentArtist);
232
233                const historicContainer = document.getElementById("historicSong");
234                historicContainer.innerHTML = "";
235
236                const historyArray = data.song_history
237                    ? data.song_history.map((item) => ({ song: item.song.title, artist: item.song.artist }))
238                    : data.history;
239
240                const maxSongsToDisplay = 4; // Adjust as needed
241                const limitedHistory = historyArray.slice(Math.max(0, historyArray.length - maxSongsToDisplay));
242
243                for (let i = 0; i < limitedHistory.length; i++) {
244                    const songInfo = limitedHistory[i];
245                    const article = document.createElement("article");
246                    article.classList.add("col-12", "col-md-6");
247                    article.innerHTML = `
248                        <div class="cover-historic" style="background-image: url('img/cover.png');"></div>
249                        <div class="music-info">
250                          <p class="song">${songInfo.song || "Desconhecido"}</p>
251                          <p class="artist">${songInfo.artist || "Desconhecido"}</p>
252                        </div>
253                      `;
254                    historicContainer.appendChild(article);
255                    try {
256                        page.refreshHistoric(songInfo, i);
257                    } catch (error) {
258                        console.error("Error refreshing historic song:", error);
259                    }
260                }
261                musicaAtual = safeCurrentSong;
262            }
263        }
264    } catch (error) {
265        console.log("Erro ao buscar dados de streaming:", error);
266    }
267}
268
269
270// Função para buscar dados de streaming de uma API específica
271async function fetchStreamingData(apiUrl) {
272  try {
273    const response = await fetch(apiUrl);
274    if (!response.ok) {
275      throw new Error(`Erro na requisição da API: ${response.status} ${response.statusText}`);
276    }
277
278    const data = await response.json();
279    return data;
280  } catch (error) {
281    console.log("Erro ao buscar dados de streaming da API:", error);
282    return null; // Retorna null em caso de erro
283  }
284}
285
286// Função para alterar o tamanho da imagem do iTunes
287function changeImageSize(url, size) {
288  const parts = url.split("/");
289  const filename = parts.pop();
290  const newFilename = `${size}${filename.substring(filename.lastIndexOf("."))}`;
291  return parts.join("/") + "/" + newFilename;
292}
293
294// Função para buscar dados da API do iTunes
295const getDataFromITunes = async (artist, title, defaultArt, defaultCover) => {
296  let text;
297  if (artist === title) {
298      text = `${title}`;
299  } else {
300      text = `${artist} - ${title}`;
301  }
302  const cacheKey = text.toLowerCase();
303  if (cache[cacheKey]) {
304      return cache[cacheKey];
305  }
306
307  const response = await fetch(`https://itunes.apple.com/search?limit=1&term=${encodeURIComponent(text)}`);
308  if (response.status === 403) {
309      const results = {
310          title,
311          artist,
312          art: defaultArt,
313          cover: defaultCover,
314          stream_url: "#not-found",
315      };
316      return results;
317  }
318  const data = response.ok ? await response.json() : {};
319  if (!data.results || data.results.length === 0) {
320      const results = {
321          title,
322          artist,
323          art: defaultArt,
324          cover: defaultCover,
325          stream_url: "#not-found",
326      };
327      return results;
328  }
329  const itunes = data.results[0];
330  const results = {
331      title: title, // Mantive o título original da transmissão
332      artist: artist, // Mantive o artista original da transmissão
333      thumbnail: itunes.artworkUrl100 || defaultArt,
334      art: itunes.artworkUrl100 ? changeImageSize(itunes.artworkUrl100, "600x600") : defaultArt,
335      cover: itunes.artworkUrl100 ? changeImageSize(itunes.artworkUrl100, "1500x1500") : defaultCover,
336      stream_url: "#not-found",
337  };
338  cache[cacheKey] = results;
339  return results;
340};
341
342// AUDIO 
343
344
345// Variável global para armazenar as músicas
346var audio = new Audio(URL_STREAMING);
347
348// Player control
349class Player {
350    constructor() {
351        this.play = function () {
352            audio.play();
353
354            var defaultVolume = document.getElementById('volume').value;
355
356            if (typeof (Storage) !== 'undefined') {
357                if (localStorage.getItem('volume') !== null) {
358                    audio.volume = intToDecimal(localStorage.getItem('volume'));
359                } else {
360                    audio.volume = intToDecimal(defaultVolume);
361                }
362            } else {
363                audio.volume = intToDecimal(defaultVolume);
364            }
365            document.getElementById('volIndicator').innerHTML = defaultVolume;
366            
367            togglePlay(); // Adiciona esta linha para atualizar o botão
368        };
369
370        this.pause = function () {
371            audio.pause();
372        };
373    }
374}
375
376// On play, change the button to pause
377audio.onplay = function () {
378    var botao = document.getElementById('playerButton');
379    var bplay = document.getElementById('buttonPlay');
380    if (botao.className === 'fa fa-play') {
381        botao.className = 'fa fa-pause';
382        bplay.firstChild.data = 'PAUSAR';
383    }
384}
385
386// On pause, change the button to play
387audio.onpause = function () {
388    var botao = document.getElementById('playerButton');
389    var bplay = document.getElementById('buttonPlay');
390    if (botao.className === 'fa fa-pause') {
391        botao.className = 'fa fa-play';
392        bplay.firstChild.data = 'PLAY';
393    }
394}
395
396// Unmute when volume changed
397audio.onvolumechange = function () {
398    if (audio.volume > 0) {
399        audio.muted = false;
400    }
401}
402
403audio.onerror = function () {
404    var confirmacao = confirm('Stream Down / Network Error. \nClick OK to try again.');
405
406    if (confirmacao) {
407        window.location.reload();
408    }
409}
410
411document.getElementById('volume').oninput = function () {
412    audio.volume = intToDecimal(this.value);
413
414    var page = new Page();
415    page.changeVolumeIndicator(this.value);
416}
417
418
419function togglePlay() {
420    const playerButton = document.getElementById("playerButton");
421    const isPlaying = playerButton.classList.contains("fa-pause-circle");
422  
423    if (isPlaying) {
424      playerButton.classList.remove("fa-pause-circle");
425      playerButton.classList.add("fa-play-circle");
426      playerButton.style.textShadow = "0 0 5px black";
427      audio.pause();
428    } else {
429      playerButton.classList.remove("fa-play-circle");
430      playerButton.classList.add("fa-pause-circle");
431      playerButton.style.textShadow = "0 0 5px black";
432      audio.load();
433      audio.play();
434    }
435  }
436
437function volumeUp() {
438    var vol = audio.volume;
439    if(audio) {
440        if(audio.volume >= 0 && audio.volume < 1) {
441            audio.volume = (vol + .01).toFixed(2);
442        }
443    }
444}
445
446function volumeDown() {
447    var vol = audio.volume;
448    if(audio) {
449        if(audio.volume >= 0.01 && audio.volume <= 1) {
450            audio.volume = (vol - .01).toFixed(2);
451        }
452    }
453}
454
455function mute() {
456    if (!audio.muted) {
457        document.getElementById('volIndicator').innerHTML = 0;
458        document.getElementById('volume').value = 0;
459        audio.volume = 0;
460        audio.muted = true;
461    } else {
462        var localVolume = localStorage.getItem('volume');
463        document.getElementById('volIndicator').innerHTML = localVolume;
464        document.getElementById('volume').value = localVolume;
465        audio.volume = intToDecimal(localVolume);
466        audio.muted = false;
467    }
468}
469
470document.addEventListener('keydown', function (event) {
471    var key = event.key;
472    var slideVolume = document.getElementById('volume');
473    var page = new Page();
474
475    switch (key) {
476        // Arrow up
477        case 'ArrowUp':
478            volumeUp();
479            slideVolume.value = decimalToInt(audio.volume);
480            page.changeVolumeIndicator(decimalToInt(audio.volume));
481            break;
482        // Arrow down
483        case 'ArrowDown':
484            volumeDown();
485            slideVolume.value = decimalToInt(audio.volume);
486            page.changeVolumeIndicator(decimalToInt(audio.volume));
487            break;
488        // Spacebar
489        case ' ':
490        case 'Spacebar':
491            togglePlay();
492            break;
493        // P
494        case 'p':
495        case 'P':
496            togglePlay();
497            break;
498        // M
499        case 'm':
500        case 'M':
501            mute();
502            break;
503        // Numeric keys 0-9
504        case '0':
505        case '1':
506        case '2':
507        case '3':
508        case '4':
509        case '5':
510        case '6':
511        case '7':
512        case '8':
513        case '9':
514            var volumeValue = parseInt(key);
515            audio.volume = volumeValue / 10;
516            slideVolume.value = volumeValue * 10;
517            page.changeVolumeIndicator(volumeValue * 10);
518            break;
519    }
520}); 
521
522function intToDecimal(vol) {
523    return vol / 100;
524}
525
526function decimalToInt(vol) {
527    return vol * 100;
528} 

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.