Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
82 changes: 82 additions & 0 deletions app/Http/Controllers/VanillaAiChatController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http; // Or use OpenAI PHP client if installed
use OpenAI\Laravel\Facades\OpenAI; // Use this if openai-php/client is set up

class VanillaAiChatController extends Controller
{
public function chat(Request $request)
{
$request->validate([
'message' => 'required|string|max:2000',
]);

$userMessage = $request->input('message');

// --- Option 1: Using OpenAI PHP Client (Recommended if set up) ---
// Ensure your OPENAI_API_KEY is in .env and you've run composer require openai-php/client
// Also, make sure you have published the OpenAI config if needed: php artisan vendor:publish --provider="OpenAI\Laravel\OpenAIServiceProvider"
try {
if (!config('openai.api_key') && !env('OPENAI_API_KEY')) {
// Fallback to a canned response if API key is not configured
return response()->json(['reply' => "Hello from Laravel! Your message was: \"{$userMessage}\". (AI not configured)"]);
}

$response = OpenAI::chat()->create([
'model' => 'gpt-3.5-turbo', // Or gpt-4 if you have access
'messages' => [
['role' => 'system', 'content' => 'You are a helpful assistant.'],
['role' => 'user', 'content' => $userMessage],
],
]);

$reply = $response->choices[0]->message->content;
return response()->json(['reply' => trim($reply)]);

} catch (\Throwable $th) {
// Log the error for debugging
// Log::error('OpenAI API Error: ' . $th->getMessage());
// Fallback to a canned response in case of an API error
// In a real app, you might want more sophisticated error handling
if (str_contains($th->getMessage(), 'cURL error 6')) { // Example: DNS resolution error
return response()->json(['reply' => "Sorry, I'm having trouble connecting to the AI service right now. Please check network or API key."]);
}
// Generic error if API key is missing or invalid
if (str_contains($th->getMessage(), 'Incorrect API key provided') || str_contains($th->getMessage(), 'You didn\'t provide an API key')) {
return response()->json(['reply' => "AI service connection error: Please ensure your API key is correctly configured in the .env file."]);
}
return response()->json(['reply' => "Sorry, an error occurred with the AI service. Your message was: \"{$userMessage}\". Error: " . $th->getMessage()]);
}

// --- Option 2: Canned response (if you don't want to use an API yet) ---
// return response()->json(['reply' => "Hello from Laravel! You said: \"{$userMessage}\". This is a test response."]);

// --- Option 3: Using Laravel's HTTP Client to call a generic AI API (more manual) ---
/*
$apiKey = env('SOME_OTHER_AI_API_KEY');
if (!$apiKey) {
return response()->json(['reply' => "AI API key not configured."]);
}
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
])->post('https://api.example-ai.com/v1/chat', [ // Replace with actual API endpoint
'prompt' => $userMessage,
'max_tokens' => 150,
]);

if ($response->successful()) {
return response()->json(['reply' => $response->json()['choices'][0]['text'] ?? 'No reply generated.']);
} else {
return response()->json(['reply' => 'Failed to get response from AI.', 'details' => $response->body()]);
}
} catch (\Exception $e) {
return response()->json(['reply' => 'Error connecting to AI service: ' . $e->getMessage()]);
}
*/
}
}
100 changes: 100 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import tkinter as tk
from tkinter import scrolledtext, Menu, filedialog, messagebox
from patch_suite_integration import KNEAUXPatchSuiteIntegration # Import the integration class

class AwesomeCodeEditor:
def __init__(self, root):
self.root = root
self.root.title("Awesome Code Editor")
self.root.geometry("800x600")

self.text_area = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, undo=True)
self.text_area.pack(fill=tk.BOTH, expand=True)
self.current_file_path = None # To keep track of the currently open file

self.menu_bar = Menu(self.root)
self.root.config(menu=self.menu_bar)

self.file_menu = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="File", menu=self.file_menu)
self.file_menu.add_command(label="Open", command=self.open_file)
self.file_menu.add_command(label="Save", command=self.save_file)
self.file_menu.add_command(label="Save As...", command=self.save_file_as)
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=self.root.quit)

self.tools_menu = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Tools", menu=self.tools_menu)
self.tools_menu.add_command(label="Patch with KNEAUX EDU", command=self.open_patch_suite_dialog)

# Initialize patch suite integration object - created on demand
self.patch_suite_instance = None


