// ==UserScript==
// @name OpenAI TTS Text Reader (gpt-4o-mini-tts)
// @namespace http://tampermonkey.net/
// @version 2.6.6 // Version incremented due to model change
// @description Read selected text with OpenAI's TTS API (gpt-4o-mini-tts model) and adjustable volume and speed. Please enter the apikey before using.
// @description:ar قراءة النص المحدد باستخدام واجهة برمجة تطبيقات تحويل النص إلى كلام من OpenAI (نموذج gpt-4o-mini-tts) مع إمكانية ضبط مستوى الصوت والسرعة. يرجى إدخال مفتاح الواجهة البرمجية (apikey) قبل الاستخدام.
// @include *
// @author wkf16 (Modified by AI assistant for Bahattab)
// @license MIT
// @grant GM_xmlhttpRequest
// @connect api.openai.com
// @antifeature cross-domain This script makes cross-domain API calls to OpenAI's TTS service, which may have implications for data security and privacy.
// ==/UserScript==
var YOUR_API_KEY = "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"; // استخدم مفتاح الواجهة البرمجية الخاص بك
(function() {
'use strict';
var currentSource = null;
var isPlaying = false;
var audioContext = new AudioContext();
var gainNode = audioContext.createGain();
gainNode.connect(audioContext.destination);
var playbackRate = 1;
// إنشاء الزر
var readButton = document.createElement("button");
styleButton(readButton);
document.body.appendChild(readButton);
// إنشاء وإضافة نص الزر
var buttonText = document.createElement("span");
buttonText.textContent = ">";
styleButtonText(buttonText);
readButton.appendChild(buttonText);
// إنشاء لوحة التحكم
var controlPanel = document.createElement("div");
styleControlPanel(controlPanel);
document.body.appendChild(controlPanel);
// إنشاء وإضافة منزلقات مستوى الصوت والسرعة إلى لوحة التحكم
var volumeControl = createSlider("الصوت", 0, 1, 0.5, 0.01, function(value) {
gainNode.gain.value = value;
});
controlPanel.appendChild(volumeControl.wrapper);
volumeControl.slider.value = 0.5;
var speedControl = createSlider("السرعة", 0.5, 1.5, 1, 0.05, function(value) { playbackRate = value; });
controlPanel.appendChild(speedControl.wrapper);
speedControl.slider.value = 1;
// حدث النقر على الزر
readButton.addEventListener('click', function() {
var selectedText = window.getSelection().toString();
console.log("Setting gainNode.gain.value to: ", gainNode.gain.value);
if (isPlaying) {
if (currentSource) {
currentSource.stop(); // إيقاف الصوت قيد التشغيل حاليًا
}
HideSpinner(buttonText); // Reset button to play state
isPlaying = false; // Ensure state is updated
} else{
if (selectedText) {
textToSpeech(selectedText);
} else {
alert("رجاءً، قم بتحديد بعض النص أولاً.");
}
}
});
// إنشاء وتنسيق لوحة التحكم والمنزلقات
function createSlider(labelText, min, max, value, step, onChange) {
var wrapper = document.createElement("div");
var label = document.createElement("label");
label.textContent = labelText;
label.style.color = "white";
label.style.textAlign = "right";
label.style.flex = "1";
var slider = document.createElement("input");
slider.type = "range";
slider.min = min;
slider.max = max;
slider.step = step;
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.padding = '8px';
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
input[type='range'] {
-webkit-appearance: none; appearance: none;
width: 90%; height: 8px; border-radius: 8px;
background: rgba(255, 255, 255, 0.2); outline: none;
margin-right: 10px; margin-left: 0;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 16px; height: 16px; border-radius: 50%;
background: #4CAF50; cursor: pointer; box-shadow: 0 0 2px #888;
}
input[type='range']:focus::-webkit-slider-thumb { background: #ccc; }
`;
document.head.appendChild(styleSheet);
slider.oninput = function() { onChange(this.value); };
wrapper.appendChild(label);
wrapper.appendChild(slider);
return { wrapper: wrapper, slider: slider };
}
// تعيين نمط لوحة التحكم
function styleControlPanel(panel) {
panel.style.position = 'fixed'; panel.style.bottom = '20px';
panel.style.right = '80px'; panel.style.width = '200px';
panel.style.background = 'rgba(0, 0, 0, 0.7)'; panel.style.borderRadius = '10px';
panel.style.padding = '10px'; panel.style.boxSizing = 'border-box';
panel.style.visibility = 'hidden'; panel.style.opacity = 0;
panel.style.transition = 'opacity 0.5s, visibility 0.5s';
panel.style.display = 'flex'; panel.style.flexDirection = 'column';
panel.style.zIndex = '10000';
}
// تعيين نمط الزر
function styleButton(button) {
button.style.position = 'fixed'; button.style.bottom = '80px';
button.style.right = '20px'; button.style.zIndex = '1000';
button.style.width = '40px'; button.style.height = '40px';
button.style.borderRadius = '50%'; button.style.backgroundColor = '#4CAF50';
button.style.border = 'none'; button.style.outline = 'none';
button.style.cursor = 'pointer';
button.style.transition = 'background-color 0.3s, opacity 0.4s ease';
}
function styleButtonText(text) {
text.style.transition = 'opacity 0.4s ease'; text.style.opacity = '1';
text.style.fontSize = "20px"; text.style.textAlign = "center";
text.style.lineHeight = "40px";
}
function createVoiceSelect() {
var selectWrapper = document.createElement("div");
var select = document.createElement("select");
var voices = ["nova", "onyx", "alloy", "echo", "fable", "shimmer"];
var voiceLabel = document.createElement("label");
voiceLabel.textContent = "الصوت:";
voiceLabel.style.color = "white"; voiceLabel.style.marginRight = "5px";
selectWrapper.appendChild(voiceLabel);
for (var i = 0; i < voices.length; i++) {
var option = document.createElement("option");
option.value = voices[i];
option.textContent = voices[i].charAt(0).toUpperCase() + voices[i].slice(1);
select.appendChild(option);
}
selectWrapper.appendChild(select);
styleSelect(selectWrapper, select);
return { wrapper: selectWrapper, select: select };
}
// تنسيق القائمة المنسدلة
function styleSelect(wrapper, select) {
wrapper.style.padding = '5px'; wrapper.style.marginBottom = '10px';
wrapper.style.display = 'flex'; wrapper.style.alignItems = 'center';
select.style.flexGrow = '1'; select.style.padding = '8px 10px';
select.style.borderRadius = '8px'; select.style.background = 'rgba(0, 0, 0, 0.7)';
select.style.border = '2px solid #4CAF50'; select.style.color = 'white';
select.style.fontFamily = 'Arial, sans-serif'; select.style.fontSize = '14px';
select.style.direction = 'ltr';
select.onmouseover = function() { this.style.backgroundColor = 'rgba(50, 50, 50, 0.5)'; };
select.onmouseout = function() { this.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; };
select.onfocus = function() { this.style.outline = 'none'; this.style.boxShadow = '0 0 5px rgba(81, 203, 238, 1)'; };
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
select option { background: rgba(0, 0, 0, 0.9); color: white; }
select option:checked { background: #4CAF50; color: white; }
select option:hover { background: rgba(50, 50, 50, 0.8); color: white; }
`;
document.head.appendChild(styleSheet);
}
// إضافة القائمة المنسدلة لاختيار الصوت إلى لوحة التحكم
var voiceSelect = createVoiceSelect();
controlPanel.appendChild(voiceSelect.wrapper);
function textToSpeech(s) {
// ----- السطر الذي تم تعديله -----
var sModelId = "gpt-4o-mini-tts"; // Changed from "tts-1" to "gpt-4o-mini-tts" for higher quality
// ----- نهاية السطر المعدل -----
var sVoiceId = voiceSelect.select.value;
var API_KEY = YOUR_API_KEY; // Make sure this is set correctly at the top
// Ensure API Key is present
if (!API_KEY || API_KEY === "YOUR_API_KEY_HERE" || API_KEY.length < 10) {
alert("يرجى إدخال مفتاح واجهة برمجة تطبيقات OpenAI صالح في بداية البرنامج النصي.");
HideSpinner(buttonText);
return;
}
ShowSpinner(buttonText); // إظهار مؤشر التحميل
GM_xmlhttpRequest({
method: "POST",
url: "https://api.openai.com/v1/audio/speech",
headers: {
"Accept": "audio/mpeg",
"Content-Type": "application/json",
"Authorization": "Bearer " + API_KEY
},
data: JSON.stringify({
model: sModelId, // Now uses "gpt-4o-mini-tts"
input: s,
voice: sVoiceId,
speed: playbackRate
}),
responseType: "arraybuffer",
onload: function(response) {
if (response.status === 200) {
// Hide spinner isn't needed here, StopSpinner handles the transition
audioContext.decodeAudioData(response.response, function(buffer) {
var source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(gainNode);
source.start(0);
currentSource = source; // حفظ مصدر الصوت الجديد
isPlaying = true;
StopSpinner(buttonText); // تحديث نص الزر إلى حالة الإيقاف المؤقت
// الاستماع لحدث انتهاء الصوت
source.onended = function() {
isPlaying = false;
currentSource = null; // Clear the source
HideSpinner(buttonText); // تحديث نص الزر إلى حالة التشغيل
}
}, function(e) {
console.error("Error decoding audio data: ", e);
HideSpinner(buttonText); // Ensure spinner hides on decode error
alert("حدث خطأ أثناء معالجة الصوت.");
});
} else {
HideSpinner(buttonText);
console.error("Error loading TTS: ", response.status, response.statusText, response.response);
try {
var errorResponse = JSON.parse(new TextDecoder("utf-8").decode(response.response));
console.error("OpenAI Error:", errorResponse);
// Check for specific common errors
if (response.status === 401) {
alert("خطأ في المصادقة (401). يرجى التحقق من مفتاح الواجهة البرمجية (API Key).");
} else if (errorResponse.error?.message) {
alert("خطأ من OpenAI: " + errorResponse.error.message);
} else {
alert("حدث خطأ أثناء الاتصال بخدمة تحويل النص إلى كلام. الرمز: " + response.status);
}
} catch (e) {
alert("حدث خطأ أثناء الاتصال بخدمة تحويل النص إلى كلام. الرمز: " + response.status);
}
}
},
onerror: function(error) {
HideSpinner(buttonText);
console.error("GM_xmlhttpRequest error: ", error);
alert("حدث خطأ في الشبكة أو في طلب الواجهة البرمجية.");
}
});
}
// تأخير عرض وإخفاء لوحة التحكم
var panelDisplayDelay = 700;
var panelHideDelay = 500;
var showPanelTimeout, hidePanelTimeout;
readButton.addEventListener('mouseenter', function() {
readButton.style.backgroundColor = '#45a049';
clearTimeout(hidePanelTimeout);
showPanelTimeout = setTimeout(function() {
controlPanel.style.visibility = 'visible';
controlPanel.style.opacity = 1;
}, panelDisplayDelay);
});
readButton.addEventListener('mouseleave', function() {
readButton.style.backgroundColor = '#4CAF50';
clearTimeout(showPanelTimeout);
hidePanelTimeout = setTimeout(function() {
if (!controlPanel.matches(':hover')) {
controlPanel.style.visibility = 'hidden';
controlPanel.style.opacity = 0;
}
}, panelHideDelay);
});
controlPanel.addEventListener('mouseenter', function() {
clearTimeout(hidePanelTimeout);
controlPanel.style.visibility = 'visible';
controlPanel.style.opacity = 1;
});
controlPanel.addEventListener('mouseleave', function() {
hidePanelTimeout = setTimeout(function() {
controlPanel.style.visibility = 'hidden';
controlPanel.style.opacity = 0;
}, panelHideDelay);
});
speedControl.slider.addEventListener('input', function() {
playbackRate = this.value;
});
function ShowSpinner(text) {
text.style.opacity = '0';
setTimeout(function() {
text.textContent = "...";
text.style.opacity = '1';
}, 400);
readButton.disabled = true;
}
function HideSpinner(text) { // Resets button to 'Play' state
text.style.opacity = '0';
setTimeout(function() {
text.textContent = ">";
text.style.opacity = '1';
}, 400);
readButton.disabled = false;
}
function StopSpinner(text) { // Sets button to 'Stop' state
text.style.opacity = '0';
setTimeout(function() {
text.textContent = "❚❚";
text.style.opacity = '1';
}, 400);
readButton.disabled = false; // Keep button enabled to allow stopping
}
})();