liblanis 0.1.2 copy "liblanis: ^0.1.2" to clipboard
liblanis: ^0.1.2 copied to clipboard

Build apps and scripts against SPH without Flutter - batteries included.

liblanis #

pub package

Pure Dart client for Schulportal Hessen (SPH / Lanis).

Session stack, multi-account storage, optional sqlite3 persistence, injectable secrets, and applet parsers. No Flutter dependency for the core API. Flutter apps can use the Riverpod integration documented below.

Published on pub.flutter-io.cn. Also used as a submodule of lanis-mobile/lanis.

Features #

  • Full SPH session stack (login, cookies, RSA/AES cryptor, HTML/JSON applet parsers)
  • **EasyLanisClient** — imperative API without Riverpod (default for scripts, CI, other Dart projects)
  • Optional Riverpod multi-account registry for Flutter apps
  • Optional single SQLite database (file or in-memory)
  • sharedOverAccountSettings and accountSpecificSettings with typed helpers
  • Offline applet snapshots table (substitutions & timetable write in v1; API is generic)
  • Configurable StorageManager (caller supplies cache directory)

Install #

dart pub add liblanis

Or add liblanis under dependencies in your pubspec.yaml (see pub.flutter-io.cn for the current version). For Flutter apps that use the Riverpod API, also add:

dependencies:
  flutter_riverpod: ^3.0.0
  sqlite3_flutter_libs: ^0.5.0 # load native sqlite on mobile

Quick start #

Import package:liblanis/easy_client.dart. No ProviderScope or code generation required.

In-memory (tests, quick scripts) #

import 'package:liblanis/easy_client.dart';

Future<void> main() async {
  final client = EasyLanisClient.inMemory();

  final id = await client.accounts.add(
    schoolId: 5151,
    username: 'student.user',
    password: r'...',
  );

  await client.login(accountId: id);

  // Direct parser call
  final plan = await client.parsers.substitutions.getHome();

  // Cached fetch with offline fallback (same as AppletParser.fetchData)
  final response = await client.parsers.calendar.fetch(forceRefresh: true);
  if (response.contentStatus == ContentStatus.offline) {
    // using cached events
  }

  client.connection.onStatusChanged.listen((status) {
    // ConnectionStatus.connected / disconnected
  });

  await client.logout();
  await client.dispose();
}

Available parsers #

Access via client.parsers after login:

Getter Applet
substitutions Vertretungsplan
timetable Stundenplan (student)
calendar Kalender
conversations Nachrichten
lessonsStudent Mein Unterricht (student)
lessonsTeacher Mein Unterricht (teacher)
dataStorage Dateispeicher
studyGroups Lerngruppen

Generic access for smoke tests: client.parsers.forApplet('kalender.php').

Flutter (Riverpod) #

Prefer package:liblanis/liblanis.dart when you want the provider-based multi-account API used by lanis-mobile.

On Flutter mobile hosts, initialize sqlite before runApp:

import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
  // ...
}

Configure and mount #

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:liblanis/liblanis.dart';

Future<void> main() async {
  final overrides = LanisClient.configure(
    databasePath: '/path/to/lanis.db', // null => in-memory
    secretStore: MySecureStore(),      // required when databasePath != null
    documentCacheDirectory: '/path/to/cache', // required for downloads
    // httpAdapter: cronetAdapter,     // optional
    userAgent: 'Lanis-Mobile/v3.7.2+83',
  );

  runApp(
    ProviderScope(
      overrides: overrides,
      child: const MyApp(),
    ),
  );
}

class MySecureStore implements SecretStore {
  // e.g. FlutterSecureStorage
  @override
  Future<void> write(String key, String value) async { /* ... */ }
  @override
  Future<String?> read(String key) async => null;
  @override
  Future<void> delete(String key) async { /* ... */ }
}

Accounts & session #

final id = await ref.read(accountsProvider.notifier).add(
  schoolId: 5151,
  schoolName: 'Example',
  username: 'user',
  password: 'pass',
);

await ref.read(activeAccountProvider.notifier).select(id);
await ref.read(sessionProvider.notifier).authenticate();

ref.listen(connectionStatusProvider, (prev, next) {
  // ConnectionStatus.connected / disconnected
});

Settings #

final shared = ref.read(sharedOverAccountSettingsProvider);
shared.setString('theme', 'dark');

final account = ref.read(accountSpecificSettingsProvider);
account.setJsonMap('vertretungsplan.php/filter', {/* ... */});

Applets #

final response = await ref.read(substitutionsProvider.future);
if (response.contentStatus == ContentStatus.offline) {
  // cached SubstitutionPlan
}
final plan = response.content;

// Or use the long-lived parser + stream:
final parser = ref.read(substitutionsParserProvider);
await parser.fetchData();
parser.stream.listen(/* FetcherResponse<SubstitutionPlan> */);

Document downloads #

final storage = ref.read(storageManagerProvider);
if (storage != null) {
  final path = await storage.downloadFile(url, filename);
}

Calling download APIs without documentCacheDirectory throws StorageNotConfiguredException.

Configuration rules #

Option Behavior
databasePath: null In-memory sqlite; SecretStore optional
databasePath: set File DB; **secretStore required**
documentCacheDirectory: null Downloads disabled
httpAdapter Optional Dio adapter injection

Development #

cd liblanis
dart pub get
dart run build_runner build
dart test
dart analyze

License #

GPL-3.0 — see LICENSE.

0
likes
140
points
83
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Build apps and scripts against SPH without Flutter - batteries included.

Repository (GitHub)
View/report issues

Topics

#schulportal #lanis #education #hessen

License

GPL-3.0 (license)

Dependencies

cookie_jar, crypto, dart_date, dio, dio_cookie_manager, encrypt, html, html_unescape, intl, meta, path, pointycastle, riverpod, riverpod_annotation, sqlite3, uuid

More

Packages that depend on liblanis