Skip to main content

Messages

This area covers the person's Moodle messaging: their notifications (notifications, markNotificationsRead), their conversations (conversations, conversation) and sending a private message (sendMessage), to one person or to several participants of a course at once (messageParticipants). It works the same for students and teachers, always as the logged-in person: there's no reading someone else's messages. Forum posts are in Forums.

Terms: notification, conversation.

The signatures below are the functions'. Each operation is also a method of the Moodle object without its first two parameters (fn(context, site, …) is moodle.fn(…)), which is how the examples call them; the lower-level primitives are in @falkenslab/moodle-driver/internals (see Going lower level).

Everything goes through the services Moodle's own notification popover and messaging drawer call. People come as a user id and a full name; messages come as plain text (paragraphs and lists kept, markup removed), dates as ISO strings in UTC.

notifications​

The person's notifications, newest first, optionally only the unread ones, all of them or a page at a time (see Long lists).

function notifications(context: BrowserContext, siteUrl: string, options?: { unreadOnly?: boolean; limit?: number; offset?: number }): Promise<Notification[]>; // ListPage<Notification> with limit

interface Notification {
id: number;
subject: string;
text: string;
fromUserId: number;
time?: string;
read: boolean;
url?: string;
component?: string;
eventType?: string;
}
  • unreadOnly: default false; with it, offset counts unread notifications.
  • limit, offset: a page of them, newest first; without limit, every one.
  • subject and text: the notification's subject and its message as plain text.
  • fromUserId: who it's from; a negative id is the site itself (the no-reply or support user).
  • url: where it leads (the post, the assignment, the graded submission…).
  • component and eventType: what sent it, such as "mod_forum" with "posts", or "mod_assign"; useful to filter by kind in any language.

It reads message_popup_get_popup_notifications, the popover's service: without a limit, everything; with one, only the page. The service has no unread filter: for a page of unread ones, the driver reads its notifications in batches of 50 (or more for a larger page) until the page is full.

Forum notifications come later​

Moodle doesn't notify a forum post when it's written. Its scheduled tasks (cron) send forum notifications afterwards, once the post's editing time has passed (30 minutes by default, the site's "Maximum editing time"). A program that replies with replyToPost and then reads the recipient's notifications won't find it at once; how soon it arrives depends on the site's cron, not on the driver. Private messages sent with sendMessage show in the conversation immediately.

markNotificationsRead​

Marks all the person's notifications read, as the popover's "Mark all as read" does. Doing it twice changes nothing.

function markNotificationsRead(context: BrowserContext, siteUrl: string): Promise<{ changed: string }>;

It goes through core_message_mark_all_notifications_as_read for the logged-in user. There's no marking a single notification.

conversations​

The person's conversations, latest first, each with its last message, all of them or a page at a time (see Long lists).

function conversations(context: BrowserContext, siteUrl: string, options?: { limit?: number; offset?: number }): Promise<ConversationSummary[]>; // ListPage<ConversationSummary> with limit

interface ConversationSummary {
id: number;
type: "private" | "group" | "self";
name?: string;
members: { userId: number; fullName: string }[];
unread: number;
lastMessage?: { fromUserId: number; text: string; time?: string };
}
  • type: "private" between two people, "group" for a course group's conversation (with the group's name), "self" for the person's notes to themselves.
  • members: the members Moodle returns with it (in a private conversation, the other person).
  • unread: how many messages the person hasn't read.
  • lastMessage: its sender, its text and when it was sent.

It reads core_message_get_conversations, the drawer's service, which returns only the page asked for.

conversation​

One conversation's members and messages, oldest first, all of them or a page at a time (see Long lists).

function conversation(context: BrowserContext, siteUrl: string, conversationId: number, options?: { limit?: number; offset?: number }): Promise<{
id: number;
members: { userId: number; fullName: string }[];
messages: { id: number; fromUserId: number; text: string; time?: string }[]; // ListPage<…> with limit
}>;
  • members: everyone in the conversation but the person, whether they wrote in it or not, as conversations gives them (in the person's own conversation, the person).
  • messages: their sender, their text and when they were sent. With limit, a page counted from the newest message: the first page holds the latest messages, more says there are older ones, and each page's messages still come oldest first. core_message_get_conversation_messages returns only the page.

conversationId is an id from conversations, or the conversationId that sendMessage returns. It reads core_message_get_conversation_members and core_message_get_conversation_messages; the person must be a member, or Moodle refuses with a MoodleServiceError (see Errors).

sendMessage​

Sends a private message to a user, as the messaging drawer does, and returns the message's id and its conversation.

function sendMessage(context: BrowserContext, siteUrl: string, userId: number, text: string): Promise<{ changed: string; messageId: number; conversationId?: number }>;
  • userId: the recipient (a participant's userId, see Participants).
  • text: the message as HTML ("<p>Hello</p>"); plain text works too.

It throws MessageError:

  • before sending anything, when the text is empty once its markup and non-breaking spaces are removed;
  • when Moodle doesn't deliver it, with Moodle's reason (the recipient blocked the person, for instance).

It goes through core_message_send_instant_messages.

messageParticipants​

Sends one message to several participants of a course, as the participants page's "Send a message" does for the users selected: one private message to each, in one call.

function messageParticipants(
context: BrowserContext,
siteUrl: string,
courseId: number,
text: string,
selection?: { userIds: number[] } | { role?: number; group?: number; inactiveSince?: Date },
): Promise<{ changed: string; sentTo: number[]; refused: { userId: number; reason: string }[] }>;
  • selection: the users by id, or the participants participants chooses with role, group and inactiveSince (see Participants); with no filter, every participant. Chosen by filters, the person is left out.
  • sentTo lists exactly who got the message; refused who Moodle didn't write to, with its reason (they blocked the person, their messaging privacy).

It throws MessageError before sending anything when the text is empty or nobody matches, and NoParticipantsError when the person can't see the course's participants. Someone who can message any user (a teacher, on a default site: moodle/site:messageanyuser) gets through a block, as in Moodle's own page: a student who blocked their teacher still gets the teacher's message (confirmed empirically, Moodle 5.2).

Example​

Read the unread notifications, then answer a student's latest message.

import { MessageError } from "@falkenslab/moodle-driver";

// `moodle` is the object from createMoodle(), as in the getting-started pages.

for (const notification of await moodle.notifications({ unreadOnly: true })) {
console.log(`[${notification.component ?? "site"}] ${notification.subject}`);
}
await moodle.markNotificationsRead();

const [latest] = await moodle.conversations();
const other = latest?.members.find((m) => m.userId === 7);
if (latest?.type === "private" && other && latest.unread > 0) {
try {
const sent = await moodle.sendMessage(other.userId, "<p>Thanks, Ada. I'll check it tomorrow.</p>");
console.log(sent.changed);
} catch (error) {
if (error instanceof MessageError) console.log(error.message);
else throw error;
}
}

Data categories​

From DATA_CATEGORIES (see Data categories):

  • notifications, markNotificationsRead: own.
  • conversations, conversation: others-personal (they hold others' messages).
  • sendMessage, messageParticipants: communication (writing to others).