Repository: Max-Eee/NeoPass
Branch: main
Commit: 079ea379f5db
Files: 25
Total size: 617.9 KB
Directory structure:
gitextract_mqhp625o/
├── README.md
├── contentScript.js
├── data/
│ ├── inject/
│ │ ├── anti-anti-debug.js
│ │ ├── chatbot.js
│ │ ├── content.js
│ │ ├── copyOverride.js
│ │ ├── customPaste.js
│ │ ├── exam.js
│ │ ├── isolated.js
│ │ ├── main.js
│ │ ├── mock_code/
│ │ │ ├── minifiedBackground.js
│ │ │ ├── minifiedContent-script.js
│ │ │ ├── mock_manifest.json
│ │ │ └── rules.json
│ │ ├── mock_code.js
│ │ ├── rightclickmenu.js
│ │ └── screenshare.js
│ └── nptel.json
├── devtools.js
├── manifest.json
├── metadata.json
├── nptel.txt
├── popup.html
├── popup.js
└── worker.js
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# **`Free`** NeoPass Extension
> **NeoPass Pro** - [Click here to see Pro features and benefits](https://neopass.tech/pro)
This chrome extension is for students taking tests on the **`Iamneo portal`**, **`HackerRank`**, **`Wildlife Ecology NPTEL`**, **`conservation-geography NPTEL`**, **`forest management NPTEL`** and `other exam portals in chrome browser` that restrict your abilities
### [**Make sure to visit our website for the best experience!**](https://freeneopass.vercel.app) 🌐
> [!IMPORTANT]
> **Free Users**: No sign-up needed! Configure your own AI API key by clicking the extension icon and going to the **Settings** tab.
> Supported providers: OpenAI, Google Gemini, Anthropic Claude, and custom endpoints.
>
>
> **Want a hassle-free experience?** Upgrade to Pro by visiting **neopass.tech/pro** for AI managed by NeoPass (GPT-5.1), increased rate limits, and NeoBrowser with built in Exam Helper access!
> [!WARNING]
> **Educational Purposes Only**: This extension is intended for educational purposes. Please use it responsibly and ethically.
> We am not responsible for any actions taken, and we do not encourage or promote cheating in any way.
> Be cautious when using the extension to maintain academic integrity.
## ✨ Features
### Free Version (Bring Your Own API Key)
- **`NPTEL Integration`** : Solve NPTEL Wildlife ecology answers
- **`NeoExamShield Bypass`** : Break free from Examly's limitations. NeoPass mimics the NeoExamShield extension
- **`Chatbot With Stealth Mode`** : Leverage AI Chatbot to enhance your search capabilities
- **`AI Search Answers/Code`** : Perform AI-powered searches, helping you find answers without switching tabs
- **`Solve MCQ`** : Quickly Search MCQ Answers by simply selecting
- **`Tab Switching Bypass`** : Prevents unwanted tab switch restrictions
- **`Pasting When Restricted`** : Quickly paste answers with ease, reducing the time spent on manual entry
- **`Multiple AI Providers`** : Support for OpenAI, Google Gemini, Anthropic Claude, and custom endpoints
### Pro Version Features
- **`Everything in free`** : All free features are included
- **`Managed AI by NeoPass`** : Powered by GPT-5.1 - no API key needed!
- **`NeoBrowser Access`** : Exclusive access to the NeoBrowser with built in Exam Helper
- **`No Network Restrictions`** : Works even if AI providers are blocked on your network
- **`Increased Rate Limits`** : Higher usage limits for intensive exam sessions
- **`Priority Support`** : Get help when you need it most
- **`Hassle-Free Experience`** : No configuration needed, just login and go!
## ⬇️ Installation
1. [Download](https://github.com/Max-Eee/NeoPass/archive/refs/heads/main.zip) the extension.
2. Open Chrome and go to the Extensions page by typing `chrome://extensions/`.
3. Enable **Developer mode** in the top right corner.
4. Click on **Load unpacked** and select the folder where the extension is located.
5. Your NeoPass extension is now installed!
### Installation Guide Video
https://github.com/user-attachments/assets/89fb986c-2edb-4252-8232-dbd10beec0cf
## 💻 Usage
### For Free Users:
1. Click the NeoPass extension icon in your browser toolbar
2. Navigate to the **Settings** tab
3. Enter your AI API key (OpenAI, Google Gemini, Anthropic, or custom endpoint)
4. Select your AI provider from the dropdown menu
5. Click "Test Connection" to verify your setup
6. Start using all NeoPass features with your own API!
> [!NOTE]
> **Network Restrictions**: If your school/organization blocks AI service providers (OpenAI, Google, etc.), the extension will not work even with a valid API key. In this case, consider using a VPN or upgrade to Pro by visiting **neopass.tech/pro**.
### For Pro Users:
1. Visit [neopass.tech/pro](https://freeneopass.vercel.app/pro) to subscribe
2. Click the extension icon and go to the **Pro** tab
3. Login with your Pro credentials you have created from the webstie
4. Enjoy hassle-free AI-powered assistance with no configuration needed!
## ⌨️ Shortcuts
### Windows/Linux Users:
- Alt + Shift + Q : Solve Iam Neo MCQs/Coding Questions with 100% ACCURACY
- Alt + Shift + A : Solve Iam Neo MCQs/Coding Questions with using AI [Backup]
- Alt + Shift + T : Autotypes Iam Neo Coding Question Solution letter by letter
- Alt + Shift + H : Solve HackerRank Questions [BETA]
> [!NOTE]
> The following shortcuts **require text to be selected** before activation:
> - Alt + Shift + N : Solve NPTEL MCQs from selected text
> - Alt + Shift + S : Search answers and code from selected text
> - Alt + Shift + M : Search MCQs from selected text
- Ctrl + V : Paste content when blocked
- Alt + C : Open/Close Chatbot
Mac Users (Click to expand)
- Ctrl + Shift + Q : Solve Iam Neo MCQs/Coding Questions with 100% ACCURACY
- Option + Shift + A : Solve Iam Neo MCQs/Coding Questions with using AI [Backup]
- Ctrl + Shift + T : Autotypes Iam Neo Coding Question Solution letter by letter
- Ctrl + Shift + H : Solve HackerRank Questions [BETA]
> [!NOTE]
> The following shortcuts **require text to be selected** before activation:
> - Option + Shift + N : Solve NPTEL MCQs from selected text
> - Option + Shift + S : Search answers and code from selected text
> - Option + Shift + M : Search MCQs from selected text
- Cmd + V : Paste content when blocked
- Option + C : Open/Close Chatbot
## 🤝 Contribute or Add NPTEL Dataset
If you want to contribute to the NPTEL question database, follow these steps:
1. Fork this repository
2. Open your NPTEL assignment page in the browser
3. Open browser developer tools (F12 or right-click > Inspect)
4. Go to the Console tab
5. Copy and paste the script from `nptel.txt` in the repository
6. Run the script by pressing Enter
7. The script will extract all questions and correct answers from the page
8. Copy the output JSON data
9. Update the `data/nptel.json` file with the new questions and answers
10. Create a pull request to contribute your additions back to the main repository
This helps expand our database and improves the accuracy of the NPTEL question solving feature!
## 💬 Feedback
We'd love to hear your thoughts! If you encounter any issues or have suggestions for improvement, please reach out. Your feedback is invaluable! 💌
📧 **Contact us at:** [freeneopass@gmail.com](mailto:freeneopass@gmail.com?subject=Issue%20Title%3A%20%5BBrief%20description%20of%20your%20issue%5D&body=Hello%20NeoPass%20Support%20Team%2C%0A%0AIssue%20Description%3A%0A%5BPlease%20describe%20your%20issue%20in%20detail%5D%0A%0AWhen%20does%20this%20occur%3A%0A%5BSpecify%20when%20the%20issue%20happens%20-%20e.g.%2C%20during%20login%2C%20while%20using%20a%20specific%20feature%2C%20etc.%5D%0A%0ASteps%20to%20Reproduce%3A%0A1.%20%5BFirst%20step%5D%0A2.%20%5BSecond%20step%5D%0A3.%20%5BThird%20step%5D%0A%0AScreenshots%2FError%20Messages%20if%20possible%3A%0A%5BPlease%20attach%20any%20relevant%20screenshots%20or%20paste%20error%20messages%20here%5D%0A%0AAdditional%20Information%3A%0A%5BAny%20other%20relevant%20details%5D%0A%0AThank%20you!)
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
================================================
FILE: contentScript.js
================================================
// Check if the chrome object is available (for compatibility)
if (typeof chrome === "undefined") {
// Handle the case where chrome is not defined (like in Firefox)
}
// Always inject mock_code.js interceptor to handle extension detection (even when not logged in)
(function injectMockCode() {
const mockScript = document.createElement('script');
mockScript.src = chrome.runtime.getURL('data/inject/mock_code.js');
mockScript.onload = function () {
console.log('✅ Mock code interceptor loaded');
this.remove(); // Clean up after execution
};
mockScript.onerror = function() {
console.error('❌ Failed to load mock code interceptor');
};
// Inject as early as possible
(document.head || document.documentElement).prepend(mockScript);
})();
// Inject exam.js (no login required)
const script = document.createElement('script');
script.src = chrome.runtime.getURL('data/inject/exam.js');
(document.head || document.documentElement).appendChild(script);
// Login prompt and status sync removed - extension features now available to all users
// Function removed - login check no longer required for extension features
// Neo Browser Download Link - Updated
const neoBrowserDownloadLink = "https://freeneopass.vercel.app";
// Function to add our NeoPass button left of the existing Neo Browser button
function replaceNeoBrowserButton() {
const neoButton = document.querySelector('button#neobrowser');
if (neoButton && !neoButton.dataset.replaced) {
// Create custom styled button/link
const ourBtn = document.createElement('a');
ourBtn.innerHTML = `
Download NeoPass Launcher
`;
ourBtn.href = neoBrowserDownloadLink;
ourBtn.target = "_blank";
ourBtn.className = neoButton.className;
ourBtn.id = "neopass-browser-btn";
ourBtn.tabIndex = 0;
// Apply gradient styling
ourBtn.style.cssText = `
position: relative !important;
display: inline-flex !important;
padding: 8px 16px !important;
font-size: 14px !important;
font-weight: 500 !important;
color: white !important;
background-color: black !important;
border-radius: 8px !important;
text-align: center !important;
text-decoration: none !important;
cursor: pointer !important;
z-index: 1 !important;
border: 2px solid transparent !important;
transition: all 0.3s ease !important;
`;
// Create gradient border effect
const beforeStyle = document.createElement('style');
beforeStyle.textContent = `
a#neopass-browser-btn {
position: relative !important;
background: linear-gradient(black, black) padding-box,
linear-gradient(45deg, #3b82f6, #8b5cf6, #ec4899) border-box !important;
border: 2px solid transparent !important;
}
a#neopass-browser-btn:hover {
transform: scale(1.05) !important;
box-shadow: 0 0 20px rgba(139, 92, 246, 0.6) !important;
}
`;
if (!document.querySelector('style[data-neobrowser-style]')) {
beforeStyle.setAttribute('data-neobrowser-style', 'true');
document.head.appendChild(beforeStyle);
}
// Insert our button to the left of the existing button
neoButton.parentNode.insertBefore(ourBtn, neoButton);
// Make the parent (app-button) a flex row so both buttons sit side by side
neoButton.parentNode.style.cssText += `
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 8px !important;
`;
neoButton.dataset.replaced = "true";
console.log('✅ NeoPass NeoBrowser button added left of existing Neo Browser button');
}
}
// Observer to detect Neo Browser button and add our button
const buttonObserver = new MutationObserver((mutations) => {
replaceNeoBrowserButton();
});
// Start observing for button changes
buttonObserver.observe(document.body, {
childList: true,
subtree: true
});
// Initial check for Neo Browser button (in case already loaded)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', replaceNeoBrowserButton);
} else {
replaceNeoBrowserButton();
}
// Listen for window messages
window.addEventListener("message", function(event) {
// Only process messages that:
// 1. Come from the same window
// 2. Are targeted for the extension
if (event.data.target === "extension") {
// Forward the message to the extension's background script
chrome.runtime.sendMessage(event.data.message, response => {
// Send the response back to the window
window.postMessage({
source: "extension",
response: response
}, "*");
});
}
});
window.addEventListener("message", function (event) {
if (event.source === window && event.data.target === "extension") {
browser.runtime.sendMessage(event.data.message, (response) => {
window.postMessage({ source: "extension", response: response }, "*");
});
}
});
// Listen for the 'beforeunload' event to remove any injected elements
window.addEventListener("beforeunload", removeInjectedElement);
// Function to send a message to the website
function sendMessageToWebsite(messageData) {
removeInjectedElement(); // Clean up any previous injected elements
// Create a new span element with a unique ID
const injectedElement = document.createElement("span");
injectedElement.id = "x-template-base-" + messageData.currentKey; // Set a unique ID based on currentKey
// Append the new element to the document body
document.body.appendChild(injectedElement);
console.log("message", messageData); // Log the message data
// Send the message to the website
window.postMessage(0, messageData.url); // 0 is the targetOrigin, meaning the same origin
}
// Function to remove injected elements from the DOM
function removeInjectedElement() {
const injectedElement = document.querySelector("[id^='x-template-base-']"); // Select elements with ID starting with "x-template-base-"
if (injectedElement) {
injectedElement.remove(); // Remove the element if it exists
}
}
================================================
FILE: data/inject/anti-anti-debug.js
================================================
!(() => {
const Proxy = window.Proxy;
const Object = window.Object;
const Array = window.Array;
/**
* Save original methods before we override them
*/
const Originals = {
createElement: document.createElement,
log: console.log,
table: console.table,
clear: console.clear,
functionConstructor: window.Function.prototype.constructor,
setInterval: window.setInterval,
createElement: document.createElement,
toString: Function.prototype.toString,
addEventListener: window.addEventListener
}
/**
* Cutoffs for logging. After cutoff is reached, will no longer log anti debug warnings.
*/
const cutoffs = {
table: {
amount: 5,
within: 5000
},
clear: {
amount: 5,
within: 5000
},
redactedLog: {
amount: 5,
within: 5000
},
debugger: {
amount: 10,
within: 10000
},
debuggerThrow: {
amount: 10,
within: 10000
}
}
/**
* Decides if anti debug warnings should be logged
*/
function shouldLog(type) {
return false;
}
window.console.log = wrapFn((...args) => {
// Keep track of redacted arguments
let redactedCount = 0;
// Filter arguments for detectors
const newArgs = args.map((a) => {
// Don't print functions.
if (typeof a === 'function') {
redactedCount++;
return "Redacted Function";
}
// Passthrough if primitive
if (typeof a !== 'object' || a === null) return a;
// For objects, scan properties
var props = Object.getOwnPropertyDescriptors(a)
for (var name in props) {
// Redact custom getters
if (props[name].get !== undefined) {
redactedCount++;
return "Redacted Getter";
}
// Also block toString overrides
if (name === 'toString') {
redactedCount++;
return "Redacted Str";
}
}
// Defeat Performance Detector
// https://github.com/theajack/disable-devtool/blob/master/src/detector/sub-detector/performance.ts
if (Array.isArray(a) && a.length === 50 && typeof a[0] === "object") {
redactedCount++;
return "Redacted LargeObjArray";
}
return a;
});
// If most arguments are redacted, its probably spam
if (redactedCount >= Math.max(args.length - 1, 1)) {
if (!shouldLog("redactedLog")) {
return;
}
}
}, Originals.log);
window.console.table = wrapFn((obj) => {
if (shouldLog("table")) {
}
}, Originals.table);
window.console.clear = wrapFn(() => {
if (shouldLog("table")) {
}
}, Originals.clear);
let debugCount = 0;
window.Function.prototype.constructor = wrapFn((...args) => {
const originalFn = Originals.functionConstructor.apply(this, args);
var fnContent = args[0];
if (fnContent) {
if (fnContent.includes('debugger')) { // An anti-debugger is attempting to stop debugging
if (shouldLog("debugger")) {
}
debugCount++;
if (debugCount > 100) {
if (shouldLog("debuggerThrow")) {
}
throw new Error("You bad!");
} else {
setTimeout(() => {
debugCount--;
}, 1);
}
const newArgs = args.slice(0);
newArgs[0] = args[0].replaceAll("debugger", ""); // remove debugger statements
return new Proxy(Originals.functionConstructor.apply(this, newArgs),{
get: function (target, prop) {
if (prop === "toString") {
return originalFn.toString;
}
return target[prop];
}
});
}
}
return originalFn;
}, Originals.functionConstructor);
document.createElement = wrapFn((el, o) => {
var string = el.toString();
var element = Originals.createElement.apply(document, [string, o]);
if (string.toLowerCase() === "iframe") {
element.addEventListener("load", () => {
try {
element.contentWindow.window.console = window.console;
} catch (e) {
}
});
}
return element;
}, Originals.createElement);
function wrapFn(newFn, old) {
return new Proxy(newFn, {
get: function (target, prop) {
const callMethods = ['apply', 'bind', 'call'];
if (callMethods.includes(prop)) {
return target[prop];
}
return old[prop];
}
});
}
})()
================================================
FILE: data/inject/chatbot.js
================================================
if (typeof chrome === "undefined") {}
if (typeof window.isMac === 'undefined') {
window.isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ||
navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
}
(function() {
chrome.storage.local.get(['stealth'], function(result) {
if (window.chatOverlayInjected) {
console.log("Chat overlay script already injected.");
return;
}
window.chatOverlayInjected = true;
const isStealthModeEnabled = result.stealth === true;
console.log("Initial stealth mode state:", isStealthModeEnabled);
function loadShowdown() {
return new Promise((resolve, reject) => {
if (typeof showdown !== 'undefined') {
resolve();
return;
}
const script = document.createElement('script');
script.src = chrome.runtime.getURL('data/lib/showdown.min.js'); // Local path
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function loadPrism() {
return new Promise((resolve) => {
// Create a lightweight inline syntax highlighter to bypass CSP
window.SimplePrism = {
highlightElement: function(codeElement) {
const code = codeElement.textContent;
const language = codeElement.className.replace('language-', '');
// Use a simpler approach to avoid overlapping replacements
let highlightedCode = this.simpleHighlight(code, language);
codeElement.innerHTML = highlightedCode;
},
simpleHighlight: function(code, language) {
// Escape HTML first
let highlighted = code.replace(/&/g, '&')
.replace(//g, '>');
// Apply basic highlighting based on language
if (language === 'python') {
highlighted = this.highlightPython(highlighted);
} else if (language === 'javascript' || language === 'js') {
highlighted = this.highlightJavaScript(highlighted);
} else if (language === 'java') {
highlighted = this.highlightJava(highlighted);
} else if (language === 'css') {
highlighted = this.highlightCSS(highlighted);
} else if (language === 'html') {
highlighted = this.highlightHTML(highlighted);
} else if (language === 'sql') {
highlighted = this.highlightSQL(highlighted);
} else if (language === 'json') {
highlighted = this.highlightJSON(highlighted);
} else {
// Default to javascript-like highlighting
highlighted = this.highlightJavaScript(highlighted);
}
return highlighted;
},
highlightPython: function(code) {
// Use a token-based approach to avoid overlapping
let tokens = [];
let currentIndex = 0;
// First, find all comments
let match;
const commentRegex = /#.*$/gm;
while ((match = commentRegex.exec(code)) !== null) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'comment',
content: match[0]
});
}
// Find strings (avoiding those inside comments)
const stringRegex = /(['"])((?:\\.|(?!\1)[^\\])*?)\1/g;
while ((match = stringRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'string',
content: match[0]
});
}
}
// Find keywords (avoiding those inside comments and strings)
const keywordRegex = /\b(def|class|if|elif|else|for|while|return|import|from|try|except|finally|with|as|and|or|not|in|is)\b/g;
while ((match = keywordRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'keyword',
content: match[0]
});
}
}
// Find booleans and None
const booleanRegex = /\b(True|False|None)\b/g;
while ((match = booleanRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'boolean',
content: match[0]
});
}
}
// Find numbers
const numberRegex = /\b\d+(\.\d+)?\b/g;
while ((match = numberRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'number',
content: match[0]
});
}
}
// Sort tokens by position
tokens.sort((a, b) => a.start - b.start);
// Build highlighted code
let result = '';
let lastIndex = 0;
tokens.forEach(token => {
// Add unhighlighted text before this token
result += code.slice(lastIndex, token.start);
// Add highlighted token
result += `${token.content}`;
lastIndex = token.end;
});
// Add remaining text
result += code.slice(lastIndex);
return result;
},
buildHighlightedCode: function(code, tokens) {
// Sort tokens by their start position
tokens.sort((a, b) => a.start - b.start);
let result = '';
let lastIndex = 0;
for (let token of tokens) {
// Add text before this token
result += code.slice(lastIndex, token.start);
// Add the highlighted token
result += `${token.content}`;
lastIndex = token.end;
}
// Add remaining text
result += code.slice(lastIndex);
return result;
},
isInsideToken: function(position, tokens) {
return tokens.some(token => position >= token.start && position < token.end);
},
highlightJavaScript: function(code) {
let tokens = [];
let match;
// Find comments first
const singleLineCommentRegex = /\/\/.*$/gm;
while ((match = singleLineCommentRegex.exec(code)) !== null) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'comment',
content: match[0]
});
}
const multiLineCommentRegex = /\/\*[\s\S]*?\*\//g;
while ((match = multiLineCommentRegex.exec(code)) !== null) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'comment',
content: match[0]
});
}
// Find strings
const stringRegex = /(['"`])((?:\\.|(?!\1)[^\\])*?)\1/g;
while ((match = stringRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'string',
content: match[0]
});
}
}
// Find keywords
const keywordRegex = /\b(function|const|let|var|if|else|for|while|return|import|export|class|extends|new|this|typeof|instanceof)\b/g;
while ((match = keywordRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'keyword',
content: match[0]
});
}
}
// Find booleans
const booleanRegex = /\b(true|false|null|undefined)\b/g;
while ((match = booleanRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'boolean',
content: match[0]
});
}
}
// Find numbers
const numberRegex = /\b\d+(\.\d+)?\b/g;
while ((match = numberRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'number',
content: match[0]
});
}
}
return this.buildHighlightedCode(code, tokens);
},
highlightJava: function(code) {
let tokens = [];
let match;
// Find comments first
const singleLineCommentRegex = /\/\/.*$/gm;
while ((match = singleLineCommentRegex.exec(code)) !== null) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'comment',
content: match[0]
});
}
const multiLineCommentRegex = /\/\*[\s\S]*?\*\//g;
while ((match = multiLineCommentRegex.exec(code)) !== null) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'comment',
content: match[0]
});
}
// Find strings
const stringRegex = /(['"])((?:\\.|(?!\1)[^\\])*?)\1/g;
while ((match = stringRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'string',
content: match[0]
});
}
}
// Find keywords
const keywordRegex = /\b(public|private|protected|static|final|class|interface|extends|implements|if|else|for|while|return|import|package|new|this)\b/g;
while ((match = keywordRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'keyword',
content: match[0]
});
}
}
// Find booleans
const booleanRegex = /\b(true|false|null)\b/g;
while ((match = booleanRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'boolean',
content: match[0]
});
}
}
// Find numbers
const numberRegex = /\b\d+(\.\d+)?[fFdDlL]?\b/g;
while ((match = numberRegex.exec(code)) !== null) {
if (!this.isInsideToken(match.index, tokens)) {
tokens.push({
start: match.index,
end: match.index + match[0].length,
type: 'number',
content: match[0]
});
}
}
return this.buildHighlightedCode(code, tokens);
},
highlightCSS: function(code) {
// Comments first
code = code.replace(/\/\*[\s\S]*?\*\//g, '$&');
// Selectors
code = code.replace(/([.#][a-zA-Z][a-zA-Z0-9_-]*)/g, '$1');
// Properties
code = code.replace(/([a-zA-Z-]+)(\s*:)/g, '$1$2');
// Values
code = code.replace(/(#[0-9a-fA-F]+)/g, '$1');
return code;
},
highlightHTML: function(code) {
// Comments first
code = code.replace(/(<!--[\s\S]*?-->)/g, '$1');
// Tags
code = code.replace(/(<\/?[^>]+>)/g, '$1');
return code;
},
highlightSQL: function(code) {
// Comments first
code = code.replace(/--.*$/gm, '$&');
// Strings
code = code.replace(/'[^']*'/g, '$&');
// Keywords
code = code.replace(/\b(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|TABLE|INDEX|PRIMARY|KEY|FOREIGN|NOT|NULL|DEFAULT|AND|OR|ORDER|BY|GROUP|HAVING|LIMIT)\b/gi, '$1');
// Numbers
code = code.replace(/\b\d+(\.\d+)?\b/g, '$&');
return code;
},
highlightJSON: function(code) {
// Property keys first (before general strings)
code = code.replace(/"([^"]*)"(\s*:)/g, '"$1"$2');
// Remaining strings
code = code.replace(/"([^"]*)"/g, '"$1"');
// Booleans and null
code = code.replace(/\b(true|false|null)\b/g, '$1');
// Numbers
code = code.replace(/\b\d+(\.\d+)?\b/g, '$&');
return code;
}
};
// Add CSS for syntax highlighting with clean default theme
// Styles will be added to shadow DOM later, not to document.head
window._chatSyntaxHighlightCSS = `
.keyword { color: #0066CC; font-weight: bold; }
.string { color: #008000; }
.comment { color: #808080; font-style: italic; }
.number { color: #FF6600; }
.boolean { color: #0066CC; font-weight: bold; }
.property { color: #9932CC; }
.selector { color: #008000; font-weight: bold; }
.value { color: #FF6600; }
.tag { color: #0066CC; }
`;
resolve();
});
}
// Chat icon SVG data URL (matching Crisp style)
const CHAT_ICON_SVG_URL = 'url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2235%22%20height%3D%2230%22%20viewBox%3D%220%200%2035%2030%22%3E%3Cdefs%3E%3Cfilter%20id%3D%22c%22%20width%3D%22123.1%25%22%20height%3D%22127.9%25%22%20x%3D%22-11.5%25%22%3E%3CfeOffset%20dy%3D%221%22%20in%3D%22SourceAlpha%22%20result%3D%22shadowOffsetOuter1%22%2F%3E%3CfeGaussianBlur%20in%3D%22shadowOffsetOuter1%22%20result%3D%22shadowBlurOuter1%22%20stdDeviation%3D%221%22%2F%3E%3CfeColorMatrix%20in%3D%22shadowBlurOuter1%22%20values%3D%220%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200.07%200%22%2F%3E%3C%2Ffilter%3E%3Cfilter%20id%3D%22e%22%20width%3D%22129.7%25%22%20height%3D%22135.9%25%22%20x%3D%22-14.8%25%22%20y%3D%22-14%25%22%3E%3CfeMorphology%20in%3D%22SourceAlpha%22%20radius%3D%221%22%20result%3D%22shadowSpreadInner1%22%2F%3E%3CfeGaussianBlur%20in%3D%22shadowSpreadInner1%22%20result%3D%22shadowBlurInner1%22%20stdDeviation%3D%222%22%2F%3E%3CfeOffset%20in%3D%22shadowBlurInner1%22%20result%3D%22shadowOffsetInner1%22%2F%3E%3CfeComposite%20in%3D%22shadowOffsetInner1%22%20in2%3D%22SourceAlpha%22%20k2%3D%22-1%22%20k3%3D%221%22%20operator%3D%22arithmetic%22%20result%3D%22shadowInnerInner1%22%2F%3E%3CfeColorMatrix%20in%3D%22shadowInnerInner1%22%20values%3D%220%200%200%200%201%200%200%200%200%201%200%200%200%200%201%200%200%200%200.750191215%200%22%2F%3E%3C%2Ffilter%3E%3ClinearGradient%20id%3D%22d%22%20x1%3D%2246.514%25%22%20x2%3D%2256.692%25%22%20y1%3D%2215.835%25%22%20y2%3D%2275.847%25%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%22.601%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20id%3D%22a%22%20d%3D%22m40.34%2016.878.005.052%201.327%2014.35a2%202%200%200%201-1.754%202.17l-7.814.934-3.293%205.326a1%201%200%200%201-1.574.165l-4.207-4.407-8.113.969a2%202%200%200%201-2.228-1.802l-1.328-14.35a2%202%200%200%201%201.755-2.17l25-2.986a2%202%200%200%201%202.223%201.749%22%2F%3E%3C%2Fdefs%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20transform%3D%22translate%28-9%20-14%29%22%3E%3Cuse%20xlink%3Ahref%3D%22%23a%22%20fill%3D%22%23000%22%20filter%3D%22url%28%23c%29%22%2F%3E%3Cuse%20xlink%3Ahref%3D%22%23a%22%20fill%3D%22url%28%23d%29%22%2F%3E%3Cuse%20xlink%3Ahref%3D%22%23a%22%20fill%3D%22%23000%22%20filter%3D%22url%28%23e%29%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")';
// State variables
let isOverlayVisible = false;
let chatHistory = [];
let isDragging = false;
let isResizing = false;
let markdownConverter = null; // Will be initialized when showdown loads
let currentStreamingDiv = null; // Tracks the current assistant message being streamed
let chatAboutQuestionEnabled = false; // Toggle for chat about question feature
let extractedQuestion = null; // Store extracted question
// Helper function to access elements in shadow DOM
function getShadowElement(id) {
const shadowHost = document.getElementById('chat-overlay-shadow-host');
if (!shadowHost || !shadowHost.shadowRoot) return null;
return shadowHost.shadowRoot.getElementById(id);
}
function getShadowRoot() {
const shadowHost = document.getElementById('chat-overlay-shadow-host');
return shadowHost ? shadowHost.shadowRoot : null;
}
function getChatButton() {
const buttonShadowHost = document.getElementById('chat-button-shadow-host');
if (!buttonShadowHost || !buttonShadowHost.shadowRoot) return null;
return buttonShadowHost.shadowRoot.getElementById('chat-button');
}
// Drag and resize state
let dragOffsetX;
let dragOffsetY;
let initialWidth;
let initialHeight;
let resizeStartX;
let resizeStartY;
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);
// Question extraction functions
function detectPlatform() {
// Check for Examly/IamNeo
if (document.querySelector('div[aria-labelledby="question-data"]')) {
return 'examly';
}
// Check for HackerRank
if (document.querySelector('.QuestionDetails_container__AIu0X') ||
document.querySelector('.monaco-editor') ||
document.querySelector('.grouped-mcq__question')) {
return 'hackerrank';
}
return null;
}
function extractExamlyQuestion() {
const questionElement = document.querySelector('div[aria-labelledby="question-data"]');
if (!questionElement) return null;
const questionText = questionElement.innerText.trim();
// Check if it's a coding question
const codingQuestionElement = document.querySelector('div[aria-labelledby="input-format"]');
if (codingQuestionElement) {
// Coding question
const programmingLanguageElement = document.querySelector('span.inner-text');
const programmingLanguage = programmingLanguageElement ? programmingLanguageElement.innerText.trim() : 'Programming language not found.';
const inputFormatElement = document.querySelector('div[aria-labelledby="input-format"]');
const inputFormatText = inputFormatElement ? inputFormatElement.innerText.trim() : '';
const outputFormatElement = document.querySelector('div[aria-labelledby="output-format"]');
const outputFormatText = outputFormatElement ? outputFormatElement.innerText.trim() : '';
const sampleTestCaseElements = document.querySelectorAll('div[aria-labelledby="each-tc-card"]');
let testCasesText = '';
sampleTestCaseElements.forEach((testCase, index) => {
const inputElement = testCase.querySelector('div[aria-labelledby="each-tc-input-container"] pre');
const outputElement = testCase.querySelector('div[aria-labelledby="each-tc-output-container"] pre');
const inputText = inputElement ? inputElement.innerText.trim() : 'Input not found';
const outputText = outputElement ? outputElement.innerText.trim() : 'Output not found';
testCasesText += `Sample Test Case ${index + 1}:\nInput:\n${inputText}\nOutput:\n${outputText}\n\n`;
});
return {
type: 'coding',
language: programmingLanguage,
question: questionText,
inputFormat: inputFormatText,
outputFormat: outputFormatText,
testCases: testCasesText
};
} else {
// MCQ question
const codeLines = [];
const codeElements = document.querySelectorAll('.ace_layer.ace_text-layer .ace_line');
codeElements.forEach(line => {
codeLines.push(line.innerText.trim());
});
const codeText = codeLines.length > 0 ? codeLines.join('\n') : null;
const optionsElements = document.querySelectorAll('div[aria-labelledby="each-option"]');
const optionsText = [];
optionsElements.forEach((option, index) => {
optionsText.push(`Option ${index + 1}: ${option.innerText.trim()}`);
});
return {
type: 'mcq',
question: questionText,
code: codeText,
options: optionsText.join('\n')
};
}
}
function extractHackerRankQuestion() {
const getCleanText = el => el?.innerText?.trim() || "";
// Check if it's a coding question (has Monaco editor)
const monacoEditor = document.querySelector('.monaco-editor, .hr-monaco-editor');
if (monacoEditor) {
// Coding question
let language = "Unknown";
let title = "No Title Found";
let instruction = "No Instructions Found";
let details = "";
const newLanguageSelector = document.querySelector('.select-language .css-3d4y2u-singleValue, .select-language .css-x7738g');
if (newLanguageSelector) {
language = getCleanText(newLanguageSelector);
} else {
language = getCleanText(document.querySelector('.select-language .css-x7738g')) || "Unknown";
}
let container = document.querySelector('.QuestionDetails_container__AIu0X');
if (container) {
const titleElement = container.querySelector('.qaas-block-question-title, h2');
if (titleElement) {
const titleText = titleElement.textContent || titleElement.innerText;
title = titleText.replace(/Bookmark question \d+/g, '').trim();
}
const instructionElement = container.querySelector('.qaas-block-question-instruction, .RichTextPreview_richText__1vKu5');
if (instructionElement) {
instruction = getCleanText(instructionElement);
}
const detailsElements = container.querySelectorAll('details');
if (detailsElements.length > 0) {
details = Array.from(detailsElements).map(detail => {
const summary = getCleanText(detail.querySelector('summary'));
const content = getCleanText(detail.querySelector('.collapsable-details'));
return `\n${summary}\n${'-'.repeat(summary.length)}\n${content}`;
}).join('\n');
}
} else {
container = document.querySelector('#main-splitpane-left');
if (container) {
title = getCleanText(container.querySelector('.question-view__title')) || "No Title Found";
instruction = getCleanText(container.querySelector('.question-view__instruction')) || "No Instructions Found";
details = Array.from(container.querySelectorAll('details') || []).map(detail => {
const summary = getCleanText(detail.querySelector('summary'));
const content = getCleanText(detail.querySelector('.collapsable-details'));
return `\n${summary}\n${'-'.repeat(summary.length)}\n${content}`;
}).join('\n');
}
}
return {
type: 'coding',
language: language,
title: title,
instruction: instruction,
details: details
};
} else {
// MCQ question
const newLayoutQuestions = document.querySelectorAll('.QuestionDetails_container__AIu0X');
if (newLayoutQuestions.length > 0) {
// New layout
const container = newLayoutQuestions[0]; // Get first question
let title = '';
let instruction = '';
let options = [];
const titleElement = container.querySelector('.qaas-block-question-title, h2');
if (titleElement) {
const titleText = titleElement.textContent || titleElement.innerText;
title = titleText.replace(/Bookmark question \d+/g, '').trim();
}
const instructionElement = container.querySelector('.qaas-block-question-instruction, .RichTextPreview_richText__1vKu5');
if (instructionElement) {
instruction = getCleanText(instructionElement);
}
let optionsContainer = container.nextElementSibling;
let attempts = 0;
while (optionsContainer && attempts < 5) {
const hasOptions = optionsContainer.querySelector('[role="checkbox"], [role="radio"]');
if (hasOptions) break;
optionsContainer = optionsContainer.nextElementSibling;
attempts++;
}
if (optionsContainer) {
let optionElements = optionsContainer.querySelectorAll('[role="radio"]');
if (optionElements.length === 0) {
optionElements = optionsContainer.querySelectorAll('[role="checkbox"]');
}
optionElements.forEach((option, index) => {
const labelId = option.getAttribute('aria-labelledby');
const labelElement = labelId ? document.getElementById(labelId) :
option.closest('.Control_optionList__vIubt, li')?.querySelector('label');
if (labelElement) {
options.push(`Option ${index + 1}: ${labelElement.textContent.trim()}`);
}
});
}
return {
type: 'mcq',
title: title,
instruction: instruction,
options: options.join('\n')
};
} else {
// Old layout
const oldLayoutQuestion = document.querySelector('.grouped-mcq__question');
if (oldLayoutQuestion) {
let title = '';
let instruction = '';
let options = [];
const titleElement = oldLayoutQuestion.querySelector('.question-view__title');
if (titleElement) {
title = titleElement.textContent.trim();
}
const instructionElement = oldLayoutQuestion.querySelector('.question-view__instruction');
if (instructionElement) {
instruction = instructionElement.textContent.trim();
}
const optionElements = oldLayoutQuestion.querySelectorAll('.ui-radio');
optionElements.forEach((option, index) => {
const labelElement = option.querySelector('.label');
if (labelElement) {
options.push(`Option ${index + 1}: ${labelElement.textContent.trim()}`);
}
});
return {
type: 'mcq',
title: title,
instruction: instruction,
options: options.join('\n')
};
}
}
}
return null;
}
function extractCurrentQuestion() {
const platform = detectPlatform();
if (platform === 'examly') {
return extractExamlyQuestion();
} else if (platform === 'hackerrank') {
return extractHackerRankQuestion();
}
return null;
}
function formatQuestionForChat(questionData) {
if (!questionData) return null;
let formattedQuestion = '';
if (questionData.type === 'coding') {
if (questionData.language) {
// Examly or HackerRank coding
formattedQuestion += `[Coding Question - ${questionData.language}]\n\n`;
if (questionData.title) {
formattedQuestion += `Title: ${questionData.title}\n\n`;
}
if (questionData.question) {
formattedQuestion += `Question:\n${questionData.question}\n\n`;
}
if (questionData.instruction) {
formattedQuestion += `Instruction:\n${questionData.instruction}\n\n`;
}
if (questionData.inputFormat) {
formattedQuestion += `Input Format:\n${questionData.inputFormat}\n\n`;
}
if (questionData.outputFormat) {
formattedQuestion += `Output Format:\n${questionData.outputFormat}\n\n`;
}
if (questionData.testCases) {
formattedQuestion += `Test Cases:\n${questionData.testCases}\n\n`;
}
if (questionData.details) {
formattedQuestion += `Additional Details:${questionData.details}\n\n`;
}
}
} else if (questionData.type === 'mcq') {
formattedQuestion += `[MCQ Question]\n\n`;
if (questionData.title) {
formattedQuestion += `Title: ${questionData.title}\n\n`;
}
if (questionData.question) {
formattedQuestion += `Question:\n${questionData.question}\n\n`;
}
if (questionData.instruction) {
formattedQuestion += `${questionData.instruction}\n\n`;
}
if (questionData.code) {
formattedQuestion += `Code:\n${questionData.code}\n\n`;
}
if (questionData.options) {
formattedQuestion += `Options:\n${questionData.options}\n`;
}
}
return formattedQuestion.trim();
}
// Create the main chat overlay UI
function createChatOverlay() {
// Check if shadow host already exists
let shadowHost = document.getElementById("chat-overlay-shadow-host");
if (shadowHost) {
return shadowHost.shadowRoot.querySelector("#chat-overlay");
}
// Create shadow host element
shadowHost = document.createElement("div");
shadowHost.id = "chat-overlay-shadow-host";
shadowHost.style.cssText = `
position: fixed;
bottom: 0;
right: 0;
z-index: 2147483647;
pointer-events: none;
`;
// Attach shadow root
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
const overlay = document.createElement("div");
overlay.id = "chat-overlay";
overlay.style.cssText = `
display: ${isOverlayVisible ? "flex" : "none"};
position: fixed;
bottom: 20px;
right: 20px;
width: 380px;
height: 500px;
background-color: #fff;
border: none;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
z-index: 2147483647;
flex-direction: column;
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
transition: opacity 0.3s ease;
pointer-events: auto;
`;
// Create header
const header = document.createElement("div");
header.style.cssText = `
padding: 16px 20px !important;
font-weight: 500 !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
background-color: #fff !important;
color: #333 !important;
cursor: move !important;
`;
header.innerHTML = `
Chat
${window.isMac ? 'Option+C' : 'Alt+C'} to toggle
Clear×
`;
// Create opacity slider container (Stealth mode control)
const sliderContainer = document.createElement("div");
sliderContainer.style.cssText = `
width: 100%;
height: 2px;
background-color: rgba(60, 84, 114, 0.1);
position: relative;
z-index: 10;
display: flex;
align-items: center;
`;
const opacitySlider = document.createElement("input");
opacitySlider.type = "range";
opacitySlider.min = "15";
opacitySlider.max = "100";
opacitySlider.value = "100";
opacitySlider.id = "opacity-slider";
opacitySlider.title = "Adjust opacity / Enable Stealth Mode";
sliderContainer.appendChild(opacitySlider);
// Create messages container
const messagesContainer = document.createElement("div");
messagesContainer.id = "chat-messages";
messagesContainer.style.cssText = `
padding: 20px;
flex: 1;
overflow-y: auto;
background-color: #fafafa;
color: #333;
scroll-behavior: smooth;
white-space: pre-wrap;
display: flex;
flex-direction: column;
gap: 12px;
`;
// Create input area
const inputArea = document.createElement("div");
inputArea.style.cssText = `
padding: 12px 16px 16px 16px;
background-color: #fff;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 10;
`;
// Create button container (which now acts as the pill wrapper)
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
display: flex;
align-items: stretch; /* Stretch children to fill height */
background-color: #f4f6f8;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 24px;
padding: 0; /* Remove all padding from container */
transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
gap: 0;
overflow: hidden; /* Ensures inner elements don't break the pill curve */
min-height: 44px;
`;
// Hover effect for the pill container
buttonContainer.addEventListener('mouseenter', () => {
buttonContainer.style.border = '1px solid rgba(60, 84, 114, 0.3)';
buttonContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.05)';
});
buttonContainer.addEventListener('mouseleave', () => {
buttonContainer.style.border = '1px solid rgba(0, 0, 0, 0.08)';
buttonContainer.style.boxShadow = '0 2px 6px rgba(0, 0, 0, 0.02)';
});
// Create input field with plain text only
const inputField = document.createElement("div");
inputField.contentEditable = "plaintext-only"; // Force plain text only
inputField.placeholder = "Message...";
inputField.style.cssText = `
flex: 1;
padding: 12px 12px 12px 16px; /* Put padding on the input instead */
border: none;
outline: none;
background-color: transparent;
color: #222;
font-family: 'Poppins', sans-serif;
font-size: 14px;
line-height: 1.5;
font-weight: 400;
min-height: 45px; /* Minimum height for 1 line */
max-height: 66px; /* Max height for exactly 2 lines (14px font * 1.5 line height * 2 + 24px padding = 66px) */
overflow-y: auto;
overflow-x: hidden;
white-space: pre-wrap;
word-wrap: break-word; /* Ensure text breaks into new lines */
-webkit-user-modify: read-write-plaintext-only;
display: block; /* Removed flex to allow proper text wrapping */
`;
// Simple paste event to ensure consistency (optional fallback)
inputField.addEventListener('paste', async function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
try {
let clipText = '';
// First try native clipboard (prioritize external app copies)
try {
clipText = await navigator.clipboard.readText();
console.log('[ChatBot Paste] Using native clipboard, length:', clipText.length);
} catch (err) {
console.log('[ChatBot Paste] Native clipboard read failed:', err.message);
}
// If empty, fall back to neoPassClipboard
if (!clipText && window.neoPassClipboard) {
clipText = window.neoPassClipboard;
console.log('[ChatBot Paste] Using neoPassClipboard, length:', clipText.length);
}
// Also try clipboardData from the paste event
if (!clipText && e.clipboardData) {
clipText = e.clipboardData.getData('text/plain');
console.log('[ChatBot Paste] Using event clipboardData, length:', clipText.length);
}
if (clipText) {
console.log('[ChatBot Paste] Attempting to insert text...');
let inserted = false;
// Try method 1: Use selection API
try {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// Ensure the range is within our input field
if (this.contains(range.commonAncestorContainer)) {
range.deleteContents();
const textNode = document.createTextNode(clipText);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
inserted = true;
console.log('[ChatBot Paste] Inserted using selection API');
}
}
} catch (selErr) {
console.log('[ChatBot Paste] Selection API failed:', selErr.message);
}
// Fallback method 2: Direct textContent manipulation
if (!inserted) {
console.log('[ChatBot Paste] Using fallback: direct insertion');
const currentText = this.textContent || '';
this.textContent = currentText + clipText;
// Move cursor to end
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(this);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
inserted = true;
}
if (inserted) {
// Dispatch input event to trigger any listeners
this.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: clipText
}));
console.log('[ChatBot Paste] Paste successful');
}
}
// Clean any potential HTML that might slip through
setTimeout(() => {
if (this.children.length > 0) {
const text = this.textContent || this.innerText;
this.textContent = text;
}
}, 10);
} catch (err) {
console.error('[ChatBot Paste] Error:', err);
// Fallback: let browser handle it
setTimeout(() => {
if (this.children.length > 0) {
const text = this.textContent || this.innerText;
this.textContent = text;
}
}, 10);
}
}, true); // Use capture phase to intercept before document-level handlers
// Add Ctrl+V / Cmd+V handler for paste
inputField.addEventListener('keydown', async function(e) {
const ctrlKey = e.ctrlKey || e.metaKey; // Support both Ctrl (Windows/Linux) and Cmd (macOS)
// Handle Enter key for sending messages
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendButton.click();
return;
}
// Handle Ctrl+V / Cmd+V for paste
if (ctrlKey && (e.key === 'V' || e.key === 'v')) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
try {
let clipText = '';
// First try native clipboard (prioritize external app copies)
try {
clipText = await navigator.clipboard.readText();
console.log('[ChatBot Ctrl+V] Using native clipboard, length:', clipText.length);
} catch (err) {
console.log('[ChatBot Ctrl+V] Native clipboard read failed:', err.message);
}
// If empty, fall back to neoPassClipboard
if (!clipText && window.neoPassClipboard) {
clipText = window.neoPassClipboard;
console.log('[ChatBot Ctrl+V] Using neoPassClipboard, length:', clipText.length);
}
if (clipText) {
console.log('[ChatBot Ctrl+V] Attempting to insert text...');
let inserted = false;
// Try method 1: Use selection API
try {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// Ensure the range is within our input field
if (this.contains(range.commonAncestorContainer)) {
range.deleteContents();
const textNode = document.createTextNode(clipText);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
inserted = true;
console.log('[ChatBot Ctrl+V] Inserted using selection API');
}
}
} catch (selErr) {
console.log('[ChatBot Ctrl+V] Selection API failed:', selErr.message);
}
// Fallback method 2: Direct textContent manipulation
if (!inserted) {
console.log('[ChatBot Ctrl+V] Using fallback: direct insertion');
const currentText = this.textContent || '';
this.textContent = currentText + clipText;
// Move cursor to end
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(this);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
inserted = true;
}
if (inserted) {
// Dispatch input event to trigger any listeners
this.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: clipText
}));
console.log('[ChatBot Ctrl+V] Paste successful');
}
} else {
console.log('[ChatBot Ctrl+V] No clipboard content available');
}
} catch (err) {
console.error('[ChatBot Ctrl+V] Error:', err);
}
}
}, true); // Use capture phase to intercept before document-level handlers
// Create checkbox container for "Chat about question"
const checkboxContainer = document.createElement("div");
checkboxContainer.style.cssText = `
display: none;
align-items: center;
gap: 8px;
padding: 4px 0;
`;
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = "chat-about-question-checkbox";
checkbox.style.cssText = `
width: 16px;
height: 16px;
cursor: pointer;
accent-color: rgb(60, 84, 114);
`;
const checkboxLabel = document.createElement("label");
checkboxLabel.htmlFor = "chat-about-question-checkbox";
checkboxLabel.style.cssText = `
font-family: 'Poppins', sans-serif;
font-size: 13px;
color: #666;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 6px;
`;
const questionIcon = `
`;
checkboxLabel.innerHTML = questionIcon + 'Chat about question';
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(checkboxLabel);
// Store last question hash to detect question changes
let lastQuestionHash = null;
// Function to generate a simple hash from question data
function getQuestionHash(questionData) {
if (!questionData) return null;
// Create a unique string from the question data
let hashString = '';
if (questionData.type) hashString += questionData.type;
if (questionData.question) hashString += questionData.question;
if (questionData.title) hashString += questionData.title;
if (questionData.instruction) hashString += questionData.instruction;
// Simple hash function
let hash = 0;
for (let i = 0; i < hashString.length; i++) {
const char = hashString.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return hash;
}
// Function to check and update checkbox visibility based on platform detection
function updateCheckboxVisibility() {
const platform = detectPlatform();
if (platform) {
// Valid platform detected, show the checkbox
checkboxContainer.style.display = 'flex';
// If checkbox is enabled, check if question has changed
if (chatAboutQuestionEnabled && checkbox.checked) {
const currentQuestionData = extractCurrentQuestion();
const currentQuestionHash = getQuestionHash(currentQuestionData);
// If question hash changed, re-extract the question
if (currentQuestionHash !== lastQuestionHash && lastQuestionHash !== null) {
if (currentQuestionData) {
extractedQuestion = formatQuestionForChat(currentQuestionData);
lastQuestionHash = currentQuestionHash;
console.log('Question changed and re-extracted for chat');
// Clear chat history when question changes
clearChatHistoryAndUI('question-switch');
// Show notification that question was updated and chat cleared
addNotificationMessage('Question updated - Chat cleared');
} else {
// Question no longer available
checkbox.checked = false;
chatAboutQuestionEnabled = false;
extractedQuestion = null;
lastQuestionHash = null;
checkboxLabel.style.color = '#666';
checkboxLabel.style.fontWeight = '400';
addNotificationMessage('Question no longer detected');
}
}
}
} else {
// No valid platform, hide the checkbox and reset state
checkboxContainer.style.display = 'none';
checkbox.checked = false;
chatAboutQuestionEnabled = false;
extractedQuestion = null;
lastQuestionHash = null;
checkboxLabel.style.color = '#666';
checkboxLabel.style.fontWeight = '400';
}
}
// Initial check when overlay is created
updateCheckboxVisibility();
// Re-check periodically in case user navigates to a different page
setInterval(updateCheckboxVisibility, 2000);
// Handle checkbox change
checkbox.addEventListener('change', function() {
chatAboutQuestionEnabled = this.checked;
if (chatAboutQuestionEnabled) {
// Extract question when enabled
const questionData = extractCurrentQuestion();
if (questionData) {
extractedQuestion = formatQuestionForChat(questionData);
lastQuestionHash = getQuestionHash(questionData);
console.log('Question extracted for chat:', extractedQuestion);
// Update label to show question is attached
checkboxLabel.style.color = 'rgb(60, 84, 114)';
checkboxLabel.style.fontWeight = '500';
} else {
// No question found, disable checkbox
this.checked = false;
chatAboutQuestionEnabled = false;
extractedQuestion = null;
lastQuestionHash = null;
// Show notification
addNotificationMessage('No question detected on this page');
}
} else {
// Reset styles when disabled
checkboxLabel.style.color = '#666';
checkboxLabel.style.fontWeight = '400';
extractedQuestion = null;
lastQuestionHash = null;
}
});
// Create send button
const sendButton = document.createElement("button");
sendButton.innerHTML = "Send";
sendButton.style.cssText = `
padding: 0 20px 0 16px; /* Wider padding for text */
margin: 0;
background-color: rgb(60, 84, 114);
color: #fff;
border: none;
border-radius: 0; /* Let the container's overflow:hidden handle the curve */
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Poppins', sans-serif;
font-weight: 500;
font-size: 14px;
letter-spacing: 0.3px;
transition: all 0.2s ease;
flex-shrink: 0;
height: auto; /* Stretch to fill parent height */
box-shadow: -1px 0 3px rgba(0, 0, 0, 0.05); /* Very subtle separation */
`;
// Create resize handle
const resizeHandle = document.createElement("div");
resizeHandle.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 12px;
height: 12px;
background-color: rgb(60, 84, 114);
cursor: nw-resize;
border-radius: 12px 0 12px 0;
opacity: 0.8;
`;
// Add custom scrollbar styles and Prism theme overrides
const scrollbarStyles = document.createElement("style");
scrollbarStyles.innerHTML = `
${window._chatSyntaxHighlightCSS || ''}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
#chat-overlay ::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
transition: background-color 0.2s ease;
}
#chat-overlay ::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.3);
}
#chat-overlay ::-webkit-scrollbar-track {
background-color: transparent;
}
#chat-overlay [contenteditable]:empty:before {
content: attr(placeholder);
color: rgba(0, 0, 0, 0.4);
font-weight: 300;
}
/* Prism theme customizations for chat overlay */
#chat-overlay pre[class*="language-"] {
background: #f8f9fa !important;
border: 1px solid #e1e4e8 !important;
border-radius: 6px !important;
margin: 15px 0 !important;
padding: 12px !important;
overflow-x: auto !important;
}
#chat-overlay code[class*="language-"] {
background: transparent !important;
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', Menlo, monospace !important;
font-size: 13px !important;
line-height: 1.4 !important;
color: #24292e !important;
}
/* Token colors for better readability */
#chat-overlay .token.comment,
#chat-overlay .token.prolog,
#chat-overlay .token.doctype,
#chat-overlay .token.cdata {
color: #6a737d !important;
}
#chat-overlay .token.punctuation {
color: #24292e !important;
}
#chat-overlay .token.property,
#chat-overlay .token.tag,
#chat-overlay .token.boolean,
#chat-overlay .token.number,
#chat-overlay .token.constant,
#chat-overlay .token.symbol,
#chat-overlay .token.deleted {
color: #005cc5 !important;
}
#chat-overlay .token.selector,
#chat-overlay .token.attr-name,
#chat-overlay .token.string,
#chat-overlay .token.char,
#chat-overlay .token.builtin,
#chat-overlay .token.inserted {
color: #032f62 !important;
}
#chat-overlay .token.operator,
#chat-overlay .token.entity,
#chat-overlay .token.url,
#chat-overlay .language-css .token.string,
#chat-overlay .style .token.string {
color: #e36209 !important;
}
#chat-overlay .token.atrule,
#chat-overlay .token.attr-value,
#chat-overlay .token.keyword {
color: #d73a49 !important;
}
#chat-overlay .token.function,
#chat-overlay .token.class-name {
color: #6f42c1 !important;
}
#chat-overlay .token.regex,
#chat-overlay .token.important,
#chat-overlay .token.variable {
color: #e36209 !important;
}
/* Remove any pseudo-elements that might cause overlay effects */
#chat-overlay pre[class*="language-"]:before,
#chat-overlay pre[class*="language-"]:after,
#chat-overlay code[class*="language-"]:before,
#chat-overlay code[class*="language-"]:after {
display: none !important;
}
/* Ensure no box-shadow or other effects */
#chat-overlay pre[class*="language-"] {
box-shadow: none !important;
text-shadow: none !important;
}
#chat-overlay code[class*="language-"] {
box-shadow: none !important;
text-shadow: none !important;
}
`;
// Assemble the components
buttonContainer.appendChild(inputField);
buttonContainer.appendChild(sendButton);
inputArea.appendChild(checkboxContainer);
inputArea.appendChild(buttonContainer);
// Create comprehensive CSS reset and styles for shadow DOM
const shadowStyles = document.createElement('style');
shadowStyles.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap');
/* CSS Reset for Shadow DOM */
* {
box-sizing: border-box;
}
#opacity-slider {
-webkit-appearance: none;
width: 100%;
height: 2px;
background: transparent;
outline: none;
margin: 0;
padding: 0;
}
#opacity-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 8px;
border-radius: 4px;
background: rgb(60, 84, 114);
cursor: pointer;
transition: transform 0.2s, background 0.2s;
}
#opacity-slider::-webkit-slider-thumb:hover {
transform: scale(1.2);
background: rgb(80, 104, 134);
}
#opacity-slider::-moz-range-thumb {
width: 16px;
height: 8px;
border-radius: 4px;
background: rgb(60, 84, 114);
cursor: pointer;
border: none;
transition: transform 0.2s, background 0.2s;
}
#opacity-slider::-moz-range-thumb:hover {
transform: scale(1.2);
background: rgb(80, 104, 134);
}
/* Re-apply base styles needed */
div, span, p {
display: block;
margin: 0;
padding: 0;
}
button {
cursor: pointer;
border: none;
background: none;
color: inherit;
font-family: 'Poppins', sans-serif;
}
input[type="checkbox"] {
cursor: pointer;
width: 16px;
height: 16px;
}
label {
cursor: pointer;
font-family: 'Poppins', sans-serif;
}
pre {
display: block;
margin: 0;
padding: 0;
font-family: monospace;
white-space: pre-wrap;
}
code {
font-family: monospace;
}
strong, b {
font-weight: bold;
}
em, i {
font-style: italic;
}
a {
color: #0066cc;
text-decoration: underline;
cursor: pointer;
}
ul, ol {
display: block;
margin: 10px 0;
padding-left: 20px;
}
li {
display: list-item;
margin: 5px 0;
}
p {
margin: 10px 0;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
margin: 15px 0 10px 0;
line-height: 1.3;
}
h1 { font-size: 2em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.3em; }
h4 { font-size: 1.1em; }
h5 { font-size: 1em; }
h6 { font-size: 0.9em; }
${scrollbarStyles.innerHTML}
`;
// Assemble the components in shadow DOM
shadowRoot.appendChild(shadowStyles);
overlay.appendChild(header);
overlay.appendChild(sliderContainer);
overlay.appendChild(messagesContainer);
overlay.appendChild(inputArea);
overlay.appendChild(resizeHandle);
shadowRoot.appendChild(overlay);
document.body.appendChild(shadowHost);
// Store shadow root reference for later access
shadowHost._shadowRoot = shadowRoot;
// Add placeholder behavior after element is in DOM
inputField.addEventListener('focus', function() {
if (this.textContent.trim() === '') {
this.setAttribute('data-placeholder', 'Type a message...');
}
});
inputField.addEventListener('blur', function() {
if (this.textContent.trim() === '') {
this.removeAttribute('data-placeholder');
}
});
// Add hover effect to send button
sendButton.addEventListener('mouseenter', () => {
sendButton.style.transform = 'translateY(-1px)';
sendButton.style.boxShadow = '0 4px 8px rgba(60, 84, 114, 0.3)';
});
sendButton.addEventListener('mouseleave', () => {
sendButton.style.transform = 'translateY(0)';
sendButton.style.boxShadow = '0 2px 4px rgba(60, 84, 114, 0.2)';
});
// Add event listeners for dragging
header.addEventListener("mousedown", (e) => {
isDragging = true;
dragOffsetX = e.clientX - overlay.getBoundingClientRect().left;
dragOffsetY = e.clientY - overlay.getBoundingClientRect().top;
});
// Add event listeners for stealth-mode
// Get the initial state from storage
chrome.storage.local.get(['stealth', 'stealthOpacity'], function(result) {
// Initialize stealth mode based on storage
let stealthModeEnabled = result.stealth === true;
let currentOpacity = result.stealthOpacity || (stealthModeEnabled ? 15 : 100);
const slider = shadowRoot.querySelector("#opacity-slider");
if (slider) {
slider.value = stealthModeEnabled ? currentOpacity : 100;
if (stealthModeEnabled) {
overlay.style.opacity = currentOpacity / 100;
} else {
overlay.style.opacity = "1";
}
slider.addEventListener("input", (e) => {
const val = parseInt(e.target.value);
overlay.style.opacity = val / 100;
});
slider.addEventListener("change", (e) => {
const val = parseInt(e.target.value);
const isStealth = val < 100;
// Only send notification if stealth mode STATE changed
if (isStealth !== stealthModeEnabled) {
stealthModeEnabled = isStealth;
const chatButton = getChatButton();
if (chatButton) {
chatButton.style.opacity = isStealth ? "0" : "1";
}
if (isStealth) {
chrome.runtime.sendMessage({
action: 'showStealthToast',
message: `Hover over the area where the chat icon is located \nor press ${window.isMac ? 'Option+C' : 'Alt+C'} to access [Chatbot opacity reduced]`,
stealthEnabled: true
});
} else {
chrome.runtime.sendMessage({
action: 'showStealthToast',
message: 'Chat icon is now visible',
stealthEnabled: false
});
}
}
chrome.storage.local.set({
stealth: isStealth,
stealthOpacity: val
});
});
}
// Listen for storage changes to update stealth mode state across all tabs
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'local' && slider) {
if (changes.stealthOpacity) {
currentOpacity = changes.stealthOpacity.newValue;
if (stealthModeEnabled) {
slider.value = currentOpacity;
if (overlay) overlay.style.opacity = currentOpacity / 100;
}
}
if (changes.stealth) {
const newStealthMode = changes.stealth.newValue === true;
stealthModeEnabled = newStealthMode;
if (newStealthMode) {
slider.value = currentOpacity;
if (overlay) overlay.style.opacity = currentOpacity / 100;
} else {
slider.value = 100;
if (overlay) overlay.style.opacity = "1";
}
// Update chat button visibility
const chatButton = getChatButton();
if (chatButton) {
chatButton.style.opacity = newStealthMode ? "0" : "1";
chatButton.style.pointerEvents = "auto";
}
}
}
});
});
// Add event listeners for resizing
// Add minimum size constants at the top with the other state variables
const MIN_WIDTH = 250; // Minimum width in pixels
const MIN_HEIGHT = 200; // Minimum height in pixels
const MAX_WIDTH = window.innerWidth - 40; // Maximum width (leaving 20px padding on each side)
const MAX_HEIGHT = window.innerHeight - 40; // Maximum height (leaving 20px padding on each side)
// Replace the resize event listener section with this updated version
resizeHandle.addEventListener("mousedown", (e) => {
isResizing = true;
resizeStartX = e.clientX;
resizeStartY = e.clientY;
initialWidth = overlay.offsetWidth;
initialHeight = overlay.offsetHeight;
e.stopPropagation(); // Prevent dragging when resizing
});
resizeHandle.addEventListener("mouseenter", () => {
resizeHandle.style.opacity = "1";
});
resizeHandle.addEventListener("mouseleave", () => {
resizeHandle.style.opacity = "0.8";
});
// Update the mousemove event listener to include size constraints
// This should be outside the createChatOverlay function as it's document level
// Add window resize handler to keep overlay within bounds
window.addEventListener('resize', () => {
const overlay = getShadowElement('chat-overlay');
if (overlay) {
const rect = overlay.getBoundingClientRect();
// Update maximum constraints
const newMaxWidth = window.innerWidth - 40;
const newMaxHeight = window.innerHeight - 40;
// Adjust size if necessary
if (rect.width > newMaxWidth) {
overlay.style.width = newMaxWidth + 'px';
}
if (rect.height > newMaxHeight) {
overlay.style.height = newMaxHeight + 'px';
}
// Keep overlay within viewport
if (rect.right > window.innerWidth) {
overlay.style.left = (window.innerWidth - rect.width) + "px";
}
if (rect.bottom > window.innerHeight) {
overlay.style.top = (window.innerHeight - rect.height) + "px";
}
}
});
// Add button event listeners
const closeButton = header.querySelector("#close-chat");
if (closeButton) {
closeButton.addEventListener("click", () => {
isOverlayVisible = false;
overlay.style.display = "none";
});
}
const clearChatButton = header.querySelector("#clear-chat");
if (clearChatButton) {
clearChatButton.addEventListener("click", () => {
clearChatHistoryAndUI('manual');
});
}
// Handle message sending
sendButton.addEventListener("click", async () => {
const message = inputField.innerText.trim();
if (message) {
try {
// Clear any error state before sending new message
clearErrorState();
// Prepare the final message to send
let finalMessage = message;
// If "Chat about question" is enabled, prepend the question
if (chatAboutQuestionEnabled && extractedQuestion) {
finalMessage = `Context: Below is the question I'm working on:\n\n${extractedQuestion}\n\n---\n\nMy Question: ${message}`;
console.log('Sending message with question context');
}
chatHistory.push({
role: "user",
content: message
});
addMessageToChat(message, "user");
inputField.innerText = "";
// Add enhanced loading indicator
const loadingDiv = addLoadingIndicator();
messagesContainer.appendChild(loadingDiv);
// Send message and wait for response with timeout
const response = await new Promise((resolve, reject) => {
let timeoutId;
let resolved = false;
// Set up timeout (30 seconds)
timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
reject(new Error('Request timed out. Please try again.'));
}
}, 30000);
// Listen for response
const messageListener = (message) => {
if (message.action === "updateChatHistory" && !resolved) {
resolved = true;
clearTimeout(timeoutId);
chrome.runtime.onMessage.removeListener(messageListener);
resolve(message);
}
};
chrome.runtime.onMessage.addListener(messageListener);
// Send the message (with question context if enabled)
// Create valid conversation context (filters errors and ensures proper role flow)
const validContext = createValidContext(chatHistory);
chrome.runtime.sendMessage({
action: "processChatMessage",
message: finalMessage, // Send the final message with or without question context
context: validContext
}).catch((error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeoutId);
chrome.runtime.onMessage.removeListener(messageListener);
reject(error);
}
});
});
// Remove loading indicator
const loadingMessage = getShadowElement("loading-message");
if (loadingMessage) {
loadingMessage.remove();
}
// The response will be handled by the runtime message listener
// No need to add the message here as it will be added via "updateChatHistory"
}
catch (error) {
console.error("Error sending message:", error);
// Remove loading indicator if it exists
const loadingMessage = getShadowElement("loading-message");
if (loadingMessage) {
loadingMessage.remove();
}
// Handle different types of errors with appropriate messages
let errorMessage = "I encountered an error processing your message. Please try again.";
let isRateLimitError = false;
if (error.message) {
if (error.message.includes('timeout') || error.message.includes('timed out')) {
errorMessage = "The request timed out. The service might be experiencing high load. Please try again in a moment.";
} else if (error.message.includes('rate limit') || error.message.includes('Daily request limit')) {
errorMessage = "You've reached your daily chat limit. Please try again tomorrow.";
isRateLimitError = true;
} else if (error.message.includes('Network') || error.message.includes('connection')) {
errorMessage = "Unable to connect to the chat service. Please check your internet connection and try again.";
} else if (error.message.includes('login') || error.message.includes('authentication')) {
errorMessage = "Please log in to use the chat feature. Click the extension icon to log in.";
} else {
// Use the error message if it's user-friendly
errorMessage = error.message;
}
}
// Add error message to chat with special styling
addErrorMessageToChat(errorMessage, isRateLimitError);
}
}
});
return overlay;
}
// Add notification message function
function addNotificationMessage(message) {
const messagesContainer = getShadowElement("chat-messages");
if (!messagesContainer) return;
const messageDiv = document.createElement("div");
messageDiv.textContent = message;
messageDiv.style.cssText = `
margin: 12px auto;
padding: 6px 12px;
background-color: rgba(60, 84, 114, 0.08);
border-radius: 12px;
color: rgb(60, 84, 114);
font-size: 11px;
text-align: center;
font-family: 'Poppins', sans-serif;
font-weight: 500;
letter-spacing: 0.2px;
width: fit-content;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Create the chat button
function createChatButton() {
// Check if shadow host for button already exists
let buttonShadowHost = document.getElementById("chat-button-shadow-host");
if (buttonShadowHost) {
return buttonShadowHost.shadowRoot.querySelector("#chat-button");
}
// Create shadow host element for button
buttonShadowHost = document.createElement("div");
buttonShadowHost.id = "chat-button-shadow-host";
buttonShadowHost.style.cssText = `
position: fixed;
bottom: 0;
right: 0;
z-index: 2147483647;
pointer-events: none;
`;
// Attach shadow root
const buttonShadowRoot = buttonShadowHost.attachShadow({ mode: 'open' });
// Create comprehensive CSS reset for button shadow DOM
const buttonStyles = document.createElement('style');
buttonStyles.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap');
/* CSS Reset for Button Shadow DOM */
* {
box-sizing: border-box;
}
button {
display: block;
cursor: pointer;
border: none;
padding: 0;
margin: 0;
background: none;
outline: none;
font-family: 'Poppins', sans-serif;
position: relative;
}
.chat-icon-span {
display: block;
position: absolute;
top: 14px;
right: 10px;
left: 9px;
bottom: 10px;
width: 35px;
height: 30px;
background-image: ${CHAT_ICON_SVG_URL};
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
pointer-events: none;
user-select: none;
z-index: 2;
}
`;
const button = document.createElement("button");
button.id = "chat-button";
button.style.cssText = `
display: block;
position: fixed;
bottom: 20px;
right: 20px;
width: 54px;
height: 54px;
background-color: rgb(60, 84, 114);
border: none;
border-radius: 100%;
color: #fff;
cursor: pointer;
z-index: 2147483647;
box-shadow: rgba(0, 0, 0, 0.05) 0px 4px 10px 0px;
transition: background-color 0.1s linear, outline 0.15s ease-in-out, transform 0.15s ease-in-out;
pointer-events: auto;
padding: 0;
margin: 0;
outline: solid 0px rgba(0, 0, 0, 0);
user-select: none;
`;
// Chat bubble icon as background-image on child span (matching Crisp style)
const iconSpan = document.createElement("span");
iconSpan.className = "chat-icon-span";
button.appendChild(iconSpan);
// Assemble button in shadow DOM
buttonShadowRoot.appendChild(buttonStyles);
buttonShadowRoot.appendChild(button);
document.body.appendChild(buttonShadowHost);
// Add hover effects for stealth mode
button.addEventListener('mouseenter', () => {
chrome.storage.local.get(['stealth'], function(result) {
const stealthModeEnabled = result.stealth === true;
if (stealthModeEnabled) {
button.style.opacity = "0.3"; // Show with reduced opacity on hover in stealth mode
}
});
});
button.addEventListener('mouseleave', () => {
chrome.storage.local.get(['stealth'], function(result) {
const stealthModeEnabled = result.stealth === true;
if (stealthModeEnabled) {
button.style.opacity = "0"; // Hide again when not hovering in stealth mode
}
});
});
let dragStartX, dragStartY, initialX, initialY;
let isDraggingButton = false;
let hasMoved = false;
// Handle button dragging with improved click detection
button.addEventListener("mousedown", (e) => {
isDraggingButton = true;
hasMoved = false;
dragStartX = e.clientX;
dragStartY = e.clientY;
initialX = button.getBoundingClientRect().left;
initialY = button.getBoundingClientRect().top;
});
document.addEventListener("mousemove", (e) => {
if (isDraggingButton) {
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
// Check if the button has moved more than 5 pixels in any direction
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
hasMoved = true;
}
const newX = initialX + deltaX;
const newY = initialY + deltaY;
// Keep button within viewport bounds
const maxX = window.innerWidth - button.offsetWidth;
const maxY = window.innerHeight - button.offsetHeight;
button.style.left = Math.min(Math.max(0, newX), maxX) + "px";
button.style.top = Math.min(Math.max(0, newY), maxY) + "px";
button.style.bottom = "auto";
button.style.right = "auto";
}
});
document.addEventListener("mouseup", () => {
if (isDraggingButton) {
isDraggingButton = false;
// Only trigger click if the button hasn't moved
if (!hasMoved) {
toggleChatOverlay();
}
}
});
// Remove double click handler and use single click with movement detection
button.addEventListener("click", (e) => {
// Click handling is now managed in the mouseup event
e.preventDefault();
});
return button;
}
// Helper function to detect programming language from code content
function detectLanguage(code) {
const codeText = code.toLowerCase().trim();
// TypeScript detection (check before JavaScript)
if (codeText.includes('interface ') || codeText.includes('type ') || codeText.includes(': string') ||
codeText.includes(': number') || codeText.includes(': boolean') || codeText.includes('export interface') ||
codeText.includes('import type') || codeText.includes('as const') || codeText.includes('enum ')) {
return 'typescript';
}
// JSX/TSX detection
if (codeText.includes('<') && codeText.includes('>') &&
(codeText.includes('return (') || codeText.includes('jsx') || codeText.includes('tsx') ||
codeText.includes('component') || codeText.includes('props'))) {
return codeText.includes(': ') ? 'tsx' : 'jsx';
}
// JavaScript detection
if (codeText.includes('function') || codeText.includes('const ') || codeText.includes('let ') ||
codeText.includes('var ') || codeText.includes('=>') || codeText.includes('console.log') ||
codeText.includes('document.') || codeText.includes('window.') || codeText.includes('require(') ||
codeText.includes('import ') || codeText.includes('export ')) {
return 'javascript';
}
// Python detection
if (codeText.includes('def ') || codeText.includes('import ') || codeText.includes('from ') ||
codeText.includes('print(') || codeText.includes('if __name__') || codeText.includes('self.') ||
codeText.includes('class ') || codeText.includes('elif ') || codeText.includes('range(') ||
codeText.includes('lambda ') || codeText.includes('yield ')) {
return 'python';
}
// Java detection
if (codeText.includes('public class') || codeText.includes('private ') || codeText.includes('public ') ||
codeText.includes('import java') || codeText.includes('system.out.println') || codeText.includes('string ') ||
codeText.includes('void main') || codeText.includes('extends ') || codeText.includes('implements ') ||
codeText.includes('@override') || codeText.includes('new ')) {
return 'java';
}
// C# detection
if (codeText.includes('using system') || codeText.includes('namespace ') || codeText.includes('public static void main') ||
codeText.includes('console.writeline') || codeText.includes('[attribute]') || codeText.includes('var ')) {
return 'csharp';
}
// C++ detection (check before C)
if (codeText.includes('std::') || codeText.includes('cout <<') || codeText.includes('cin >>') ||
codeText.includes('#include ') || codeText.includes('using namespace std') ||
codeText.includes('class ') || codeText.includes('template<')) {
return 'cpp';
}
// C detection
if (codeText.includes('#include') || codeText.includes('printf(') || codeText.includes('scanf(') ||
codeText.includes('int main') || codeText.includes('malloc(') || codeText.includes('free(') ||
codeText.includes('sizeof(')) {
return 'c';
}
// PHP detection
if (codeText.includes('')) {
return 'php';
}
// Ruby detection
if (codeText.includes('def ') || codeText.includes('end') || codeText.includes('puts ') ||
codeText.includes('require ') || codeText.includes('class ') || codeText.includes('@')) {
return 'ruby';
}
// Go detection
if (codeText.includes('package ') || codeText.includes('func ') || codeText.includes('import (') ||
codeText.includes('fmt.println') || codeText.includes('go ') || codeText.includes('defer ')) {
return 'go';
}
// Rust detection
if (codeText.includes('fn ') || codeText.includes('let mut') || codeText.includes('println!') ||
codeText.includes('use ') || codeText.includes('struct ') || codeText.includes('impl ')) {
return 'rust';
}
// Swift detection
if (codeText.includes('import swift') || codeText.includes('var ') || codeText.includes('let ') ||
codeText.includes('func ') || codeText.includes('class ') || codeText.includes('print(')) {
return 'swift';
}
// Kotlin detection
if (codeText.includes('fun ') || codeText.includes('val ') || codeText.includes('var ') ||
codeText.includes('class ') || codeText.includes('println(') || codeText.includes('import kotlin')) {
return 'kotlin';
}
// HTML detection
if (codeText.includes('') ||
(codeText.includes('<') && codeText.includes('>') && !codeText.includes('function'))) {
return 'xml';
}
// Bash/Shell detection
if (codeText.includes('#!/bin/bash') || codeText.includes('#!/bin/sh') ||
codeText.includes('echo ') || codeText.includes('grep ') || codeText.includes('awk ') ||
codeText.includes('sed ') || codeText.includes('chmod ') || codeText.includes('sudo ') ||
codeText.includes('ls ') || codeText.includes('cd ') || codeText.includes('mkdir ')) {
return 'bash';
}
// Default fallback
return 'javascript';
}
// Render content (for initial or streaming updates)
function renderChatContent(messageContainer, content) {
try {
// Convert markdown to HTML using showdown library
if (typeof showdown !== 'undefined') {
// Initialize markdown converter if not already done
if (!markdownConverter) {
markdownConverter = new showdown.Converter();
}
const htmlContent = markdownConverter.makeHtml(content);
// Clear and set new content
messageContainer.innerHTML = "";
const contentContainer = document.createElement("div");
contentContainer.innerHTML = htmlContent;
// Style code blocks and add copy functionality
contentContainer.querySelectorAll("pre code").forEach(codeBlock => {
// Detect language from class name first (from markdown ```language)
let language = '';
const classNames = codeBlock.className.split(' ');
for (const className of classNames) {
if (className.startsWith('language-')) {
language = className.replace('language-', '');
break;
}
}
// If no language specified in markdown, use auto-detection
if (!language || language === '') {
language = detectLanguage(codeBlock.textContent);
}
// Set the language class for Prism (ensure it's set even if detected)
codeBlock.className = `language-${language}`;
// Apply SimplePrism highlighting if available
if (typeof SimplePrism !== 'undefined') {
try {
SimplePrism.highlightElement(codeBlock);
} catch (error) {
console.warn('Failed to highlight code block:', error);
// Continue without highlighting
}
}
// Style the parent
element to ensure clean background
const preElement = codeBlock.parentNode;
if (preElement && preElement.tagName === 'PRE') {
preElement.style.cssText = `
background: #f8f9fa !important;
border: 1px solid #e1e4e8 !important;
border-radius: 6px !important;
margin: 15px 0 !important;
padding: 0 !important;
overflow: visible !important;
position: relative !important;
`;
}
// Style the code block (let Prism handle syntax colors)
codeBlock.style.cssText = `
background: transparent !important;
border: none !important;
border-radius: 0 !important;
padding: 12px !important;
display: block !important;
margin: 0 !important;
overflow-x: auto !important;
white-space: pre !important;
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', Menlo, monospace !important;
font-size: 13px !important;
line-height: 1.4 !important;
`;
// Create a wrapper for the code block to handle hover events
const codeWrapper = document.createElement("div");
codeWrapper.style.cssText = `
position: relative;
background: transparent;
border: none;
margin: 0;
padding: 0;
`;
// Move the code block into the wrapper
codeBlock.parentNode.insertBefore(codeWrapper, codeBlock);
codeWrapper.appendChild(codeBlock);
// Create copy button with new styling
const copyButton = document.createElement("button");
copyButton.innerText = "Copy";
copyButton.style.cssText = `
position: absolute;
right: 8px;
top: 8px;
background-color: rgb(60, 84, 114);
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
padding: 6px 12px;
font-size: 12px;
font-family: 'Poppins', sans-serif;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 10;
`;
// Add hover effects
codeWrapper.addEventListener('mouseenter', () => {
copyButton.style.opacity = "1";
});
codeWrapper.addEventListener('mouseleave', () => {
copyButton.style.opacity = "0";
});
// Add copy functionality
copyButton.addEventListener("click", () => {
navigator.clipboard.writeText(codeBlock.innerText)
.then(() => {
copyButton.innerText = "Copied";
setTimeout(() => {
copyButton.innerText = "Copy";
}, 5000);
})
.catch(error => {
console.error("Failed to copy: ", error);
});
});
// Add the copy button to the wrapper
codeWrapper.appendChild(copyButton);
});
// Add the content to the message container
messageContainer.appendChild(contentContainer);
} else {
// Fallback for when showdown is not available
messageContainer.textContent = content;
}
} catch (error) {
console.error('Error rendering chat content:', error);
// Fallback to plain text
messageContainer.textContent = content;
}
}
// Add message to chat
function addMessageToChat(message, role) {
// Get the chat messages container
const chatMessagesContainer = getShadowElement("chat-messages");
if (!chatMessagesContainer) return;
// Create a new message container
const messageContainer = document.createElement("div");
messageContainer.style.cssText = `
margin-bottom: 12px;
padding: 12px 16px;
border-radius: 16px;
max-width: 85%;
width: fit-content;
word-wrap: break-word;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
`;
// Style the message differently based on the role (user or assistant)
if (role === "user") {
messageContainer.style.backgroundColor = "rgb(60, 84, 114)"; // User messages use blue
messageContainer.style.color = "#ffffff";
messageContainer.style.alignSelf = "flex-end";
messageContainer.style.borderBottomRightRadius = "4px";
} else {
messageContainer.style.backgroundColor = "#ffffff"; // Assistant messages use white/subtle grey
messageContainer.style.color = "#333333";
messageContainer.style.alignSelf = "flex-start";
messageContainer.style.border = "1px solid #eaeaea";
messageContainer.style.borderBottomLeftRadius = "4px";
}
// Add the message to the chat
chatMessagesContainer.appendChild(messageContainer);
// Render initial content
renderChatContent(messageContainer, message);
// Scroll to bottom
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
return messageContainer;
}
// Function to clear error state and remove error messages from chat history
function clearErrorState() {
// Remove error messages from chat history (in case any slipped through)
chatHistory = chatHistory.filter(msg => msg.role !== "error");
// Optionally clear error messages from UI after successful response
// This helps provide a cleaner experience when the user resolves their issue
// We keep them for now to maintain transparency, but you could uncomment below to remove them:
/*
const chatMessagesContainer = document.getElementById("chat-messages");
if (chatMessagesContainer) {
const errorMessages = chatMessagesContainer.querySelectorAll('[style*="f8d7da"], [style*="fff3cd"]');
errorMessages.forEach(errorMsg => errorMsg.remove());
}
*/
}
// Function to create valid conversation context for the API
function createValidContext(chatHistory) {
// First filter out error messages
let filteredHistory = chatHistory.filter(msg => msg.role !== "error");
// Ensure valid conversation flow (alternating user/assistant roles)
let validContext = [];
let lastRole = null;
for (const message of filteredHistory) {
// Skip consecutive messages with the same role (except the first)
if (lastRole === message.role) {
// If we have consecutive user messages, skip the earlier one
// If we have consecutive assistant messages, skip the earlier one
if (validContext.length > 0) {
validContext.pop(); // Remove the previous message of the same role
}
}
validContext.push(message);
lastRole = message.role;
}
// Ensure the conversation doesn't end with an assistant message if we're about to add a user message
// The API expects user -> assistant -> user flow
if (validContext.length > 0 && validContext[validContext.length - 1].role === "assistant") {
// This is fine, we can add a user message next
} else if (validContext.length > 0 && validContext[validContext.length - 1].role === "user") {
// We have a trailing user message, which is fine since we're about to send another user message
// But we should remove the trailing user message to avoid consecutive user messages
validContext.pop();
}
return validContext;
}
// Add error message to chat with special styling
function addErrorMessageToChat(errorMessage, isRateLimitError = false) {
const chatMessagesContainer = getShadowElement("chat-messages");
if (!chatMessagesContainer) return;
// Create error message container
const errorContainer = document.createElement("div");
errorContainer.style.cssText = `
margin-bottom: 12px;
padding: 12px 16px;
border-radius: 8px;
max-width: 95%;
word-wrap: break-word;
background-color: ${isRateLimitError ? '#fff3cd' : '#f8d7da'};
border: 1px solid ${isRateLimitError ? '#ffeaa7' : '#f5c6cb'};
color: ${isRateLimitError ? '#856404' : '#721c24'};
align-self: flex-start;
font-family: 'Poppins', sans-serif;
position: relative;
`;
// Add error icon and message
const errorContent = document.createElement("div");
errorContent.style.cssText = `
display: flex;
align-items: flex-start;
gap: 10px;
`;
// Error icon
const errorIcon = document.createElement("div");
errorIcon.innerHTML = isRateLimitError ?
`` :
``;
errorIcon.style.cssText = `
flex-shrink: 0;
margin-top: 2px;
opacity: 0.8;
`;
// Error text
const errorText = document.createElement("div");
errorText.style.cssText = `
flex-grow: 1;
font-size: 14px;
line-height: 1.4;
`;
errorText.textContent = errorMessage;
// Add retry suggestion for certain errors
if (!isRateLimitError && !errorMessage.includes('log in')) {
const retryText = document.createElement("div");
retryText.style.cssText = `
margin-top: 8px;
font-size: 12px;
opacity: 0.8;
font-style: italic;
`;
retryText.textContent = "You can try sending your message again.";
errorText.appendChild(retryText);
}
errorContent.appendChild(errorIcon);
errorContent.appendChild(errorText);
errorContainer.appendChild(errorContent);
// Note: Don't add error messages to chatHistory to prevent them from being sent as context
// This prevents error states from persisting across requests
// Add to chat and scroll
chatMessagesContainer.appendChild(errorContainer);
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
}
// Add this new loading indicator function
function addLoadingIndicator() {
const loadingDiv = document.createElement("div");
loadingDiv.id = "loading-message";
loadingDiv.style.cssText = `
margin-bottom: 16px;
padding: 14px 16px;
border-radius: 14px;
background-color: #fff;
align-self: flex-start;
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
gap: 8px;
font-family: 'Poppins', sans-serif;
font-size: 14px;
color: rgba(0, 0, 0, 0.6);
`;
// Add typing animation dots
const dotsContainer = document.createElement("div");
dotsContainer.style.cssText = `
display: flex;
gap: 4px;
margin-left: 4px;
`;
for (let i = 0; i < 3; i++) {
const dot = document.createElement("div");
dot.style.cssText = `
width: 6px;
height: 6px;
background-color: rgba(0, 0, 0, 0.4);
border-radius: 50%;
animation: typingAnimation 1.4s infinite;
animation-delay: ${i * 0.2}s;
`;
dotsContainer.appendChild(dot);
}
loadingDiv.textContent = "Thinking";
loadingDiv.appendChild(dotsContainer);
// No need to add keyframes - they're already in shadow DOM styles
return loadingDiv;
}
// Function to toggle chat overlay visibility
function toggleChatOverlay() {
isOverlayVisible = !isOverlayVisible;
const shadowHost = document.getElementById("chat-overlay-shadow-host");
let chatOverlay = shadowHost ? shadowHost.shadowRoot.querySelector("#chat-overlay") : null;
if (!chatOverlay) {
chatOverlay = createChatOverlay(); // Creates shadow host and returns overlay
}
if (chatOverlay) {
chatOverlay.style.display = isOverlayVisible ? "flex" : "none";
// Focus on input field when showing overlay
if (isOverlayVisible) {
setTimeout(() => {
const inputField = getShadowRoot()?.querySelector('[contenteditable]');
if (inputField) {
inputField.focus();
// Place cursor at the end of existing text
const range = document.createRange();
const sel = window.getSelection();
// If there's content, move cursor to the end
if (inputField.childNodes.length > 0) {
range.setStart(inputField.childNodes[inputField.childNodes.length - 1],
inputField.childNodes[inputField.childNodes.length - 1].length || 0);
} else {
range.setStart(inputField, 0);
}
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}, 100);
}
}
}
// Function to clear chat history and UI (reusable)
function clearChatHistoryAndUI(reason = 'manual') {
try {
const messagesContainer = getShadowElement("chat-messages");
if (messagesContainer) {
// Clear the chat history array
chatHistory = [];
// Clear the UI
messagesContainer.innerHTML = "";
// Clear any error state
clearErrorState();
// Send message to background script to reset context
chrome.runtime.sendMessage({
action: "resetContext"
});
// Add a notification message based on the reason
let notificationMessage = "Chat history cleared.";
if (reason === 'providerChange') {
notificationMessage = "Chat history cleared - switched to new AI provider.";
}
addNotificationMessage(notificationMessage);
console.log(`Chat history cleared (${reason})`);
}
} catch (error) {
console.error('Error clearing chat history:', error);
}
}
// Function to detect and block clashing chat elements
function blockClashingChatElements() {
// List of class patterns to block (updated class names)
const blockedClassPatterns = [
'cc-1m2mf', // Old class
'cc-1qbp0', // New duplicate chatbot icon
'cc-1o31k', // New duplicate chatbot icon child
'cc-otlyh', // New duplicate chatbot icon child
'cc-11f3x', // New duplicate chatbot icon child
'cc-1v4wj' // New duplicate chatbot icon child
];
// Function to hide elements matching any of the blocked patterns
function hideBlockedElements() {
blockedClassPatterns.forEach(className => {
// Match elements with the exact class or classes containing this pattern
const selector = `[class*="${className}"]`;
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
// Only hide if it's not part of our chat overlay
if (!element.closest('#chat-overlay')) {
element.style.display = 'none';
}
});
});
}
// Add observer to continuously check for and block the element
const observer = new MutationObserver((mutations) => {
hideBlockedElements();
});
// Start observing document body for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
// Also try to block any existing elements immediately
hideBlockedElements();
// Add CSS to ensure elements with these classes are always hidden
const styleElement = document.createElement('style');
const cssRules = blockedClassPatterns.map(className => `
[class*="${className}"]:not(#chat-overlay):not(#chat-overlay *) {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
`).join('\n');
styleElement.textContent = cssRules;
document.head.appendChild(styleElement);
}
// Set up document-level event handlers
document.addEventListener("mousemove", (e) => {
const shadowHost = document.getElementById("chat-overlay-shadow-host");
if (!shadowHost) return;
const overlay = shadowHost.shadowRoot?.querySelector("#chat-overlay");
if (!overlay) return;
if (isDragging) {
const newLeft = e.clientX - dragOffsetX;
const newTop = e.clientY - dragOffsetY;
// Prevent dragging outside viewport
const maxX = window.innerWidth - overlay.offsetWidth;
const maxY = window.innerHeight - overlay.offsetHeight;
overlay.style.left = Math.min(Math.max(0, newLeft), maxX) + "px";
overlay.style.top = Math.min(Math.max(0, newTop), maxY) + "px";
overlay.style.bottom = "auto";
overlay.style.right = "auto";
}
if (isResizing) {
const resizeHandle = overlay.querySelector("div[style*='nw-resize']");
if (!resizeHandle) return;
const MIN_WIDTH = 250;
const MIN_HEIGHT = 200;
const MAX_WIDTH = window.innerWidth - 40;
const MAX_HEIGHT = window.innerHeight - 40;
const dx = resizeStartX - e.clientX;
const dy = resizeStartY - e.clientY;
const newWidth = Math.min(Math.max(MIN_WIDTH, initialWidth + dx), MAX_WIDTH);
const newHeight = Math.min(Math.max(MIN_HEIGHT, initialHeight + dy), MAX_HEIGHT);
const rect = overlay.getBoundingClientRect();
const newLeft = rect.right - newWidth;
const newTop = rect.bottom - newHeight;
// Ensure the overlay stays within viewport bounds
if (newLeft >= 0 && newTop >= 0) {
overlay.style.width = newWidth + "px";
overlay.style.height = newHeight + "px";
overlay.style.left = newLeft + "px";
overlay.style.top = newTop + "px";
}
}
});
// Handle mouse up for drag and resize
document.addEventListener("mouseup", () => {
isDragging = false;
isResizing = false;
});
// Add global keyboard event listeners
document.addEventListener("keydown", (e) => {
// Use Alt (Option) on all platforms including Mac
const modifierKey = e.altKey;
// Toggle chat with Alt/Option + C
// Use e.code to be layout-independent (Option modifies e.key on macOS)
if (modifierKey && e.code === "KeyC") {
e.preventDefault(); // Prevent default browser behavior
toggleChatOverlay();
}
// Close chat with Escape
if (e.key === "Escape" && isOverlayVisible) {
isOverlayVisible = false;
const overlay = getShadowElement("chat-overlay");
if (overlay) {
overlay.style.display = "none";
}
}
});
// Initialize everything
async function init() {
try {
// Try to load showdown and our inline prism highlighter
await Promise.all([loadShowdown(), loadPrism()]);
console.log("Showdown and SimplePrism libraries loaded successfully");
} catch (error) {
console.error('Failed to load libraries:', error);
// Continue even if libraries fail to load
}
// Block clashing chat elements
blockClashingChatElements();
// Create the chat button
const chatButton = createChatButton();
// Get current stealth mode state
chrome.storage.local.get(['stealth'], function(result) {
const stealthModeEnabled = result.stealth === true;
// Hide chat button if stealth mode is enabled
if (stealthModeEnabled && chatButton) {
chatButton.style.opacity = "0"; // Use opacity instead of display none
chatButton.style.pointerEvents = "auto"; // Keep pointer events active
}
// Create the chat overlay initially but keep it hidden
// This ensures Alt+C (Option+C on Mac) will work right from the start
try {
const overlay = createChatOverlay();
// Set overlay opacity based on stealth mode
if (stealthModeEnabled && overlay) {
overlay.style.opacity = "0.15";
}
} catch (error) {
console.error('Error creating chat overlay:', error);
}
});
}
// Start the initialization
init();
// Add global storage change listener for stealth mode updates across tabs
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'local') {
// Clear error state when database or authentication changes occur
if (changes.accessToken || changes.refreshToken) {
clearErrorState();
console.log("Auth state changed, cleared chat error state");
}
if (changes.stealth) {
const newStealthMode = changes.stealth.newValue === true;
// Update chat button visibility globally
const chatButton = getChatButton();
if (chatButton) {
chatButton.style.opacity = newStealthMode ? "0" : "1";
chatButton.style.pointerEvents = "auto"; // Keep pointer events active in both states
// Icon is set via backgroundImage on child span, no innerHTML reset needed
}
// Update overlay opacity if it exists
const overlay = document.getElementById("chat-overlay");
if (overlay) {
overlay.style.opacity = newStealthMode ? "0.15" : "1";
}
}
}
});
// Listen for messages from Chrome runtime
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "updateChatHistory") {
const {
role,
content,
isStreaming
} = message;
// First remove loading indicator if it exists
const loadingMessage = getShadowElement("loading-message");
if (loadingMessage) {
loadingMessage.remove();
}
// Handle error responses from the background script
if (role === "error" || content.includes("error") || content.includes("failed")) {
// Determine if this is a rate limit error
const isRateLimitError = content.includes("limit") || content.includes("exceeded") || content.includes("tomorrow");
addErrorMessageToChat(content, isRateLimitError);
} else if (role === "assistant") {
// Clear any existing error state on successful response
clearErrorState();
if (isStreaming) {
if (!currentStreamingDiv) {
// Create a new assistant message container for streaming
currentStreamingDiv = addMessageToChat("", "assistant");
}
// Update the content incrementally
renderChatContent(currentStreamingDiv, content);
// Scroll to bottom during streaming
const chatMessagesContainer = getShadowElement("chat-messages");
if (chatMessagesContainer) {
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
}
} else {
// Stream finished or single response
if (currentStreamingDiv) {
// Final update for existing stream
renderChatContent(currentStreamingDiv, content);
currentStreamingDiv = null;
} else {
// Non-streaming assistant response
addMessageToChat(content, "assistant");
}
// Add to local chat history for conversation context
chatHistory.push({
role: "assistant",
content: content
});
}
} else {
// Handle other roles (like 'user' echo from server, though usually local)
addMessageToChat(content, role);
}
}
// Handle clear chat history action
if (message.action === "clearChatHistory") {
const reason = message.reason || 'external';
clearChatHistoryAndUI(reason);
if (sendResponse) {
sendResponse({ success: true });
}
}
// Handle direct error messages from background script
if (message.action === "chatError") {
// Remove loading indicator if it exists
const loadingMessage = getShadowElement("loading-message");
if (loadingMessage) {
loadingMessage.remove();
}
const { error, errorType, detailedInfo } = message;
let errorMessage = error || "An error occurred processing your message.";
let isRateLimitError = false;
// Enhance error message based on type
if (errorType === 'rateLimit') {
isRateLimitError = true;
if (!errorMessage.includes("tomorrow") && !errorMessage.includes("wait")) {
errorMessage += " Please try again later.";
}
} else if (errorType === 'auth') {
errorMessage = "Please log in to use the chat feature. Click the extension icon to log in.";
} else if (errorType === 'network') {
errorMessage += " Please check your internet connection and try again.";
} else if (errorType === 'server') {
errorMessage += " The service is temporarily unavailable.";
}
addErrorMessageToChat(errorMessage, isRateLimitError);
}
});
});
})();
================================================
FILE: data/inject/content.js
================================================
window.addEventListener('blur', function() {
window.focus();
});
// Declare shared isMac variable (this will be the first to run)
window.isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ||
navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
// Automatically enable text selection on all websites
(function() {
// Function to enable text selection globally
function enableTextSelectionGlobally() {
// Remove CSS rules that disable text selection
const style = document.createElement('style');
style.id = 'force-text-selection-style';
style.innerHTML = `
* {
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
user-select: text !important;
-webkit-touch-callout: default !important;
}
/* Override common classes that disable text selection */
.no-select, .noselect, .unselectable,
.qaas-disable-text-selection,
.qaas-disable-text-selection *,
[data-disable-text-selection],
[data-disable-text-selection] *,
[unselectable="on"],
[onselectstart],
[ondragstart] {
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
user-select: text !important;
-webkit-touch-callout: default !important;
}
`;
// Only add if not already present
if (!document.getElementById('force-text-selection-style')) {
document.head.appendChild(style);
}
// Remove specific attributes and classes that disable text selection
const disabledElements = document.querySelectorAll(`
.no-select, .noselect, .unselectable,
.qaas-disable-text-selection,
[data-disable-text-selection],
[unselectable="on"],
[onselectstart],
[ondragstart]
`);
disabledElements.forEach(element => {
// Remove classes
element.classList.remove('no-select', 'noselect', 'unselectable', 'qaas-disable-text-selection');
// Remove attributes
element.removeAttribute('data-disable-text-selection');
element.removeAttribute('unselectable');
element.removeAttribute('onselectstart');
element.removeAttribute('ondragstart');
// Force styles
element.style.userSelect = 'text';
element.style.webkitUserSelect = 'text';
element.style.mozUserSelect = 'text';
element.style.msUserSelect = 'text';
element.style.webkitTouchCallout = 'default';
});
// Override common event handlers that prevent text selection
document.onselectstart = null;
document.ondragstart = null;
document.oncontextmenu = null;
// Remove event listeners that might interfere with text selection
const body = document.body;
if (body) {
body.onselectstart = null;
body.ondragstart = null;
}
}
// Apply immediately
enableTextSelectionGlobally();
// Apply when DOM is fully loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', enableTextSelectionGlobally);
}
// Re-apply when new content is added (for dynamic websites)
const observer = new MutationObserver(function(mutations) {
let shouldReapply = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Check if any added nodes have text selection disabled
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const hasDisabledSelection = node.matches && node.matches(`
.no-select, .noselect, .unselectable,
.qaas-disable-text-selection,
[data-disable-text-selection],
[unselectable="on"],
[onselectstart],
[ondragstart]
`);
if (hasDisabledSelection || node.querySelector) {
shouldReapply = true;
}
}
});
}
});
if (shouldReapply) {
enableTextSelectionGlobally();
}
});
// Start observing
observer.observe(document.body || document.documentElement, {
childList: true,
subtree: true
});
})();
// Function to convert HTML to readable text with proper formatting
function htmlToText(element) {
if (!element) return '';
// Clone the element to avoid modifying the original
const clone = element.cloneNode(true);
// Handle superscripts - convert text to ^text
clone.querySelectorAll('sup').forEach(sup => {
sup.textContent = '^' + sup.textContent;
});
// Handle subscripts - convert text to _text
clone.querySelectorAll('sub').forEach(sub => {
sub.textContent = '_' + sub.textContent;
});
// Handle line breaks
clone.querySelectorAll('br').forEach(br => {
br.replaceWith('\n');
});
// Get the text content
return clone.innerText.trim();
}
// Function to extract the question, code, and options
function extractQuestionCodeAndOptions() {
// Extracting the question text
const questionElement = document.querySelector('div[aria-labelledby="question-data"]');
const questionText = questionElement ? htmlToText(questionElement) : '';
// Extracting the code
const codeLines = [];
const codeElements = document.querySelectorAll('.ace_layer.ace_text-layer .ace_line');
codeElements.forEach(line => {
codeLines.push(line.innerText.trim());
});
const codeText = codeLines.length > 0 ? codeLines.join('\n') : null; // Set to null if no code is found
// Extracting options
const optionsElements = document.querySelectorAll('div[aria-labelledby="each-option"]'); // Update this selector as necessary
const optionsText = [];
optionsElements.forEach((option, index) => {
optionsText.push(`Option ${index + 1}: ${htmlToText(option)}`);
});
return {
question: questionText,
code: codeText, // This can be null if no code is present
options: optionsText.join('\n') // Join options with new line characters
};
}
// Async function to handle question, code, and options extraction
async function handleQuestionExtraction() {
const { question, code, options } = extractQuestionCodeAndOptions();
if (!question) {
return;
}
console.log('Question:', question);
console.log('Code:\n', code ? code : 'No code available');
console.log('Options:\n', options);
// Send the extracted data to background.js
// The clicking will be handled by the clickMCQOption message handler
chrome.runtime.sendMessage({
action: 'extractData',
question: question,
code: code,
options: options,
isMCQ: true
});
}
// Function to extract coding question details
function extractCodingQuestion() {
// Extract programming language
const programmingLanguageElement = document.querySelector('span.inner-text');
const programmingLanguage = programmingLanguageElement ? programmingLanguageElement.innerText.trim() : 'Programming language not found.';
// Extract question components
const questionElement = document.querySelector('div[aria-labelledby="question-data"]');
const questionText = questionElement ? htmlToText(questionElement) : 'Question not found.';
const inputFormatElement = document.querySelector('div[aria-labelledby="input-format"]');
const inputFormatText = inputFormatElement ? htmlToText(inputFormatElement) : '';
const outputFormatElement = document.querySelector('div[aria-labelledby="output-format"]');
const outputFormatText = outputFormatElement ? htmlToText(outputFormatElement) : '';
// Extract sample test cases with robust fallback method
const testCases = [];
// Try Method 1: Find test case containers with aria-labelledby="each-tc-card"
let containers = document.querySelectorAll('div[aria-labelledby="each-tc-card"]');
if (containers.length > 0) {
console.log('[Test Cases] Method 1: Found', containers.length, 'test case containers');
containers.forEach((container) => {
const inputPre = container.querySelector('div[aria-labelledby="each-tc-input-container"] pre');
const outputPre = container.querySelector('div[aria-labelledby="each-tc-output-container"] pre');
if (inputPre && outputPre) {
testCases.push({
input: inputPre.textContent.trim(),
output: outputPre.textContent.trim()
});
}
});
}
// Try Method 2: Find by aria-labelledby="each-tc-container"
if (testCases.length === 0) {
console.log('[Test Cases] Method 1 failed. Trying Method 2...');
containers = document.querySelectorAll('[aria-labelledby="each-tc-container"]');
if (containers.length > 0) {
console.log('[Test Cases] Method 2: Found', containers.length, 'test case containers');
containers.forEach((container) => {
const inputPre = container.querySelector('[aria-labelledby="each-tc-input"]');
const outputPre = container.querySelector('[aria-labelledby="each-tc-output"]');
if (inputPre && outputPre) {
testCases.push({
input: inputPre.textContent.trim(),
output: outputPre.textContent.trim()
});
}
});
}
}
// Try Method 3: Find pre elements with Input/Output labels
if (testCases.length === 0) {
console.log('[Test Cases] Method 2 failed. Trying Method 3...');
const allPres = document.querySelectorAll('pre');
const inputs = [];
const outputs = [];
allPres.forEach(pre => {
const text = pre.textContent.trim();
const prevElement = pre.previousElementSibling;
if (prevElement) {
const labelText = prevElement.textContent.toLowerCase();
if (labelText.includes('input') && !labelText.includes('output')) {
inputs.push(text);
} else if (labelText.includes('output')) {
outputs.push(text);
}
}
});
console.log('[Test Cases] Method 3: Found', inputs.length, 'inputs and', outputs.length, 'outputs');
// Pair inputs and outputs
for (let i = 0; i < Math.min(inputs.length, outputs.length); i++) {
testCases.push({
input: inputs[i],
output: outputs[i]
});
}
}
let testCasesText = '';
if (testCases.length > 0) {
testCases.forEach((testCase, index) => {
testCasesText += `Sample Test Case ${index + 1}:\nInput:\n${testCase.input}\nOutput:\n${testCase.output}\n\n`;
});
console.log('[Test Cases] Successfully extracted', testCases.length, 'test cases');
} else {
console.warn('[Test Cases] All methods failed. No test cases extracted.');
testCasesText = 'No test cases found. Please check the page structure.';
}
// Send data to background.js for querying
chrome.runtime.sendMessage({
action: 'extractData',
programmingLanguage: programmingLanguage,
question: questionText,
inputFormat: inputFormatText,
outputFormat: outputFormatText,
testCases: testCasesText,
isCoding: true
}, async (response) => {
if (response && response.success && response.response) {
try {
// Clean the response
let cleanedResponse = response.response.trim()
.replace(/^```[a-z]*\n/, '')
.replace(/\n```$/, '');
console.log('[AI Answer] Cleaned response length:', cleanedResponse.length);
// Copy to clipboard first
await navigator.clipboard.writeText(cleanedResponse);
console.log('[AI Answer] Code copied to clipboard');
// Dispatch custom event to page context (where ace is available)
// This will be handled by exam.js which runs in page context
window.dispatchEvent(new CustomEvent('NEOPASS_INSERT_CODE', {
detail: { code: cleanedResponse }
}));
console.log('[AI Answer] Dispatched code insertion event to page context');
} catch (error) {
console.error("Error processing AI response:", error);
}
}
});
}
function solveIamneoExamly(){
// Check if this is a coding question or MCQ
const codingQuestionElement = document.querySelector('div[aria-labelledby="input-format"]');
if (codingQuestionElement) {
extractCodingQuestion();
} else {
handleQuestionExtraction();
}
}
document.addEventListener('keydown', (event) => {
// Use Option (Alt) key on all platforms
const modifierKey = event.altKey;
if (modifierKey && event.shiftKey && event.code === 'KeyA') {
solveIamneoExamly();
}
});
// Add event listener for Option+O to toggle toast opacity
document.addEventListener('keydown', (event) => {
// Use Option (Alt) key on all platforms
const modifierKey = event.altKey;
if (modifierKey && event.code === 'KeyO') {
chrome.runtime.sendMessage({
action: 'toggleToastOpacity'
});
}
});
// Function to extract code from snippets
function extractSnippets() {
const headerContainer = Array.from(document.querySelectorAll('div[aria-labelledby="tt-header"]'))
.find(container => container.innerText.includes('Header Snippet'));
const footerContainer = Array.from(document.querySelectorAll('div[aria-labelledby="footer"]'))
.find(container => container.innerText.includes('Footer Snippet'));
const extractCode = container => {
if (!container) return '';
const codeLines = container.querySelectorAll('.ace_line');
return Array.from(codeLines).map(line => line.textContent).join('\n');
};
const snippets = {
header: extractCode(headerContainer),
footer: extractCode(footerContainer)
};
// Send snippets directly to background.js
chrome.runtime.sendMessage({
action: 'processSnippets',
snippets: snippets
});
}
// Remove old listener and add new one
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'extractSnippets') {
extractSnippets();
}
if (message.action === 'solveIamneoExamly') {
solveIamneoExamly();
}
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "updateChatHistory") {
const { role, content } = message;
// Remove loading indicator if it exists
const loadingMessage = document.getElementById("loading-message");
if (loadingMessage) {
loadingMessage.remove();
}
// Add the actual message
chatHistory.push({
role: role,
content: content
});
addMessageToChat(content, role);
}
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'clickMCQOption') {
(async () => {
try {
// Check if this is HackerRank
if (request.isHackerRank) {
let clicked = false;
// Handle multiple choice questions (checkboxes) differently
if (request.isMultipleChoice) {
console.log('Multiple choice question detected, response:', request.response);
// Enhanced parsing for multiple options
// Look for patterns like: "1. text, 3. text" or "A. text, C. text" or "1, 3" or "A, C"
const optionNumbers = [];
// Pattern 1: "1. text, 3. text" or "A. text, C. text"
let matches = request.response.match(/([A-Z]|\d+)\.\s*[^,]+/gi);
if (matches) {
matches.forEach(match => {
const num = match.match(/^([A-Z]|\d+)\./);
if (num) {
let optionIndex;
if (isNaN(num[1])) {
// Convert A,B,C to 0,1,2
optionIndex = num[1].charCodeAt(0) - 'A'.charCodeAt(0);
} else {
// Convert 1,2,3 to 0,1,2
optionIndex = parseInt(num[1]) - 1;
}
if (optionIndex >= 0) {
optionNumbers.push(optionIndex);
}
}
});
}
// Pattern 2: Simple comma-separated numbers or letters: "1, 3, 5" or "A, C, E"
if (optionNumbers.length === 0) {
const simpleMatches = request.response.match(/(?:^|[,\s])([A-Z]|\d+)(?=[,\s]|$)/gi);
if (simpleMatches) {
simpleMatches.forEach(match => {
const cleaned = match.trim().replace(/^[,\s]+|[,\s]+$/g, '');
let optionIndex;
if (isNaN(cleaned)) {
// Convert A,B,C to 0,1,2
optionIndex = cleaned.charCodeAt(0) - 'A'.charCodeAt(0);
} else {
// Convert 1,2,3 to 0,1,2
optionIndex = parseInt(cleaned) - 1;
}
if (optionIndex >= 0) {
optionNumbers.push(optionIndex);
}
});
}
}
// Remove duplicates
const uniqueOptionNumbers = [...new Set(optionNumbers)];
console.log('Parsed multiple choice options:', uniqueOptionNumbers.map(n => n + 1));
// Click all the selected options for multiple choice
const checkboxes = document.querySelectorAll('[role="checkbox"]');
if (checkboxes.length > 0) {
console.log(`Found ${checkboxes.length} checkboxes, will click options:`, uniqueOptionNumbers.map(n => n + 1));
// Click options with delay to ensure UI state is properly updated
for (let i = 0; i < uniqueOptionNumbers.length; i++) {
const optionNumber = uniqueOptionNumbers[i];
if (optionNumber >= 0 && optionNumber < checkboxes.length) {
const checkbox = checkboxes[optionNumber];
// Wait a bit before checking and clicking each option
await new Promise(resolve => setTimeout(resolve, 300));
// Re-check the current state after delay
const isCurrentlyChecked = checkbox.getAttribute('aria-checked') === 'true' ||
checkbox.getAttribute('data-state') === 'checked' ||
checkbox.checked === true;
console.log(`Option ${optionNumber + 1} current state: ${isCurrentlyChecked ? 'checked' : 'unchecked'}`);
// Only click if not already checked
if (!isCurrentlyChecked) {
console.log(`Clicking checkbox option ${optionNumber + 1}...`);
// Try multiple click methods to ensure it works
checkbox.click();
// Alternative click method - dispatch events directly
checkbox.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
checkbox.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
checkbox.dispatchEvent(new MouseEvent('click', { bubbles: true }));
// Wait a bit more to let the UI update
await new Promise(resolve => setTimeout(resolve, 200));
// Verify the click worked
const newState = checkbox.getAttribute('aria-checked') === 'true' ||
checkbox.getAttribute('data-state') === 'checked' ||
checkbox.checked === true;
if (newState) {
console.log(`✅ HackerRank checkbox option ${optionNumber + 1} clicked successfully`);
clicked = true;
} else {
console.log(`⚠️ HackerRank checkbox option ${optionNumber + 1} click may have failed - retrying...`);
// Retry once more
checkbox.click();
await new Promise(resolve => setTimeout(resolve, 100));
const retryState = checkbox.getAttribute('aria-checked') === 'true' ||
checkbox.getAttribute('data-state') === 'checked' ||
checkbox.checked === true;
if (retryState) {
console.log(`✅ HackerRank checkbox option ${optionNumber + 1} clicked successfully on retry`);
clicked = true;
} else {
console.log(`❌ HackerRank checkbox option ${optionNumber + 1} failed to click`);
}
}
} else {
console.log(`✅ HackerRank checkbox option ${optionNumber + 1} already selected`);
clicked = true; // Still count as successful
}
}
}
// If no options were found, fall back to single option logic
if (uniqueOptionNumbers.length === 0) {
console.log('No multiple options found, falling back to single option logic');
const optionMatch = request.response.match(/(?:options?\s*)?([A-Z]|\d+)\.?/i);
if (optionMatch) {
let optionNumber;
if (isNaN(optionMatch[1])) {
optionNumber = optionMatch[1].charCodeAt(0) - 'A'.charCodeAt(0);
} else {
optionNumber = parseInt(optionMatch[1]) - 1;
}
if (optionNumber >= 0 && optionNumber < checkboxes.length) {
await new Promise(resolve => setTimeout(resolve, 200));
const checkbox = checkboxes[optionNumber];
const isCurrentlyChecked = checkbox.getAttribute('aria-checked') === 'true' ||
checkbox.getAttribute('data-state') === 'checked' ||
checkbox.checked === true;
if (!isCurrentlyChecked) {
checkbox.click();
console.log(`HackerRank single checkbox option ${optionNumber + 1} clicked as fallback`);
clicked = true;
} else {
console.log(`HackerRank single checkbox option ${optionNumber + 1} already selected`);
clicked = true;
}
}
}
}
}
} else {
// Single choice question - use enhanced logic
const optionMatch = request.response.match(/(?:options?\s*)?([A-Z]|\d+)\.?/i);
if (optionMatch) {
let optionNumber;
if (isNaN(optionMatch[1])) {
// Handle letter options (A, B, C, etc.)
optionNumber = optionMatch[1].toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
} else {
// Handle number options (1, 2, 3, etc.)
optionNumber = parseInt(optionMatch[1]) - 1;
}
console.log(`Single choice detected, clicking option: ${optionNumber + 1}`);
// Add a small delay before clicking
await new Promise(resolve => setTimeout(resolve, 200));
// Try new layout first - check for radio buttons
const newLayoutRadios = document.querySelectorAll('[role="radio"]');
if (newLayoutRadios.length > optionNumber && optionNumber >= 0) {
const radio = newLayoutRadios[optionNumber];
// Check if already selected
const isCurrentlySelected = radio.getAttribute('aria-checked') === 'true' ||
radio.getAttribute('data-state') === 'checked' ||
radio.checked === true;
if (!isCurrentlySelected) {
radio.click();
console.log(`HackerRank new layout radio option ${optionNumber + 1} clicked successfully`);
clicked = true;
} else {
console.log(`HackerRank new layout radio option ${optionNumber + 1} already selected`);
clicked = true;
}
} else {
// Try checkboxes if no radio buttons found (fallback for single checkbox)
const newLayoutCheckboxes = document.querySelectorAll('[role="checkbox"]');
if (newLayoutCheckboxes.length > optionNumber && optionNumber >= 0) {
const checkbox = newLayoutCheckboxes[optionNumber];
const isCurrentlyChecked = checkbox.getAttribute('aria-checked') === 'true' ||
checkbox.getAttribute('data-state') === 'checked' ||
checkbox.checked === true;
if (!isCurrentlyChecked) {
checkbox.click();
console.log(`HackerRank new layout checkbox option ${optionNumber + 1} clicked successfully`);
clicked = true;
} else {
console.log(`HackerRank new layout checkbox option ${optionNumber + 1} already selected`);
clicked = true;
}
} else {
// Fallback to old layout (radio buttons)
const questionContainer = document.querySelector('.grouped-mcq__question');
if (questionContainer) {
const radios = questionContainer.querySelectorAll('input[type="radio"]');
if (radios.length > optionNumber && optionNumber >= 0) {
const radio = radios[optionNumber];
if (!radio.checked) {
radio.click();
console.log(`HackerRank old layout option ${optionNumber + 1} clicked successfully`);
clicked = true;
} else {
console.log(`HackerRank old layout option ${optionNumber + 1} already selected`);
clicked = true;
}
}
}
}
}
}
}
if (!clicked) {
chrome.runtime.sendMessage({
action: 'showMCQToast',
message: request.response,
});
}
} else {
// Original logic for other platforms (Examly)
const optionMatch = request.response.match(/(?:options?\s*)?(\d+)\.?/i);
if (optionMatch) {
const optionNumber = parseInt(optionMatch[1])-1;
// Use exact same selector as Alt+Shift+Q
const answerElement = document.querySelector(`#tt-option-${optionNumber} > label > span.checkmark1`);
if (answerElement) {
answerElement.dispatchEvent(new Event("click", { bubbles: true }));
console.log(`Option element ${optionNumber + 1} clicked successfully`);
} else {
chrome.runtime.sendMessage({
action: 'showMCQToast',
message: request.response,
});
}
} else {
chrome.runtime.sendMessage({
action: 'showMCQToast',
message: request.response,
});
}
}
} catch (error) {
chrome.runtime.sendMessage({
action: 'showMCQToast',
message: request.response,
});
}
})();
}
});
// Function to extract HackerRank MCQ data (updated for new layout)
function extractHackerRankMCQ() {
const questions = [];
// Try new layout first (2024+ layout)
const newLayoutQuestions = document.querySelectorAll('.QuestionDetails_container__AIu0X');
if (newLayoutQuestions.length > 0) {
// New layout processing
newLayoutQuestions.forEach((container, index) => {
const questionData = {
questionNumber: index + 1,
title: '',
instruction: '',
options: [],
selectedAnswer: null
};
// Extract question title from new layout
const titleElement = container.querySelector('.qaas-block-question-title, h2');
if (titleElement) {
// Remove bookmark icon and get clean title
const titleText = titleElement.textContent || titleElement.innerText;
questionData.title = titleText.replace(/Bookmark question \d+/g, '').trim();
}
// Extract question instruction/content from new layout
const instructionElement = container.querySelector('.qaas-block-question-instruction, .RichTextPreview_richText__1vKu5');
if (instructionElement) {
let instructionText = instructionElement.textContent || instructionElement.innerText;
instructionText = instructionText.replace(/\s+/g, ' ').trim();
questionData.instruction = instructionText;
}
// Look for options in multiple possible containers
let optionsContainer = container.nextElementSibling;
let attempts = 0;
while (optionsContainer && attempts < 5) {
// Check for both radio buttons and checkboxes
const hasOptions = optionsContainer.querySelector('[role="checkbox"], [role="radio"], .ui-radio');
if (hasOptions) {
break;
}
optionsContainer = optionsContainer.nextElementSibling;
attempts++;
}
// Also check for options within the same container or nearby
if (!optionsContainer || !optionsContainer.querySelector('[role="checkbox"], [role="radio"]')) {
optionsContainer = container.parentElement?.querySelector('.Control_container__F35yA') ||
document.querySelector('.Control_container__F35yA');
}
if (optionsContainer) {
// Try radio buttons first (new layout)
let optionElements = optionsContainer.querySelectorAll('[role="radio"]');
// If no radio buttons, try checkboxes
if (optionElements.length === 0) {
optionElements = optionsContainer.querySelectorAll('[role="checkbox"]');
}
optionElements.forEach((option, optionIndex) => {
const labelId = option.getAttribute('aria-labelledby');
const labelElement = labelId ? document.getElementById(labelId) :
option.closest('.Control_optionList__vIubt, li')?.querySelector('label');
if (labelElement) {
const optionText = labelElement.textContent.trim();
const isChecked = option.getAttribute('aria-checked') === 'true' ||
option.getAttribute('data-state') === 'checked';
questionData.options.push({
value: option.value || optionIndex.toString(),
text: optionText,
isSelected: isChecked
});
if (isChecked) {
questionData.selectedAnswer = option.value || optionIndex.toString();
}
}
});
}
// Only add question if it has options (to distinguish from coding questions)
if (questionData.options.length > 0) {
questions.push(questionData);
}
});
} else {
// Fallback to old layout
const oldLayoutQuestions = document.querySelectorAll('.grouped-mcq__question');
oldLayoutQuestions.forEach((container, index) => {
const questionData = {
questionNumber: index + 1,
title: '',
instruction: '',
options: [],
selectedAnswer: null
};
// Extract question title from old layout
const titleElement = container.querySelector('.question-view__title');
if (titleElement) {
questionData.title = titleElement.textContent.trim();
}
// Extract question instruction/content from old layout
const instructionElement = container.querySelector('.question-view__instruction');
if (instructionElement) {
let instructionText = instructionElement.textContent.trim();
instructionText = instructionText.replace(/\s+/g, ' ').trim();
questionData.instruction = instructionText;
}
// Extract options from old layout
const optionElements = container.querySelectorAll('.ui-radio');
optionElements.forEach((option, optionIndex) => {
const labelElement = option.querySelector('.label');
const inputElement = option.querySelector('input[type="radio"]');
if (labelElement && inputElement) {
const optionText = labelElement.textContent.trim();
const optionValue = inputElement.value;
const isChecked = inputElement.checked;
questionData.options.push({
value: optionValue,
text: optionText,
isSelected: isChecked
});
if (isChecked) {
questionData.selectedAnswer = optionValue;
}
}
});
questions.push(questionData);
});
}
return questions;
}
// Function to extract HackerRank coding question (updated for new layout)
function extractHackerRankCoding() {
const getCleanText = el => el?.innerText?.trim() || "";
// Try new layout first (2024+ layout)
let language = "Unknown";
let title = "No Title Found";
let instruction = "No Instructions Found";
let details = "";
let starterCode = "";
// Check for new layout language selector
const newLanguageSelector = document.querySelector('.select-language .css-3d4y2u-singleValue, .select-language .css-x7738g');
if (newLanguageSelector) {
language = getCleanText(newLanguageSelector);
} else {
// Fallback to old layout
language = getCleanText(document.querySelector('.select-language .css-x7738g')) || "Unknown";
}
// Try new layout question container
let container = document.querySelector('.QuestionDetails_container__AIu0X');
if (container) {
// New layout
const titleElement = container.querySelector('.qaas-block-question-title, h2');
if (titleElement) {
const titleText = titleElement.textContent || titleElement.innerText;
title = titleText.replace(/Bookmark question \d+/g, '').trim();
}
const instructionElement = container.querySelector('.qaas-block-question-instruction, .RichTextPreview_richText__1vKu5');
if (instructionElement) {
instruction = getCleanText(instructionElement);
}
// Look for details sections in new layout
const detailsElements = container.querySelectorAll('details');
if (detailsElements.length > 0) {
details = Array.from(detailsElements).map(detail => {
const summary = getCleanText(detail.querySelector('summary'));
const content = getCleanText(detail.querySelector('.collapsable-details'));
return `\n${summary}\n${'-'.repeat(summary.length)}\n${content}`;
}).join('\n');
}
} else {
// Fallback to old layout
container = document.querySelector('#main-splitpane-left');
if (container) {
title = getCleanText(container.querySelector('.question-view__title')) || "No Title Found";
instruction = getCleanText(container.querySelector('.question-view__instruction')) || "No Instructions Found";
details = Array.from(container.querySelectorAll('details') || []).map(detail => {
const summary = getCleanText(detail.querySelector('summary'));
const content = getCleanText(detail.querySelector('.collapsable-details'));
return `\n${summary}\n${'-'.repeat(summary.length)}\n${content}`;
}).join('\n');
}
}
// Get starter code from Monaco editor (works for both layouts)
const codeLines = Array.from(document.querySelectorAll('.view-lines .view-line')).map(line =>
line.innerText
).join('\n').trim();
starterCode = codeLines;
return {
language,
title,
instruction,
details,
starterCode: starterCode
};
}
// Function to normalize code indentation
function normalizeCodeIndentation(code) {
if (!code) return code;
const lines = code.split('\n');
// Remove empty lines at the beginning and end
while (lines.length > 0 && lines[0].trim() === '') {
lines.shift();
}
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
lines.pop();
}
if (lines.length === 0) return '';
// Find the minimum indentation (excluding empty lines)
let minIndent = Infinity;
for (const line of lines) {
if (line.trim() !== '') {
const indent = line.match(/^\s*/)[0].length;
minIndent = Math.min(minIndent, indent);
}
}
// Remove the common indentation from all lines
if (minIndent > 0 && minIndent !== Infinity) {
for (let i = 0; i < lines.length; i++) {
if (lines[i].trim() !== '') {
lines[i] = lines[i].substring(minIndent);
}
}
}
return lines.join('\n');
}
// Function to insert code into Monaco editor with proper formatting
async function insertCodeIntoMonacoEditor(text) {
console.log('insertCodeIntoMonacoEditor called with text length:', text.length);
// Normalize the code indentation first
const normalizedText = normalizeCodeIndentation(text);
console.log('Text after normalization:', normalizedText);
// 1. Try to find Monaco editor instance through the global scope
if (typeof monaco !== 'undefined' && window.monaco) {
try {
const editor = window.monaco.editor.getEditors()[0];
if (editor) {
console.log('Found Monaco editor instance, setting value directly...');
editor.setValue(normalizedText);
editor.focus();
return true;
}
} catch (error) {
console.log('Monaco API method failed, trying alternative approaches...');
}
}
// 2. Try to access Monaco editor through DOM manipulation
const monacoEditor = document.querySelector('.monaco-editor');
console.log('Monaco editor DOM element found:', !!monacoEditor);
if (!monacoEditor) {
console.error("❌ Monaco editor not found.");
return false;
}
try {
// 3. Focus the editor properly
const editorTextArea = monacoEditor.querySelector('textarea.inputarea') ||
monacoEditor.querySelector('textarea') ||
monacoEditor.querySelector('.monaco-editor-background');
if (editorTextArea) {
console.log('Found Monaco textarea, focusing...');
editorTextArea.focus();
editorTextArea.click();
} else {
console.log('Monaco textarea not found, clicking editor container...');
monacoEditor.focus();
monacoEditor.click();
}
// 4. Wait a bit for focus to settle
await new Promise(resolve => setTimeout(resolve, 200));
// 5. Clear existing content using keyboard shortcuts
console.log('Clearing existing content...');
// Use Select All (Cmd+A on macOS, Ctrl+A elsewhere)
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 'a',
code: 'KeyA',
ctrlKey: !window.isMac,
metaKey: window.isMac,
bubbles: true
}));
await new Promise(resolve => setTimeout(resolve, 100));
// Use Delete or Backspace to clear
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Delete',
code: 'Delete',
bubbles: true
}));
await new Promise(resolve => setTimeout(resolve, 100));
// 6. Copy normalized text to clipboard
await navigator.clipboard.writeText(normalizedText);
console.log('Text copied to clipboard');
// 7. Paste (Cmd+V on macOS, Ctrl+V elsewhere)
console.log('Pasting content...');
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 'v',
code: 'KeyV',
ctrlKey: !window.isMac,
metaKey: window.isMac,
bubbles: true
}));
await new Promise(resolve => setTimeout(resolve, 300));
// 8. Try input event as fallback
if (editorTextArea) {
console.log('Trying input event fallback...');
// Set the value directly on the textarea
editorTextArea.value = normalizedText;
// Trigger input events
editorTextArea.dispatchEvent(new Event('input', { bubbles: true }));
editorTextArea.dispatchEvent(new Event('change', { bubbles: true }));
// Try to trigger Monaco's internal update
editorTextArea.dispatchEvent(new KeyboardEvent('keydown', {
key: 'End',
code: 'End',
bubbles: true
}));
}
console.log('✅ Successfully inserted code into Monaco editor');
return true;
} catch (error) {
console.error("❌ Error inserting code into Monaco editor:", error);
// Final fallback: copy to clipboard
try {
await navigator.clipboard.writeText(normalizedText);
console.log('Fallback: copied normalized text to clipboard');
} catch (clipboardError) {
console.error('Clipboard fallback also failed:', clipboardError);
}
return false;
}
}
// Function to handle HackerRank extraction (both MCQ and coding, updated for new layout)
function handleHackerRankMCQ() {
// Check if it's a coding question first (Monaco editor present)
const monacoEditor = document.querySelector('.monaco-editor, .hr-monaco-editor');
// Check for MCQ options specifically (more precise detection)
const hasRadioOptions = document.querySelector('[role="radio"], [role="radiogroup"]');
const hasCheckboxOptions = document.querySelector('[role="checkbox"]');
const hasOldMcqOptions = document.querySelector('.grouped-mcq__question .ui-radio');
const hasOptionsControl = document.querySelector('.Control_container__F35yA');
// More precise MCQ detection
const isMCQ = hasRadioOptions || hasCheckboxOptions || hasOldMcqOptions ||
(hasOptionsControl && !monacoEditor);
if (monacoEditor && !isMCQ) {
// This is definitely a coding question
const codingData = extractHackerRankCoding();
if (!codingData.instruction || codingData.instruction === "No Instructions Found") {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No HackerRank coding question found.',
isError: true
});
return;
}
// Format the question for AI
const questionText = `
Language: ${codingData.language}
Title: ${codingData.title}
Instructions:
${codingData.instruction}
${codingData.details}
Starter Code:
-------------
${codingData.starterCode}
`.trim();
console.log('HackerRank Coding Question:', questionText);
// Send the extracted data to background.js
chrome.runtime.sendMessage({
action: 'extractData',
programmingLanguage: codingData.language,
question: questionText,
inputFormat: codingData.details,
outputFormat: '',
testCases: '',
isHackerRank: true,
isCoding: true }, async (response) => {
console.log('HackerRank coding response received:', response);
if (response && response.success && response.response) {
try {
console.log('Raw AI response:', response.response);
// Clean the response more thoroughly
let cleanedResponse = response.response.trim();
console.log('Response after trim:', cleanedResponse);
// Remove code block delimiters if present (more comprehensive)
cleanedResponse = cleanedResponse
.replace(/^```[a-zA-Z]*\s*\n/, '') // Remove opening ``` with optional language
.replace(/\n\s*```\s*$/, '') // Remove closing ``` with optional whitespace
.replace(/^```[a-zA-Z]*\s*/, '') // Remove opening ``` without newline
.replace(/\s*```\s*$/, ''); // Remove closing ``` without newline
// Remove any leading/trailing whitespace after code block removal
cleanedResponse = cleanedResponse.trim();
console.log('Cleaned response (after removing code blocks):', cleanedResponse);
// Insert code into Monaco editor with proper formatting
console.log('Attempting to insert code into Monaco editor...');
const success = await insertCodeIntoMonacoEditor(cleanedResponse);
console.log('Monaco editor insertion result:', success);
if (!success) {
// If insertion fails, copy to clipboard as fallback
console.log('Monaco insertion failed, copying to clipboard as fallback');
await navigator.clipboard.writeText(cleanedResponse);
chrome.runtime.sendMessage({
action: 'showToast',
message: 'Copied to clipboard - paste manually',
isError: false
});
} else {
console.log('Successfully inserted code into Monaco editor');
chrome.runtime.sendMessage({
action: 'showToast',
message: 'Code inserted successfully',
isError: false
});
}
} catch (error) {
console.error("Error processing coding response:", error);
chrome.runtime.sendMessage({
action: 'showToast',
message: 'Error processing response',
isError: true
});
}
} else {
console.error('Invalid response received:', response);
}
});
} else if (isMCQ) {
// This is an MCQ question
const extractedData = extractHackerRankMCQ();
if (extractedData.length === 0) {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No HackerRank MCQ questions found.',
isError: true
});
return;
}
// Process the first question
const firstQuestion = extractedData[0];
if (!firstQuestion.instruction && !firstQuestion.title) {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No question text found.',
isError: true
});
return;
}
if (firstQuestion.options.length === 0) {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No options found for MCQ question.',
isError: true
});
return;
}
// Format the question and options for AI with explicit instructions
const questionText = firstQuestion.title ? `${firstQuestion.title}\n${firstQuestion.instruction}` : firstQuestion.instruction;
const optionsText = firstQuestion.options.map((option, index) =>
`Option ${index + 1}: ${option.text}`
).join('\n');
// Detect if this is a multiple choice question (checkboxes) or single choice (radio buttons)
const hasCheckboxes = document.querySelector('[role="checkbox"]');
const isMultipleChoice = hasCheckboxes && !document.querySelector('[role="radio"]');
// Add explicit instruction for multiple choice questions
let finalQuestionText = questionText;
if (isMultipleChoice) {
finalQuestionText = `[MULTIPLE CHOICE QUESTION - SELECT ALL CORRECT OPTIONS]\n\n${questionText}\n\nIMPORTANT: This question allows multiple correct answers. Please respond with ALL correct option numbers separated by commas (e.g., "Options 1, 3, 5" or "1, 3, 5").`;
} else {
finalQuestionText = `[SINGLE CHOICE QUESTION - SELECT ONE OPTION]\n\n${questionText}\n\nIMPORTANT: This question allows only ONE correct answer. Please respond with the single correct option number (e.g., "Option 2" or "2").`;
}
console.log('HackerRank MCQ Question:', finalQuestionText);
console.log('Options:\n', optionsText);
console.log('Question type:', isMultipleChoice ? 'Multiple Choice (checkboxes)' : 'Single Choice (radio buttons)');
// Send the extracted data to background.js
chrome.runtime.sendMessage({
action: 'extractData',
question: finalQuestionText, // Use the enhanced question text
code: null,
options: optionsText,
isHackerRank: true,
isMCQ: true,
isMultipleChoice: isMultipleChoice // Add flag for multiple choice questions
}, (response) => {
console.log("Response from background:", response);
});
} else {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No HackerRank question found on this page.',
isError: true
});
}
}
// Add event listener for Ctrl+Shift+H (Mac) or Alt+Shift+H (Windows) for HackerRank MCQ extraction
document.addEventListener('keydown', (event) => {
// Use Ctrl on Mac, Alt on Windows/other platforms
const modifierKey = window.isMac ? event.ctrlKey : event.altKey;
if (modifierKey && event.shiftKey && event.code === 'KeyH') {
handleHackerRankMCQ();
}
});
// Listen for code request from exam.js (Alt+Shift+T)
window.addEventListener('NEOPASS_REQUEST_CODE_TYPED', function(event) {
const { programmingLanguage, question, inputFormat, outputFormat, testCases } = event.detail;
console.log('[content.js] Received NEOPASS_REQUEST_CODE_TYPED event');
// Send data to background.js for querying
chrome.runtime.sendMessage({
action: 'extractData',
programmingLanguage: programmingLanguage,
question: question,
inputFormat: inputFormat,
outputFormat: outputFormat,
testCases: testCases,
isCoding: true
}, async (response) => {
if (response && response.success && response.response) {
try {
// Clean the response
let cleanedResponse = response.response.trim()
.replace(/^```[a-z]*\n/, '')
.replace(/\n```$/, '');
console.log('[content.js] Cleaned AI response length:', cleanedResponse.length);
// Dispatch custom event back to exam.js with the typed code
window.dispatchEvent(new CustomEvent('NEOPASS_INSERT_CODE_TYPED', {
detail: { code: cleanedResponse }
}));
console.log('[content.js] Dispatched NEOPASS_INSERT_CODE_TYPED event to exam.js');
} catch (error) {
console.error("[content.js] Error processing AI response:", error);
}
} else {
console.error("[content.js] Failed to get AI response");
}
});
});
================================================
FILE: data/inject/copyOverride.js
================================================
// Custom Ctrl+C override functionality - Prevents default copy on divs
(function() {
'use strict';
// Create an invisible textarea for our controlled copy operations
const invisibleTextarea = document.createElement('textarea');
invisibleTextarea.id = 'neopass-invisible-copy';
invisibleTextarea.style.position = 'fixed';
invisibleTextarea.style.opacity = '0';
invisibleTextarea.style.pointerEvents = 'none';
invisibleTextarea.style.left = '-9999px';
invisibleTextarea.style.top = '-9999px';
invisibleTextarea.style.width = '1px';
invisibleTextarea.style.height = '1px';
invisibleTextarea.style.border = 'none';
invisibleTextarea.style.outline = 'none';
invisibleTextarea.style.resize = 'none';
invisibleTextarea.style.overflow = 'hidden';
document.body.appendChild(invisibleTextarea);
// Store the last copied text in a global variable for paste operations
window.neoPassClipboard = '';
// Flag to track when we're performing a custom copy operation
let isCustomCopying = false;
// Override navigator.clipboard.writeText to use our custom copy AND store in clipboard
const originalWriteText = navigator.clipboard.writeText;
navigator.clipboard.writeText = async function(text) {
console.log('[CopyOverride] Intercepted clipboard writeText:', text.substring(0, 100));
window.neoPassClipboard = text; // Store for later paste
try {
// Try to use the original writeText first for compatibility
await originalWriteText.call(navigator.clipboard, text);
console.log('[CopyOverride] Successfully wrote to native clipboard');
} catch (err) {
console.log('[CopyOverride] Native clipboard write failed, using custom copy:', err);
await customCopy(text);
}
console.log('[CopyOverride] Stored in neoPassClipboard, length:', text.length);
return Promise.resolve();
};
// Override document.execCommand to use our custom copy method
const originalExecCommand = document.execCommand;
document.execCommand = function(command, showUI, value) {
if (command === 'copy') {
const activeElement = document.activeElement;
if (activeElement !== invisibleTextarea) {
console.log('Intercepted execCommand copy, using custom copy');
const text = activeElement.value || activeElement.textContent;
if (text) {
return customCopy(text);
}
return false;
}
}
return originalExecCommand.call(this, command, showUI, value);
};
// Function to perform custom copy operation
async function customCopy(selectedText) {
if (!selectedText) return false;
try {
// Set flag to prevent blocking our own copy
isCustomCopying = true;
// Store in our global clipboard variable
window.neoPassClipboard = selectedText;
// Try to write to native clipboard first
try {
await originalWriteText.call(navigator.clipboard, selectedText);
console.log('[CopyOverride] Wrote to native clipboard via writeText');
} catch (clipErr) {
console.log('[CopyOverride] writeText failed, using execCommand:', clipErr);
}
invisibleTextarea.value = selectedText;
invisibleTextarea.select();
invisibleTextarea.setSelectionRange(0, selectedText.length);
const success = originalExecCommand.call(document, 'copy');
console.log('Text copied using invisible textarea:', success, 'Stored in neoPassClipboard');
// Clear the textarea
invisibleTextarea.value = '';
invisibleTextarea.blur();
// Reset flag after a longer delay to allow all copy events to complete
setTimeout(() => {
isCustomCopying = false;
}, 300);
return success;
} catch (err) {
console.error('Copy using invisible textarea failed:', err);
isCustomCopying = false;
return false;
}
}
// Function to get selected text
function getSelectedText() {
const selection = window.getSelection();
return selection.toString().trim();
}
// Function removed - login check no longer required
// CRITICAL: Block ALL copy events at the earliest phase
document.addEventListener('copy', function(event) {
// Allow copy if we're currently performing a custom copy
if (isCustomCopying) {
// Silently allow during custom copy window
return;
}
// Only allow copy from our invisible textarea - block everything else
if (event.target !== invisibleTextarea && document.activeElement !== invisibleTextarea) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, true); // Capture phase - runs before any other handlers
// Handle keyboard copy (Ctrl+C / Cmd+C)
document.addEventListener('keydown', async function(event) {
if ((event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey && event.key === 'c') {
const selectedText = getSelectedText();
if (selectedText) {
// Prevent default FIRST
event.preventDefault();
event.stopImmediatePropagation();
// Clear selection IMMEDIATELY to prevent spurious copy events
window.getSelection().removeAllRanges();
console.log('[CopyOverride] Ctrl+C detected, initiating custom copy');
try {
// Store in global clipboard
window.neoPassClipboard = selectedText;
// Perform custom copy with flag protection
const success = await customCopy(selectedText);
console.log('[CopyOverride] Custom copy executed:', {
success,
textLength: selectedText.length,
preview: selectedText.substring(0, 40) + (selectedText.length > 40 ? '...' : '')
});
} catch (error) {
console.error('[CopyOverride] Error in custom copy handler:', error);
isCustomCopying = false; // Reset flag on error
}
}
}
}, true); // Capture phase
// Handle context menu copy
document.addEventListener('contextmenu', function(event) {
const selectedText = getSelectedText();
if (selectedText) {
window.neoPassSelectedText = selectedText;
window.neoPassClipboard = selectedText; // Also store in main clipboard
}
}, true);
// Log clipboard status for debugging
window.getNeoPassClipboard = function() {
console.log('[CopyOverride] Current neoPassClipboard:', window.neoPassClipboard);
return window.neoPassClipboard;
};
console.log('Custom copy prevention initialized - default copy blocked on all elements');
})();
================================================
FILE: data/inject/customPaste.js
================================================
async function performPasteByTyping() {
console.log('[PasteByTyping] Function called');
const activeElement = document.activeElement;
console.log('[PasteByTyping] Active element:', {
tagName: activeElement?.tagName,
isContentEditable: activeElement?.isContentEditable,
id: activeElement?.id,
className: activeElement?.className
});
if (!activeElement || !(activeElement.isContentEditable || activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
console.log('[PasteByTyping] No valid input element focused');
return;
}
try {
let clipText = '';
let clipboardSource = 'none';
// First, try native clipboard (prioritize external app copies)
try {
clipText = await navigator.clipboard.readText();
clipboardSource = 'native';
console.log('[PasteByTyping] Using native clipboard:', clipText.substring(0, 100));
} catch (clipErr) {
console.log('[PasteByTyping] Native clipboard read failed:', clipErr.message);
}
// If empty, fall back to our custom clipboard storage
if (!clipText && window.neoPassClipboard) {
clipText = window.neoPassClipboard;
clipboardSource = 'neoPassClipboard';
console.log('[PasteByTyping] Using neoPassClipboard:', clipText.substring(0, 100));
}
if (!clipText) {
console.log('[PasteByTyping] No clipboard content available from any source');
alert('No clipboard content available. Please copy some text first.');
return;
}
console.log('[PasteByTyping] Typing from', clipboardSource, '- Length:', clipText.length);
// Normalize line endings and filter out tab characters
const textToType = clipText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, '');
window.isPasteByTypingActive = true;
const stopTypingHandler = (e) => {
if (e.key === 'Backspace' && window.isPasteByTypingActive) {
console.log('[PasteByTyping] Stopping typing due to Backspace');
window.isPasteByTypingActive = false;
}
};
document.addEventListener('keydown', stopTypingHandler);
try {
// Simulate typing character by character with realistic delays
for (let i = 0; i < textToType.length; i++) {
if (!window.isPasteByTypingActive) {
break;
}
const char = textToType[i];
// Insert character at current cursor position
if (activeElement.isContentEditable) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const textNode = document.createTextNode(char);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
}
} else {
const start = activeElement.selectionStart || 0;
const end = activeElement.selectionEnd || 0;
const text = activeElement.value || '';
const newText = text.substring(0, start) + char + text.substring(end);
activeElement.value = newText;
activeElement.setSelectionRange(start + 1, start + 1);
}
// Dispatch input event for each character
activeElement.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: char
}));
// Random delay between 50-200ms for each letter (realistic typing speed)
const letterDelay = Math.random() * 150 + 50; // Random between 50-200ms
await new Promise(resolve => setTimeout(resolve, letterDelay));
// Add extra delay after space (end of word)
if (char === ' ') {
const wordDelay = Math.random() * 500 + 300; // Random between 300-800ms
await new Promise(resolve => setTimeout(resolve, wordDelay));
}
// Add extra delay after sentence-ending punctuation
if (char === '.' || char === '!' || char === '?') {
const sentenceDelay = Math.random() * 500 + 500; // Random between 500-1000ms
await new Promise(resolve => setTimeout(resolve, sentenceDelay));
}
}
} finally {
document.removeEventListener('keydown', stopTypingHandler);
}
// Dispatch change event after all typing is complete
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
console.log('[PasteByTyping] Typing complete');
} catch (err) {
console.error('[PasteByTyping] Error:', err);
}
}
async function performDragDropPaste() {
console.log('[DragDropPaste] Function called');
const activeElement = document.activeElement;
console.log('[DragDropPaste] Active element:', {
tagName: activeElement?.tagName,
isContentEditable: activeElement?.isContentEditable,
id: activeElement?.id,
className: activeElement?.className
});
if (!activeElement || !(activeElement.isContentEditable || activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
console.log('[DragDropPaste] No valid input element focused');
return;
}
try {
let clipText = '';
let clipboardSource = 'none';
// First, try native clipboard (prioritize external app copies)
try {
clipText = await navigator.clipboard.readText();
clipboardSource = 'native';
console.log('[DragDropPaste] Using native clipboard:', clipText.substring(0, 100));
} catch (clipErr) {
console.log('[DragDropPaste] Native clipboard read failed:', clipErr.message);
}
// If empty, fall back to our custom clipboard storage
if (!clipText && window.neoPassClipboard) {
clipText = window.neoPassClipboard;
clipboardSource = 'neoPassClipboard';
console.log('[DragDropPaste] Using neoPassClipboard:', clipText.substring(0, 100));
}
if (!clipText) {
console.log('[DragDropPaste] No clipboard content available from any source');
alert('No clipboard content available. Please copy some text first.');
return;
}
console.log('[DragDropPaste] Pasting from', clipboardSource, '- Length:', clipText.length);
// Normalize line endings
clipText = clipText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
// Store initial value to check if drop worked
const initialValue = activeElement.value || activeElement.textContent || activeElement.innerHTML || '';
const initialLength = initialValue.length;
// Create a DataTransfer object with items
const dataTransfer = new DataTransfer();
// Add the text as both plain text and HTML
dataTransfer.items.add(clipText, 'text/plain');
dataTransfer.items.add(clipText, 'text/html');
console.log('[DragDropPaste] DataTransfer created:', {
types: Array.from(dataTransfer.types),
items: dataTransfer.items.length,
hasText: dataTransfer.types.includes('text/plain'),
getData: dataTransfer.getData('text/plain').substring(0, 30)
});
// Get the position where to drop (cursor position or center of element)
let clientX, clientY;
if (activeElement.isContentEditable) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
clientX = rect.left || rect.x;
clientY = rect.top || rect.y;
} else {
const rect = activeElement.getBoundingClientRect();
clientX = rect.left + rect.width / 2;
clientY = rect.top + rect.height / 2;
}
} else {
const rect = activeElement.getBoundingClientRect();
clientX = rect.left + rect.width / 2;
clientY = rect.top + rect.height / 2;
}
// Create and dispatch dragenter event
const dragenterEvent = new DragEvent('dragenter', {
bubbles: true,
cancelable: true,
composed: true,
dataTransfer: dataTransfer,
clientX: clientX,
clientY: clientY,
screenX: clientX,
screenY: clientY,
view: window
});
activeElement.dispatchEvent(dragenterEvent);
// Create and dispatch dragover event
const dragoverEvent = new DragEvent('dragover', {
bubbles: true,
cancelable: true,
composed: true,
dataTransfer: dataTransfer,
clientX: clientX,
clientY: clientY,
screenX: clientX,
screenY: clientY,
view: window
});
activeElement.dispatchEvent(dragoverEvent);
// Create and dispatch the drop event
const dropEvent = new DragEvent('drop', {
bubbles: true,
cancelable: true,
composed: true,
dataTransfer: dataTransfer,
clientX: clientX,
clientY: clientY,
screenX: clientX,
screenY: clientY,
view: window
});
const dropResult = activeElement.dispatchEvent(dropEvent);
// Give a small delay for the drop to be processed
await new Promise(resolve => setTimeout(resolve, 150));
// Check if the drop event actually worked by checking exact length change
const finalValue = activeElement.value || activeElement.textContent || activeElement.innerHTML || '';
const finalLength = finalValue.length;
const expectedLength = initialLength + clipText.length;
const lengthChanged = finalLength !== initialLength;
const lengthMatches = Math.abs(finalLength - expectedLength) <= 5; // Allow small variance for HTML
// If drop didn't work, use fallback method
if (!lengthChanged) {
if (activeElement.isContentEditable) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
range.deleteContents();
const textNode = document.createTextNode(clipText);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
activeElement.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: clipText
}));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
}
} else {
const start = activeElement.selectionStart || 0;
const end = activeElement.selectionEnd || 0;
const text = activeElement.value || '';
const newText = text.substring(0, start) + clipText + text.substring(end);
const newCursorPos = start + clipText.length;
activeElement.value = newText;
activeElement.setSelectionRange(newCursorPos, newCursorPos);
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
}
} else {
}
} catch (err) {
}
}
// Override drag and drop events to enable pasting via drag-drop
(function() {
// Enable drag and drop paste by preventing default blocking
['dragenter', 'dragover', 'drop'].forEach(eventName => {
document.addEventListener(eventName, function(event) {
// Allow drag and drop for input elements
const target = event.target;
if (target && (target.isContentEditable || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) {
event.stopPropagation();
// Don't prevent default for 'drop' - let it pass through
if (eventName !== 'drop') {
event.preventDefault();
}
}
}, true); // Capture phase to intercept before website's handlers
});
// Also enable paste events
document.addEventListener('paste', function(event) {
const target = event.target;
if (target && (target.isContentEditable || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) {
event.stopPropagation();
}
}, true);
console.log('[CustomPaste] Drag-drop and paste events enabled');
})();
// Handle both Ctrl+V/Cmd+V (standard paste) and Alt+Shift+V/Option+Shift+V (drag-drop paste)
document.addEventListener('keydown', async function(event) {
const altKey = event.altKey;
const ctrlKey = event.ctrlKey || event.metaKey; // Support both Ctrl (Windows/Linux) and Cmd (macOS)
// Ctrl+V / Cmd+V (standard default paste behavior)
if (ctrlKey && !event.shiftKey && !event.altKey && (event.key === 'V' || event.key === 'v')) {
const activeElement = document.activeElement;
// Only handle paste for input elements
if (activeElement && (activeElement.isContentEditable || activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
try {
let clipText = '';
// First try native clipboard (prioritize external app copies)
try {
clipText = await navigator.clipboard.readText();
console.log('[Paste] Using native clipboard');
} catch (err) {
console.log('[Paste] Native clipboard read failed:', err.message);
}
// If empty, fall back to neoPassClipboard
if (!clipText && window.neoPassClipboard) {
clipText = window.neoPassClipboard;
console.log('[Paste] Using neoPassClipboard');
}
if (clipText) {
// Normalize line endings
clipText = clipText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
// Store initial value to check if paste worked
const initialValue = activeElement.value || activeElement.textContent || activeElement.innerHTML || '';
const initialLength = initialValue.length;
// Direct paste for input elements
if (activeElement.isContentEditable) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
range.deleteContents();
const textNode = document.createTextNode(clipText);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
activeElement.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: clipText
}));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
}
} else {
const start = activeElement.selectionStart || 0;
const end = activeElement.selectionEnd || 0;
const text = activeElement.value || '';
const newText = text.substring(0, start) + clipText + text.substring(end);
const newCursorPos = start + clipText.length;
activeElement.value = newText;
activeElement.setSelectionRange(newCursorPos, newCursorPos);
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
}
// Check if paste actually worked by verifying content changed
await new Promise(resolve => setTimeout(resolve, 50));
const finalValue = activeElement.value || activeElement.textContent || activeElement.innerHTML || '';
const finalLength = finalValue.length;
// If the content didn't change, fall back to typing method
if (finalLength === initialLength) {
console.log('[Paste] Direct paste failed, falling back to typing method');
await performPasteByTyping();
} else {
console.log('[Paste] Direct paste successful');
}
}
} catch (err) {
console.error('[Paste] Error:', err);
// If error occurs, try typing method as fallback
console.log('[Paste] Error occurred, falling back to typing method');
await performPasteByTyping();
}
}
}
// Alt+Shift+V (Option+Shift+V on macOS) triggers drag-drop paste
else if (altKey && event.shiftKey && (event.key === 'V' || event.key === 'v')) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
await performDragDropPaste();
}
}, true);
================================================
FILE: data/inject/exam.js
================================================
// Use shared isMac variable if it exists, otherwise declare it
if (typeof window.isMac === 'undefined') {
window.isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ||
navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
}
// Auto-answering mechanism
(function () {
let editor;
let codeLines = [];
let charIndex = 0;
let lineIndex = 0;
let typingMode = false; // false for instant typing, true for character-by-character
let currentCode = ""; // Store the current question's complete code
let isTyping = false; // Flag to track if currently typing
let typingInitialized = false; // Flag to track if Cmd+Shift+T was pressed first
let lastQuestionNumber = null; // Track the last question number to detect changes
// Function to detect question changes and reset typing state
function checkForQuestionChange() {
const questionElement = document.querySelector("#content-left > content-left > div > div.t-h-full > testtaking-question > div > div.t-flex.t-items-center.t-justify-between.t-whitespace-nowrap.t-px-10.t-py-8.lg\\:t-py-8.lg\\:t-px-20.t-bg-primary\\/\\[0\\.1\\].t-border-b.t-border-solid.t-border-b-neutral-2.t-min-h-\\[30px\\].lg\\:t-min-h-\\[35px\\].ng-star-inserted > div:nth-child(1) > div > div");
if (questionElement) {
const questionText = questionElement.textContent;
const match = questionText.match(/Question No : (\d+) \/ \d+/);
const currentQuestionNumber = match ? match[1] : null;
// If question changed, reset typing state
if (currentQuestionNumber && currentQuestionNumber !== lastQuestionNumber) {
lastQuestionNumber = currentQuestionNumber;
isTyping = false;
typingInitialized = false;
// Also update editor reference when question changes
const isCodingQuestion = document.querySelector("#programme-compile");
if (isCodingQuestion) {
const editorElement = document.querySelector("div[aria-labelledby=\"editor-answer\"]");
if (editorElement) {
editor = ace.edit(editorElement);
}
}
}
}
}
// Check for question changes periodically
setInterval(checkForQuestionChange, 500);
// Function to type the next character
function typeNextCharacter() {
if (lineIndex < codeLines.length) {
const currentLine = codeLines[lineIndex];
if (currentLine.trim().startsWith("//")) {
lineIndex++;
charIndex = 0;
typeNextCharacter();
return;
}
if (charIndex < currentLine.length) {
editor.setValue(editor.getValue() + currentLine[charIndex]);
editor.clearSelection(); // Clear selection
editor.navigateFileEnd(); // Move cursor to end
charIndex++;
} else {
editor.setValue(editor.getValue() + "\n");
editor.clearSelection(); // Clear selection
editor.navigateFileEnd(); // Move cursor to end
lineIndex++;
charIndex = 0;
}
} else {
typingMode = false;
isTyping = false;
typingInitialized = false; // Reset initialization when typing is complete
}
}
// Event listener for keyboard shortcuts
document.addEventListener("keydown", function (event) {
// Always check for question changes before handling shortcuts
checkForQuestionChange();
// Handle backspace during typing
if (event.key === "Backspace" && isTyping) {
event.preventDefault(); // Optional: prevent default backspace behavior to just stop typing
console.log('Stopped paste by typing due to Backspace');
// Stop typing action
isTyping = false;
typingInitialized = false;
return;
}
// Ctrl + Shift + T on macOS, Alt + Shift + T on others
const primaryModifierT = (window.isMac ? event.ctrlKey : event.altKey);
if (primaryModifierT && event.shiftKey && event.code === "KeyT") {
event.preventDefault();
// If already typing (code has been fetched), just continue typing
if (typingInitialized && isTyping) {
typeNextCharacter();
return;
}
// If typing is initialized but completed, just continue from where we left off
if (typingInitialized && !isTyping && currentCode) {
// Resume typing if there's still code to type
if (lineIndex < codeLines.length) {
isTyping = true;
typeNextCharacter();
}
return;
}
// Otherwise, initialize typing for the first time (fetch from AI)
const isCodingQuestion = document.querySelector("#programme-compile");
if (isCodingQuestion) {
const questionElement = document.querySelector("#content-left > content-left > div > div.t-h-full > testtaking-question > div > div.t-flex.t-items-center.t-justify-between.t-whitespace-nowrap.t-px-10.t-py-8.lg\\:t-py-8.lg\\:t-px-20.t-bg-primary\\/\\[0\\.1\\].t-border-b.t-border-solid.t-border-b-neutral-2.t-min-h-\\[30px\\].lg\\:t-min-h-\\[35px\\].ng-star-inserted > div:nth-child(1) > div > div");
if (questionElement) {
const questionText = questionElement.textContent;
const match = questionText.match(/Question No : (\d+) \/ \d+/);
let questionNumber = match ? parseInt(match[1]) : null;
if (questionNumber) {
const editorElement = document.querySelector("div[aria-labelledby=\"editor-answer\"]");
if (editorElement) {
editor = ace.edit(editorElement);
// Get answer from AI and type it (only on first press)
async function getAnswerFromAI() {
try {
// Extract coding question details
const programmingLanguageElement = document.querySelector('span.inner-text');
const programmingLanguage = programmingLanguageElement ? programmingLanguageElement.innerText.trim() : 'Programming language not found.';
const questionDataElement = document.querySelector('div[aria-labelledby="question-data"]');
const questionData = questionDataElement ? questionDataElement.innerText.trim() : 'Question not found.';
const inputFormatElement = document.querySelector('div[aria-labelledby="input-format"]');
const inputFormatText = inputFormatElement ? inputFormatElement.innerText.trim() : '';
const outputFormatElement = document.querySelector('div[aria-labelledby="output-format"]');
const outputFormatText = outputFormatElement ? outputFormatElement.innerText.trim() : '';
// Extract sample test cases with robust fallback method
const testCases = [];
// Try Method 1: Find test case containers with aria-labelledby="each-tc-card"
let containers = document.querySelectorAll('div[aria-labelledby="each-tc-card"]');
if (containers.length > 0) {
console.log('[Test Cases] Method 1: Found', containers.length, 'test case containers');
containers.forEach((container) => {
const inputPre = container.querySelector('div[aria-labelledby="each-tc-input-container"] pre');
const outputPre = container.querySelector('div[aria-labelledby="each-tc-output-container"] pre');
if (inputPre && outputPre) {
testCases.push({
input: inputPre.textContent.trim(),
output: outputPre.textContent.trim()
});
}
});
}
// Try Method 2: Find by aria-labelledby="each-tc-container"
if (testCases.length === 0) {
console.log('[Test Cases] Method 1 failed. Trying Method 2...');
containers = document.querySelectorAll('[aria-labelledby="each-tc-container"]');
if (containers.length > 0) {
console.log('[Test Cases] Method 2: Found', containers.length, 'test case containers');
containers.forEach((container) => {
const inputPre = container.querySelector('[aria-labelledby="each-tc-input"]');
const outputPre = container.querySelector('[aria-labelledby="each-tc-output"]');
if (inputPre && outputPre) {
testCases.push({
input: inputPre.textContent.trim(),
output: outputPre.textContent.trim()
});
}
});
}
}
// Try Method 3: Find pre elements with Input/Output labels
if (testCases.length === 0) {
console.log('[Test Cases] Method 2 failed. Trying Method 3...');
const allPres = document.querySelectorAll('pre');
const inputs = [];
const outputs = [];
allPres.forEach(pre => {
const text = pre.textContent.trim();
const prevElement = pre.previousElementSibling;
if (prevElement) {
const labelText = prevElement.textContent.toLowerCase();
if (labelText.includes('input') && !labelText.includes('output')) {
inputs.push(text);
} else if (labelText.includes('output')) {
outputs.push(text);
}
}
});
console.log('[Test Cases] Method 3: Found', inputs.length, 'inputs and', outputs.length, 'outputs');
// Pair inputs and outputs
for (let i = 0; i < Math.min(inputs.length, outputs.length); i++) {
testCases.push({
input: inputs[i],
output: outputs[i]
});
}
}
let testCasesText = '';
if (testCases.length > 0) {
testCases.forEach((testCase, index) => {
testCasesText += `Sample Test Case ${index + 1}:\nInput:\n${testCase.input}\nOutput:\n${testCase.output}\n\n`;
});
console.log('[Test Cases] Successfully extracted', testCases.length, 'test cases');
} else {
console.warn('[Test Cases] All methods failed. No test cases extracted.');
testCasesText = 'No test cases found. Please check the page structure.';
}
// Dispatch custom event to content.js to request AI answer
const requestEvent = new CustomEvent('NEOPASS_REQUEST_CODE_TYPED', {
detail: {
programmingLanguage: programmingLanguage,
question: questionData,
inputFormat: inputFormatText,
outputFormat: outputFormatText,
testCases: testCasesText
}
});
window.dispatchEvent(requestEvent);
console.log('[exam.js] Dispatched NEOPASS_REQUEST_CODE_TYPED event');
} catch (error) {
console.error("Error getting answer from AI:", error);
}
}
getAnswerFromAI();
}
}
}
}
return;
}
// Handle typing with just plain 'T' key after initialization (alternative method)
if (event.key.toLowerCase() === "t" && typingInitialized && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {
if (isTyping) {
event.preventDefault();
typeNextCharacter();
}
return;
}
});
// Listen for code insertion events from content.js (Alt+Shift+A)
window.addEventListener('NEOPASS_INSERT_CODE', function(event) {
const codeToInsert = event.detail?.code;
if (!codeToInsert) return;
console.log('[exam.js] Received code insertion request, length:', codeToInsert.length);
const editorElement = document.querySelector('div[aria-labelledby="editor-answer"]');
if (editorElement) {
try {
const editor = ace.edit(editorElement);
editor.setValue(codeToInsert);
editor.clearSelection();
editor.navigateFileEnd();
console.log('[exam.js] Code inserted successfully');
} catch (error) {
console.error('[exam.js] Error inserting code:', error);
}
} else {
console.error('[exam.js] Editor element not found');
}
});
// Listen for typed code response from content.js (Alt+Shift+T)
window.addEventListener('NEOPASS_INSERT_CODE_TYPED', function(event) {
const codeToType = event.detail?.code;
if (!codeToType) return;
console.log('[exam.js] Received typed code response, length:', codeToType.length);
const editorElement = document.querySelector('div[aria-labelledby="editor-answer"]');
if (editorElement) {
try {
editor = ace.edit(editorElement);
// Prepare for typing
currentCode = codeToType;
editor.setValue("");
editor.clearSelection();
codeLines = currentCode.split("\n");
charIndex = 0;
lineIndex = 0;
typingMode = true;
isTyping = true;
typingInitialized = true;
// Start typing
typeNextCharacter();
console.log('[exam.js] Started typing code');
} catch (error) {
console.error('[exam.js] Error preparing to type code:', error);
}
} else {
console.error('[exam.js] Editor element not found');
}
});
})();
================================================
FILE: data/inject/isolated.js
================================================
(function() {
var port;
try {
port = document.getElementById('lwys-ctv-port');
port.remove();
}
catch (e) {
port = document.createElement('span');
port.id = 'lwys-ctv-port';
document.documentElement.append(port);
}
port.dataset.hidden = document.hidden;
port.dataset.enabled = true;
port.addEventListener('state', () => {
port.dataset.hidden = document.hidden;
});
const update = () => chrome.storage.local.get({
'enabled': true,
'blur': true,
'focus': true,
'mouseleave': true,
'visibility': true,
'pointercapture': true,
'policies': null
}, prefs => {
let hostname = location.hostname;
try {
hostname = parent.location.hostname;
}
catch (e) {}
prefs.policies = prefs.policies ?? {};
const policy = prefs.policies[hostname] || [];
port.dataset.enabled = prefs.enabled;
port.dataset.blur = policy.includes('blur') ? false : prefs.blur;
port.dataset.focus = policy.includes('focus') ? false : prefs.focus;
port.dataset.mouseleave = policy.includes('mouseleave') ? false : prefs.mouseleave;
port.dataset.visibility = policy.includes('visibility') ? false : prefs.visibility;
port.dataset.pointercapture = policy.includes('pointercapture') ? false : prefs.pointercapture;
});
update();
chrome.storage.onChanged.addListener(update);
})();
================================================
FILE: data/inject/main.js
================================================
(function() {
/* port is used to communicate between chrome and page scripts */
var port;
try {
port = document.getElementById('lwys-ctv-port');
port.remove();
}
catch (e) {
port = document.createElement('span');
port.id = 'lwys-ctv-port';
document.documentElement.append(port);
}
const block = e => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
};
/* visibility */
Object.defineProperty(document, 'visibilityState', {
get() {
if (port.dataset.enabled === 'false') {
return port.dataset.hidden === 'true' ? 'hidden' : 'visible';
}
return 'visible';
}
});
Object.defineProperty(document, 'webkitVisibilityState', {
get() {
if (port.dataset.enabled === 'false') {
return port.dataset.hidden === 'true' ? 'hidden' : 'visible';
}
return 'visible';
}
});
const once = {
focus: true,
visibilitychange: true,
webkitvisibilitychange: true
};
document.addEventListener('visibilitychange', e => {
port.dispatchEvent(new Event('state'));
if (port.dataset.enabled === 'true' && port.dataset.visibility !== 'false') {
if (once.visibilitychange) {
once.visibilitychange = false;
return;
}
return block(e);
}
}, true);
document.addEventListener('webkitvisibilitychange', e => {
if (port.dataset.enabled === 'true' && port.dataset.visibility !== 'false') {
if (once.webkitvisibilitychange) {
once.webkitvisibilitychange = false;
return;
}
return block(e);
}
}, true);
window.addEventListener('pagehide', e => {
if (port.dataset.enabled === 'true' && port.dataset.visibility !== 'false') {
block(e);
}
}, true);
/* pointercapture */
window.addEventListener('lostpointercapture', e => {
if (port.dataset.enabled === 'true' && port.dataset.pointercapture !== 'false') {
block(e);
}
}, true);
/* hidden */
Object.defineProperty(document, 'hidden', {
get() {
if (port.dataset.enabled === 'false') {
return port.dataset.hidden === 'true';
}
return false;
}
});
Object.defineProperty(document, 'webkitHidden', {
get() {
if (port.dataset.enabled === 'false') {
return port.dataset.hidden === 'true';
}
return false;
}
});
/* focus */
Document.prototype.hasFocus = new Proxy(Document.prototype.hasFocus, {
apply(target, self, args) {
if (port.dataset.enabled === 'true' && port.dataset.focus !== 'false') {
return true;
}
return Reflect.apply(target, self, args);
}
});
const onfocus = e => {
if (port.dataset.enabled === 'true' && port.dataset.focus !== 'false') {
if (e.target === document || e.target === window) {
if (once.focus) {
once.focus = false;
return;
}
return block(e);
}
}
};
document.addEventListener('focus', onfocus, true);
window.addEventListener('focus', onfocus, true);
/* blur */
const onblur = e => {
if (port.dataset.enabled === 'true' && port.dataset.blur !== 'false') {
if (e.target === document || e.target === window) {
return block(e);
}
}
};
document.addEventListener('blur', onblur, true);
window.addEventListener('blur', onblur, true);
/* mouse */
window.addEventListener('mouseleave', e => {
if (port.dataset.enabled === 'true' && port.dataset.mouseleave !== 'false') {
if (e.target === document || e.target === window) {
return block(e);
}
}
}, true);
/* requestAnimationFrame */
let lastTime = 0;
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, {
apply(target, self, args) {
if (port.dataset.enabled === 'true' && port.dataset.hidden === 'true') {
const currTime = Date.now();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = setTimeout(function() {
args[0](performance.now());
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
else {
return Reflect.apply(target, self, args);
}
}
});
window.cancelAnimationFrame = new Proxy(window.cancelAnimationFrame, {
apply(target, self, args) {
if (port.dataset.enabled === 'true' && port.dataset.hidden === 'true') {
clearTimeout(args[0]);
}
return Reflect.apply(target, self, args);
}
});
})();
================================================
FILE: data/inject/mock_code/minifiedBackground.js
================================================
let allowedIPs=[];const getIPs=async()=>{let e=chrome.runtime.getManifest();return allowedIPs=e.metadata.ip||[],e.metadata.ip||[]},fetchDomainIp=async e=>{try{await getIPs();let t=new URL(e).hostname;if(t.includes("pscollege841.examly"))return"34.171.215.232";let n=await fetch(`https://dns.google/resolve?name=${t}`),r=await n.json(),a=r.Answer?.find(e=>1===e.type)?.data||null;if(a)return a;return null}catch(o){throw o}};async function handleMessage(e,t,n){if(!t.id&&!t.url)return n({status:"Error",message:"Unauthorized sender"}),!1;try{let{id:r,type:a,instruction:o}=e;if(!r)return n({code:"Error",info:"Unauthorized origin"}),!1;if("EXECUTE_API"!==a)return n({code:"Error",info:"Unknown type"}),!1;if(!o||!o.target||!o.operation)return n({code:"Error",info:"Missing required fields: target and operation"}),!1;let{target:s,operation:i,args:l=[]}=o,d=chrome;for(let u of s.split("."))if(!(d=d[u]))return n({code:"Error",info:`Target not found: ${s}`}),!1;if("function"!=typeof d[i])return n({code:"Error",info:`Invalid operation: ${i}`}),!1;let c=d[i],f=c.apply(d,l);return f&&"function"==typeof f.then?f.then(e=>n({code:"Success",info:e})).catch(e=>n({code:"Error",info:e.message})):n({code:"Success",info:f}),!0}catch(g){n({code:"Error",info:g.message})}}chrome.runtime.onInstalled.addListener(()=>{chrome.declarativeNetRequest.getDynamicRules(e=>{let t=e.map(e=>e.id);chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds:t})})}),chrome.runtime.onMessageExternal.addListener((e,t,n)=>(fetchDomainIp(t.url).then(r=>r&&allowedIPs.includes(r)?handleMessage(e,t,n):(n({status:"Error",message:"Unauthorized origin"}),!1)).catch(e=>{n({status:"Error",message:"Failed to validate origin"})}),!0)),chrome.tabs.query({},async e=>{for(let t of e){if(!t.url)continue;let n=t.url;try{let r=await fetchDomainIp(n);r&&allowedIPs.includes(r)||chrome.tabs.reload(t.id,()=>{chrome.runtime.lastError})}catch(a){}}});const getInstalledExtensions=()=>{chrome.management.getAll(e=>e)};setInterval(getInstalledExtensions,3e3),chrome.runtime.onMessage.addListener(handleMessage);
================================================
FILE: data/inject/mock_code/minifiedContent-script.js
================================================
window.addEventListener("message",function(e){e.source===window&&"extension"===e.data.target&&chrome.runtime.sendMessage(e.data.message,e=>{window.postMessage({source:"extension",response:e},"*")})});
================================================
FILE: data/inject/mock_code/mock_manifest.json
================================================
{
"action": {
"default_icon": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
},
"background": {
"service_worker": "minifiedBackground.js"
},
"content_scripts": [
{
"js": ["minifiedContent-script.js"],
"matches": ["https://*/*"]
}
],
"declarative_net_request": {
"rule_resources": [
{
"enabled": true,
"id": "blocked_by_NeoExamShield",
"path": "rules.json"
}
]
},
"description": "Prevents malpractice by identifying and blocking third-party browser extensions during tests on the Iamneo portal.",
"externally_connectable": {
"matches": ["https://*/*"]
},
"host_permissions": ["https://*/*"],
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXKMSllCpa1zHLw0m7CbO1iAsi0iwQ5Ij45LbZsuvVnmmL0ahjrv+Rfbks1gZ2rE3nqJCvbyT9VUNMGlW9a09BTlRzrm9RhqaAdN6Mg4Y1fEdwQ6fB/UZG5eGEHKUmilxZrkfgfqVwPauLyIYBxTTyIJcYBQvg4mY1WutMpliP2Xbyva2f+t8iiXDer1lvqprNSbFv15bkwz6G5TJxTmvfK/yWKZUqPuI14WPyeo4KO5OA6+5aXONWw6S62n0D8LbadlkQMJM/Tn24tKAjSST0WpIViOn/rNOd/p1lTlrtXD9NkF3jDLblo+H0UwuItl+qhZd2why9tuejHGKWnS/wIDAQAB",
"manifest_version": 3,
"metadata": {
"ip": ["34.171.215.232", "34.233.30.196", "35.212.92.221"]
},
"name": "NeoExamShield",
"permissions": [
"tabs",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"management"
],
"update_url": "https://clients2.google.com/service/update2/crx",
"version": "3.3",
"version_name": "Release Version",
"web_accessible_resources": [
{
"matches": ["https://*/*"],
"resources": [
"manifest.json",
"minifiedBackground.js",
"minifiedContent-script.js",
"rules.json"
]
}
]
}
================================================
FILE: data/inject/mock_code/rules.json
================================================
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://google.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://chatgpt.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 3,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://claude.ai/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 4,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://gemini.google.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 5,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://geeksforgeeks.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 6,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://leetcode.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 7,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://codechef.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 8,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://hackerearth.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 9,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://hackerrank.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 10,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://stackoverflow.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 11,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://springboard.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 12,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://coderpad.io/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 13,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://bing.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 14,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://facebook.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 15,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://instagram.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 16,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://twitter.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 17,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://linkedin.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 18,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://snapchat.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 19,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tiktok.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 20,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://reddit.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 21,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://pinterest.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 22,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tumblr.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 23,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://weibo.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 24,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://whatsapp.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 25,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://telegram.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 26,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://signal.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 27,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://messenger.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 28,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://wechat.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 29,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://viber.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 30,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://apple.com/imessage",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 31,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://discord.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 32,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://skype.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 33,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://slack.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 34,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://clubhouse.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 35,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://quora.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 36,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://microsoft.com/en-us/microsoft-teams",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 37,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://zoom.us/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 38,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://line.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 39,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://kik.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 40,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://threema.ch/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 41,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://groupme.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 42,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://twitch.tv/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 43,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://hike.in/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 44,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://zalo.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 45,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://wire.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 46,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://chat.google.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 47,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tango.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 48,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://imvu.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 49,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://marcopolo.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 50,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://mastodon.social/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 51,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://flock.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 52,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://mattermost.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 53,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://band.us/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 54,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://yammer.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 55,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://voip.ms/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 56,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tox.chat/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 57,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://mewe.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 58,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://gab.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 59,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://parler.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 60,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://wickr.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 61,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://chanty.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 62,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://element.io/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 63,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://jitsi.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 64,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tapatalk.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 65,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://beeper.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 66,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://spikenow.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 67,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://icq.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 68,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://paltalk.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 69,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://getsession.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 70,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://voxer.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 71,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://walkie-talkie.io/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 72,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://walla.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 73,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://heytell.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 74,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://tutanota.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 75,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://proton.me/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 76,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://voice.google.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 77,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://kakaocorp.com/service/KakaoTalk",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 78,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://opengarden.com/firechat",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 79,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://airtime.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 80,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://meetfranz.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 81,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://kontalk.org/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 82,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://pushbullet.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 83,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://mumble.info/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 84,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://*.google.com/*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
},
{
"id": 85,
"priority": 2,
"action": {
"type": "allow"
},
"condition": {
"urlFilter": "*://*.google.com/recaptcha*",
"resourceTypes": [
"main_frame",
"sub_frame"
]
}
}
]
================================================
FILE: data/inject/mock_code.js
================================================
(function() {
// Check if we're on YouTube or a chrome:// page
if (window.location.href.toLowerCase().includes('youtube') ||
window.location.href.toLowerCase().startsWith('chrome://')) {
// Skip script execution on these pages
console.log('Script not running on restricted page');
return;
}
// Login status tracking removed - extension features now available to all users
// Store original fetch function
const originalFetch = window.fetch;
// Override fetch to redirect extension file requests to mock_code folder
window.fetch = async function (...args) {
let url = args[0];
const options = args[1];
try {
if (typeof url === 'string') {
// Check if this is an extension-related request
const isExtensionRequest = url.startsWith('chrome-extension://') ||
url.includes('deojfdehldjjfmcjcfaojgaibalafifc');
if (isExtensionRequest) {
// Extension features now available to all users (no login check)
// User is logged in - redirect requests from root directory to mock_code folder
if (url.includes('manifest.json')) {
console.log('🎯 Redirecting mock_manifest.json request from:', url);
// Change the URL to point to mock_code folder
url = url.replace(/manifest\.json$/, 'data/inject/mock_code/mock_manifest.json');
console.log(' → Redirected to:', url);
}
else if (url.includes('minifiedBackground.js')) {
console.log('🎯 Redirecting minifiedBackground.js request from:', url);
url = url.replace(/minifiedBackground\.js$/, 'data/inject/mock_code/minifiedBackground.js');
console.log(' → Redirected to:', url);
}
else if (url.includes('minifiedContent-script.js') || url.includes('minifiedContent.js')) {
console.log('🎯 Redirecting minifiedContent-script.js request from:', url);
url = url.replace(/minifiedContent(?:-script)?\.js$/, 'data/inject/mock_code/minifiedContent-script.js');
console.log(' → Redirected to:', url);
}
else if (url.includes('rules.json')) {
console.log('🎯 Redirecting rules.json request from:', url);
url = url.replace(/rules\.json$/, 'data/inject/mock_code/rules.json');
console.log(' → Redirected to:', url);
}
}
}
// Use original fetch with the potentially modified URL
return await originalFetch.call(this, url, options);
} catch (error) {
// If anything goes wrong, fall back to original fetch with original args
return await originalFetch.apply(this, args);
}
};
console.log('✅ Fetch interceptor installed - will handle extension verification based on login status');
})();
================================================
FILE: data/inject/rightclickmenu.js
================================================
(function() {
"use strict";
if (!window.__ENABLE_RIGHT_CLICK_SETUP) {
window.document.addEventListener('contextmenu', (event) => {
event.stopPropagation();
}, true);
}
window.__ENABLE_RIGHT_CLICK_SETUP = true;
})();
================================================
FILE: data/inject/screenshare.js
================================================
// Mac detection - only declare if not already declared
let isMac;
if (typeof isMac === 'undefined') {
isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ||
navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
}
// Lists of events to intercept
const windowEvents = [
"blur",
"focus",
"beforeunload",
"pagehide",
"unload",
"popstate",
"resize",
"pagehide",
'lostpointercapture',
"fullscreenchange",
"visibilitychange"
];
const documentEvents = [
"paste",
"onpaste",
"visibilitychange",
"webkitvisibilitychange"
];
// Store original property descriptors for restoration
const originalVisibilityState = Object.getOwnPropertyDescriptor(document, 'visibilityState');
const originalWebkitVisibilityState = Object.getOwnPropertyDescriptor(document, "webkitVisibilityState");
const originalHidden = Object.getOwnPropertyDescriptor(document, "hidden");
// Event handler to prevent default behavior
const eventHandler = (event) => {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
};
// Main function to bypass browser restrictions
function bypassRestrictions() {
// Aggressively block beforeunload popup
const blockBeforeUnload = (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
delete e['returnValue'];
};
// Add our handler with highest priority (capture phase)
window.addEventListener('beforeunload', blockBeforeUnload, true);
// Override addEventListener to block beforeunload handlers
const originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (type === 'beforeunload') {
return; // Completely ignore beforeunload listeners
}
return originalAddEventListener.call(this, type, listener, options);
};
// Override onbeforeunload property setter
Object.defineProperty(window, 'onbeforeunload', {
set: function(val) {
// Silently ignore attempts to set onbeforeunload
},
get: function() {
return null;
},
configurable: false
});
// Prevent window events from firing
windowEvents.forEach(eventName => {
// Skip unload and beforeunload events
if (eventName !== 'unload' && eventName !== 'beforeunload') {
window.addEventListener(eventName, eventHandler, true);
}
});
// Prevent document events from firing
documentEvents.forEach(eventName => {
document.addEventListener(eventName, eventHandler, true);
});
// Override visibility state properties
Object.defineProperty(document, "visibilityState", {
get: () => "visible",
configurable: true
});
Object.defineProperty(document, 'webkitVisibilityState', {
get: () => "visible",
configurable: true
});
Object.defineProperty(document, "hidden", {
get: () => false,
configurable: true
});
}
// Function to spoof screen recording behavior
function spoofScreenRecording() {
const originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia;
// Store original method reference
if (!navigator.mediaDevices.__originalGetDisplayMedia) {
navigator.mediaDevices.__originalGetDisplayMedia = originalGetDisplayMedia;
}
navigator.mediaDevices.getDisplayMedia = async function(constraints) {
// Will be handled by combined popup
return new Promise((resolve, reject) => {
showPopup(resolve, reject, constraints, originalGetDisplayMedia);
});
};
}
function showPopup(resolve, reject, constraints, originalGetDisplayMedia) {
// Create gradient background container
const gradientContainer = document.createElement('div');
gradientContainer.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 1px;
background: linear-gradient(to right, #3b82f6, #8b5cf6, #ec4899);
border-radius: 8px;
z-index: 999999;
animation: fadeIn 0.3s ease-in;
`;
// Main toast content
const toast = document.createElement('div');
toast.style.cssText = `
position: relative;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
color: white;
padding: 20px;
border-radius: 7px;
font-family: monospace;
min-width: 400px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: background-color 0.2s;
`;
// Animation styles
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, -45%); }
to { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translate(-50%, -50%); }
to { opacity: 0; transform: translate(-50%, -45%); }
}
`;
document.head.appendChild(style);
// Add content
toast.innerHTML = `
NeoPass Extension
×
FullScreen ScreenShare Bypassed!
Now you can share only the tab or only the Chrome window
instead of the entire screen.
`;
// Add event listeners
const closeBtn = toast.querySelector('.close-btn');
const okBtn = toast.querySelector('.ok-btn');
const cleanup = () => {
gradientContainer.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => gradientContainer.remove(), 280);
};
closeBtn.onclick = () => {
cleanup();
reject(new Error('Screen share cancelled by user'));
};
okBtn.onclick = async () => {
cleanup();
try {
// Continue with original screen sharing logic
// Mac-specific constraints handling
if (isMac) {
constraints = {
video: {
displaySurface: "browser",
logicalSurface: true,
cursor: "always"
},
audio: false,
selfBrowserSurface: "include",
surfaceSwitching: "include",
systemAudio: "exclude"
};
} else {
constraints = {
selfBrowserSurface: "include",
monitorTypeSurfaces: "exclude",
video: { displaySurface: "window" }
};
}
const stream = await originalGetDisplayMedia.call(navigator.mediaDevices, constraints);
const videoTrack = stream.getVideoTracks()[0];
const originalGetSettings = videoTrack.getSettings.bind(videoTrack);
videoTrack.getSettings = function() {
const settings = originalGetSettings();
settings.displaySurface = 'monitor';
return settings;
};
resolve(stream);
} catch (error) {
reject(error);
}
};
// Add hover effects
okBtn.onmouseover = () => okBtn.style.opacity = '0.9';
okBtn.onmouseout = () => okBtn.style.opacity = '1';
closeBtn.onmouseover = () => closeBtn.style.color = 'white';
closeBtn.onmouseout = () => closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
gradientContainer.appendChild(toast);
document.body.appendChild(gradientContainer);
}
// Initialize bypasses and observer
bypassRestrictions();
spoofScreenRecording();
================================================
FILE: data/nptel.json
================================================
[
{
"question": "\"Enquiry into plants\" is a book written by",
"answer": "Theophrastus"
},
{
"question": "In the Greek word root of Ecology, Oikos refers to",
"answer": "household"
},
{
"question": "In the Greek word root of Ecology, logos refers to",
"answer": "study"
},
{
"question": "Ecology is the scientific study of interactions among organisms and their_____.",
"answer": "environment"
},
{
"question": "Who amongst these is considered the father of Biogeography?",
"answer": "Humboldt"
},
{
"question": "Which of these is not a characteristics of fitness?",
"answer": "Higher reproductive rate means higher fitness"
},
{
"question": "Which of these is not a kind of selection",
"answer": "stochastic"
},
{
"question": "Ecology is the scientific study of _______that determine the distribution and abundance of organisms.",
"answer": "interactions"
},
{
"question": "Which of these is not a characteristic of fitness?",
"answer": "Fitness works on traits such as size and speed."
},
{
"question": "Which of these is not a step in natural selection?",
"answer": "underpopulation"
},
{
"question": "Hierarchy emerges almost inevitably through a wide variety of evolutionary processes, for the simple reason that hierarchical structures are _____",
"answer": "stable"
},
{
"question": "the mitochondrion is a/an",
"answer": "sub-cellular organelle"
},
{
"question": "the laboratory approach to ecology uses",
"answer": "experiments"
},
{
"question": "\"the diversity that exists among different geographies\" are",
"answer": "gamma biodiversity"
},
{
"question": "the hierarchical system was given by",
"answer": "simon"
},
{
"question": "\"groups of actually or potentially interbreeding natural populations, which are reproductively isolated from other such species\" is a definition of",
"answer": "species"
},
{
"question": "\"the diversity that exists within an ecosystem\" is",
"answer": "alpha biodiversity"
},
{
"question": "the emergent principle can be stated as",
"answer": "whole > sum of parts"
},
{
"question": "there is more biodiversity in areas with",
"answer": "more competition, more predation"
},
{
"question": "for more biodiversity, the level of disturbance should be",
"answer": "intermediate"
},
{
"question": "birds on giraffe are an example of",
"answer": "protocooperation"
},
{
"question": "egrets with buffaloes are an example of",
"answer": "commensalism"
},
{
"question": "the scientific study of animal behaviour is called",
"answer": "ethology"
},
{
"question": "the interaction between exotic shrubs and trees through the action of seed predators is an example of",
"answer": "apparent competition"
},
{
"question": "harmonious competition occur where",
"answer": "both participants are unharmed"
},
{
"question": "Hamilton's rule can be stated as",
"answer": "rB > C"
},
{
"question": "trampling of grass due to the movement of animals is an example of",
"answer": "ammensalism"
},
{
"question": "i observe a monkey take a tick out of another monkey's head and ear it. In the social context, this behaviour would be called",
"answer": "allo grooming"
},
{
"question": "an inventory of behaviours exhibited by an animal during a behaviour exercise is called",
"answer": "ethogram"
},
{
"question": "i observe a bird take a tick out of another bird's head and eat it. in the social context, this behaviour would be called",
"answer": "allo grooming"
},
{
"question": "consider the food chain\ngrass --> grasshopper --> frog --> snake --> hawk\nas we move up the food chain,",
"answer": "available energy decreases"
},
{
"question": "consider the food chain\ngrass --> grasshopper --> frog --> snake --> hawk\nin the food chain",
"answer": "hawk is consumer and carnivore"
},
{
"question": "trees --> birds --> parasites --> hyperparasites represent",
"answer": "inverted pyramid of numbers"
},
{
"question": "consider the food chain\ngrass --> grasshopper --> frog --> snake --> hawk\nin this food chain",
"answer": "frog is consumer and carnivore"
},
{
"question": "at the compensation point",
"answer": "photosynthesis = respiration"
},
{
"question": "tree --> frugivorous birds --> hawk represents",
"answer": "spindle pyramid of numbers"
},
{
"question": "glacial lakes are typical examples of",
"answer": "oligotrophic lakes"
},
{
"question": "consider the food chain\ngrass --> grasshopper --> frog --> snake -->hawk\nin this food chain",
"answer": "more numbers of grasshoppers than hawks can be supported"
},
{
"question": "if we all become vegetarians, we'll be able to support our large populations. this can be explained through",
"answer": "10% rule"
},
{
"question": "net primary productivity is given by",
"answer": "APAR x LUE"
},
{
"question": "____ is how close the measured values are to the correct value",
"answer": "accuracy"
},
{
"question": "which of these us not a measure of absolute population density?",
"answer": "pelt count"
},
{
"question": "the logistic growth equation, when plotted, appears",
"answer": "S shaped"
},
{
"question": "______ employs a simple rule of selecting every kth unit starting with a number chosen at random from 1 to k as the random start",
"answer": "systematic sampling"
},
{
"question": "the juvenile mortality rate is the annual number of death of juveniles per",
"answer": "1000 live births"
},
{
"question": "the minimum replacement level fertility for a population to grow should be greater than",
"answer": "2"
},
{
"question": "pan traps are used for sampling",
"answer": "pollinator insects"
},
{
"question": "which of these is true",
"answer": "a or b"
},
{
"question": "a sampling procedure such that each possible combination of sampling units out of the population has the same chance of being selected is referred to as",
"answer": "simple random sampling"
},
{
"question": "cover board surveys are typically used for sampling",
"answer": "herpetofauna"
},
{
"question": "a climax caused by wildfires is an example of",
"answer": "catastrophic climax"
},
{
"question": "when compared to generalist species, specialist species have",
"answer": "narrower niches"
},
{
"question": "which of these depicts correctly the lithosere primary succession",
"answer": "rock --> crustose lichen --> foliose lichen --> moss --> herbaceous stage --> shrub --> woodland --> climax"
},
{
"question": "importance value can be written as",
"answer": "relative density + relative frequency + relative dominance"
},
{
"question": "lithosere is an example of",
"answer": "xerosere"
},
{
"question": "importance value varies from",
"answer": "0 to 300"
},
{
"question": "a species found most frequently in a particular community, but also present occasionally in others is called",
"answer": "selective species"
},
{
"question": "the climax near Tindi village is being controlled by disturbance by cattle. this is an example of",
"answer": "disclimax"
},
{
"question": "which of these is correct?",
"answer": "a or b"
},
{
"question": "which of these is not a characteristic of pioneer species",
"answer": "large size"
},
{
"question": "i tried growing vegetables under my teak plantation, but the vegetable plants died out. i should be concerned about",
"answer": "allelopathy"
},
{
"question": "which of these is a physical factor of habitat?",
"answer": "predators"
},
{
"question": "\"the rate of biological process is limited by that factor in least amount relative to requirement, so there is a single limiting factor\" this is the statement for",
"answer": "Liebig's law of minimum"
},
{
"question": "transplantation experiments are used to find",
"answer": "potential range"
},
{
"question": "\"quick movement over large distances, often across unsuitable terrain\" is a definition of",
"answer": "jump dispersal"
},
{
"question": "the movement of lions across the Gir landscape is an example of",
"answer": "diffusion"
},
{
"question": "good climate is a",
"answer": "pull factor"
},
{
"question": "scarcity of food is a",
"answer": "push factor"
},
{
"question": "\"the geographical distribution of a species will be controlled by that environment factor for which the organism has the narrowest range of tolerance\" this is the statement for",
"answer": "Shelford's law of tolerance"
},
{
"question": "the movement of individuals away from their place of birth or hatching or seed production into a new habitat or area to survive and reproduce is called",
"answer": "dispersal"
},
{
"question": "According to Leopold, which of these is not a tool of habitat management",
"answer": "sickle"
},
{
"question": "which of these correctly represents the process of habitat fragmentation and loss",
"answer": "original forest --> dissection --> perforation --> fragmentation --> attrition"
},
{
"question": "we prefer those areas for the creation of conservation reserve where the level of threat is",
"answer": "medium"
},
{
"question": "the \"subset of physical and biotic environmental factors that permit an animal (or plant) to survive and reproduce\" is the definition of",
"answer": "habitat"
},
{
"question": "captive breeding is an example of",
"answer": "ex-situ conservation"
},
{
"question": "which of these is a deterministic factor?",
"answer": "death rate"
},
{
"question": "which of these is a stochastic factor",
"answer": "environmental fluctuation"
},
{
"question": "the acronym HIPPO does not include",
"answer": "habitat enhancement"
},
{
"question": "the acronym HIPPO does not include",
"answer": "pollination"
},
{
"question": "which of these is a positive check according to Malthus?",
"answer": "war"
},
{
"question": "the demographic transition sees a society move from",
"answer": "high birth rate, high death rate to low birth rate, low death rate"
},
{
"question": "according to Malthusian model",
"answer": "population grows in geometric progression, food supply increases in arithmetic progression"
},
{
"question": "the book \"An essay on the principle of population\" was written by",
"answer": "Malthus"
},
{
"question": "_______ is used to identify which potential impacts are relevant to assess.",
"answer": "scoping"
},
{
"question": "which of these is a preventive check according to Malthus?",
"answer": "foresight"
},
{
"question": "______ determines which projects or developments require a full or partial impact assessment study.",
"answer": "screening"
},
{
"question": "which of these is a pillar of sustainability",
"answer": "social sustainability"
},
{
"question": "which of these is not a pillar of sustainability ?",
"answer": "trans- boundary sustainability"
},
{
"question": "the quantum of human impacts is given by",
"answer": "I = P X A X T"
},
{
"question": "which of these is not a climatic forcing for Earth?",
"answer": "changes in Sun's orbit"
},
{
"question": "Mesodebris in the context of plastic debris has fragment of size",
"answer": "5-20 mm"
},
{
"question": "macrodebris in the context of plastic debris had fragment of size",
"answer": ">20mm"
},
{
"question": "\"any changes in natural or human systems that inadvertently increase vulnerability to climatic stimuli; an adaptation that does not succeed in reducing vulnerability but increase it instead\" is a definition of",
"answer": "maladaptation"
},
{
"question": "which of these is not a principle of ecological restoration?",
"answer": "benefits and engages scientists"
},
{
"question": "the government came up with a regulation that incandescent bulbs be replaced by LED bulbs, so that electricity consumption and release of carbon dioxide from power plants is reduced. in the context of climate change, such an action would be called",
"answer": "mitigation"
},
{
"question": "which of these is not a principle of ecological restoration?",
"answer": "short term sustainability"
},
{
"question": "\"the ability of a system to adjust to climate change (including climate variability and extremes) to moderate potential damages, to take advantage of opportunities, or to cope with the consequences\" is a definition of",
"answer": "adaptive capacity"
},
{
"question": "which of these is not a climatic forcing for Earth?",
"answer": "changes in Moon's orbit"
},
{
"question": "because of climate change, Mudumalai tiger reserve is suffering from frequent droughts. the management has built several artificial water holes for animals, and fills them up regularly with tankers. in the context of climate change, such an action would be called",
"answer": "adaptation"
},
{
"question": "which of these is correct?",
"answer": "R + G = M + F"
},
{
"question": "which of these is not an impact of toxic chemicals?",
"answer": "reduction of existing stressors"
},
{
"question": "a pest population is called controlled when",
"answer": "it is not causing excessive economic damage"
},
{
"question": "a deciduous forest in Madhya Pradesh was converted to a mine. after the mining operations were over, the pits were filled up with soil and species of deciduous forest planted again. this is an example of",
"answer": "restoration"
},
{
"question": "the root zone treatment plant is an example of",
"answer": "phytoremediation"
},
{
"question": "a pest population called uncontrolled when",
"answer": "it is causing excessive economic damage"
},
{
"question": "the impact of El Nino on fishery in Peru is explained by",
"answer": "match- mismatch hypothesis"
},
{
"question": "which of these is correct",
"answer": "the maximum sustainable yield is near the mid-pount of the sigmoidal curve"
},
{
"question": "Ludwig's ratchet predicts",
"answer": "increasing harvesting rate"
},
{
"question": "a deciduous forest in Madhya Pradesh was converted to a mine. after the mining operations were over, the pits were filled up with water and a lake was created. it is now visited by several migratory birds. this is an example of",
"answer": "replacement"
},
{
"question": "The fig tree bears fruits in times when animals do not have much access to food. In this context, it would be a good example of",
"answer": "keystone species"
},
{
"question": "People come to Sessa orchid sanctuary in Arunachal Pradesh to witness orchids, which in this context would be classified as",
"answer": "flagship species"
},
{
"question": "The elephant has a home range of several square kilometres, regulates the ecosystem by its habit of destructive feeding, and people can relate to this animal which is important for conservation Given this background, the elephant can be called as",
"answer": "all of the above"
},
{
"question": "Consider the food chain: Grass \u2192 Grasshopper \u2192 Frog --> Snake \u2192 Hawk. In this food chain,",
"answer": "grass is producer"
},
{
"question": "Consider the food chain: Grass \u2192 Grasshopper \u2192 Frog --> Snake \u2192 Hawk. In this food chain,",
"answer": "grasshopper is consumer"
},
{
"question": "Which of the following best describes habitat selection?",
"answer": "Habitat selection has both innate and learnt components."
},
{
"question": "The regular, seasonal movement of animals, often along fixed routes is called",
"answer": "migration"
},
{
"question": "In the word root for conservation, con stands for",
"answer": "together"
},
{
"question": "\u201cthe ability of a single economic actor (or small group of actors) to have a substantial influence on market prices\u201d is known as",
"answer": "market power"
},
{
"question": "\u201can economy that allocates resources through the decentralised decisions of many firms and households as they interact in markets for goods and services\u201d is a / an",
"answer": "market economy"
},
{
"question": "In the word root for conservation, servare stands for",
"answer": "to keep"
},
{
"question": "Which of these is true?",
"answer": "Wants are unlimited, resources are limited"
},
{
"question": "\u201can increase in the overall level of prices in the economy\u201d is",
"answer": "inflation"
},
{
"question": "Phillips curve shows the relation between",
"answer": "inflation rate and unemployment rate"
},
{
"question": "In the word root for Economics, oikos stands for",
"answer": "house"
},
{
"question": "Most of rational thinking occurs",
"answer": "at the margin"
},
{
"question": "Input costs that do not require an outlay of money are",
"answer": "implicit costs"
},
{
"question": "Which of these is not a pillar of sustainability?",
"answer": "trans-boundary sustainability"
},
{
"question": "The Trinity explosion of 1945 is taken as the beginning of the",
"answer": "Anthropocene"
},
{
"question": "According to Malthusian model,",
"answer": "Population grows in geometric progression, food supply increases in arithmetic progression"
},
{
"question": "The quantum of human impacts can be written as",
"answer": "I = P X A X T"
},
{
"question": "The logistic growth equation curve is",
"answer": "S-shaped"
},
{
"question": "___ is used to identify which potential impacts are relevant to assess. (Fill in the blank)",
"answer": "scoping"
},
{
"question": "\u201cthe potential or capacity of a material to have adverse effects on living organisms\u201d is",
"answer": "toxicity"
},
{
"question": "A deciduous forest in Madhya Pradesh was converted to a mine. After the mining operations were over, the pits were filled up with soil and\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 species of deciduous forest planted again. This is an example of",
"answer": "restoration"
},
{
"question": "Hydrocarbons derived from incomplete burning of mineral oils are",
"answer": "pyrogenic hydrocarbons"
},
{
"question": "A deciduous forest in Madhya Pradesh was converted to a mine. After the mining operations were over, the pits were filled up with water and a\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0lake was created. It is now visited by several migratory birds. This is an example of",
"answer": "replacement"
},
{
"question": "\u201cthe relative effect of exposure\u201d is",
"answer": "sensitivity"
},
{
"question": "___ determines which projects or developments require a full or partial impact assessment study. (Fill in the blank)",
"answer": "screening"
},
{
"question": "\u201cthe extent to which a chemical is available for uptake into an organism\u201d is",
"answer": "bioavailability"
},
{
"question": "Hydrocarbons derived from biological processes acting on mineral oils are",
"answer": "biogenic hydrocarbons"
},
{
"question": "\u201cAny changes in natural or human systems that inadvertently increase vulnerability to climatic stimuli; an adaptation that does not succeed in\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reducing vulnerability but increases it instead\u201d is a definition for",
"answer": "maladaptation"
},
{
"question": "\"The geographical distribution of a species will be controlled by that environmental factor for which the organism has the narrowest range of tolerance.\" This is the statement for",
"answer": "Shelford\u2019s law of tolerance"
},
{
"question": "Which of these is a stochastic factor?",
"answer": "environmental fluctuation"
},
{
"question": "Which of these correctly represents the process of habitat fragmentation and loss?",
"answer": "Original forest \u2192 Dissection \u2192 Perforation \u2192 Fragmentation \u2192 Attrition"
},
{
"question": "A root zone treatment plant is an example of",
"answer": "phytoremediation"
},
{
"question": "\u201ca measure of the responsiveness of quantity demanded or quantity supplied to a change in one of its determinants\u201d is",
"answer": "elasticity"
},
{
"question": "\u201cthe ability to produce a good using fewer inputs than another producer\u201d is",
"answer": "absolute advantage"
},
{
"question": "Common resource goods are",
"answer": "non-excludable, rival in consumption"
},
{
"question": "\u201cIf private parties can bargain without cost over the allocation of resources, they can solve the problem of externalities on their own.\u201d This is a statement for",
"answer": "Coase theorem"
},
{
"question": "Development that meets the needs of the present without compromising the ability of future generations to meet their own needs is known as",
"answer": "Sustainable development"
},
{
"question": "Club goods are",
"answer": "excludable, non-rival in consumption"
},
{
"question": "Which of these is not a method of internalisation of externalities?",
"answer": "free market"
},
{
"question": "Private goods are",
"answer": "excludable, rival in consumption"
},
{
"question": "\u201ca simplified description, especially a mathematical one, of a system or process, to assist calculations and predictions\u201d is the definition of a / an",
"answer": "model"
},
{
"question": "\u201cthe impact of one person\u2019s actions on the well-being of a bystander\u201d is",
"answer": "externality"
},
{
"question": "\u201cthe claim that, other things being equal, the quantity demanded of a good falls when the price of the good rises\u201d is a statement of",
"answer": "law of demand"
},
{
"question": "\u201ca legal maximum on the price at which a good can be sold\u201d is",
"answer": "price ceiling"
},
{
"question": "\u201ca good for which, other things being equal, an increase in income leads to a decrease in demand\u201d is",
"answer": "inferior good"
},
{
"question": "\u201ca table that shows the relationship between the price of a good and the quantity supplied\u201d is",
"answer": "supply schedule"
},
{
"question": "\u201ca graph of the relationship between the price of a good and the quantity demanded\u201d is",
"answer": "demand curve"
},
{
"question": "\u201ca table that shows the relationship between the price of a good and the quantity demanded\u201d is",
"answer": "demand schedule"
},
{
"question": "\u201ca measure of how much the quantity demanded of one good responds to a change in the price of another good, computed as the percentage change in quantity demanded of the first good divided by the percentage change in price of the second good\u201d is",
"answer": "cross-price elasticity of demand"
},
{
"question": "Rice and wheat are",
"answer": "substitutes"
},
{
"question": "\u201ca good for which, other things being equal, an increase in income leads to an increase in demand\u201d is",
"answer": "normal good"
},
{
"question": "\u201ca measure of how much the quantity demanded of a good responds to a change in the price of that good, computed as the percentage change in quantity demanded divided by the percentage change in price\u201d is",
"answer": "price elasticity of demand"
},
{
"question": "\"the price of a good that prevails in the world market for that good\u201d is the definition of",
"answer": "world price"
},
{
"question": "The area between the demand curve and the price is an indicator of",
"answer": "consumer surplus"
},
{
"question": "\u201cthe amount a buyer is willing to pay for a good minus the amount the buyer actually pays for it\u201d is",
"answer": "consumer surplus"
},
{
"question": "\u201cthe amount a seller is paid for a good minus the seller\u2019s cost of providing it\u201d is",
"answer": "producer surplus"
},
{
"question": "Value to buyers - Cost to sellers is",
"answer": "total surplus"
},
{
"question": "\u201cthe fall in total surplus that results from a market distortion, such as a tax\u201d is",
"answer": "deadweight loss"
},
{
"question": "Laffer's curve is the relationship between",
"answer": "tax size and tax revenue"
},
{
"question": "Imposition of tariff",
"answer": "increases producer surplus and government revenue"
},
{
"question": "\u201cthe maximum amount that a buyer will pay for a good\u201d is",
"answer": "willingness to pay"
},
{
"question": "The area between the supply curve and the price is an indicator of",
"answer": "producer surplus"
},
{
"question": "For a positive consumption externality,",
"answer": "SMB = PMB + MB"
},
{
"question": "For a positive production externality,",
"answer": "SMB = PMB"
},
{
"question": "\u201cThe direct cost to producers of producing an additional unit of a good\u201d is",
"answer": "private marginal cost (PMC)"
},
{
"question": "For a negative production externality,",
"answer": "SMC = PMC + MD"
},
{
"question": "\u201cThe private marginal cost to producers plus any costs associated with the production of the good that are imposed on others\u201d is",
"answer": "social marginal cost (SMC)"
},
{
"question": "Partying with loud noise is an example of",
"answer": "negative consumption externality"
},
{
"question": "\u201cWhen an individual\u2019s consumption increases the well-being of others, but the individual is not compensated by those others,\u201d we have",
"answer": "positive consumption externality"
},
{
"question": "\u201cWhen a firm\u2019s production increases the well-being of others but the firm is not compensated by those others,\u201d we have",
"answer": "positive production externality"
},
{
"question": "\u201cWhen an individual\u2019s consumption reduces the well-being of others who are not compensated by the individual,\u201d we have",
"answer": "negative consumption externality"
},
{
"question": "For a negative consumption externality,",
"answer": "SMB = PMB \u2212 MD"
},
{
"question": "\u201ccosts that have already been committed and cannot be recovered\u201d are",
"answer": "sunk costs"
},
{
"question": "\u201ctotal revenue minus total cost, including both explicit and implicit costs\u201d is a definition of",
"answer": "economic profit"
},
{
"question": "\u201cthe increase in total cost that arises from an extra unit of production\u201d are",
"answer": "marginal costs"
},
{
"question": "A monopolist firm\u2019s profit is given by",
"answer": "(Price - ATC) \u00d7 Q"
},
{
"question": "Which of the following is true for a competitive firm?",
"answer": "MR = MC"
},
{
"question": "\u201cthe amount a firm receives for the sale of its output\u201d is a definition of",
"answer": "total revenue"
},
{
"question": "When the cost of production for a single firm is much lesser than the cost of production for competitive firms, we have a / an",
"answer": "natural monopoly"
},
{
"question": "\u201ccosts that do not vary with the quantity of output produced\u201d are",
"answer": "fixed costs"
},
{
"question": "\u201ccosts that vary with the quantity of output produced\u201d are",
"answer": "variable costs"
},
{
"question": "\u201cthe increase in output that arises from an additional unit of input\u201d is",
"answer": "marginal product"
},
{
"question": "\u201can absolute level of income set by the government for each family size below which a family is deemed to be in poverty\u201d is known as",
"answer": "poverty line"
},
{
"question": "Absolute poverty depends",
"answer": "on income and on access to social services"
},
{
"question": "\u201cthe equipment and structures used to produce goods and services\u201d is the definition of",
"answer": "capital"
},
{
"question": "\u201cabove-equilibrium wages paid by firms to increase worker productivity\u201d are known as",
"answer": "efficiency wage"
},
{
"question": "\u201cgovernment policy aimed at protecting people against the risk of adverse events\u201d is",
"answer": "Social insurance"
},
{
"question": "\u201ca difference in wages that arises to offset the non-monetary characteristics of different jobs\u201d is known as",
"answer": "compensating differential"
},
{
"question": "For a competitive and profit-maximising firm,",
"answer": "each factor\u2019s rental price = the value of the marginal product for that factor"
},
{
"question": "\u201ca condition characterised by severe deprivation of basic human needs, including food, safe drinking water, sanitation facilities, health, shelter, education and information\u201d is known as",
"answer": "absolute poverty"
},
{
"question": "\u201ca condition where a household\u2019s income is lower than the median income in the particular country\u201d is known as",
"answer": "relative poverty"
},
{
"question": "\u201cthe increase in the amount of output from an additional unit of labor\u201d is",
"answer": "marginal product of labour"
},
{
"question": "\u201can action taken by an uninformed party to induce an informed party to reveal information\u201d is known as",
"answer": "screening"
},
{
"question": "\u201cthe part of actual resources that can be developed profitably in the future\u201d are",
"answer": "reserve resources"
},
{
"question": "\u201cthe limit on the consumption bundles that a consumer can afford\u201d is known as",
"answer": "budget constraint"
},
{
"question": "\u201cmental short cut using emotion (gut feeling) to influences the decision\u201d is",
"answer": "affect heuristic"
},
{
"question": "\u201cthose resources that are currently being used after surveying, quantification and qualification\u201d are",
"answer": "actual resources"
},
{
"question": "\u201cthe change in consumption that results when a price change moves the consumer along a given indifference curve to a point with a new marginal rate of substitution\u201d is known as",
"answer": "substitution effect"
},
{
"question": "An inferior good whose demand increases with price is called as",
"answer": "Giffen good"
},
{
"question": "Which of these is not a property of indifference curves?",
"answer": "Indifference curves cross at right angles"
},
{
"question": "\u201can action taken by an informed party to reveal private information to an uninformed party\u201d is known as",
"answer": "signalling"
},
{
"question": "\u201csimple strategies or mental processes used to quickly form judgments, make decisions, and find solutions to complex problems\u201d is known as",
"answer": "heuristics"
},
{
"question": "As per Wildlife Protection Act 1972 (WPA 1972), wild life includes any animal, aquatic or land vegetation which forms part of any _____ .",
"answer": "habitat"
},
{
"question": "The tiger has a home range of several square kilometres, regulates the ecosystem through controlling herbivore populations and trophic cascades, and people come to tiger reserves to watch tigers. Thus, the tiger can be called as",
"answer": "all of the above"
},
{
"question": "Soil formation is an example of",
"answer": "supporting service"
},
{
"question": "Zoo is an example of",
"answer": "ex-situ conservation"
},
{
"question": "Nutrient cycling is an example of",
"answer": "supporting service"
},
{
"question": "We prefer those areas for the creation of a conservation reserve where the level of threat is",
"answer": "medium"
},
{
"question": "The elephant has a home range of several square kilometres, regulates the ecosystem by its habit of destructive feeding, and people can relate to this animal which is important for conservation. Given this background, the elephant can be called as",
"answer": "all of the above"
},
{
"question": "Biological control of pest populations is an example of",
"answer": "regulating service"
},
{
"question": "\u201cthe ability to produce a good at a lower opportunity cost than another producer\u201d is a definition of",
"answer": "comparative advantage"
},
{
"question": "\u201cfluctuations in economic activity, such as employment and production\u201d are referred to as",
"answer": "business cycles"
},
{
"question": "The property of society getting the most it can from its scarce resources is a definition of",
"answer": "efficiency"
},
{
"question": "In the word root for Economics, nemein stands for",
"answer": "manage"
},
{
"question": "Whatever must be given up to obtain some item is a definition of",
"answer": "opportunity costs"
},
{
"question": "Rational decision making compares",
"answer": "marginal benefits to marginal costs"
},
{
"question": "\u201csomething that induces a person to act\u201d is a definition of",
"answer": "incentive"
},
{
"question": "Input costs that require an outlay of money are",
"answer": "explicit costs"
},
{
"question": "The property of distributing economic prosperity uniformly among the members of society is a definition of",
"answer": "equality"
},
{
"question": "\"the ability of an individual to own and exercise control over scarce resources\" is known as",
"answer": "property rights"
},
{
"question": "\"The inputs used to produce goods and services\" are known as",
"answer": "Factors of production"
},
{
"question": "When a consumer is at risk of getting sold a good of cheap quality, that situation is called",
"answer": "Adverse selection"
},
{
"question": "Defence is a",
"answer": "Public good"
},
{
"question": "Environment is a",
"answer": "Common resources"
},
{
"question": "The political philosophy according to which the government should punish crimes and enforce voluntary agreements but not redistribute income is",
"answer": "Libertarianism"
},
{
"question": "Mesodebris size",
"answer": "5mm - 20mm"
},
{
"question": "Size of macrodebris w.r.t plastic",
"answer": "> 20mm"
},
{
"question": "What affects Earth the least",
"answer": "Sun's orbit"
},
{
"question": "A question was asked on something increasing",
"answer": "Escalating"
},
{
"question": "For a positive consumption externality",
"answer": "SMC = PMC"
},
{
"question": "Impact of carbon",
"answer": "Economic cost of carbon"
},
{
"question": "Which of the following is true for monopoly?",
"answer": "P > MR"
},
{
"question": "Government policies for \"veil of ignorance\"",
"answer": "Policies focusing on fairness, equality, justice, or protecting the disadvantaged"
},
{
"question": "Supply of medicines is an example of",
"answer": "Provisioning service"
},
{
"question": "The rate of any biological process is limited by that factor in least amount relative to requirement. This is the statement for",
"answer": "Liebig's law of the minimum"
},
{
"question": "The claim that, other things being equal, the quantity supplied of a good rises when the price of the good rises is a statement of",
"answer": "Law of supply"
},
{
"question": "A legal minimum on the price at which a good can be sold is",
"answer": "Price floor"
},
{
"question": "A graph of the relationship between the price of a good and the quantity supplied is",
"answer": "Supply curve"
},
{
"question": "Hydrogen for nuclear fusion is an example of",
"answer": "Stock resource"
},
{
"question": "Loss of ecosystem services due to mining is an example of",
"answer": "Negative production externality"
},
{
"question": "The fig tree would be a good example of",
"answer": "Keystone species"
},
{
"question": "The extent to which a chemical substance is available for uptake into an organism is",
"answer": "Bioavailability"
},
{
"question": "Because of climate change, Mudumalai Tiger Reserve is suffering from frequent droughts. The management builds artificial water holes and fills them with tankers. This action is called",
"answer": "Adaptation"
},
{
"question": "The market value of the inputs a firm uses in production is",
"answer": "Total cost"
},
{
"question": "A visual model of the economy showing how money flows through markets among households and firms is",
"answer": "Circular flow diagram"
},
{
"question": "Coffee powder and sugar are",
"answer": "Complements"
},
{
"question": "Total revenue minus total explicit cost is a definition of",
"answer": "Accounting profit"
},
{
"question": "Public goods are",
"answer": "Non-excludable, non-rival in consumption"
},
{
"question": "The rate at which a consumer is willing to trade one good for another is known as",
"answer": "Marginal rate of substitution"
},
{
"question": "\"Apple's iPod is good, so Apple should be good for other devices as well.\" This is an example of",
"answer": "Halo effect"
},
{
"question": "Those resources that have been surveyed but we lack the technology to use them are",
"answer": "Potential resources"
},
{
"question": "The government mandated replacing incandescent bulbs with LED bulbs to reduce electricity and CO\u2082 emissions. This is called",
"answer": "Mitigation"
},
{
"question": "Mental shortcut justifying increased investment in a decision because prior investment was made is",
"answer": "Escalation of commitment"
},
{
"question": "The increasing concentration of a toxic substance in organisms at higher levels of a food chain is called",
"answer": "Biomagnification"
},
{
"question": "A measure of how the quantity supplied of a good responds to a change in its price is",
"answer": "Price elasticity of supply"
},
{
"question": "\u201cScience of relationships between organisms and their environments\u201d is the definition of:",
"answer": "ecology"
},
{
"question": "Sustainable harvest of resources falls under the category of:",
"answer": "conservation"
},
{
"question": "The discipline of Demography is most closely related to:",
"answer": "Population Geography"
},
{
"question": "\"The rate of any biological process is limited by that factor in least amount relative to requirement, so there is a single limiting factor.\" This is the\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0statement for",
"answer": "Liebig\u2019s law of the minimum"
},
{
"question": "\"The geographical distribution of a species will be controlled by that environmental factor for which the organism has the narrowest range of\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 tolerance.\" This is the statement for",
"answer": "Shelford\u2019s law of tolerance"
},
{
"question": "The regional approach to Geography was developed by:",
"answer": "Karl Ritter"
},
{
"question": "In an undisturbed sedimentary strata, bottom layers are older than layers above them. This is known as",
"answer": "Principle of superposition"
},
{
"question": "Mount Vesuvius is an example of",
"answer": "Composite volcano"
},
{
"question": "Krakatoa eruption resulted in the formation of",
"answer": "Caldera"
},
{
"question": "Which of these is not a method of absolute dating?",
"answer": "inclusion study"
},
{
"question": "Which of these is true about S waves?",
"answer": "They are transverse in nature"
},
{
"question": "Hawaiian volcanoes are an example of",
"answer": "Shield volcano"
},
{
"question": "Within a depositional basin, strata are laterally continuous in all directions till the edge of the basin. This is known as",
"answer": "Principle of lateral continuity"
},
{
"question": "Which of these is a gas giant?",
"answer": "Saturn"
},
{
"question": "Assemblage of fossils are unique to the time that they lived in, and so can be used to age rocks across a wide geographic distribution. This is known as",
"answer": "Principle of fossil succession"
},
{
"question": "Which of these is an example of direct source of information about the Earth?",
"answer": "volcanic eruption"
},
{
"question": "\u201cthe points where three or more cirques meet\u201d is the definition of",
"answer": "horn"
},
{
"question": "Which of these is an example of endogenic process?",
"answer": "folding"
},
{
"question": "Sandstone is an example of",
"answer": "siliciclastic rock"
},
{
"question": "\u201csmooth oval-shaped ridge-like features comprised of glacial till, gravel and sand arranged parallel to the direction of ice movement\u201d is the definition of",
"answer": "drumlin"
},
{
"question": "Which of these is an example of exogenic process?",
"answer": "deposition"
},
{
"question": "\u201cwhite or colourless hard mineral virtually insoluble in water\u201d is a description of",
"answer": "quartz"
},
{
"question": "Dolomite is an example of",
"answer": "carbonate rock"
},
{
"question": "\u201cgreen or black coloured inosilicate minerals forming prism or needle-like crystals\u201d is a description of",
"answer": "amphiboles"
},
{
"question": "\u201cdeep, long and wide troughs or basins with very steep concave to vertically dropping high walls as its head and sides\u201d is the definition of",
"answer": "cirque"
},
{
"question": "\u201cmagnesium iron silicate; a primary component of the Earth\u2019s upper mantle\u201d is a description of",
"answer": "olivine"
},
{
"question": "Which of these air masses is generally cool and moist?",
"answer": "mP"
},
{
"question": "Which of these air masses is generally cold and dry?",
"answer": "cP"
},
{
"question": "The ISS orbits in which layer?",
"answer": "Thermosphere"
},
{
"question": "Surplus seed and sperm banking is an adaptation option to facilitate",
"answer": "resilience to climate changes"
},
{
"question": "The classical period for taking averages for climate is",
"answer": "30 years"
},
{
"question": "Noctilucent clouds are present in which layer?",
"answer": "Mesosphere"
},
{
"question": "Which of these is home to the ozone layer?",
"answer": "Stratosphere"
},
{
"question": "Most of the weather phenomena occur in",
"answer": "Troposphere"
},
{
"question": "Equal density curves are called",
"answer": "isopycnal curves"
},
{
"question": "Which of these is a minor feature of the ocean floor?",
"answer": "guyot"
},
{
"question": "\u201cmountains with pointed summits rising from the sea floor, but not reaching the surface of the ocean\u201d is a description of",
"answer": "seamount"
},
{
"question": "Which of these is the largest habitat on the Earth?",
"answer": "abyssal plain"
},
{
"question": "Which of these are the largest mountain ranges on Earth?",
"answer": "mid-oceanic ridge"
},
{
"question": "\u201cdeep valleys, often cutting across continental shelves and slopes\u201d is a description of",
"answer": "submarine canyon"
},
{
"question": "Continental slope has a gradient of",
"answer": "2-5 degree"
},
{
"question": "Which of these is not a prominent tidal pattern?",
"answer": "mixed diurnal"
},
{
"question": "\u201crelatively steep-sided, narrow, deep basins\u201d is a description of",
"answer": "oceanic deep / trench"
},
{
"question": "In the sea, a layer where the temperature decreases rapidly from the mixed upper layer to the cold deeper layer is called",
"answer": "thermocline"
},
{
"question": "In Koeppen classification, which is hot summer temperature?",
"answer": "a"
},
{
"question": "In Koeppen classification, which is desert precipitation?",
"answer": "W"
},
{
"question": "India\u2019s location is",
"answer": "Tropical in South, Sub-tropical in North"
},
{
"question": "In Koeppen classification, which is cold arid temperature?",
"answer": "k"
},
{
"question": "In Koeppen classification, which is winter dry precipitation?",
"answer": "w"
},
{
"question": "In Koeppen classification, which is cool summer temperature?",
"answer": "c"
},
{
"question": "Which of these is the correct sequence of seasons in India?",
"answer": "Hot weather season \u2192 South-West monsoon season \u2192 Retreating monsoon season \u2192 Cold weather season"
},
{
"question": "In Koeppen classification, which is monsoonal precipitation?",
"answer": "m"
},
{
"question": "Physiography is the outcome of",
"answer": "all of these"
},
{
"question": "In Koeppen classification, which is equatorial climate?",
"answer": "A"
},
{
"question": "Soil formation is dependent upon",
"answer": "all of the above"
},
{
"question": "\"groups of actually or potentially interbreeding natural populations, which are reproductively isolated from other such groups\" is a definition of",
"answer": "species"
},
{
"question": "Mechanical action of ocean waves is an example of",
"answer": "physical weathering"
},
{
"question": "The climax near Tindni village is being controlled by disturbance by cattle. This is an example of",
"answer": "disclimax"
},
{
"question": "Carbonation is an example of",
"answer": "chemical weathering"
},
{
"question": "In soil profile, C refers to",
"answer": "substratum layer"
},
{
"question": "Regur is a term for",
"answer": "black cotton soil"
},
{
"question": "\"the diversity that exists among different geographies\" is",
"answer": "gamma (\u03b3) biodiversity"
},
{
"question": "The \u201csubset of physical and biotic environmental factors that permit an animal (or plant) to survive and reproduce\u201d is the definition of",
"answer": "habitat"
},
{
"question": "Which of these is commonly observed during humanising of nature?",
"answer": "possibilism"
},
{
"question": "Which of these is commonly observed in primitive societies?",
"answer": "environmental determinism"
},
{
"question": "Hydrogen for nuclear fusion comes under the category of",
"answer": "stock resources"
},
{
"question": "Which of these is the largest source of ammonia (NH3) in the atmosphere?",
"answer": "animal manure"
},
{
"question": "\u201cthe part of actual resources that can be developed profitably in the future\u201d is a definition of",
"answer": "reserve resources"
},
{
"question": "Timber from forests that is being harvested can be categorised under",
"answer": "actual resources"
},
{
"question": "Rain gardens are primarily meant to",
"answer": "increase recharge to groundwater"
},
{
"question": "Oil that has not been drilled can be categorised under",
"answer": "potential resources"
},
{
"question": "Which of these is the largest source of nitrogen oxides in the atmosphere?",
"answer": "mobile sources"
},
{
"question": "\u201cthose resources that are currently being used after surveying, quantification and qualification\u201d is a definition of",
"answer": "actual resources"
},
{
"question": "\u201cthose resources that may be used in the future\u201d is a definition of",
"answer": "potential resources"
},
{
"question": "Low concentration ores come in the category of",
"answer": "reserve resources"
},
{
"question": "New Delhi can best be categorised as",
"answer": "administrative town"
},
{
"question": "Which of these is a factor governing barrier effect of roads?",
"answer": "all of these"
},
{
"question": "Varanasi can best be categorised as",
"answer": "religious town"
},
{
"question": "Visakhapatnam can best be categorised as",
"answer": "transport town"
},
{
"question": "Which of these is / are example(s) of mitigation measures to mitigate impacts of linear infrastructure on wildlife?",
"answer": "all of these"
},
{
"question": "Bhilai can best be categorised as",
"answer": "industrial town"
},
{
"question": "The movement of individuals away from their place of birth or hatching or seed production into a new habitat or area to survive and reproduce is\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0called",
"answer": "dispersal"
},
{
"question": "The Geographical discipline of Climatology is most closely related to:",
"answer": "Meteorology"
},
{
"question": "The systematic approach to Geography was developed by:",
"answer": "Alexander von Humboldt"
},
{
"question": "Which of these is true?",
"answer": "none of these"
},
{
"question": "\u201callowing some places and some creatures to exist without significant human interference\u201d is the definition of:",
"answer": "preservation"
},
{
"question": "The time of flight for LiDAR is 0.00001 sec. Find the distance of the object from the instrument.",
"answer": "1500 m"
},
{
"question": "Which of these uses imputed willingness to pay?",
"answer": "replacement cost method"
},
{
"question": "\u201cThe situation of people, infrastructure, housing, production capacities and other tangible human assets located in hazard-prone areas.\u201d is a definition for",
"answer": "exposure"
},
{
"question": "\u201cA process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation.\u201d is a definition for",
"answer": "hazard"
},
{
"question": "\u201cA serious disruption of the functioning of a community or a society at any scale due to hazardous events interacting with conditions of exposure, vulnerability and capacity, leading to one or more of the following: human, material, economic and environmental losses and impacts.\u201d is a definition for",
"answer": "disaster"
},
{
"question": "\u201cThe combination of all the strengths, attributes and resources available within an organisation, community or society to manage and reduce disaster risks and strengthen resilience.\u201d is a definition for",
"answer": "capacity"
},
{
"question": "Bathymetric LiDAR uses",
"answer": "green light"
},
{
"question": "IMU stands for",
"answer": "Inertial measurement unit"
},
{
"question": "Which of these is not a consumptive value?",
"answer": "education"
},
{
"question": "The frequency of flyovers is an indicator of",
"answer": "temporal resolution"
},
{
"question": "Mhow can best be categorised as",
"answer": "garrison town"
},
{
"question": "the diversity that exists within an ecosystem is",
"answer": "alpha (\u03b1) biodiversity"
},
{
"question": "Bangar is a type of",
"answer": "alluvial soil"
},
{
"question": "Cryofracturing is an example of",
"answer": "physical weathering"
},
{
"question": "stratified and assorted deposits comprised of fine matter with rounded edges' is the definition of",
"answer": "outwash deposit"
},
{
"question": "a naturally occurring inorganic solid, with a definite chemical composition, and an ordered atomic arrangement' is the definition of",
"answer": "mineral"
},
{
"question": "Continental shelf has a gradient of",
"answer": "0-1 degree"
},
{
"question": "In Koeppen classification, which is extremely continental temperature?",
"answer": "d"
},
{
"question": "In Koeppen classification, which is fully humid precipitation?",
"answer": "f"
},
{
"question": "In Koeppen classification, which is hot arid temperature?",
"answer": "h"
},
{
"question": "In Koeppen classification, which is warm temperate climate?",
"answer": "C"
},
{
"question": "In Koeppen classification, which is snow climate?",
"answer": "D"
},
{
"question": "Which of these create the best suited conditions for atmosphere on a planet?",
"answer": "high gravity, low temperature"
},
{
"question": "Which of these air masses is generally warm and moist?",
"answer": "mT"
},
{
"question": "Which of these is correct? (Regarding extrusive rocks)",
"answer": "Extrusive rocks: formed outside crust, often fine grained"
},
{
"question": "sinuous ridge formed by coarse materials deposited by streams flowing below the glaciers' is the definition of",
"answer": "esker"
},
{
"question": "Which of these is correct? (Regarding intrusive rocks)",
"answer": "Intrusive rocks: formed inside crust, often coarse grained"
},
{
"question": "Deformation events (e.g. folds, faults, igneous intrusions) that cut across rocks are younger than the rocks themselves. This is known as",
"answer": "Principle of cross-cutting relationships"
},
{
"question": "Moho discontinuity occurs between",
"answer": "Crust and mantle"
},
{
"question": "What is Psychology?",
"answer": "A study of cognitive processes & behavior"
},
{
"question": "A broad field of study that explores a variety of questions about thoughts, feelings and actions is ______?",
"answer": "Psychology"
},
{
"question": "A systematic assessment that provides information on political views, consumer buying habits and many other topics is called__________?",
"answer": "Survey"
},
{
"question": "The word Psyche means _________?",
"answer": "Mind or soul"
},
{
"question": "Who established psychology laboratory in Germany?",
"answer": "Wilhelm Wundt"
},
{
"question": "\u201cTo help people to function effectively and fulfill their own unique potential\u201d is the aim of_________?",
"answer": "Humanistic psychology"
},
{
"question": "Behaviorists use a learning process to change abnormal behavior is called_________?",
"answer": "Conditioning"
},
{
"question": "The study of changes in behavior during life time is called ______.",
"answer": "Developmental Psychology"
},
{
"question": "The study of differences and similarities in the behavior of animals of different species is called ____________________.",
"answer": "Comparative Psychology"
},
{
"question": "Which of the following is true regarding learning in psychology?",
"answer": "Learning can be defined in many ways"
},
{
"question": "Learning is seen as an effect attributed to an element in ___.",
"answer": "environment"
},
{
"question": "Non-associative learning refers to ___ in the intensity of a response as the result of the repeated presentation of a stimulus.",
"answer": "a decrease"
},
{
"question": "Which of the following is a type of learning style?",
"answer": "all of the above"
},
{
"question": "\u2018Psychology as the Behaviorist Views It\u2019 is the famous work of ___.",
"answer": "John Watson"
},
{
"question": "The state of conscious incompetence may be valuable ___ of a learning experience.",
"answer": "at the start"
},
{
"question": "Adding to existing student knowledge is known as ___.",
"answer": "conceptual growth"
},
{
"question": "Deliberate practice involves the following, except ___.",
"answer": "forgetting"
},
{
"question": "Students persist in the face of challenging tasks and process information more deeply when they adopt ___ goals.",
"answer": "mastery"
},
{
"question": "___ refers to a decrease in response rate while ___ refers to an increase in response rate.",
"answer": "habituation, sensitization"
},
{
"question": "According to constructivism, reality is determined by ___ .",
"answer": "the experiences of the learner"
},
{
"question": "Teachers and instructors who understand the constructivist learning theory understand that their students ___.",
"answer": "bring their own unique experiences"
},
{
"question": "The domain of social constructivism is largely attributed to the work done by ___ by adding the focus on societal and peer influence on learning.",
"answer": "Lev Vygotsky"
},
{
"question": "Which of the following is not a major type of learning described by behavioral psychology?",
"answer": "traditional learning"
},
{
"question": "Which of the following is true according to behaviorism?",
"answer": "all behaviors can be unlearned"
},
{
"question": "In ___ conditioning, presentation of unconditioned stimulus precedes the presentation of the conditioned stimulus.",
"answer": "backward"
},
{
"question": "Smiling and complimenting students for good performance is a type of ____.",
"answer": "positive reinforcement"
},
{
"question": "Which of the following psychologists was instrumental in engendering the dramatic shift from behaviorism to cognitive theories?",
"answer": "All of the above"
},
{
"question": "IQ is a typical measurement for ___.",
"answer": "intelligence"
},
{
"question": "BBL deals with the concept of learning in a ___ context.",
"answer": "neuro-physiological"
},
{
"question": "Which of the following is true?",
"answer": "Human memory involves the ability to both preserve and recover information"
},
{
"question": "___ refers to connections between the nerve cells",
"answer": "Synapses"
},
{
"question": "In order to create a new memory, information must be changed into ___.",
"answer": "a usable form"
},
{
"question": "The memory ___ process allows us to bring stored memories into conscious awareness.",
"answer": "retrieval"
},
{
"question": "Which of the following is not a major domain of cognition?",
"answer": "Aggression"
},
{
"question": "Which of the following is not an assumption of the information processing view?",
"answer": "There are limits on how much information can be processed"
},
{
"question": "___ refers to rote rehearsal of repetition",
"answer": "maintenance rehearsal"
},
{
"question": "___ presentations can enhance working memory capacity",
"answer": "multimodal"
},
{
"question": "Episodic and semantic memories are categorized under ___ memory",
"answer": "declarative"
},
{
"question": "According to the neural network model, memory capacity depends on ___",
"answer": "all of the above"
},
{
"question": "Creative thinking is the ability to generate ___ ideas.",
"answer": "all of the above"
},
{
"question": "___ are representative items within each concept.",
"answer": "Prototypes"
},
{
"question": "A mental representation can take the form of a ___.",
"answer": "all of the given"
},
{
"question": "Which of the following is true?",
"answer": "Thinking is a sub-vocal talking"
},
{
"question": "Cognitive architecture is a theory about the ___ that create a mind in natural or artificial systems.",
"answer": "structures"
},
{
"question": "Bloom\u2019s Taxonomy is a system of hierarchical models (arranged in a hierarchical rank,) used to categorize ___ into varying levels of complexity.",
"answer": "learning objectives"
},
{
"question": "___ knowledge refers to knowledge of terminology and specific details.",
"answer": "Factual"
},
{
"question": "Choosing to skim subheadings of unimportant information to get to the information we need as a metacognitive skill is called ___.",
"answer": "Skimming"
},
{
"question": "___ refers to the ability to regulate your thoughts and set aside any personal biases to come to the best conclusion.",
"answer": "Self-regulation"
},
{
"question": "___ method of solving problems refers to a rule that guarantees the solution to a problem",
"answer": "algorithm"
},
{
"question": "Motivation is seen as a ___ that drives and directs human behavior.",
"answer": "mental impulse"
},
{
"question": "Which of the following is false regarding motivation?",
"answer": "Motivation can be directly observed"
},
{
"question": "Which of the following does not come under intrinsic motivation?",
"answer": "social pressure"
},
{
"question": "Doing something/behaving well because one feels pressure from within is called ___.",
"answer": "introjected regulation"
},
{
"question": "Which of the following is not a biological need/drive?",
"answer": "love"
},
{
"question": "___ theory suggests that people are motivated to take action in order to receive a reward.",
"answer": "incentive"
},
{
"question": "The Arousal Theory of Motivation was first proposed by ___.",
"answer": "Murray"
},
{
"question": "___ theory is based on the assumption that our behavior is based on making a conscious choice from a set of possible alternative behaviors.",
"answer": "expectancy"
},
{
"question": "Which of the following is not an attribute of effective goal setting?",
"answer": "unrealistic goals"
},
{
"question": "Maslow\u2019s hierarchy of needs consists of ___ tiers.",
"answer": "five"
},
{
"question": "___ is mainly responsible for popularizing emotional intelligence.",
"answer": "Daniel Goleman"
},
{
"question": "EQ is used to measure __________.",
"answer": "emotional intelligence"
},
{
"question": "Experts suggest that EQ is more important than IQ.",
"answer": "True"
},
{
"question": "Being able to put words to feelings refers to ___.",
"answer": "emotional literacy"
},
{
"question": "The first step in emotional intelligence is ___.",
"answer": "perceiving emotions accurately"
},
{
"question": "According to nine-layer model pyramid, ___ lies at the bottom of the pyramid.",
"answer": "Emotional stimuli"
},
{
"question": "The mixed model by ___ claims that EQ is a combination of competencies, skills, and \u201cfacilitators\u201d that contribute to how people express themselves, respond to challenges in their environment, and connect with others.",
"answer": "Bar-On"
},
{
"question": "The transfer of emotions from one person to another is called ___.",
"answer": "emotional contagion"
},
{
"question": "Hochschild has given the concept of ___ using the example of flight attendants.",
"answer": "emotional labor"
},
{
"question": "Review of literature shows that ___ is associated with increasing levels of burnout.",
"answer": "surface acting"
},
{
"question": "Which of the following is false about learning according to Gagne\u2019s definition?",
"answer": "It is simply ascribable to processes of growth"
},
{
"question": "Manual or physical skills come under ___ component of Bloom\u2019s taxonomy.",
"answer": "Psychomotor"
},
{
"question": "Reflection, learning and education was explored by ___.",
"answer": "John Dewey"
},
{
"question": "The principles of ___ refer to how close in time two events must be for a bond to be formed.",
"answer": "contiguity"
},
{
"question": "___ gives information to learners about their success or failure concerning the task at hand.",
"answer": "Cognitive feedback"
},
{
"question": "The ___ orientation to learning has a quality of personal involvement\u2014the whole person in both feeling and cognitive aspects being in the learning event.",
"answer": "humanistic"
},
{
"question": "Learning through play is ___ learning.",
"answer": "active"
},
{
"question": "People with ___ learning style are prone to sorting their ideas after speaking, rather than thinking ideas through before.",
"answer": "auditory"
},
{
"question": "Which of the following statements is false?",
"answer": "Effective instruction is static"
},
{
"question": "In ADDIE model, I stand for ___.",
"answer": "implementation"
},
{
"question": "Pedagogy can be defined as a way of ___ any theoretical topic or academic subject.",
"answer": "teaching"
},
{
"question": "Good pedagogy is about ___.",
"answer": "eliciting responses that demonstrate understanding"
},
{
"question": "Which of the following is a pedagogical approach in teaching?",
"answer": "integrative"
},
{
"question": "According to the ___ approach, learners are the makers of meaning and knowledge.",
"answer": "constructivist learning"
},
{
"question": "Which of the following is false about inquiry-based learning?",
"answer": "is a form of passive learning"
},
{
"question": "The ___ model describes what effective teachers do in their classrooms to engage students in intellectually challenging work.",
"answer": "pedagogical"
},
{
"question": "In experiential learning, experiences are structured to require the student to ___.",
"answer": "all of the above"
},
{
"question": "The following are the roles of an instructor in experiential learning, except___.",
"answer": "blurring the boundaries"
},
{
"question": "Kolb's experiential learning style theory is typically represented by a ___ -stage learning cycle",
"answer": "four"
},
{
"question": "People with ___ learning style are hands-on learners, and rely on intuition over logic.",
"answer": "accommodating"
},
{
"question": "A ___ classroom is the changing face of learning.",
"answer": "digital"
},
{
"question": "Which of the following is not a feature of e-learning?",
"answer": "teacher-centeredness"
},
{
"question": "Which of the following is a synonym of e-learning?",
"answer": "none of the above"
},
{
"question": "In ___ e-learning course, the content is simple and comprises text, images, audio, power point presentation and text questions.",
"answer": "text-driven"
},
{
"question": "Recent thinking in cognitive psychology places the ___ firmly at the center of the learning experience.",
"answer": "individual"
},
{
"question": "Which of the following is true?",
"answer": "All of the above"
},
{
"question": "Learners may multitask when engaging in an online course, which could be resulting in ___.",
"answer": "shorter attention spans"
},
{
"question": "The brain can process ___ visual cues per hour!",
"answer": "36,000"
},
{
"question": "Our brain has approximately ___ neurons that send information to one another.",
"answer": "85 billion"
},
{
"question": "___ is referring to learners' automatic performance on specific procedures with predetermined steps to be followed.",
"answer": "chaining"
},
{
"question": "The ___ Model for New Normal in Education has been designed by many educational institutions.",
"answer": "Readiness"
},
{
"question": "The most critical factor that plays a significant role in the success of any remote learning program is the ___.",
"answer": "readiness of the participants"
},
{
"question": "The typical picture of a teacher being a ___ of knowledge to the students has been replaced.",
"answer": "dispenser"
},
{
"question": "What does today\u2019s teacher need to be successful in the classroom?",
"answer": "all of the above"
},
{
"question": "Students who consider intelligence as a ___ entity tend to focus on their goals despite obstacles.",
"answer": "malleable"
},
{
"question": "___ feedback to students is important for learning.",
"answer": "all of the above"
},
{
"question": "Students tend to enjoy learning and have better outcomes when their motivation is more intrinsic than extrinsic.",
"answer": "True"
},
{
"question": "Long term goals are also called ___ goals.",
"answer": "distal"
},
{
"question": "Which of the following is an example of a reward?",
"answer": "all of the above"
},
{
"question": "___ is a teaching approach that generates feedback students can use to improve their performance.",
"answer": "Assessment for learning"
},
{
"question": "Social cognition is the cognitive processes that influence ___.",
"answer": "social behavior"
},
{
"question": "Smooth and adaptive social interactions depend on the recruitment of ___.",
"answer": "all of the above"
},
{
"question": "Which of the following abilities does not come under social cognition?",
"answer": "rote memory"
},
{
"question": "Social cognition develops primarily in old age.",
"answer": "False"
},
{
"question": "A theory of mind refers to a person's ability to understand and think about the ___ of other people.",
"answer": "mental states"
},
{
"question": "The ___ theory details the processes of observational learning and modeling, and the influence of self-efficacy on the production of behavior.",
"answer": "social-cognitive"
},
{
"question": "___ processes account for the information that is selected for observation in the environment.",
"answer": "attentional"
},
{
"question": "Children become more aggressive when they observe aggressive or violent models.",
"answer": "True"
},
{
"question": "The ___ model of observational learning involves real or fictional characters displaying behaviors in books, films, television programs, or online media.",
"answer": "symbolic"
},
{
"question": "___ refers to the anticipated consequences of a person's behavior.",
"answer": "expectations"
},
{
"question": "The rate of any biological process is limited by that factor in least amount relative to requirement, so there is a single limiting factor. This is the statement for",
"answer": "Liebig's law of the minimum"
},
{
"question": "Allowing some places and some creatures to exist without significant human interference is the definition of:",
"answer": "preservation"
},
{
"question": "Which of these is true about P waves?",
"answer": "They are longitudinal in nature"
},
{
"question": "Inclusions are older than the host rock. This is known as",
"answer": "Principle of inclusions"
},
{
"question": "Mount Fuji is an example of",
"answer": "Stratovolcano"
},
{
"question": "Layers of rocks deposited from above (e.g. sediments and lava flows) are originally laid down horizontally. This is known as",
"answer": "Principle of original horizontality"
},
{
"question": "waxy is an example of",
"answer": "lustre"
},
{
"question": "ease of passage of light through the mineral is known as",
"answer": "transparency"
},
{
"question": "unassorted coarse and fine debris left by melting glaciers, often with angular to sub-angular rock fragments is the definition of",
"answer": "glacial till"
},
{
"question": "green or black-coloured inosilicates forming 10% of the Earth's crust is a description of",
"answer": "Pyroxene"
},
{
"question": "the ridge edge where two cirques meet is the definition of",
"answer": "arete"
},
{
"question": "Sudden cooling of magma results in",
"answer": "smooth grained igneous rocks"
},
{
"question": "Gypsum is an example of",
"answer": "evaporite rock"
},
{
"question": "Thermal stresses lead to",
"answer": "physical weathering"
},
{
"question": "Which of these are correctly arranged as per Moh's scale from softest to hardest?",
"answer": "talc, calcite, feldspar, diamond"
},
{
"question": "Which of these is the densest layer?",
"answer": "Troposphere"
},
{
"question": "Which of these contains the most water vapour and aerosols?",
"answer": "Troposphere"
},
{
"question": "Which of these is true about Coriolis force on the Earth?",
"answer": "It is maximum at poles and zero at Equator."
},
{
"question": "From the surface of the Earth to upwards, which of these is the correct sequence of atmospheric layers?",
"answer": "troposphere, stratosphere, mesosphere, thermosphere, exosphere"
},
{
"question": "The ability of a system to adjust to climate change (including climate variability and extremes) to moderate potential damages, to take advantage of opportunities, or to cope with the consequences is a definition for",
"answer": "adaptive capacity"
},
{
"question": "Flights of jet planes typically occur in",
"answer": "Stratosphere"
},
{
"question": "Ionosphere is part of which layer?",
"answer": "Thermosphere"
},
{
"question": "Low islands consisting of coral reefs surrounding a central depression is a description of",
"answer": "atoll"
},
{
"question": "Deep valleys, often cutting across continental shelves and slopes is a description of",
"answer": "submarine canyon"
},
{
"question": "Equal salinity curves are called",
"answer": "isohaline curves"
},
{
"question": "Which of these is a major feature of the ocean floor?",
"answer": "oceanic deep / trench"
},
{
"question": "Equal temperature curves are called",
"answer": "isotherm curves"
},
{
"question": "Which of these is not a primary force initiating and governing movement of ocean currents?",
"answer": "arrangement of coasts"
},
{
"question": "Relatively steep-sided, narrow, deep basins is a description of",
"answer": "oceanic deep / trench"
},
{
"question": "Which of these are broad groups of peninsular plateau of India?",
"answer": "all of these"
},
{
"question": "In Koeppen classification, which is steppe precipitation?",
"answer": "S"
},
{
"question": "In Koeppen classification, which is summer dry precipitation?",
"answer": "s"
},
{
"question": "In Koeppen classification, which is polar tundra temperature?",
"answer": "T"
},
{
"question": "In Koeppen classification, which is warm summer temperature?",
"answer": "b"
},
{
"question": "In Koeppen classification, which is polar climate?",
"answer": "E"
},
{
"question": "the diversity that exists among different geographies is",
"answer": "gamma (\u03b3) biodiversity"
},
{
"question": "Vertical arrangement of soil horizons is called",
"answer": "soil profile"
},
{
"question": "Khadar is a type of",
"answer": "alluvial soil"
},
{
"question": "The term laterite soil is derived from Latin later which means",
"answer": "brick"
},
{
"question": "Which of these has the highest organic matter content?",
"answer": "peaty soil"
},
{
"question": "groups of actually or potentially interbreeding natural populations, which are reproductively isolated from other such groups is a definition of",
"answer": "Species"
},
{
"question": "Which of these depicts correctly the lithosere primary succession?",
"answer": "Rock - Crustose lichen - Foliose lichen - Moss - Herbaceous stage - Shrub - Woodland - Climax"
},
{
"question": "Religious benefits are an example of",
"answer": "cultural service"
},
{
"question": "According to Leopold, which of these is not a tool of habitat management?",
"answer": "sickle"
},
{
"question": "The subset of physical and biotic environmental factors that permit an animal (or plant) to survive and reproduce is definition of",
"answer": "habitat"
},
{
"question": "The book An Essay on the Principle of Population was written by",
"answer": "Malthus"
},
{
"question": "Which of these is the largest source of sulphur oxides in the atmosphere?",
"answer": "electricity generation"
},
{
"question": "those resources that are currently being used after surveying, quantification and qualification is a definition of",
"answer": "actual resources"
},
{
"question": "Rain water usage is meant to",
"answer": "Increase recharge to groundwater"
},
{
"question": "those resources that have been surveyed but we lack the technology to use them is a definition of",
"answer": "stock resources"
},
{
"question": "those resources that may be used in the future is a definition of",
"answer": "potential resources"
},
{
"question": "the part of actual resources that can be developed profitably in the future is a definition of",
"answer": "reserve resources"
},
{
"question": "Oxford can best be categorised as",
"answer": "educational town"
},
{
"question": "the ability to produce a good using fewer inputs than another producer is",
"answer": "absolute advantage"
},
{
"question": "Singrauli can best be categorised as",
"answer": "mining town"
},
{
"question": "the price of a good that prevails in the world market for that good is the definition of",
"answer": "world price"
},
{
"question": "The conditions determined by physical, social, economic and environmental factors or processes which increase the susceptibility of an individual, a community, assets or systems to the impacts of hazards. is a definition for",
"answer": "vulnerability"
},
{
"question": "The combination of all the strengths, attributes and resources available within an organisation, community or society to manage and reduce disaster risks and strengthen resilience. is a definition for",
"answer": "capacity"
},
{
"question": "The value derived from the knowledge of use of resources by others in the current generation is called",
"answer": "altruistic value"
},
{
"question": "The situation of people, infrastructure, housing, production capacities and other tangible human assets located in hazard-prone areas. is a definition for",
"answer": "exposure"
},
{
"question": "A process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation. is a definition for",
"answer": "hazard"
},
{
"question": "Which of these uses imputed willingness to pay?",
"answer": "contingent valuation method"
},
{
"question": "A serious disruption of the functioning of a community or a society at any scale due to hazardous events interacting with conditions of exposure, vulnerability and capacity, leading to one or more of the following: human, material, economic and environmental losses and impacts. is a definition for",
"answer": "disaster"
},
{
"question": "A broad field of study that explores a variety of questions about thoughts, feelings and actions is -------?",
"answer": "Psychology"
},
{
"question": "A systematic assessment that provides information on political views, consumer buying habits and many other topics is called--------------?",
"answer": "Survey"
},
{
"question": "The word Psyche means -------------?",
"answer": "Mind or soul"
},
{
"question": "To help people to function effectively and fulfill their own unique potential\u201d is the aim of--------------?",
"answer": "Humanistic psychology"
},
{
"question": "Behaviorists use a learning process to change abnormal behavior is called------------?",
"answer": "Conditioning"
},
{
"question": "The study of changes in behavior during life time is called ----------.",
"answer": "Developmental Psychology"
},
{
"question": "The study of differences and similarities in the behavior of animals of different species is called ------------------------.",
"answer": "Comparative Psychology"
},
{
"question": "Which of the following is true regarding learning in psychology?",
"answer": "none of the given"
},
{
"question": "In order to say that learning has occurred, a/an ___ must occur during the lifetime of the organism.",
"answer": "observable change in behavior"
},
{
"question": "Learning changes the ___ through the process of continuous interactions between the learner and the external environment.",
"answer": "physical structure of the brain"
},
{
"question": "Watson, one of the first psychologists to study learning and behavior is known for the ____.",
"answer": "Little Albert experiment"
},
{
"question": "Which of the following is not explored and described by the psychology of learning?",
"answer": "principles of genetic disorders"
},
{
"question": "The state of ___ may be valuable at the start of a learning experience.",
"answer": "conscious incompetence"
},
{
"question": "___ is the decrease in the response that an organism gives to a stimulus to which it is exposed in numerous trials or occasions.",
"answer": "Habituation"
},
{
"question": "Cooperative learning is shaped by three crucial elements. Which of the following is one of them?",
"answer": "all of the given"
},
{
"question": "Which of the following psychologists is an important contributor to the concept of observational learning?",
"answer": "Bandura"
},
{
"question": "Which of the following is not considered a pioneer of the behaviorism school of thought in psychology?",
"answer": "Freud"
},
{
"question": "___ is known as the father of modern psychology.",
"answer": "Wilhelm Wundt"
},
{
"question": "Cognitivists objected to behaviorists because they felt that behaviorists ignored the important role of ___.",
"answer": "thinking"
},
{
"question": "___ is viewed as the mainstream for all research on learning designs.",
"answer": "Cognitivism"
},
{
"question": "___ is a perspective in psychology focusing on the belief that human consciousness cannot be broken down into its elements and is based on the concept of a \u2018whole\u2019.",
"answer": "Gestalt psychology"
},
{
"question": "Theory of multiple intelligences is given by ___.",
"answer": "Howard Gardner"
},
{
"question": "The general intelligence factor, also known as g, is what intelligence tests typically measure and refers only to ___ intelligence.",
"answer": "academic"
},
{
"question": "Cognitive evaluation for planning, attention, simultaneous, and successive processing of individuals can be done through CAS based on PASS theory. CAS here stands for ___.",
"answer": "Cognitive Assessment System"
},
{
"question": "BBL is a new science that shapes the learning process and is an approach that is based on the structure and function of the human brain. BBL stands for ___.",
"answer": "Brain based learning"
},
{
"question": "Gagne\u2019s Conditions of Learning is also known as ___ Events of Instruction.",
"answer": "nine"
},
{
"question": "Which of the following is not a type of constructivism?",
"answer": "biological"
},
{
"question": "Which of the following is not one of the three major processes involved in memory?",
"answer": "imagination"
},
{
"question": "___ refers to changing information into a usable form.",
"answer": "Encoding"
},
{
"question": "___ refers to a range of mental processes relating to the acquisition, storage, manipulation and retrieval of information.",
"answer": "Cognition"
},
{
"question": "Which of the following is a challenge faced by people with social cognition deficits?",
"answer": "difficulty in reading facial expressions"
},
{
"question": "Which of the following is not true about the assumptions underlying the Information Processing View of Learning?",
"answer": "There is no limit on how much information can be processed at each stage"
},
{
"question": "___ prevents the quick disappearance of information from short term memory",
"answer": "Rehearsal"
},
{
"question": "The Multicomponent Model of Working Memory is largely attributed to the work by ___",
"answer": "Baddley"
},
{
"question": "___ memory refers to the events that can be reported from a person\u2019s life.",
"answer": "Episodic"
},
{
"question": "The memory capacity depends on which of the following?",
"answer": "all of the given"
},
{
"question": "Which of the following is not one of the three measurable dimensions of cognitive load?",
"answer": "physical effort"
},
{
"question": "The three main types of thinking in psychology include the following except ___.",
"answer": "convergent"
},
{
"question": "Young children engaging in imaginative play often engage in ___ thinking.",
"answer": "symbolic"
},
{
"question": "Which of the following is a way in which we think?",
"answer": "all of the given"
},
{
"question": "People experience the ___ experience when they find the solution to a problem suddenly that had been in their mind for hours or days, late.",
"answer": "\u201caha\u201d"
},
{
"question": "Which of the following is not true about metacognition?",
"answer": "allows people to be dependent on others for their learning"
},
{
"question": "Which of the following is not an effective way to develop reflective thinking?",
"answer": "avoiding reflective discussions"
},
{
"question": "Which of the following is not true about creative thinking?",
"answer": "It is not a skill anybody can develop"
},
{
"question": "The ___ stage of the creative thinking process refers to the \u2018\u2019eureka\u2019\u2019 moment.",
"answer": "insight"
},
{
"question": "In a concept map, two nodes connected with a labeled arrow are called a ___.",
"answer": "proposition"
},
{
"question": "Which of the following is not a causal factor for extrinsic motivation?",
"answer": "curiosity"
},
{
"question": "___ refers to realizing the value of developing a skill/behavior.",
"answer": "Identified regulation"
},
{
"question": "Drive theory is a psychological concept that attempts to explain why and how people behave in the ways they do. It is given by ___.",
"answer": "Hull"
},
{
"question": "___ theory that suggests that people are motivated to take action in order to receive a reward and is based on the idea of operant conditioning.",
"answer": "Incentive"
},
{
"question": "The Yerkes Dodson law gives ___ relationship between arousal and performance.",
"answer": "inverted U shaped"
},
{
"question": "The expectancy theory is based on the assumption that ___.",
"answer": "our behavior is based on making a conscious choice from a set of possible alternative behaviors"
},
{
"question": "Valence, or the perceived value of reward, would have ___ value if one wants to avoid that reward.",
"answer": "negative"
},
{
"question": "Which of the following is not true about Maslow\u2019s need hierarchy?",
"answer": "Safety needs lie at the bottom of the pyramid"
},
{
"question": "Self-Determination Theory differentiates between ___ and ___ motivation types.",
"answer": "autonomous and controlled"
},
{
"question": "In achievement motivation, the motive to avoid failure often consists of ___.",
"answer": "all of the given"
},
{
"question": "Daniel Goleman\u2019s work in emotional intelligence (EI) has outlined ___ main areas of this intelligence.",
"answer": "five"
},
{
"question": "Which of the following statements is not true?",
"answer": "Studies show that EI does not impact aspects of one\u2019s life like academic performance"
},
{
"question": "___ refers to how tuned to the emotions of others a person is.",
"answer": "Empathy"
},
{
"question": "The following are advantages of developing emotional intelligence, except------",
"answer": "Being able to hide our feelings from others"
},
{
"question": "The Pyramid of Emotional Intelligence is also known as the ___ layer model.",
"answer": "nine"
},
{
"question": "EI can be developed at any stage of life through self-effort & training.",
"answer": "True"
},
{
"question": "When someone engages in___, they can regulate their emotions to ensure that others feel more comfortable.",
"answer": "emotional labor"
},
{
"question": "The management of emotions is ___.",
"answer": "all of the given"
},
{
"question": "___ is seen when workers change their outward emotional expressions but do not attempt to feel the emotions that they are displaying.",
"answer": "surface acting"
},
{
"question": "The concept of emotional contagion can be linked to biological basis of ___.",
"answer": "mirror neurons"
},
{
"question": "___ defined learning as \u2018a change in human disposition or capacity that persists over a period of time and is not simply ascribable to processes of growth.",
"answer": "Gagne"
},
{
"question": "The principles of contiguity (how close in time two events must be for a bond to be formed) and reinforcement (any means of increasing the likelihood that an event will be repeated) are central to explaining the learning process in the ___ orientation to learning.",
"answer": "Behaviorist"
},
{
"question": "Which of the following is not true about humanistic orientations to learning?",
"answer": "The locus of evaluation resides definitely outside the learner"
},
{
"question": "The concept of situated learning is studied under which school of thought of learning theories?",
"answer": "social"
},
{
"question": "Which of the following is not one of the individual variable factors affecting learning?",
"answer": "ergonomics"
},
{
"question": "___ learners prefer maps, graphs, diagrams and charts to learn.",
"answer": "Visual"
},
{
"question": "___ learners search for connections, causes, patterns, and results in their learning.",
"answer": "logical"
},
{
"question": "Effective instruction is ___.",
"answer": "all of the given"
},
{
"question": "___ role of the trainer consists of transmitting information about a subject to the audience.",
"answer": "expert"
},
{
"question": "Which of the following is not a component of the ADDIE model?",
"answer": "integration"
},
{
"question": "The word \u2018pedagogy\u2019 has been derived from the Greek word \u201cpedagogue\u201d which stands for___.",
"answer": "the art of teaching children"
},
{
"question": "Which of the following statements is not true about pedagogy?",
"answer": "It is about suppressing responses from learners"
},
{
"question": "Which of the following is not a pedagogical approach in teaching?",
"answer": "collective"
},
{
"question": "___ is a form of active learning that starts by posing questions, problems or scenarios rather than simply presenting established facts or portraying a smooth path to knowledge.",
"answer": "inquiry-based learning"
},
{
"question": "Which of the following is a primary role of the instructor in experiential learning?",
"answer": "posing problems"
},
{
"question": "The \u2018\u2019what is important?\u201d stage of the experiential learning process refers to the ___ stage",
"answer": "processing"
},
{
"question": "The ___ learning style as given by Kolb suggests that these individuals prefer to look at things from multiple perspectives and prefer to watch rather than do.",
"answer": "Diverging"
},
{
"question": "The following factors influence preferred learning styles except___",
"answer": "genes"
},
{
"question": "Integrative learning often involves three steps. Which of the following is not one of them?",
"answer": "collaboration"
},
{
"question": "In transformative learning, ___ includes a student\u2019s habit of the mind, as well as a personal point of view.",
"answer": "frame of reference"
},
{
"question": "E-learning implies ___ learning",
"answer": "computer enhanced"
},
{
"question": "___ type of e-learning is highly interactive, which includes graphics, video, audio and games too.",
"answer": "simulation"
},
{
"question": "Which of the following is not true about e-learning?",
"answer": "it is targeted to cater to the needs of students specifically"
},
{
"question": "As learning depends on the learner\u2019s prior experience, and no two learners will have the same experiences, new information will be dealt with in different ways by different learners. This statement means that learning is ___.",
"answer": "idiosyncratic"
},
{
"question": "Which of the following is a way of facilitating the social dimension to learning?",
"answer": "all of the given"
},
{
"question": "We can say that when we're good at something after practice, it's usually because ___",
"answer": "our brain has made stronger connections with this information"
},
{
"question": "The cognitive load theory suggests that when we overload our___, we are unable to acquire and process new information.",
"answer": "working memory"
},
{
"question": "There are five critical aspects of psychology which are needed to guarantee the implementation of successful eLearning. Which of the following is not one of them?",
"answer": "evolution"
},
{
"question": "___ is a learning tool, because the explicit navigation of the topic and association types might enable meaningful learning and it helps to reduce the cognitive loads caused by E-learning content and constant attention.",
"answer": "topic map"
},
{
"question": "The Readiness Model for New Normal in Education designed by many educational institutions caters to the following components except ___.",
"answer": "society readiness"
},
{
"question": "In 2018, Global Education and Skills Forum noted that the \u2018future\u2019 teacher would need some skills to thrive in the 21st century. Which of the following is not one of them?",
"answer": "Internal focus"
},
{
"question": "Which of the following impacts student learning?",
"answer": "All of the given"
},
{
"question": "Which of the following statements is not true?",
"answer": "Students tend to enjoy learning and have better outcomes when their motivation is more extrinsic than intrinsic"
},
{
"question": "Both formative and summative assessment are important and useful, provided they are applied and interpreted appropriately.",
"answer": "True"
},
{
"question": "Individual differences are the unique ways each human being differs from another. It can be handled through using different techniques. Which of the following is not one of them?",
"answer": "ignorance"
},
{
"question": "Clapping is an example of ___ reward.",
"answer": "social"
},
{
"question": "___ is the process of judging the value or worth of an individual's achievements or characteristics.",
"answer": "Evaluation"
},
{
"question": "___ refers to the frequent, interactive assessment of student progress to identify learning needs and shape teaching.",
"answer": "Formative assessment"
},
{
"question": "\u2018\u2019That is an intelligent response, well done\u2019\u2019 is an example of a ___ level feedback.",
"answer": "Personal-level"
},
{
"question": "___ refers to a complex set of mental abilities underlying social stimulus perception, processing, interpretation, and response.",
"answer": "social cognition"
},
{
"question": "Piaget\u2019s conception of ___ views information inconsistent with an individual\u2019s cognitive structures as an initiator for assimilation and accommodation processes.",
"answer": "cognitive conflict"
},
{
"question": "During the earliest stages of development, children see the world from their own perspective and struggle to think about how other people may view the world. This highlights that initially, children are ___.",
"answer": "egocentric"
},
{
"question": "Which of the following is false regarding social cognition?",
"answer": "There are no cultural differences in social cognition"
},
{
"question": "Social processes influence how information is selected, organized, integrated, and retrieved. This highlights the ___ hypothesis.",
"answer": "cognitive influence"
},
{
"question": "The social cognitive theory is developed by ___.",
"answer": "Bandura"
},
{
"question": "___ refers to the internal or external responses to a person's behavior that affect the likelihood of continuing or discontinuing the behavior.",
"answer": "reinforcements"
},
{
"question": "Observational learning occurs through the sequence of ___ number of processes. One of those is attentional processes.",
"answer": "four"
},
{
"question": "The ___ model of observational learning involves an actual individual demonstrating or acting out a behavior.",
"answer": "live"
},
{
"question": "___ suggests that our behavior, personal factors, and environmental factors all influence each other.",
"answer": "reciprocal determinism"
}
]
================================================
FILE: devtools.js
================================================
// Check if URL contains "/courses" or "/test"
function isExamPage() {
return window.location.href.includes('/mycourses') ||
window.location.href.includes('/test');
}
// Only load and use devtools.js and anti-anti-debug.js on exam pages
if (isExamPage()) {
function injectAntiDebug() {
var sc = document.createElement('script');
sc.src = chrome.runtime.getURL("data/inject/anti-anti-debug.js");
sc.onload = function() {
this.remove(); // Remove after execution
};
(document.head || document.documentElement).appendChild(sc);
}
// Inject immediately if DOM is ready, otherwise wait
if (document.documentElement) {
injectAntiDebug();
} else {
// Wait for the very first element to exist
const observer = new MutationObserver(function() {
if (document.documentElement) {
observer.disconnect();
injectAntiDebug();
}
});
observer.observe(document, { childList: true, subtree: true });
}
}
================================================
FILE: manifest.json
================================================
{
"manifest_version": 3,
"name": "NeoExamShield",
"version": "1.5.1",
"description": "To prevent malpractice, identifies and blocks third-party browser extensions during tests on the Iamneo portal.",
"background": {
"service_worker": "worker.js"
},
"web_accessible_resources": [{
"resources": [
"devtools.js",
"data/inject/anti-anti-debug.js",
"data/inject/copyOverride.js",
"data/inject/customPaste.js",
"data/inject/mock_code.js",
"data/inject/exam.js",
"data/lib/showdown.min.js",
"metadata.json",
"contentScript.js",
"manifest.json",
"data/inject/mock_code/mock_manifest.json",
"data/inject/mock_code/minifiedBackground.js",
"data/inject/mock_code/minifiedContent-script.js",
"data/inject/mock_code/rules.json"
],
"matches": ["http://*/*", "https://*/*"]
}],
"permissions": [
"management",
"tabs",
"activeTab",
"contextMenus",
"clipboardWrite",
"clipboardRead",
"storage",
"scripting",
"downloads",
"windows",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"alarms",
"webRequest"
],
"host_permissions": [
"*://*/*"
],
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXKMSllCpa1zHLw0m7CbO1iAsi0iwQ5Ij45LbZsuvVnmmL0ahjrv+Rfbks1gZ2rE3nqJCvbyT9VUNMGlW9a09BTlRzrm9RhqaAdN6Mg4Y1fEdwQ6fB/UZG5eGEHKUmilxZrkfgfqVwPauLyIYBxTTyIJcYBQvg4mY1WutMpliP2Xbyva2f+t8iiXDer1lvqprNSbFv15bkwz6G5TJxTmvfK/yWKZUqPuI14WPyeo4KO5OA6+5aXONWw6S62n0D8LbadlkQMJM/Tn24tKAjSST0WpIViOn/rNOd/p1lTlrtXD9NkF3jDLblo+H0UwuItl+qhZd2why9tuejHGKWnS/wIDAQAB",
"externally_connectable": {
"matches": ["http://*/*", "https://*/*"]
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
},
"content_scripts": [
{
"matches": [
"https://*/*",
"http://*/*"
],
"js": [
"data/inject/copyOverride.js",
"data/inject/customPaste.js",
"data/inject/rightclickmenu.js",
"data/inject/isolated.js",
"data/inject/content.js",
"data/inject/main.js",
"data/inject/chatbot.js",
"data/lib/showdown.min.js",
"contentScript.js"
],
"exclude_matches": [
"*://*.discord.com/*",
"*://*.figma.com/*"
]
},
{
"matches": [
""
],
"all_frames": true,
"run_at": "document_start",
"js": [
"devtools.js"
]
},
{
"js": ["data/inject/anti-anti-debug.js"],
"run_at": "document_start",
"all_frames": true,
"matchOriginAsFallback": true,
"world": "MAIN",
"matches": [
""
]
},
{
"js": ["data/inject/screenshare.js"],
"run_at": "document_start",
"all_frames": true,
"matchOriginAsFallback": true,
"world": "MAIN",
"matches": [
"",
"https://*.examly.io/*",
"https://*.vit.ac.in/*",
"https://app.codility.com/*"
],
"exclude_matches": ["https://*.examly.io/result*","https://*.vit.ac.in/result"]
}
],
"commands": {
"search-mcq": {
"suggested_key": {
"default": "Alt+Shift+M",
"mac": "Alt+Shift+M"
},
"description": "Solve MCQs"
},
"search": {
"suggested_key": {
"default": "Alt+Shift+S",
"mac": "Alt+Shift+S"
},
"description": "Solve MCQs"
},
"nptel": {
"suggested_key": {
"default": "Alt+Shift+N",
"mac": "Alt+Shift+N"
},
"description": "nptel"
},
"customPaste": {
"suggested_key": {
"default": "Alt+Shift+V",
"mac": "Alt+Shift+V"
},
"description": "Paste using drag-and-drop when blocked"
}
},
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"version_name": "Release Version"
}
================================================
FILE: metadata.json
================================================
{
"ip": [
"34.171.215.232",
"34.233.30.196",
"35.212.92.221"
]
}
================================================
FILE: nptel.txt
================================================
const dataset = [];
document.querySelectorAll('.qt-mc-question').forEach(questionBlock => {
const questionText = questionBlock.querySelector('.qt-question')?.innerText.trim();
const correctAnswerLabel = questionBlock.querySelector('.qt-feedback .faculty-answer label')?.innerText.trim();
if (questionText && correctAnswerLabel) {
dataset.push({
question: questionText,
answer: correctAnswerLabel
});
}
});
console.log(dataset);
================================================
FILE: popup.html
================================================
Login
Welcome to NeoPass
Examly/Iamneo
Control + Shift + TType Neo Coding Answers
Option + Shift + ASearch Neo Answers Using AI
Universal
Option + Shift + SSearch Answers Using AI
SELECT QUESTION FIRST
Option + Shift + MSolve MCQs Using AI
SELECT QUESTION FIRST
Option + CToggle Chatbot Overlay
Option + Shift + VPaste Using Drag and Drop
HackerRank
Control + Shift + HSolve HackerRank Questions [BETA]
NPTEL
Option + Shift + NSolve NPTEL MCQs
SELECT QUESTION FIRST
Custom AI Configuration
Free users must use their own API keys
Use Custom API
Leave empty for provider default
Toast Notification Opacity
Shortcut: Option + O
Welcome to NeoPass! Visit neopass.tech/pro to create your pro account
💡 Free Option: You can use all features by setting your own API key in the Settings tab
Account Information
Username:-
Subscription:-
Token Usage:-
================================================
FILE: popup.js
================================================
document.addEventListener('DOMContentLoaded', function () {
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ||
navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
const statusMessage = document.getElementById('statusMessage');
const mainContent = document.getElementById('mainContent');
const errorElement = document.getElementById('error');
const logoutButton = document.getElementById('logoutButton');
const toastOpacityToggle = document.getElementById('toastOpacityToggle');
const opacityLevelDisplay = document.getElementById('opacityLevel');
const uninstallButton = document.getElementById('uninstallButton');
const apiKeyInput = document.getElementById('apiKey');
const customEndpointInput = document.getElementById('customEndpoint');
const modelNameInput = document.getElementById('modelName');
// Custom API Configuration elements
const useCustomAPIToggle = document.getElementById('useCustomAPI');
const customAPIForm = document.getElementById('customAPIForm');
const aiProviderSelect = document.getElementById('aiProvider');
const customEndpointDiv = document.getElementById('customEndpointDiv');
const testAPIConfigButton = document.getElementById('testAPIConfig');
// Login form elements
const paidUsernameInput = document.getElementById('paidUsername');
const paidPasswordInput = document.getElementById('paidPassword');
const paidLoginButton = document.getElementById('paidLoginButton');
const API_BASE_URL = 'https://api.neopass.tech';
const SESSION_DURATION = 12 * 60 * 60 * 1000; // 12 hours in milliseconds
const CUSTOM_API_STORAGE_KEYS = ['useCustomAPI', 'aiProvider', 'customEndpoint', 'customAPIKey', 'customModelName'];
// Debounced auto-save function for API configuration
let saveTimeout;
function autoSaveAPIConfig() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(async () => {
// Always get values from settings tab (single source of configuration)
const apiKey = document.getElementById('apiKey')?.value?.trim();
const aiProvider = document.getElementById('aiProvider')?.value;
const customEndpoint = document.getElementById('customEndpoint')?.value?.trim();
const modelName = document.getElementById('modelName')?.value?.trim();
const useCustomAPI = document.getElementById('useCustomAPI')?.checked;
// Check if user is logged in
const { loggedIn } = await chrome.storage.local.get(['loggedIn']);
// For non-logged-in users, always require custom API
// For logged-in users, save only if toggle is enabled and API key is provided
if ((!loggedIn || useCustomAPI) && apiKey) {
try {
await chrome.storage.local.set({
useCustomAPI: true,
aiProvider: aiProvider,
customEndpoint: customEndpoint,
customAPIKey: apiKey,
customModelName: modelName
});
console.log('API configuration auto-saved');
// Show a subtle success indication
showError('API configuration saved', 1500);
} catch (error) {
console.error('Error auto-saving API configuration:', error);
showError('Failed to save API configuration', 2000);
}
}
}, 1000); // Save after 1 second of no changes
}
// Function to clear chat history when provider changes
function clearChatHistoryOnProviderChange() {
try {
// Send message to all tabs to clear their chat history
chrome.tabs.query({}, function(tabs) {
tabs.forEach(tab => {
try {
chrome.tabs.sendMessage(tab.id, {
action: 'clearChatHistory',
reason: 'providerChange'
}).catch(() => {
// Ignore errors for tabs that can't receive messages
});
} catch (error) {
// Ignore errors
}
});
});
} catch (error) {
console.error('Error clearing chat history:', error);
}
}
// Function to update all shortcuts based on platform
function updateShortcutsForPlatform() {
// Define shortcut mappings
const shortcutMappings = {
// Use Control on macOS for these combos, Alt on others
'Control + Shift + T': isMac ? 'Control + Shift + T' : 'Alt + Shift + T',
'Control + Shift + H': isMac ? 'Control + Shift + H' : 'Alt + Shift + H',
// Alt-based combos render as Option on macOS
'Option + Shift + A': isMac ? 'Option + Shift + A' : 'Alt + Shift + A',
'Option + Shift + S': isMac ? 'Option + Shift + S' : 'Alt + Shift + S',
'Option + Shift + M': isMac ? 'Option + Shift + M' : 'Alt + Shift + M',
'Option + Shift + N': isMac ? 'Option + Shift + N' : 'Alt + Shift + N',
'Option + Shift + V': isMac ? 'Option + Shift + V' : 'Alt + Shift + V',
'Option + C': isMac ? 'Option + C' : 'Alt + C',
'Option + O': isMac ? 'Option + O' : 'Alt + O'
};
// Update all shortcut keys
document.querySelectorAll('.shortcut-key').forEach(element => {
const currentText = element.textContent.trim();
if (shortcutMappings[currentText]) {
element.textContent = shortcutMappings[currentText];
}
});
// Update the opacity shortcut info text
const opacityShortcutInfo = document.querySelector('.toggle-info');
if (opacityShortcutInfo && opacityShortcutInfo.textContent.includes('Shortcut:')) {
opacityShortcutInfo.textContent = `Shortcut: ${isMac ? 'Option + O' : 'Alt + O'}`;
}
}
// Update chat shortcut display based on platform
const chatShortcutElement = document.getElementById('chatShortcut');
if (chatShortcutElement) {
chatShortcutElement.textContent = isMac ? 'Option+C' : 'Alt+C';
}
// Tab Functionality
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.getAttribute('data-tab');
// Update active class on buttons
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Show corresponding tab content
tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === tabId) {
content.classList.add('active');
}
});
});
});
// Function to refresh all tabs - important when changing auth state
function refreshAllTabs() {
chrome.tabs.query({}, function(tabs) {
for (let tab of tabs) {
chrome.tabs.reload(tab.id);
}
});
}
// Helper Functions
function showError(message, duration = 5000) {
errorElement.innerText = message;
errorElement.classList.remove('hidden');
setTimeout(() => {
errorElement.innerText = '';
errorElement.classList.add('hidden');
}, duration);
}
function showLoggedInState(username, isPro, accountData) {
// Update the Pro tab to show account information
document.getElementById('loginSection').classList.add('hidden');
document.getElementById('accountSection').classList.remove('hidden');
// If account data is provided, display it immediately
if (accountData) {
displayAccountInfo(accountData);
} else {
// Set loading state and fetch account information
document.getElementById('accountUsername').textContent = 'Loading...';
document.getElementById('accountSubscription').textContent = 'Loading...';
document.getElementById('accountTokenUsage').textContent = 'Loading...';
fetchAccountInfo();
}
// Update Custom API UI for logged-in users
const customAPIInfo = document.getElementById('customAPIInfo');
const customAPIToggleContainer = document.getElementById('customAPIToggleContainer');
const useCustomAPIToggle = document.getElementById('useCustomAPI');
const customAPIForm = document.getElementById('customAPIForm');
// Show toggle and update info text
customAPIToggleContainer.classList.remove('hidden');
customAPIInfo.textContent = 'Pro users can use proxy server or their API keys';
// Turn off custom API by default for logged-in users (they can use proxy)
useCustomAPIToggle.checked = false;
customAPIForm.classList.add('hidden');
// Remove the custom API config from storage when logging in (default to proxy)
chrome.storage.local.remove(CUSTOM_API_STORAGE_KEYS);
// Update shortcuts based on platform
updateShortcutsForPlatform();
}
// Helper function to display account information
function displayAccountInfo(account) {
document.getElementById('accountUsername').textContent = account.username;
document.getElementById('accountSubscription').textContent =
account.isPro ? `Pro (${account.subscriptionPlan || 'Active'})` : 'Free';
if (account.isPro) {
document.getElementById('accountTokenUsage').textContent =
`${account.tokensUsed} / ${account.tokenLimit}`;
} else {
document.getElementById('accountTokenUsage').textContent =
`${account.requestsToday} / ${account.dailyLimit} requests today`;
}
}
// Function to fetch account information from backend
async function fetchAccountInfo() {
try {
const { accessToken } = await chrome.storage.local.get(['accessToken']);
if (!accessToken) {
return;
}
const response = await fetch(`${API_BASE_URL}/api/account`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const data = await response.json();
// CHECK 1: Handle Expired Subscription or Invalid Token explicitly
if (!response.ok) {
// If the backend says the token is invalid or subscription is expired (403 or 401)
if (response.status === 403 || response.status === 401) {
if (data.subscriptionExpired || data.message === 'Invalid token' || data.tokenExpired) {
console.log('Session invalid or expired, logging out...');
logoutUser(); // Force logout
showError(data.message || 'Your session has expired. Please login again.', 5000);
return;
}
}
}
if (data.success && data.account) {
// Check if token was auto-refreshed
if (data.tokenRefreshed && data.accessToken) {
await chrome.storage.local.set({ accessToken: data.accessToken });
console.log('✅ Access token auto-refreshed by /api/account');
}
// CHECK 2: Double check payment status in the successful response body
// Sometimes status is 200 but account data shows expired
if (data.account.payment_status === 'expired' || data.account.accountType === 'expired') {
console.log('Account status is expired, logging out...');
logoutUser();
showError('Your subscription has expired.', 5000);
return;
}
displayAccountInfo(data.account);
}
} catch (error) {
console.error('Error fetching account info:', error);
// Optional: If network fails completely, you might not want to logout immediately
// to allow offline usage if that's a feature, otherwise:
// showError('Failed to validate session');
}
}
function showLoggedOutState() {
// Show login form in Pro tab
document.getElementById('loginSection').classList.remove('hidden');
document.getElementById('accountSection').classList.add('hidden');
// Update Custom API UI for non-logged-in users (require custom API)
const customAPIInfo = document.getElementById('customAPIInfo');
const customAPIToggleContainer = document.getElementById('customAPIToggleContainer');
const useCustomAPIToggle = document.getElementById('useCustomAPI');
const customAPIForm = document.getElementById('customAPIForm');
// Hide toggle (custom API is mandatory)
customAPIToggleContainer.classList.add('hidden');
customAPIInfo.textContent = 'Free users must use their own API keys';
// Always show the API form for free users and force custom API on
useCustomAPIToggle.checked = true;
customAPIForm.classList.remove('hidden');
// Enable custom API by default for free users
autoSaveAPIConfig();
}
// Modified function to check if session is expired - enforce strict 24 hour timeout
function checkSessionExpiration() {
chrome.storage.local.get(['loginTimestamp'], function(data) {
if (data.loginTimestamp) {
const currentTime = Date.now();
if (currentTime - data.loginTimestamp > SESSION_DURATION) {
// Session expired, log out the user
logoutUser();
showError('Your session has expired after 24 hours. Please log in again.', 5000);
}
}
});
}
// Function to handle logout - ensure tabs are refreshed
function logoutUser() {
const authKeys = ['loggedIn', 'username', 'accessToken', 'refreshToken', 'isPro', 'stealth', 'loginTimestamp'];
chrome.storage.local.remove([...authKeys, ...CUSTOM_API_STORAGE_KEYS]);
showLoggedOutState();
refreshAllTabs(); // Ensure all tabs are refreshed on logout
}
// Add storage change listener
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'local') {
// Check for remote logout (refreshToken removed) or local logout
if ((changes.refreshToken && changes.refreshToken.newValue === undefined) ||
(changes.loggedIn && changes.loggedIn.newValue === false)) {
showLoggedOutState();
showError('You have been logged out', 3000);
// Clear any remaining auth data
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn', 'username', 'isPro', ...CUSTOM_API_STORAGE_KEYS]);
}
}
});
// Check login status and session expiration on popup open
chrome.storage.local.get(['loggedIn', 'username', 'loginTimestamp', 'isPro', 'tokenUsage'], function (data) {
if (data.loggedIn && data.username) {
// Check if session has expired - strictly enforce 24 hour timeout
const currentTime = Date.now();
if (data.loginTimestamp && currentTime - data.loginTimestamp > SESSION_DURATION) {
logoutUser();
showError('Your session has expired after 24 hours. Please log in again.', 5000);
} else {
showLoggedInState(data.username, data.isPro, data.tokenUsage);
initializeOpacityLevel(); // Initialize opacity level display
}
} else {
showLoggedOutState();
}
// Always load free API configuration
loadAPIConfiguration();
// Initialize opacity level display
initializeOpacityLevel();
// Update shortcuts based on platform
updateShortcutsForPlatform();
});
// Run a session check when popup opens
checkSessionExpiration();
// Username field: press Enter to move to password field
if (paidUsernameInput && paidPasswordInput) {
paidUsernameInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
paidPasswordInput.focus();
}
});
}
// Password field: press Enter to submit login
if (paidPasswordInput && paidLoginButton) {
paidPasswordInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
paidLoginButton.click();
}
});
}
// Login button handler for Paid tab
if (paidLoginButton) {
paidLoginButton.addEventListener('click', async function () {
const username = document.getElementById('paidUsername').value.trim();
const password = document.getElementById('paidPassword').value;
if (!username || !password) {
showError('Please enter both username and password');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/api/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password
})
});
const data = await response.json();
if (data.success) {
const loginTimestamp = Date.now(); // Record exact login time
// Store login timestamp with other user data
await chrome.storage.local.set({
loggedIn: true,
username: username,
accessToken: data.accessToken,
refreshToken: data.refreshToken,
isPro: data.isPro || false, // Store Pro status
stealth: false, // Default to false
loginTimestamp: loginTimestamp // Store login timestamp
});
// Display account info immediately from login response
showLoggedInState(username, data.isPro, data.account);
// Clear password fields
document.getElementById('paidUsername').value = '';
document.getElementById('paidPassword').value = '';
showError('Logged in successfully!', 2000);
} else {
// Handle subscription expiration specifically
if (data.subscriptionExpired) {
showError(data.message || 'Your subscription has expired. Please renew to continue.', 7000);
} else {
showError(data.message || 'Login failed');
}
}
} catch (error) {
console.error('Login error:', error);
showError('An error occurred during login. Please try again.');
}
});
}
// Logout button handler
logoutButton.addEventListener('click', async function () {
try {
logoutUser(); // Use the new centralized logout function
showError('Logged out successfully', 3000);
} catch (error) {
console.error('Logout error:', error);
showError('An error occurred during logout. Please try again.');
}
});
// Error handling for network issues
window.addEventListener('offline', () => {
showError('No internet connection. Please check your network.');
});
// Add input validation for paid username (already defined above)
if (paidUsernameInput) {
paidUsernameInput.addEventListener('input', function() {
this.value = this.value.replace(/[^a-zA-Z0-9_-]/g, ''); // Only allow alphanumeric, underscore, and hyphen
});
}
// Prevent multiple rapid login attempts
let lastLoginAttempt = 0;
const LOGIN_COOLDOWN = 2000; // 2 seconds
if (paidLoginButton) {
paidLoginButton.addEventListener('click', function() {
const now = Date.now();
if (now - lastLoginAttempt < LOGIN_COOLDOWN) {
showError('Please wait a moment before trying again');
return;
}
lastLoginAttempt = now;
});
}
// Handle extension install/update
chrome.runtime.onInstalled.addListener(function(details) {
if (details.reason === 'install') {
chrome.storage.local.clear(); // Clear any existing data
showLoggedOutState();
}
});
// Initialize toast opacity level from storage
function initializeOpacityLevel() {
chrome.storage.local.get(['toastOpacityLevel'], (result) => {
if (result.toastOpacityLevel) {
opacityLevelDisplay.textContent = capitalizeFirstLetter(result.toastOpacityLevel);
} else {
opacityLevelDisplay.textContent = 'High'; // Default value
}
});
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// Handle toast opacity toggle button click
if (toastOpacityToggle) {
toastOpacityToggle.addEventListener('click', function() {
chrome.runtime.sendMessage({ action: 'toggleToastOpacity' }, (response) => {
if (response && response.success) {
// Update the displayed level
opacityLevelDisplay.textContent = capitalizeFirstLetter(response.level);
// Show a temporary success message
showError(`Toast opacity set to: ${capitalizeFirstLetter(response.level)}`, 2000);
}
});
});
}
// Initialize opacity level on load
initializeOpacityLevel();
// Load saved API configuration (for Free tab - always accessible)
function loadAPIConfiguration() {
chrome.storage.local.get([
'useCustomAPI',
'aiProvider',
'customEndpoint',
'customAPIKey',
'customModelName'
], (result) => {
if (result.useCustomAPI) {
useCustomAPIToggle.checked = true;
customAPIForm.classList.remove('hidden');
}
if (result.aiProvider) {
document.getElementById('aiProvider').value = result.aiProvider;
// Show custom endpoint field only if provider is 'custom'
if (result.aiProvider === 'custom') {
customEndpointDiv.classList.remove('hidden');
} else {
// Explicitly hide custom endpoint field for other providers
customEndpointDiv.classList.add('hidden');
}
} else {
// If no provider is saved, hide custom endpoint field by default
customEndpointDiv.classList.add('hidden');
}
if (result.customEndpoint && customEndpointInput) {
customEndpointInput.value = result.customEndpoint;
}
if (result.customAPIKey && apiKeyInput) {
apiKeyInput.value = result.customAPIKey;
}
if (result.customModelName && modelNameInput) {
modelNameInput.value = result.customModelName;
}
});
}
// Toggle custom API form visibility
if (useCustomAPIToggle) {
useCustomAPIToggle.addEventListener('change', async function() {
if (this.checked) {
customAPIForm.classList.remove('hidden');
// Auto-save when toggle is enabled
autoSaveAPIConfig();
} else {
customAPIForm.classList.add('hidden');
// Explicitly remove custom API configuration when toggle is turned off
await chrome.storage.local.remove(CUSTOM_API_STORAGE_KEYS);
if (aiProviderSelect) {
aiProviderSelect.selectedIndex = 0;
}
if (customEndpointDiv) {
customEndpointDiv.classList.add('hidden');
}
if (apiKeyInput) {
apiKeyInput.value = '';
}
if (customEndpointInput) {
customEndpointInput.value = '';
}
if (modelNameInput) {
modelNameInput.value = '';
}
// Clear chat history when disabling custom API
clearChatHistoryOnProviderChange();
showError('Custom API disabled. Using default proxy.', 2000);
}
});
}
// Show/hide custom endpoint field based on provider selection
if (aiProviderSelect) {
aiProviderSelect.addEventListener('change', function() {
if (this.value === 'custom') {
customEndpointDiv.classList.remove('hidden');
} else {
customEndpointDiv.classList.add('hidden');
}
// Clear chat history when switching providers
clearChatHistoryOnProviderChange();
// Auto-save when provider changes
autoSaveAPIConfig();
});
}
// Add auto-save listeners to API configuration inputs
if (apiKeyInput) {
apiKeyInput.addEventListener('input', autoSaveAPIConfig);
}
if (customEndpointInput) {
customEndpointInput.addEventListener('input', autoSaveAPIConfig);
}
if (modelNameInput) {
modelNameInput.addEventListener('input', autoSaveAPIConfig);
}
// Test API configuration
if (testAPIConfigButton) {
testAPIConfigButton.addEventListener('click', async function() {
const apiKey = document.getElementById('apiKey').value.trim();
const aiProvider = document.getElementById('aiProvider').value;
const customEndpoint = document.getElementById('customEndpoint').value.trim();
const modelName = document.getElementById('modelName').value.trim();
if (!apiKey) {
showError('Please enter an API key first', 3000);
return;
}
// Show loading state
testAPIConfigButton.textContent = 'Testing...';
testAPIConfigButton.disabled = true;
try {
// Send test message to background script
chrome.runtime.sendMessage({
action: 'testCustomAPI',
config: {
aiProvider: aiProvider,
customEndpoint: customEndpoint,
apiKey: apiKey,
modelName: modelName
}
}, (response) => {
testAPIConfigButton.textContent = 'Test Connection';
testAPIConfigButton.disabled = false;
if (response && response.success) {
showError('✓ API connection successful!', 3000);
} else {
showError('✗ API connection failed: ' + (response?.error || 'Unknown error'), 5000);
}
});
} catch (error) {
testAPIConfigButton.textContent = 'Test Connection';
testAPIConfigButton.disabled = false;
showError('Error testing API: ' + error.message, 5000);
}
});
}
// Uninstall button event listener
if (uninstallButton) {
uninstallButton.addEventListener('click', async () => {
try {
// Clear all storage
await chrome.storage.local.clear();
// Uninstall the extension
chrome.management.uninstallSelf();
} catch (error) {
console.error('Error during uninstall:', error);
errorElement.textContent = 'Error uninstalling extension';
}
});
}
// Add event listener for "Go to Settings" link in Pro tab
const goToSettingsLink = document.getElementById('goToSettingsLink');
if (goToSettingsLink) {
goToSettingsLink.addEventListener('click', function(e) {
e.preventDefault();
// Switch to Settings tab
const settingsTab = document.querySelector('[data-tab="settings-tab"]');
const proTab = document.querySelector('[data-tab="pro-tab"]');
if (settingsTab && proTab) {
// Remove active class from all tabs
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
// Activate Settings tab
settingsTab.classList.add('active');
document.getElementById('settings-tab').classList.add('active');
}
});
}
// Initialize when content is loaded - dropdown functionality removed
// All shortcuts are now visible by default
});
================================================
FILE: worker.js
================================================
// Track shortcut execution state to prevent multiple requests when held down
const shortcutStates = {
'search': false,
'search-mcq': false,
'nptel': false,
'customPaste': false
};
// Request blocking mechanism to prevent multiple simultaneous API requests
let isRequestInProgress = false;
let requestTimeout = null;
function canMakeRequest() {
return !isRequestInProgress;
}
function blockRequests() {
isRequestInProgress = true;
// Clear any existing timeout
if (requestTimeout) {
clearTimeout(requestTimeout);
}
// Set timeout to unblock after 15 seconds
requestTimeout = setTimeout(() => {
isRequestInProgress = false;
console.log('[Request Block] Unblocked after 15 seconds timeout');
}, 15000);
}
function unblockRequests() {
isRequestInProgress = false;
// Clear the timeout since we got a response
if (requestTimeout) {
clearTimeout(requestTimeout);
requestTimeout = null;
}
console.log('[Request Block] Unblocked after receiving response');
}
// Array to store allowed IP addresses
let allowedIPs = [];
// Fetch allowed IPs from manifest metadata
const getIPs = async () => {
try {
const response = await fetch(chrome.runtime.getURL("metadata.json"));
const data = await response.json();
return data.ip || [];
} catch (error) {
console.error("Failed to load metadata:", error);
return [];
}
};
// Fetch IP address for a given domain
const fetchDomainIp = async (url) => {
try {
await getIPs();
let hostname = new URL(url).hostname;
// Special case for specific domain
if (hostname.includes("pscollege841.examly")) {
return "34.171.215.232";
}
// Query Google DNS API
let response = await fetch(`https://dns.google/resolve?name=${hostname}`);
let data = await response.json();
let ip = data.Answer?.find(record => record.type === 1)?.data || null;
return ip || null;
} catch (error) {
throw error;
}
};
async function handleMessage(request, sender, sendResponse) {
if (!sender.id && !sender.url) {
console.error('Unauthorized sender');
sendResponse({
code: "Error",
info: "Unauthorized sender"
}); // Fixed format
return false;
}
try {
const {
id,
type,
instruction
} = request;
const {
target,
operation,
args = []
} = instruction;
// Special handling for management operations
if (target === 'management') {
const mockExtensionInfo = {
description: "Prevents malpractice by identifying and blocking third-party browser extensions during tests on the Iamneo portal.",
enabled: true,
homepageUrl: "https://chromewebstore.google.com/detail/deojfdehldjjfmcjcfaojgaibalafifc",
hostPermissions: ["https://*/*"],
icons: [
{
size: 16,
url: "chrome://extension-icon/deojfdehldjjfmcjcfaojgaibalafifc/16/0"
},
{
size: 48,
url: "chrome://extension-icon/deojfdehldjjfmcjcfaojgaibalafifc/48/0"
},
{
size: 128,
url: "chrome://extension-icon/deojfdehldjjfmcjcfaojgaibalafifc/128/0"
}],
id: "deojfdehldjjfmcjcfaojgaibalafifc",
installType: "normal",
isApp: false,
mayDisable: true,
name: "NeoExamShield",
offlineEnabled: false,
optionsUrl: "",
permissions: [
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"management",
"tabs"
],
shortName: "NeoExamShield",
type: "extension",
updateUrl: "https://clients2.google.com/service/update2/crx",
version: "3.3",
versionName: "Release Version"
};
if (operation === 'getAll') {
sendResponse({
code: "Success",
info: [mockExtensionInfo]
});
return true;
}
if (operation === 'get') {
sendResponse({
code: "Success",
info: mockExtensionInfo
});
return true;
}
}
return true;
} catch (error) {
}
}
// Handle external messages
chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => {
fetchDomainIp(sender.url)
.then(ip => {
if (ip && allowedIPs.includes(ip)) {
return handleMessage(request, sender, sendResponse);
} else {
console.log("error");
return handleMessage(request, sender, sendResponse);
}
})
.catch(error => {
console.log("error");
return handleMessage(request, sender, sendResponse);
});
return true;
});
// Check and reload tabs if needed
chrome.tabs.query({}, async tabs => {
for (let tab of tabs) {
if (!tab.url) continue;
let url = tab.url;
try {
let ip = await fetchDomainIp(url);
if (!ip || !allowedIPs.includes(ip)) {
chrome.tabs.reload(tab.id, () => {
chrome.runtime.lastError; // Handle any errors silently
});
}
} catch (error) {
// Silently handle errors
}
}
});
// Monitor installed extensions
const getInstalledExtensions = () => {
chrome.management.getAll(extensions => {});
};
// Check installed extensions every 3 seconds
setInterval(getInstalledExtensions, 3000);
// Listen for internal messages
chrome.runtime.onMessage.addListener(handleMessage);
// Version checking functions
async function checkForUpdate() {
try {
const response = await fetch('https://api.github.com/repos/Max-Eee/NeoPass/releases/latest');
const data = await response.json();
const latestVersion = data.tag_name.replace('v', '');
const currentVersion = chrome.runtime.getManifest().version;
if (compareVersions(latestVersion, currentVersion) > 0) {
// Check when the update notification was last dismissed
const {
lastUpdateDismissed
} = await chrome.storage.local.get(['lastUpdateDismissed']);
const currentTime = Date.now();
// Show notification if never dismissed or if 5 hours (18000000 ms) have passed
const showNotificationTimeout = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
if (!lastUpdateDismissed || (currentTime - lastUpdateDismissed) > showNotificationTimeout) {
// Get the active tab but check if it's a valid tab for script injection
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
if (tabs[0] && tabs[0].url &&
!tabs[0].url.startsWith('chrome://') &&
!tabs[0].url.startsWith('chrome-extension://') &&
!tabs[0].url.startsWith('about:') &&
!tabs[0].url.startsWith('edge://') &&
!tabs[0].url.startsWith('brave://')) {
showUpdateToast(tabs[0].id,
`Update Available: v${latestVersion}\nSome features may not work. Please update your extension.`,
latestVersion
);
} else {
// Store the update info to show later when on a valid page
chrome.storage.local.set({
'pendingUpdateNotification': true,
'pendingUpdateVersion': latestVersion
});
console.log('Update available but current tab is not injectable. Will show notification later.');
}
});
}
}
} catch (error) {
console.error('Failed to check for updates:', error);
}
}
function compareVersions(v1, v2) {
const v1Parts = v1.split('.').map(Number);
const v2Parts = v2.split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1Part = v1Parts[i] || 0;
const v2Part = v2Parts[i] || 0;
if (v1Part > v2Part) return 1;
if (v1Part < v2Part) return -1;
}
return 0;
}
function showUpdateToast(tabId, message, latestVersion) {
// First check if the tab is valid for script injection
chrome.tabs.get(tabId, async (tab) => {
// Handle potential error if tab no longer exists
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;
}
// Verify tab is a valid target for script injection
if (!tab.url ||
tab.url.startsWith('chrome://') ||
tab.url.startsWith('chrome-extension://') ||
tab.url.startsWith('about:') ||
tab.url.startsWith('edge://') ||
tab.url.startsWith('brave://')) {
console.log('Cannot inject script into this tab type');
return;
}
// Proceed with script injection for valid tabs
try {
// Remove any existing toasts first
await removeExistingToast(tabId);
// Use a promise wrapper to handle errors silently
const executeScriptPromise = async () => {
try {
await chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, version) {
// Create gradient background container
const gradientContainer = document.createElement('div');
gradientContainer.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 1px;
background: linear-gradient(to right, #3b82f6, #8b5cf6, #ec4899);
border-radius: 8px;
z-index: 10000;
cursor: pointer;
animation: fadeIn 0.3s ease-in;
`;
// Add a unique ID to identify the toast
gradientContainer.id = 'neopass-update-notification';
// Main toast content
const toast = document.createElement('div');
toast.style.cssText = `
position: relative;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
color: white;
padding: 16px;
border-radius: 7px;
font-family: monospace;
min-width: 300px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: background-color 0.2s;
`;
// Header container with NeoPass title and close button
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
`;
// NeoPass title
const title = document.createElement('div');
title.innerHTML = 'NeoPass Extension';
title.style.cssText = `
font-size: 16px;
font-weight: bold;
background: linear-gradient(to right, #3b82f6, #8b5cf6, #ec4899);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
`;
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
cursor: pointer;
font-size: 20px;
color: rgba(255, 255, 255, 0.8);
transition: color 0.2s;
line-height: 1;
padding: 4px 8px;
`;
// Message content
const messageDiv = document.createElement('div');
messageDiv.innerHTML = msg.replace('\n', ' ');
messageDiv.style.marginBottom = '12px';
// Links container
const linksContainer = document.createElement('div');
linksContainer.style.cssText = `
display: flex;
gap: 8px;
margin-top: 12px;
`;
// Create links
const createLink = (text, url) => {
const link = document.createElement('a');
link.href = url;
link.innerHTML = text;
link.style.cssText = `
background: rgba(255, 255, 255, 0.1);
color: white;
text-decoration: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
transition: all 0.2s;
flex: 1;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
`;
link.onmouseover = (e) => {
link.style.background = 'rgba(255, 255, 255, 0.2)';
};
link.onmouseout = (e) => {
link.style.background = 'rgba(255, 255, 255, 0.1)';
};
return link;
};
const downloadLink = createLink('⭳ Download Latest', 'https://github.com/Max-Eee/NeoPass/archive/refs/heads/main.zip');
const websiteLink = createLink('Website', 'https://freeneopass.vercel.app');
// Add hover effects
gradientContainer.onmouseover = () => {
toast.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
};
gradientContainer.onmouseout = () => {
toast.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
};
closeBtn.onmouseover = (e) => {
closeBtn.style.color = 'white';
};
closeBtn.onmouseout = (e) => {
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
};
// Click handlers
gradientContainer.onclick = (e) => {
if (e.target === gradientContainer || e.target === toast || e.target === messageDiv) {
window.open('https://github.com/Max-Eee/NeoPass/releases/latest');
}
};
// Modified close button handler to store dismissal time
closeBtn.onclick = (e) => {
e.stopPropagation(); // Prevent triggering the container's click
gradientContainer.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => gradientContainer.remove(), 280);
// Store the dismissal time
chrome.runtime.sendMessage({
action: "updateDismissed",
version: version,
timestamp: Date.now()
});
};
// Listen for dismissal message from other tabs
chrome.runtime.onMessage.addListener((message) => {
if (message.action === "removeUpdateNotification") {
if (gradientContainer && gradientContainer.parentElement) {
gradientContainer.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => gradientContainer.remove(), 280);
}
}
});
// Add animation styles
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-20px); }
}
`;
document.head.appendChild(style);
// Assemble and append
header.appendChild(title);
header.appendChild(closeBtn);
linksContainer.appendChild(downloadLink);
linksContainer.appendChild(websiteLink);
toast.appendChild(header);
toast.appendChild(messageDiv);
toast.appendChild(linksContainer);
gradientContainer.appendChild(toast);
// Remove existing update toast if any
const existingToast = document.getElementById('neopass-update-notification');
if (existingToast) {
existingToast.remove();
}
document.body.appendChild(gradientContainer);
},
args: [message, latestVersion]
});
} catch (err) {
// Silently handle the error and store notification for showing later
// without logging to console
chrome.storage.local.set({
'pendingUpdateNotification': true,
'pendingUpdateVersion': latestVersion
});
}
};
// Execute the script with silent error handling
executeScriptPromise();
} catch (error) {
// Only log truly unexpected errors
console.error('Error in showUpdateToast:', error);
}
});
}
// Add listener for tab updates to show pending notifications
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
// Only check when page is fully loaded
if (changeInfo.status === 'complete' && tab.url &&
!tab.url.startsWith('chrome://') &&
!tab.url.startsWith('chrome-extension://') &&
!tab.url.startsWith('about:') &&
!tab.url.startsWith('edge://') &&
!tab.url.startsWith('brave://')) {
// Check for pending notifications
chrome.storage.local.get(['pendingUpdateNotification', 'pendingUpdateVersion'], function(data) {
if (data.pendingUpdateNotification) {
// Clear the pending flag
chrome.storage.local.set({
'pendingUpdateNotification': false
});
// Show the notification
showUpdateToast(tab.id,
`Update Available: v${data.pendingUpdateVersion}\nSome features may not work. Please update your extension.`,
data.pendingUpdateVersion
);
}
});
// Standard update check logic (shows on every tab until dismissed)
checkForUpdate();
}
});
// Set up an alarm for update checking
function setupUpdateAlarm() {
chrome.alarms.get('updateCheck', (alarm) => {
// If alarm doesn't exist, create it
if (!alarm) {
chrome.alarms.create('updateCheck', {
// Check twice per day
periodInMinutes: 12 * 60
});
}
});
}
// Listen for alarm
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'updateCheck') {
checkForUpdate();
}
});
// Set up alarm when extension starts
chrome.runtime.onStartup.addListener(setupUpdateAlarm);
// Also set up alarm on install
chrome.runtime.onInstalled.addListener((details) => {
setupUpdateAlarm();
// Also do an immediate check on install/update
if (details.reason === 'update' || details.reason === 'install') {
checkForUpdate();
}
});
// Additional listener for update dismissal messages from content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "updateDismissed") {
chrome.storage.local.set({
lastUpdateDismissed: message.timestamp,
lastUpdateVersion: message.version
});
// Broadcast to all tabs to remove the notification
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, {
action: "removeUpdateNotification"
}).catch(() => {
// Ignore errors for tabs that can't receive messages
});
});
});
}
});
let extensionStatus = 'on';
// Context menu creation
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'separator1',
type: 'separator',
contexts: ['editable', 'selection']
});
if (extensionStatus === 'on') {
chrome.contextMenus.create({
id: 'search',
title: 'Search',
contexts: ['selection']
});
chrome.contextMenus.create({
id: 'solveMCQ',
title: 'MCQ',
contexts: ['selection']
});
chrome.contextMenus.create({
id: 'separator2',
type: 'separator',
contexts: ['editable', 'selection']
});
chrome.contextMenus.create({
id: 'nptel',
title: 'NPTEL',
contexts: ['selection']
});
// Add new menu item for IamNeo/Examly questions
chrome.contextMenus.create({
id: 'solveExamly',
title: 'Solve IamNeo/Examly Question',
contexts: ['all']
});
// Add custom paste menu items
chrome.contextMenus.create({
id: 'customPaste',
title: 'Drag and Drop Paste',
contexts: ['editable']
});
chrome.contextMenus.create({
id: 'pasteByTyping',
title: 'Paste by Typing',
contexts: ['editable']
});
}
});
// Handle context menu clicks
function isLoggedIn(callback) {
chrome.storage.local.get(['loggedIn'], function(result) {
callback(result.loggedIn);
});
}
// Function to prompt user to log in
function showLoginPrompt(tabId) {
showToast(tabId, 'Please log in to use this feature.', true);
chrome.action.openPopup();
}
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'search' && info.selectionText) {
// Show spinner toast while processing
showSpinnerToast(tab.id, 'Analyzing question...');
queryRequest(info.selectionText).then(response => {
handleQueryResponse(response, tab.id);
}).catch(error => {
console.error('Context menu search error:', error);
showToast(tab.id, 'Search failed. Please try again.', true, 'An error occurred while processing your search request.');
});
}
if (info.menuItemId === 'solveMCQ' && info.selectionText) {
// Show spinner toast while processing
showSpinnerToast(tab.id, 'Analyzing MCQ question...');
queryRequest(info.selectionText, true).then(response => {
handleQueryResponse(response, tab.id, true);
}).catch(error => {
console.error('Context menu MCQ error:', error);
showToast(tab.id, 'MCQ search failed. Please try again.', true, 'An error occurred while processing your MCQ request.');
});
}
if (info.menuItemId === 'nptel') {
if (info.selectionText) {
handleNPTEL({
result: info.selectionText
}, tab.id);
} else {
showToast(tab.id, 'No text selected', true);
}
}
// Add handler for the new menu item
if (info.menuItemId === 'solveExamly') {
chrome.tabs.sendMessage(tab.id, {
action: 'solveIamneoExamly'
});
}
// Handle custom paste menu item
if (info.menuItemId === 'customPaste') {
// For context menu or keyboard shortcut:
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['data/inject/customPaste.js']
}, () => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: async () => {
if (typeof performDragDropPaste === 'function') {
await performDragDropPaste();
return true;
}
return false;
}
}, (results) => {
if (results && results[0] && !results[0].result) {
showToast(tab.id, 'Paste operation failed. Please try again.', true);
}
});
});
}
// Handle paste by typing menu item
if (info.menuItemId === 'pasteByTyping') {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['data/inject/customPaste.js']
}, () => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: async () => {
if (typeof performPasteByTyping === 'function') {
await performPasteByTyping();
return true;
}
return false;
}
}, (results) => {
if (results && results[0] && !results[0].result) {
showToast(tab.id, 'Paste by typing operation failed. Please try again.', true);
}
});
});
}
});
chrome.commands.onCommand.addListener((command, tab) => {
if (shortcutStates[command]) {
return; // Skip if the shortcut is already being processed
}
shortcutStates[command] = true; // Mark the shortcut as being processed
if (command === 'search') {
chrome.scripting.executeScript({
target: {
tabId: tab.id
},
function: getSelectedText
}, (selection) => {
if (selection[0] && selection[0].result) {
// Show spinner toast while processing
showSpinnerToast(tab.id, 'Analyzing question...');
queryRequest(selection[0].result).then(response => {
handleQueryResponse(response, tab.id);
shortcutStates[command] = false; // Reset the state after processing
}).catch(error => {
console.error('Search shortcut error:', error);
showToast(tab.id, 'Search failed. Please try again.', true, 'An error occurred while processing your search request.');
shortcutStates[command] = false; // Reset the state on error
});
} else {
shortcutStates[command] = false; // Reset the state if no selection
}
});
}
if (command === 'search-mcq') {
chrome.scripting.executeScript({
target: {
tabId: tab.id
},
function: getSelectedText
}, (selection) => {
if (selection[0] && selection[0].result) {
// Show spinner toast while processing
showSpinnerToast(tab.id, 'Analyzing question...');
queryRequest(selection[0].result, true).then(response => {
handleQueryResponse(response, tab.id, true);
shortcutStates[command] = false; // Reset the state after processing
}).catch(error => {
console.error('MCQ shortcut error:', error);
showToast(tab.id, 'MCQ search failed. Please try again.', true, 'An error occurred while processing your MCQ request.');
shortcutStates[command] = false; // Reset the state on error
});
} else {
shortcutStates[command] = false; // Reset the state if no selection
}
});
}
if (command === 'customPaste') {
chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: async () => {
try {
const clipboardText = await navigator.clipboard.readText();
const activeElement = document.activeElement;
if (activeElement && (activeElement.isContentEditable || activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
const start = activeElement.selectionStart || 0;
const end = activeElement.selectionEnd || 0;
const text = activeElement.value || activeElement.textContent;
const newText = text.substring(0, start) + clipboardText + text.substring(end);
if (activeElement.isContentEditable) {
activeElement.textContent = newText;
} else {
activeElement.value = newText;
}
// Dispatch both input and change events
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}
} catch (err) {
console.error('Clipboard API read failed:', err);
return false;
}
}
}, (results) => {
shortcutStates[command] = false; // Reset the state after processing
if (results && results[0] && !results[0].result) {
showToast(tab.id, 'Paste failed. Please try again.', true);
}
});
}
if (command === 'nptel') {
chrome.scripting.executeScript({
target: {
tabId: tab.id
},
function: getSelectedText
}, (results) => {
if (results[0] && results[0].result) {
handleNPTEL(results[0], tab.id); // Pass result[0] and tab.id
}
shortcutStates[command] = false; // Reset the state after processing
});
}
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "checkLoginStatus") {
chrome.storage.local.get(["loggedIn"], function(result) {
sendResponse({
loggedIn: result.loggedIn === true
});
});
return true; // Keep the message channel open for async response
}
if (message.action === "showLoginPrompt") {
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
if (tabs.length > 0) {
showLoginPrompt(tabs[0].id); // Call existing function to show login prompt
}
});
}
});
function handleNPTEL(result, tabId) {
const selectedText = result.result; // Access result.result here
if (selectedText) {
// Call your findAnswer function or do the NPTEL search
const bestAnswers = findAnswer(selectedText); // Expecting an array of answers
if (bestAnswers) {
if (Array.isArray(bestAnswers) && bestAnswers.length > 0) {
// Deduplicate answers - convert to Set and back to Array to remove duplicates
const uniqueAnswers = [...new Set(bestAnswers)];
// Prepare the display string with indexing
let answersString;
if (uniqueAnswers.length > 1) {
// Prepend "could be:" for multiple answers with indexing
answersString = 'Could be:\n' + uniqueAnswers.map((answer, index) => `${index + 1}. ${answer}`).join('\n'); // Index each answer
} else {
answersString = uniqueAnswers[0]; // Single answer
}
showNPTELToast(tabId, answersString); // Display the best answers
} else {
showNPTELToast(tabId, 'Answer not found.\nPlease select only the question.', true);
}
} else {
showNPTELToast(tabId, 'Answer not found.\nPlease select only the question.', true);
}
} else {
showNPTELToast(tabId, 'No text selected', true);
}
}
// Helper functions
function getSelectedText() {
const selectedText = window.getSelection().toString().trim();
if (!selectedText) {
chrome.runtime.sendMessage({
action: 'showToast',
message: 'No text selected',
isError: true
});
return '';
}
return selectedText;
}
function handleQueryResponse(response, tabId, isMCQ = false) {
if (response && typeof response === 'string') {
// Success case - response is the actual text
if (isMCQ) {
showMCQToast(tabId, response);
} else {
copyToClipboard(response);
showToast(tabId, 'Copied to Clipboard!');
}
} else if (response && response.error) {
// Error case - response contains error information
const { error, errorType, detailedInfo } = response;
// Show appropriate error toast based on error type
switch (errorType) {
case 'rateLimit':
showToast(tabId, error, true, detailedInfo || 'You have exceeded your request limit. Please wait before trying again.');
break;
case 'auth':
showToast(tabId, error, true, detailedInfo || 'Please log in or refresh your session to continue using the service.');
break;
case 'forbidden':
showToast(tabId, error, true, detailedInfo || 'Access to this feature is restricted. Please check your account status.');
break;
case 'server':
showToast(tabId, error, true, detailedInfo || 'The service is experiencing issues. Please try again in a few moments.');
break;
case 'network':
showToast(tabId, error, true, detailedInfo || 'Please check your internet connection and try again.');
break;
case 'client':
showToast(tabId, error, true, detailedInfo || 'There was an issue with your request. Try rephrasing or shortening your text.');
break;
default:
showToast(tabId, error, true, detailedInfo || 'An unexpected error occurred. Please try again after 30 seconds.');
}
} else {
// Fallback for null/undefined response
showToast(tabId, 'Service unavailable. Please try again after 30s.', true, 'The service did not respond. This may be due to high server load or maintenance.');
}
}
function handleQueryResponseForIamNeoExamly(response, tabId, isMCQ = false, isHackerRank = false, isMultipleChoice = false) {
if (response && typeof response === 'string') {
// Success case - response is the actual text
if (isMCQ) {
chrome.tabs.sendMessage(tabId, {
action: 'clickMCQOption',
response: response,
isHackerRank: isHackerRank,
isMultipleChoice: isMultipleChoice
});
} else {
copyToClipboard(response);
}
} else if (response && response.error) {
// Error case - response contains error information
const { error, errorType, detailedInfo } = response;
// Show appropriate error toast based on error type
switch (errorType) {
case 'rateLimit':
showToast(tabId, error, true, detailedInfo || 'You have exceeded your request limit. Please wait before trying again.');
break;
case 'auth':
showToast(tabId, error, true, detailedInfo || 'Please log in or refresh your session to continue using the service.');
break;
case 'forbidden':
showToast(tabId, error, true, detailedInfo || 'Access to this feature is restricted. Please check your account status.');
break;
case 'server':
showToast(tabId, error, true, detailedInfo || 'The service is experiencing issues. Please try again in a few moments.');
break;
case 'network':
showToast(tabId, error, true, detailedInfo || 'Please check your internet connection and try again.');
break;
case 'client':
showToast(tabId, error, true, detailedInfo || 'There was an issue with your request. Try rephrasing or shortening your text.');
break;
default:
showToast(tabId, error, true, detailedInfo || 'An unexpected error occurred. Please try again after 30 seconds.');
}
} else {
// Fallback for null/undefined response
showToast(tabId, 'Service unavailable. Please try again after 30s.', true, 'The service did not respond. This may be due to high server load or maintenance.');
}
}
// Enhanced queryRequest function with comprehensive error handling
// Returns either:
// - String: successful response text
// - Object: { error: string, errorType: string, detailedInfo: string }
async function queryRequest(text, isMCQ = false, isMultipleChoice = false, tabId = null) {
// Check if a request is already in progress
if (!canMakeRequest()) {
console.log('[Request Block] Request blocked - another request is in progress');
return {
error: 'Please wait for your previous request to complete.',
errorType: 'rateLimit',
detailedInfo: 'Multiple simultaneous requests are not allowed. Please wait a moment before trying again.'
};
}
// Block new requests
blockRequests();
try {
// Check if user has custom API configured
const customAPIConfig = await getCustomAPIConfig();
if (customAPIConfig.useCustomAPI && customAPIConfig.apiKey) {
const result = await queryCustomAPI(text, isMCQ, isMultipleChoice, customAPIConfig);
unblockRequests();
return result;
}
// Check if user is logged in
const {
accessToken,
refreshToken,
isPro
} = await getTokens();
// If not logged in and no custom API configured, require custom API
if (!accessToken || !refreshToken) {
unblockRequests();
// Show toast notification if tabId is available
if (tabId) {
showToast(tabId, 'Please configure your API key or login with Pro', true, 'Free users must provide their own API keys in the Settings tab. Click the extension icon to configure.');
}
// Open popup to Pro tab after a short delay
setTimeout(() => {
try {
chrome.action.openPopup();
} catch (e) {
console.log('Could not open popup automatically:', e.message);
}
}, 1000);
return {
error: 'Please configure your custom API key in Settings or login with Pro to use our proxy-server.',
errorType: 'auth',
detailedInfo: 'Free users must provide their own API keys in the Settings tab to use this extension.'
};
}
// Always use Pro endpoint
const API_URL = `${API_BASE_URL}/api/pro-text`;
const body = {
prompt: text,
refreshToken: refreshToken // Required for server-side automatic token refresh
};
if (isMCQ) {
if (isMultipleChoice) {
// Multiple choice question - can select multiple options
body.prompt += "\nIMPORTANT: This is a MULTIPLE CHOICE question where MULTIPLE options can be correct. Analyze the question carefully and provide ALL correct options.\n\nFormat your response EXACTLY like this:\n- If options are A, B, C and A and C are correct: 'A. [text of option A], C. [text of option C]'\n- If options are 1, 2, 3 and 1 and 3 are correct: '1. [text of option 1], 3. [text of option 3]'\n- If only one option is correct, provide just that one: 'B. [text of option B]'\n\nDO NOT include explanations, reasoning, or anything else. ONLY the correct option(s) in the exact format shown above, separated by commas if multiple.\nIf this is not an MCQ question, simply respond with 'Not an MCQ'";
} else {
// Single choice question - only one option can be selected
body.prompt += "\nIMPORTANT: This is a SINGLE CHOICE question where ONLY ONE option is correct. Analyze the question carefully and provide the single correct option.\n\nFormat your response EXACTLY like this:\n- If options are A, B, C: 'A. [text of option A]' or 'C. [text of option C]'\n- If options are 1, 2, 3: '1. [text of option 1]' or '3. [text of option 3]'\n\nDO NOT include explanations, reasoning, or anything else. ONLY the single correct answer in the exact format shown above.\nIf this is not an MCQ question, simply respond with 'Not an MCQ'";
}
}
console.log('[queryRequest] Sending request to API', API_URL, 'with body:', body);
try {
let response = await makeAuthenticatedRequest(API_URL, 'POST', accessToken, body);
// Server automatically handles token refresh if access token expired
// If auth fails, it means refresh token is also invalid/expired
if (!response.ok && (response.status === 401 || response.status === 403)) {
console.log('[queryRequest] Authentication failed - session expired');
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn']);
return {
error: 'Session expired. Please log in again.',
errorType: 'auth',
detailedInfo: 'Your session has expired. Please log in again to continue using NeoPass features.'
};
}
if (!response.ok) {
let errorMessage = 'An unexpected error occurred. Please try again.';
let errorType = 'general';
let detailedInfo = `Server responded with status ${response.status}`;
try {
const errorData = await response.json();
console.error("Error querying:", errorData);
// Handle specific error types based on status code and response
if (response.status === 429) {
errorType = 'rateLimit';
if (errorData.error && errorData.error.includes('Token limit exceeded')) {
errorMessage = 'Token limit exceeded. Please upgrade or wait for your limit to reset.';
if (errorData.details) {
detailedInfo = `You have used ${errorData.details.used} out of ${errorData.details.limit} tokens. ${errorData.details.remaining} tokens remaining.`;
} else {
detailedInfo = 'You have reached your token limit for this billing period.';
}
} else if (errorData.message && errorData.message.includes('Daily request limit exceeded')) {
errorMessage = 'Daily request limit exceeded. Please try again tomorrow.';
detailedInfo = `You have reached your daily request limit. ${errorData.nextReset ? `Limit resets at ${new Date(errorData.nextReset).toLocaleString()}` : 'Limit resets daily at midnight UTC.'}`;
} else if (errorData.message && errorData.message.includes('wait for your previous request')) {
errorMessage = 'Please wait for your previous request to complete.';
detailedInfo = 'Multiple simultaneous requests are not allowed. Please wait a moment before trying again.';
} else {
errorMessage = 'Too many requests. Please wait before trying again.';
detailedInfo = 'Rate limit exceeded. Please wait a few moments before making another request.';
}
} else if (response.status === 403) {
errorType = 'forbidden';
// Check if this is a Pro subscription expiration
if ((errorData.error && (errorData.error.includes('Pro subscription') || errorData.error.includes('active Pro subscription') || errorData.error.includes('subscription') || errorData.error.includes('expired'))) ||
(errorData.message && (errorData.message.includes('subscription') || errorData.message.includes('expired')))) {
errorMessage = 'Pro subscription required or expired.';
detailedInfo = 'This service requires an active Pro subscription. Please upgrade or renew your Pro subscription.';
// Auto-logout user when subscription expires
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn', 'username', 'isPro', 'loginTimestamp']);
console.log('🔒 Auto-logout: Pro subscription expired');
} else if (errorData.message && errorData.message.includes('star')) {
errorMessage = 'Please star the repository to use this service.';
detailedInfo = 'This service requires starring the GitHub repository. Please star it and try again.';
} else {
errorMessage = 'Access denied. Please check your account status.';
detailedInfo = 'Your request was denied. This may be due to account restrictions or service limitations.';
}
} else if (response.status === 500) {
errorType = 'server';
errorMessage = 'Service temporarily unavailable. Please try again in a moment.';
detailedInfo = 'The server encountered an internal error. This is usually temporary and should resolve shortly.';
} else if (response.status === 400) {
errorType = 'client';
errorMessage = 'Invalid request. Please try rephrasing your question.';
detailedInfo = 'The request format was invalid. Try shortening your text or rephrasing your question.';
} else {
errorMessage = errorData.message || `Server error (${response.status})`;
detailedInfo = errorData.error || `HTTP ${response.status}: ${errorMessage}`;
}
} catch (parseError) {
console.error("Error parsing error response:", parseError);
detailedInfo = `HTTP ${response.status}: Unable to parse error details`;
}
return { error: errorMessage, errorType, detailedInfo };
}
const responseData = await response.json();
// Server automatically refreshes access token if it expired
// Store the new access token (refresh token remains unchanged)
if (responseData.newAccessToken) {
await chrome.storage.local.set({ accessToken: responseData.newAccessToken });
console.log('✅ Access token auto-refreshed by server and stored');
}
return responseData.text;
} catch (error) {
console.error("Error querying:", error);
let errorMessage = 'Network error. Please check your connection and try again.';
let errorType = 'network';
let detailedInfo = 'Failed to connect to the service. This could be due to network issues or service downtime.';
if (error.name === 'TypeError' && error.message.includes('fetch')) {
errorMessage = 'Unable to connect to the service. Please try again.';
detailedInfo = 'Network connection failed. Please check your internet connection and try again.';
} else if (error.message.includes('timeout')) {
errorMessage = 'Request timed out. Please try again.';
detailedInfo = 'The request took too long to complete. This may be due to high server load.';
} else {
detailedInfo = error.message || 'An unexpected error occurred during the request.';
}
return { error: errorMessage, errorType, detailedInfo };
}
} catch (error) {
console.error("Error in queryRequest:", error);
return {
error: 'An unexpected error occurred.',
errorType: 'general',
detailedInfo: error.message || 'Failed to process the request.'
};
} finally {
// Ensure we always unblock requests even if something unexpected happens
unblockRequests();
}
}// Helper function to get custom API configuration
async function getCustomAPIConfig() {
return new Promise((resolve) => {
chrome.storage.local.get([
'useCustomAPI',
'aiProvider',
'customEndpoint',
'customAPIKey',
'customModelName'
], (result) => {
resolve({
useCustomAPI: result.useCustomAPI || false,
aiProvider: result.aiProvider || 'openai',
customEndpoint: result.customEndpoint || '',
apiKey: result.customAPIKey || '',
modelName: result.customModelName || ''
});
});
});
}
// Function to query custom AI API
async function queryCustomAPI(text, isMCQ, isMultipleChoice, config) {
const { aiProvider, customEndpoint, apiKey, modelName } = config;
// Construct the prompt based on query type
let prompt = text;
if (isMCQ) {
if (isMultipleChoice) {
prompt += "\nIMPORTANT: This is a MULTIPLE CHOICE question where MULTIPLE options can be correct. Analyze the question carefully and provide ALL correct options.\n\nFormat your response EXACTLY like this:\n- If options are A, B, C and A and C are correct: 'A. [text of option A], C. [text of option C]'\n- If options are 1, 2, 3 and 1 and 3 are correct: '1. [text of option 1], 3. [text of option 3]'\n- If only one option is correct, provide just that one: 'B. [text of option B]'\n\nDO NOT include explanations, reasoning, or anything else. ONLY the correct option(s) in the exact format shown above, separated by commas if multiple.\nIf this is not an MCQ question, simply respond with 'Not an MCQ'";
} else {
prompt += "\nIMPORTANT: This is a SINGLE CHOICE question where ONLY ONE option is correct. Analyze the question carefully and provide the single correct option.\n\nFormat your response EXACTLY like this:\n- If options are A, B, C: 'A. [text of option A]' or 'C. [text of option C]'\n- If options are 1, 2, 3: '1. [text of option 1]' or '3. [text of option 3]'\n\nDO NOT include explanations, reasoning, or anything else. ONLY the single correct answer in the exact format shown above.\nIf this is not an MCQ question, simply respond with 'Not an MCQ'";
}
}
try {
let apiUrl, requestBody, headers;
// Configure API call based on provider
switch (aiProvider) {
case 'openai':
apiUrl = 'https://api.openai.com/v1/chat/completions';
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
};
requestBody = {
model: modelName || 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7
};
break;
case 'anthropic':
apiUrl = 'https://api.anthropic.com/v1/messages';
headers = {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
};
requestBody = {
model: modelName || 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: [{ role: 'user', content: prompt }]
};
break;
case 'google':
const googleModel = modelName || 'gemini-2.5-flash';
apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${googleModel}:generateContent?key=${apiKey}`;
headers = {
'Content-Type': 'application/json'
};
requestBody = {
contents: [{ parts: [{ text: prompt }] }]
};
break;
case 'deepseek':
apiUrl = 'https://api.deepseek.com/v1/chat/completions';
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
};
requestBody = {
model: modelName || 'deepseek-chat',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7
};
break;
case 'custom':
if (!customEndpoint) {
return {
error: 'Custom endpoint not configured',
errorType: 'config',
detailedInfo: 'Please configure a custom API endpoint in the extension settings.'
};
}
apiUrl = customEndpoint;
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
};
requestBody = {
model: modelName || 'default',
messages: [{ role: 'user', content: prompt }]
};
break;
default:
return {
error: 'Unknown AI provider',
errorType: 'config',
detailedInfo: 'The selected AI provider is not supported.'
};
}
const response = await fetch(apiUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
error: `API request failed: ${response.status}`,
errorType: 'api',
detailedInfo: errorData.error?.message || errorData.message || `HTTP ${response.status}: ${response.statusText}`
};
}
const data = await response.json();
// Extract response based on provider
let responseText;
switch (aiProvider) {
case 'openai':
case 'deepseek':
responseText = data.choices?.[0]?.message?.content;
break;
case 'anthropic':
responseText = data.content?.[0]?.text;
break;
case 'google':
responseText = data.candidates?.[0]?.content?.parts?.[0]?.text;
break;
case 'custom':
// Try common response formats
responseText = data.choices?.[0]?.message?.content ||
data.content?.[0]?.text ||
data.response ||
data.text;
break;
}
if (!responseText) {
return {
error: 'Invalid API response format',
errorType: 'parse',
detailedInfo: 'Could not extract response text from API response.'
};
}
return responseText;
} catch (error) {
return {
error: 'Network or API error',
errorType: 'network',
detailedInfo: error.message || 'Failed to connect to the custom AI API. Please check your configuration.'
};
}
}
const API_BASE_URL = 'https://api.neopass.tech';
// Listen for messages from Chrome runtime for ChatBot
// Helper function to get tokens from chrome storage
async function getTokens() {
return new Promise((resolve) => {
chrome.storage.local.get(['accessToken', 'refreshToken', 'isPro'], resolve);
});
}
// Helper function to make authenticated request
async function makeAuthenticatedRequest(url, method, token, body = null) {
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
const options = {
method,
headers,
...(body && {
body: JSON.stringify(body)
})
};
return fetch(url, options);
}
// Listen for test custom API message
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "testCustomAPI") {
(async () => {
try {
const config = message.config;
const testPrompt = "Hello, this is a test message. Please respond with 'API connection successful!' if you receive this.";
const result = await queryCustomAPI(testPrompt, false, false, config);
if (typeof result === 'string') {
sendResponse({
success: true,
message: 'API connection successful!'
});
} else {
sendResponse({
success: false,
error: result.detailedInfo || result.error
});
}
} catch (error) {
sendResponse({
success: false,
error: error.message || 'Unknown error occurred'
});
}
})();
return true; // Keep the message channel open
}
});
// Listen for messages from Chrome runtime for ChatBot
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "processChatMessage") {
// Use async/await properly with Promise
(async () => {
try {
await handleChatMessage(message, sender);
sendResponse({
success: true
});
} catch (error) {
console.error('Chat processing error:', error);
sendResponse({
success: false,
error: error.message
});
}
})();
return true; // Keep the message channel open
}
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'extractData') {
(async () => {
try {
// Format prompt based on question type
let queryText;
if (request.isCoding) {
if (request.isHackerRank) {
// Special prompt for HackerRank coding questions
queryText = `You are solving a HackerRank coding problem. Provide ONLY the complete solution code that can be directly run.
IMPORTANT REQUIREMENTS:
- Provide ONLY the solution code, no explanations or comments
- The code must be complete and ready to run
- Include all necessary imports and function definitions
- Handle input/output exactly as specified
- Ensure the solution passes all test cases
${request.question}
Respond with ONLY the ${request.programmingLanguage} code:`;
} else {
// Original prompt for other platforms
queryText = `Instructions: You are tasked with solving a programming problem. Respond strictly with the solution code in the required programming language.
Ensure the code: Meets the requirements outlined in the problem statement.
Stricly Passes all test cases, including edge cases and boundary conditions.
Always get the input from the users.` +
`Question:\n${request.question}\n\n` +
(request.programmingLanguage ? `Solve Striclty Using This Programing Language:\n${request.programmingLanguage}` : '') +
(request.inputFormat ? `Input Format:\n${request.inputFormat}\n\n` : '') +
(request.outputFormat ? `Output Format:\n${request.outputFormat}\n\n` : '') +
(request.testCases ? `Test Cases:\n${request.testCases}` : '');
}
} else {
// MCQ handling with support for multiple choice
queryText = request.code ?
`${request.question.trim()}\nCode:\n${request.code.trim()}\nOptions:\n${request.options.trim()}` :
`${request.question.trim()}\nOptions:\n${request.options.trim()}`;
}
// Add console logging for the prompt
console.log('Sending prompt to API:', {
type: request.isCoding ? 'Coding Question' : 'MCQ',
prompt: queryText,
length: queryText.length
}); // Send query and handle response
const response = await queryRequest(queryText, request.isMCQ, request.isMultipleChoice, sender.tab.id);
// Check if response is successful (string) or contains error
if (response && typeof response === 'string') {
// Success case
console.log('AI Response received:', {
type: request.isCoding ? 'Coding Question' : 'MCQ',
isHackerRank: request.isHackerRank,
isMultipleChoice: request.isMultipleChoice,
response: response,
responseLength: response.length
});
handleQueryResponseForIamNeoExamly(response, sender.tab.id, request.isMCQ, request.isHackerRank, request.isMultipleChoice);
sendResponse({
success: true,
response,
status: 'success'
});
} else if (response && response.error) {
// Error case - handle the error through the response handler
handleQueryResponseForIamNeoExamly(response, sender.tab.id, request.isMCQ, request.isHackerRank, request.isMultipleChoice);
sendResponse({
error: response.error,
status: 'error',
errorType: response.errorType
});
} else {
// Fallback case
console.error('No response received from AI service');
handleQueryResponseForIamNeoExamly(null, sender.tab.id, request.isMCQ, request.isHackerRank, request.isMultipleChoice);
sendResponse({
error: 'No response from query service',
status: 'error',
errorType: 'general'
});
}
} catch (error) {
console.error("Query processing error:", error);
// Show a generic error toast only if the error wasn't already handled by queryRequest
showToast(sender.tab.id, 'An unexpected error occurred. Please try again.', true, 'The request failed due to an unexpected error. This may be temporary.');
sendResponse({
error: error.message,
status: 'error',
details: error.toString()
});
}
})();
return true; // Keep message channel open for async response
}
});
async function handleChatMessage(message, sender) {
try {
// Check if user has custom API configured
const customAPIConfig = await getCustomAPIConfig();
if (customAPIConfig.useCustomAPI && customAPIConfig.apiKey) {
// Use custom API for chat
const chatPrompt = message.context
? `Context: ${message.context}\n\nUser: ${message.message}\n\nPlease provide a helpful response.`
: message.message;
const result = await queryCustomAPI(chatPrompt, false, false, customAPIConfig);
if (typeof result === 'string') {
sendChatResponse(sender.tab.id, result);
} else {
sendChatErrorResponse(sender.tab.id, result.error || 'Failed to get response from custom API');
}
return;
}
// Check if user is logged in
const {
accessToken,
refreshToken,
isPro
} = await getTokens();
// If not logged in and no custom API configured, require custom API
if (!accessToken || !refreshToken) {
sendChatErrorResponse(sender.tab.id, "Please configure your custom API key in Settings or login with Pro to use our proxy-server.");
return;
}
// Always use Pro endpoint
const chatEndpoint = `${API_BASE_URL}/api/pro-chat`;
const requestBody = {
message: message.message,
context: message.context,
refreshToken: refreshToken // Send refresh token for server-side auto-refresh
};
// Include image if present
if (message.image) {
requestBody.image = message.image;
}
let response = await makeAuthenticatedRequest(
chatEndpoint,
"POST",
accessToken,
requestBody
);
// Server automatically handles token refresh if access token expired
// If auth fails, it means refresh token is also invalid/expired
if (!response.ok && (response.status === 401 || response.status === 403)) {
// Check if this is an auth error vs Pro subscription error
try {
const errorData = await response.json();
if (errorData.message && errorData.message.includes('subscription')) {
// This is a Pro subscription issue, not an auth issue
sendChatErrorResponse(sender.tab.id, "Your Pro subscription is required or has expired. Please upgrade or renew.");
return;
}
} catch (e) {
// Couldn't parse error, assume auth failure
}
// Authentication failed - clear tokens
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn']);
sendChatErrorResponse(sender.tab.id, "Session expired. Please log in again.");
return;
}
// Handle different error scenarios with specific user messages
if (!response.ok) {
let errorMessage = "Sorry, I encountered an error processing your message.";
try {
const errorData = await response.json();
if (response.status === 429) {
if (errorData.error && errorData.error.includes('Token limit exceeded')) {
errorMessage = "Token limit exceeded. Please upgrade or wait for your limit to reset.";
if (errorData.details) {
errorMessage += ` (Used: ${errorData.details.used}/${errorData.details.limit})`;
}
} else if (errorData.message && errorData.message.includes('Daily request limit exceeded')) {
errorMessage = "You've reached your daily chat limit. Please try again tomorrow.";
} else if (errorData.message && errorData.message.includes('wait for your previous request')) {
errorMessage = "Please wait for your previous message to be processed before sending another.";
} else {
errorMessage = "Too many requests. Please wait a moment before trying again.";
}
} else if (response.status === 403) {
// Check if this is a Pro subscription expiration
if ((errorData.error && (errorData.error.includes('Pro subscription') || errorData.error.includes('active Pro subscription') || errorData.error.includes('subscription') || errorData.error.includes('expired'))) ||
(errorData.message && (errorData.message.includes('subscription') || errorData.message.includes('expired')))) {
errorMessage = "Your Pro subscription is required or has expired. Please upgrade or renew your Pro subscription to continue using this service.";
// Auto-logout user when subscription expires
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn', 'username', 'isPro', 'loginTimestamp']);
console.log('🔒 Auto-logout: Pro subscription expired');
} else if (errorData.message && errorData.message.includes('star')) {
errorMessage = "Please star the repository to use the chat feature.";
} else {
errorMessage = "Access denied. Please check your account status or try logging in again.";
}
} else if (response.status === 500) {
errorMessage = "The chat service is temporarily unavailable. Please try again in a moment.";
} else if (response.status === 400) {
errorMessage = "Your message couldn't be processed. Try rephrasing or shortening it.";
} else {
errorMessage = errorData.message || `Service error (${response.status}). Please try again.`;
}
} catch (parseError) {
console.error("Error parsing chat error response:", parseError);
errorMessage = `Chat service error (${response.status}). Please try again later.`;
}
// Send error message with proper error role
sendChatErrorResponse(sender.tab.id, errorMessage);
return;
}
const data = await response.json();
if (response.ok && data.success) {
// Server automatically refreshes access token if it expired
// Store the new access token (refresh token remains unchanged)
if (data.newAccessToken) {
await chrome.storage.local.set({ accessToken: data.newAccessToken });
console.log('✅ Access token auto-refreshed during chat request and stored');
}
sendChatResponse(sender.tab.id, data.response);
} else {
const errorMessage = data.error || "Failed to get a response. Please try again.";
sendChatErrorResponse(sender.tab.id, `Sorry, ${errorMessage}`);
}
} catch (error) {
console.error("Chat processing error:", error);
let errorMessage = "Sorry, I encountered an error processing your message.";
if (error.name === 'TypeError' && error.message.includes('fetch')) {
errorMessage = "Unable to connect to the chat service. Please check your connection and try again.";
} else if (error.message.includes('timeout')) {
errorMessage = "The request timed out. Please try again.";
} else {
errorMessage = "Sorry, I encountered an unexpected error. Please try again or log in again if the issue persists.";
}
sendChatErrorResponse(sender.tab.id, errorMessage);
}
}
// Helper function to send chat responses
function sendChatResponse(tabId, content) {
chrome.tabs.sendMessage(tabId, {
action: "updateChatHistory",
role: "assistant",
content: content
});
}
// Helper function to send chat error responses (prevents errors from being added to context)
function sendChatErrorResponse(tabId, content) {
chrome.tabs.sendMessage(tabId, {
action: "updateChatHistory",
role: "error",
content: content
});
}
// ========================================
// NOTE: Token refresh is now handled automatically by the server
// ========================================
// The server's authenticateTokenWithRefresh middleware automatically:
// 1. Detects when access token expires
// 2. Generates a new access token (keeps same refresh token)
// 3. Returns newAccessToken in the response
// 4. Client stores the new access token
//
// Old client-side refresh logic has been removed as it's no longer needed
// ========================================
async function copyToClipboard(text, tabId) {
try {
// Use modern Clipboard API with fallback
await chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: async (content) => {
try {
await navigator.clipboard.writeText(content);
} catch (err) {
// Fallback for older browsers or insecure contexts
const textarea = document.createElement('textarea');
textarea.textContent = content;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
},
args: [text]
});
return true;
} catch (err) {
console.error('Failed to copy text:', err);
return false;
}
}
function copyToClipboard(text) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
if (tabs[0]) {
chrome.scripting.executeScript({
target: {
tabId: tabs[0].id
},
func: async function(content) {
try {
await navigator.clipboard.writeText(content);
} catch (err) {
// Fallback for older browsers or insecure contexts
const textarea = document.createElement('textarea');
textarea.textContent = content;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
},
args: [text]
});
}
});
}
async function checkStealthMode() {
return new Promise((resolve) => {
chrome.storage.local.get(['stealth'], (result) => {
resolve(result.stealth === true);
});
});
}
// Define opacity levels for toast messages
const opacityLevels = {
high: 1.0,
medium: 0.5,
low: 0.2
};
// Default opacity level
let currentOpacityLevel = "high";
// Track active toast element ID
let activeToastId = null;
// Function to remove any existing toast
function removeExistingToast(tabId) {
chrome.scripting.executeScript({
target: { tabId: tabId },
func: function() {
// Remove all possible toast types
const toastSelectors = [
'#neopass-active-toast',
'#stealth-mode-toast',
'.neopass-update-toast',
'[id*="toast"]',
'[class*="toast"]'
];
toastSelectors.forEach(selector => {
const existingToasts = document.querySelectorAll(selector);
existingToasts.forEach(toast => {
if (toast && toast.parentNode) {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 100);
}
});
});
}
});
}
// Function to toggle and store toast opacity level
async function toggleToastOpacity() {
// Rotate through opacity levels
switch (currentOpacityLevel) {
case "high":
currentOpacityLevel = "medium";
break;
case "medium":
currentOpacityLevel = "low";
break;
case "low":
currentOpacityLevel = "high";
break;
default:
currentOpacityLevel = "high";
}
// Store the new opacity level
await chrome.storage.local.set({
'toastOpacityLevel': currentOpacityLevel
});
// Show feedback toast with current opacity level
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
if (tabs[0]) {
showOpacityLevelToast(tabs[0].id, `Toast opacity set to: ${currentOpacityLevel}`);
}
});
return currentOpacityLevel;
}
// Get the current toast opacity value
async function getToastOpacity() {
return new Promise((resolve) => {
chrome.storage.local.get(['toastOpacityLevel'], (result) => {
if (result.toastOpacityLevel) {
currentOpacityLevel = result.toastOpacityLevel;
}
resolve(opacityLevels[currentOpacityLevel] || 1.0);
});
});
}
// Show a toast with the current opacity level
function showOpacityLevelToast(tabId, message) {
// Remove any existing toast first
removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, opacityLevel) {
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-active-toast'; // Add ID for tracking
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = 'rgba(15, 15, 20, 0.95)';
toast.style.color = '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacityLevel;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '320px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'center';
// Create message container with icon
const messageContainer = document.createElement('div');
messageContainer.style.display = 'flex';
messageContainer.style.alignItems = 'center';
messageContainer.style.gap = '10px';
messageContainer.style.flexGrow = '1';
// Settings icon (blue indicator dot)
const settingsIcon = document.createElement('span');
settingsIcon.style.display = 'inline-block';
settingsIcon.style.width = '8px';
settingsIcon.style.height = '8px';
settingsIcon.style.backgroundColor = '#64b5f6';
settingsIcon.style.borderRadius = '50%';
settingsIcon.style.boxShadow = '0 0 4px rgba(100, 181, 246, 0.6)';
// Message text
const messageText = document.createElement('span');
messageText.textContent = msg;
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
messageText.style.lineHeight = '1.4';
messageText.style.wordBreak = 'break-word';
messageContainer.appendChild(settingsIcon);
messageContainer.appendChild(messageText);
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.marginLeft = '8px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Create opacity indicator using text badges
const opacityIndicator = document.createElement('div');
opacityIndicator.style.marginTop = '10px';
opacityIndicator.style.width = '100%';
opacityIndicator.style.display = 'flex';
opacityIndicator.style.alignItems = 'center';
opacityIndicator.style.justifyContent = 'space-between';
opacityIndicator.style.gap = '8px';
// Helper function to create opacity badge
function createOpacityBadge(level, text, isActive) {
const badge = document.createElement('div');
badge.textContent = text;
badge.style.fontSize = '11px';
badge.style.padding = '3px 6px';
badge.style.borderRadius = '4px';
badge.style.fontWeight = isActive ? '600' : '400';
if (isActive) {
badge.style.backgroundColor = 'rgba(255, 255, 255, 0.15)';
badge.style.color = 'white';
} else {
badge.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
badge.style.color = 'rgba(255, 255, 255, 0.5)';
}
return badge;
}
// Add opacity level indicators
const lowBadge = createOpacityBadge('low', 'Low', opacityLevel <= 0.2);
const mediumBadge = createOpacityBadge('medium', 'Medium', opacityLevel > 0.2 && opacityLevel < 1.0);
const highBadge = createOpacityBadge('high', 'High', opacityLevel >= 1.0);
opacityIndicator.appendChild(lowBadge);
opacityIndicator.appendChild(mediumBadge);
opacityIndicator.appendChild(highBadge);
// Event listeners
closeBtn.onmouseover = function() {
closeBtn.style.color = '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
closeBtn.onclick = function() {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble the toast
headerContainer.appendChild(messageContainer);
headerContainer.appendChild(closeBtn);
toast.appendChild(headerContainer);
toast.appendChild(opacityIndicator);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
// Auto-hide toast after a delay
let hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 3000);
},
args: [message, opacityLevels[currentOpacityLevel]]
});
}
// Update existing showToast function to use the current opacity level
async function showToast(tabId, message, isError = false, detailedInfo = '') {
const opacity = await getToastOpacity();
// Set default detailed info if not provided
if (!detailedInfo) {
if (isError) {
detailedInfo = 'Possible causes:\n• Network connection issues\n• Server timeout\n• Authorization issues\n• Extension needs to be updated';
} else {
detailedInfo = 'Operation completed successfully.';
}
}
// Remove any existing toast first
await removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, isError, opacity, detailedInfo) {
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-active-toast'; // Add ID for tracking
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = isError ? 'rgba(40, 10, 10, 0.95)' : 'rgba(15, 15, 20, 0.95)';
toast.style.color = isError ? '#ff6b6b' : '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacity;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '320px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = isError ? '1px solid rgba(255, 107, 107, 0.2)' : '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'flex-start';
// Create message container
const messageContainer = document.createElement('div');
messageContainer.style.flexGrow = '1';
messageContainer.style.marginRight = '12px';
// Add indicator dot
const indicatorDot = document.createElement('span');
indicatorDot.style.display = 'inline-block';
indicatorDot.style.width = '8px';
indicatorDot.style.height = '8px';
indicatorDot.style.backgroundColor = isError ? '#ff6b6b' : '#4ade80';
indicatorDot.style.borderRadius = '50%';
indicatorDot.style.marginRight = '8px';
indicatorDot.style.boxShadow = isError ? '0 0 4px rgba(255, 107, 107, 0.6)' : '0 0 4px rgba(74, 222, 128, 0.6)';
// Add message text
const messageText = document.createElement('span');
messageText.textContent = msg;
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
messageText.style.lineHeight = '1.4';
messageText.style.wordBreak = 'break-word';
// Combine dot and text
const messageContent = document.createElement('div');
messageContent.style.display = 'flex';
messageContent.style.alignItems = 'center';
messageContent.appendChild(indicatorDot);
messageContent.appendChild(messageText);
messageContainer.appendChild(messageContent);
// Create buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.alignItems = 'center';
buttonsContainer.style.marginLeft = '8px';
// Info button
const infoBtn = document.createElement('button');
infoBtn.innerHTML = '';
infoBtn.title = 'Show more information';
infoBtn.style.background = 'none';
infoBtn.style.border = 'none';
infoBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
infoBtn.style.cursor = 'pointer';
infoBtn.style.padding = '2px';
infoBtn.style.marginRight = '6px';
infoBtn.style.borderRadius = '4px';
infoBtn.style.lineHeight = '0';
infoBtn.style.transition = 'all 0.2s';
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Detailed info container (initially hidden)
const detailedInfoContainer = document.createElement('div');
detailedInfoContainer.style.marginTop = '12px';
detailedInfoContainer.style.padding = '10px 12px';
detailedInfoContainer.style.backgroundColor = isError ? 'rgba(255, 107, 107, 0.1)' : 'rgba(255, 255, 255, 0.1)';
detailedInfoContainer.style.borderRadius = '6px';
detailedInfoContainer.style.fontSize = '13px';
detailedInfoContainer.style.display = 'none';
detailedInfoContainer.style.maxHeight = '120px';
detailedInfoContainer.style.overflow = 'auto';
detailedInfoContainer.style.lineHeight = '1.4';
detailedInfoContainer.style.color = isError ? 'rgba(255, 107, 107, 0.9)' : 'rgba(255, 255, 255, 0.9)';
detailedInfoContainer.textContent = detailedInfo;
// Add event listeners
let expanded = false;
let hideTimeoutId = null;
infoBtn.onmouseover = function() {
infoBtn.style.color = isError ? '#ff6b6b' : '#ffffff';
infoBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
infoBtn.onmouseout = function() {
infoBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
infoBtn.style.backgroundColor = 'transparent';
};
closeBtn.onmouseover = function() {
closeBtn.style.color = isError ? '#ff6b6b' : '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
infoBtn.onclick = function() {
expanded = !expanded;
detailedInfoContainer.style.display = expanded ? 'block' : 'none';
infoBtn.innerHTML = expanded ?
'' :
'';
// Clear the auto-hide timeout when info is expanded
if (expanded) {
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
} else {
// Restart the auto-hide timer when info is collapsed
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
}
};
closeBtn.onclick = function() {
// Clear any existing timeout
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble the toast
buttonsContainer.appendChild(infoBtn);
buttonsContainer.appendChild(closeBtn);
headerContainer.appendChild(messageContainer);
headerContainer.appendChild(buttonsContainer);
toast.appendChild(headerContainer);
toast.appendChild(detailedInfoContainer);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
// Set initial auto-hide timeout
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
},
args: [message, isError, opacity, detailedInfo]
});
}
// Show stealth mode toast notification
async function showStealthToast(tabId, message, stealthEnabled) {
const opacity = await getToastOpacity();
// Remove any existing toast first
await removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, stealthEnabled, opacity) {
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-active-toast'; // Use same ID for tracking
// Set colors based on stealth mode state
const textColor = stealthEnabled ? '#4ade80' : '#ff6b6b';
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = 'rgba(15, 15, 20, 0.95)';
toast.style.color = '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacity;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '480px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'center';
// Create message container with icon
const messageContainer = document.createElement('div');
messageContainer.style.display = 'flex';
messageContainer.style.alignItems = 'center';
messageContainer.style.gap = '10px';
messageContainer.style.flexGrow = '1';
messageContainer.style.marginRight = '12px';
// Add indicator dot
const indicatorDot = document.createElement('span');
indicatorDot.style.display = 'inline-block';
indicatorDot.style.width = '8px';
indicatorDot.style.height = '8px';
indicatorDot.style.backgroundColor = textColor;
indicatorDot.style.borderRadius = '50%';
indicatorDot.style.boxShadow = `0 0 4px ${stealthEnabled ? 'rgba(74, 222, 128, 0.6)' : 'rgba(255, 107, 107, 0.6)'}`;
// Message text
const messageText = document.createElement('span');
messageText.innerHTML = msg.replace(/\n/g, ' ');
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
messageText.style.lineHeight = '1.4';
messageText.style.wordBreak = 'break-word';
messageText.style.color = textColor;
messageText.style.textAlign = 'center';
messageText.style.flex = '1';
messageContainer.appendChild(indicatorDot);
messageContainer.appendChild(messageText);
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Event listeners
closeBtn.onmouseover = function() {
closeBtn.style.color = '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
closeBtn.onclick = function() {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble the toast
headerContainer.appendChild(messageContainer);
headerContainer.appendChild(closeBtn);
toast.appendChild(headerContainer);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
// Auto-hide toast after 5 seconds
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
},
args: [message, stealthEnabled, opacity]
});
// Update storage with new stealth mode state
chrome.storage.local.set({ stealth: stealthEnabled });
}
// Add toast opacity toggle message listener
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'toggleToastOpacity') {
toggleToastOpacity()
.then(newLevel => {
sendResponse({
success: true,
level: newLevel
});
})
.catch(error => {
console.error("Error toggling opacity:", error);
sendResponse({
success: false,
error: error.toString()
});
});
return true; // Keep the message channel open for async response
}
});
// Initialize opacity level from storage on startup
chrome.runtime.onStartup.addListener(() => {
chrome.storage.local.get(['toastOpacityLevel'], (result) => {
if (result.toastOpacityLevel) {
currentOpacityLevel = result.toastOpacityLevel;
}
});
});
// Event listeners
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.tabs.get(activeInfo.tabId, (tab) => {
tabDetails = tab;
});
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete") {
tabDetails = tab;
}
});
chrome.windows.onFocusChanged.addListener((windowId) => {
if (windowId === chrome.windows.WINDOW_ID_NONE) {
return;
}
chrome.tabs.query({
active: true,
windowId: windowId
}, (tabs) => {
if (tabs.length > 0) {
tabDetails = tabs[0];
}
});
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
currentKey = message.key;
if (message.action === "pageReloaded" || message.action === "windowFocus") {} else if (message.action === "openNewTab") {
openNewMinimizedWindowWithUrl(message.url);
}
if (message.action === 'showToast') {
showToast(sender.tab.id, message.message, message.isError);
}
if (message.action === 'showStealthToast') {
showStealthToast(sender.tab.id, message.message, message.stealthEnabled);
}
if (message.action === 'showMCQToast') {
showMCQToast(sender.tab.id, message.message);
}
});
// Add storage change listener for remote logout
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'local') {
// Check if refreshToken was removed (remote logout)
if (changes.refreshToken && changes.refreshToken.newValue === undefined) {
// Clear all auth data
chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn', 'username']);
// Notify active tabs about logout
chrome.tabs.query({}, function(tabs) {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, {
action: 'remoteLogout'
})
.catch(() => {}); // Ignore errors for inactive tabs
});
});
}
}
});
// Always-active integration
const log = (...args) => chrome.storage.local.get({
log: false
}, prefs => prefs.log && console.log(...args));
const activate = () => {
if (activate.busy) {
return;
}
activate.busy = true;
chrome.storage.local.get({
enabled: true
}, async prefs => {
try {
await chrome.scripting.unregisterContentScripts();
if (prefs.enabled) {
const props = {
'matches': ['*://*/*'],
'allFrames': true,
'matchOriginAsFallback': true,
'runAt': 'document_start'
};
await chrome.scripting.registerContentScripts([{
...props,
'id': 'main',
'js': ['data/inject/main.js'],
'world': 'MAIN'
}, {
...props,
'id': 'isolated',
'js': ['data/inject/isolated.js'],
'world': 'ISOLATED'
}]);
}
} catch (e) {
chrome.action.setBadgeBackgroundColor({
color: '#b16464'
});
chrome.action.setBadgeText({
text: 'E'
});
chrome.action.setTitle({
title: 'Blocker Registration Failed: ' + e.message
});
console.error('Blocker Registration Failed', e);
}
activate.busy = false;
});
};
chrome.runtime.onStartup.addListener(activate);
chrome.runtime.onInstalled.addListener(activate);
chrome.storage.onChanged.addListener(ps => {
if (ps.enabled) {
activate();
}
});
// Add new message listener for snippet processing
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'processSnippets') {
const {
snippets
} = message;
if (!snippets.header && !snippets.footer) {
showToast(sender.tab.id, 'No snippets found', true);
return;
}
const combinedText = `// Header Snippet\n${snippets.header}\n\n// Footer Snippet\n${snippets.footer}`;
// Use existing copyToClipboard function
copyToClipboard(combinedText);
showToast(sender.tab.id, 'Snippets copied to clipboard');
}
});
// Add new message listener for coding question extraction
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'extractCodingQuestion') {
const {
data
} = message;
// Format the extracted data
const formattedText = `Programming Language:
${data.programmingLanguage}
Question:
${data.question}
Input Format:
${data.inputFormat}
Output Format:
${data.outputFormat}
Sample Test Cases:
${data.testCases}`;
// Copy to clipboard and show notification
copyToClipboard(formattedText);
showToast(sender.tab.id, 'Coding question details copied to clipboard');
}
});
// Add new message listener for reset context (clear chat history)
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'resetContext') {
// Log the context reset for debugging
console.log('Chat context reset requested from tab:', sender.tab?.id);
// Optionally, you could clear any stored conversation context here
// For now, just acknowledge the reset
if (sendResponse) {
sendResponse({ success: true, message: 'Context reset' });
}
}
});
// Session expiration handling
const SESSION_DURATION = 12 * 60 * 60 * 1000; // 12 hours in milliseconds
// Function to check if session is expired and logout if needed
// Strict enforcement of 24 hour timeout regardless of activity
async function checkAndHandleSessionExpiration() {
try {
const data = await chrome.storage.local.get(['loggedIn', 'loginTimestamp']);
if (data.loggedIn && data.loginTimestamp) {
const currentTime = Date.now();
if (currentTime - data.loginTimestamp > SESSION_DURATION) {
console.log('24-hour session timeout reached, logging out user');
// Clear all auth data and custom API keys
await chrome.storage.local.remove(['accessToken', 'refreshToken', 'loggedIn', 'username', 'loginTimestamp', 'stealth', 'useCustomAPI', 'aiProvider', 'customEndpoint', 'customAPIKey', 'customModelName']);
// Refresh all tabs to apply logout state
chrome.tabs.query({}, function(tabs) {
tabs.forEach(tab => {
// First notify tabs about session expiration
try {
chrome.tabs.sendMessage(tab.id, {
action: 'sessionExpired'
})
.catch(() => {}); // Ignore errors for tabs that can't receive messages
} catch (err) {
// Ignore errors
}
// Then refresh all tabs
try {
chrome.tabs.reload(tab.id);
} catch (err) {
// Ignore errors if tab can't be reloaded
}
});
});
}
}
} catch (error) {
console.error('Error checking session expiration:', error);
}
}
// Set up alarm for periodic session checks - check frequently to ensure timely logout
chrome.alarms.create('sessionExpirationCheck', {
periodInMinutes: 5 // Check every 5 minutes to ensure timely logout
});
// Listen for alarm and perform session check
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'sessionExpirationCheck') {
checkAndHandleSessionExpiration();
}
// ...existing alarm handlers...
});
// Also check on startup and when installed
chrome.runtime.onStartup.addListener(() => {
checkAndHandleSessionExpiration();
});
chrome.runtime.onInstalled.addListener(() => {
checkAndHandleSessionExpiration();
});
// Check session expiration whenever extension is used
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Check session on various message types to ensure frequent validation
if (message.action) {
checkAndHandleSessionExpiration();
}
return true; // Keep the message channel open for async response
});
// Also add listener for session expired actions from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'sessionExpired') {
// Show notification that session has expired after 24 hours
showToast(sender.tab.id, 'Your session has expired after 24 hours. Please log in again.', true);
sendResponse({
success: true
});
}
return true; // Keep the message channel open for async response
});
// NPTEL Integration
function findAnswer(query) {
const normalizedQuery = normalizeText(query); // Normalize the query
const bestAnswers = []; // Array to store the best answers
let smallestDistance = Infinity; // Track the smallest distance
for (const item of dataset) {
const normalizedQuestion = normalizeText(item.question); // Normalize the question
const distance = levenshteinDistance(normalizedQuery, normalizedQuestion);
// If the distance is within the threshold
const threshold = 15; // Adjust this value based on your needs
if (distance <= threshold) {
if (distance < smallestDistance) {
smallestDistance = distance; // Update smallest distance
bestAnswers.length = 0; // Clear previous answers
bestAnswers.push(item.answer); // Store the new best answer
} else if (distance === smallestDistance) {
bestAnswers.push(item.answer); // Add to the list of best answers
}
}
}
return bestAnswers.length > 0 ? bestAnswers : null; // Return the best answers or null if none found
}
// Function to calculate the Levenshtein distance
function levenshteinDistance(s1, s2) {
const dp = Array(s1.length + 1).fill(null).map(() => Array(s2.length + 1).fill(0));
for (let i = 0; i <= s1.length; i++) {
for (let j = 0; j <= s2.length; j++) {
if (i === 0) {
dp[i][j] = j; // Deletions
} else if (j === 0) {
dp[i][j] = i; // Additions
} else {
dp[i][j] = Math.min(
dp[i - 1][j] + 1, // Deletion
dp[i][j - 1] + 1, // Insertion
dp[i - 1][j - 1] + (s1[i - 1] === s2[j - 1] ? 0 : 1) // Substitution
);
}
}
}
return dp[s1.length][s2.length];
}
// Normalization function to clean up the text
function normalizeText(text) {
return text
.toLowerCase() // Convert to lowercase
.replace(/[-]/g, ' ') // Replace dashes with spaces
.replace(/[^\w\s]/g, '') // Remove all non-word characters (except whitespace)
.trim(); // Trim leading and trailing spaces
}
// Load NPTEL dataset from JSON file
let dataset = [];
async function loadNptelDataset() {
try {
const response = await fetch(chrome.runtime.getURL('data/nptel.json'));
dataset = await response.json();
console.log(`NPTEL dataset loaded: ${dataset.length} questions`);
} catch (error) {
console.error('Failed to load NPTEL dataset:', error);
}
}
// Load dataset on initialization
loadNptelDataset();
// Update showMCQToast to use the current opacity level and include info button
async function showMCQToast(tabId, message, detailedInfo = '') {
const opacity = await getToastOpacity();
// Set default detailed info if not provided
if (!detailedInfo) {
detailedInfo = 'This is the answer to the MCQ question based on analysis of the question content. If you received an incorrect answer, please try rephrasing your question or providing more context.';
}
// Remove any existing toast first
await removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, opacity, detailedInfo) {
// Check if this is "Not an MCQ" response
const isNotMCQ = msg.toLowerCase().includes("not an mcq");
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-active-toast'; // Add ID for tracking
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = 'rgba(15, 15, 20, 0.95)';
toast.style.color = '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacity;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '400px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'center';
// Create answer container with formatted answer
const answerContainer = document.createElement('div');
answerContainer.style.display = 'flex';
answerContainer.style.alignItems = 'center';
answerContainer.style.flexGrow = '1';
if (!isNotMCQ) {
// Parse the message to separate option identifier from answer text
let optionIdentifier, optionAnswer;
// Handle different format patterns like "A. answer", "1. answer", "A answer", "1 answer"
const match = msg.match(/^([A-Za-z0-9]+)\.?\s+(.+)$/);
if (match) {
optionIdentifier = match[1].trim();
optionAnswer = match[2].trim();
} else {
// Fallback if the pattern doesn't match
const parts = msg.split(' ');
optionIdentifier = parts[0].replace('.', '');
optionAnswer = parts.slice(1).join(' ');
}
// Determine if option is letter or number based
const isLetter = /^[A-Za-z]$/.test(optionIdentifier);
const optionColor = isLetter ? '#4285f4' : '#f4b400'; // Blue for letters, Yellow/Gold for numbers
// Option indicator dot
const optionDot = document.createElement('div');
optionDot.style.width = '22px';
optionDot.style.height = '22px';
optionDot.style.backgroundColor = optionColor;
optionDot.style.color = 'white';
optionDot.style.borderRadius = '50%';
optionDot.style.display = 'flex';
optionDot.style.alignItems = 'center';
optionDot.style.justifyContent = 'center';
optionDot.style.marginRight = '10px';
optionDot.style.fontWeight = 'bold';
optionDot.style.fontSize = '12px';
optionDot.style.boxShadow = `0 2px 4px ${optionColor}66`;
optionDot.textContent = optionIdentifier.toUpperCase();
// Answer text
const answerText = document.createElement('span');
answerText.textContent = optionAnswer;
answerText.style.fontSize = '14px';
answerText.style.fontWeight = '500';
answerContainer.appendChild(optionDot);
answerContainer.appendChild(answerText);
} else {
// For "Not an MCQ" response - no icon, just show the text
const messageText = document.createElement('span');
messageText.textContent = msg;
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
answerContainer.appendChild(messageText);
}
// Create buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.alignItems = 'center';
buttonsContainer.style.marginLeft = '10px';
// Info button
const infoBtn = document.createElement('button');
infoBtn.innerHTML = '';
infoBtn.title = 'Show more information';
infoBtn.style.background = 'none';
infoBtn.style.border = 'none';
infoBtn.style.color = 'rgba(255, 255, 255, 0.8)';
infoBtn.style.cursor = 'pointer';
infoBtn.style.padding = '2px';
infoBtn.style.marginRight = '6px';
infoBtn.style.borderRadius = '4px';
infoBtn.style.lineHeight = '0';
infoBtn.style.transition = 'all 0.2s';
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Detailed info container (initially hidden)
const detailedInfoContainer = document.createElement('div');
detailedInfoContainer.style.marginTop = '12px';
detailedInfoContainer.style.padding = '10px 12px';
detailedInfoContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
detailedInfoContainer.style.borderRadius = '6px';
detailedInfoContainer.style.fontSize = '13px';
detailedInfoContainer.style.display = 'none';
detailedInfoContainer.style.maxHeight = '120px';
detailedInfoContainer.style.overflow = 'auto';
detailedInfoContainer.style.lineHeight = '1.4';
detailedInfoContainer.style.color = 'rgba(255, 255, 255, 0.9)';
detailedInfoContainer.textContent = isNotMCQ ?
'The selected text does not appear to be a multiple-choice question. Please try selecting a valid MCQ.' :
detailedInfo;
// Add event listeners
let expanded = false;
let hideTimeoutId = null;
infoBtn.onmouseover = function() {
infoBtn.style.color = '#ffffff';
infoBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
infoBtn.onmouseout = function() {
infoBtn.style.color = 'rgba(255, 255, 255, 0.8)';
infoBtn.style.backgroundColor = 'transparent';
};
closeBtn.onmouseover = function() {
closeBtn.style.color = '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
infoBtn.onclick = function() {
expanded = !expanded;
detailedInfoContainer.style.display = expanded ? 'block' : 'none';
infoBtn.innerHTML = expanded ?
'' :
'';
// Clear the auto-hide timeout when info is expanded
if (expanded) {
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
} else {
// Restart the auto-hide timer when info is collapsed
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
}
};
closeBtn.onclick = function() {
// Clear any existing timeout
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble the toast
buttonsContainer.appendChild(infoBtn);
buttonsContainer.appendChild(closeBtn);
headerContainer.appendChild(answerContainer);
headerContainer.appendChild(buttonsContainer);
toast.appendChild(headerContainer);
toast.appendChild(detailedInfoContainer);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
// Set initial auto-hide timeout
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
},
args: [message, opacity, detailedInfo]
});
}
// Update showNPTELToast to use the current opacity level and include info button
async function showNPTELToast(tabId, message, isError = false, detailedInfo = '') {
const opacity = await getToastOpacity();
// Set default detailed info if not provided
if (!detailedInfo) {
if (isError) {
detailedInfo = 'Possible issues with NPTEL search:\n• The question may not be in our database\n• Try selecting only the exact question text\n• The question might be newly added to NPTEL';
} else {
detailedInfo = 'This answer was found by matching your question with the NPTEL question database. The confidence level depends on how closely your selected text matches a known question.';
}
}
// Remove any existing toast first
await removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, isError, opacity, detailedInfo) {
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-active-toast'; // Add ID for tracking
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = isError ? 'rgba(40, 10, 10, 0.95)' : 'rgba(15, 15, 20, 0.95)';
toast.style.color = isError ? '#ff6b6b' : '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacity;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '320px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = isError ? '1px solid rgba(255, 107, 107, 0.2)' : '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'flex-start';
// Create message container
const messageContainer = document.createElement('div');
messageContainer.style.flexGrow = '1';
messageContainer.style.marginRight = '12px';
// Add indicator dot
const indicatorDot = document.createElement('span');
indicatorDot.style.display = 'inline-block';
indicatorDot.style.width = '8px';
indicatorDot.style.height = '8px';
indicatorDot.style.backgroundColor = isError ? '#ff6b6b' : '#4ade80';
indicatorDot.style.borderRadius = '50%';
indicatorDot.style.marginRight = '8px';
indicatorDot.style.boxShadow = isError ? '0 0 4px rgba(255, 107, 107, 0.6)' : '0 0 4px rgba(74, 222, 128, 0.6)';
// Add message text
const messageText = document.createElement('span');
messageText.innerHTML = msg.replace(/\n/g, ' '); // Use innerHTML to handle newlines
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
messageText.style.lineHeight = '1.4';
messageText.style.wordBreak = 'break-word';
// Combine dot and text
const messageContent = document.createElement('div');
messageContent.style.display = 'flex';
messageContent.style.alignItems = 'center';
messageContent.appendChild(indicatorDot);
messageContent.appendChild(messageText);
messageContainer.appendChild(messageContent);
// Create buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.alignItems = 'center';
buttonsContainer.style.marginLeft = '8px';
// Info button
const infoBtn = document.createElement('button');
infoBtn.innerHTML = '';
infoBtn.title = 'Show more information';
infoBtn.style.background = 'none';
infoBtn.style.border = 'none';
infoBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
infoBtn.style.cursor = 'pointer';
infoBtn.style.padding = '2px';
infoBtn.style.marginRight = '6px';
infoBtn.style.borderRadius = '4px';
infoBtn.style.lineHeight = '0';
infoBtn.style.transition = 'all 0.2s';
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Detailed info container (initially hidden)
const detailedInfoContainer = document.createElement('div');
detailedInfoContainer.style.marginTop = '12px';
detailedInfoContainer.style.padding = '10px 12px';
detailedInfoContainer.style.backgroundColor = isError ? 'rgba(255, 107, 107, 0.1)' : 'rgba(255, 255, 255, 0.1)';
detailedInfoContainer.style.borderRadius = '6px';
detailedInfoContainer.style.fontSize = '13px';
detailedInfoContainer.style.display = 'none';
detailedInfoContainer.style.maxHeight = '120px';
detailedInfoContainer.style.overflow = 'auto';
detailedInfoContainer.style.lineHeight = '1.4';
detailedInfoContainer.style.color = isError ? 'rgba(255, 107, 107, 0.9)' : 'rgba(255, 255, 255, 0.9)';
detailedInfoContainer.textContent = detailedInfo;
// Add event listeners
let expanded = false;
let hideTimeoutId = null;
infoBtn.onmouseover = function() {
infoBtn.style.color = isError ? '#ff6b6b' : '#ffffff';
infoBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
infoBtn.onmouseout = function() {
infoBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
infoBtn.style.backgroundColor = 'transparent';
};
closeBtn.onmouseover = function() {
closeBtn.style.color = isError ? '#ff6b6b' : '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = isError ? 'rgba(255, 107, 107, 0.8)' : 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
infoBtn.onclick = function() {
expanded = !expanded;
detailedInfoContainer.style.display = expanded ? 'block' : 'none';
infoBtn.innerHTML = expanded ?
'' :
'';
// Clear the auto-hide timeout when info is expanded
if (expanded) {
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
} else {
// Restart the auto-hide timer when info is collapsed
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
}
};
closeBtn.onclick = function() {
// Clear any existing timeout
if (hideTimeoutId) {
clearTimeout(hideTimeoutId);
hideTimeoutId = null;
}
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble the toast
buttonsContainer.appendChild(infoBtn);
buttonsContainer.appendChild(closeBtn);
headerContainer.appendChild(messageContainer);
headerContainer.appendChild(buttonsContainer);
toast.appendChild(headerContainer);
toast.appendChild(detailedInfoContainer);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
// Set initial auto-hide timeout
hideTimeoutId = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
}, 5000);
},
args: [message, isError, opacity, detailedInfo]
});
}
// Show a spinner toast while AI query is being processed
async function showSpinnerToast(tabId, message = 'Processing your request...') {
const opacity = await getToastOpacity();
// Remove any existing toast first
await removeExistingToast(tabId);
chrome.scripting.executeScript({
target: {
tabId: tabId
},
func: function(msg, opacity) {
// Create toast container
const toast = document.createElement('div');
toast.id = 'neopass-spinner-toast';
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.backgroundColor = 'rgba(15, 15, 20, 0.95)';
toast.style.color = '#f8f9fa';
toast.style.padding = '14px 16px';
toast.style.borderRadius = '8px';
toast.style.zIndex = '999999';
toast.style.opacity = opacity;
toast.style.transition = 'all 0.3s ease';
toast.style.maxWidth = '320px';
toast.style.fontFamily = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
toast.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
toast.style.border = '1px solid rgba(255, 255, 255, 0.1)';
toast.style.backdropFilter = 'blur(10px)';
toast.style.WebkitBackdropFilter = 'blur(10px)';
// Create header container
const headerContainer = document.createElement('div');
headerContainer.style.display = 'flex';
headerContainer.style.justifyContent = 'space-between';
headerContainer.style.alignItems = 'center';
// Create message container with spinner
const messageContainer = document.createElement('div');
messageContainer.style.display = 'flex';
messageContainer.style.alignItems = 'center';
messageContainer.style.gap = '10px';
messageContainer.style.flexGrow = '1';
// Spinner indicator (pulsing dot)
const spinnerDot = document.createElement('span');
spinnerDot.style.display = 'inline-block';
spinnerDot.style.width = '8px';
spinnerDot.style.height = '8px';
spinnerDot.style.backgroundColor = '#64b5f6';
spinnerDot.style.borderRadius = '50%';
spinnerDot.style.boxShadow = '0 0 4px rgba(100, 181, 246, 0.6)';
spinnerDot.style.animation = 'pulse 1.5s ease-in-out infinite';
// Message text
const messageText = document.createElement('span');
messageText.textContent = msg;
messageText.style.fontSize = '14px';
messageText.style.fontWeight = '500';
messageText.style.lineHeight = '1.4';
messageText.style.wordBreak = 'break-word';
messageContainer.appendChild(spinnerDot);
messageContainer.appendChild(messageText);
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '';
closeBtn.title = 'Close';
closeBtn.style.background = 'none';
closeBtn.style.border = 'none';
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.cursor = 'pointer';
closeBtn.style.padding = '2px';
closeBtn.style.marginLeft = '8px';
closeBtn.style.borderRadius = '4px';
closeBtn.style.lineHeight = '0';
closeBtn.style.transition = 'all 0.2s';
// Add CSS animation keyframes
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.2);
}
}
`;
document.head.appendChild(style);
// Event listeners
closeBtn.onmouseover = function() {
closeBtn.style.color = '#ffffff';
closeBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
};
closeBtn.onmouseout = function() {
closeBtn.style.color = 'rgba(255, 255, 255, 0.8)';
closeBtn.style.backgroundColor = 'transparent';
};
closeBtn.onclick = function() {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => toast.remove(), 300);
};
// Assemble and append
headerContainer.appendChild(messageContainer);
headerContainer.appendChild(closeBtn);
toast.appendChild(headerContainer);
document.body.appendChild(toast);
// Add entrance animation
toast.style.transform = 'translateY(10px) translateX(-50%)';
setTimeout(() => {
toast.style.transform = 'translateY(0) translateX(-50%)';
}, 10);
},
args: [message, opacity]
});
}