flutter_smartschool 0.2.0 copy "flutter_smartschool: ^0.2.0" to clipboard
flutter_smartschool: ^0.2.0 copied to clipboard

An unofficial Dart library for the Smartschool platform.

flutter_smartschool #

An unofficial Dart client library for the Smartschool school platform. It handles authentication (including TOTP 2FA and birthday-based account verification), cookie persistence, and the mix of XML-protocol and JSON/REST endpoints that Smartschool uses internally.

Repository: yvanvds/dartschool

Unofficial. This library reverse-engineers the private Smartschool web API. It is not endorsed by or affiliated with Smartschool. Use responsibly.

Features #

  • Authenticated Smartschool client with cookie persistence and MFA/account-verification support.
  • Full messaging workflow (MessagesService): list, read, attachments, recipient search, send, archive, trash, labels.
  • Intradesk read support (IntradeskService): root/folder listing and file download.
  • Interactive terminal browser for Intradesk: example/intradesk_browser.dart.

Installation #

Add the package to pubspec.yaml:

dependencies:
	flutter_smartschool: ^0.1.0

or directly from GitHub (dartschool) while iterating:

dependencies:
	flutter_smartschool:
		git:
			url: https://github.com/yvanvds/dartschool.git

Package name vs repository name #

Using different names is totally fine:

  • Package/import name is flutter_smartschool (what users add in pubspec.yaml and import in code).
  • Git repository can be dartschool.

This is common on pub.dev and does not cause technical issues.


Quick start #

import 'package:flutter_smartschool/flutter_smartschool.dart';

Future<void> main() async {
	// 1. Provide credentials β€” pick one of the three credential classes.
	final creds = PathCredentials(); // reads credentials.yml from disk

	// 2. Create an authenticated client.
	final client = await SmartschoolClient.create(creds);
	await client.ensureAuthenticated();

	// 3. Use a service.
	final messages = MessagesService(client);

	// List the 20 most-recent inbox headers.
	final headers = await messages.getHeaders();
	for (final msg in headers) {
		print('${msg.date}  ${msg.sender}: ${msg.subject}');
	}

	// Fetch the full body and attachment list of the first message.
	final full = await messages.getMessage(headers.first.id);
	print(full?.body);

	final attachments = await messages.getAttachments(headers.first.id);
	for (final a in attachments) {
		print('  πŸ“Ž ${a.filename} (${a.size} bytes)');
	}

	// Send a message to yourself.
	final myself = await messages.getCurrentUserAsRecipient();
	await messages.sendMessage(
		to: [myself],
		subject: 'Hello from flutter_smartschool',
		bodyHtml: '<p>It works!</p>',
	);
}

See example/send_message_lifecycle_example.dart for a complete send β†’ inbox poll β†’ archive β†’ trash flow.

For Intradesk navigation and file downloads, see example/intradesk_browser.dart (interactive text UI).


Credentials #

Three credential classes are provided, all extending the abstract Credentials base.

Class Source
AppCredentials Inline constructor arguments
EnvCredentials Environment variables (SMARTSCHOOL_USERNAME, SMARTSCHOOL_PASSWORD, SMARTSCHOOL_MAIN_URL, SMARTSCHOOL_MFA)
PathCredentials credentials.yml file β€” searched from cwd upwards, then ~/.cache/smartschool/

credentials.yml format:

username: john.doe
password: s3cr3t
main_url: school.smartschool.be
mfa: 2010-05-15   # date for account-verification, or Base32 secret for TOTP

If you need mfa, open your smartschool profile, two-factor authentication, add authenticator app. When a QR code is displayed, choose 'I do not have a camera'. A code is shown and that's the one you need.


SmartschoolClient #

The authenticated HTTP client. Create one instance per session and share it across services.

