Created
May 5, 2025 07:49
-
-
Save sunmeat/998e3280e90eb3dda647b531a969473e to your computer and use it in GitHub Desktop.
MovieDB API js example with Axios + OOP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang=""> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Лучшие фильмы (Axios + OOP)</title> | |
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
background-color: #121212; | |
color: #ffffff; | |
padding: 2rem; | |
margin: 0; | |
} | |
.container { | |
max-width: 800px; | |
margin: 0 auto; | |
background-color: #1e1e1e; | |
padding: 20px; | |
border-radius: 10px; | |
} | |
button { | |
padding: 0.5rem 1rem; | |
margin: 10px; | |
border-radius: 5px; | |
border: none; | |
background-color: #1db954; | |
color: #ffffff; | |
cursor: pointer; | |
font-size: 16px; | |
} | |
button:hover { | |
background-color: #1ed760; | |
} | |
.movie-box { | |
margin: 20px 0; | |
padding: 10px; | |
border: 1px solid #444; | |
border-radius: 5px; | |
} | |
.movie-title { | |
color: #FFFF00; | |
font-size: 1.2em; | |
font-weight: bold; | |
} | |
.movie-release { | |
color: #00FF00; | |
} | |
.movie-actors { | |
color: #FF00FF; | |
} | |
.movie-crew { | |
color: #00FFFF; | |
} | |
.movie-overview { | |
color: #00FFFF; | |
white-space: pre-wrap; | |
} | |
.movie-rating { | |
color: #FF0000; | |
} | |
.star-filled { | |
color: #FFFF00; | |
} | |
.star-empty { | |
color: #B8860B; | |
} | |
.error { | |
color: #FF5555; | |
margin: 10px 0; | |
} | |
hr { | |
border: 0; | |
border-top: 1px solid #444; | |
margin: 10px 0; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h2>Лучшие фильмы</h2> | |
<button onclick="movieCollection.fetchMovies()">Загрузить 100 лучших фильмов</button> | |
<div id="result"></div> | |
</div> | |
<script> | |
const baseUrl = 'https://api.themoviedb.org/3/'; | |
const apiKey = '8f663caefb9c78b4b33f1c2ff31d13f3'; // если не сработает, сгенерить новый АПИ-ключ | |
class Movie { | |
constructor({ id, title, overview, release_date, vote_average, vote_count }) { | |
this.id = id; | |
this.title = title || 'Без названия'; | |
this.overview = overview || 'Описание отсутствует'; | |
this.releaseDate = release_date ? new Date(release_date).toLocaleDateString('ru-RU', { | |
day: '2-digit', | |
month: 'long', | |
year: 'numeric' | |
}) : 'Дата неизвестна'; | |
this.voteAverage = vote_average; | |
this.voteCount = vote_count || 0; | |
} | |
renderRating() { | |
if (!this.voteAverage) { | |
return '<span class="movie-rating">Нет рейтинга</span>'; | |
} | |
const rating = this.voteAverage; | |
const fullStars = Math.round(rating); | |
const totalStars = 10; | |
let stars = ''; | |
for (let i = 0; i < fullStars; i++) { | |
stars += '<span class="star-filled">*</span>'; | |
} | |
for (let i = fullStars; i < totalStars; i++) { | |
stars += '<span class="star-empty">*</span>'; | |
} | |
return `${stars} <span class="movie-rating">${rating} (${this.voteCount} голосов)</span>`; | |
} | |
render(actors, director, writer, composer) { | |
const wrappedOverview = wrapText(this.overview, 90); | |
const movieBox = document.createElement('div'); | |
movieBox.className = 'movie-box'; | |
movieBox.innerHTML = ` | |
<div class="movie-title">${this.title}</div> | |
<div>Дата выхода: <span class="movie-release">${this.releaseDate}</span></div> | |
<div>Рейтинг: ${this.renderRating()}</div> | |
<div>Актёры:</div> | |
<div class="movie-actors">${actors.map(actor => `- ${actor}`).join('<br>')}</div> | |
<div>Режиссёр: <span class="movie-crew">${director}</span></div> | |
<div>Сценарист: <span class="movie-crew">${writer}</span></div> | |
<div>Композитор: <span class="movie-crew">${composer}</span></div> | |
<div>Описание:</div> | |
<div class="movie-overview">${wrappedOverview.join('\n')}</div> | |
`; | |
return movieBox; | |
} | |
} | |
class Credit { | |
constructor({ name, character, job }) { | |
this.name = name || 'Неизвестно'; | |
this.character = character; | |
this.job = job; | |
} | |
formatActor() { | |
return this.character ? `${this.name} (${this.character})` : this.name; | |
} | |
} | |
class MovieApi { | |
constructor(baseUrl, apiKey) { | |
this.baseUrl = baseUrl; | |
this.apiKey = apiKey; | |
} | |
async getTopRatedMovies() { | |
const movies = []; | |
for (let page = 1; page <= 10 && movies.length < 100; page++) { | |
try { | |
const response = await axios.get(`${this.baseUrl}movie/top_rated`, { | |
params: { | |
api_key: this.apiKey, | |
language: 'ru-RU', | |
page | |
}, | |
headers: { | |
'Accept': 'application/json' | |
} | |
}); | |
const results = response.data.results || []; | |
movies.push(...results.map(data => new Movie(data))); | |
} catch (error) { | |
console.error(`Ошибка на странице ${page}:`, error); | |
} | |
} | |
return movies.slice(0, 100); | |
} | |
async getMovieCredits(movieId) { | |
try { | |
const response = await axios.get(`${this.baseUrl}movie/${movieId}/credits`, { | |
params: { | |
api_key: this.apiKey, | |
language: 'ru-RU' | |
}, | |
headers: { | |
'Accept': 'application/json' | |
} | |
}); | |
const cast = response.data.cast || []; | |
const crew = response.data.crew || []; | |
const credits = [...cast, ...crew].map(data => new Credit(data)); | |
// ...cast раскладывает массив cast по элементам | |
// ...crew - то же самое для crew | |
// [...cast, ...crew] создаёт новый массив, в котором сначала все элементы из cast, потом все из crew | |
const actors = credits | |
.filter(c => c.job === undefined) | |
.slice(0, 5) | |
.map(c => c.formatActor()); | |
const director = credits.find(c => c.job === 'Director')?.name || 'Неизвестно'; | |
const writer = credits.find(c => c.job === 'Writer' || c.job === 'Screenplay')?.name || 'Неизвестно'; | |
const composer = credits.find(c => c.job === 'Original Music Composer')?.name || 'Неизвестно'; | |
return { actors: actors.length ? actors : ['Список актёров недоступен'], director, writer, composer }; | |
} catch (error) { | |
console.error(`Ошибка при получении кредитов для фильма ${movieId}:`, error); | |
return { actors: ['Список актёров недоступен'], director: 'Неизвестно', writer: 'Неизвестно', composer: 'Неизвестно' }; | |
} | |
} | |
} | |
class MovieCollection { | |
constructor() { | |
this.movies = []; | |
this.api = new MovieApi(baseUrl, apiKey); | |
} | |
async fetchMovies() { | |
const resultDiv = document.querySelector('#result'); | |
resultDiv.innerHTML = '<p>Загрузка лучших фильмов...</p>'; | |
this.movies = await this.api.getTopRatedMovies(); | |
resultDiv.innerHTML = ''; | |
if (!this.movies.length) { | |
resultDiv.innerHTML = '<p class="error">Не удалось загрузить фильмы.</p>'; | |
return; | |
} | |
for (const [index, movie] of this.movies.entries()) { | |
const credits = await this.api.getMovieCredits(movie.id); | |
const movieElement = movie.render(credits.actors, credits.director, credits.writer, credits.composer); | |
const numberedMovie = document.createElement('div'); | |
numberedMovie.innerHTML = `<div style="color: #FFFFFF">${index + 1}. </div>`; | |
numberedMovie.appendChild(movieElement); | |
numberedMovie.appendChild(document.createElement('hr')); | |
resultDiv.appendChild(numberedMovie); | |
} | |
} | |
} | |
function wrapText(text, maxWidth) { | |
const words = text.split(' '); | |
const lines = []; | |
let line = ''; | |
for (const word of words) { | |
if (line.length + word.length + 1 > maxWidth) { | |
lines.push(line); | |
line = word; | |
} else { | |
line += (line ? ' ' : '') + word; | |
} | |
} | |
if (line) lines.push(line); | |
return lines; | |
} | |
const movieCollection = new MovieCollection(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment