Last active
May 2, 2025 10:55
-
-
Save sunmeat/404f684da54c7e1b6fe5d4957e410bd6 to your computer and use it in GitHub Desktop.
апи плюс промисы
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>Промисы + АПИ</title> | |
<style> | |
body { | |
font-family: 'Courier New', Courier, monospace; | |
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); | |
color: #00ffe5; | |
padding: 3rem; | |
line-height: 1.6; | |
} | |
h1, h2 { | |
text-align: center; | |
color: #ff0077; | |
text-shadow: 2px 2px 5px #000; | |
} | |
section { | |
background: rgba(0, 0, 0, 0.7); | |
border: 1px solid #00ffe5; | |
border-radius: 12px; | |
padding: 2rem; | |
margin: 2rem 0; | |
box-shadow: 0 0 20px #00ffe5; | |
} | |
pre { | |
background: #111; | |
color: #0ff; | |
padding: 1rem; | |
border-radius: 10px; | |
overflow-x: auto; | |
} | |
code { | |
font-size: 1rem; | |
} | |
button { | |
background-color: #ff0077; | |
border: none; | |
padding: 0.8rem 2rem; | |
color: white; | |
font-size: 1rem; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: 0.3s; | |
display: block; | |
margin: 1rem auto 0; | |
} | |
button:hover { | |
background-color: #00ffe5; | |
color: #111; | |
} | |
input[type="number"] { | |
background: #111; | |
color: #0ff; | |
border: 1px solid #00ffe5; | |
padding: 0.5rem; | |
border-radius: 8px; | |
font-size: 1rem; | |
width: 100px; | |
margin: 0 auto; | |
display: block; | |
} | |
label { | |
display: block; | |
text-align: center; | |
margin-bottom: 0.5rem; | |
} | |
img { | |
display: block; | |
max-width: 100%; | |
height: auto; | |
margin: 1rem auto; | |
border: 2px solid #00ffe5; | |
border-radius: 10px; | |
box-shadow: 0 0 15px #00ffe5; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Комбиниция АПИ</h1> | |
<section> | |
<h2>Найти картину по году</h2> | |
<label for="yearInput">Введите год создания картины:</label> | |
<input type="number" id="yearInput" placeholder="Год" min="1000" max="2025" value="1989"> | |
<img id="paintingImage" src="" alt="Картина" style="display: none;"> | |
<pre><code id="result"></code></pre> | |
<button onclick="findPainting()">Найти картину</button> | |
</section> | |
<script> | |
// получение случайной картины по году (Harvard Art Museums API) | |
function getPaintingByYear(year) { | |
const apiKey = '3a0ac081-0b02-4fa4-8b3d-aebc991047aa'; | |
const url = `https://api.harvardartmuseums.org/object?yearmade=${year}&hasimage=1&size=100&apikey=${apiKey}`; | |
return fetch(url) | |
.then(response => { | |
if (!response.ok) throw new Error('Ошибка при запросе к Harvard API'); | |
return response.json(); | |
}) | |
.then(data => { | |
if (!data.records || data.records.length === 0) { | |
throw new Error('Картины за этот год не найдены'); | |
} | |
return data.records; | |
}); | |
} | |
// получение информации об авторе картины | |
function getArtistInfo(artistId) { | |
const apiKey = '3a0ac081-0b02-4fa4-8b3d-aebc991047aa'; | |
const url = `https://api.harvardartmuseums.org/person/${artistId}?apikey=${apiKey}`; | |
return fetch(url) // https://learn.javascript.ru/fetch | |
.then(response => { | |
if (!response.ok) throw new Error('Ошибка при запросе информации об авторе'); | |
return response.json(); | |
}); | |
} | |
// геокодирование города рождения художника (Nominatim) | |
function getCityCoordinates(city) { | |
const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(city)}&format=json&limit=1&addressdetails=1`; | |
return fetch(url, { | |
headers: { 'User-Agent': 'PaintingSunsetApp/1.0' } | |
}) | |
.then(response => { | |
if (!response.ok) throw new Error('Ошибка при геокодировании города'); | |
return response.json(); | |
}) | |
.then(data => { | |
if (!data || data.length === 0) { | |
throw new Error('Город не найден'); | |
} | |
return { | |
lat: parseFloat(data[0].lat), | |
lon: parseFloat(data[0].lon), | |
display_name: data[0].display_name, | |
timezone: data[0].timezone || null | |
}; | |
}); | |
} | |
// поиск ближайшего магазина АТБ | |
function findNearestATB(lat, lon) { | |
const url = `https://nominatim.openstreetmap.org/search?q=АТБ+near+${lat},${lon}&format=json&limit=1&addressdetails=1`; | |
return fetch(url, { | |
headers: { 'User-Agent': 'PaintingSunsetApp/1.0' } | |
}) | |
.then(response => { | |
if (!response.ok) throw new Error('Ошибка при поиске магазина АТБ'); | |
return response.json(); | |
}) | |
.then(data => { | |
if (!data || data.length === 0) { | |
return 'Магазин АТБ не найден поблизости'; | |
} | |
const store = data[0]; | |
const storeLat = parseFloat(store.lat); | |
const storeLon = parseFloat(store.lon); | |
const distance = haversineDistance(lat, lon, storeLat, storeLon); | |
return `Ближайший магазин АТБ: ${store.display_name}\nКоординаты: (${storeLat}, ${storeLon})\nРасстояние: ${distance.toFixed(2)} км`; | |
}); | |
} | |
// рассчёт расстояния между двумя точками (формула гаверсинуса) | |
function haversineDistance(lat1, lon1, lat2, lon2) { | |
const R = 6371; // Радиус Земли в км | |
const dLat = (lat2 - lat1) * Math.PI / 180; | |
const dLon = (lon2 - lon1) * Math.PI / 180; | |
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + | |
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * | |
Math.sin(dLon / 2) * Math.sin(dLon / 2); | |
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
return R * c; | |
} | |
// приблизительное определение часового пояса по долготе | |
function estimateTimezone(lon) { | |
const hoursOffset = Math.round(lon / 15); // 1 час на 15° долготы | |
if (hoursOffset === 0) return { name: 'UTC', offset: 0 }; | |
const sign = hoursOffset > 0 ? '+' : '-'; | |
return { name: `UTC${sign}${Math.abs(hoursOffset)}`, offset: hoursOffset }; | |
} | |
// повторные попытки для запросов | |
function fetchWithRetry(url, options, retries = 3, delayMs = 1000) { | |
return new Promise((resolve, reject) => { | |
let attempt = 0; | |
function tryFetch() { | |
fetch(url, options) | |
.then(response => { | |
if (!response.ok) throw new Error(`HTTP error: ${response.status}`); | |
return response.json(); | |
}) | |
.then(data => resolve(data)) | |
.catch(error => { | |
if (attempt < retries - 1) { | |
attempt++; | |
setTimeout(tryFetch, delayMs); | |
} else { | |
reject(error); | |
} | |
}); | |
} | |
tryFetch(); | |
}); | |
} | |
// получение текущего времени и часового пояса | |
function getCurrentTime(lat, lon) { | |
const formatTime = (date, timezone) => date.toLocaleTimeString('ru-RU', { | |
hour: '2-digit', | |
minute: '2-digit', | |
second: '2-digit', | |
hour12: false, | |
timeZone: timezone | |
}); | |
const apiKey = '1AKVDXKKF55B'; | |
const url = `http://api.timezonedb.com/v2.1/get-time-zone?key=${apiKey}&format=json&by=position&lat=${lat}&lng=${lon}`; | |
return fetchWithRetry(url, { headers: { 'User-Agent': 'PaintingSunsetApp/1.0' } }) | |
.then(data => { | |
if (!data.status || data.status !== 'OK' || !data.timestamp || !data.zoneName) { | |
throw new Error('Данные о времени или часовом поясе недоступны'); | |
} | |
console.log('TimeZoneDB response:', data); | |
const date = new Date(data.timestamp * 1000); | |
const time = formatTime(date, data.zoneName); | |
const isDst = data.dst === 1; | |
const dstOffsetHours = isDst ? 1 : 0; | |
return { | |
currentTime: time, | |
timezone: data.zoneName, | |
dst_offset: dstOffsetHours, | |
is_dst: isDst | |
}; | |
}) | |
.catch(error => { | |
console.error('TimeZoneDB error:', error); | |
const url = `https://api.sunrisesunset.io/json?lat=${lat}&lng=${lon}&date=today`; | |
return fetchWithRetry(url, { headers: { 'User-Agent': 'PaintingSunsetApp/1.0' } }) | |
.then(data => { | |
if (data.status !== 'OK' || !data.results.timezone) { | |
throw new Error('Ошибка в данных Sunrise-Sunset API'); | |
} | |
console.log('Sunrise-Sunset response:', data); | |
const timezone = data.results.timezone || 'UTC'; | |
const date = new Date(); | |
const time = formatTime(date, timezone); | |
const isDst = timezone.includes('America') && new Date().getMonth() === 3; | |
const dstOffsetHours = isDst ? 1 : 0; | |
return { | |
currentTime: time, | |
timezone: timezone, | |
dst_offset: dstOffsetHours, | |
is_dst: isDst | |
}; | |
}) | |
.catch(fallbackError => { | |
console.error('Sunrise-Sunset error:', fallbackError); | |
return { | |
currentTime: 'Ошибка: не удалось получить время', | |
timezone: 'Недоступно', | |
dst_offset: 0, | |
is_dst: false | |
}; | |
}); | |
}); | |
} | |
// получение времени восхода/заката и длительности дня | |
function getSunriseSunset(lat, lon, timezone) { | |
const url = `https://api.sunrisesunset.io/json?lat=${lat}&lng=${lon}&date=today${timezone && timezone !== 'UTC' ? `&tzid=${encodeURIComponent(timezone)}` : ''}`; | |
return fetchWithRetry(url, { headers: { 'User-Agent': 'PaintingSunsetApp/1.0' } }) | |
.then(data => { | |
if (data.status !== 'OK') { | |
throw new Error('Ошибка в данных Sunrise-Sunset API'); | |
} | |
return { | |
sunrise: data.results.sunrise, | |
sunset: data.results.sunset, | |
day_length: data.results.day_length | |
}; | |
}) | |
.catch(error => { | |
console.error('Sunrise-Sunset API error:', error); | |
return { | |
sunrise: 'Недоступно', | |
sunset: 'Недоступно', | |
day_length: 'Недоступно' | |
}; | |
}); | |
} | |
// задержка для предотвращения превышения лимитов API | |
function delay(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
// основная функция для обработки запроса | |
function findPainting() { | |
const year = document.getElementById('yearInput').value; | |
const output = document.getElementById('result'); | |
const image = document.getElementById('paintingImage'); | |
output.textContent = 'Ищем картину...\n'; | |
image.style.display = 'none'; | |
image.src = ''; | |
if (!year || isNaN(year) || year < 1000 || year > 2025) { | |
output.textContent = 'Ошибка: Введите корректный год (1000-2025)'; | |
return; | |
} | |
getPaintingByYear(year) | |
.then(paintings => { | |
let attempts = 0; | |
const maxAttempts = paintings.length > 10 ? 10 : paintings.length; | |
function tryFindPainting() { | |
if (attempts >= maxAttempts) { | |
throw new Error('Не удалось найти картину с известным автором и городом рождения'); | |
} | |
const randomIndex = Math.floor(Math.random() * paintings.length); | |
const painting = paintings[randomIndex]; | |
attempts++; | |
if (!painting.people || painting.people.length === 0) { | |
return tryFindPainting(); | |
} | |
const artistId = painting.people[0].personid; | |
return getArtistInfo(artistId) | |
.then(artist => { | |
const birthplace = artist.displayname && artist.birthplace ? artist.birthplace : 'Неизвестно'; | |
if (!artist.displayname || birthplace === 'Неизвестно') { | |
return tryFindPainting(); | |
} | |
output.textContent += `Картина: ${painting.title || 'Без названия'}\n`; | |
if (painting.primaryimageurl) { | |
image.src = painting.primaryimageurl; | |
image.style.display = 'block'; | |
} else { | |
output.textContent += 'Изображение картины недоступно\n'; | |
} | |
output.textContent += `Художник: ${artist.displayname}\n`; | |
output.textContent += `Город рождения: ${birthplace}\n`; | |
return { painting, artist, birthplace }; | |
}); | |
} | |
return tryFindPainting(); | |
}) | |
.then(({ painting, artist, birthplace }) => { | |
return getCityCoordinates(birthplace) | |
.then(city => { | |
output.textContent += `Координаты города: (${city.lat}, ${city.lon})\n`; | |
output.textContent += `Полное название: ${city.display_name}\n`; | |
return city; | |
}); | |
}) | |
.then(city => { | |
return delay(1000).then(() => city); | |
}) | |
.then(city => { | |
return getCurrentTime(city.lat, city.lon) | |
.then(timeData => { | |
output.textContent += `Текущее время: ${timeData.currentTime} (${timeData.timezone})\n`; | |
return { city, timeData }; | |
}); | |
}) | |
.then(({ city, timeData }) => { | |
return getSunriseSunset(city.lat, city.lon, timeData.timezone) | |
.then(sunData => { | |
output.textContent += `Восход: ${sunData.sunrise}\n`; | |
output.textContent += `Закат: ${sunData.sunset}\n`; | |
output.textContent += `Длительность дня: ${sunData.day_length}\n`; | |
return city; | |
}); | |
}) | |
.then(city => { | |
return findNearestATB(city.lat, city.lon) | |
.then(atbInfo => { | |
output.textContent += `${atbInfo}\n`; | |
output.textContent += 'Задача выполнена!'; | |
}); | |
}) | |
.catch(error => { | |
console.error('Ошибка:', error); | |
output.textContent += `Ошибка: ${error.message || 'Не удалось выполнить запрос. Попробуйте снова.'}`; | |
}); | |
} | |
// обработчик нажатия Enter в поле ввода | |
document.getElementById('yearInput').addEventListener('keypress', function(event) { | |
if (event.key === 'Enter') { | |
findPainting(); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment