File size: 4,509 Bytes
ccd306d 695d6e5 ccd306d 695d6e5 ccd306d 695d6e5 ccd306d 695d6e5 72d416a 1b98a60 461fbba 695d6e5 1b98a60 695d6e5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YT Search</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f9f9f9; }
#search-bar { display: flex; justify-content: center; padding: 20px; background-color: #333; }
#search-bar input { width: 70%; padding: 10px; font-size: 16px; }
#search-bar button { padding: 10px; font-size: 16px; }
#results { display: flex; flex-wrap: wrap; gap: 15px; padding: 20px; }
.tile { background: #fff; border: 1px solid #ddd; border-radius: 5px; width: 300px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.tile img { width: 100%; height: auto; }
.tile h3 { font-size: 18px; padding: 10px; margin: 0; }
.tile p { padding: 0 10px; margin: 0 0 10px; font-size: 14px; }
.uploader { display: flex; align-items: center; padding: 10px; border-top: 1px solid #eee; }
.uploader img { width: 30px; height: 30px; border-radius: 50%; margin-right: 10px; }
</style>
</head>
<body>
<div id="search-bar">
<input type="text" id="query" placeholder="Search for videos">
<button onclick="searchVideos()">Search</button>
</div>
<div id="results"></div>
<script>
async function searchVideos() {
const query = document.getElementById('query').value;
if (!query) return;
const response = await fetch(`/search?query=${encodeURIComponent(query)}`);
const data = await response.json();
const results = document.getElementById('results');
results.innerHTML = '';
if (data.error) {
results.innerHTML = `<p>Error: ${data.error}</p>`;
return;
}
data.forEach(video => {
const tile = document.createElement('div');
tile.className = 'tile';
// サムネイルURLが空の場合にデフォルトのサムネイルを使用
const thumbnail = video.thumbnail || `https://inv.nadeko.net/vi/${video.url.split('v=')[1]}/mqdefault.jpg`;
// チャンネルアイコンURLを取得
fetch(`/channel-icon?channel_id=${video.uploader_url.split('/').pop()}`)
.then(response => response.json())
.then(data => {
const uploaderIcon = data.icon_url || 'https://upload.wikimedia.org/wikipedia/commons/4/42/Gray_chaos_circle.svg'; // デフォルトのアイコン
tile.innerHTML = `
<img src="${thumbnail}" alt="Thumbnail">
<h3>${video.title}</h3>
<p>Views: ${video.view_count.toLocaleString()}</p>
<p>Duration: ${Math.floor(video.duration / 60)}:${(video.duration % 60).toString().padStart(2, '0')}</p>
<div class="uploader">
<img src="${uploaderIcon}" alt="Uploader">
<span>${video.uploader}</span>
</div>
`;
})
.catch(error => {
console.error('Error fetching channel icon:', error);
const uploaderIcon = 'https://upload.wikimedia.org/wikipedia/commons/4/42/Gray_chaos_circle.svg'; // デフォルトのアイコン
tile.innerHTML = `
<img src="${thumbnail}" alt="Thumbnail">
<h3>${video.title}</h3>
<p>Views: ${video.view_count.toLocaleString()}</p>
<p>Duration: ${Math.floor(video.duration / 60)}:${(video.duration % 60).toString().padStart(2, '0')}</p>
<div class="uploader">
<img src="${uploaderIcon}" alt="Uploader">
<span>${video.uploader}</span>
</div>
`;
});
tile.addEventListener('click', () => {
window.open(`https://www.youtube.com/watch?v=${video.url.split('v=')[1]}`, '_blank');
});
results.appendChild(tile);
});
}
</script>
</body>
</html>
|