npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

iserv-api

v1.4.1

Published

Unofficial TypeScript SDK for IServ school management servers

Readme

iserv-api

Unofficial TypeScript SDK for IServ school management servers. Authenticate with username and password, no API key required.

Installation

npm install iserv-api

Basic usage

import { IServAPI } from "iserv-api";

const api = await IServAPI.connect("your-school.iserv.de", "username", "password");

const info = await api.users.getOwnInfo();
console.log(info);

await api.disconnect();

Table of contents


Supported functionality

Own account

Get own user info

const info = await api.users.getOwnInfo();

Returns name, email, groups, roles, rights, and public profile info of the logged-in user.

Set own user info

await api.users.setOwnInfo({
  nickname: "Ali",
  city: "Berlin",
  hidden: false,
});

Available fields: title, company, birthday, nickname, schoolClass, street, zipcode, city, country, phone, mobilePhone, fax, mail, homepage, icq, jabber, msn, skype, note, hidden

Get notifications

const data = await api.notifications.getAll();

Returns all notifications including unread count and notification items.

Get badges

const badges = await api.notifications.getBadges();

Returns sidebar badge counts (e.g. unread email count).

Read all notifications

await api.notifications.readAll();

Marks all notifications as read.

Read a notification

await api.notifications.read(123);

Marks a single notification as read by its ID.


Users

Get profile picture

await api.users.getProfilePicture("alice", "./avatars");

Saves the user's profile picture to the specified folder as {username}.{ext}.

Get profile picture buffer

const buffer = await api.users.getProfilePictureBuffer("alice");
const buffer = await api.users.getProfilePictureBuffer("alice", 128, 128);

Returns the profile picture as a Buffer. Width and height must be positive integers between 1 and 4096.

Get user info

const info = await api.users.getInfo("alice");

Returns the public address book information of any user.

Search users

const results = await api.users.search("Alice");

Searches the address book. Returns an array of { name, userUrl }.

Search users autocomplete

const results = await api.users.searchAutocomplete("ali", 10);

Faster autocomplete search. Returns up to limit results (default 50).

Search messenger recipients

const recipients = await api.users.searchMessengerRecipients("Alice Example");
const alice = recipients[0];

Searches IServ's messenger recipient autocomplete endpoint. This is useful for messenger operations such as createDirectMessage(), where the returned value is the recipient identifier expected by IServ.


Email

Get emails

const emails = await api.email.getEmails({
  mailbox: "INBOX",
  limit: 25,
  offset: 0,
  sort: "date",
  order: "desc",
});

Get message

const message = await api.email.getMessage(uid, "INBOX");

Returns the full message including headers, body parts, and attachment metadata.

Send email

await api.email.sendEmail({
  to: "[email protected]",
  subject: "Hello",
  body: "Plain text body",
  htmlBody: "<p>HTML body</p>",
  smtpsPort: 465,
  attachments: ["./file.pdf"],
});

Attachments must be relative paths. smtpsPort must be 465 or 587.

Mark as unread

await api.email.markAsUnread(uid);
await api.email.markAsUnread(uid, "INBOX"); // mailbox defaults to "INBOX"

Marks a message as unread by its UID.

Mark as read

await api.email.markAsRead(uid);
await api.email.markAsRead(uid, "INBOX");

Marks a message as read by its UID.


Calendar

Get upcoming events

const { events } = await api.calendar.getUpcomingEvents();

Get event sources

const sources = await api.calendar.getEventSources();

Returns all available calendars and plugins. Each source has an id, label, and type ("cal" or "plugin").

Get events

const events = await api.calendar.getEvents("2025-01-01", "2025-12-31");

Returns all events across all sources in the given time range.

Search events

const results = await api.calendar.searchEvents("Math exam", "2025-01-01", "2025-12-31");

Get plugin events

const events = await api.calendar.getPluginEvents("holiday", "2025-01-01", "2025-12-31");

Plugin IDs come from event sources where type === "plugin".

Create event