final client = await SmartschoolClient.create(credentials);
await client.ensureAuthenticated();
Method / getter Description
SmartschoolClient.create(credentials) Factory β€” creates the Dio client, configures cookie jar, returns ready instance
ensureAuthenticated() Triggers login if not already done; safe to call repeatedly
getRaw(path) Authenticated GET β†’ response body as String
getJson(path, {query}) Authenticated GET with JSON Accept header β†’ decoded dynamic
postFormRaw(path, fields) application/x-www-form-urlencoded POST β†’ String
postFormEncodedRaw(path, body) Same but accepts a pre-encoded body string
postMultipartRaw(path, formData) multipart/form-data POST β†’ String
postXml(...) Posts to the legacy XML dispatcher and returns parsed element maps
dio Exposes the underlying Dio instance for advanced / dev use

MessagesService #

All message operations. Construct with a SmartschoolClient.

final messages = MessagesService(client);

Reading #

Method Returns Description
getHeaders({boxType, boxId, sortBy, sortOrder, alreadySeenIds}) List<ShortMessage> List message headers for any box. Pass alreadySeenIds for lightweight polling.
getArchiveHeaders({boxId, sortBy, sortOrder, alreadySeenIds}) List<ShortMessage> Convenience wrapper for the archive folder β€” resolves the box ID automatically.
getArchiveBoxId() Future<int> Returns the archive folder's numeric box ID (cached; falls back to 208).
getMessage(msgId, {boxType}) Future<FullMessage?> Fetches the full HTML body, receiver lists, and metadata for a message.
getAttachments(msgId, {boxType}) Future<List<MessageAttachment>> Returns the attachment list for a message.

Mutating #

Method Returns Description
markUnread(msgId, {boxType, boxId}) Future<MessageChanged?> Marks a message as unread.
setLabel(msgId, label, {boxType}) Future<MessageChanged?> Applies a colour flag (MessageLabel). Use noFlag to clear.
moveToTrash(msgId) Future<MessageDeletionStatus?> Moves a message to the trash.
moveToArchive(msgIds) Future<List<MessageChanged>> Archives one or more messages (REST endpoint).

Composing & searching #

Method Returns Description
getCurrentUserAsRecipient() Future<MessageSearchUser> Returns the currently-logged-in user as a compose recipient (reads IDs from compose page JS β€” safe and reliable).
searchRecipients(query) Future<List<MessageSearchResult>> JSON-based recipient search; results lack ssId β€” use searchRecipientsForCompose when sending.
searchRecipientsForCompose(query) Future<(List<MessageSearchUser>, List<MessageSearchGroup>)> Compose-form XML search; results carry ssId/userLt required by sendMessage.
sendMessage({to, cc, bcc, toGroups, ..., subject, bodyHtml, attachmentPaths}) Future<void> Full multi-step send: loads compose form, registers recipients, uploads attachments, submits.

Static parsers (exposed for testing) #

Method Description
parseHiddenFields(htmlBody) Extracts all <input type="hidden"> name→value pairs from an HTML page.
parseComposeCurrentUserIds(htmlBody) Extracts (userId, ssId, userLt) from the window.tinymceInitConfig block.
parseArchiveBoxIdFromMessagesHtml(htmlBody) Extracts the archive folder box ID from the Messages module HTML.

IntradeskService #

Access to the Smartschool Intradesk document repository. Construct with a SmartschoolClient.

final intradesk = IntradeskService(client);

// Root listing
final root = await intradesk.getRootListing();
for (final folder in root.folders) {
  print('${folder.name}  hasChildren: ${folder.hasChildren}');
}

// Drill into a sub-folder
final sub = await intradesk.getFolderListing(root.folders.first.id);

// Download a file
final bytes = await intradesk.downloadFile(sub.files.first.id);
await File('output.docx').writeAsBytes(bytes);

Methods #

Method Returns Description
getRootListing() Future<IntradeskListing> Root-level folders, files, and weblinks.
getFolderListing(folderId) Future<IntradeskListing> Folders, files, and weblinks inside the identified folder.
downloadFile(fileId) Future<Uint8List> Raw bytes of the identified file.

Not yet implemented: file upload β€” the server-side endpoint and required form fields have not been captured safely.
Not scoped: the /recent endpoint returns an SPA HTML shell, not a JSON listing.

