4.7 KiB
LoBBS Plugin Author's Guide
How LoBBS plugins fit together, how to add hooks of your own, and checklists for a new app. Hook signatures, paging, storage, and stack limits are in LoBBS internals.
How plugins fit together
LoBBS core does not know what mail, news, or the wall are. It peels the request id and the verb, fires slash_cmd, and renders whatever result set comes back. Every command, including /login and /help, is a plugin that subscribed to hooks.
- Adding or removing an app is one line in
LoBBSWireup.cpp. Nothing else changes, because nothing else calls it. - Every handler runs and none can claim a hook. A new plugin can add to what LoBBS does, but it cannot quietly switch another one off. What the node does is the sum of what is installed, in priority order.
- Plugins never call each other. They meet at named hooks and pass LoScalar records. Remove a plugin and the ones listening to it keep working. Their handlers for its hooks are just never called.
- Data crosses the bus as LoScalar fields, not C++ types. A handler that only reads and writes numbered fields could later be a script instead of compiled code.
Define your own hooks
The hooks in the internals table are the ones core and the bundled apps fire. They are not a closed set. A hook is only a name, so your app can fire its own and any other plugin can subscribe without including your headers.
Fire an action where something happened that others might react to:
LoScalar args;
args.setUint64(LODB_F_ID, mailUuid);
args.setString(LODB_F_TITLE, toUsername);
lobbsDoAction("mail_sent", ctx, args);
Apply a filter where others might want to change a value before you use it. You seed the default, and the value you read back is what every handler agreed on:
LoScalar post;
post.setString(LODB_F_DESCRIPTION, body);
lobbsApplyFilter("news_before_post", ctx, post, LoScalar());
post.getString(LODB_F_DESCRIPTION, body);
Another plugin subscribes by name:
static void notifyOnMail(LoBBSCommandCtx *ctx, const LoScalar &args)
{
// args carries the mail id and recipient
}
lobbsAddAction("mail_sent", notifyOnMail, LOBBS_HOOK_PRIORITY_FEATURE);
These two hooks are examples. The bundled apps do not fire them.
Rules for a hook you own:
- Prefix the name with your app (
mail_,wall_). Unprefixed names are the shared hooks in the internals table. - Pass a string literal. The bus keeps the pointer.
- One kind per name. A handler registered with a different kind is logged and skipped.
- Use title (
98) and description (97) for the obvious text. Give other args fields numbers in0..93. - Fire with the
ctxof the command you are handling, so subscribers see the same sender and session. - Document it in the same change: a row in the hook table in
LoBBSHooks.hand in internals with name, kind, initial value, and args.
New command app
- In
lobbsXRegisterCommands(), registerslash_cmd,help_topics, andhelp_for_topic. Addstatus_lines,display_human, orseedwhen needed. - One
LoBBSVerbtable driveslobbsDispatchSubandlobbsHelpForTable. Gate withLOBBS_V_LOGINandLOBBS_V_SYSOP. - In
slash_cmd, return quietly unlesslobbsSlashVerbIsmatches your verb. - Build a
LoBBSResponseand calllobbsCommandReplyResponse. Do not page or parsepN. - Talk only through
ctx,args, and the filter value. Do not call another plugin. Do not assume you run first or alone. - Include
LoBBSStackGuard.hlast in every.cpp. Keep functions under 512 bytes of stack on nRF52. - Add one
lobbsXRegisterCommands()call inLoBBSWireup.cpp. - Do not
#includeAuthDal.hfrom feature code. Use the session onctxandAppUtil.
Runtime settings
- Register a
config_keyslist filter and calllobbsConfigPushKeyfor each uint32 setting (default, min, max, help text shown by/help config key). - Read values with
lobbsConfigGet(ctx, "your.key")in command handlers before calling your DAL. - Optional: register
config_validateat a priority afterLOBBS_HOOK_PRIORITY_HELPfor cross-field checks. - Register
config_changedto apply side effects (resize buffers, refresh caches).
Mail is the reference implementation for subcommands and help tables.
New record type
- Pick a table name. App fields use
0..93. Body in field97, short name in98. - Do not set field
99. Leave95and96unset on insert.removeFieldthem beforeupdatewhen you want LoDB to stamp times. registerTableat app construction. PreferLoDb::upsertfor keyed or singleton rows.- Build uuids with
lodb_new_uuid. Do not printuint64_twith%lluon nRF52. UseloU64ToDecfromloutil/LoUtil.h.