const result = await api.calendar.createEvent({
  subject: "Math exam",
  calendar: "/alice/home",
  start: "2025-09-27T14:00:00",
  end: "2025-09-27T16:00:00",
  location: "Room 101",
  description: "Bring calculator",
  alarms: ["1D", "2H"],
  isAllDayLong: false,
  participants: ["bob", "[email protected]"],
  showMeAs: "OPAQUE",
  privacy: "PUBLIC",
  recurring: {
    intervalType: "WEEKLY",
    interval: 1,
    recurrenceDays: ["MO", "WE"],
    endType: "COUNT",
    endInterval: 10,
  },
});

Alarm types:

Preset strings: "0M" "5M" "15M" "30M" "1H" "2H" "12H" "1D" "2D" "7D"

Custom datetime alarm:

{ custom_date_time: { dateTime: "2025-09-26T10:00:00" } }

Custom interval alarm:

{
  custom_interval: {
    interval: { days: 1, hours: 2, minutes: 0 },
    before: true,
  }
}

Recurring options:

| Field | Type | Description | |---|---|---| | intervalType | "NO" \| "DAILY" \| "WEEKDAYS" \| "WEEKLY" \| "MONTHLY" \| "YEARLY" | Repeat pattern | | interval | number | Repeat every N units (required for most types) | | recurrenceDays | WeekDay[] | Required for WEEKLY | | monthlyIntervalType | "BYMONTHDAY" \| "BYDAY" | Required for MONTHLY | | endType | "NEVER" \| "COUNT" \| "UNTIL" | How the recurrence ends | | endInterval | number | Required if endType is "COUNT" | | untilDate | string | Required if endType is "UNTIL", format "DD.MM.YYYY" |

Delete event

await api.calendar.deleteEvent({
  uid: "[email protected]",
  hash: "541f2d74099d785d1286c03903a2e826",
  calendar: "/alice/home",
  start: "2025-09-27T14:00:00+02:00",
  series: false,
});

uid, hash, calendar, and start are returned by getEvents().


Files

Get WebDAV client

const client = api.files.getClient();

Returns a pre-authenticated webdav client. See the webdav package documentation for all available methods.

const files = await client.getDirectoryContents("/");
await client.putFileContents("/notes.txt", "hello");
const data = await client.getFileContents("/notes.txt");

Get folder size

const size = await api.files.getFolderSize("/Documents");

Get disk space

const usage = await api.files.getDiskSpace();

Returns disk space info for all accessible storage volumes (label, free space, color).


Messenger

The messenger service wraps the Matrix protocol used by IServ. A Matrix session is established automatically during login.

Get rooms

const rooms = await api.messenger.getRooms();

Returns all joined rooms (group chats and direct messages).

Each room has:

| Field | Type | Description | |---|---|---| | id | string | Matrix room ID | | name | string | Room name or display name of the other person | | isDirect | boolean | Whether this is a DM | | unreadCount | number | Unread message count | | lastMessage | RoomLastMessage \| null | Most recent message |

lastMessage fields: body, sender (Matrix user ID), senderName (display name, falls back to Matrix user ID), timestamp.

Get messages

const { messages, start, end } = await api.messenger.getMessages(roomId, { limit: 30, from: end });

Returns up to limit messages (default 30) in reverse chronological order. Pass end from a previous response as from to paginate backwards. end is undefined when there are no more messages.

Each message has:

| Field | Type | Description | |---|---|------------------------------------------------------------------------------| | eventId | string | Matrix event ID | | sender | string | Matrix user ID | | senderName | string | Display name, falls back to the Matrix user ID if unavailable | | body | string | Message text, empty string if the message is end-to-end encrypted | | msgtype | string | e.g. "m.text", "m.image", "m.file""m.encrypted" for E2EE messages | | timestamp | number | Unix ms | | encrypted | boolean | true if the message content cannot be decrypted by this SDK |

Get messages by name

const { messages } = await api.messenger.getMessagesByName("Max Mustermann", { limit: 20 });

Looks up the room by name and returns its messages. Throws if no room or multiple rooms match the name. Accepts the same options as getMessages().

Get members

const members = await api.messenger.getMembers(roomId);

Returns all current members of a room (excludes users who have left).

Each member has: userId, displayName, avatarUrl, membership ("join" | "invite" | "ban" | "knock").

Get profile

const profile = await api.messenger.getProfile(userId);

Returns the Matrix profile of any user: userId, displayName, avatarUrl.

