正常版本,没有MARKDOWN
Browse files- youtube_sub.js +146 -13
youtube_sub.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
// ==UserScript==
|
| 2 |
// @name YouTube Subtitle Manager
|
| 3 |
// @namespace http://tampermonkey.net/
|
|
@@ -6,6 +7,9 @@
|
|
| 6 |
// @author Your name
|
| 7 |
// @match https://www.youtube.com/*
|
| 8 |
// @grant GM_addStyle
|
|
|
|
|
|
|
|
|
|
| 9 |
// @run-at document-end
|
| 10 |
// ==/UserScript==
|
| 11 |
|
|
@@ -316,6 +320,38 @@ GM_addStyle(`
|
|
| 316 |
color: #000;
|
| 317 |
opacity: 1;
|
| 318 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
`;
|
| 320 |
|
| 321 |
// Apply main styles
|
|
@@ -703,7 +739,7 @@ GM_addStyle(`
|
|
| 703 |
if (sub.words && sub.words.length > 0) {
|
| 704 |
const line = document.createElement('div');
|
| 705 |
line.className = 'subtitle-line';
|
| 706 |
-
|
| 707 |
// Add prefix buttons
|
| 708 |
const prefixControls = document.createElement('div');
|
| 709 |
prefixControls.className = 'line-controls';
|
|
@@ -718,11 +754,11 @@ GM_addStyle(`
|
|
| 718 |
prefixControls.appendChild(btn);
|
| 719 |
});
|
| 720 |
line.appendChild(prefixControls);
|
| 721 |
-
|
| 722 |
// Add subtitle text container
|
| 723 |
const textContainer = document.createElement('div');
|
| 724 |
textContainer.className = 'subtitle-text';
|
| 725 |
-
|
| 726 |
sub.words.forEach((word, i) => {
|
| 727 |
if (word.text && word.text.trim()) {
|
| 728 |
const wordBtn = document.createElement('button');
|
|
@@ -730,23 +766,120 @@ GM_addStyle(`
|
|
| 730 |
wordBtn.textContent = word.text.trim();
|
| 731 |
wordBtn.dataset.start = word.startTime;
|
| 732 |
wordBtn.dataset.end = word.endTime;
|
| 733 |
-
wordBtn.onclick = (e) => {
|
| 734 |
-
e.stopPropagation();
|
| 735 |
-
// const video = document.querySelector('video');
|
| 736 |
-
// if (video) {
|
| 737 |
-
// video.currentTime = parseFloat(word.startTime);
|
| 738 |
-
// }
|
| 739 |
console.log(`You click "${word.text.trim()}" word`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
};
|
| 741 |
textContainer.appendChild(wordBtn);
|
| 742 |
-
|
| 743 |
if (i < sub.words.length - 1) {
|
| 744 |
textContainer.appendChild(document.createTextNode(' '));
|
| 745 |
}
|
| 746 |
}
|
| 747 |
});
|
| 748 |
line.appendChild(textContainer);
|
| 749 |
-
|
| 750 |
// Add suffix buttons
|
| 751 |
const suffixControls = document.createElement('div');
|
| 752 |
suffixControls.className = 'line-controls';
|
|
@@ -761,13 +894,13 @@ GM_addStyle(`
|
|
| 761 |
suffixControls.appendChild(btn);
|
| 762 |
});
|
| 763 |
line.appendChild(suffixControls);
|
| 764 |
-
|
| 765 |
// Add timestamp
|
| 766 |
const timestamp = document.createElement('span');
|
| 767 |
timestamp.className = 'timestamp';
|
| 768 |
timestamp.textContent = this.formatTime(sub.startTime);
|
| 769 |
line.appendChild(timestamp);
|
| 770 |
-
|
| 771 |
line.dataset.time = sub.startTime;
|
| 772 |
line.dataset.index = index;
|
| 773 |
container.appendChild(line);
|
|
|
|
| 1 |
+
// Update userscript headers
|
| 2 |
// ==UserScript==
|
| 3 |
// @name YouTube Subtitle Manager
|
| 4 |
// @namespace http://tampermonkey.net/
|
|
|
|
| 7 |
// @author Your name
|
| 8 |
// @match https://www.youtube.com/*
|
| 9 |
// @grant GM_addStyle
|
| 10 |
+
// @grant GM_xmlhttpRequest
|
| 11 |
+
// @connect sonygod-flash.hf.space
|
| 12 |
+
// @require https://cdn.jsdelivr.net/npm/marked/marked.min.js
|
| 13 |
// @run-at document-end
|
| 14 |
// ==/UserScript==
|
| 15 |
|
|
|
|
| 320 |
color: #000;
|
| 321 |
opacity: 1;
|
| 322 |
}
|
| 323 |
+
|
| 324 |
+
.word-definition-popup {
|
| 325 |
+
position: fixed;
|
| 326 |
+
right: -400px;
|
| 327 |
+
top: 50%;
|
| 328 |
+
transform: translateY(-50%);
|
| 329 |
+
width: 380px;
|
| 330 |
+
background: rgba(33, 33, 33, 0.95);
|
| 331 |
+
border-radius: 8px;
|
| 332 |
+
padding: 20px;
|
| 333 |
+
color: white;
|
| 334 |
+
transition: right 0.3s ease;
|
| 335 |
+
z-index: 10000;
|
| 336 |
+
max-height: 80vh;
|
| 337 |
+
overflow-y: auto;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
.word-definition-popup.show {
|
| 341 |
+
right: 460px;
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.word-definition-popup .markdown {
|
| 345 |
+
font-size: 14px;
|
| 346 |
+
line-height: 1.6;
|
| 347 |
+
}
|
| 348 |
+
`;
|
| 349 |
+
|
| 350 |
+
// Add popup HTML
|
| 351 |
+
const popupHTML = `
|
| 352 |
+
<div class="word-definition-popup">
|
| 353 |
+
<div class="markdown"></div>
|
| 354 |
+
</div>
|
| 355 |
`;
|
| 356 |
|
| 357 |
// Apply main styles
|
|
|
|
| 739 |
if (sub.words && sub.words.length > 0) {
|
| 740 |
const line = document.createElement('div');
|
| 741 |
line.className = 'subtitle-line';
|
| 742 |
+
|
| 743 |
// Add prefix buttons
|
| 744 |
const prefixControls = document.createElement('div');
|
| 745 |
prefixControls.className = 'line-controls';
|
|
|
|
| 754 |
prefixControls.appendChild(btn);
|
| 755 |
});
|
| 756 |
line.appendChild(prefixControls);
|
| 757 |
+
|
| 758 |
// Add subtitle text container
|
| 759 |
const textContainer = document.createElement('div');
|
| 760 |
textContainer.className = 'subtitle-text';
|
| 761 |
+
|
| 762 |
sub.words.forEach((word, i) => {
|
| 763 |
if (word.text && word.text.trim()) {
|
| 764 |
const wordBtn = document.createElement('button');
|
|
|
|
| 766 |
wordBtn.textContent = word.text.trim();
|
| 767 |
wordBtn.dataset.start = word.startTime;
|
| 768 |
wordBtn.dataset.end = word.endTime;
|
| 769 |
+
wordBtn.onclick = async (e) => {
|
| 770 |
+
e.stopPropagation();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
console.log(`You click "${word.text.trim()}" word`);
|
| 772 |
+
|
| 773 |
+
let popup = document.querySelector('.word-definition-popup');
|
| 774 |
+
if (!popup) {
|
| 775 |
+
// Create popup container
|
| 776 |
+
popup = document.createElement('div');
|
| 777 |
+
popup.className = 'word-definition-popup';
|
| 778 |
+
|
| 779 |
+
// Create markdown container
|
| 780 |
+
const markdownDiv = document.createElement('div');
|
| 781 |
+
markdownDiv.className = 'markdown';
|
| 782 |
+
popup.appendChild(markdownDiv);
|
| 783 |
+
|
| 784 |
+
// Add popup to document
|
| 785 |
+
document.body.appendChild(popup);
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
// Ensure markdown container exists
|
| 789 |
+
let markdownContainer = popup.querySelector('.markdown');
|
| 790 |
+
if (!markdownContainer) {
|
| 791 |
+
markdownContainer = document.createElement('div');
|
| 792 |
+
markdownContainer.className = 'markdown';
|
| 793 |
+
popup.appendChild(markdownContainer);
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
try {
|
| 797 |
+
// Use GM_xmlhttpRequest with updated config
|
| 798 |
+
const response = await new Promise((resolve, reject) => {
|
| 799 |
+
GM_xmlhttpRequest({
|
| 800 |
+
method: 'POST',
|
| 801 |
+
url: 'https://sonygod-flash.hf.space/ask',
|
| 802 |
+
headers: {
|
| 803 |
+
'Content-Type': 'application/json',
|
| 804 |
+
'Accept': 'application/json',
|
| 805 |
+
'Origin': 'https://www.youtube.com'
|
| 806 |
+
},
|
| 807 |
+
data: JSON.stringify({
|
| 808 |
+
prompt: `中文翻译 "${word.text.trim()}"并详细介绍,100字以内,包括对应的英文近义词,英文反义词`,
|
| 809 |
+
model: 'GEMINI'
|
| 810 |
+
}),
|
| 811 |
+
onload: function (response) {
|
| 812 |
+
// Handle different status codes
|
| 813 |
+
if (response.status === 405) {
|
| 814 |
+
reject(new Error('API endpoint does not accept POST method. Try GET instead.'));
|
| 815 |
+
} else if (response.status >= 200 && response.status < 300) {
|
| 816 |
+
try {
|
| 817 |
+
resolve(JSON.parse(response.responseText));
|
| 818 |
+
} catch (e) {
|
| 819 |
+
reject(new Error('Invalid JSON response'));
|
| 820 |
+
}
|
| 821 |
+
} else {
|
| 822 |
+
reject(new Error(`HTTP error! status: ${response.status}`));
|
| 823 |
+
}
|
| 824 |
+
},
|
| 825 |
+
onerror: function (error) {
|
| 826 |
+
reject(new Error('Network request failed: ' + error.error));
|
| 827 |
+
},
|
| 828 |
+
ontimeout: function () {
|
| 829 |
+
reject(new Error('Request timed out'));
|
| 830 |
+
}
|
| 831 |
+
});
|
| 832 |
+
});
|
| 833 |
+
|
| 834 |
+
console.log('API Response:', response);
|
| 835 |
+
|
| 836 |
+
const content = response?.data?.response;
|
| 837 |
+
if (!content) {
|
| 838 |
+
throw new Error('No content in API response');
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
// Create text node instead of using innerHTML
|
| 842 |
+
const markdownContent = marked.parse(content);
|
| 843 |
+
if (markdownContent) {
|
| 844 |
+
// Clear existing content
|
| 845 |
+
while (markdownContainer.firstChild) {
|
| 846 |
+
markdownContainer.removeChild(markdownContainer.firstChild);
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
// Create temporary container
|
| 850 |
+
const temp = document.createElement('div');
|
| 851 |
+
temp.textContent = markdownContent;
|
| 852 |
+
|
| 853 |
+
// Safely append text content
|
| 854 |
+
const fragment = document.createDocumentFragment();
|
| 855 |
+
fragment.appendChild(temp);
|
| 856 |
+
markdownContainer.appendChild(fragment);
|
| 857 |
+
|
| 858 |
+
popup.classList.add('show');
|
| 859 |
+
} else {
|
| 860 |
+
throw new Error('Failed to parse markdown content');
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
} catch (error) {
|
| 864 |
+
console.error('API call failed:', error);
|
| 865 |
+
popup.querySelector('.markdown').innerHTML = `
|
| 866 |
+
<div style="color: red">
|
| 867 |
+
Error: ${error.message}<br>
|
| 868 |
+
Please check console for details.
|
| 869 |
+
</div>
|
| 870 |
+
`;
|
| 871 |
+
popup.classList.add('show');
|
| 872 |
+
}
|
| 873 |
};
|
| 874 |
textContainer.appendChild(wordBtn);
|
| 875 |
+
|
| 876 |
if (i < sub.words.length - 1) {
|
| 877 |
textContainer.appendChild(document.createTextNode(' '));
|
| 878 |
}
|
| 879 |
}
|
| 880 |
});
|
| 881 |
line.appendChild(textContainer);
|
| 882 |
+
|
| 883 |
// Add suffix buttons
|
| 884 |
const suffixControls = document.createElement('div');
|
| 885 |
suffixControls.className = 'line-controls';
|
|
|
|
| 894 |
suffixControls.appendChild(btn);
|
| 895 |
});
|
| 896 |
line.appendChild(suffixControls);
|
| 897 |
+
|
| 898 |
// Add timestamp
|
| 899 |
const timestamp = document.createElement('span');
|
| 900 |
timestamp.className = 'timestamp';
|
| 901 |
timestamp.textContent = this.formatTime(sub.startTime);
|
| 902 |
line.appendChild(timestamp);
|
| 903 |
+
|
| 904 |
line.dataset.time = sub.startTime;
|
| 905 |
line.dataset.index = index;
|
| 906 |
container.appendChild(line);
|