mirror of
https://github.com/venera-app/venera-configs.git
synced 2025-09-27 08:27:24 +00:00

* add new source from comick * fix some code * fix gif load and comic list info(none-type/chapter/volume) * add some comick hidden tags * revise coding error in file * info updata time * fix no-EN error * add new function - Multi-language comic selection support - Added comic recommendations - Fixed empty chapter return bug - Resolved tag click issues - Optimized data processing * Optimize network request Remove redundant requests and prevent async deadlocks * Update comick.js * new small comic source from baihehui * Fixed some bugs and added some sorting methods * Fixed some bugs and added some sorting methods * Add a new resource from ykmh * Remove invalid request * fixed chapter api * Update index.json * Update index.json * Update comick.js * fix search bug from manhuagui Fix ”querySelectorAll“ bug in search page. Add multi-group of chapters in info page. * add lanraragi
275 lines
13 KiB
JavaScript
275 lines
13 KiB
JavaScript
/** @type {import('./_venera_.js')} */
|
|
class Lanraragi extends ComicSource {
|
|
name = "Lanraragi"
|
|
key = "lanraragi"
|
|
version = "1.0.0"
|
|
minAppVersion = "1.4.0"
|
|
url = "https://git.nyne.dev/nyne/venera-configs/raw/branch/main/lanraragi.js"
|
|
|
|
settings = {
|
|
api: { title: "API", type: "input", default: "http://lrr.tvc-16.science" }
|
|
}
|
|
get baseUrl() {
|
|
|
|
const api = this.loadSetting('api') || this.settings.api.default
|
|
return api.replace(/\/$/, '')
|
|
}
|
|
|
|
async init() {
|
|
try {
|
|
const url = `${this.baseUrl}/api/categories`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) { this.saveData('categories', []); return }
|
|
let data = []
|
|
try { data = JSON.parse(res.body) } catch (_) { data = [] }
|
|
if (!Array.isArray(data)) data = []
|
|
this.saveData('categories', data)
|
|
this.saveData('categories_ts', Date.now())
|
|
} catch (_) { this.saveData('categories', []) }
|
|
}
|
|
|
|
// account = {
|
|
// login: async (account, pwd) => {},
|
|
// loginWithWebview: { url: "", checkStatus: (url, title) => false, onLoginSuccess: () => {} },
|
|
// loginWithCookies: { fields: ["ipb_member_id","ipb_pass_hash","igneous","star"], validate: async (values) => false },
|
|
// logout: () => {},
|
|
// registerWebsite: null,
|
|
// }
|
|
|
|
explore = [
|
|
{ title: "Lanraragi", type: "multiPageComicList", load: async (page = 1) => {
|
|
const url = `${this.baseUrl}/api/archives`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const list = data.slice((page-1)*50, page*50)
|
|
const parseComic = (item) => {
|
|
let base = this.baseUrl.replace(/\/$/, '')
|
|
if (!/^https?:\/\//.test(base)) base = 'http://' + base
|
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
|
return new Comic({ id: item.arcid, title: item.title, subTitle: '', cover, tags: item.tags ? item.tags.split(',').map(t=>t.trim()).filter(Boolean) : [], description: `页数: ${item.pagecount} | 新: ${item.isnew} | 扩展: ${item.extension}` })
|
|
}
|
|
return { comics: list.map(parseComic), maxPage: Math.ceil(data.length/50) }
|
|
}}
|
|
]
|
|
|
|
category = {
|
|
title: "Lanraragi",
|
|
parts: [ { name: "ALL", type: "dynamic", loader: () => {
|
|
const data = this.loadData('categories')
|
|
if (!Array.isArray(data) || data.length === 0) throw 'Please check your API settings or categories.'
|
|
const items = []
|
|
for (const cat of data) {
|
|
if (!cat) continue
|
|
const id = cat.id ?? cat._id ?? cat.name
|
|
const label = cat.name ?? String(id)
|
|
try { items.push({ label, target: new PageJumpTarget({ page: 'category', attributes: { category: id, param: null } }) }) }
|
|
catch (_) { items.push({ label, target: { page: 'category', attributes: { category: id, param: null } } }) }
|
|
}
|
|
return items
|
|
} } ],
|
|
enableRankingPage: false,
|
|
}
|
|
|
|
categoryComics = {
|
|
load: async (category, param, options, page) => {
|
|
// Use /search endpoint filtered by category tag value
|
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
|
const pageSize = 100
|
|
const start = Math.max(0, (page - 1) * pageSize)
|
|
|
|
const qp = []
|
|
const add = (k, v) => qp.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
add('draw', String(Date.now() % 1000))
|
|
add('columns[0][data]', '')
|
|
add('columns[0][name]', 'title')
|
|
add('columns[0][searchable]', 'true')
|
|
add('columns[0][orderable]', 'true')
|
|
add('columns[0][search][value]', '')
|
|
add('columns[0][search][regex]', 'false')
|
|
add('columns[1][data]', 'tags')
|
|
add('columns[1][name]', 'artist')
|
|
add('columns[1][searchable]', 'true')
|
|
add('columns[1][orderable]', 'true')
|
|
add('columns[1][search][value]', '')
|
|
add('columns[1][search][regex]', 'false')
|
|
add('columns[2][data]', 'tags')
|
|
add('columns[2][name]', 'series')
|
|
add('columns[2][searchable]', 'true')
|
|
add('columns[2][orderable]', 'true')
|
|
add('columns[2][search][value]', '')
|
|
add('columns[2][search][regex]', 'false')
|
|
add('columns[3][data]', 'tags')
|
|
add('columns[3][name]', 'tags')
|
|
add('columns[3][searchable]', 'true')
|
|
add('columns[3][orderable]', 'false')
|
|
// Filter by category identifier in tags column
|
|
add('columns[3][search][value]', category || '')
|
|
add('columns[3][search][regex]', 'false')
|
|
add('order[0][column]', '0')
|
|
add('order[0][dir]', 'asc')
|
|
add('start', String(start))
|
|
add('length', String(pageSize))
|
|
add('search[value]', '')
|
|
add('search[regex]', 'false')
|
|
|
|
const url = `${base}/search?${qp.join('&')}`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const list = Array.isArray(data.data) ? data.data : []
|
|
const comics = list.map(item => {
|
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
|
const tags = item.tags ? item.tags.split(',').map(t => t.trim()).filter(Boolean) : []
|
|
return new Comic({
|
|
id: item.arcid,
|
|
title: item.title || item.filename || item.arcid,
|
|
subTitle: '',
|
|
cover,
|
|
tags,
|
|
description: `页数: ${item.pagecount} | 新: ${item.isnew} | 扩展: ${item.extension}`
|
|
})
|
|
})
|
|
|
|
const total = typeof data.recordsFiltered === 'number' && data.recordsFiltered >= 0
|
|
? data.recordsFiltered
|
|
: (list.length < pageSize ? start + list.length : start + pageSize)
|
|
const maxPage = Math.max(1, Math.ceil(total / pageSize))
|
|
return { comics, maxPage }
|
|
}
|
|
}
|
|
|
|
search = {
|
|
load: async (keyword, options, page = 1) => {
|
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
|
|
|
// Fetch all results once (start=-1), then page locally for consistent UX across servers
|
|
const qp = []
|
|
const add = (k, v) => qp.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
const pick = (key, def) => {
|
|
let v = options && (options[key])
|
|
if (typeof v === 'string') {
|
|
const idx = v.indexOf('-');
|
|
if (idx > 0) v = v.slice(0, idx)
|
|
}
|
|
return (v === undefined || v === null || v === '') ? def : v
|
|
}
|
|
const sortby = pick(0, 'title')
|
|
const order = pick(1, 'asc')
|
|
const newonly = String(pick(2, 'false'))
|
|
const untaggedonly = String(pick(3, 'false'))
|
|
const groupby = String(pick(4, 'true'))
|
|
|
|
add('filter', (keyword || '').trim())
|
|
add('start', '-1')
|
|
add('sortby', sortby)
|
|
add('order', order)
|
|
add('newonly', newonly)
|
|
add('untaggedonly', untaggedonly)
|
|
add('groupby_tanks', groupby)
|
|
|
|
const url = `${base}/api/search?${qp.join('&')}`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const all = Array.isArray(data.data) ? data.data : []
|
|
|
|
const pageSize = 100
|
|
const start = Math.max(0, (page - 1) * pageSize)
|
|
const slice = all.slice(start, start + pageSize)
|
|
|
|
const comics = slice.map(item => {
|
|
const cover = `${base}/api/archives/${item.arcid}/thumbnail`
|
|
const tags = item.tags ? item.tags.split(',').map(t => t.trim()).filter(Boolean) : []
|
|
return new Comic({
|
|
id: item.arcid,
|
|
title: item.title || item.filename || item.arcid,
|
|
subTitle: '',
|
|
cover,
|
|
tags,
|
|
description: `页数: ${item.pagecount ?? ''} | 新: ${item.isnew ?? ''} | 扩展: ${item.extension ?? ''}`
|
|
})
|
|
})
|
|
|
|
const total = (typeof data.recordsFiltered === 'number' && data.recordsFiltered >= 0)
|
|
? data.recordsFiltered
|
|
: all.length
|
|
const maxPage = Math.max(1, Math.ceil(total / pageSize))
|
|
return { comics, maxPage }
|
|
},
|
|
loadNext: async (keyword, options, next) => {
|
|
const page = (typeof next === 'number' && next > 0) ? next : 1
|
|
return await this.search.load(keyword, options, page)
|
|
},
|
|
optionList: [
|
|
{ type: "select", options: ["title-按标题","lastread-最近阅读"], label: "sortby", default: "title" },
|
|
{ type: "select", options: ["asc-升序","desc-降序"], label: "order", default: "asc" },
|
|
{ type: "select", options: ["false-全部","true-仅新"], label: "newonly", default: "false" },
|
|
{ type: "select", options: ["false-全部","true-仅未打标签"], label: "untaggedonly", default: "false" },
|
|
{ type: "select", options: ["true-启用","false-禁用"], label: "groupby_tanks", default: "true" }
|
|
],
|
|
enableTagsSuggestions: false,
|
|
}
|
|
|
|
// favorites = {
|
|
// multiFolder: false,
|
|
// addOrDelFavorite: async (comicId, folderId, isAdding, favoriteId) => {},
|
|
// loadFolders: async (comicId) => {},
|
|
// addFolder: async (name) => {},
|
|
// deleteFolder: async (folderId) => {},
|
|
// loadComics: async (page, folder) => {},
|
|
// loadNext: async (next, folder) => {},
|
|
// singleFolderForSingleComic: false,
|
|
// }
|
|
|
|
comic = {
|
|
loadInfo: async (id) => {
|
|
const url = `${this.baseUrl}/api/archives/${id}/metadata`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const cover = `${this.baseUrl}/api/archives/${id}/thumbnail`
|
|
let tags = data.tags ? data.tags.split(',').map(t=>t.trim()).filter(Boolean) : []
|
|
const rating = tags.find(t=>t.startsWith('rating:'))
|
|
if (rating) tags = tags.filter(t=>!t.startsWith('rating:'))
|
|
const chapters = new Map(); chapters.set(id, data.title || 'Local manga')
|
|
return { title: data.title || data.filename || id, cover, description: data.summary || '', tags: { "Tags": tags, "Extension": [data.extension], "Rating": rating ? [rating.replace('rating:', '')] : [], "Page": [String(data.pagecount)] }, chapters }
|
|
},
|
|
loadThumbnails: async (id, next) => {
|
|
const metaUrl = `${this.baseUrl}/api/archives/${id}/metadata`
|
|
const res = await Network.get(metaUrl)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const pagecount = data.pagecount || 1
|
|
const thumbnails = []
|
|
for (let i = 1; i <= pagecount; i++) thumbnails.push(`${this.baseUrl}/api/archives/${id}/thumbnail?page=${i}`)
|
|
return { thumbnails, next: null }
|
|
},
|
|
starRating: async (id, rating) => {},
|
|
loadEp: async (comicId, epId) => {
|
|
const base = (this.baseUrl || '').replace(/\/$/, '')
|
|
const url = `${base}/api/archives/${comicId}/files?force=false`
|
|
const res = await Network.get(url)
|
|
if (res.status !== 200) throw `Invalid status code: ${res.status}`
|
|
const data = JSON.parse(res.body)
|
|
const images = (data.pages || []).map(p => {
|
|
if (!p) return null
|
|
const s = String(p)
|
|
if (/^https?:\/\//i.test(s)) return s
|
|
return `${base}${s.startsWith('/') ? s : '/' + s}`
|
|
}).filter(Boolean)
|
|
return { images }
|
|
},
|
|
// onImageLoad: (url, comicId, epId) => ({}),
|
|
// onThumbnailLoad: (url) => ({}),
|
|
// likeComic: async (id, isLike) => {},
|
|
// loadComments: async (comicId, subId, page, replyTo) => {},
|
|
// sendComment: async (comicId, subId, content, replyTo) => {},
|
|
// likeComment: async (comicId, subId, commentId, isLike) => {},
|
|
// voteComment: async (id, subId, commentId, isUp, isCancel) => {},
|
|
// idMatch: null,
|
|
// onClickTag: (namespace, tag) => {},
|
|
// link: { domains: ['example.com'], linkToId: (url) => null },
|
|
enableTagsTranslate: false,
|
|
}
|
|
} |