Send message

const result = await api.messenger.sendMessage(roomId, "Hello!");
console.log(result.eventId);

Sends a text message to a room by its Matrix room ID. Returns the eventId of the sent message.

An optional txnId can be passed as a third argument for idempotency — if the same txnId is used twice, the server will deduplicate the send:

await api.messenger.sendMessage(roomId, "Hello!", "my-unique-id");

Create direct message

const recipients = await api.users.searchMessengerRecipients("Alice Example");
const alice = recipients[0];

if (!alice) throw new Error("User not found");

const { roomId } = await api.messenger.createDirectMessage(alice.value);

Creates or opens a direct message room using IServ's messenger form flow. The matrixId argument should usually come from api.users.searchMessengerRecipients(...)[n].value. Returns the Matrix roomId.

Leave room

await api.messenger.leaveRoom(roomId);

Leaves a Matrix room by room ID.

React to message

const result = await api.messenger.reactToMessage(roomId, eventId, "👍");
console.log(result.eventId);

Adds a reaction to a message. Pass an optional fourth txnId for idempotency.

Edit message

const result = await api.messenger.editMessage(roomId, eventId, "Updated text");
console.log(result.eventId);

Sends a Matrix replacement event for an existing text message. Pass an optional fourth txnId for idempotency.

Reply to message

const { messages } = await api.messenger.getMessages(roomId, { limit: 1 });
const original = messages[0];

const result = await api.messenger.replyToMessage(roomId, original, "Thanks!");
console.log(result.eventId);

Sends a text reply. The second argument can be any Message object returned by getMessages(). Pass an optional fourth txnId for idempotency.

Remove reaction

const result = await api.messenger.removeReaction(roomId, reactionEventId);
console.log(result.eventId);

Redacts a reaction event. Throws if the reaction doesn't exist or you're not allowed to remove it.

Delete message

const result = await api.messenger.deleteMessage(roomId, eventId);
console.log(result.eventId);

Redacts a message event. Throws a error if the message doesn't exist or you're not allowed to delete it.

Send message by name

const result = await api.messenger.sendMessageByName("Max Mustermann", "Hello!");
console.log(result.eventId);

Looks up the room by display name and sends a text message. Throws if no room or multiple rooms match the name. Accepts an optional txnId as a third argument, same as sendMessage().

Note: This method calls getRooms() internally, which makes an extra network request. If you already have the room ID, prefer sendMessage() directly.

React to message by name

const result = await api.messenger.reactToMessageByName("Max Mustermann", eventId, "👍");
console.log(result.eventId);

Looks up the room by display name and reacts to a message. Throws if no room or multiple rooms match the name. Accepts an optional txnId as a fourth argument, same as reactToMessage().

Note: This method calls getRooms() internally, which makes an extra network request. If you already have the room ID, prefer reactToMessage() directly.

Listen for messages

const listener = await api.messenger.listenForMessages((event, stop) => {
  console.log(`[${event.roomName}] ${event.message.senderName}: ${event.message.body}`);
  stop(); // stop after first message
});

// Stop from outside the callback at any time:
listener.stop();

With options:

const listener = await api.messenger.listenForMessages(
  (event) => {
    console.log(event.message.body);
  },
  {
    pollTimeout: 10000,
    roomIds: [roomId],
    onError: (err) => console.error("sync error:", err),
  },
);

Starts a real-time message listener using Matrix long-polling. The callback fires for every incoming m.room.message event. A stop function is passed as the second argument to the callback for convenience.

| Option | Type | Default | Description | |---|---|---------|---| | pollTimeout | number | 30000 | Long-poll timeout in ms | | roomIds | string[] | / | Only emit events from these room IDs | | onError | (err: Error) => void | / | Called on sync errors instead of logging |


Conference

Get conference health

const health = await api.conference.getHealth();

Returns the health status of the IServ video conference endpoint.


Logging

The SDK logs to stderr using a built-in logger. Set ISERV_DEBUG=1 to enable debug output:

ISERV_DEBUG=1 node app.js

License

MIT

Disclaimer: This is an unofficial SDK not affiliated with IServ GmbH. Use at your own risk. The authors are not responsible for any damages or data loss caused by use of this package.