onenotif 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Notifier.js ADDED
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+
3
+ export class NotifierUI {
4
+ constructor(config = {}) {
5
+ this.notifications = [];
6
+ this.maxNotifications = config.maxNotifications || 20;
7
+ this.separator = '─'.repeat(60);
8
+ this.showWelcome();
9
+ }
10
+
11
+ showWelcome() {
12
+ console.clear();
13
+ console.log(chalk.cyan.bold('╔════════════════════════════════════════════════════════════╗'));
14
+ console.log(chalk.cyan.bold('║') + ' ' + chalk.white.bold('OneNotif') + ' - Notification Viewer ' + chalk.cyan.bold('║'));
15
+ console.log(chalk.cyan.bold('╚════════════════════════════════════════════════════════════╝'));
16
+ console.log(chalk.gray('Waiting for notifications...\n'));
17
+ }
18
+
19
+ formatTime(date) {
20
+ return date.toLocaleTimeString('en-US', { hour12: false });
21
+ }
22
+
23
+ display(notification) {
24
+ const isNew = !this.notifications.includes(notification);
25
+
26
+ // Add to history
27
+ this.notifications.push(notification);
28
+ if (this.notifications.length > this.maxNotifications) {
29
+ this.notifications.shift();
30
+ }
31
+
32
+ console.log('');
33
+ console.log(chalk.gray(this.separator));
34
+
35
+ // Source badge
36
+ const sourceBadge = this.getSourceColor(notification.source);
37
+ const timeStr = this.formatTime(notification.timestamp);
38
+ console.log(sourceBadge(' ' + notification.source + ' ') + chalk.gray(' · ') + chalk.gray(timeStr));
39
+
40
+ // Title
41
+ if (notification.title) {
42
+ console.log(chalk.white.bold(' ' + notification.title));
43
+ }
44
+
45
+ // Content
46
+ if (notification.content) {
47
+ console.log(chalk.gray(' ' + notification.content));
48
+ }
49
+
50
+ // From info
51
+ if (notification.from) {
52
+ console.log(chalk.dim(' from: @' + notification.from));
53
+ }
54
+
55
+ console.log(chalk.gray(this.separator));
56
+
57
+ // If it's a status message, show differently
58
+ if (notification.type === 'status') {
59
+ console.log(chalk.cyan(' ' + notification.content || notification.message));
60
+ }
61
+ }
62
+
63
+ getSourceColor(source) {
64
+ const colors = {
65
+ 'Telegram': chalk.bgRed,
66
+ 'Webhook': chalk.bgBlue,
67
+ 'Email': chalk.bgGreen,
68
+ 'System': chalk.bgYellow
69
+ };
70
+ return colors[source] || chalk.bgGray;
71
+ }
72
+ }
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # OneNotif
2
+
3
+ Simple terminal notification viewer for Telegram and other sources.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g onenotif
9
+ ```
10
+
11
+ Requires Node.js 18 or higher.
12
+
13
+ ## Setup
14
+
15
+ 1. Create a Telegram bot:
16
+ - Open Telegram and search for @BotFather
17
+ - Send `/newbot` and follow the instructions
18
+ - Copy the bot token
19
+
20
+ 2. Get your Telegram ID:
21
+ - Search for @userinfobot in Telegram
22
+ - It will show your user ID
23
+
24
+ 3. Configure:
25
+
26
+ Create a config file at `~/.onenotif/config.json`:
27
+ ```json
28
+ {
29
+ "sources": {
30
+ "telegram": {
31
+ "enabled": true,
32
+ "botToken": "YOUR_BOT_TOKEN",
33
+ "allowedUsers": ["your_telegram_id"]
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ Or set environment variable:
40
+ ```bash
41
+ export ONENOTIF_TELEGRAM_TOKEN="your_bot_token"
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ Start the app:
47
+ ```bash
48
+ onenotif
49
+ ```
50
+
51
+ Send messages to your bot on Telegram - they'll appear in your terminal!
52
+
53
+ ```
54
+ ────────────────────────────────────────────────────────────
55
+ Telegram · 14:32:15
56
+ John
57
+ Hello from Telegram!
58
+ from @username
59
+ ────────────────────────────────────────────────────────────
60
+ ```
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ git clone https://github.com/yourusername/onenotif.git
66
+ cd onenotif
67
+ npm install
68
+ npm start
69
+ ```
70
+
71
+ ## Adding More Sources
72
+
73
+ Create a new source class in `sources/` that extends `BaseSource`:
74
+
75
+ ```js
76
+ import { BaseSource } from './BaseSource.js';
77
+
78
+ export class MySource extends BaseSource {
79
+ async start() {
80
+ // Your implementation
81
+ // Call this.emit({ title, content, timestamp }) when notification arrives
82
+ }
83
+ }
84
+ ```
85
+
86
+ Then add it to `cli.js`.
87
+
88
+ ## License
89
+
90
+ MIT
package/cli.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { config as loadEnv } from 'dotenv';
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { homedir } from 'os';
8
+ import chalk from 'chalk';
9
+ import { TelegramSource } from './sources/TelegramSource.js';
10
+ import { WebhookSource } from './sources/WebhookSource.js';
11
+ import { NotifierUI } from './Notifier.js';
12
+
13
+ loadEnv();
14
+
15
+ // Get the directory where this script is located
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ // Load config - try multiple paths in order
19
+ let config = {
20
+ sources: {
21
+ telegram: { enabled: true },
22
+ webhook: { enabled: false }
23
+ },
24
+ display: { maxNotifications: 20 }
25
+ };
26
+
27
+ const configPaths = [
28
+ process.env.CONFIG_PATH,
29
+ join(homedir(), '.onenotif', 'config.json'),
30
+ join(__dirname, 'config.json'),
31
+ './config.json'
32
+ ].filter(Boolean);
33
+
34
+ for (const path of configPaths) {
35
+ if (existsSync(path)) {
36
+ try {
37
+ config = { ...config, ...JSON.parse(readFileSync(path, 'utf-8')) };
38
+ break;
39
+ } catch (err) {
40
+ // Continue to next path
41
+ }
42
+ }
43
+ }
44
+
45
+ // Initialize UI
46
+ const ui = new NotifierUI(config.display);
47
+ const sources = [];
48
+
49
+ // Initialize Telegram
50
+ if (config.sources?.telegram?.enabled) {
51
+ const tgConfig = {
52
+ botToken: config.sources.telegram.botToken || process.env.ONENOTIF_TELEGRAM_TOKEN || process.env.TELEGRAM_BOT_TOKEN,
53
+ allowedUsers: config.sources.telegram.allowedUsers || []
54
+ };
55
+
56
+ if (tgConfig.botToken) {
57
+ const telegram = new TelegramSource(tgConfig);
58
+ telegram.onNotification((notif) => ui.display(notif));
59
+ sources.push(telegram);
60
+ } else {
61
+ ui.display({
62
+ source: 'System',
63
+ type: 'error',
64
+ title: 'Telegram',
65
+ content: 'Set TELEGRAM_BOT_TOKEN env var or add botToken to config.json',
66
+ timestamp: new Date()
67
+ });
68
+ }
69
+ }
70
+
71
+ // Initialize Webhook
72
+ if (config.sources?.webhook?.enabled) {
73
+ const webhook = new WebhookSource({
74
+ port: config.sources.webhook.port || 3000
75
+ });
76
+ webhook.onNotification((notif) => ui.display(notif));
77
+ sources.push(webhook);
78
+ }
79
+
80
+ // Start all sources
81
+ console.log(chalk.gray('Starting notification sources...\n'));
82
+
83
+ for (const source of sources) {
84
+ try {
85
+ await source.start();
86
+ } catch (err) {
87
+ ui.display({
88
+ source: 'System',
89
+ type: 'error',
90
+ title: source.name,
91
+ content: err.message,
92
+ timestamp: new Date()
93
+ });
94
+ }
95
+ }
96
+
97
+ // Keep running
98
+ console.log(chalk.green('\n✓ All sources started. Press Ctrl+C to exit.\n'));
99
+
100
+ // Graceful shutdown
101
+ process.on('SIGINT', async () => {
102
+ console.log(chalk.yellow('\n\nShutting down...'));
103
+ for (const source of sources) {
104
+ await source.stop();
105
+ }
106
+ process.exit(0);
107
+ });
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "onenotif",
3
+ "version": "0.1.0",
4
+ "description": "Simple terminal notification viewer for Telegram and other sources",
5
+ "type": "module",
6
+ "main": "cli.js",
7
+ "bin": {
8
+ "onenotif": "./cli.js"
9
+ },
10
+ "exports": {
11
+ ".": "./cli.js",
12
+ "./sources": "./sources/index.js"
13
+ },
14
+ "scripts": {
15
+ "start": "node cli.js",
16
+ "dev": "node --watch cli.js",
17
+ "test": "node cli.js"
18
+ },
19
+ "keywords": [
20
+ "notification",
21
+ "telegram",
22
+ "terminal",
23
+ "cli",
24
+ "notifier",
25
+ "alerts",
26
+ "webhook"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "files": [
34
+ "cli.js",
35
+ "Notifier.js",
36
+ "sources/",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/techbruwh/onenotif"
43
+ },
44
+ "homepage": "https://github.com/techbruwh/onenotif",
45
+ "bugs": "https://github.com/techbruwh/onenotif/issues",
46
+ "dependencies": {
47
+ "dotenv": "^16.4.5",
48
+ "grammy": "^1.21.1",
49
+ "chalk": "^5.3.0"
50
+ },
51
+ "devDependencies": {}
52
+ }
@@ -0,0 +1,32 @@
1
+ // Base class for notification sources
2
+ export class BaseSource {
3
+ constructor(name, config) {
4
+ this.name = name;
5
+ this.config = config;
6
+ this.callbacks = [];
7
+ }
8
+
9
+ // Override this in subclasses
10
+ async start() {
11
+ throw new Error(`${this.name}: start() not implemented`);
12
+ }
13
+
14
+ async stop() {
15
+ // Override if needed
16
+ }
17
+
18
+ // Register callback for new notifications
19
+ onNotification(callback) {
20
+ this.callbacks.push(callback);
21
+ }
22
+
23
+ // Notify all listeners
24
+ emit(notification) {
25
+ for (const cb of this.callbacks) {
26
+ cb({
27
+ source: this.name,
28
+ ...notification
29
+ });
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,65 @@
1
+ import { Bot } from 'grammy';
2
+ import { BaseSource } from './BaseSource.js';
3
+
4
+ export class TelegramSource extends BaseSource {
5
+ constructor(config) {
6
+ super('Telegram', config);
7
+ this.bot = null;
8
+ }
9
+
10
+ async start() {
11
+ if (!this.config.botToken) {
12
+ console.error('Telegram: No botToken provided');
13
+ return;
14
+ }
15
+
16
+ this.bot = new Bot(this.config.botToken);
17
+
18
+ this.bot.command('start', (ctx) => this.handleStart(ctx));
19
+ this.bot.command('help', (ctx) => this.handleHelp(ctx));
20
+
21
+ // Listen for any text message
22
+ this.bot.on('message:text', (ctx) => this.handleMessage(ctx));
23
+
24
+ // Start polling
25
+ await this.bot.start();
26
+ this.emit({ type: 'status', message: '✅ Telegram bot started' });
27
+ }
28
+
29
+ handleStart(ctx) {
30
+ ctx.reply('Welcome to OneNotif! Send me any message and it will appear in your terminal.');
31
+ }
32
+
33
+ handleHelp(ctx) {
34
+ ctx.reply('Commands: /start, /help. Any message you send will be shown in the terminal.');
35
+ }
36
+
37
+ handleMessage(ctx) {
38
+ const allowedUsers = this.config.allowedUsers || [];
39
+
40
+ // Check if user is allowed
41
+ if (allowedUsers.length > 0 && !allowedUsers.includes(String(ctx.from.id))) {
42
+ ctx.reply('Sorry, you are not authorized to use this bot.');
43
+ return;
44
+ }
45
+
46
+ const notification = {
47
+ type: 'message',
48
+ title: ctx.from.first_name || ctx.from.username || 'User',
49
+ content: ctx.message.text,
50
+ timestamp: new Date(),
51
+ from: ctx.from.username || ctx.from.id
52
+ };
53
+
54
+ this.emit(notification);
55
+
56
+ // Send read receipt (optional)
57
+ // ctx.reply('📨 Notification received!');
58
+ }
59
+
60
+ async stop() {
61
+ if (this.bot) {
62
+ await this.bot.stop();
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,57 @@
1
+ import { BaseSource } from './BaseSource.js';
2
+
3
+ export class WebhookSource extends BaseSource {
4
+ constructor(config) {
5
+ super('Webhook', config);
6
+ this.server = null;
7
+ }
8
+
9
+ async start() {
10
+ const http = await import('http');
11
+
12
+ this.server = http.createServer((req, res) => {
13
+ if (req.method === 'POST' && req.url === '/webhook') {
14
+ let body = '';
15
+
16
+ req.on('data', (chunk) => {
17
+ body += chunk.toString();
18
+ });
19
+
20
+ req.on('end', () => {
21
+ try {
22
+ const data = JSON.parse(body);
23
+
24
+ this.emit({
25
+ type: 'webhook',
26
+ title: data.title || 'Webhook',
27
+ content: data.message || data.content || JSON.stringify(data),
28
+ timestamp: new Date()
29
+ });
30
+
31
+ res.writeHead(200, { 'Content-Type': 'application/json' });
32
+ res.end(JSON.stringify({ status: 'received' }));
33
+ } catch (err) {
34
+ res.writeHead(400);
35
+ res.end('Invalid JSON');
36
+ }
37
+ });
38
+ } else {
39
+ res.writeHead(404);
40
+ res.end('Not found');
41
+ }
42
+ });
43
+
44
+ this.server.listen(this.config.port || 3000, () => {
45
+ this.emit({
46
+ type: 'status',
47
+ message: `✅ Webhook server listening on port ${this.config.port || 3000}`
48
+ });
49
+ });
50
+ }
51
+
52
+ async stop() {
53
+ if (this.server) {
54
+ this.server.close();
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ export { BaseSource } from './BaseSource.js';
2
+ export { TelegramSource } from './TelegramSource.js';
3
+ export { WebhookSource } from './WebhookSource.js';