├── .gitignore
├── icons
└── icon-128.png
├── images
├── chat-screenshot.png
├── chrome-window.png
├── developer-mode.png
├── get-overlay-url.png
└── load-unpacked.png
├── manifest.json
├── settings
├── options.js
└── options.html
├── README.md
├── youtube.css
├── youtube.js
└── jquery.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/icons/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/icons/icon-128.png
--------------------------------------------------------------------------------
/images/chat-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/images/chat-screenshot.png
--------------------------------------------------------------------------------
/images/chrome-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/images/chrome-window.png
--------------------------------------------------------------------------------
/images/developer-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/images/developer-mode.png
--------------------------------------------------------------------------------
/images/get-overlay-url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/images/get-overlay-url.png
--------------------------------------------------------------------------------
/images/load-unpacked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronpk/live-chat-overlay/main/images/load-unpacked.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Live Chat Overlay",
3 | "description": "Restyle the YouTube popout chat so you can overlay it for livestreams",
4 | "manifest_version": 3,
5 | "version": "0.3.9",
6 | "homepage_url": "https://github.com/aaronpk/live-chat-overlay",
7 | "icons": {
8 | "128": "icons/icon-128.png"
9 | },
10 | "permissions": [
11 | "storage"
12 | ],
13 | "host_permissions": [
14 | "https://youtube.com/*", "https://www.youtube.com/*", "https://studio.youtube.com/*"
15 | ],
16 | "content_scripts": [{
17 | "css": ["youtube.css"],
18 | "js": ["jquery.js", "youtube.js"],
19 | "all_frames": false,
20 | "matches": ["https://youtube.com/live_chat*", "https://www.youtube.com/live_chat*", "https://studio.youtube.com/live_chat*"]
21 | }],
22 | "options_ui": {
23 | "page": "settings/options.html"
24 | },
25 | "browser_specific_settings": {
26 | "gecko": {
27 | "id": "live-chat-overlay@aaronpk.tv",
28 | "strict_min_version": "80.0"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/settings/options.js:
--------------------------------------------------------------------------------
1 | function generateSessionID(){
2 | var text = "";
3 | var chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
4 | for (var i = 0; i < 10; i++){
5 | text += chars.charAt(Math.floor(Math.random() * chars.length));
6 | }
7 | return text;
8 | };
9 |
10 | function toggleSessionID() {
11 | if (this.checked) {
12 | document.querySelector("#session-id").value = generateSessionID();
13 | } else {
14 | document.querySelector("#session-id").value = "";
15 | }
16 | }
17 |
18 |
19 | function saveOptions(e) {
20 | e.preventDefault();
21 | chrome.storage.sync.set({
22 | color: document.querySelector("#color").value,
23 | scale: document.querySelector("#scale").value,
24 | sizeOffset: document.querySelector("#size-offset").value,
25 | commentBottom: document.querySelector("#comment-bottom").value,
26 | commentHeight: document.querySelector("#comment-height").value,
27 | commentBackgroundColor: document.querySelector("#comment-bg-color").value,
28 | commentColor: document.querySelector("#comment-color").value,
29 | authorBackgroundColor: document.querySelector("#author-bg-color").value,
30 | authorAvatarBorderColor: document.querySelector("#author-avatar-border-color").value,
31 | authorAvatarOverlayOpacity: document.querySelector("#author-avatar-overlay-opacity").value,
32 | authorColor: document.querySelector("#author-color").value,
33 | fontFamily: document.querySelector("#font-family").value,
34 | highlightWords: document.querySelector("#highlight-words").value.toLowerCase().replace(/[^a-z0-9, ]/gi, '').split(",").map(e => e.trim()),
35 | showOnlyFirstName: document.querySelector("#firstname").checked,
36 | autoHideSeconds: document.querySelector("#auto-hide-seconds").value,
37 | popoutURL: document.querySelector("#popout-url").value,
38 | serverURL: document.querySelector("#server-url").value,
39 | persistentSessionID: document.querySelector("#persistent-session-id").checked,
40 | sessionID: document.querySelector("#session-id").value
41 | });
42 | }
43 |
44 | function restoreOptions() {
45 |
46 | var properties = ["color","scale","commentBottom","commentHeight","sizeOffset","authorBackgroundColor","authorAvatarBorderColor","authorColor","commentBackgroundColor","commentColor","fontFamily","showOnlyFirstName","highlightWords","popoutURL","serverURL","autoHideSeconds","authorAvatarOverlayOpacity","persistentSessionID","sessionID"];
47 | chrome.storage.sync.get(properties, function(result){
48 | document.querySelector("#color").value = result.color || "#000";
49 | document.querySelector("#scale").value = result.scale || "1.0";
50 | document.querySelector("#size-offset").value = result.sizeOffset || "0";
51 | document.querySelector("#comment-bottom").value = result.commentBottom || "10px";
52 | document.querySelector("#comment-height").value = result.commentHeight || "30vh";
53 | document.querySelector("#author-bg-color").value = result.authorBackgroundColor || "#ffa500";
54 | document.querySelector("#author-avatar-border-color").value = result.authorAvatarBorderColor || "#ffa500";
55 | document.querySelector("#author-avatar-overlay-opacity").value = result.authorAvatarOverlayOpacity || "0.1";
56 | document.querySelector("#author-color").value = result.authorColor || "#222";
57 | document.querySelector("#comment-bg-color").value = result.commentBackgroundColor || "#222";
58 | document.querySelector("#comment-color").value = result.commentColor || "#fff";
59 | document.querySelector("#font-family").value = result.fontFamily || "Avenir Next, Helvetica, Geneva, Verdana, Arial, sans-serif";
60 | document.querySelector("#firstname").checked = result.showOnlyFirstName || false;
61 | document.querySelector("#auto-hide-seconds").value = result.autoHideSeconds || 0;
62 | document.querySelector("#highlight-words").value = result.highlightWords.join(", ") || "q, question";
63 | document.querySelector("#popout-url").value = result.popoutURL || "https://chat.aaronpk.tv/overlay/";
64 | document.querySelector("#server-url").value = result.serverURL || "https://chat.aaronpk.tv/overlay/pub";
65 | document.querySelector("#persistent-session-id").checked = result.persistentSessionID || false;
66 | document.querySelector("#session-id").value = result.sessionID || "";
67 | });
68 |
69 | document.querySelector("#persistent-session-id").addEventListener("change", toggleSessionID);
70 | }
71 |
72 | document.addEventListener("DOMContentLoaded", restoreOptions);
73 | document.querySelector("form").addEventListener("submit", saveOptions);
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Live Chat Overlay
2 | =================
3 |
4 | This browser extension turns the YouTube popout chat window into something that can be used to show chat comments keyed over a video. You can also bring it in to software like OBS or Lightstream Studio as a browser source!
5 |
6 | 
7 |
8 | [Setup Tutorial](https://youtu.be/HwctGtdsHZI)
9 |
10 | ## Installation
11 |
12 | ### Google Chrome Store
13 |
14 | The easiest way to install the extension from the Google Chrome store. Click the link below and you should see a prompt to install it in Chrome.
15 |
16 | * https://chrome.google.com/webstore/detail/live-chat-overlay/aplaefbnohemkmngdogmbkpompjlijia
17 |
18 |
19 | ### Manual Installation
20 |
21 | First, [download this project](https://github.com/aaronpk/live-chat-overlay/archive/refs/heads/main.zip) and unzip it somewhere on your computer. In Chrome, launch the Extensions page.
22 |
23 | Go to **Window** and click **Extensions** (or enter this in your address bar `chrome://extensions/`)
24 |
25 | 
26 |
27 | Turn on **Developer Mode**
28 |
29 | 
30 |
31 | That will show a new set of options. Click "Load Unpacked"
32 |
33 | 
34 |
35 | Choose the folder you downloaded the extension into, and you should see it now in your list of extensions!
36 |
37 | ## Usage
38 |
39 | Open up the YouTube live chat for a video, and click YouTube's "popout chat" button to open it in a new window. Or replace the `VIDEOID` in the URL below with your video's ID.
40 |
41 | `https://www.youtube.com/live_chat?is_popout=1&v=VIDEOID`
42 |
43 | ### Keying from a Computer
44 |
45 | You'll next need to bring that into your video stream and key it out, which will depend on what software or hardware you are using. In the ATEM Mini, you can use these settings in the upstream keyer:
46 |
47 | * Luma key
48 | * not premultiplied
49 | * clip: 7%
50 | * gain: 100%
51 |
52 | Or you can use the downstream keyer:
53 |
54 | * not premultiplied
55 | * mask:
56 | * top: X
57 | * bottom: -9
58 | * left: -16
59 | * right: 16
60 |
61 | ### Adding as a Browser Source
62 |
63 | In software such as [OBS](https://obsproject.com) or [Lightstream Studio](http://strea.mr/aaronparecki) you can pop out a remote window as a browser source, and remote control it from your main YouTube chat window.
64 |
65 | Before you start, you'll want to make sure you change the background color in the extension settings to the word "transparent" so that the browser window will have a transparent background.
66 |
67 | Once you've loaded the YouTube popout chat window described above, you will see a button in the YouTube chat window called "Get Overlay URL". Clicking that will reveal a URL you can copy and load into OBS or your favorite streaming platform that supports browser overlay sources.
68 |
69 | 
70 |
71 | You can open the URL on your computer too if you want to full screen it on a second monitor.
72 |
73 | ### Persistent Links
74 |
75 | There is now an option for "Persistent Session ID" to enable a persistent link for your stream so that you don't have to re-add the browser source every time. Here is how to use it.
76 |
77 | In the options form, check "Persistent Session ID" which will show a text box with a randomly generated Session ID. This Session ID will be used for all you future chats. You can also change the Session ID in that field if you want to reuse one that you already saved in a browser source.
78 |
79 | Note: You should always use a randomly generated ID. If you use a short word like "test", then the chats will be combined with anyone else who has used the same short word.
80 |
81 |
82 | ## See this in action!
83 |
84 | You can see this in action on many of [Aaron Parecki's livestreams](https://www.youtube.com/watch?v=CHQITWm5wDQ&list=PLRyLn6THA5wPracMVE74IHovBT3ebcsJV)!
85 |
86 |
87 | ## Running your own remote server
88 |
89 | The extension defaults to pushing the chat messages through a server managed by [Aaron Parecki](https://aaronpk.tv), and the remote window is loaded from that website. The chat messages aren't stored on the server, and there is very little resource usage for this, but if you are more comfortable running this on your own server, head over to the [overlay remote GitHub project](https://github.com/aaronpk/live-chat-overlay-remote) for instructions.
90 |
91 |
92 | ## TODO
93 |
94 | See https://github.com/aaronpk/youtube-chat-overlay/issues
95 |
96 |
97 | ## Credits
98 |
99 | The original CSS and JavaScript originally came from a video by [ROJ BTS](https://www.youtube.com/watch?v=NHy9D4ClTvc), so huge thanks to him for the initial work!
100 |
101 |
--------------------------------------------------------------------------------
/settings/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
43 |
44 |
45 |
46 | Customize the colors of the live chat overlay. Enter the hex codes of the colors you'd like to use. Remember that full black #000 will be keyed out. You can delete a value to reset it to the default.
47 |
48 | Black: #000
49 | Dark Grey: #222
50 | White: #fff
51 |
52 |
53 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/youtube.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* basic colors */
3 | --keyer-bg-color: #000; /* change for a chroma key if you want, beware profile avatars may key out */
4 | --comment-color: #fff;
5 | --comment-bg-color: #222; /* needs to be slightly above black to not get keyed out */
6 | --comment-border-radius: 0px;
7 | --comment-font-size: 40px;
8 | --author-color: #222;
9 | --author-bg-color: #ffa500;
10 | --author-avatar-border-color: #ffa500;
11 | --author-border-radius: 0px;
12 | --author-avatar-border-size: 3px;
13 | --author-avatar-size: 128px;
14 | --author-font-size: 30px;
15 | --author-avatar-overlay-opacity: 0.1;
16 | /* donation/superchat specific */
17 | --donation-color: #5a4211;
18 | --donation-bg-color: #fff;
19 | --donation-gradient-stop0: #BF953F;
20 | --donation-gradient-stop1: #ede599;
21 | --donation-gradient-stop2: #B38728;
22 | --donation-label-color: #fff;
23 | --donation-label-text: 'SUPERCHAT';
24 | --donation-shadow-color: #fff;
25 |
26 | --active-comment-bg-color: #90dd91;
27 | --shown-comment-bg-color: #555555;
28 | --highlighted-comment-bg-color: #f0f07d;
29 |
30 | /*
31 | LAYOUT
32 | */
33 | --comment-width: auto; /* go 100% for a full screen lower third style*/
34 | --comment-padding: 20px 40px 20px 70px;
35 | --comment-area-height: 30vh;
36 | --comment-scale: 1;
37 | --comment-area-size-offset: 0;
38 | --comment-area-bottom: 10px;
39 | /*
40 | STYLE
41 | */
42 | --font-family: Avenir Next, Helvetica, Geneva, Verdana, Arial, sans-serif;
43 | --highlight-chat-font-weight: 600;
44 | --author-font-weight: 700;
45 | --comment-font-weight: 600;
46 | }
47 |
48 | body {
49 | background-color: var(--keyer-bg-color);
50 | }
51 | body.inline-chat yt-live-chat-app {
52 | margin-bottom: var(--comment-area-height);
53 | height: calc(100vh - var(--comment-area-height)) !important;
54 | }
55 | .btn-clear {
56 | position: absolute;
57 | z-index: 99999;
58 | right: 20px;
59 | font-size: 30px;
60 | border: 1px #bbb solid;
61 | border-radius: 4px;
62 | color: #fff;
63 | background: #444;
64 | bottom: 60px;
65 | }
66 | body.inline-chat .btn-clear {
67 | bottom: calc(var(--comment-area-height) + 60px);
68 | }
69 |
70 | .highlighted-comment {
71 | background-color: var(--highlighted-comment-bg-color) !important;
72 | }
73 | html[dark] .highlighted-comment #message.yt-live-chat-text-message-renderer {
74 | color: black;
75 | }
76 | html[dark] .highlighted-comment:hover #message.yt-live-chat-text-message-renderer {
77 | color: white;
78 | }
79 | html[dark] .highlighted-comment #author-name.yt-live-chat-author-chip {
80 | color: #444;
81 | }
82 | html[dark] .highlighted-comment:hover #author-name.yt-live-chat-author-chip {
83 | color: #DDD;
84 | }
85 | yt-live-chat-text-message-renderer:hover {
86 | background-color: #eee !important;
87 | }
88 | html[dark] yt-live-chat-text-message-renderer:hover {
89 | background-color: #444 !important;
90 | }
91 | yt-live-chat-text-message-renderer.shown-comment {
92 | background-color: var(--shown-comment-bg-color) !important;
93 | opacity: 0.4;
94 | }
95 | .shown-comment:hover {
96 | opacity: 0.5;
97 | }
98 | yt-live-chat-text-message-renderer.active-comment {
99 | background-color: var(--active-comment-bg-color) !important;
100 | opacity: 1;
101 | }
102 | highlight-chat {
103 | font-family: var(--font-family);
104 | font-weight: var(--highlight-chat-font-weight);
105 | box-sizing: border-box;
106 | display: block;
107 | position: absolute;
108 | bottom: var(--comment-area-bottom);
109 | left: calc(var(--comment-area-size-offset) * -1px);
110 | right: calc(var(--comment-area-size-offset) * -1px);
111 | width: auto;
112 | height: var(--comment-area-height);
113 | z-index:99999999999;
114 | overflow: hidden;
115 | margin: 0px;
116 | padding: 40px 50px 40px 220px;
117 | background-color: var(--keyer-bg-color);
118 | color: #fff;
119 | font-size: 30px;
120 | transform: scale(var(--comment-scale));
121 | }
122 | highlight-chat.preview {
123 | border: 1px #ccc solid;
124 | }
125 | .hl-c-cont {
126 | position: relative;
127 | padding: 20px;
128 | width: 100%;
129 | margin: 0 auto;
130 | transition: .5s all cubic-bezier(0.250, 0.250, 0.105, 1.2);
131 | }
132 | .hl-c-cont.fadeout {
133 | transform: translateY(600px);
134 | }
135 |
136 | .hl-name {
137 | position: absolute;
138 | top: -20px;
139 | left: 50px;
140 | font-weight: var(--author-font-weight);
141 | background: var(--author-bg-color);
142 | color: var(--author-color);
143 | padding: 10px;
144 | transform: rotate(-0deg);
145 | z-index: 1;
146 | border-radius: var(--author-border-radius);
147 | font-size: var(--author-font-size);
148 | }
149 | .hl-name::before {
150 | content: '';
151 | background: #f6cc0c;
152 | }
153 | .hl-message {
154 | position: absolute;
155 | width: var(--comment-width);
156 | font-weight: var(--comment-font-weight);
157 | padding: var(--comment-padding);
158 | color: var(--comment-color);
159 | background-color: var(--comment-bg-color);
160 | border-radius: var(--comment-border-radius);
161 | font-size: var(--comment-font-size);
162 | }
163 | .hl-message img {
164 | width: 50px;
165 | vertical-align: middle;
166 | }
167 | .hl-message img.sticker {
168 | width: 120px;
169 | }
170 | .hl-message a {
171 | color: white;
172 | }
173 | .hl-img {
174 | position: absolute;
175 | top: 0;
176 | z-index: 1;
177 | left: -60px;
178 | width: var(--author-avatar-size);
179 | height: var(--author-avatar-size);
180 | background-color: var(--author-avatar-border-color);
181 | border-radius: 50%;
182 | border: 0;
183 | padding: var(--author-avatar-border-size);
184 | }
185 | .hl-img img {
186 | display: block;
187 | width: 100%;
188 | border-radius: 50%;
189 | z-index: 1;
190 | }
191 | /* overlay a very faint white layer to bump the blacks above the luma key cutoff */
192 | .hl-img:after {
193 | content: '';
194 | display: block;
195 | height: 100%;
196 | width: 100%;
197 | top: 0;
198 | left: 0;
199 | border-radius: 50%;
200 | position:absolute;
201 | pointer-events: none;
202 | z-index: 3;
203 | }
204 | .hl-img:after {
205 | background: rgba(255,255,255,var(--author-avatar-overlay-opacity));
206 | mix-blend-mode: lighten;
207 | }
208 |
209 | .hl-badges {
210 | display: inline-block;
211 | margin-left: 10px;
212 | }
213 | .hl-badges img.yt-live-chat-author-badge-renderer {
214 | width: 24px;
215 | height: 24px;
216 | }
217 |
218 | .donation {
219 | position: absolute;
220 | display: block;
221 | text-align: center;
222 | left: 10px;
223 | top: 108px;
224 | z-index: 3;
225 | min-width: 128px;
226 | border-radius: 10px;
227 | padding: 30px 5px 0;
228 | overflow: hidden;
229 | background: linear-gradient(to right, var(--donation-gradient-stop0), var(--donation-gradient-stop1), var(--donation-gradient-stop2));
230 | color: var(--donation-color);
231 | transform: rotate(-5deg) translateX(-50%);
232 | }
233 |
234 | .donation.silver {
235 | background: linear-gradient(to right, #d6d6d6, #f1f1f1, #d6d6d6);
236 | color: #434343;
237 | }
238 | .donation.gold {
239 | background: linear-gradient(to right, #BF953F, #FCF6BA, #d0aa57, #FBF5B7, #AA771C);
240 | color: #4d3400;
241 | }
242 | .donation.platinum {
243 | background:
244 | linear-gradient(
245 | -72deg,
246 | #dedeff,
247 | #ffffff 16%,
248 | #dedeff 21%,
249 | #ffffff 24%,
250 | #555564 27%,
251 | #dedeff 36%,
252 | #ffffff 45%,
253 | #ffffff 60%,
254 | #dedeff 72%,
255 | #ffffff 80%,
256 | #dedeff 84%,
257 | #555564);
258 | color: #333;
259 | }
260 | .donation.diamond {
261 | background: linear-gradient(to right, #e5f1fa, #ffffff, #dbe7ec);
262 | color: #00374d;
263 | }
264 | .donation.emerald {
265 | background: linear-gradient(to right, #3fbf3f, #99ed9a, #3fbf3f);
266 | color: #115a14;
267 | }
268 |
269 |
270 | .membership {
271 | padding: 10px 10px 5px 10px;
272 | font-size: 0.8em;
273 | }
274 | .membership-length {
275 | font-size: 0.8em;
276 | }
277 | .donation::before {
278 | position: absolute;
279 | top: 0;
280 | left: 0;
281 | width: 100%;
282 | font-size: 18px;
283 | text-align: center;
284 | background-color: rgba(0, 0, 0, 0.23);
285 | border-radius: 10px 10px 0 0;
286 | padding: 5px 0 0;
287 | display: block;
288 | content: var(--donation-label-text);
289 | color: var(--donation-label-color);
290 | }
291 | .membership::before {
292 | content: '';
293 | }
294 | .donation::after {
295 | content: '';
296 | position: absolute;
297 | top: -50%;
298 | left: 0;
299 | height: 200%;
300 | width: 1px;
301 | background-color: var(--donation-bg-color);
302 | box-shadow: 0 0 20px 20px var(--donation-shadow-color);
303 | opacity: 0.7;
304 | transform: rotate(9deg) translate3D(250px, 0, 0);
305 | animation: superchat 3s ease-in-out infinite;
306 | }
307 | @keyframes superchat {
308 | from {
309 | transform: rotate(9deg) translate3D(-250px, 0, 0);
310 | }
311 | }
312 |
313 | /* hide chat input to give more space to the chat */
314 | /*
315 | #input-panel, yt-live-chat-viewer-engagement-message-renderer {
316 | display: none !important;
317 | }
318 | */
319 |
320 | yt-live-chat-item-list-renderer {
321 | margin-bottom: 20px;
322 | }
323 |
324 | yt-live-chat-text-message-renderer {
325 | font-size: 24px !important;
326 | line-height: 32px;
327 | }
328 |
329 |
330 | yt-live-chat-text-message-renderer,
331 | yt-live-chat-paid-message-renderer,
332 | yt-live-chat-membership-item-renderer {
333 | cursor: pointer;
334 | }
335 |
336 | yt-live-chat-text-message-renderer[is-deleted],
337 | yt-live-chat-paid-message-renderer[is-deleted],
338 | yt-live-chat-membership-item-renderer[is-deleted] {
339 | cursor: default;
340 | }
341 |
342 |
343 |
344 |
345 | yt-live-chat-viewer-engagement-message-renderer {
346 | display: none !important;
347 | }
348 |
349 |
350 | #featured {
351 | position: absolute;
352 | z-index: 1000;
353 | top: 8px;
354 | right: 50px;
355 | }
356 | #featured img {
357 | vertical-align: middle;
358 | }
359 |
360 |
361 | .button {
362 | text-decoration: none;
363 | padding: 6px;
364 | border: 1px #bbb solid;
365 | border-radius: 4px;
366 | color: #fff;
367 | background: #444;
368 | }
369 | #pop-out-button {
370 | margin-left: 20px;
371 | }
372 | #pop-out-url {
373 | margin-left: 20px;
374 | font-size: 1.2em;
375 | width: 400px;
376 | border-radius: 4px;
377 | }
378 |
379 | .hidden {
380 | display: none;
381 | }
382 |
383 | yt-live-chat-app tp-yt-iron-dropdown {
384 | display: none;
385 | }
386 | yt-live-chat-app yt-sort-filter-sub-menu-renderer tp-yt-iron-dropdown {
387 | display: block;
388 | }
389 |
390 |
--------------------------------------------------------------------------------
/youtube.js:
--------------------------------------------------------------------------------
1 | var showOnlyFirstName;
2 |
3 | var highlightWords = [];
4 | var usePersistentSessionID = false;
5 | var sessionID = "";
6 | var remoteWindowURL = "https://chat.aaronpk.tv/overlay/";
7 | var remoteServerURL = "https://chat.aaronpk.tv/overlay/pub";
8 | var version = "0.3.9";
9 | var config = {};
10 | var lastID = "";
11 | var videoID = "";
12 | var autoHideTimer = null;
13 |
14 | $("body").unbind("click").on("click", "yt-live-chat-text-message-renderer,yt-live-chat-paid-message-renderer,yt-live-chat-membership-item-renderer,ytd-sponsorships-live-chat-gift-purchase-announcement-renderer,yt-live-chat-paid-sticker-renderer", function() {
15 |
16 | $(".active-comment").removeClass("active-comment");
17 |
18 | // "Click" on some innocuous part of the page to hide the moderation popup thingy.
19 | // YouTube seems to want to pop that up any time you click anywhere on a message.
20 | setTimeout(function(){
21 | $("yt-live-chat-message-input-renderer").click();
22 | }, 200);
23 |
24 | clearTimeout(autoHideTimer);
25 |
26 | // Don't show deleted messages
27 | if($(this)[0].hasAttribute("is-deleted")) {
28 | console.log("Not showing deleted message");
29 | return;
30 | }
31 |
32 | var data = {};
33 |
34 | $(".hl-c-cont").remove();
35 |
36 | data.chatId = $(this).attr("id");
37 |
38 | if(data.chatId === lastID) {
39 | hideActiveChat();
40 | return;
41 | }
42 |
43 | data.authorname = $(this).find("#author-name").text();
44 | if(showOnlyFirstName) {
45 | data.authorname = data.authorname.replace(/ [^ ]+$/, '');
46 | }
47 | data.authorimg = $(this).find("#img").attr("src");
48 | // Replace the 32px and 64px avatar with a 128px avatar but keep the identifier identical before the first '='
49 | const equalIndex = data.authorimg.indexOf("=");
50 | if (equalIndex !== -1) {
51 | let part1 = data.authorimg.slice(0, equalIndex);
52 | let part2 = data.authorimg.slice(equalIndex);
53 | part2 = part2.replace("s32", "s128").replace("s64", "s128");
54 |
55 | data.authorimg = part1 + part2;
56 | }
57 |
58 | data.message = $(this).find("#message").html();
59 |
60 | data.sticker = $(this).find(".yt-live-chat-paid-sticker-renderer #sticker #img").attr("src");
61 |
62 |
63 | // Donation amounts for stickers use a different id than regular superchats
64 | if(data.sticker) {
65 | data.donation = $(this).find("#purchase-amount-chip").html();
66 | } else {
67 | data.donation = $(this).find("#purchase-amount .yt-live-chat-paid-message-renderer").html();
68 | }
69 |
70 | data.badges = "";
71 | if($(this).find("#chat-badges .yt-live-chat-author-badge-renderer img").length > 0) {
72 | data.badges = $(this).find("#chat-badges .yt-live-chat-author-badge-renderer img").parent().html();
73 | }
74 |
75 | // Mark this comment as shown
76 | $(this).addClass("shown-comment").addClass("active-comment");
77 |
78 | data.donationHTML = '';
79 | if(data.donation) {
80 | data.donationHTML = '' + data.donation + '
';
81 | }
82 |
83 | data.membership = $(this).find(".yt-live-chat-membership-item-renderer #header-subtext").html(); // membership level e.g. "SILVER"
84 | data.giftedMembership = $(this).find(".ytd-sponsorships-live-chat-header-renderer #primary-text").html(); // Bob gifted 20 memberships
85 |
86 | data.membershipHTML = '';
87 |
88 | // Try to find the membership level name
89 | data.membershipLevel = '';
90 | if(data.membership) {
91 | var membershipLevelName;
92 | if(m=data.membership.match(/(Welcome|Upgraded membership) to (.+)!/)) {
93 | membershipLevelName = m[2];
94 | } else {
95 | membershipLevelName = data.membership;
96 | }
97 | switch(membershipLevelName) {
98 | case 'SILVER':
99 | data.membershipLevel = 'silver'; break;
100 | case 'GOLD':
101 | data.membershipLevel = 'gold'; break;
102 | case 'PLATINUM':
103 | data.membershipLevel = 'platinum'; break;
104 | case 'DIAMOND':
105 | data.membershipLevel = 'diamond'; break;
106 | case 'EMERALD':
107 | data.membershipLevel = 'emerald'; break;
108 | }
109 | }
110 |
111 |
112 | if(data.giftedMembership) {
113 | data.membershipHTML = 'GIFT
';
114 | data.message = data.giftedMembership;
115 | } else if(data.membership) {
116 | if(data.message) {
117 | data.membershipLength = $(this).find(".yt-live-chat-membership-item-renderer #header-primary-text").text(); // "Member for 20 months"
118 | if(data.membershipLength) {
119 | if(m = data.membershipLength.match(/Member for (.+)/)) {
120 | data.membership = data.membership + ''+m[1]+' ';
121 | }
122 | }
123 | // Member chat, show their member tier under their photo. Message will have been extracted already.
124 | data.membershipHTML = ''+data.membership+'
';
125 | } else {
126 | // New member or upgrade, show the tier in the main message section
127 | if(data.membership.match(/Upgraded membership/)) {
128 | data.membershipHTML = 'UPGRADE
';
129 | } else {
130 | data.membershipHTML = 'NEW MEMBER!
';
131 | }
132 | data.message = data.membership;
133 | }
134 | }
135 |
136 |
137 | if(data.sticker) {
138 | data.message = ' ';
139 | }
140 |
141 | data.backgroundColor = "";
142 | data.textColor = "";
143 | if(this.style.getPropertyValue('--yt-live-chat-paid-message-primary-color')) {
144 | data.backgroundColor = "background-color: "+this.style.getPropertyValue('--yt-live-chat-paid-message-primary-color')+";";
145 | data.textColor = "color: #111;";
146 | }
147 |
148 | // This doesn't work yet
149 | // if(this.style.getPropertyValue('--yt-live-chat-sponsor-color')) {
150 | // data.backgroundColor = "background-color: "+this.style.getPropertyValue('--yt-live-chat-sponsor-color')+";";
151 | // data.textColor = "color: #111;";
152 | // }
153 |
154 | // console.log(data);
155 |
156 | var html = ''
157 | + '
' + data.authorname
158 | + '
' + data.badges + '
'
159 | + '
'
160 | + '
' + data.message + '
'
161 | + '
'
162 | +data.donationHTML+data.membershipHTML
163 | +'
';
164 |
165 | lastID = data.chatId;
166 |
167 | if(sessionID) {
168 |
169 | var remote = {
170 | version: version,
171 | command: "show",
172 | html: html,
173 | config: config,
174 | v: videoID
175 | }
176 | $.post(remoteServerURL+"?v="+videoID+"&id="+sessionID, JSON.stringify(remote));
177 |
178 | } else {
179 |
180 | $( "highlight-chat" ).removeClass("preview").append(html)
181 | .delay(10).queue(function(next){
182 | $( ".hl-c-cont" ).removeClass("fadeout");
183 | next();
184 | });
185 |
186 | }
187 |
188 | if(config.autoHideSeconds && config.autoHideSeconds > 0) {
189 | autoHideTimer = setTimeout(function(){
190 | hideActiveChat();
191 | }, config.autoHideSeconds*1000);
192 | }
193 |
194 | });
195 |
196 | function hideActiveChat() {
197 | if(sessionID) {
198 | var remote = {
199 | version: version,
200 | command: "hide",
201 | config: config,
202 | v: videoID
203 | };
204 | $.post(remoteServerURL+"?v="+videoID+"&id="+sessionID, JSON.stringify(remote));
205 | }
206 |
207 | $(".hl-c-cont").addClass("fadeout").delay(300).queue(function(){
208 | $(".hl-c-cont").remove().dequeue();
209 | });
210 |
211 | lastID = false;
212 | }
213 |
214 | $("body").on("click", ".btn-clear", function() {
215 | hideActiveChat();
216 | });
217 |
218 | $("yt-live-chat-app").before( 'CLEAR ' );
219 | $("body").addClass("inline-chat");
220 |
221 | // Restore settings
222 | var configProperties = ["color","scale","sizeOffset",
223 | "commentBottom","commentHeight","authorBackgroundColor",
224 | "authorAvatarBorderColor","authorColor","commentBackgroundColor","commentColor",
225 | "fontFamily","showOnlyFirstName","highlightWords",
226 | "popoutURL","serverURL","autoHideSeconds",
227 | "authorAvatarOverlayOpacity","persistentSessionID","sessionID"
228 | ];
229 | chrome.storage.sync.get(configProperties, function(item){
230 | var color = "#000";
231 | if(item.color) {
232 | color = item.color;
233 | }
234 |
235 | let root = document.documentElement;
236 | root.style.setProperty("--keyer-bg-color", color);
237 |
238 | if(item.authorBackgroundColor) {
239 | root.style.setProperty("--author-bg-color", item.authorBackgroundColor);
240 | root.style.setProperty("--author-avatar-border-color", item.authorBackgroundColor);
241 | }
242 | if(item.authorAvatarBorderColor) {
243 | root.style.setProperty("--author-avatar-border-color", item.authorAvatarBorderColor);
244 | }
245 | if(item.authorAvatarOverlayOpacity) {
246 | root.style.setProperty("--author-avatar-overlay-opacity", item.authorAvatarOverlayOpacity);
247 | }
248 | if(item.commentBackgroundColor) {
249 | root.style.setProperty("--comment-bg-color", item.commentBackgroundColor);
250 | }
251 | if(item.authorColor) {
252 | root.style.setProperty("--author-color", item.authorColor);
253 | }
254 | if(item.commentColor) {
255 | root.style.setProperty("--comment-color", item.commentColor);
256 | }
257 | if(item.fontFamily) {
258 | root.style.setProperty("--font-family", item.fontFamily);
259 | }
260 | if(item.scale) {
261 | root.style.setProperty("--comment-scale", item.scale);
262 | }
263 | if(item.commentBottom) {
264 | root.style.setProperty("--comment-area-bottom", item.commentBottom);
265 | }
266 | if(item.commentHeight) {
267 | root.style.setProperty("--comment-area-height", item.commentHeight);
268 | }
269 | if(item.sizeOffset) {
270 | root.style.setProperty("--comment-area-size-offset", item.sizeOffset);
271 | }
272 | showOnlyFirstName = item.showOnlyFirstName;
273 | highlightWords = item.highlightWords;
274 |
275 | if(item.popoutURL) {
276 | remoteWindowURL = item.popoutURL;
277 | }
278 | if(item.serverURL) {
279 | remoteServerURL = item.serverURL;
280 | }
281 |
282 | if(item.persistentSessionID && item.sessionID) {
283 | usePersistentSessionID = item.sessionID;
284 | }
285 |
286 | config = item;
287 | });
288 |
289 |
290 | // $("#primary-content").append('Aspect Ratio: ');
291 | $("#primary-content").append('Get Overlay URL ');
292 | $("#primary-content").append(' ');
293 |
294 | function displayAspectRatio() {
295 | var ratio = Math.round(window.innerWidth / window.innerHeight * 100) / 100;
296 | ratio += " (target 1.77)";
297 | $("#aspect-ratio").text(ratio);
298 | }
299 | // displayAspectRatio();
300 | // window.onresize = displayAspectRatio;
301 |
302 | $("#pop-out-button").click(function(e){
303 | e.preventDefault();
304 |
305 | if(usePersistentSessionID) {
306 | sessionID = usePersistentSessionID;
307 | }
308 |
309 | if(!sessionID) {
310 | if(window.location.hash) {
311 | sessionID = window.location.hash.replace("#", "");
312 | } else {
313 | sessionID = generateSessionID();
314 | }
315 | }
316 |
317 | window.location.hash = sessionID;
318 |
319 | $("#pop-out-url").val(remoteWindowURL+"#"+sessionID);
320 | $("#pop-out-url").parent().removeClass("hidden");
321 |
322 | $("highlight-chat").remove();
323 | $("body").removeClass("inline-chat");
324 | $("#aspect-ratio-container").addClass("hidden");
325 | $("#get-overlay-url-container").addClass("hidden");
326 | });
327 |
328 | $("#pop-out-url").click(function(){
329 | $(this).select();
330 | });
331 |
332 | $(document).keyup(function(e){
333 |
334 | // Escape key hides active chat
335 | if(e.keyCode === 27) {
336 | hideActiveChat();
337 | }
338 |
339 | });
340 |
341 | $(function(){
342 |
343 | // Show a placeholder message so you can position the window before the chat is live
344 | var data = {};
345 | data.message = "this livestream is the best!";
346 | data.authorimg = remoteWindowURL+"/youtube-live-chat-sample-avatar.png";
347 | $( "highlight-chat" ).addClass("preview").append('')
348 | .delay(10).queue(function(next){
349 | $( ".hl-c-cont" ).removeClass("fadeout");
350 | next();
351 | });
352 |
353 | // Restore the popout URL field if they refresh the page
354 | if(window.location.hash) {
355 | $("#pop-out-button").click();
356 | }
357 |
358 | // Show banner
359 | const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
360 |
361 | const params = new URLSearchParams(window.location.search);
362 | videoID = params.get('v');
363 |
364 | $.post("https://chat.aaronpk.tv/featured.php", {
365 | lang: window.navigator.language,
366 | tz: timezone,
367 | version: version,
368 | v: videoID
369 | }, function(response){
370 | if(response && response.img) {
371 | var link = 'https://chat.aaronpk.tv/redirect.php?tag='+response.tag+'&lang='+window.navigator.language+'&tz='+timezone+"&version="+version;
372 | $("body").append('');
373 | }
374 | });
375 |
376 | removeReactionButtons();
377 |
378 | });
379 |
380 |
381 |
382 | function generateSessionID(){
383 | var text = "";
384 | var chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
385 | for (var i = 0; i < 10; i++){
386 | text += chars.charAt(Math.floor(Math.random() * chars.length));
387 | }
388 | return text;
389 | };
390 |
391 | function onElementInserted(containerSelector, callback) {
392 |
393 | var watchedTagNames = [
394 | "yt-live-chat-text-message-renderer".toUpperCase(),
395 | "yt-live-chat-paid-message-renderer".toUpperCase(),
396 | "yt-live-chat-membership-item-renderer".toUpperCase(),
397 | "yt-live-chat-paid-sticker-renderer".toUpperCase(),
398 | "ytd-sponsorships-live-chat-gift-purchase-announcement-renderer".toUpperCase()
399 | ];
400 |
401 | var onMutationsObserved = function(mutations) {
402 | mutations.forEach(function(mutation) {
403 | // console.log("A mutation happened");
404 | if (mutation.addedNodes.length) {
405 | for (var i = 0, len = mutation.addedNodes.length; i < len; i++) {
406 | if(watchedTagNames.includes(mutation.addedNodes[i].tagName)) {
407 | callback(mutation.addedNodes[i]);
408 | }
409 | }
410 | }
411 | });
412 | };
413 |
414 | var target = document.querySelectorAll(containerSelector)[0];
415 | var config = { childList: true, subtree: true };
416 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
417 | var observer = new MutationObserver(onMutationsObserved);
418 | observer.observe(target, config);
419 |
420 | }
421 |
422 |
423 | onElementInserted(".yt-live-chat-item-list-renderer#items", function(element){
424 | // console.log("New dom element inserted", element.tagName);
425 | // Check for highlight words
426 | var chattext = $(element).find("#message").text();
427 | var chatWords = chattext.split(" ");
428 | var highlights = chatWords.filter(value => highlightWords.includes(value.toLowerCase().replace(/[^a-z0-9]/gi, '')));
429 | $(element).removeClass("shown-comment");
430 | if(highlights.length > 0) {
431 | $(element).addClass("highlighted-comment");
432 | }
433 | });
434 |
435 |
436 | /*
437 | function removeModerationMenu(element) {
438 | $(element).find("tp-yt-iron-dropdown, #menu").remove();
439 | // Remove the "top chat/live chat" option since removing the iron-dropdown also removes the dropdown from that.
440 | // This way people won't be confused about why pressing it isn't working anymore.
441 | $(element).find("yt-sort-filter-sub-menu-renderer").remove();
442 | }
443 |
444 | function removeReactionButtons() {
445 | // $("yt-reaction-control-panel-overlay-view-model").remove();
446 | }
447 | */
448 |
449 |
--------------------------------------------------------------------------------
/jquery.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0
+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0