flutter_smartschool 0.2.0
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 inpubspec.yamland 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/recentendpoint 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/downEnter: open folder or download selected fileB/Backspace: go to parent folderQ: 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 #
- Call
loginonce at the start of the session. - Call
get_pageon the target module URL (e.g./?module=Messages&file=composeMessage). - Inspect the returned HTML/JSON to identify form field names, JS config blobs, and API endpoints.
- Use
post_formorrequestto replicate browser actions. - 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.ymllocal and private. It is listed in.gitignoreand must never be committed.