Skip to content
CapacitorSet edited this page Oct 18, 2017 · 10 revisions

Writing plugins

Plugins are classes that listen to specific events from the Telegram API.

General structure

See the architecture diagram in this folder for an overview of how plugins communicate with the other components.

const Plugin = require("../Plugin");

module.exports = class MyPlugin extends Plugin {
	// Information about your plugin.
	static get plugin() {
		return {
			name: "MyPlugin",
			description: "It does things.",
			help: "Try saying 'Hello!', or using the /hello command."
		}
	}

	// Optional: initialize the plugin. Gets passed the event listener, the bot object, the bot configuration, and the auth API object.
	// You shouldn't need direct access to the event listener or the bot object.
	constructor(listener, bot, config, auth) {
		super(listener, bot, config, auth);

		/* ... */
	}

	// Optional: stop the plugin.
	stop() {
		foo.stop();
	}

	// An object, mapping commands to their handlers.
	// Handlers can return a string, representing a text reply.
	get commands() { return {
		hello: ({args, message}) => {
			// Args is an array of arguments passed by the user (eg. `/foo bar` would have args = ["bar"])
			// Message is the message that triggered the handler

			return "Heyo!";
	}; }

	// Alternatively, you can use the more verbose onCommand listener.
	onCommand({message, command, args}) {
		if (command === "hello") {
			this.sendMessage(message.chat.id, "Heyo!");
		}
	}

	// Called when a message is received.
	onText({message}) {
		if (message === "Hello!") {
			this.sendMessage(message.chat.id, "Hi!");
		}
	}

	onPhoto({message}) {
		/* ... */
	}

	/* ... */
}

Common pitfall! To reply to messages, you use this.sendMessage(...). However, note that inside function() {...} blocks, this does not refer to the plugin. So, you must use arrow functions in callbacks. For instance:

onCommand({message, command, args}) {
	if (command !== "reddit") return;
	// Do NOT use function(err, request) { ... }!
	request("https://reddit.com/...", (err, request) => {
		this.sendMessage(message.chat.id, ...)
	});

Metadata

Every plugin must provide some data about itself, using the plugin getter. It returns an object with the following properties:

  • name, mandatory
  • description, a short description of what the plugin does (recommended)
  • help, the help text to be shown when the user runs /help myPlugin (recommended)
  • visibility, either Plugin.Visiblity.VISIBLE (the default) or Plugin.Visibility.HIDDEN. You probably won't need this.
  • type, a bitmask of Plugin.Type.NORMAL, .INLINE (whether the plugin can be used in inline mode), .PROXY (whether the plugin acts as a proxy, see below), and .SPECIAL (for internal usage). The default is .NORMAL.

Constructor, stop

It is optional to add a constructor (which must always call start!) or a stop method,

If the plugin is in an invalid state (for instance, a token is invalid), or an issue occurs while initialising the plugin, constructor must throw an error. This will cause the plugin not to be loaded.

onText, onPhoto, ...

These (optional) functions are called when a message of their type is received. The full list is: onText, onAudio, onDocument, onPhoto, onSticker, onVideo, onVoice, onContact, onLocation, onNewChatParticipant, onLeftChatParticipant. They are passed two parameters, message and reply. The former is the object returned by the Telegram API; the latter is a function, to be called like this: reply({type: "text", text: "Hello!"}).

Plugins are not required to implement any of these methods. Proxies, for instance, usually don't.

Proxy

If a plugin is registered as a proxy (get plugin() contains isProxy: true), the proxy method is called on every incoming message. It returns an ES6 Promise.

  • If the Proxy approves the message, it must return Promise.resolve().
  • If the Proxy rejects the message, it returns a Promise.reject(). It will cause the message not to be passed along the plugin chain.

Example:

class Antiflood extends Plugin {
    static get plugin() {
        return {
            name: "Antiflood",
            description: "Automatically ignore spamming users",
            help: "...",
            isProxy: true
        };
    }

    proxy(eventName, message) {
        if (this.isFlood(message))
            return Promise.reject("flood");
        return Promise.resolve();
    }

    isFlood(message) {
        /* ... */
    }
}
Clone this wiki locally