Add cxyz Android project structure and initial configuration
- Introduced a new Flutter project for the cxyz Android manga/comic reader, including essential files such as pubspec.yaml, AndroidManifest.xml, and Gradle configuration. - Added .gitignore files to exclude unnecessary files and directories for both the main project and Android-specific files. - Implemented initial README.md with project description, features, setup instructions, and API details for ComicLand, MangaRead, and MangaTown. - Configured analysis options and dependencies in pubspec.yaml to support Flutter development. - Established a pnpm workspace and lockfile for dependency management. - Included various Android resources such as icons, styles, and launch backgrounds for a complete application setup.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import '../source.dart';
|
||||
import 'constants.dart';
|
||||
|
||||
class ComicLandUrlParts {
|
||||
const ComicLandUrlParts({
|
||||
required this.origin,
|
||||
required this.slug,
|
||||
this.chapterIndex,
|
||||
});
|
||||
|
||||
final String origin;
|
||||
final String slug;
|
||||
final String? chapterIndex;
|
||||
}
|
||||
|
||||
bool isLikelyComicLandSite(String url) {
|
||||
try {
|
||||
final host = Uri.parse(url).host.toLowerCase();
|
||||
return comicLandHosts.contains(host) || host.contains('comicland');
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String seriesSlugFromListingUrl(String pageUrl) {
|
||||
try {
|
||||
final parts = Uri.parse(pageUrl).pathSegments.where((s) => s.isNotEmpty).toList();
|
||||
final comicIdx = parts.indexOf('comic');
|
||||
if (comicIdx != -1 && comicIdx + 1 < parts.length) {
|
||||
return parts[comicIdx + 1];
|
||||
}
|
||||
} catch (_) {}
|
||||
return 'comic';
|
||||
}
|
||||
|
||||
ComicLandUrlParts parseComicLandUrl(String pageUrl) {
|
||||
final u = Uri.parse(pageUrl);
|
||||
final parts = u.pathSegments.where((s) => s.isNotEmpty).toList();
|
||||
final comicIdx = parts.indexOf('comic');
|
||||
final slug =
|
||||
comicIdx != -1 && comicIdx + 1 < parts.length ? parts[comicIdx + 1] : 'comic';
|
||||
String? chapterIndex;
|
||||
final chapterIdx = parts.indexOf('chapter');
|
||||
if (chapterIdx != -1 && chapterIdx + 1 < parts.length) {
|
||||
chapterIndex = parts[chapterIdx + 1];
|
||||
}
|
||||
return ComicLandUrlParts(
|
||||
origin: '${u.scheme}://${u.host}',
|
||||
slug: slug,
|
||||
chapterIndex: chapterIndex,
|
||||
);
|
||||
}
|
||||
|
||||
String chapterUrlFromParts(String siteOrigin, String slug, Object chapterIndex) {
|
||||
final origin = siteOrigin.replaceAll(RegExp(r'/+$'), '');
|
||||
return '$origin/comic/$slug/chapter/$chapterIndex';
|
||||
}
|
||||
|
||||
String listingUrlForSlug(String slug) =>
|
||||
'${comicLandOrigin.replaceAll(RegExp(r'/+$'), '')}/comic/$slug';
|
||||
|
||||
String chapterKeyFromUrl(String chapterUrl) {
|
||||
try {
|
||||
final segs = Uri.parse(chapterUrl).pathSegments.where((s) => s.isNotEmpty).toList();
|
||||
return segs.isEmpty ? 'chapter' : segs.last;
|
||||
} catch (_) {
|
||||
return 'chapter';
|
||||
}
|
||||
}
|
||||
|
||||
String? chapterNumberFromTitle(String title) {
|
||||
final m = RegExp(r'^Chapter\s+([\d.]+)', caseSensitive: false)
|
||||
.firstMatch(title.trim().replaceAll(RegExp(r'\s+'), ' '));
|
||||
return m?.group(1);
|
||||
}
|
||||
|
||||
String sanitizeFsStem(String stem) {
|
||||
final cleaned = stem
|
||||
.replaceAll(RegExp(r'[^a-z0-9._-]+', caseSensitive: false), '_')
|
||||
.replaceAll(RegExp(r'^_+|_+$'), '');
|
||||
if (cleaned.isEmpty) return 'chapter';
|
||||
return cleaned.length > 180 ? cleaned.substring(0, 180) : cleaned;
|
||||
}
|
||||
|
||||
String sanitizeComicDirName(String name) {
|
||||
final collapsed = name.trim().replaceAll(RegExp(r'\s+'), ' ');
|
||||
final cleaned = String.fromCharCodes(
|
||||
collapsed.runes.where((code) {
|
||||
if (code < 32 || code == 127) return false;
|
||||
final ch = String.fromCharCode(code);
|
||||
return !RegExp(r'[<>:"/\\|?*]').hasMatch(ch);
|
||||
}),
|
||||
).replaceAll(RegExp(r'\.+$'), '').trim();
|
||||
if (cleaned.isEmpty) return 'comic';
|
||||
return cleaned.length > 180 ? cleaned.substring(0, 180) : cleaned;
|
||||
}
|
||||
|
||||
String stemForComicLandChapterCbz(String chapterPageUrl) {
|
||||
try {
|
||||
final segs =
|
||||
Uri.parse(chapterPageUrl).pathSegments.where((s) => s.isNotEmpty).toList();
|
||||
final comicIdx = segs.indexOf('comic');
|
||||
if (comicIdx != -1 &&
|
||||
segs.length >= comicIdx + 4 &&
|
||||
segs[comicIdx + 2] == 'chapter') {
|
||||
return '${segs[comicIdx + 1]}-chapter-${segs[comicIdx + 3]}';
|
||||
}
|
||||
if (segs.length >= 2) {
|
||||
return '${segs[segs.length - 2]}-${segs.last}';
|
||||
}
|
||||
return segs.isEmpty ? 'chapter' : segs.last;
|
||||
} catch (_) {
|
||||
return 'chapter';
|
||||
}
|
||||
}
|
||||
|
||||
String transferLinkBasename(String? chapterNumber, String? label, String? cbzPath) {
|
||||
final candidates = [chapterNumber, label]
|
||||
.whereType<String>()
|
||||
.where((s) => s.trim().isNotEmpty);
|
||||
for (final raw in candidates) {
|
||||
final text = raw.trim();
|
||||
final m = RegExp(r'(\d+)((?:\.\d+)*)([a-zA-Z]*)').firstMatch(text);
|
||||
if (m != null) {
|
||||
final padded = m.group(1)!.padLeft(3, '0');
|
||||
return 'chapter $padded${m.group(2) ?? ''}${m.group(3) ?? ''}.cbz';
|
||||
}
|
||||
}
|
||||
if (cbzPath != null && cbzPath.isNotEmpty) {
|
||||
final base = cbzPath.split('/').last;
|
||||
if (base.isNotEmpty) return base;
|
||||
}
|
||||
return 'chapter unknown.cbz';
|
||||
}
|
||||
|
||||
CatalogComic catalogFromJson(Map<String, dynamic> row) {
|
||||
final slug = (row['slug'] as String?)?.trim() ?? 'comic';
|
||||
return CatalogComic(
|
||||
source: SourceId.comicland,
|
||||
slug: slug,
|
||||
title: (row['title'] as String?)?.trim().isNotEmpty == true
|
||||
? (row['title'] as String).trim()
|
||||
: slug,
|
||||
listingUrl: listingUrlForSlug(slug),
|
||||
coverUrl: row['cover_url'] as String?,
|
||||
description: row['description'] as String?,
|
||||
rating: (row['rating'] is num) ? (row['rating'] as num).toDouble() : null,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user