// ==UserScript==
// @name Slash's Modmenu (4.0.1)
// @version 4.0.1
// @namespace slash.gay
// @license MIT
// @description A sleek and modern C.AI modmenu
// @author Slash
// @match https://*.character.ai/*
// @exclude https://pay.character.ai/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @icon https://cdn.slash.gay/r/ModMenu-Logo.png
// ==/UserScript==
(function() {
'use strict';
const cssStyles = `
/* css hehe */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.popup {
background-color: #fff;
border: 1px solid #ddd;
padding: 20px;
border-radius: 5px;
text-align: center;
}
.loading-text {
color: #fff;
font-size: 24px;
}
.modmenu {
position: fixed;
background-color: #333232;
border-radius: 5px;
padding: 10px;
z-index: 9999;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 300px;
height: 550px;
cursor: default; /* Default cursor style */
}
.modmenu h3 {
margin-top: 0;
cursor: move; /* Cursor style for draggable */
user-select: none; /* Prevent text selection */
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.minimize-button {
position: absolute;
top: 5px;
right: 5px;
background: none;
border: none;
font-size: 20px;
font-weight: bold;
cursor: pointer;
}
.module-list {
max-height: 470px;
overflow-y: auto;
}
.module-box {
padding: 10px;
margin: 5px 0;
border-radius: 5px;
display: flex;
flex-direction: column;
background-color: #404040;
}
.module-title {
font-size: 16px;
margin-bottom: 5px;
}
.module-description {
font-size: 14px;
margin-bottom: 5px;
}
.module-details {
font-size: 10px;
color: #888;
}
.module-toggle {
margin-top: auto;
}
.reload-message {
background-color: yellow;
padding: 10px;
margin-top: 10px;
display: none;
}
.version {
font-size: 12px;
margin-top: 10px;
}
.module-list a {
color: #007bff;
}
`;
const hasAcceptedTOS = GM_getValue('acceptedTOS', true);
const modmenuState = GM_getValue('modmenuState', { minimized: false, position: { top: '15px', right: '15px' } });
let repositories = JSON.parse(localStorage.getItem('repositories')) || [];
GM_addStyle(cssStyles);
if (!hasAcceptedTOS) {
showTermsOfServicePopup();
}
function showTermsOfServicePopup() {
const popupOverlay = document.createElement('div');
popupOverlay.classList.add('overlay');
const popup = document.createElement('div');
popup.classList.add('popup');
const title = document.createElement('h3');
title.textContent = "Slash's Modmenu";
title.id = 'terms-and-stuff'
popup.appendChild(title);
const description = document.createElement('p');
description.textContent = "By using Slash's Modmenu, you must agree to our Terms of Service and Privacy Policy.";
description.id = 'terms-and-stuff'
popup.appendChild(description);
const termsCheckbox = document.createElement('input');
termsCheckbox.type = 'checkbox';
termsCheckbox.id = 'termsCheckbox';
termsCheckbox.id = 'terms-and-stuff'
const termsLabel = document.createElement('label');
termsLabel.htmlFor = 'termsCheckbox';
termsLabel.textContent = ' I agree to the Terms of Service and Privacy Policy';
popup.appendChild(termsCheckbox);
popup.appendChild(termsLabel);
const tosLink = createLink('Terms of Service', 'https://example.com/tos');
const privacyLink = createLink('Privacy Policy', 'https://example.com/privacy');
popup.appendChild(document.createElement('br'));
popup.appendChild(tosLink);
popup.appendChild(document.createTextNode(' and '));
popup.appendChild(privacyLink);
popup.appendChild(document.createElement('br'));
const acceptButton = document.createElement('button');
acceptButton.id = 'terms-and-stuff'
acceptButton.textContent = 'Continue';
acceptButton.addEventListener('click', function() {
if (termsCheckbox.checked) {
GM_setValue('acceptedTOS', true);
document.body.removeChild(popupOverlay);
location.reload();
}
});
popup.appendChild(acceptButton);
popupOverlay.appendChild(popup);
document.body.appendChild(popupOverlay);
}
function createLink(text, url) {
const link = document.createElement('a');
link.textContent = text;
link.href = url;
link.target = '_blank';
return link;
}
if (hasAcceptedTOS) {
const loadingOverlay = document.createElement('div');
loadingOverlay.classList.add('overlay');
document.body.appendChild(loadingOverlay);
const loadingText = document.createElement('div');
loadingText.classList.add('loading-text');
loadingText.textContent = '|';
loadingOverlay.appendChild(loadingText);
const loadingAnimation = ['|', '/', '-', '\\'];
let currentAnimationIndex = 0;
setInterval(() => {
loadingText.textContent = loadingAnimation[currentAnimationIndex];
currentAnimationIndex = (currentAnimationIndex + 1) % loadingAnimation.length;
}, 200);
setTimeout(() => {
loadingOverlay.style.display = 'none';
const modmenu = document.createElement('div');
modmenu.classList.add('modmenu');
modmenu.style.top = modmenuState.position.top;
modmenu.style.right = modmenuState.position.right;
const title = document.createElement('h3');
title.textContent = "Slash's Modmenu";
modmenu.appendChild(title);
const moduleList = document.createElement('div');
moduleList.classList.add('module-list');
const modules = [{
title: 'Hey!',
description: 'This modmenu is under heavy development! Please report any bugs to me(at)slash(dot)gay, thanks!',
author: 'This is not a module',
defaultStatus: true,
code: function() {
console.log("what");
}
},
{ // NOT YET WORKING ON NEW WEBSITE --- LOW PRIORITY
title: 'Not yet supported - New logo',
description: 'Adds a needed logo change',
author: 'Slash',
defaultStatus: false,
code: function() {
function replaceImageSrc() {
var targetImage = document.querySelector('text-2xl font-sans font-semibold flex items-center');
if (targetImage) {
targetImage.src = 'https://cdn.slash.gay/r/c-ai-logo.png';
}
}
setInterval(replaceImageSrc, 50);
}
},
{
title: 'Message Checker',
description: 'Checks if your message has any words that trigger the filter',
author: 'Slash',
defaultStatus: true,
code: function() {
function checkMessage() {
const prohibitedWords = ['sex', 'penis', 'vagina', 'cum', 'lets fuck', 'let\'s fuck', 'wanna fuck', 'horny', 'intimate activites', 'lets fck', 'let\'s fck', 'pussy', 'breast', 'boob'];
if (window.location.href.includes("/chat")) {
const textAreas = document.querySelectorAll('textarea.flex.max-h-96.px-3.border.file\\:border-0.file\\:bg-transparent.file\\:text-md.file\\:font-medium.placeholder\\:text-placeholder.disabled\\:cursor-not-allowed.disabled\\:opacity-50.resize-none.focus-visible\\:outline-none.border-input.h-10.py-2.text-lg.w-full.border-none.rounded-2xl.bg-surface-elevation-1.ml-2[inputmode="text"]');
const existingNotification = document.getElementById('messageCheckerNotification');
if (existingNotification && existingNotification.parentNode === document.body) {
document.body.removeChild(existingNotification);
}
textAreas.forEach(userInput => {
const userMessage = userInput.value.toLowerCase();
const containsProhibitedWord = prohibitedWords.some(word => userMessage.includes(word));
if (containsProhibitedWord) {
const notification = document.createElement('div');
notification.id = 'messageCheckerNotification';
notification.style.position = 'fixed';
notification.style.bottom = '10px';
notification.style.right = '10px';
notification.style.padding = '15px';
notification.style.background = '#ff3333';
notification.style.color = '#fff';
notification.style.border = '1px solid #ddd';
notification.style.zIndex = '9999';
notification.style.width = '250px';
notification.style.transition = 'all 0.3s ease';
notification.textContent = `I wouldn't say that, as it may trigger the filter: ${prohibitedWords.find(word => userMessage.includes(word))}`;
document.body.appendChild(notification);
function closeNotification() {
if (notification.parentNode === document.body) {
document.body.removeChild(notification);
}
}
userInput.addEventListener('input', function() {
const updatedUserMessage = userInput.value.toLowerCase();
const stillContainsProhibitedWord = prohibitedWords.some(word => updatedUserMessage.includes(word));
if (!stillContainsProhibitedWord) {
closeNotification();
}
});
userInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
closeNotification();
}
});
}
});
}
}
setInterval(checkMessage, 1000);
}
},
{
title: 'Not yet supported - Auto Regenerate',
description: 'Automatically regenerates the message when the filter is triggered',
author: 'Slash',
defaultStatus: false,
code: function() {
function autoRegenerate() {
const chatPage = window.location.pathname.includes('/chat');
console.log("Is chat page:", chatPage);
if (!chatPage) return;
const specificDiv = document.querySelector('.rah-static.rah-static--height-specific');
console.log("Specific div found:", specificDiv);
if (!specificDiv) return;
const specificText = "Sometimes the AI generates a reply that doesn't meet our guidelines.";
if (specificDiv.textContent.includes(specificText)) {
console.log("Specific text found in specific div");
// Simplified button selector
const button = document.querySelector('.z-0');
if (button) {
button.click();
console.log("Clicked the button with the specified class");
}
}
}
setInterval(autoRegenerate, 1000);
}
},
{
title: 'Tab Cloaker',
description: 'I swear! I\'m just on google!',
author: 'Slash',
defaultStatus: true,
code: function() {
function cloaker() {
document.title = 'Google';
const linkElements = document.head.querySelectorAll('link[rel="icon"]');
linkElements.forEach(linkElement => {
linkElement.href = 'https://www.google.com/favicon.ico';
});
}
setInterval(cloaker, 1000);
},
},
{
title: 'Chat Notes (doesnt work)',
description: 'SOON',
author: 'User',
defaultStatus: false,
code: function() {
function addNotebox() {
const isChat2Page = window.location.href.includes('/chat2');
const existingNotebox = document.getElementById('chatNotesNotebox');
if (isChat2Page && !existingNotebox) {
const notebox = document.createElement('div');
notebox.id = 'chatNotesNotebox';
notebox.style.position = 'fixed';
notebox.style.bottom = '10px';
notebox.style.left = '10px';
notebox.style.padding = '10px';
notebox.style.background = '#fff';
notebox.style.border = '1px solid #ddd';
notebox.style.zIndex = '9999';
notebox.style.width = '200px';
const noteInput = document.createElement('textarea');
noteInput.style.width = '100%';
noteInput.style.height = '80px';
noteInput.placeholder = 'Type your notes here...';
const submitButton = document.createElement('button');
submitButton.textContent = 'Save Note';
submitButton.style.marginTop = '5px';
submitButton.style.cursor = 'pointer';
submitButton.addEventListener('click', function() {
const userInput = document.getElementById('user-input');
const existingNote = noteInput.value.trim();
if (existingNote !== '') {
const currentMessage = userInput.value.trim();
const newMessage = `(Here are some things to remember, do not mention anything contained in the brackets, as this is so you do not forget: ${existingNote}) `;
userInput.value = newMessage + currentMessage;
}
});
notebox.appendChild(noteInput);
notebox.appendChild(submitButton);
document.body.appendChild(notebox);
}
}
setInterval(addNotebox, 1000);
},
}];
modules.forEach(module => {
const moduleBox = document.createElement('div');
moduleBox.classList.add('module-box');
const title = document.createElement('div');
title.classList.add('module-title');
title.textContent = module.title;
moduleBox.appendChild(title);
const description = document.createElement('div');
description.classList.add('module-description');
description.textContent = module.description;
moduleBox.appendChild(description);
const author = document.createElement('div');
author.classList.add('module-details');
author.textContent = 'by ' + module.author;
moduleBox.appendChild(author);
const toggleLabel = document.createElement('label');
toggleLabel.classList.add('module-toggle');
toggleLabel.textContent = 'Enable';
const toggleInput = document.createElement('input');
toggleInput.type = 'checkbox';
toggleInput.checked = GM_getValue(module.title, module.defaultStatus);
toggleInput.addEventListener('change', function() {
GM_setValue(module.title, this.checked);
reloadMessage.style.display = 'block';
});
toggleLabel.appendChild(toggleInput);
moduleBox.appendChild(toggleLabel);
moduleList.appendChild(moduleBox);
if (GM_getValue(module.title, module.defaultStatus)) {
module.code();
}
});
modmenu.appendChild(moduleList);
const reloadMessage = document.createElement('div');
reloadMessage.classList.add('reload-message');
reloadMessage.textContent = 'Reload to apply changes!';
modmenu.appendChild(reloadMessage);
const versionText = document.createElement('p');
versionText.classList.add('version');
versionText.innerHTML = 'v4.0.0 • <a href="https://slash.gay/" target="_blank">https://slash.gay/</a> • <strong>Beta Release</strong>'; // This might be pulled from a URL someday
modmenu.appendChild(versionText);
document.body.appendChild(modmenu);
const minimizeButton = document.createElement('button');
minimizeButton.textContent = '-';
minimizeButton.classList.add('minimize-button');
modmenu.appendChild(minimizeButton);
minimizeButton.addEventListener('click', function() {
modmenuState.minimized = !modmenuState.minimized; // Toggle the minimized state
updateModmenuAppearance(); // Update the modmenu appearance based on the new state
updateModmenuState(); // Save the modmenu state
});
// Event listeners for dragging the modmenu
let isDragging = false;
let offsetX, offsetY;
title.addEventListener('mousedown', function(event) {
isDragging = true;
const rect = modmenu.getBoundingClientRect();
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
});
document.addEventListener('mousemove', function(event) {
if (isDragging) {
const x = event.clientX - offsetX;
const y = event.clientY - offsetY;
modmenu.style.left = `${x}px`;
modmenu.style.top = `${y}px`;
}
});
document.addEventListener('mouseup', function() {
isDragging = false;
});
function updateModmenuAppearance() {
if (modmenuState.minimized) {
moduleList.style.display = 'none'; // Hide module list
title.style.display = 'none'; // Hide title
versionText.style.display = 'none'; // Hide version text
minimizeButton.style.display = 'block'; // Show minimize button
modmenu.style.width = '50px'; // Set modmenu width to a smaller
modmenu.style.height = '30px'; // Set modmenu height to a smaller
} else {
moduleList.style.display = 'block'; // Show module list
title.style.display = 'block'; // Show title
versionText.style.display = 'block'; // Show version text
minimizeButton.style.display = 'block'; // Show minimize button
modmenu.style.width = '300px'; // Set modmenu width back to the original
modmenu.style.height = '550px'; // Set modmenu height back to the original
}
}
function updateModmenuState() {
GM_setValue('modmenuState', modmenuState);
}
}, 3000);
}
})();