PageSourceSearch

https://aniwatch.co.at/wp-content/themes/Zoro-Tv/assets/js/watch.min.js?ver=1.2.1

js aniwatch.co.at collected 2026-10-05 22:56:44 UTC 23,934 bytes, 729 lines download raw bytes

1let animeId = null;
2let epId = null;
3let epNumber = null;
4let isLoggedIn = user.is_logged_in || false;
5window.addEventListener('hianime:user-ready', function (event) {
6    isLoggedIn = !!(event.detail && event.detail.is_logged_in);
7});
8let watchingLogInterval = null;
9let watchingLogTimeout = null;
10let lastContinueWatchingKey = '';
11let continueWatchingRequestInFlight = false;
12let currentVideoData = {
13    currentTime: 0,
14    duration: 0
15};
16const hiEpisodeCacheTtl = 6 * 60 * 60 * 1000;
17const hiContinueWatchingInitialDelay = 60 * 1000;
18const hiContinueWatchingInterval = 60 * 1000;
19const hiPendingRequests = {};
20let hiPageFullyLoaded = document.readyState === 'complete';
21
22window.addEventListener('load', function () {
23    hiPageFullyLoaded = true;
24});
25
26function hiRestUrl(path) {
27    const baseUrl = (hianime_ep_ajax.rest_url || '').replace(/\/+$/, '');
28    return baseUrl + '/' + String(path || '').replace(/^\/+/, '');
29}
30
31function hiCacheGet(key) {
32    try {
33        const cached = sessionStorage.getItem(key);
34        if (!cached) return null;
35
36        const item = JSON.parse(cached);
37        if (!item || !item.expires || item.expires < Date.now()) {
38            sessionStorage.removeItem(key);
39            return null;
40        }
41
42        return item.value;
43    } catch (e) {
44        return null;
45    }
46}
47
48function hiCacheSet(key, value) {
49    try {
50        sessionStorage.setItem(key, JSON.stringify({
51            expires: Date.now() + hiEpisodeCacheTtl,
52            value: value
53        }));
54    } catch (e) {}
55}
56
57function hiFetchPublicJson(url, options = {}) {
58    const timeout = options.timeout || 3000;
59    const retries = options.retries || 3;
60    const credentials = options.credentials || 'omit';
61
62    function attempt(remaining) {
63        if (window.fetch) {
64            const controller = window.AbortController ? new AbortController() : null;
65            const timer = controller ? setTimeout(() => controller.abort(), timeout) : null;
66
67            return fetch(url, {
68                method: 'GET',
69                credentials: credentials,
70                cache: 'no-store',
71                signal: controller ? controller.signal : undefined,
72                headers: {
73                    'Accept': 'application/json',
74                    'Cache-Control': 'no-cache'
75                }
76            }).then((response) => {
77                if (timer) clearTimeout(timer);
78                if (!response.ok) {
79                    throw new Error(response.statusText || 'Request failed');
80                }
81
82                return response.json();
83            }).catch((error) => {
84                if (timer) clearTimeout(timer);
85                if (remaining > 1) {
86                    return attempt(remaining - 1);
87                }
88                throw error;
89            });
90        }
91
92        return $.ajax({
93            url: url,
94            type: 'GET',
95            timeout: timeout
96        }).catch((error) => {
97            if (remaining > 1) {
98                return attempt(remaining - 1);
99            }
100            throw error;
101        });
102    }
103
104    return attempt(retries);
105}
106
107function hiAfterPageLoad(callback) {
108    if (hiPageFullyLoaded || document.readyState === 'complete') {
109        setTimeout(callback, 0);
110        return;
111    }
112
113    window.addEventListener('load', function () {
114        setTimeout(callback, 0);
115    }, { once: true });
116}
117
118function selectEpisode($episode, options = {}) {
119    if (!$episode || !$episode.length) {
120        return;
121    }
122
123    const episodeId = $episode.data('id');
124    if (!episodeId) {
125        console.error('Episode ID not found');
126        return;
127    }
128
129    const isSameEpisode = String(episodeId) === String(epId || '');
130
131    $('.ep-item').removeClass('active');
132    $episode.addClass('active');
133    epId = episodeId;
134    epNumber = $episode.data('number');
135
136    if (!isSameEpisode || options.reloadSources) {
137        loadSources(episodeId);
138    } else if (!$('.player-frame iframe').length) {
139        selectServer($('#servers-content .server-item').first());
140    }
141
142    if (!options.skipHistory) {
143        const episodeUrl = $episode.attr('href');
144        if (episodeUrl && episodeUrl !== window.location.href && episodeUrl !== window.location.pathname) {
145            history.pushState(null, '', episodeUrl);
146        }
147    }
148
149    $('#wp-admin-bar-edit a').attr("href", "/wp-admin/post.php?post=" + epId + "&action=edit");
150}
151
152function selectServer($serverItem) {
153    if (!$serverItem || !$serverItem.length) {
154        return;
155    }
156
157    const serverName = $serverItem.data('server-name');
158    const serverHash = $serverItem.data('hash');
159    if (!serverName || !serverHash) {
160        console.error('Server name or hash not found');
161        return;
162    }
163
164    $('#servers-content .server-item .btn').removeClass('active');
165    $serverItem.find('.btn').addClass('active');
166
167    const embedUrl = atob(serverHash);
168    const iframe = document.createElement('iframe');
169    iframe.title = serverName;
170    iframe.frameBorder = '0';
171    iframe.allowFullscreen = true;
172    iframe.src = embedUrl;
173
174    $('.player-frame').empty().append(iframe);
175}
176
177function handleEpisodeListResponse(response) {
178    if (!response?.status) {
179        console.error('Failed to load episode list');
180        $('#episodes-content').html('<div class="alert alert-warning">Failed to load episodes. Please refresh the page.</div>');
181        return;
182    }
183
184    if (response.totalItems > 1) {
185        $('.block-prev, .block-next').show();
186    }
187
188    $('#episodes-content').html(response.html);
189
190    if ($('.toggle-lang').hasClass('off')) {
191        toggleAnimeName();
192    }
193
194    const $selectedEpisode = $('.ep-item[data-id="' + epId + '"]').first();
195    if ($selectedEpisode.length) {
196        selectEpisode($selectedEpisode, { skipHistory: true });
197    }
198
199    if ($(".ep-page-item").length > 0) {
200        const activePage = $(".ep-item.active").parent().data("page");
201        $(".ep-page-item[data-page=" + activePage + "]").click();
202    } else {
203        const activePage = $(".ep-item.active").parent().data("page");
204        $("#episodes-page-" + activePage).show();
205    }
206}
207
208function handleSourcesResponse(response) {
209    if (!response?.status || !response?.html) {
210        console.error('Failed to load sources');
211        $('#servers-content').html('<div class="alert alert-warning">Failed to load sources. Please refresh the page.</div>');
212        return;
213    }
214
215    $('#servers-content').html(response.html);
216    $('#servers-content .loading-relative').remove();
217
218    const firstServerItem = $('#servers-content .server-item').first();
219    if (firstServerItem.length) {
220        selectServer(firstServerItem);
221        currentVideoData = {
222            currentTime: 0,
223            duration: 0
224        };
225        startLoggingContinueWatching();
226    }
227}
228
229function hydrateInitialWatchPage() {
230    if (!$('#episodes-content .ep-item').length) {
231        loadEpisodeList();
232        return;
233    }
234
235    if ($('.ep-item').length > 1) {
236        $('.block-prev, .block-next').show();
237    }
238
239    let $selectedEpisode = $('.ep-item[data-id="' + epId + '"]').first();
240    if (!$selectedEpisode.length) {
241        $selectedEpisode = $('.ep-item.active').first();
242    }
243    if (!$selectedEpisode.length) {
244        $selectedEpisode = $('.ep-item').first();
245    }
246
247    if ($selectedEpisode.length) {
248        $('.ep-item').removeClass('active');
249        $selectedEpisode.addClass('active');
250        epId = $selectedEpisode.data('id');
251        epNumber = $selectedEpisode.data('number');
252        $('#wp-admin-bar-edit a').attr("href", "/wp-admin/post.php?post=" + epId + "&action=edit");
253    }
254
255    if ($(".ep-page-item").length > 0) {
256        const activePage = $(".ep-item.active").parent().data("page");
257        $(".ep-page-item").removeClass("active");
258        $(".ep-page-item .ic-active").hide();
259        const $pageItem = $(".ep-page-item[data-page=" + activePage 
259+ "]").first();
260        $pageItem.addClass("active");
261        $pageItem.find(".ic-active").show();
262        $(".ss-list-min").hide().removeClass("active");
263        $("#episodes-page-" + activePage).show().addClass("active");
264        $("#current-page").text($pageItem.text().trim());
265    } else {
266        $(".ss-list").show().addClass("active");
267    }
268
269    if ($('.toggle-lang').hasClass('off')) {
270        toggleAnimeName();
271    }
272
273    const initialServersHtml = $('#servers-content').html();
274    if (initialServersHtml && epId) {
275        hiCacheSet('hi_episode_servers_' + epId, {
276            status: true,
277            html: initialServersHtml
278        });
279    }
280
281    hiAfterPageLoad(function () {
282        const firstServerItem = $('#servers-content .server-item').first();
283        if (firstServerItem.length) {
284            selectServer(firstServerItem);
285            currentVideoData = {
286                currentTime: 0,
287                duration: 0
288            };
289            startLoggingContinueWatching();
290        } else {
291            $('.player-frame').empty();
292        }
293    });
294}
295// Main initialization
296$(document).ready(() => {
297    initializeAnimeId();
298
299    $(document).on('click', '.ep-item', function (e) {
300        e.preventDefault();
301        selectEpisode($(this));
302    });
303
304    $(document).on('click', '#servers-content .server-item', function (e) {
305        e.preventDefault();
306        selectServer($(this));
307    });
308
309    $(document).on("click", ".ep-page-item", function () {
310        $(".ep-page-item").removeClass("active"),
311            $(".ep-page-item .ic-active").hide(),
312            $(this).addClass("active"),
313            $(this).find(".ic-active").show(),
314            $(".ss-list-min").hide(),
315            $(".ss-list-min").removeClass("active"),
316            $("#episodes-page-" + $(this).data("page")).show(),
317            $("#episodes-page-" + $(this).data("page")).addClass("active"),
318            $("#current-page").text($(this).text().trim())
319    });
320    // Search episode functionality
321    $(document).on("keyup", "#search-ep", function (e) {
322        e.preventDefault();
323
324        var targetElement, inputValue = e.target.value;
325
326        // Remove highlight from all episode items
327        $(".ep-item").removeClass("highlight");
328
329        if (inputValue) {
330            // Find episode item with matching data-number
331            var episodeItem = $(".ep-item[data-number=" + inputValue + "]");
332
333            if (episodeItem.length > 0) {
334                // Get the parent container
335                targetElement = episodeItem.parent();
336
337                // Click on the corresponding page item
338                $(".ep-page-item[data-page=" + targetElement.data("page") + "]").click();
339
340                // If Enter key was pressed (keyCode 13)
341                if (e.keyCode === 13) {
342                    // Clear the input and click the episode item
343                    $(e.target).val("");
344                    episodeItem.click();
345                } else {
346                    // Just highlight the episode item
347                    episodeItem.addClass("highlight");
348                }
349            }
350        } else {
351            // If input is empty, go to the page containing the active episode
352            targetElement = $(".ep-item.active").parent().data("page");
353            $(".ep-page-item[data-page=" + targetElement + "]").click();
354        }
355    });
356
357    $("#media-resize").click(function (e) {
358        $(".anis-watch-wrap").toggleClass("extend");
359        if ($(".anis-watch-wrap").hasClass("extend")) {
360            $(this).html('<i class="fas fa-compress mr-1"></i>Collapse');
361        } else {
362            $(this).html('<i class="fas fa-expand mr-1"></i>Expand');
363        }
364    });
365    $("#turn-off-light").click(function (e) {
366        $("#mask-overlay, .anis-watch-wrap").toggleClass("active");
367    });
368    $("#mask-overlay").click(function (e) {
369        $("#mask-overlay, .anis-watch-wrap").removeClass("active");
370        $("#turn-off-light").removeClass("off");
371    });
372    $(".dt-comment").click(function () {
373        window.scrollTo({
374            top: document.getElementById("comment-block").offsetTop,
375            behavior: 'smooth'
376        });
377    });
378
379    // Auto play setting
380    if (parseInt(getUserSetting('auto_play', '0')) === 1) {
381        $('.quick-settings[data-option="auto_play"]').removeClass("off");
382    }
383
384    // Auto next setting
385    if (parseInt(getUserSetting('auto_next', '0')) === 1) {
386        $('.quick-settings[data-option="auto_next"]').removeClass("off");
387    }
388
389    // Auto skip intro setting
390    if (parseInt(getUserSetting('auto_skip_intro', '0')) === 1) {
391        $('.quick-settings[data-option="auto_skip_intro"]').removeClass("off");
392    }
393  
394    // Quick settings click handler
395    $(".quick-settings").click(function () {
396        const option = $(this).data("option");
397        const isOff = $(this).hasClass("off");
398        updateLocalStorageSetting(option, isOff ? '0' : '1');
399    });
400
401    $(".btn-emo").click(function (){
402        $(this).closest('.block-rating').addClass('rated');
403        $(this).siblings().removeClass('emo-rated');
404        $(this).addClass('emo-rated');
405        let ratings = window.localStorage.getItem('emo-ratings') || '{}';
406        window.localStorage.setItem('emo-ratings', JSON.stringify({...JSON.parse(ratings), [`${$(this).data('id')}`]: $(this).data('mark')}));
407    })
408
409      // If user has rated this anime, auto-select the rating button
410    let animeId = $('#ani_detail').data('anime-id');
411    const ratings = JSON.parse(window.localStorage.getItem('emo-ratings') || '{}');
412    if (ratings && ratings[animeId]) {
413        $(`.btn-emo[data-id="${animeId}"][data-mark="${ratings[animeId]}"]`).click();
414    }
415
416    // Quick settings toggle handler
417    $(".quick-settings-toggle").click(function () {
418        $(this).toggleClass("active");
419        const option = $(this).data("option");
420        const isActive = $(this).hasClass("active");
421        updateUserSetting(option, isActive ? 1 : 0);
422    });
423
424    updateScheduleDate();
425
426});
427
428function updateScheduleDate() {
429    const $scheduleAlert = $(".schedule-alert");
430    
431    if ($scheduleAlert.length === 0) {
432        return;
433    }
434    
435    const $scheduleDate = $("#schedule-date");
436    const dateValue = $scheduleDate.data("value");
437    
438    if (!dateValue) {
439        return;
440    }
441    
442    const isoDateString = dateValue.split(" ").join("T") + "Z";
443    const date = new Date(isoDateString);
444    
445    if (!isNaN(date.getTime())) {
446        const localizedDate = date.toLocaleString("en-US");
447        $scheduleDate.text(localizedDate);
448    }
449}
450
451function initializeAnimeId() {
452
453    if (animeId) return;
454
455    // Try to get anime ID from detail link // id="ani_detail" data-anime-id
456    const animeDetail = $('#ani_detail');
457    if (animeDetail.length) {
458        animeId = animeDetail.data('anime-id');
459        epId = animeDetail.data('id');
460    }
461
462
463
464    // Fallback: data attribute
465    if (!animeId) {
466        return;
467    }
468    if (animeId) {
469        hydrateInitialWatchPage();
470    }
471}
472
473function loadEpisodeList() {
474    if (!animeId) {
475        console.error('Anime ID not found');
476        return;
477    }
478
479    const cacheKey = 'hi_episode_list_' + animeId;
480    const cachedResponse = hiCacheGet(cacheKey);
481    if (cachedResponse) {
482        handleEpisodeListResponse(cachedResponse);
483        return;
484    }
485
486    if (hiPendingRequests[cacheKey]) {
487        return;
488    }
489
490    $('#episodes-content').html(`
491        <div class="loading-relative loading-box loading-box-episodes">
492            <div class="loading">
493                <div class="span1"></div>
494                <div class="span2"></div>
495                <div class="span3"></div>
496            </div>
497        </div>
498    `);
499
500    hiFetchPublicJson(hiRestUrl('episode/list/' + animeId), { timeout: 250000, retries: 3 })
501        .then((response) => {
502            if (response?.status) {
503                hiCacheSet(cacheKey, response);
504            }
505            handleEpisodeListResponse(response);
506            delete hiPendingRequests[cacheKey];
507        }, (error) => {
508            console.error('AJAX error loading episode list:', error);
509            $('#episodes-content').html(`<div class="alert alert-danger">Error loading episodes: ${error.message || error}</div>`);
510            delete hiPendingRequests[cacheKey];
511        });
512
513    hiPendingRequests[cacheKey] = true;
514
515
516}
517
518let activeSourcesController = null;
519
520function loadSources(episodeId) {
521    if (!episodeId) {
522        console.error('Episode ID not found');
523        return;
524    }
525
526    if (activeSourcesController) {
527        activeSourcesController.abort();
528        activeSourcesController = null;
529    }
530
531    const cacheKey = 'hi_episode_servers_' + episodeId;
532    const cachedResponse = hiCacheGet(cacheKey);
533    if (cachedResponse) {
534        handleSourcesResponse(cachedResponse);
535        return;
536    }
537
538    if (hiPendingRequests[cacheKey]) {
539        return;
540    }
541
542    $('#servers-content, .player-frame').html(`
543        <div class="loading-relative loading-box">
544            <div class="loading">
545                <div class="span1"></div>
546                <div class="span2"></div>
547                <div class="span3"></div>
548            </div>
549        </div>
550    `);
551
552    const controller = window.AbortController ? new AbortController() : null;
553    activeSourcesController = controller;
554
555    const url = hiRestUrl('episode/servers/' + episodeId);
556    const fetchPromise = controller
557        ? fetch(url, {
558            method: 'GET',
559            credentials: 'omit',
560            cache: 'no-store',
561            signal: controller.signal,
562            headers: { 'Accept': 'application/json', 'Cache-Control': 'no-cache' }
563          }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
564        : hiFetchPublicJson(url, { timeout: 250000, retries: 3 });
565
566    hiPendingRequests[cacheKey] = true;
567
568    fetchPromise.then((response) => {
569            if (response?.status && response?.html) {
570                hiCacheSet(cacheKey, response);
571            }
572            handleSourcesResponse(response);
573            delete hiPendingRequests[cacheKey];
574            if (activeSourcesController === controller) activeSourcesController = null;
575        }, (error) => {
576            if (error?.name === 'AbortError') return;
577            console.error('AJAX error loading sources:', error);
578            $('#servers-content').html(`<div class="alert alert-danger">Error loading sources: ${error.message || error}</div>`);
579            delete hiPendingRequests[cacheKey];
580            if (activeSourcesController === controller) activeSourcesController = null;
581        });
582}
583
584
585function toggleAnimeName() {
586    $(".dynamic-name").each(function () {
587        var currentName = $(this).text(),
588            jName = $(this).data("jname"),
589            _this = $(this);
590        _this.animate(
591            {
592                opacity: 0,
593            },
594            200,
595            function () {
596                if (jName.length > 0) {
597                    _this.text(jName).animate(
598                        {
599                            opacity: 1,
600                        },
601                        200
602                    );
603                    _this.data("jname", currentName);
604                }
605            }
606        );
607    });
608}
609
610function nextEpisode() {
611    var e, t;
612    $(".ep-item.active").is(":last-child") ? (e = (t = $(".ep-page-item.active").next()).data("page"),
613        0 < t.length && (t.click(),
614            $("#episodes-page-" + e + " .ep-item").first().click())) : 0 < (t = $(".ep-item.active").next()).length && t.click()
615}
616
617function prevEpisode() {
618    var e, t;
619    $(".ep-item.active").is(":first-child") ? (e = (t = $(".ep-page-item.active").prev()).data("page"),
620        0 < t.length && (t.click(),
621            $("#episodes-page-" + e + " .ep-item").last().click())) : 0 < (t = $(".ep-item.active").prev()).length && t.click()
622}
623
624function startLoggingContinueWatching() {
625    if (watchingLogInterval) {
626        clearInterval(watchingLogInterval);
627    }
628    if (watchingLogTimeout) {
629        clearTimeout(watchingLogTimeout);
630    }
631
632    lastContinueWatchingKey = '';
633
634    if (!isLoggedIn) {
635        return;
636    }
637
638    watchingLogTimeout = setTimeout(() => {
639        updateContinueWatching(animeId, epNumber, epId, currentVideoData.currentTime, currentVideoData.duration);
640    }, hiContinueWatchingInitialDelay);
641
642    watchingLogInterval = setInterval(() => {
643        if (animeId && epId && isLoggedIn && !document.hidden) {
644            updateContinueWatching(animeId, epNumber, epId, currentVideoData.currentTime, currentVideoData.duration);
645        }
646    }, hiContinueWatchingInterval);
647
648    // Update continue watching when user leaves the page
649    // window.addEventListener('beforeunload', function() {
650    //     if (animeId && epId && isLoggedIn) {
651    //         updateContinueWatching(animeId, epNumber, epId, currentVideoData.currentTime, currentVideoData.duration);
652    //     }
653    // });
654
655    // // Update continue watching when page becomes hidden
656    // document.addEventListener('visibilitychange', function() {
657    //     if (document.hidden && animeId && epId && isLoggedIn) {
658    //         updateContinueWatching(animeId, epNumber, epId, currentVideoData.currentTime, currentVideoData.duration);
659    //     }
660    // });
661}
662
663window.addEventListener('message', function (event) {
664    // Handle specific event data
665    if (event.data && typeof event.data === 'object') {
666        const data = event.data;
667        if (data.type === 'watching-log') {
668            currentVideoData.currentTime = data.currentTime || 0;
669            currentVideoData.duration = data.duration || 0;
670            if (data.currentTime >= data.duration && parseInt(getUserSetting('auto_next', '0'))) {
671                nextEpisode();
672            }
673            return;
674        }
675    }
676});
677
678function updateContinueWatching(animeId, episodeNumber, episodeId, time, duration) {
679    if (!isLoggedIn || !animeId || !episodeId || continueWatchingRequestInFlight) {
680        return;
681    }
682
683    time = parseInt(time, 10) || 0;
684    duration = parseInt(duration, 10) || 0;
685
686    if (duration > 0 && time < 5) {
687        return;
688    }
689
690    const continueWatchingKey = [
691        animeId,
692        episodeNumber || 0,
693        episodeId,
694        Math.floor(time),
695        duration
696    ].join(':');
697    if (continueWatchingKey === lastContinueWatchingKey) {
698        return;
699    }
700
701    lastContinueWatchingKey = continueWatchingKey;
702    continueWatchingRequestInFlight = true;
703
704    $.ajax({
705        url: user.rest_url + 'continue-watching',
706        type: 'POST',
707        contentType: 'application/json',
708        data: JSON.stringify({
709            anime_id: animeId,
710            ep_id: episodeId,
711            ep: episodeNumber,
712            time: time,
713            duration: duration
714        }),
715        success: (response) => {
716            if (response && response.success) {
717                console.log('Continue watching updated successfully');
718            } else {
719                console.error('Failed to update continue watching:', response);
720            }
721        },
722        error: (xhr, status, error) => {
723            console.error('REST error updating continue watching:', error);
724        },
725        complete: () => {
726            continueWatchingRequestInFlight = false;
727        }
728    });
729}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.