Example #

Run the interactive browser:

dart run example/intradesk_browser.dart

Controls:

  • U / D: move selection up/down
  • Enter: open folder or download selected file
  • B / Backspace: go to parent folder
  • Q: quit

Models #

ShortMessage #

Returned by getHeaders / getArchiveHeaders. Fields: id, sender, subject, date, unread, deleted, attachment, coloredFlag, allowReply, realBox, …

FullMessage #

Returned by getMessage. Adds: body (HTML), receivers, ccReceivers, bccReceivers, canReply, senderPicture, …

MessageAttachment #

Returned by getAttachments. Fields: id, filename, size, downloadUrl (absolute).

MessageSearchUser / MessageSearchGroup #

Used as recipients in sendMessage. Key fields: userId/groupId, ssId, userLt, displayName.

MessageChanged / MessageDeletionStatus #

Returned by mutation operations. Carry the id of the affected message and a newValue / status field.

IntradeskListing #

Returned by getRootListing / getFolderListing. Fields: folders (List<IntradeskFolder>), files (List<IntradeskFile>), weblinks (raw maps).

IntradeskFolder #

Fields: id, name, color, state, visible, confidential, parentFolderId (empty at root), hasChildren, isFavourite, capabilities (IntradeskFolderCapabilities), platform, dateCreated, dateChanged, dateStateChanged.

IntradeskFile #

Fields: id, name, state, parentFolderId, ownerId, confidential, isFavourite, currentRevision (IntradeskFileRevision?), capabilities (IntradeskFileCapabilities), platform, dateCreated, dateChanged, dateStateChanged.

IntradeskFileRevision #

Current revision metadata. Fields: id, fileId, fileSize, label, dateCreated, owner (IntradeskFileOwner).


Enums #

Enum Values
BoxType inbox, draft, scheduled, sent, trash
SortField date, from, readUnread, attachment, flag
SortOrder asc, desc
RecipientType to, cc, bcc
MessageLabel noFlag, greenFlag, yellowFlag, redFlag, blueFlag

Exceptions #

Exception Thrown when
SmartschoolAuthenticationError Login fails or session has expired
SmartschoolComposeError The compose form cannot be parsed, or the server rejects the message
SmartschoolAttachmentUploadError An attachment upload step fails

Smartschool Researcher MCP Server #

This repository includes a local MCP server that wraps the DevInspector HTTP client so Copilot Agent mode can explore live Smartschool endpoints directly. It is intended for development and reverse-engineering only.

  • Entrypoint: bin/smartschool_researcher_mcp.dart
  • VS Code config: .vscode/mcp.json (pre-configured)
  • Credentials: credentials.yml (auto-discovered; never commit this file)

Available tools #

Tool Description
login Authenticates with credentials.yml, or with inline username/password/mainUrl.
login_status Checks whether the current MCP session has an active Smartschool session.
get_page Authenticated GET β†’ statusCode, headers, body.
get_json GET with JSON Accept header β†’ parsed json field in addition to raw body.
post_form Authenticated application/x-www-form-urlencoded POST.
request Generic tool: arbitrary method, headers, query params, body, content type.

Typical agent workflow #

  1. Call login once at the start of the session.
  2. Call get_page on the target module URL (e.g. /?module=Messages&file=composeMessage).
  3. Inspect the returned HTML/JSON to identify form field names, JS config blobs, and API endpoints.
  4. Use post_form or request to replicate browser actions.
  5. Design the Dart service method and models from the confirmed response shape.

Pass maxBodyChars to any tool to truncate large responses before they fill the context window.

Keep credentials.yml local and private. It is listed in .gitignore and must never be committed.

1
likes
0
points
373
downloads

Publisher

unverified uploader

Weekly Downloads

An unofficial Dart library for the Smartschool platform.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

cookie_jar, dio, dio_cookie_manager, html, otp, path, xml, yaml

More

Packages that depend on flutter_smartschool