fix: anime api
This commit is contained in:
parent
75aa410f98
commit
39991d9ffc
@ -95,9 +95,24 @@ async def get_process_locks(
|
|||||||
|
|
||||||
|
|
||||||
class AnimeSummary(BaseModel):
|
class AnimeSummary(BaseModel):
|
||||||
id: str
|
"""Summary of an anime series with missing episodes."""
|
||||||
title: str
|
key: str # Unique identifier (used as id in frontend)
|
||||||
missing_episodes: int
|
name: str # Series name (can be empty)
|
||||||
|
site: str # Provider site
|
||||||
|
folder: str # Local folder name
|
||||||
|
missing_episodes: dict # Episode dictionary: {season: [episode_numbers]}
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Pydantic model configuration."""
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"key": "beheneko-the-elf-girls-cat",
|
||||||
|
"name": "Beheneko",
|
||||||
|
"site": "aniworld.to",
|
||||||
|
"folder": "beheneko the elf girls cat (2025)",
|
||||||
|
"missing_episodes": {"1": [1, 2, 3, 4]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AnimeDetail(BaseModel):
|
class AnimeDetail(BaseModel):
|
||||||
@ -203,26 +218,41 @@ async def list_anime(
|
|||||||
series = series_app.List.GetMissingEpisode()
|
series = series_app.List.GetMissingEpisode()
|
||||||
summaries: List[AnimeSummary] = []
|
summaries: List[AnimeSummary] = []
|
||||||
for serie in series:
|
for serie in series:
|
||||||
episodes_dict = getattr(serie, "episodeDict", {}) or {}
|
# Get all properties from the serie object
|
||||||
missing_episodes = len(episodes_dict)
|
key = getattr(serie, "key", "")
|
||||||
key = getattr(serie, "key", getattr(serie, "folder", ""))
|
name = getattr(serie, "name", "")
|
||||||
title = getattr(serie, "name", "")
|
site = getattr(serie, "site", "")
|
||||||
|
folder = getattr(serie, "folder", "")
|
||||||
|
episode_dict = getattr(serie, "episodeDict", {}) or {}
|
||||||
|
|
||||||
|
# Convert episode dict keys to strings for JSON serialization
|
||||||
|
missing_episodes = {str(k): v for k, v in episode_dict.items()}
|
||||||
|
|
||||||
summaries.append(
|
summaries.append(
|
||||||
AnimeSummary(
|
AnimeSummary(
|
||||||
id=key,
|
key=key,
|
||||||
title=title,
|
name=name,
|
||||||
|
site=site,
|
||||||
|
folder=folder,
|
||||||
missing_episodes=missing_episodes,
|
missing_episodes=missing_episodes,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply sorting if requested
|
# Apply sorting if requested
|
||||||
if sort_by:
|
if sort_by:
|
||||||
if sort_by == "title":
|
if sort_by in ["title", "name"]:
|
||||||
summaries.sort(key=lambda x: x.title)
|
summaries.sort(key=lambda x: x.name or x.key)
|
||||||
elif sort_by == "id":
|
elif sort_by == "id":
|
||||||
summaries.sort(key=lambda x: x.id)
|
summaries.sort(key=lambda x: x.key)
|
||||||
elif sort_by == "missing_episodes":
|
elif sort_by == "missing_episodes":
|
||||||
summaries.sort(key=lambda x: x.missing_episodes, reverse=True)
|
# Sort by total number of missing episodes
|
||||||
|
# (count all episodes across all seasons)
|
||||||
|
summaries.sort(
|
||||||
|
key=lambda x: sum(
|
||||||
|
len(eps) for eps in x.missing_episodes.values()
|
||||||
|
),
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
return summaries
|
return summaries
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
|
|||||||
@ -567,14 +567,24 @@ class AniWorldApp {
|
|||||||
|
|
||||||
// Check if response has the expected format
|
// Check if response has the expected format
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
// API returns array of AnimeSummary objects directly
|
// API returns array of AnimeSummary objects with full serie data
|
||||||
this.seriesData = data.map(anime => ({
|
this.seriesData = data.map(anime => {
|
||||||
id: anime.id,
|
// Count total missing episodes from the episode dictionary
|
||||||
name: anime.title,
|
const episodeDict = anime.missing_episodes || {};
|
||||||
title: anime.title,
|
const totalMissing = Object.values(episodeDict).reduce(
|
||||||
missing_episodes: anime.missing_episodes || 0,
|
(sum, episodes) => sum + (Array.isArray(episodes) ? episodes.length : 0),
|
||||||
episodeDict: {} // Will be populated when needed
|
0
|
||||||
}));
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: anime.key,
|
||||||
|
name: anime.name,
|
||||||
|
site: anime.site,
|
||||||
|
folder: anime.folder,
|
||||||
|
episodeDict: episodeDict,
|
||||||
|
missing_episodes: totalMissing
|
||||||
|
};
|
||||||
|
});
|
||||||
} else if (data.status === 'success') {
|
} else if (data.status === 'success') {
|
||||||
// Legacy format support
|
// Legacy format support
|
||||||
this.seriesData = data.series;
|
this.seriesData = data.series;
|
||||||
@ -633,7 +643,7 @@ class AniWorldApp {
|
|||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
if (this.sortAlphabetical) {
|
if (this.sortAlphabetical) {
|
||||||
// Pure alphabetical sorting when A-Z is enabled
|
// Pure alphabetical sorting when A-Z is enabled
|
||||||
return (a.name || a.folder).localeCompare(b.name || b.folder);
|
return this.getDisplayName(a).localeCompare(this.getDisplayName(b));
|
||||||
} else {
|
} else {
|
||||||
// Default sorting: missing episodes first (descending), then by name
|
// Default sorting: missing episodes first (descending), then by name
|
||||||
// Always show series with missing episodes first
|
// Always show series with missing episodes first
|
||||||
@ -705,7 +715,7 @@ class AniWorldApp {
|
|||||||
${isSelected ? 'checked' : ''}
|
${isSelected ? 'checked' : ''}
|
||||||
${!canBeSelected ? 'disabled' : ''}>
|
${!canBeSelected ? 'disabled' : ''}>
|
||||||
<div class="series-info">
|
<div class="series-info">
|
||||||
<h3>${this.escapeHtml(serie.name)}</h3>
|
<h3>${this.escapeHtml(this.getDisplayName(serie))}</h3>
|
||||||
<div class="series-folder">${this.escapeHtml(serie.folder)}</div>
|
<div class="series-folder">${this.escapeHtml(serie.folder)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="series-status">
|
<div class="series-status">
|
||||||
@ -849,8 +859,8 @@ class AniWorldApp {
|
|||||||
|
|
||||||
resultsList.innerHTML = results.map(result => `
|
resultsList.innerHTML = results.map(result => `
|
||||||
<div class="search-result-item">
|
<div class="search-result-item">
|
||||||
<span class="search-result-name">${this.escapeHtml(result.name)}</span>
|
<span class="search-result-name">${this.escapeHtml(this.getDisplayName(result))}</span>
|
||||||
<button class="btn btn-small btn-primary" onclick="app.addSeries('${this.escapeHtml(result.link)}', '${this.escapeHtml(result.name)}')">
|
<button class="btn btn-small btn-primary" onclick="app.addSeries('${this.escapeHtml(result.link)}', '${this.escapeHtml(this.getDisplayName(result))}')">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
@ -1026,6 +1036,24 @@ class AniWorldApp {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get display name for anime/series object.
|
||||||
|
* Returns name if available and not empty, otherwise returns key.
|
||||||
|
* @param {Object} anime - Anime/series object with name and key properties
|
||||||
|
* @returns {string} Display name
|
||||||
|
*/
|
||||||
|
getDisplayName(anime) {
|
||||||
|
if (!anime) return '';
|
||||||
|
// Use name if it exists and is not empty (after trimming whitespace)
|
||||||
|
const name = anime.name || '';
|
||||||
|
const trimmedName = name.trim();
|
||||||
|
if (trimmedName) {
|
||||||
|
return trimmedName;
|
||||||
|
}
|
||||||
|
// Fallback to key
|
||||||
|
return anime.key || anime.folder || '';
|
||||||
|
}
|
||||||
|
|
||||||
updateConnectionStatus() {
|
updateConnectionStatus() {
|
||||||
const indicator = document.getElementById('connection-status-display');
|
const indicator = document.getElementById('connection-status-display');
|
||||||
if (indicator) {
|
if (indicator) {
|
||||||
@ -2017,7 +2045,7 @@ class AniWorldApp {
|
|||||||
// Update current downloading
|
// Update current downloading
|
||||||
if (data.current_downloading) {
|
if (data.current_downloading) {
|
||||||
currentDownload.classList.remove('hidden');
|
currentDownload.classList.remove('hidden');
|
||||||
document.getElementById('current-serie-name').textContent = data.current_downloading.name;
|
document.getElementById('current-serie-name').textContent = this.getDisplayName(data.current_downloading);
|
||||||
document.getElementById('current-episode').textContent = `${data.current_downloading.missing_episodes} episodes remaining`;
|
document.getElementById('current-episode').textContent = `${data.current_downloading.missing_episodes} episodes remaining`;
|
||||||
} else {
|
} else {
|
||||||
currentDownload.classList.add('hidden');
|
currentDownload.classList.add('hidden');
|
||||||
@ -2028,7 +2056,7 @@ class AniWorldApp {
|
|||||||
queueList.innerHTML = data.queue.map((serie, index) => `
|
queueList.innerHTML = data.queue.map((serie, index) => `
|
||||||
<div class="queue-item">
|
<div class="queue-item">
|
||||||
<div class="queue-item-index">${index + 1}</div>
|
<div class="queue-item-index">${index + 1}</div>
|
||||||
<div class="queue-item-name">${this.escapeHtml(serie.name)}</div>
|
<div class="queue-item-name">${this.escapeHtml(this.getDisplayName(serie))}</div>
|
||||||
<div class="queue-item-status">Waiting</div>
|
<div class="queue-item-status">Waiting</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user