def open_file(self):
filepath = filedialog.askopenfilename(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
if not filepath:
return
try:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
self.text_area.delete(1.0, tk.END)
self.text_area.insert(tk.END, content)
self.current_file_path = filepath # Store current file path
self.root.title(f"Awesome Code Editor - {filepath}")
except Exception as e:
messagebox.showerror("Error", f"Failed to open file: {e}")
self.current_file_path = None

def save_file_as(self):
filepath = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
if not filepath:
return False # Indicate cancellation
try:
with open(filepath, "w", encoding="utf-8") as f:
content = self.text_area.get(1.0, tk.END)
f.write(content)
self.current_file_path = filepath # Update current file path
self.root.title(f"Awesome Code Editor - {filepath}")
return True # Indicate success
except Exception as e:
messagebox.showerror("Error", f"Failed to save file: {e}")
return False # Indicate failure

def save_file(self):
if self.current_file_path:
try:
with open(self.current_file_path, "w", encoding="utf-8") as f:
content = self.text_area.get(1.0, tk.END)
f.write(content)
self.root.title(f"Awesome Code Editor - {self.current_file_path}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save file: {e}")
else:
self.save_file_as() # If no current file, use Save As logic

def open_patch_suite_dialog(self):
if self.patch_suite_instance is None or not self.patch_suite_instance.patch_window or not self.patch_suite_instance.patch_window.winfo_exists():
self.patch_suite_instance = KNEAUXPatchSuiteIntegration(self.root)
self.patch_suite_instance.show_patch_dialog()

# Optionally, if a file is open, you could pass its path or directory to the patch suite
# For example:
# current_dir = None
# if self.current_file_path:
# current_dir = Path(self.current_file_path).parent
# self.patch_suite_instance.show_patch_dialog(initial_dir=current_dir)
# This would require KNEAUXPatchSuiteIntegration to accept an initial_dir parameter.

def main():
root = tk.Tk()
editor = AwesomeCodeEditor(root)
root.mainloop()

if __name__ == "__main__":
main()
164 changes: 164 additions & 0 deletions patch_suite_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import tkinter as tk
from tkinter import filedialog, messagebox, Toplevel
import zipfile
import shutil # Added for removing temp directory
import re
from pathlib import Path

PREMIUM_PATTERNS = [
(re.compile(r"\b(isPremium|licenseValid|proVersion|hasProAccess)\s*=\s*false\b"), r"\1 = True"),
(re.compile(r"if\s*\(!?\s*(isPremium|licenseValid|proVersion|hasProAccess)\)"), r"if True"), # Simplified for editor context
(re.compile(r"checkLicense\s*\([^)]*\)\s*{[^}]*}"), r"checkLicense() { return True }"), # JS/TS like
]

TEXT_FILE_EXTENSIONS = {'.js', '.ts', '.json', '.html', '.py', '.txt', '.cfg', '.xml', '.md', '.java', '.cs', '.cpp', '.c', '.php'} # Expanded list

class KNEAUXPatchSuiteIntegration:
def __init__(self, editor_root):
self.editor_root = editor_root # Main editor window to anchor dialogs
self.patch_window = None

def show_patch_dialog(self):
if self.patch_window and self.patch_window.winfo_exists():
self.patch_window.lift()
return

self.patch_window = Toplevel(self.editor_root)
self.patch_window.title("KNEAUX-COD3 EDU Patch Suite")
self.patch_window.geometry("450x200")
self.patch_window.transient(self.editor_root) # Make it a child of the main window

tk.Label(self.patch_window, text="🔧 Select an extension (ZIP/CRX) or folder to patch:").pack(pady=15)
tk.Button(self.patch_window, text="📂 Select File or Folder", command=self.select_and_process).pack(pady=5)
tk.Button(self.patch_window, text="✖️ Close", command=self.patch_window.destroy).pack(pady=10)


def select_and_process(self):
# Determine if it's a file or folder dialog needed
# For simplicity, starting with askopenfilename and then checking type
path_str = filedialog.askopenfilename(
parent=self.patch_window,
title="Select ZIP/CRX file or any file in a target folder",
filetypes=[("Supported Archives", "*.zip *.crx"), ("All files", "*.*")]
)

if not path_str:
# User cancelled
# Try asking for a directory if no file was selected, or provide a separate button for directory
path_str = filedialog.askdirectory(
parent=self.patch_window,
title="Select Folder to Patch"
)
if not path_str:
messagebox.showinfo("Info", "No file or folder selected.", parent=self.patch_window)
return

file_path = Path(path_str)

if file_path.is_dir():
self.scan_and_patch_folder(file_path)
elif file_path.is_file():
if file_path.suffix in ['.zip', '.crx']:
temp_dir = file_path.parent / (file_path.stem + "_unzipped_temp")
if temp_dir.exists():
shutil.rmtree(temp_dir) # Clean up previous attempt
temp_dir.mkdir(parents=True, exist_ok=True)

try:
with zipfile.ZipFile(file_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)

patched_files_count = self.scan_and_patch_folder(temp_dir, is_temp=True)

if patched_files_count > 0:
self.rezip_folder(temp_dir, file_path.parent / (file_path.stem + "_patched" + file_path.suffix))
elif patched_files_count == 0:
messagebox.showinfo("ℹ️ No Changes", "No patchable code found in the archive.", parent=self.patch_window)
# If patched_files_count is None, an error occurred during patching

except zipfile.BadZipFile:
messagebox.showerror("Error", "Invalid or corrupted ZIP/CRX file.", parent=self.patch_window)
except Exception as e:
messagebox.showerror("Error", f"Failed to process archive: {e}", parent=self.patch_window)
finally:
if temp_dir.exists():
shutil.rmtree(temp_dir) # Clean up
elif file_path.suffix in TEXT_FILE_EXTENSIONS:
# Allow patching a single, currently open, or selected text file
if self.patch_single_file(file_path):
messagebox.showinfo("✅ Patch Complete", f"File '{file_path.name}' patched.", parent=self.patch_window)
else:
messagebox.showinfo("ℹ️ No Changes", f"No patchable patterns found in '{file_path.name}'.", parent=self.patch_window)

elif file_path.suffix in ['.exe', '.bin']:
messagebox.showinfo("Note", "Binary patching is not supported. Please select source files, folders, or archives (ZIP/CRX).", parent=self.patch_window)
else:
messagebox.showwarning("Unsupported File", f"File type '{file_path.suffix}' is not directly patchable. Select a folder, ZIP, or CRX.", parent=self.patch_window)
else:
messagebox.showerror("Error", "Selected path is not a valid file or folder.", parent=self.patch_window)


def patch_code_content(self, content):
original_content = content
for pattern, repl in PREMIUM_PATTERNS:
content = pattern.sub(repl, content)
return content, content != original_content

def patch_single_file(self, file_path: Path):
try:
content = file_path.read_text(encoding='utf-8')
patched_content, changed = self.patch_code_content(content)
if changed:
file_path.write_text(patched_content, encoding='utf-8')
print(f"[+] Patched: {file_path}")
return True
except UnicodeDecodeError:
print(f"[!] Skipping binary or non-UTF-8 file: {file_path.name}")
except Exception as e:
messagebox.showerror("File Patch Error", f"Error patching file {file_path.name}:\n{e}", parent=self.patch_window or self.editor_root)
print(f"[!] Error processing {file_path.name}: {e}")
return False

def scan_and_patch_folder(self, folder_path: Path, is_temp: bool = False):
patched_files_count = 0
error_occurred = False
for path_object in folder_path.rglob('*'):
if path_object.is_file() and path_object.suffix in TEXT_FILE_EXTENSIONS:
if self.patch_single_file(path_object):
patched_files_count += 1
# Check if patch_single_file returned None due to an error, if so, set error_occurred flag
# This check is a bit tricky as False means no patches applied, not necessarily an error.
# We rely on messagebox shown in patch_single_file for error reporting.

if not is_temp: # Only show message for direct folder patching here. Archive patching has its own summary.
if patched_files_count > 0:
messagebox.showinfo("✅ Patch Complete", f"{patched_files_count} file(s) patched in folder '{folder_path.name}'.", parent=self.patch_window)
else:
messagebox.showinfo("ℹ️ No Changes", f"No patchable code found in folder '{folder_path.name}'.", parent=self.patch_window)

return patched_files_count # Return count for archive processing logic


def rezip_folder(self, folder_to_zip: Path, output_zip_path: Path):
try:
with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file in folder_to_zip.rglob('*'):
if file.is_file():
zipf.write(file, file.relative_to(folder_to_zip))
messagebox.showinfo("📦 Repack Complete", f"Patched archive saved to:\n{output_zip_path}", parent=self.patch_window)
except Exception as e:
messagebox.showerror("Repack Error", f"Failed to repack archive: {e}", parent=self.patch_window)
print(f"[!] Error repacking {folder_to_zip} to {output_zip_path}: {e}")

# Example of how it might be instantiated from the main editor (for testing purposes)
if __name__ == '__main__':
root = tk.Tk()
root.title("Main Editor Window (Test)")
root.geometry("600x400")

def open_patcher():
patch_suite_instance = KNEAUXPatchSuiteIntegration(root)
patch_suite_instance.show_patch_dialog()

tk.Button(root, text="Open KNEAUX Patcher", command=open_patcher).pack(pady=20)
root.mainloop()
Loading