Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import 'package:openlib/state/state.dart'
openPdfWithExternalAppProvider,
openEpubWithExternalAppProvider,
userAgentProvider,
cookieProvider;
cookieProvider,
baseUrlProvider;

void main() async {
WidgetsFlutterBinding.ensureInitialized();
Expand All @@ -54,6 +55,12 @@ void main() async {

String browserUserAgent = await dataBase.getBrowserOptions('userAgent');
String browserCookie = await dataBase.getBrowserOptions('cookie');
String annasArchiveBaseUrl;
try {
annasArchiveBaseUrl = await dataBase.getPreference('annasArchiveBaseUrl');
} catch (e) {
annasArchiveBaseUrl = 'https://annas-archive.li';
}

if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
Expand All @@ -73,6 +80,7 @@ void main() async {
.overrideWith((ref) => openEpubwithExternalapp),
userAgentProvider.overrideWith((ref) => browserUserAgent),
cookieProvider.overrideWith((ref) => browserCookie),
baseUrlProvider.overrideWith((ref) => annasArchiveBaseUrl),
],
child: const MyApp(),
),
Expand Down Expand Up @@ -144,8 +152,7 @@ class _MainScreenState extends ConsumerState<MainScreen> {
child: AppBar(
backgroundColor: Theme.of(context).colorScheme.surface,
title: const Text("Openlib"),
titleTextStyle:
Theme.of(context).textTheme.displayLarge,
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
),
Expanded(
Expand All @@ -156,8 +163,7 @@ class _MainScreenState extends ConsumerState<MainScreen> {
),
bottomNavigationBar: SafeArea(
child: GNav(
backgroundColor:
isDarkMode ? Colors.black : Colors.grey.shade200,
backgroundColor: isDarkMode ? Colors.black : Colors.grey.shade200,
haptic: true,
tabBorderRadius: 50,
tabActiveBorder: Border.all(
Expand All @@ -170,10 +176,8 @@ class _MainScreenState extends ConsumerState<MainScreen> {
color: Colors.white,
activeColor: Colors.white,
iconSize: 19,
tabBackgroundColor:
Theme.of(context).colorScheme.secondary,
padding:
const EdgeInsets.symmetric(horizontal: 13, vertical: 6.5),
tabBackgroundColor: Theme.of(context).colorScheme.secondary,
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 6.5),
tabs: const [
GButton(icon: Icons.trending_up, text: 'Home'),
GButton(icon: Icons.search, text: 'Search'),
Expand Down
90 changes: 51 additions & 39 deletions lib/services/annas_archieve.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ class BookInfoData extends BookData {
// ====================================================================

class AnnasArchieve {
static const String baseUrl = "https://annas-archive.se";
String baseUrl;

AnnasArchieve({this.baseUrl = "https://annas-archive.li"});

final Dio dio = Dio();

Expand Down Expand Up @@ -91,7 +93,7 @@ class AnnasArchieve {
}
return value;
}

// --------------------------------------------------------------------
// _parser FUNCTION (Search Results - Fixed nth-of-type issue)
// --------------------------------------------------------------------
Expand All @@ -108,7 +110,8 @@ class AnnasArchieve {
container.querySelector('a.line-clamp-\\[3\\].js-vim-focus');
final thumbnailElement = container.querySelector('a[href^="/md5/"] img');

if (mainLinkElement == null || mainLinkElement.attributes['href'] == null) {
if (mainLinkElement == null ||
mainLinkElement.attributes['href'] == null) {
continue;
}

Expand All @@ -120,27 +123,31 @@ class AnnasArchieve {
// Fix: Use sequential traversal instead of :nth-of-type
dom.Element? authorLinkElement = mainLinkElement.nextElementSibling;
dom.Element? publisherLinkElement = authorLinkElement?.nextElementSibling;

if (authorLinkElement?.attributes['href']?.startsWith('/search?q=') != true) {
authorLinkElement = null;

if (authorLinkElement?.attributes['href']?.startsWith('/search?q=') !=
true) {
authorLinkElement = null;
}
if (publisherLinkElement?.attributes['href']?.startsWith('/search?q=') != true) {
publisherLinkElement = null;
if (publisherLinkElement?.attributes['href']?.startsWith('/search?q=') !=
true) {
publisherLinkElement = null;
}

final String? authorRaw = authorLinkElement?.text.trim();
final String? author = (authorRaw != null && authorRaw.contains('icon-'))
? authorRaw.split(' ').skip(1).join(' ').trim()
: authorRaw;

final String? publisher = publisherLinkElement?.text.trim();

final infoElement = container.querySelector('div.text-gray-800');
// No need for _safeParse here if we only treat info as a string
final String? info = infoElement?.text.trim();
final String? info = infoElement?.text.trim();

final bool hasMatchingFileType = fileType.isEmpty
? (info?.contains(RegExp(r'(PDF|EPUB|CBR|CBZ)', caseSensitive: false)) == true)
? (info?.contains(
RegExp(r'(PDF|EPUB|CBR|CBZ)', caseSensitive: false)) ==
true)
: info?.toLowerCase().contains(fileType.toLowerCase()) == true;

if (hasMatchingFileType) {
Expand All @@ -165,59 +172,64 @@ class AnnasArchieve {
// --------------------------------------------------------------------
Future<BookInfoData?> _bookInfoParser(resData, url) async {
var document = parse(resData.toString());
final main = document.querySelector('div.main-inner');
final main = document.querySelector('div.main-inner');
if (main == null) return null;

// --- Mirror Link Extraction ---
String? mirror;
final slowDownloadLinks = main.querySelectorAll('ul.list-inside a[href*="/slow_download/"]');
if (slowDownloadLinks.isNotEmpty && slowDownloadLinks.first.attributes['href'] != null) {
mirror = baseUrl + slowDownloadLinks.first.attributes['href']!;
final slowDownloadLinks =
main.querySelectorAll('ul.list-inside a[href*="/slow_download/"]');
if (slowDownloadLinks.isNotEmpty &&
slowDownloadLinks.first.attributes['href'] != null) {
mirror = baseUrl + slowDownloadLinks.first.attributes['href']!;
}
// --------------------------------


// --- Core Info Extraction ---

// Title
final titleElement = main.querySelector('div.font-semibold.text-2xl');
final titleElement = main.querySelector('div.font-semibold.text-2xl');

// Author
final authorLinkElement = main.querySelector('a[href^="/search?q="].text-base');

final authorLinkElement =
main.querySelector('a[href^="/search?q="].text-base');

// Publisher
dom.Element? publisherLinkElement = authorLinkElement?.nextElementSibling;
if (publisherLinkElement?.localName != 'a' || publisherLinkElement?.attributes['href']?.startsWith('/search?q=') != true) {
publisherLinkElement = null;
if (publisherLinkElement?.localName != 'a' ||
publisherLinkElement?.attributes['href']?.startsWith('/search?q=') !=
true) {
publisherLinkElement = null;
}

// Thumbnail
final thumbnailElement = main.querySelector('div[id^="list_cover_"] img');

// Info/Metadata
final infoElement = main.querySelector('div.text-gray-800');

// Description
dom.Element? descriptionElement;
final descriptionLabel = main.querySelector('div.js-md5-top-box-description div.text-xs.text-gray-500.uppercase');

final descriptionLabel = main.querySelector(
'div.js-md5-top-box-description div.text-xs.text-gray-500.uppercase');

if (descriptionLabel?.text.trim().toLowerCase() == 'description') {
descriptionElement = descriptionLabel?.nextElementSibling;
descriptionElement = descriptionLabel?.nextElementSibling;
}
String description = descriptionElement?.text.trim() ?? " ";

if (titleElement == null) {
return null;
}

final String title = titleElement.text.trim().split('<span')[0].trim();
final String title = titleElement.text.trim().split('<span')[0].trim();
final String author = authorLinkElement?.text.trim() ?? "unknown";
final String? thumbnail = thumbnailElement?.attributes['src'];

final String publisher = publisherLinkElement?.text.trim() ?? "unknown";
// NOTE: If you extract any numeric data from the 'info' string later in your app (e.g., file size or page count)
// and attempt to convert it to an integer or double, that's where you should use _safeParse.
final String info = infoElement?.text.trim() ?? '';
final String info = infoElement?.text.trim() ?? '';

return BookInfoData(
title: title,
Expand Down Expand Up @@ -265,10 +277,10 @@ class AnnasArchieve {
options: Options(headers: defaultDioHeaders));
return _parser(response.data, fileType);
} on DioException catch (e) {
if (e.type == DioExceptionType.unknown) {
throw "socketException";
}
rethrow;
if (e.type == DioExceptionType.unknown) {
throw "socketException";
}
rethrow;
}
}

Expand All @@ -279,7 +291,7 @@ class AnnasArchieve {
BookInfoData? data = await _bookInfoParser(response.data, url);
if (data != null) {
// Here's where you might use _safeParse if the API returned a numeric field
// E.g., int pages = _safeParse(data.pages).toInt();
// E.g., int pages = _safeParse(data.pages).toInt();
return data;
} else {
throw 'unable to get data';
Expand All @@ -291,4 +303,4 @@ class AnnasArchieve {
rethrow;
}
}
}
}
2 changes: 2 additions & 0 deletions lib/services/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ class MyLibraryDb {
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('openEpubwithExternalApp', 0)");
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('bookStorageDirectory', '$bookStorageDefaultDirectory')");
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('annasArchiveBaseUrl', 'https://annas-archive.li')");
},
);
}
Expand Down
32 changes: 21 additions & 11 deletions lib/state/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,18 @@ final enableFiltersState = StateProvider<bool>((ref) => true);
// Web/Download States
final cookieProvider = StateProvider<String>((ref) => "");
final userAgentProvider = StateProvider<String>((ref) => "");
final baseUrlProvider =
StateProvider<String>((ref) => "https://annas-archive.li");
Comment on lines 80 to +83
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new configurable baseUrlProvider is only wired into search/book-info requests. Download mirror handling still hard-codes https://annas-archive.se in lib/services/download_file.dart (and even drops those URLs in _reorderMirrors), which will make downloads behave incorrectly if the user configures the base URL to that domain. Consider refactoring mirror ordering/filtering to not hard-code a specific domain and/or to use the configured base URL.

Copilot uses AI. Check for mistakes.
final webViewLoadingState = StateProvider.autoDispose<bool>((ref) => true);
final downloadProgressProvider = StateProvider.autoDispose<double>((ref) => 0.0);
final downloadProgressProvider =
StateProvider.autoDispose<double>((ref) => 0.0);
final mirrorStatusProvider = StateProvider.autoDispose<bool>((ref) => false);
final totalFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
final downloadedFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
final downloadState = StateProvider.autoDispose<ProcessState>((ref) => ProcessState.waiting);
final checkSumState = StateProvider.autoDispose<CheckSumProcessState>((ref) => CheckSumProcessState.waiting);
final downloadState =
StateProvider.autoDispose<ProcessState>((ref) => ProcessState.waiting);
final checkSumState = StateProvider.autoDispose<CheckSumProcessState>(
(ref) => CheckSumProcessState.waiting);
final cancelCurrentDownload = StateProvider<CancelToken>((ref) {
return CancelToken();
});
Expand Down Expand Up @@ -140,20 +145,22 @@ final getTrendingBooks = FutureProvider<List<TrendingBookData>>((ref) async {
GoodReads goodReads = GoodReads();
// Assuming these classes are available from your project imports
// ignore: prefer_const_constructors
final penguinTrending = PenguinRandomHouse();
final penguinTrending = PenguinRandomHouse();
// ignore: prefer_const_constructors
final bookDigits = BookDigits();

List<TrendingBookData> trendingBooks = await Future.wait<List<TrendingBookData>>([
List<TrendingBookData> trendingBooks =
await Future.wait<List<TrendingBookData>>([
goodReads.trendingBooks(),
penguinTrending.trendingBooks(),
// openLibrary.trendingBooks(), // Commented out as in the original
bookDigits.trendingBooks(),
]).then((List<List<TrendingBookData>> listOfData) =>
listOfData.expand((element) => element).toList());
listOfData.expand((element) => element).toList());

if (trendingBooks.isEmpty) {
throw Exception('Nothing Trending Today :('); // Use Exception instead of String
throw Exception(
'Nothing Trending Today :('); // Use Exception instead of String
}
trendingBooks.shuffle();
return trendingBooks;
Expand All @@ -175,9 +182,11 @@ final getSubCategoryTypeList = FutureProvider.family
// Provider for Anna's Archive Search Results
final searchProvider = FutureProvider.family
.autoDispose<List<BookData>, String>((ref, searchQuery) async {
if (searchQuery.isEmpty) return []; // Return empty list if search query is empty
if (searchQuery.isEmpty)
return []; // Return empty list if search query is empty

final AnnasArchieve annasArchieve = AnnasArchieve();
final String baseUrl = ref.watch(baseUrlProvider);
final AnnasArchieve annasArchieve = AnnasArchieve(baseUrl: baseUrl);
List<BookData> data = await annasArchieve.searchBooks(
searchQuery: searchQuery,
content: ref.watch(getTypeValue),
Expand All @@ -190,7 +199,8 @@ final searchProvider = FutureProvider.family
// Provider for Book Info Details
final bookInfoProvider =
FutureProvider.family<BookInfoData, String>((ref, url) async {
final AnnasArchieve annasArchieve = AnnasArchieve();
final String baseUrl = ref.watch(baseUrlProvider);
final AnnasArchieve annasArchieve = AnnasArchieve(baseUrl: baseUrl);
BookInfoData data = await annasArchieve.bookInfo(url: url);
return data;
});
Expand Down Expand Up @@ -236,4 +246,4 @@ Future<void> saveEpubState(
String fileName, String? position, WidgetRef ref) async {
String pos = position ?? '';
await dataBase.saveBookState(fileName, pos);
}
}
Loading
Loading