WP Manifestindependent plugin directory
manifest / email / notify-telegram

Notify Telegram

Telegram notifications for WordPress events

by Plugin Lab · github.com/wpseed/notify-telegram

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/wpseed/notify-telegram/archive/refs/heads/main.zip

Notifications for WordPress events, delivered to the channels you configure: Telegram, email or any incoming webhook. Scaffolded from the lab's starter-plugin template: namespace Wpseed\NotifyTelegram, text domain notify-telegram, package wpseed/notify-telegram.

What is inside

File / directory Purpose
notify-telegram.php Plugin header, autoloader include, Plugin::boot()
src/Plugin.php Singleton entry point: builds the objects, registers the hooks, activation
src/Message.php The text that leaves the site: %placeholder% rendering and validation
src/Settings/Settings.php The single option: switches, channel values, message templates
src/Channel/ Channel interface, Result, ChannelRegistry, TelegramChannel, EmailChannel, WebhookChannel
src/Event/ Event, EventRegistry, EventSource, UserEvents (registration, failed login), CommentEvents
src/Delivery/ Router (what goes out), Queue (WP-Cron, retries), Log (last 20 attempts)
src/Admin/AdminPage.php The single menu entry and the React boot behind it: Events and Settings are tabs on that one screen
src/Rest/SettingsController.php The routes the screen reads and writes through: /settings, /test, /log
src/Delivery/TestSender.php The test button: one message through every configured channel
admin-ui/, package.json, vite.config.mjs React + antd sources and the Vite build of the screen
bin/build Archive builder (composer build): a copy without the development files + zip into dist/
.github/workflows/build-plugin.yml The same build in CI, triggered by a version tag
.github/workflows/php-floor.yml The PHP floor check: install and lint on 8.2, on every push
readme.txt The listing in the plugin directory: header, description, FAQ, changelog — checked with the directory's own readme validator
tests/unit/ Fast unit tests (PHPUnit, WordPress is not loaded)
tests/integration/ Integration tests (WP_UnitTestCase, real WordPress + MySQL)

Setup and commands

cd web/app/plugins/notify-telegram

composer install          # development dependencies (phpunit, wp-phpunit, WPCS)
composer test             # every test
composer test:unit        # unit only
composer test:integration # integration only (boots WordPress)
composer lint             # phpcs (WordPress Coding Standards)
composer lint:fix         # phpcbf, auto-fix
composer build            # shippable archive: dist/notify-telegram/ + dist/notify-telegram.zip

npm install               # front-end dependencies (React, antd, Vite)
npm run build             # bundle the admin screen into assets/admin/
npm run watch             # same, rebuilding on every change

Events, channels and delivery

WordPress hook  →  EventSource  (declares the event, fills its placeholders)
                →  Router       (master switch, event toggle, channels that are enabled and configured)
                →  Queue        (one WP-Cron event per channel, retries, log)
                →  Channel      (Telegram / email / webhook)

Adding a channel is one class: implement Channel (id, label, fields, is_configured, send) and add it to the array in Plugin::create_channels(), or hand it in through the notify_telegram_channels filter. fields() is what the settings screen renders, so a channel brings its own form. Adding an event is one class too: implement EventSource and register it through the notify_telegram_event_sources filter — the placeholders an event declares are both its documentation and the validation rules for its message template.

The screen

Notify Telegram  →  admin.php?page=notify-telegram               Events    (default)
                    admin.php?page=notify-telegram&tab=settings  Settings

One menu entry, no submenu: Events and Settings are tabs inside the page. WordPress prints the submenu list only when $submenu holds entries for the parent slug (wp-admin/menu-header.php), so the plugin registers a single add_menu_page() and no add_submenu_page() at all — not even one that reuses the parent slug, which is the usual trick for labelling the first entry and is exactly what unfolds the list on hover. The sidebar shows one plain link, and toplevel_page_notify-telegram is the only screen the bundle is enqueued on.

Events lists every registered event with its switch, its message and the placeholders that event accepts; Settings holds the master switch, one card per channel with the fields the channel itself declares, the test button and the last twenty delivery attempts. The state lives in the root component, so switching tabs never loses an edit and one Save button stores both — the plugin keeps all of it in a single option anyway.

The open tab is addressable: AdminPage::initial_tab() puts the requested tab query argument (only events or settings pass; anything else falls back to the first tab) into the configuration the screen is booted with, and the application writes the chosen tab back into the address bar with history.replaceState(). A reload or a shared link therefore opens the same tab, without a WordPress page per tab.

The menu entry

One entry, and AdminPage::MENU_POSITION is the only knob for where it sits: 75.9 gives it its own slot under Tools (75) and above Settings (80), while core's other sections are at 4 (a separator), 10 (Media), 20 (Pages), 60 (Appearance), 65 (Plugins) and 70 (Users) — a plugin that passes no position at all lands after all of them. Move the number to move the entry: 3 is directly under Dashboard, 59.9 just above Appearance. A position another plugin has already taken costs nothing: core checks the key before writing and gives the later registration a small offset instead of a shared slot, which is why positions live in the menu as string keys and why a float is a legitimate value here.

The icon is the core bell (dashicons-bell), so the entry is painted by the admin colour scheme exactly like the rest of the sidebar — including the dimmed state, the hover colour and the highlight of the open screen, none of which an image of our own would follow. AdminPage::ICON is the one place to change it: any dashicons-* class works, and WordPress renders it through div.wp-menu-image:before (wp-admin/menu-header.php). An icon of our own is possible — a data:image/svg+xml;base64,… URI is the one form core special-cases, and it keeps whatever palette the SVG declares — but that icon then ignores the colour scheme entirely, so it is not what ships here.

PHP prints the WordPress heading, the description and the mount element; the application fills the mount element and nothing else. The lab's starter-plugin keeps a working reference of the build setup — including the part that is easy to get wrong, that the bundle must be an IIFE, because wp_enqueue_script() prints a classic `