ok
Browse files- youtube_sub.js +92 -18
youtube_sub.js
CHANGED
|
@@ -520,6 +520,7 @@ GM_addStyle(`
|
|
| 520 |
this.setupUI();
|
| 521 |
this.setupEventListeners();
|
| 522 |
window.subtitleManagerInstance = this;
|
|
|
|
| 523 |
}
|
| 524 |
cleanup() {
|
| 525 |
// Remove existing instance
|
|
@@ -943,8 +944,8 @@ GM_addStyle(`
|
|
| 943 |
popup = document.createElement('div');
|
| 944 |
popup.className = 'word-definition-popup';
|
| 945 |
|
| 946 |
-
|
| 947 |
-
|
| 948 |
// Create markdown container
|
| 949 |
const markdownDiv = document.createElement('div');
|
| 950 |
markdownDiv.className = 'markdown';
|
|
@@ -971,10 +972,24 @@ GM_addStyle(`
|
|
| 971 |
speakerBtn.onclick = (e) => {
|
| 972 |
e.stopPropagation();
|
| 973 |
try {
|
| 974 |
-
const utterance = new SpeechSynthesisUtterance(wordText);
|
| 975 |
utterance.lang = 'en-US';
|
| 976 |
utterance.rate = 0.9;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 977 |
speechSynthesis.speak(utterance);
|
|
|
|
| 978 |
} catch (error) {
|
| 979 |
console.error('TTS failed:', error);
|
| 980 |
}
|
|
@@ -993,24 +1008,52 @@ GM_addStyle(`
|
|
| 993 |
|
| 994 |
try {
|
| 995 |
// Use GM_xmlhttpRequest with updated config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 996 |
const response = await new Promise((resolve, reject) => {
|
| 997 |
GM_xmlhttpRequest({
|
| 998 |
-
method: '
|
| 999 |
-
url:
|
| 1000 |
headers: {
|
| 1001 |
-
'Content-Type': 'application/json',
|
| 1002 |
'Accept': 'application/json',
|
| 1003 |
'Origin': 'https://www.youtube.com'
|
| 1004 |
},
|
| 1005 |
-
data: JSON.stringify({
|
| 1006 |
-
prompt: `中文翻译 "${word.text.trim()}"并详细介绍,100字以内,包括对应的英文近义词,英文反义词`,
|
| 1007 |
-
model: 'GEMINI'
|
| 1008 |
-
}),
|
| 1009 |
onload: function (response) {
|
| 1010 |
-
|
| 1011 |
-
if (response.status === 405) {
|
| 1012 |
-
reject(new Error('API endpoint does not accept POST method. Try GET instead.'));
|
| 1013 |
-
} else if (response.status >= 200 && response.status < 300) {
|
| 1014 |
try {
|
| 1015 |
resolve(JSON.parse(response.responseText));
|
| 1016 |
} catch (e) {
|
|
@@ -1033,7 +1076,13 @@ GM_addStyle(`
|
|
| 1033 |
|
| 1034 |
const content = response?.data?.response;
|
| 1035 |
if (!content) {
|
| 1036 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1037 |
}
|
| 1038 |
|
| 1039 |
// Create text node instead of using innerHTML
|
|
@@ -1073,13 +1122,38 @@ GM_addStyle(`
|
|
| 1073 |
|
| 1074 |
} catch (error) {
|
| 1075 |
console.error('API call failed:', error);
|
| 1076 |
-
|
|
|
|
| 1077 |
<div style="color: red">
|
| 1078 |
Error: ${error.message}<br>
|
| 1079 |
Please check console for details.
|
| 1080 |
</div>
|
| 1081 |
-
|
| 1082 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
}
|
| 1084 |
};
|
| 1085 |
textContainer.appendChild(wordBtn);
|
|
|
|
| 520 |
this.setupUI();
|
| 521 |
this.setupEventListeners();
|
| 522 |
window.subtitleManagerInstance = this;
|
| 523 |
+
this.speakerClickCount = 0; // Add counter
|
| 524 |
}
|
| 525 |
cleanup() {
|
| 526 |
// Remove existing instance
|
|
|
|
| 944 |
popup = document.createElement('div');
|
| 945 |
popup.className = 'word-definition-popup';
|
| 946 |
|
| 947 |
+
|
| 948 |
+
|
| 949 |
// Create markdown container
|
| 950 |
const markdownDiv = document.createElement('div');
|
| 951 |
markdownDiv.className = 'markdown';
|
|
|
|
| 972 |
speakerBtn.onclick = (e) => {
|
| 973 |
e.stopPropagation();
|
| 974 |
try {
|
| 975 |
+
const utterance = new SpeechSynthesisUtterance(wordText);
|
| 976 |
utterance.lang = 'en-US';
|
| 977 |
utterance.rate = 0.9;
|
| 978 |
+
|
| 979 |
+
// Get voices and select based on click count
|
| 980 |
+
const voices = speechSynthesis.getVoices();
|
| 981 |
+
const isFemaleTurn = this.speakerClickCount % 2 === 1;
|
| 982 |
+
const voice = voices.find(v =>
|
| 983 |
+
v.lang.includes('en') &&
|
| 984 |
+
v.name.toLowerCase().includes('female') === isFemaleTurn
|
| 985 |
+
);
|
| 986 |
+
|
| 987 |
+
if (voice) {
|
| 988 |
+
utterance.voice = voice;
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
speechSynthesis.speak(utterance);
|
| 992 |
+
this.speakerClickCount++; // Increment counter
|
| 993 |
} catch (error) {
|
| 994 |
console.error('TTS failed:', error);
|
| 995 |
}
|
|
|
|
| 1008 |
|
| 1009 |
try {
|
| 1010 |
// Use GM_xmlhttpRequest with updated config
|
| 1011 |
+
// const response = await new Promise((resolve, reject) => {
|
| 1012 |
+
// GM_xmlhttpRequest({
|
| 1013 |
+
// method: 'POST',
|
| 1014 |
+
// url: 'https://sonygod-flash.hf.space/ask',
|
| 1015 |
+
// headers: {
|
| 1016 |
+
// 'Content-Type': 'application/json',
|
| 1017 |
+
// 'Accept': 'application/json',
|
| 1018 |
+
// 'Origin': 'https://www.youtube.com'
|
| 1019 |
+
// },
|
| 1020 |
+
// data: JSON.stringify({
|
| 1021 |
+
// prompt: `中文翻译 "${word.text.trim()}"并详细介绍,100字以内,包括对应的英文近义词,英文反义词`,
|
| 1022 |
+
// model: 'GEMINI'
|
| 1023 |
+
// }),
|
| 1024 |
+
// onload: function (response) {
|
| 1025 |
+
// // Handle different status codes
|
| 1026 |
+
// if (response.status === 405) {
|
| 1027 |
+
// reject(new Error('API endpoint does not accept POST method. Try GET instead.'));
|
| 1028 |
+
// } else if (response.status >= 200 && response.status < 300) {
|
| 1029 |
+
// try {
|
| 1030 |
+
// resolve(JSON.parse(response.responseText));
|
| 1031 |
+
// } catch (e) {
|
| 1032 |
+
// reject(new Error('Invalid JSON response'));
|
| 1033 |
+
// }
|
| 1034 |
+
// } else {
|
| 1035 |
+
// reject(new Error(`HTTP error! status: ${response.status}`));
|
| 1036 |
+
// }
|
| 1037 |
+
// },
|
| 1038 |
+
// onerror: function (error) {
|
| 1039 |
+
// reject(new Error('Network request failed: ' + error.error));
|
| 1040 |
+
// },
|
| 1041 |
+
// ontimeout: function () {
|
| 1042 |
+
// reject(new Error('Request timed out'));
|
| 1043 |
+
// }
|
| 1044 |
+
// });
|
| 1045 |
+
// });
|
| 1046 |
+
|
| 1047 |
const response = await new Promise((resolve, reject) => {
|
| 1048 |
GM_xmlhttpRequest({
|
| 1049 |
+
method: 'GET',
|
| 1050 |
+
url: `https://sonygod-flash.hf.space/translate/${encodeURIComponent(word.text.trim())}`,
|
| 1051 |
headers: {
|
|
|
|
| 1052 |
'Accept': 'application/json',
|
| 1053 |
'Origin': 'https://www.youtube.com'
|
| 1054 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1055 |
onload: function (response) {
|
| 1056 |
+
if (response.status >= 200 && response.status < 300) {
|
|
|
|
|
|
|
|
|
|
| 1057 |
try {
|
| 1058 |
resolve(JSON.parse(response.responseText));
|
| 1059 |
} catch (e) {
|
|
|
|
| 1076 |
|
| 1077 |
const content = response?.data?.response;
|
| 1078 |
if (!content) {
|
| 1079 |
+
|
| 1080 |
+
// Check for error response
|
| 1081 |
+
if (response?.status && response?.error) {
|
| 1082 |
+
throw new Error(`API Error: ${response.status} - ${response.error}`);
|
| 1083 |
+
} else {
|
| 1084 |
+
throw new Error('No content in API response');
|
| 1085 |
+
}
|
| 1086 |
}
|
| 1087 |
|
| 1088 |
// Create text node instead of using innerHTML
|
|
|
|
| 1122 |
|
| 1123 |
} catch (error) {
|
| 1124 |
console.error('API call failed:', error);
|
| 1125 |
+
// Create error message HTML
|
| 1126 |
+
const errorHtml = `
|
| 1127 |
<div style="color: red">
|
| 1128 |
Error: ${error.message}<br>
|
| 1129 |
Please check console for details.
|
| 1130 |
</div>
|
| 1131 |
+
`;
|
| 1132 |
+
|
| 1133 |
+
// Sanitize error message
|
| 1134 |
+
const sanitizedError = DOMPurify.sanitize(errorHtml, {
|
| 1135 |
+
RETURN_TRUSTED_TYPE: true
|
| 1136 |
+
});
|
| 1137 |
+
|
| 1138 |
+
// Create temporary container
|
| 1139 |
+
const errorContainer = document.createElement('div');
|
| 1140 |
+
errorContainer.className = 'markdown';
|
| 1141 |
+
|
| 1142 |
+
try {
|
| 1143 |
+
errorContainer.innerHTML = sanitizedError;
|
| 1144 |
+
|
| 1145 |
+
// Clear existing content
|
| 1146 |
+
const markdownElement = popup.querySelector('.markdown');
|
| 1147 |
+
while (markdownElement.firstChild) {
|
| 1148 |
+
markdownElement.removeChild(markdownElement.firstChild);
|
| 1149 |
+
}
|
| 1150 |
+
|
| 1151 |
+
// Append sanitized error message
|
| 1152 |
+
markdownElement.appendChild(errorContainer);
|
| 1153 |
+
popup.classList.add('show');
|
| 1154 |
+
} catch (e) {
|
| 1155 |
+
console.error('Error displaying message:', e);
|
| 1156 |
+
}
|
| 1157 |
}
|
| 1158 |
};
|
| 1159 |
textContainer.appendChild(wordBtn);
|