Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active May 2, 2025 10:55
Show Gist options
  • Save sunmeat/404f684da54c7e1b6fe5d4957e410bd6 to your computer and use it in GitHub Desktop.
Save sunmeat/404f684da54c7e1b6fe5d4957e410bd6 to your computer and use it in GitHub Desktop.
апи плюс промисы
<!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