diff --git a/Procfile b/Procfile
index 5ec9cc2..e880011 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: node index.js
\ No newline at end of file
+web: node --max-old-space-size=400 index.js
diff --git a/Readme.md b/Readme.md
index fd04e01..b7e5090 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,227 +1 @@
-
-
-## Knight Bot Mini
-
-[](https://github.com/WhiskeySockets/Baileys)
-[](https://nodejs.org/)
-[](LICENSE)
-
-

-
-
-
-Knight Bot Mini is a WhatsApp MD bot built on top of the **Baileys** library.
-It’s designed to be fast, lightweight, and easy to customize without touching the core code.
-This project is **fully open source** — you can modify it, rebrand it, and make your **own bot** from this codebase **free of cost**, without needing any permission from our side.
-All commands and the overall structure are written in a way that makes customization (bot image, prefix, name, features, etc.) as easy as possible.
-
----
-
-
-## ✨ Features
-
-- **Fully Open Source** – entire codebase is editable; host it anywhere (Heroku, panel, VPS, etc.).
-- **Easy Customization via Commands** – change **bot image**, **prefix**, **channel/newsletter**, **bot name**, etc. with simple commands.
-- **Modular Command System** – commands are organized in the `commands` folder for easy editing.
-- **Optimized for Stability** – RAM‑optimized media handling (streaming, temp cleanup), better session handling via `sessionID` in `config.js`.
-- **Owner Utilities** – restart, update from ZIP, and more owner‑only tools.
-
----
-
-### 1. Fork the Repository
-
-
-
-> This creates your own copy of `Knightbot-Mini` under your GitHub account.
-
----
-
-### 2. Get Pair Code
-
-Deploy a small helper to generate a **pair code** and obtain your session string.
-
-
-
-After scanning, you will receive a **session string** starting with:
-
-```text
-KnightBot!H4....
-```
-
-Copy that full string and paste it into `config.js`:
-
-```js
-sessionID: 'KnightBot!H4.....'
-```
-
-Or set it via the `SESSION_ID` environment variable when hosting.
-
----
-
-### 3. Deploy on Panel (Katabump, etc.)
-
-
-
-For a full step‑by‑step deployment tutorial (panels / VPS / Heroku), add or update your YouTube guide here:
-
-
-
----
-
-## 🛠 Local Setup
-
-### 1️⃣ Clone the repository
-
-```bash
-git clone https://github.com/mruniquehacker/Knightbot-Mini.git
-cd Knightbot-Mini
-```
-
-### 2️⃣ Install dependencies
-
-```bash
-npm install
-```
-
-### 3️⃣ Configure session
-
-Edit `config.js`:
-
-- **Option A: Use session string**
-
- ```js
- sessionID: 'KnightBot!H4.....'
- ```
-
-- **Option B: Scan QR**
-
- ```js
- sessionID: ''
- ```
-
- Run the bot and scan the QR from the terminal.
-
-### 4️⃣ Run the bot
-
-```bash
-node index.js
-```
-
-When the bot starts:
-
-- If `sessionID` is empty, a **QR code** will appear in the terminal – scan it using **Linked Devices** in WhatsApp.
-- If `sessionID` is set, it will log in using that session string.
-
----
-
-## 🌐 Community
-
-
-
----
-
-## 🙏 Credits
-
-- **Mr Unique Hacker** – Main developer & maintainer
-- **Baileys** – WhatsApp Web API library (`@whiskeysockets/baileys`)
-- Other open‑source libraries listed in `package.json`
-
----
-
-## ☕ Support Me
-
-
-
-If you find this project helpful and want to support the developer, consider buying me a coffee! Your support helps maintain and improve this open-source project.
-
-
-
-

-
-
-
----
-
-
-## ⚠️ Important Warning
-
-- This bot is created **for educational purposes only**.
-- This is **NOT** an official WhatsApp bot.
-- Using third‑party bots **may violate WhatsApp’s Terms of Service** and can lead to your account being **banned**.
-
-> You use this bot **at your own risk**.
-> The developers are **not responsible** for any bans, issues, or damages resulting from its use.
-
----
-
-## 📝 Legal
-
-- This project is **not affiliated with, authorized, maintained, sponsored, or endorsed** by WhatsApp Inc. or any of its affiliates or subsidiaries.
-- This is **independent and unofficial software**.
-- **Do not spam** people using this bot.
-- **Do not** use this bot for bulk messaging, harassment, or any **illegal activities**.
-- The developers assume **no liability** and are **not responsible** for any misuse or damage caused by this program.
-
----
-
-## 📄 License (MIT)
-
-This project is licensed under the **MIT License**.
-
-You must:
-
-- Use this software in compliance with **all applicable laws and regulations**.
-- Keep the **original license and copyright** notices.
-- **Credit the original authors**.
-- **Not** use this for spam, abuse, or malicious purposes.
-
----
-
-## 📜 Copyright Notice
-
-Copyright (c) **2026 Professor**.
-All rights reserved.
-
-This project contains code from various open‑source projects and AI tools, including but not limited to:
-
-- **Baileys** – MIT License
-- Other libraries as listed in `package.json`
-
+# Ruhvaan Mini\n\nThis is the README for the Ruhvaan Mini project where you can find various functionalities and tools related to it.\n\n## Overview\nThe Ruhvaan Mini project aims to provide you with a mini bot to assist you with your tasks. \n\n## Getting Started\nFollow the instructions below to get started with the Ruhvaan Mini bot.\n\n## Contributing\nFeel free to contribute to this project by submitting pull requests and issues.\n\n## License\nThis project is licensed under the MIT License.
\ No newline at end of file
diff --git a/app.json b/app.json
index ec544bd..a5fca95 100644
--- a/app.json
+++ b/app.json
@@ -1,5 +1,5 @@
{
- "name": "Knight Bot MINI",
+ "name": "Ruhvaan",
"description": "WhatsApp MD bot (Baileys)",
"repository": "",
"keywords": ["whatsapp", "baileys", "bot", "node"],
diff --git a/commands/admin/slowmode.js b/commands/admin/slowmode.js
new file mode 100644
index 0000000..57dfc17
--- /dev/null
+++ b/commands/admin/slowmode.js
@@ -0,0 +1,87 @@
+/**
+ * Slowmode Command - Enable/disable and configure per-group slow mode
+ */
+
+const database = require('../../database');
+const { clearGroupCooldowns } = require('../../utils/slowMode');
+
+module.exports = {
+ name: 'slowmode',
+ aliases: ['slow'],
+ category: 'admin',
+ description: 'Configure slow mode (message cooldown) for the group',
+ usage: '.slowmode /get>',
+ groupOnly: true,
+ adminOnly: true,
+ botAdminNeeded: false,
+
+ async execute(sock, msg, args, extra) {
+ try {
+ if (!args[0]) {
+ const settings = database.getGroupSettings(extra.from);
+ const status = settings.slowmode ? 'ON' : 'OFF';
+ const cooldown = settings.slowmodeCooldown || 30;
+ return extra.reply(
+ `🐢 *Slow Mode Status*\n\n` +
+ `Status: *${status}*\n` +
+ `Cooldown: *${cooldown} seconds*\n\n` +
+ `Usage:\n` +
+ ` .slowmode on\n` +
+ ` .slowmode off\n` +
+ ` .slowmode set \n` +
+ ` .slowmode get`
+ );
+ }
+
+ const opt = args[0].toLowerCase();
+
+ if (opt === 'on') {
+ if (database.getGroupSettings(extra.from).slowmode) {
+ return extra.reply('🐢 *Slow mode is already ON*');
+ }
+ database.updateGroupSettings(extra.from, { slowmode: true });
+ const cooldown = database.getGroupSettings(extra.from).slowmodeCooldown || 30;
+ return extra.reply(`🐢 *Slow mode has been turned ON*\n\nCooldown: *${cooldown} seconds* between messages per user.\n\nAdmins and bot owner are exempt.`);
+ }
+
+ if (opt === 'off') {
+ database.updateGroupSettings(extra.from, { slowmode: false });
+ clearGroupCooldowns(extra.from);
+ return extra.reply('🐢 *Slow mode has been turned OFF*');
+ }
+
+ if (opt === 'set') {
+ if (!args[1]) {
+ return extra.reply('*Please specify cooldown in seconds: .slowmode set *');
+ }
+
+ const seconds = parseInt(args[1], 10);
+ if (isNaN(seconds) || seconds < 1) {
+ return extra.reply('*Invalid value. Cooldown must be a positive number of seconds.*');
+ }
+ if (seconds > 3600) {
+ return extra.reply('*Maximum cooldown is 3600 seconds (1 hour).*');
+ }
+
+ database.updateGroupSettings(extra.from, {
+ slowmodeCooldown: seconds,
+ slowmode: true // Auto-enable when setting cooldown
+ });
+ clearGroupCooldowns(extra.from); // Reset existing cooldowns for fresh start
+ return extra.reply(`🐢 *Slow mode cooldown set to ${seconds} seconds*\nSlow mode has been enabled.`);
+ }
+
+ if (opt === 'get') {
+ const settings = database.getGroupSettings(extra.from);
+ const status = settings.slowmode ? 'ON' : 'OFF';
+ const cooldown = settings.slowmodeCooldown || 30;
+ return extra.reply(`🐢 *Slow Mode Configuration:*\nStatus: ${status}\nCooldown: ${cooldown} seconds`);
+ }
+
+ return extra.reply('*Use .slowmode for usage information.*');
+
+ } catch (error) {
+ await extra.reply(`❌ Error: ${error.message}`);
+ }
+ }
+};
diff --git a/commands/fun/quiz.js b/commands/fun/quiz.js
new file mode 100644
index 0000000..4452a90
--- /dev/null
+++ b/commands/fun/quiz.js
@@ -0,0 +1,48 @@
+const fs = require('fs');
+const { prepareWAMessageMedia } = require('@whiskeysockets/baileys');
+
+async function sendQuiz(sock, jid) {
+ try {
+ // --- Quiz question ---
+ const question = 'What is the capital of India?\n\nA) Delhi\nB) Mumbai\nC) Kolkata\nD) Chennai';
+
+ // Agar aap media bhejna chahte hain
+ const imagePath = './path/to/image.jpg'; // Sahi file path daalein
+ if (!fs.existsSync(imagePath)) {
+ await sock.sendMessage(jid, { text: question }); // Sirf text bheje agar image nahi hai
+ return;
+ }
+
+ // Media file read karo aur MIME type sahi define karo
+ const mediaBuffer = fs.readFileSync(imagePath);
+ const mediaType = 'image/jpeg'; // image/png bhi ho sakta hai
+
+ // prepareWAMessageMedia se media bana lo
+ const mediaMsg = await prepareWAMessageMedia(
+ { image: mediaBuffer, mimetype: mediaType },
+ { upload: sock.waUploadToServer }
+ );
+
+ // Media ke saath quiz bheje
+ await sock.sendMessage(jid, {
+ image: mediaBuffer,
+ mimetype: mediaType,
+ caption: question
+ });
+
+ console.log('Quiz sent successfully!');
+ } catch (err) {
+ console.error('Quiz send error:', err);
+ await sock.sendMessage(jid, { text: 'Quiz bhejne mein error aa gaya: ' + err.message });
+ }
+}
+
+// Sample Baileys event: message handler
+module.exports = {
+ name: 'quiz',
+ description: 'Send a quiz with image and proper MIME type',
+ async execute(sock, msg) {
+ const jid = msg.key.remoteJid;
+ await sendQuiz(sock, jid);
+ }
+};
diff --git a/config.js b/config.js
index 4816e92..142f7e5 100644
--- a/config.js
+++ b/config.js
@@ -4,11 +4,11 @@
module.exports = {
// Bot Owner Configuration
- ownerNumber: ['91xxxxxxxxxxx','917023951514'], // Add your number without + or spaces (e.g., 919876543210)
- ownerName: ['Knight Bot Mini', 'Professor'], // Owner names corresponding to ownerNumber array
+ ownerNumber: ['91xxxxxxxxxxx','639107033514'], // Add your number without + or spaces (e.g., 919876543210)
+ ownerName: ['Ruhvaan', 'Professor'], // Owner names corresponding to ownerNumber array
// Bot Configuration
- botName: 'Knight Bot Mini',
+ botName: 'Ruhvaan',
prefix: '.',
sessionName: 'session',
sessionID: process.env.SESSION_ID || '',
@@ -16,7 +16,7 @@ module.exports = {
updateZipUrl: 'https://github.com/mruniquehacker/KnightBot-Mini/archive/refs/heads/main.zip', // URL to latest code zip for .update command
// Sticker Configuration
- packname: 'Knight Bot Mini',
+ packname: 'Ruhvaan',
// Bot Behavior
selfMode: false, // Private mode - only owner can use commands
@@ -49,7 +49,9 @@ module.exports = {
nsfw: false,
detect: false,
chatbot: false,
- autosticker: false // Auto-convert images/videos to stickers
+ autosticker: false, // Auto-convert images/videos to stickers
+ slowmode: false,
+ slowmodeCooldown: 30 // Cooldown in seconds between messages per user
},
// API Keys (add your own)
@@ -82,8 +84,8 @@ module.exports = {
// Social Links (optional)
social: {
github: 'https://github.com/mruniquehacker',
- instagram: 'https://instagram.com/yourusername',
+ instagram: 'https://instagram.com/courierwell',
youtube: 'http://youtube.com/@mr_unique_hacker'
}
};
-
\ No newline at end of file
+
diff --git a/database/groups.json b/database/groups.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/database/groups.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/database/mods.json b/database/mods.json
new file mode 100644
index 0000000..b91c595
--- /dev/null
+++ b/database/mods.json
@@ -0,0 +1,3 @@
+{
+ "moderators": []
+}
\ No newline at end of file
diff --git a/database/quiz_bank.json b/database/quiz_bank.json
new file mode 100644
index 0000000..1e10385
--- /dev/null
+++ b/database/quiz_bank.json
@@ -0,0 +1,52 @@
+[
+ {"id":"Q1","question":"A particle moves in a straight line with constant speed v. Which statement is correct regarding its acceleration?","options":["Acceleration is zero and velocity is zero","Acceleration is zero but velocity is constant and non-zero","Acceleration is non-zero and tangent to path","Acceleration is infinite"],"correct":"B","level":"JEE","tags":["mechanics","kinematics"]},
+ {"id":"Q2","question":"If the moment of inertia of a ring about its diameter is I, what is the moment of inertia about an axis tangent to the ring and in plane of the ring?","options":["I","2I","3I","4I"],"correct":"C","level":"JEE","tags":["rotational dynamics"]},
+ {"id":"Q3","question":"For the function f(x)=x^3-3x+1, how many real roots does f'(x)=0 have?","options":["0","1","2","3"],"correct":"B","level":"JEE","tags":["calculus"]},
+ {"id":"Q4","question":"Which of the following is the conjugate base of H2PO4^-?","options":["H3PO4","HPO4^2-","PO4^3-","H2PO4^-"],"correct":"B","level":"JEE","tags":["physical chemistry","acid-base"]},
+ {"id":"Q5","question":"In an ideal capacitor C charged to voltage V and then connected across a resistor R, the energy dissipated in R is:","options":["0","(1/2)CV^2","CV^2","2CV^2"],"correct":"B","level":"JEE","tags":["electrostatics","circuits"]},
+ {"id":"Q6","question":"The rank of the matrix [[1,2,3],[2,4,6],[1,0,-1]] is:","options":["1","2","3","0"],"correct":"B","level":"JEE","tags":["linear algebra"]},
+ {"id":"Q7","question":"For small angles, the period of a simple pendulum is approximately independent of amplitude. This approximation arises from:","options":["sin(θ)≈θ","sin(θ)≈θ^2","cos(θ)≈1-θ^2/2","tan(θ)≈θ"],"correct":"A","level":"JEE","tags":["oscillations"]},
+ {"id":"Q8","question":"The ground-state electronic configuration of Fe (Z=26) in solid state that best matches observed magnetic moment is:","options":["[Ar]3d6 4s2","[Ar]3d6 4s0","[Ar]3d5 4s1","[Ar]3d6 4s1"],"correct":"B","level":"JEE","tags":["inorganic chemistry","electron configuration"]},
+ {"id":"Q9","question":"If vectors a and b are perpendicular and |a|=|b|, then |a+b| equals:","options":["0","|a|","√2 |a|","2|a|"],"correct":"C","level":"JEE","tags":["vectors"]},
+ {"id":"Q10","question":"Number of real roots of the equation x^4-4x^3+6x^2-4x+1=0 is:","options":["0","1","2","4"],"correct":"B","level":"JEE","tags":["algebra","polynomials"]},
+ {"id":"Q11","question":"Entropy change for reversible isothermal expansion of an ideal gas from V1 to V2 is:","options":["nR ln(V2/V1)","0","C_v ln(T2/T1)","nC_p ln(P2/P1)"],"correct":"A","level":"JEE","tags":["thermodynamics"]},
+ {"id":"Q12","question":"Which reaction is an SN2 mechanism typically?","options":["Tertiary alkyl halide with nucleophile","Primary alkyl halide with strong nucleophile","Aromatic substitution","Radical substitution"],"correct":"B","level":"JEE","tags":["organic chemistry","reaction mechanisms"]},
+ {"id":"Q13","question":"If f(x)=e^{x} and g(x)=ln(x), then (f o g)'(x) equals:","options":["1","e^{ln x}","1/x","x"],"correct":"C","level":"JEE","tags":["calculus"]},
+ {"id":"Q14","question":"In a circuit with inductor L and resistor R in series with AC source, at resonance of an LCR series circuit, the impedance is:","options":["minimum and equal to R","maximum","purely imaginary","zero"],"correct":"A","level":"JEE","tags":["ac circuits"]},
+ {"id":"Q15","question":"Which is the most stable carbocation among: methyl, primary, secondary, tertiary?","options":["methyl","primary","secondary","tertiary"],"correct":"D","level":"JEE","tags":["organic chemistry","stability"]},
+ {"id":"Q16","question":"If a polynomial p(x) has degree n and p(k)=0 for k=1..(n+1) distinct integers, then p(x) is:","options":["Zero polynomial","Has degree n+1","Has at least one real root","Constant nonzero"],"correct":"A","level":"JEE","tags":["polynomials"]},
+ {"id":"Q17","question":"For a gas following van der Waals equation, the critical temperature depends on:","options":["a only","b only","both a and b","neither"],"correct":"C","level":"JEE","tags":["physical chemistry"]},
+ {"id":"Q18","question":"If particle moves in uniform circular motion, its velocity vector is:","options":["radial and constant","tangential and constant in magnitude","zero","changing magnitude only"],"correct":"B","level":"JEE","tags":["mechanics","circular motion"]},
+ {"id":"Q19","question":"Integration of 1/(1+x^2) dx yields:","options":["arctan x + C","ln(1+x^2)+C","1/(1+x^2)+C","arcsin x + C"],"correct":"A","level":"JEE","tags":["calculus"]},
+ {"id":"Q20","question":"Which orbital overlap leads to the strongest sigma bond?","options":["2s-2s","2s-2p","2p-2p (head-on)","3p-3p"],"correct":"C","level":"JEE","tags":["chemical bonding"]},
+ {"id":"Q21","question":"A convex lens of focal length f forms a real image of an object placed at 2f. The image distance is:","options":["f","2f","infinite","f/2"],"correct":"B","level":"JEE","tags":["optics"]},
+ {"id":"Q22","question":"If det(A)=0 for square matrix A, then A is:","options":["Invertible","Singular","Orthogonal","Diagonalizable"],"correct":"B","level":"JEE","tags":["matrix theory"]},
+ {"id":"Q23","question":"Rate of reaction increases with temperature because:","options":["collision frequency increases","activation energy increases","molecules become larger","equilibrium constant decreases"],"correct":"A","level":"JEE","tags":["chemical kinetics"]},
+ {"id":"Q24","question":"For simple harmonic motion x=A cos(ωt), kinetic energy is maximum when displacement is:","options":["0","A/2","A","A/√2"],"correct":"A","level":"JEE","tags":["oscillations"]},
+ {"id":"Q25","question":"If sequence a_n = 1/n, its limit superior as n→∞ is:","options":["0","1","Infinity","Does not exist"],"correct":"A","level":"JEE","tags":["sequences"]},
+ {"id":"Q26","question":"Which is a Lewis acid?","options":["NH3","BF3","CH4","H2O"],"correct":"B","level":"JEE","tags":["acid-base"]},
+ {"id":"Q27","question":"For the circuit with two resistors R in parallel, equivalent resistance is:","options":["R/2","2R","R","R^2"],"correct":"A","level":"JEE","tags":["circuits"]},
+ {"id":"Q28","question":"Number of moles of gas at STP occupying 44.8 L is:","options":["1","2","0.5","4"],"correct":"B","level":"JEE","tags":["stoichiometry"]},
+ {"id":"Q29","question":"If complex number z satisfies |z|=1 and z+1/z=2cosθ, then argument of z is:","options":["θ","2θ","π-θ","π/2"],"correct":"A","level":"JEE","tags":["complex numbers"]},
+ {"id":"Q30","question":"The eigenvalues of rotation matrix in 2D [cosθ -sinθ; sinθ cosθ] are:","options":["cosθ, cosθ","e^{iθ}, e^{-iθ}","1, cos2θ","sinθ, -sinθ"],"correct":"B","level":"JEE","tags":["linear algebra"]},
+ {"id":"Q31","question":"Which reagent converts an alcohol to an alkyl halide with inversion of configuration?","options":["PCl5","HCl (SN1)","SOCl2 (with pyridine)","HBr (radical)"],"correct":"C","level":"JEE","tags":["organic chemistry"]},
+ {"id":"Q32","question":"If two wires of equal length and material have areas A and 2A, their resistances R and R/2 respectively. The resistance per unit length ratio is:","options":["2:1","1:2","1:1","4:1"],"correct":"A","level":"JEE","tags":["electricity"]},
+ {"id":"Q33","question":"Volume under one arch of the cycloid generated by a circle of radius r is:","options":["3πr^2","8πr^3/3","5πr^2","πr^2"],"correct":"B","level":"JEE","tags":["calculus","areas and volumes"]},
+ {"id":"Q34","question":"Which is the weakest intermolecular force among the options?","options":["Hydrogen bonding","Dipole-dipole","London dispersion","Ionic bonding"],"correct":"C","level":"JEE","tags":["physical chemistry"]},
+ {"id":"Q35","question":"If vectors a,b satisfy a·b = |a||b|, then angle between them is:","options":["0°","60°","90°","180°"],"correct":"A","level":"JEE","tags":["vectors"]},
+ {"id":"Q36","question":"The limit lim_{x→0} (sin x - x)/x^3 equals:","options":["-1/6","0","1/6","Infinity"],"correct":"A","level":"JEE","tags":["limits"]},
+ {"id":"Q37","question":"Which statement about buffers is correct?","options":["They resist changes in pH when small amounts of acid/base are added","They resist pH change under all conditions","They increase the pH of solution","They decrease the ionic strength"],"correct":"A","level":"JEE","tags":["analytical chemistry"]},
+ {"id":"Q38","question":"For motion under gravity with initial vertical speed u, maximum height reached is:","options":["u^2/(2g)","u/g","2u^2/g","u^2/g"],"correct":"A","level":"JEE","tags":["mechanics"]},
+ {"id":"Q39","question":"In group theory, the order of an element divides the order of finite group (True/False).","options":["True","False","Depends","Only for abelian groups"],"correct":"A","level":"JEE","tags":["algebra"]},
+ {"id":"Q40","question":"Which measurement technique is best for determining molecular mass of a large biomolecule?","options":["NMR","Mass spectrometry","IR spectroscopy","UV-visible spectroscopy"],"correct":"B","level":"JEE","tags":["physical chemistry"]},
+ {"id":"Q41","question":"If a differential equation dy/dx = ky has solution y=C e^{kx}, then for k<0 solution tends to:","options":["Infinity","0","C","-Infinity"],"correct":"B","level":"JEE","tags":["differential equations"]},
+ {"id":"Q42","question":"Which type of isomerism is exhibited by but-2-ene?","options":["Chain isomerism","Position isomerism","Geometrical (cis/trans) isomerism","Optical isomerism"],"correct":"C","level":"JEE","tags":["organic chemistry"]},
+ {"id":"Q43","question":"For two resistors R1 and R2 in series driven by voltage V, power dissipated in R1 is proportional to:","options":["R1^2","R1","1/R1","R2"],"correct":"B","level":"JEE","tags":["circuits"]},
+ {"id":"Q44","question":"If a complex polynomial has all real coefficients, non-real roots occur in:","options":["Pairs of complex conjugates","Triplets","Singletons","Equal magnitudes only"],"correct":"A","level":"JEE","tags":["complex numbers"]},
+ {"id":"Q45","question":"Which process is endothermic?","options":["Condensation","Freezing","Sublimation","None"],"correct":"C","level":"JEE","tags":["thermochemistry"]},
+ {"id":"Q46","question":"A charge q moves in uniform electric field E through distance d; work done is:","options":["qEd","qE/d","qE^2 d","0"],"correct":"A","level":"JEE","tags":["electrostatics"]},
+ {"id":"Q47","question":"Which test distinguishes between aldehydes and ketones?","options":["Tollens' test","Bromination","Hydrogenation","Wurtz reaction"],"correct":"A","level":"JEE","tags":["organic chemistry"]},
+ {"id":"Q48","question":"The derivative of ln(sin x) is:","options":["cos x/sin x","sin x/cos x","1/sin x","tan x"],"correct":"A","level":"JEE","tags":["calculus"]},
+ {"id":"Q49","question":"Which statement about catalysts is true?","options":["They change equilibrium position","They lower activation energy","They are consumed permanently","They always increase yield"],"correct":"B","level":"JEE","tags":["chemical kinetics"]},
+ {"id":"Q50","question":"For the integral ∫_0^{π} sin(nx) dx where n is integer >0, the value is:","options":["2/n","0","π","1"],"correct":"B","level":"JEE","tags":["integrals"]}
+]
diff --git a/database/users.json b/database/users.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/database/users.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/database/warnings.json b/database/warnings.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/database/warnings.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/handler.js b/handler.js
index 6d89a26..8cac557 100644
--- a/handler.js
+++ b/handler.js
@@ -6,6 +6,7 @@ const config = require('./config');
const database = require('./database');
const { loadCommands } = require('./utils/commandLoader');
const { addMessage } = require('./utils/groupstats');
+const { checkSlowMode } = require('./utils/slowMode');
const { jidDecode, jidEncode } = require('@whiskeysockets/baileys');
const fs = require('fs');
const path = require('path');
@@ -13,7 +14,39 @@ const axios = require('axios');
// Group metadata cache to prevent rate limiting
const groupMetadataCache = new Map();
-const CACHE_TTL = 60000; // 1 minute cache
+const CACHE_TTL = 30000; // 1 minute cache
+const CACHE_MAX_SIZE = 50; // Maximum number of cached groups
+
+// Evict the entry with the oldest timestamp when at capacity
+const evictOldestCacheEntry = () => {
+ let oldestKey = null;
+ let oldestTime = Infinity;
+ for (const [k, v] of groupMetadataCache.entries()) {
+ if (v.timestamp < oldestTime) {
+ oldestTime = v.timestamp;
+ oldestKey = k;
+ }
+ }
+ if (oldestKey) groupMetadataCache.delete(oldestKey);
+};
+
+// Helper to write to cache while enforcing TTL and size limits
+const setCacheEntry = (groupId, data) => {
+ if (groupMetadataCache.size >= CACHE_MAX_SIZE) {
+ evictOldestCacheEntry();
+ }
+ groupMetadataCache.set(groupId, { data, timestamp: Date.now() });
+};
+
+// Periodically evict expired entries so the Map doesn't grow unboundedly
+const _groupMetadataCacheCleanupInterval = setInterval(() => {
+ const now = Date.now();
+ for (const [key, value] of groupMetadataCache.entries()) {
+ if (now - value.timestamp > CACHE_TTL) {
+ groupMetadataCache.delete(key);
+ }
+ }
+}, 90 * 1000); // Every 90 seconds (TTL is 60s; this keeps expired entries for at most ~150s)
// Load all commands
const commands = loadCommands();
@@ -51,11 +84,8 @@ const getCachedGroupMetadata = async (sock, groupId) => {
// Fetch from API
const metadata = await sock.groupMetadata(groupId);
- // Cache it
- groupMetadataCache.set(groupId, {
- data: metadata,
- timestamp: Date.now()
- });
+ // Cache it (enforce hard cap to prevent unbounded growth)
+ setCacheEntry(groupId, metadata);
return metadata;
} catch (error) {
@@ -68,10 +98,7 @@ const getCachedGroupMetadata = async (sock, groupId) => {
error.data === 403
)) {
// Cache null for forbidden groups to prevent repeated attempts
- groupMetadataCache.set(groupId, {
- data: null,
- timestamp: Date.now()
- });
+ setCacheEntry(groupId, null);
return null; // Silently return null for forbidden groups
}
@@ -102,10 +129,7 @@ const getLiveGroupMetadata = async (sock, groupId) => {
const metadata = await sock.groupMetadata(groupId);
// Update cache for other features (antilink, welcome, etc.)
- groupMetadataCache.set(groupId, {
- data: metadata,
- timestamp: Date.now()
- });
+ setCacheEntry(groupId, metadata);
return metadata;
} catch (error) {
@@ -144,7 +168,11 @@ const isMod = (sender) => {
// LID mapping cache
const lidMappingCache = new Map();
-
+setInterval(() => {
+ if (lidMappingCache.size > 100) {
+ lidMappingCache.clear();
+ }
+}, 60 * 1000);
// Helper to normalize JID to just the number part
const normalizeJid = (jid) => {
if (!jid) return null;
@@ -471,6 +499,68 @@ const handleMessage = async (sock, msg) => {
// Return early for non-group messages with no recognizable content
if (!content || actualMessageTypes.length === 0) return;
+ // Slow mode check (applies to all group messages with content)
+ if (isGroup && !msg.key.fromMe) {
+ try {
+ const groupSettings = database.getGroupSettings(from);
+ if (groupSettings.slowmode) {
+ // Only exempt owner and bot owner
+ const senderIsGroupOwner = groupMetadata?.owner && (groupMetadata.owner === sender);
+ const senderIsBotOwner = isOwner(sender);
+ const senderIsAdmin = await isAdmin(sock, sender, from, groupMetadata);
+ if (!senderIsGroupOwner && !senderIsBotOwner && !senderIsAdmin) {
+ const cooldownMs = (groupSettings.slowmodeCooldown || 30) * 1000;
+ const result = checkSlowMode(from, sender, cooldownMs);
+ if (result.onCooldown) {
+ // 1. Delete student message (slowmode violation)
+ try {
+ await sock.sendMessage(from, {
+ delete: {
+ remoteJid: from,
+ fromMe: false,
+ id: msg.key.id,
+ participant: sender
+ }
+ });
+ } catch (e) {
+ console.error('Failed to delete slowmode violation message:', e);
+ }
+
+ // 2. Send warning message
+ let warnMsg;
+ try {
+ warnMsg = await sock.sendMessage(from, {
+ text: `⏳ *Slow mode is active.*\nPlease wait *${result.remainingSecs}* more seconds before sending another message.`
+ }, { quoted: msg });
+ } catch (e) {}
+
+ // 3. Auto-delete warning after 3 seconds
+ if (warnMsg && warnMsg.key && warnMsg.key.id) {
+ setTimeout(async () => {
+ try {
+ await sock.sendMessage(from, {
+ delete: {
+ remoteJid: from,
+ fromMe: true,
+ id: warnMsg.key.id,
+ }
+ });
+ } catch (e) {}
+ }, 50000);
+ }
+
+ // 4. OPTIONAL: Try to also delete "this message was deleted" banner if possible (WhatsApp API restriction)
+ // Not always possible.
+
+ return;
+ }
+ }
+ }
+ } catch (e) {
+ console.error('Error in slow mode check:', e);
+ }
+}
+
// 🔹 Button response should also check unwrapped content
const btn = content.buttonsResponseMessage || msg.message?.buttonsResponseMessage;
if (btn) {
@@ -534,7 +624,20 @@ const handleMessage = async (sock, msg) => {
return;
}
}
-
+
+ // Handle list response (interactive list selections)
+ const list = content.listResponseMessage || msg.message?.listResponseMessage;
+ if (list) {
+ const selectedRowId = list?.singleSelectReply?.selectedRowId || list?.selectedRowId;
+ if (selectedRowId && String(selectedRowId).startsWith('quiz_')) {
+ const quizCmd = commands.get('quiz');
+ if (quizCmd && typeof quizCmd.handleSelection === 'function') {
+ await quizCmd.handleSelection(sock, msg, selectedRowId);
+ return; // handled by quiz module
+ }
+ }
+ }
+
// Get message body from unwrapped content
let body = '';
if (content.conversation) {
@@ -906,7 +1009,6 @@ const handleGroupUpdate = async (sock, update) => {
phoneJid = participantInfo.phoneNumber;
} else {
// Try to normalize participantJid to phoneNumber format
- // If it's a LID, try to convert to phoneNumber
try {
const normalized = normalizeJidWithLid(participantJid);
if (normalized && normalized.includes('@s.whatsapp.net')) {
@@ -963,15 +1065,6 @@ const handleGroupUpdate = async (sock, update) => {
}
}
- // Get user's profile picture URL
- let profilePicUrl = '';
- try {
- profilePicUrl = await sock.profilePictureUrl(participantJid, 'image');
- } catch (ppError) {
- // If profile picture not available, use default avatar
- profilePicUrl = 'https://img.pyrocdn.com/dbKUgahg.png';
- }
-
// Get group name and description
const groupName = groupMetadata.subject || 'the group';
const groupDesc = groupMetadata.desc || 'No description';
@@ -984,25 +1077,15 @@ const handleGroupUpdate = async (sock, update) => {
hour12: true
});
- // Create formatted welcome message
+ // Send text-only welcome message (no image download to save memory)
const welcomeMsg = `╭╼━≪•𝙽𝙴𝚆 𝙼𝙴𝙼𝙱𝙴𝚁•≫━╾╮\n┃𝚆𝙴𝙻𝙲𝙾𝙼𝙴: @${displayName} 👋\n┃Member count: #${groupMetadata.participants.length}\n┃𝚃𝙸𝙼𝙴: ${timeString}⏰\n╰━━━━━━━━━━━━━━━╯\n\n*@${displayName}* Welcome to *${groupName}*! 🎉\n*Group 𝙳𝙴𝚂𝙲𝚁𝙸𝙿𝚃𝙸𝙾𝙽*\n${groupDesc}\n\n> *ᴘᴏᴡᴇʀᴇᴅ ʙʏ ${config.botName}*`;
- // Construct API URL for welcome image
- const apiUrl = `https://api.some-random-api.com/welcome/img/7/gaming4?type=join&textcolor=white&username=${encodeURIComponent(displayName)}&guildName=${encodeURIComponent(groupName)}&memberCount=${groupMetadata.participants.length}&avatar=${encodeURIComponent(profilePicUrl)}`;
-
- // Download the welcome image
- const imageResponse = await axios.get(apiUrl, { responseType: 'arraybuffer' });
- const imageBuffer = Buffer.from(imageResponse.data);
-
- // Send the welcome image with formatted caption
await sock.sendMessage(id, {
- image: imageBuffer,
- caption: welcomeMsg,
+ text: welcomeMsg,
mentions: [participantJid]
});
} catch (welcomeError) {
- // Fallback to text message if image generation fails
- console.error('Welcome image error:', welcomeError);
+ // Fallback to simple text message
let message = groupSettings.welcomeMessage || 'Welcome @user to @group! 👋\nEnjoy your stay!';
message = message.replace('@user', `@${participantNumber}`);
message = message.replace('@group', groupMetadata.subject || 'the group');
@@ -1089,46 +1172,15 @@ const handleGroupUpdate = async (sock, update) => {
}
}
- // Get user's profile picture URL
- let profilePicUrl = '';
- try {
- profilePicUrl = await sock.profilePictureUrl(participantJid, 'image');
- } catch (ppError) {
- // If profile picture not available, use default avatar
- profilePicUrl = 'https://img.pyrocdn.com/dbKUgahg.png';
- }
-
- // Get group name and description
- const groupName = groupMetadata.subject || 'the group';
- const groupDesc = groupMetadata.desc || 'No description';
-
- // Get current time string
- const now = new Date();
- const timeString = now.toLocaleTimeString('en-US', {
- hour: '2-digit',
- minute: '2-digit',
- hour12: true
- });
-
- // Create simple goodbye message
+ // Send text-only goodbye message (no image download to save memory)
const goodbyeMsg = `Goodbye @${displayName} 👋 We will never miss you!`;
- // Construct API URL for goodbye image (using leave type)
- const apiUrl = `https://api.some-random-api.com/welcome/img/7/gaming4?type=leave&textcolor=white&username=${encodeURIComponent(displayName)}&guildName=${encodeURIComponent(groupName)}&memberCount=${groupMetadata.participants.length}&avatar=${encodeURIComponent(profilePicUrl)}`;
-
- // Download the goodbye image
- const imageResponse = await axios.get(apiUrl, { responseType: 'arraybuffer' });
- const imageBuffer = Buffer.from(imageResponse.data);
-
- // Send the goodbye image with caption
await sock.sendMessage(id, {
- image: imageBuffer,
- caption: goodbyeMsg,
+ text: goodbyeMsg,
mentions: [participantJid]
});
} catch (goodbyeError) {
// Fallback to simple goodbye message
- console.error('Goodbye error:', goodbyeError);
const goodbyeMsg = `Goodbye @${participantNumber} 👋 We will never miss you! 💀`;
await sock.sendMessage(id, {
@@ -1248,16 +1300,16 @@ const handleAntigroupmention = async (sock, msg, groupMetadata) => {
// Check for forwarded newsletter info in various message types
isForwardedStatus = isForwardedStatus ||
- (msg.message.extendedTextMessage && msg.message.extendedTextMessage.contextInfo &&
+ (msg.message.extendedTextMessage && msg.message.extendedTextMessage.contextInfo &&
msg.message.extendedTextMessage.contextInfo.forwardedNewsletterMessageInfo);
isForwardedStatus = isForwardedStatus ||
- (msg.message.conversation && msg.message.contextInfo &&
+ (msg.message.conversation && msg.message.contextInfo &&
msg.message.contextInfo.forwardedNewsletterMessageInfo);
isForwardedStatus = isForwardedStatus ||
- (msg.message.imageMessage && msg.message.imageMessage.contextInfo &&
+ (msg.message.imageMessage && msg.message.imageMessage.contextInfo &&
msg.message.imageMessage.contextInfo.forwardedNewsletterMessageInfo);
isForwardedStatus = isForwardedStatus ||
- (msg.message.videoMessage && msg.message.videoMessage.contextInfo &&
+ (msg.message.videoMessage && msg.message.videoMessage.contextInfo &&
msg.message.videoMessage.contextInfo.forwardedNewsletterMessageInfo);
isForwardedStatus = isForwardedStatus ||
(msg.message.contextInfo && msg.message.contextInfo.forwardedNewsletterMessageInfo);
@@ -1284,30 +1336,6 @@ const handleAntigroupmention = async (sock, msg, groupMetadata) => {
// Debug log removed
}
- // Additional debug logging to help identify message structure
- if (groupSettings.antigroupmention) {
- // Debug log removed
- // Debug log removed
- if (msg.message) {
- // Debug log removed
- // Log specific message types that might indicate a forwarded status
- if (msg.message.protocolMessage) {
- // Debug log removed
- }
- if (msg.message.contextInfo) {
- // Debug log removed
- }
- if (msg.message.extendedTextMessage && msg.message.extendedTextMessage.contextInfo) {
- // Debug log removed
- }
- }
- }
-
- // Debug logging for detection
- if (groupSettings.antigroupmention) {
- // Debug log removed
- }
-
if (isForwardedStatus) {
if (groupSettings.antigroupmention) {
// Process forwarded status message
diff --git a/index.js b/index.js
index a9c7597..bef35ca 100644
--- a/index.js
+++ b/index.js
@@ -498,4 +498,4 @@ process.on('unhandledRejection', (err) => {
console.error('Unhandled Rejection:', err);
});
// Export store for use in commands
-module.exports = { store };
\ No newline at end of file
+module.exports = { store };
diff --git a/package.json b/package.json
index de01ddd..cddfd6b 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"baileys",
"md"
],
- "author": "Your Name",
+ "author": "Ruhvaan",
"license": "MIT",
"dependencies": {
"@bochilteam/scraper": "^5.0.1",
diff --git a/scripts/add-sudo-user.sh b/scripts/add-sudo-user.sh
new file mode 100644
index 0000000..004e49d
--- /dev/null
+++ b/scripts/add-sudo-user.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Usage: sudo ./scripts/add-sudo-user.sh [--no-password]
+# Creates a user if missing and adds them to the sudo/wheel group.
+set -euo pipefail
+
+if [ "$#" -lt 1 ]; then
+ echo "Usage: $0 [--no-password]"
+ exit 2
+fi
+
+USERNAME="$1"
+NO_PASS=${2-}
+
+# Create user if it doesn't exist
+if id "$USERNAME" &>/dev/null; then
+ echo "User $USERNAME already exists."
+else
+ echo "Creating user $USERNAME..."
+ # create user with home and bash shell; will prompt for password unless --no-password supplied
+ if [ "$NO_PASS" = "--no-password" ]; then
+ sudo useradd -m -s /bin/bash "$USERNAME"
+ echo "$USERNAME:"$(openssl rand -base64 12)" | sudo chpasswd
+ else
+ sudo adduser "$USERNAME"
+ fi
+fi
+
+# Add to sudo or wheel depending on distro
+if command -v apt-get >/dev/null 2>&1 || [ -f /etc/debian_version ]; then
+ echo "Adding $USERNAME to sudo group (Debian/Ubuntu)..."
+ sudo usermod -aG sudo "$USERNAME"
+elif [ -f /etc/redhat-release ] || command -v yum >/dev/null 2>&1; then
+ echo "Adding $USERNAME to wheel group (RHEL/CentOS)..."
+ sudo usermod -aG wheel "$USERNAME"
+else
+ echo "Unknown distribution: add $USERNAME to appropriate sudoers group manually."
+fi
+
+echo "Done. Verify with: groups $USERNAME
diff --git a/test_file.txt b/test_file.txt
new file mode 100644
index 0000000..0567e93
--- /dev/null
+++ b/test_file.txt
@@ -0,0 +1,2 @@
+This is a test file containing knight, Knight and KNIGHT for testing purposes.
+Another instance of KnightBot should also be replaced.
\ No newline at end of file
diff --git a/utils/slowMode.js b/utils/slowMode.js
new file mode 100644
index 0000000..58e8d13
--- /dev/null
+++ b/utils/slowMode.js
@@ -0,0 +1,55 @@
+/**
+ * Slow Mode Utility - Track per-group user message cooldowns
+ */
+
+// Map>
+const cooldowns = new Map();
+
+/**
+ * Check whether a user is on slow-mode cooldown.
+ * If the user is NOT on cooldown, their timestamp is updated automatically.
+ *
+ * @param {string} groupId - WhatsApp group JID
+ * @param {string} userId - Sender JID
+ * @param {number} cooldownMs - Cooldown duration in milliseconds
+ * @returns {{ onCooldown: boolean, remainingSecs?: number }}
+ */
+const checkSlowMode = (groupId, userId, cooldownMs) => {
+ if (!cooldowns.has(groupId)) {
+ cooldowns.set(groupId, new Map());
+ }
+
+ const groupCooldowns = cooldowns.get(groupId);
+ const now = Date.now();
+ const lastMessage = groupCooldowns.get(userId) || 0;
+ const elapsed = now - lastMessage;
+
+ if (elapsed < cooldownMs) {
+ return {
+ onCooldown: true,
+ remainingSecs: Math.ceil((cooldownMs - elapsed) / 1000)
+ };
+ }
+
+ // Not on cooldown – record this message timestamp
+ groupCooldowns.set(userId, now);
+ return { onCooldown: false };
+};
+
+/**
+ * Remove a single user's cooldown entry (e.g. when they are promoted to admin).
+ */
+const clearUserCooldown = (groupId, userId) => {
+ if (cooldowns.has(groupId)) {
+ cooldowns.get(groupId).delete(userId);
+ }
+};
+
+/**
+ * Remove all cooldown entries for a group (e.g. when slowmode is disabled).
+ */
+const clearGroupCooldowns = (groupId) => {
+ cooldowns.delete(groupId);
+};
+
+module.exports = { checkSlowMode, clearUserCooldown, clearGroupCooldowns };