Better bateworld.com

21/10/2025, 20:41:33

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @namespace   Circlejerk Scripts
// @name        Better bateworld.com
// @version     1.3.3
// @author      Thick Bro
// @match       https://bateworld.com//html5-chat/chat2/*
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_getValues
// @grant       GM_setValue
// @grant       GM_setValues
// @grant       GM_listValues
// @description 21/10/2025, 20:41:33
// @license GPL-3.0-or-later
// ==/UserScript==


;// CONCATENATED MODULE: ./src/betterbw/utils/sortFunctions.ts
const topNewest = (a, b)=>b.bias - a.bias || b.onlineSince - a.onlineSince;
const topRandom = (a, b)=>b.bias - a.bias || Math.random() - 0.5;

;// CONCATENATED MODULE: ./src/betterbw/styles/static.css?raw
const staticraw_namespaceObject = "body>div:nth-child(1) {\n  height: calc(100% - 4px);\n}\n\nbody:not([data-algo]) button[data-sort-order=\"\"],\nbody[data-algo=\"\"] button[data-sort-order=\"\"],\nbody[data-algo=\"new\"] button[data-sort-order=\"new\"],\nbody[data-algo=\"top\"] button[data-sort-order=\"top\"] {\n  color: white;\n  background-color: #50c180;\n}\n\n.panel-action {\n  position: absolute;\n  left: 3px;\n}\n\n.panel-action-btn {\n  cursor: pointer;\n  margin-left: 8px;\n  background: rgba(255, 255, 255, 0.8);\n  color: inherit;\n  font-size: 14px;\n\n  float: right;\n  position: relative;\n  border: 1px solid #DDD;\n  top: 2px;\n  height: 15px;\n  line-height: 0;\n  opacity: 1;\n  transition: opacity .2s ease-out;\n}\n\n.panel-action-btn:where([data-status-value=\"--\"],\n  [data-status-value=\"-\"],\n  [data-status-value=\"+\"],\n  [data-status-value=\"++\"]) {\n  margin-left: 0;\n}\n\n.jsPanel[data-grid-index]:not([data-grid-index=\"9\"]):not(.ui-draggable-dragging) {\n  transition:\n    top .15s linear,\n    right .15s linear,\n    bottom .15s linear,\n    left .15s linear,\n    box-shadow .2s ease-out;\n}\n\n.jsPanel:not(:hover) .panel-action-btn {\n  opacity: 0;\n}\n\n.jsPanel .speaks {\n  width: 3px;\n  rotate: 180deg;\n  top: 0;\n  bottom: 0;\n  left: 0;\n\n  .volume {\n    background: yellow;\n    box-shadow: 1px 1px 1px rgb(0, 0, 0), 2px 2px 1px rgb(255, 255, 255);\n  }\n}\n\n.jsPanel .jsPanel-content {\n  background: #111;\n}\n\n:where(#userMenu, .jsPanel)[data-status=\"--\"] [data-status-value=\"--\"] {\n  background: rgba(205, 0, 0, 1);\n  color: #fff;\n}\n\n:where(#userMenu, .jsPanel)[data-status=\"-\"] [data-status-value=\"-\"] {\n  background: rgba(237, 146, 0, 1);\n  color: #fff;\n}\n\n:where(#userMenu, .jsPanel)[data-status=\"+\"] [data-status-value=\"+\"] {\n  background: rgba(0, 167, 228, 1);\n  color: #fff;\n}\n\n:where(#userMenu, .jsPanel)[data-status=\"++\"] [data-status-value=\"++\"] {\n  background: rgba(149, 50, 255, 1);\n  color: #fff;\n}\n\n:where(#userMenu, .jsPanel)[data-is-cooldown=\"true\"] [data-status-value=\"⏱\"] {\n  background: rgba(50, 50, 50, 1);\n  color: #fff;\n}\n\n#userMenu[data-private-cam=\"true\"] [data-action=\"viewWebcam\"] {\n  color: red;\n\n  .fa:after {\n    content: \" \\f023\";\n    /* font-family: \"Font Awesome 5 Free\"; */\n    color: red;\n    font-weight: 900;\n  }\n}\n\n.jsPanel[data-status=\"undefined\"] .jsPanel-title {\n  text-decoration-color: orange;\n  text-decoration-line: underline;\n  text-decoration-style: solid;\n  text-decoration-thickness: 2px;\n}\n\n.jsPanel[data-status=\"undefined\"] button:where([data-status-value=\"++\"]) {\n  display: none;\n}\n\n.jsPanel[data-status=\"--\"] button:where([data-status-value=\"++\"]) {\n  display: none;\n}\n\n.jsPanel[data-status=\"-\"] button:where([data-status-value=\"++\"]) {\n  display: none;\n}\n\n.jsPanel[data-status=\"+\"] button:where([data-status-value=\"--\"]) {\n  display: none;\n}\n\n.jsPanel[data-status=\"++\"] button:where([data-status-value=\"--\"]) {\n  display: none;\n}\n\n.jsPanel:where([data-rotation=\"\"], [data-rotation=\"0\"]) video {\n  rotate: 0deg;\n}\n\n.jsPanel[data-rotation=\"90\"] video {\n  rotate: 90deg;\n}\n\n.jsPanel[data-rotation=\"180\"] video {\n  rotate: 180deg;\n}\n\n.jsPanel[data-rotation=\"270\"] video {\n  rotate: 270deg;\n}\n\n.slide_block {\n  width: 14px;\n}\n\n#usersContainer {\n  --container-width: 240px;\n  width: var(--container-width);\n\n  &.leftLayout #slide_block {\n    top: 50px;\n    left: 31px;\n    z-index: 101;\n    height: 37px;\n  }\n}\n\n#myWebcamContainer .btn {\n  text-wrap: inherit;\n  line-height: 1 !important;\n}\n\n#userList .userItem {\n  border-bottom: none;\n  margin-left: 1px;\n}\n\n#userList .userLabel {\n  top: 0;\n  margin-left: 2px;\n  padding-left: 8px;\n\n  border-top-left-radius: 9px;\n  border-bottom-left-radius: 9px;\n\n  .userSince {\n    left: unset;\n  }\n}\n\n.webcamBtn {\n  padding-inline: 1rem;\n  margin: 0;\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.webcamBtn i.lock {\n  &.fa-unlock {\n    display: none;\n  }\n\n  &.fa-lock {\n    text-shadow: -2px 2px 0 white;\n    right: -25px;\n    top: 1px;\n  }\n\n  position: absolute;\n  left: -11px;\n  top: 5px;\n  font-size: 16px;\n}\n\n.webcamBtn i.fa-volume-down {\n  padding-inline: 4px;\n}\n\n.eye-icon .fa-eye.isWatching {\n  /* color: #333 !important; */\n  color: gold !important;\n  position: absolute;\n  right: 42px;\n  bottom: 5px;\n  text-shadow: -1px -1px 1px goldenrod;\n}\n\n#userList .userItem:has(.webcamBtn.visible i.lock.fa-lock) {\n  --lock-color: rgba(205, 0, 0, 0.33);\n  --v-padding: 0px;\n\n  .userLabel {\n    box-shadow: inset 3px 0 1px 2px var(--lock-color);\n  }\n\n  .webcamBtn.visible {\n    box-shadow: inset -3px 0 1px 2px var(--lock-color);\n    border: none;\n  }\n}\n\n.jsPanel {\n  box-shadow: none;\n  border-color: #888 !important;\n}\n\n#roomsBtn {\n  line-height: 1 !important;\n}\n\n#header {\n  height: 30px !important;\n}\n\n#tabsAndFooter,\n#footer {\n  /* width: min(50%, 800px); */\n  width: min(50%, 642px);\n}\n\nvideo.mobile.mobile {\n  max-width: 100% !important;\n  max-height: 100% !important;\n}\n\n.jsPanel-headerbar {\n  min-height: 18px;\n}\n\n.jsPanel-titlebar {\n  min-height: 16px;\n}\n\n.jsPanel-titlebar h3 {\n  margin-block: 1px;\n}\n";
;// CONCATENATED MODULE: ./src/betterbw/features/cooldown.ts
// Cooldown helpers: store expiry timestamps (ms since epoch) using GM_setValue
function cooldownIt(param) {
    let { id, minutes = 15, panel } = param;
    var _jsPanel_activePanels_getPanel;
    const expiry = Date.now() + minutes * 60 * 1000;
    setCooldown(id, expiry);
    console.log(`User ${id} put on cooldown until`, new Date(expiry).toISOString());
    if (!panel) return;
    (_jsPanel_activePanels_getPanel = jsPanel.activePanels.getPanel(panel.id)) === null || _jsPanel_activePanels_getPanel === void 0 ? void 0 : _jsPanel_activePanels_getPanel.close();
}
function setCooldown(id, expiryMs) {
    GM_setValue(`${id}_cooldown`, expiryMs);
}
function getCooldownExpiry(id) {
    const val = GM_getValue(`${id}_cooldown`);
    return val ? Number(val) : null;
}
function isOnCooldown(id) {
    const expiry = getCooldownExpiry(id);
    return expiry && Date.now() < expiry;
}

;// CONCATENATED MODULE: ./src/betterbw/utils/organizePanels.ts

const base = {
    my: 'right-top',
    at: 'right-top'
};
const gridStyle = {
    marginTop: 30,
    marginRight: 5,
    gapX: 4,
    gapY: 4,
    width: 365,
    height: 318
};
const col = [
    -gridStyle.marginRight,
    -(gridStyle.marginRight + gridStyle.width + gridStyle.gapX),
    -(gridStyle.marginRight + (gridStyle.width + gridStyle.gapX) * 2),
    -(gridStyle.marginRight + (gridStyle.width + gridStyle.gapX) * 3)
];
const row = [
    gridStyle.marginTop,
    gridStyle.marginTop + gridStyle.height + gridStyle.gapY,
    gridStyle.marginTop + (gridStyle.height + gridStyle.gapY) * 2
];
const positions3x3plus1 = [
    ()=>({
            ...base,
            offsetX: col[0],
            offsetY: row[0]
        }),
    ()=>({
            ...base,
            offsetX: col[0],
            offsetY: row[1]
        }),
    ()=>({
            ...base,
            offsetX: col[1],
            offsetY: row[0]
        }),
    ()=>({
            ...base,
            offsetX: col[1],
            offsetY: row[1]
        }),
    ()=>({
            ...base,
            offsetX: col[2],
            offsetY: row[0]
        }),
    ()=>({
            ...base,
            offsetX: col[2],
            offsetY: row[1]
        }),
    ()=>({
            ...base,
            offsetX: col[0],
            offsetY: row[2]
        }),
    ()=>({
            ...base,
            offsetX: col[1],
            offsetY: row[2]
        }),
    ()=>({
            ...base,
            offsetX: col[2],
            offsetY: row[2]
        }),
    ()=>({
            ...base,
            offsetX: col[3],
            offsetY: 65
        })
];
function organizePanels() {
    let positions = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : positions3x3plus1;
    const opened = queryPanels();
    if (!opened.length) return;
    const GRID_SIZE = 10;
    const grid = Array(GRID_SIZE).fill(null);
    // Split the opened panels in two groups
    const groups = {
        prePositioned: [],
        newlyCreated: []
    };
    for (const panel of opened){
        const group = panel.dataset.gridIndex ? groups.prePositioned : groups.newlyCreated;
        group.push(panel);
    }
    // Put the pre-positioned panels back on the same index
    for (const panel of groups.prePositioned){
        if (panel.dataset.gridIndex == null || panel.dataset.gridIndex === '') continue;
        if (grid[+panel.dataset.gridIndex]) {
            groups.newlyCreated.unshift(panel);
        } else {
            grid[+panel.dataset.gridIndex] = panel;
        }
    }
    // Put the 10th user in a better position, if possible
    if (grid[9]) {
        const nextAvailableSlot = grid.indexOf(null);
        if (nextAvailableSlot > -1) {
            grid[9].dataset.gridIndex = nextAvailableSlot.toString();
            grid[nextAvailableSlot] = grid[9];
            grid[9] = null;
        }
    }
    // Position the new panels
    for (const panel of groups.newlyCreated){
        const nextAvailableSlot = grid.indexOf(null);
        if (nextAvailableSlot > -1) {
            grid[nextAvailableSlot] = panel;
            panel.dataset.gridIndex = nextAvailableSlot.toString();
        }
    }
    grid.map((panel)=>panel ? jsPanel.activePanels.getPanel(panel.id) : null).forEach((panel, idx, grid)=>{
        if (!panel) return;
        const positionFn = positions[idx];
        if (!positionFn) return;
        panel.resize({
            width: gridStyle.width,
            height: gridStyle.height
        }).reposition(positionFn(grid));
    });
}

;// CONCATENATED MODULE: ./src/betterbw/utils/scrappers.ts
function getUsername(panel) {
    var _Array_from_find_nodeValue, _Array_from_find;
    const title = panel.querySelector('.jsPanel-title');
    const username = (_Array_from_find = Array.from(title.childNodes).find((e)=>{
        var _e_nodeValue;
        return e.nodeType === Node.TEXT_NODE && ((_e_nodeValue = e.nodeValue) === null || _e_nodeValue === void 0 ? void 0 : _e_nodeValue.trim());
    })) === null || _Array_from_find === void 0 ? void 0 : (_Array_from_find_nodeValue = _Array_from_find.nodeValue) === null || _Array_from_find_nodeValue === void 0 ? void 0 : _Array_from_find_nodeValue.trim();
    const name = username === null || username === void 0 ? void 0 : username.split('_')[0];
    if (!name) console.error(`Couldn't find username on ${title.textContent}`);
    return name || 'FAIL';
}

;// CONCATENATED MODULE: ./src/betterbw/utils/openPanel.ts




const PANEL_SELECTOR = '.jsPanel.jsPanel-theme-default';
const queryPanels = ()=>document.querySelectorAll(PANEL_SELECTOR);
const queryPanel = (target)=>target.querySelector(PANEL_SELECTOR);
function tryToOpenPanel(/** @type {{item: HTMLDivElement, id: string, status: string, bias: number, onlineSince: number}} */ candidate) {
    // console.log('Trying to open panel for', candidate.id);
    candidate.item.querySelector('.webcamBtn').click();
}
const getCandidates = function() {
    let /** @type {function({item: HTMLDivElement, id: string, status: string, bias: number, onlineSince: number}, {item: HTMLDivElement, id: string, status: string, bias: number, onlineSince: number}): number} */ compareFn = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : topRandom, _biases = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
    const biases = {
        // "--": 0,
        '-': 1,
        undefined: 2,
        '+': 3,
        '++': 4,
        ..._biases
    };
    const userItems = document.querySelectorAll('#userList [data-status="online"][data-webcam="true"]:not(:has(.fa.fa-lock))');
    const entries = [];
    for (const item of userItems.values()){
        var _item_dataset_username, _item_querySelector;
        const id = (_item_dataset_username = item.dataset.username) === null || _item_dataset_username === void 0 ? void 0 : _item_dataset_username.split('_')[0];
        if (!id) continue;
        const status = GM_getValue(`${id}_status`);
        // skip users explicitly faded out
        if (status === '--') continue;
        // skip users currently on cooldown
        if (isOnCooldown(id)) continue;
        entries.push({
            item,
            id,
            status,
            bias: biases[status],
            onlineSince: parseInt(((_item_querySelector = item.querySelector('.userLabel [data-date]')) === null || _item_querySelector === void 0 ? void 0 : _item_querySelector.dataset.date) || '0')
        });
    }
    entries.sort(compareFn);
    return entries;
};
function openCandidates(candidates, limit) {
    const opened = queryPanels();
    let openedLength = opened.length || 0;
    if (openedLength >= 10) return console.log('10 panels already open');
    const maxToOpen = limit ? Math.min(openedLength + limit, 10) : 10;
    const openedIds = new Set(Array.from(opened).map((panel)=>getUsername(panel)));
    while(openedLength < maxToOpen && candidates.length > 0){
        const candidate = candidates.shift();
        if (openedIds.has(candidate.id)) continue;
        tryToOpenPanel(candidate);
        openedLength++;
    }
    organizePanels();
}

;// CONCATENATED MODULE: ./src/betterbw/utils/waitToBe.ts
function waitToBe(selector) {
    let attributeFilter = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [
        'aria-hidden'
    ], predicate = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : (el)=>el.getAttribute('aria-hidden') !== 'false';
    return new Promise((resolve)=>{
        let attrObserver = null;
        let domObserver = null;
        function cleanup() {
            if (attrObserver) {
                attrObserver.disconnect();
                attrObserver = null;
            }
            if (domObserver) {
                domObserver.disconnect();
                domObserver = null;
            }
        }
        function attachAttrObserver(el) {
            if (!el) return false;
            if (predicate(el)) {
                cleanup();
                resolve(el);
                return true;
            }
            // Watch for attribute changes
            attrObserver = new MutationObserver((muts)=>{
                for (const m of muts){
                    if (m.type === 'attributes' && m.attributeName && attributeFilter.includes(m.attributeName)) {
                        if (predicate(el)) {
                            cleanup();
                            resolve(el);
                            return;
                        }
                    }
                }
            });
            attrObserver.observe(el, {
                attributes: true,
                attributeFilter
            });
            return false;
        }
        // If element already exists, attach attribute observer
        const existing = document.querySelector(selector);
        if (attachAttrObserver(existing)) return;
        // Otherwise watch for element being added to the DOM
        domObserver = new MutationObserver((muts)=>{
            for (const m of muts){
                for (const node of m.addedNodes){
                    if (!(node instanceof HTMLElement)) continue;
                    const found = node.matches(selector) ? node : node.querySelector(selector);
                    if (!found) continue;
                    if (attachAttrObserver(found)) {
                        if (domObserver) {
                            domObserver.disconnect();
                            domObserver = null;
                        }
                        return;
                    }
                }
            }
        });
        domObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

;// CONCATENATED MODULE: ./src/betterbw/algo.ts
let algo_algo = '';
const getAlgo = ()=>algo_algo;
const setAlgo = (val)=>{
    document.body.dataset.algo = algo_algo = val;
};

;// CONCATENATED MODULE: ./src/betterbw/utils/formatters.ts
const dataUsername = (id)=>`[data-username="${id}"],[data-username^="${id}_"]`;

;// CONCATENATED MODULE: ./src/betterbw/styles/tiers.css?raw
const tiersraw_namespaceObject = "#tabs .userItem,\n#userList .userItem {\n  --text-decoration: line-through solid 1.8rem;\n  --decoration-opacity: 0.2;\n}\n\n.group_minus_minus {\n  #userList & {\n    opacity: 0.3 !important;\n  }\n\n  #tabs &:first-child {\n    text-decoration: var(--text-decoration) rgba(205, 0, 0, 0.15);\n  }\n}\n\n.group_minus {\n  #tabs &:first-child {\n    text-decoration: var(--text-decoration) rgba(237, 146, 0, var(--decoration-opacity));\n  }\n\n  #userList & .userLabel {\n    background: rgba(237, 146, 0, var(--decoration-opacity));\n  }\n}\n\n.group_plus {\n  #tabs &:first-child {\n    text-decoration: var(--text-decoration) rgba(0, 100, 255, var(--decoration-opacity));\n  }\n\n  #userList & .userLabel {\n    background: rgba(0, 100, 255, var(--decoration-opacity));\n  }\n}\n\n.group_plus_plus {\n  #tabs &:first-child {\n    text-decoration: var(--text-decoration) rgba(149, 50, 255, var(--decoration-opacity));\n  }\n\n  #userList & .userLabel {\n    background: rgba(149, 50, 255, var(--decoration-opacity));\n  }\n}\n";
;// CONCATENATED MODULE: ./src/betterbw/dynamicStyle.ts


const dynamicStyle_userItems = (group)=>`.userItem:where(${group.map(dataUsername).join(',')})`;
function getCSS() {
    const arrayOfKeys = GM_listValues().filter((key)=>key.match(/\.*?_status/));
    const values = GM_getValues(arrayOfKeys);
    // Group users
    const groups = {
        '--': [],
        '-': [],
        '+': [],
        '++': []
    };
    for (const [key, value] of Object.entries(values)){
        const id = key.split('_')[0];
        groups[value].push(id);
    }
    // Generate css selectors
    const selectors = {
        group_minus_minus: dynamicStyle_userItems(groups['--']),
        group_minus: dynamicStyle_userItems(groups['-']),
        group_plus: dynamicStyle_userItems(groups['+']),
        group_plus_plus: dynamicStyle_userItems(groups['++'])
    };
    return tiersraw_namespaceObject.replace(/\.(group_.*?)( {)/g, (_match, key, openStyle)=>selectors[key] + openStyle);
}
const dynamicStyle = GM_addStyle(getCSS());
const refreshDynamicStyle = async ()=>dynamicStyle.innerHTML = getCSS();

;// CONCATENATED MODULE: ./src/betterbw/features/rotateCam.ts
function getRotation(element) {
    if (!element || !element.dataset.rotation) return 0;
    return parseInt(element.dataset.rotation, 10) || 0;
}
function rotateCam(param) {
    let { id, panel } = param;
    const currentRotation = getRotation(panel);
    const newRotation = (currentRotation + 90) % 360;
    panel.dataset.rotation = newRotation;
    GM_setValue(`${id}_rotation`, newRotation);
}

;// CONCATENATED MODULE: ./src/betterbw/features/panelActions.tsx




// Action definitions: label and handler per action
const PANEL_ACTIONS = [
    {
        icon: '--',
        label: 'Nope\nDo not suggest this person.',
        handler: (param)=>{
            let { id, panel } = param;
            var _jsPanel_activePanels_getPanel;
            GM_setValue(`${id}_status`, '--');
            refreshDynamicStyle();
            if (!panel) return;
            panel.dataset.status = '--';
            (_jsPanel_activePanels_getPanel = jsPanel.activePanels.getPanel(panel.id)) === null || _jsPanel_activePanels_getPanel === void 0 ? void 0 : _jsPanel_activePanels_getPanel.close();
        }
    },
    {
        icon: '-',
        label: 'So so\nIt depends on the day, on the mood...',
        handler: (param)=>{
            let { id, panel } = param;
            GM_setValue(`${id}_status`, '-');
            refreshDynamicStyle();
            if (!panel) return;
            panel.dataset.status = '-';
        }
    },
    {
        icon: '+',
        label: 'Yeah\nI liked you, buddy',
        handler: (param)=>{
            let { id, panel } = param;
            GM_setValue(`${id}_status`, '+');
            refreshDynamicStyle();
            if (!panel) return;
            panel.dataset.status = '+';
        }
    },
    {
        icon: '++',
        label: 'Ohhh Yeah!\nI liked you a lot, buddy!',
        handler: (param)=>{
            let { id, panel } = param;
            GM_setValue(`${id}_status`, '++');
            refreshDynamicStyle();
            if (!panel) return;
            panel.dataset.status = '++';
        }
    },
    {
        icon: '⟳',
        label: 'Rotate',
        handler: (param)=>{
            let { id, panel } = param;
            if (!panel) return;
            rotateCam({
                id,
                panel
            });
        }
    },
    {
        icon: '⏱',
        label: 'Cooldown 15m\nDo not suggest this person for the next 15 minutes',
        handler: cooldownIt
    }
].reverse();
function getPanelActions(data) {
    return PANEL_ACTIONS.map((action)=>{
        const btn = document.createElement('button');
        btn.dataset.statusValue = action.icon;
        btn.textContent = action.icon;
        btn.title = action.label;
        btn.className = 'panel-action-btn';
        btn.addEventListener('click', (e)=>{
            e.stopPropagation();
            action.handler(data);
        });
        return btn;
    });
}
function getMenuActions() {
    return PANEL_ACTIONS.filter((action)=>![
            'Rotate'
        ].includes(action.label)).map((action)=>{
        const btn = document.createElement('button');
        btn.dataset.statusValue = action.icon;
        btn.textContent = action.icon;
        btn.title = action.label;
        btn.className = 'panel-action-btn';
        btn.addEventListener('click', (e)=>{
            e.stopPropagation();
            const id = getLatestUser();
            if (id != null) action.handler({
                id,
                panel: null
            });
        });
        return btn;
    });
}
function getLatestUser() {
    var _muteItem_textContent_split_at;
    try {
        return chatHTML5.myUser.id.split('_')[0];
    } catch (e) {
        console.error('Oooops...');
    }
    const muteItem = document.querySelector('#userMenu [data-action="mute"]');
    if (!muteItem) return;
    const username = (_muteItem_textContent_split_at = muteItem.textContent.split(' ').at(-1)) === null || _muteItem_textContent_split_at === void 0 ? void 0 : _muteItem_textContent_split_at.split('_').at(0);
    return username;
}
// Attach control buttons for each declared action
function attachPanelActions(panel) {
    var _panel_querySelector;
    if (panel.dataset.actionsAttached === '1') return;
    const panelJS = jsPanel.activePanels.getPanel(panel.id);
    if (!panelJS) return console.warn('Panel not found for', panel.id);
    panelJS.resize({
        width: 365,
        height: 318
    });
    const header = panel.querySelector('.jsPanel-hdr .jsPanel-title');
    if (!header) return;
    const id = getUsername(panel);
    panel.dataset.username = id;
    panel.dataset.status = GM_getValue(`${id}_status`);
    const actions = document.createElement('div');
    actions.className = 'panel-action';
    getPanelActions({
        id,
        panel
    }).forEach((action)=>actions.appendChild(action));
    header.appendChild(actions);
    panel.dataset.actionsAttached = '1';
    panel.dataset.rotation = GM_getValue(`${id}_rotation`);
    (_panel_querySelector = panel.querySelector('.jsPanel-btn.jsPanel-btn-close')) === null || _panel_querySelector === void 0 ? void 0 : _panel_querySelector.addEventListener('click', ()=>{
        cooldownIt({
            id,
            minutes: 1
        });
    });
    const video = panel.querySelector('video');
    if (video) video.volume = 0.08;
}
function cleanupPanel(panel) {}

;// CONCATENATED MODULE: ./src/betterbw/utils/spyOn.ts
function spyOn(obj, key, callback) {
    // Define the handler for intercepting the set operation on selectedUserid
    const handler = {
        set (target, prop, value) {
            // If the `key` changed, trigger the callback
            if (prop === key && target[prop] !== value) {
                callback(value);
            }
            // Proceed with the default behavior of setting the property
            target[prop] = value;
            return true;
        }
    };
    // Create a proxy to observe the changes
    return new Proxy(obj, handler);
}

;// CONCATENATED MODULE: ./src/betterbw/styles/openPanels.css?raw
const openPanelsraw_namespaceObject = ".userItem:where([data-username=\"I_am_watching\"]) {\n\n  /* Turn webcam button green, on the sidebar */\n  #userList & .webcamBtn {\n    background: rgba(80, 206, 133, 1) !important;\n  }\n\n  /* Put a \"panel\" icon close the user's name */\n  #tabs &:before {\n    content: '';\n    display: block;\n    position: absolute;\n    width: 12px;\n    height: 11px;\n    top: 50%;\n    transform: translateY(-50%) translateX(-120%);\n    border-radius: 2px;\n    border: rgba(178, 178, 178, 1) solid 1px;\n    border-top-width: 3px;\n  }\n}\n\n.watchingMe,\n.user_watching_me {\n  --gridGap: 4px;\n  box-shadow: #FFD700 0px 0px 1px var(--gridGap) !important;\n  transition: box-shadow .2s ease-out;\n\n  & .jsPanel-headerbar {\n    background: gold;\n    background: linear-gradient(gold 0%, transparent 30%, transparent 70%, gold 85%);\n  }\n\n  & .jsPanel-title::before {\n    content: \"👁\u{fe0f}\";\n    content: \"\\f06e\";\n    font-family: \"Font Awesome 5 Free\";\n    font-weight: 400;\n\n    color: gold;\n    position: absolute;\n    z-index: 1;\n    left: -2px;\n    top: -5px;\n    text-shadow: 1px 1px 0 goldenrod;\n    font-size: 14px;\n  }\n}\n";
;// CONCATENATED MODULE: ./src/betterbw/dynamicOpenedStyle.ts




const dynamicOpenedStyle = GM_addStyle('');
function updateCssForOpenedPanels() {
    const opened = queryPanels();
    if (!(opened === null || opened === void 0 ? void 0 : opened.length)) return;
    const usernames = Array.from(opened).map((panel)=>getUsername(panel));
    const selectorsIamWatching = usernames.map(dataUsername).join(',');
    const selectorsWatchingMe = usernames.map((id)=>`body:has(#userList .userItem:where(${dataUsername(id)}) .eye-icon .isWatching) .jsPanel[data-username="${id}"]`).join(', ');
    dynamicOpenedStyle.innerHTML = openPanelsraw_namespaceObject.replace(/\[data-username="I_am_watching"\]/g, selectorsIamWatching).replace(/\.user_watching_me/g, selectorsWatchingMe);
}

;// CONCATENATED MODULE: ./src/betterbw/features/observeOpenPanels.ts





function iterate(nodes, selector, fn) {
    let found = false;
    for (const node of nodes){
        if (!(node instanceof HTMLElement)) continue;
        const panels = node.matches(selector) ? [
            node
        ] : node.querySelectorAll(selector);
        if (!panels.length) continue;
        panels.forEach(fn);
        found = true;
    }
    return found;
}
let observeOpenPanels_nextUser = null;
const setNextCam = (val)=>observeOpenPanels_nextUser = val;
function observePanels() {
    let selector = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : PANEL_SELECTOR;
    // Observe DOM for dynamic panels
    const observerPanels = new MutationObserver((mutations)=>{
        let nodesAdded = false;
        let nodesRemoved = false;
        for (const mutation of mutations){
            if (iterate(mutation.addedNodes, selector, attachPanelActions)) nodesAdded = true;
            if (iterate(mutation.removedNodes, selector, cleanupPanel)) nodesRemoved = true;
        }
        if (nodesAdded || nodesRemoved) organizePanels();
        if (nodesRemoved) {
            if (observeOpenPanels_nextUser) {
                var _nextUser_querySelector;
                (_nextUser_querySelector = observeOpenPanels_nextUser.querySelector('.webcamBtn.visible')) === null || _nextUser_querySelector === void 0 ? void 0 : _nextUser_querySelector.click();
                observeOpenPanels_nextUser = null;
            } else {
                const algo = getAlgo();
                if (algo) {
                    setTimeout(()=>{
                        var _document_querySelector;
                        (_document_querySelector = document.querySelector(`button[data-sort-order="${algo}"]`)) === null || _document_querySelector === void 0 ? void 0 : _document_querySelector.click();
                    }, 32);
                }
            }
        }
        if (nodesAdded || nodesRemoved) updateCssForOpenedPanels();
    });
    observerPanels.observe(document.body, {
        childList: true,
        subtree: false
    });
    return {
        teardown: ()=>{}
    };
}

;// CONCATENATED MODULE: ./src/betterbw/setupTools.ts








function setupHeader() {
    const header = document.querySelector('#header .header-custom-btns');
    if (!header) return;
    {
        const button = document.createElement('button');
        button.textContent = 'ø';
        button.dataset.sortOrder = '';
        button.title = 'Organize open panels and stop the algorithm';
        button.addEventListener('click', ()=>{
            organizePanels();
            setAlgo('');
        });
        header.prepend(button);
    }
    {
        const button = document.createElement('button');
        button.textContent = 'New';
        button.dataset.sortOrder = 'new';
        button.title = 'Prioritize recently online';
        button.addEventListener('click', ()=>{
            setAlgo('new');
            openCandidates(getCandidates(topRandom, {
                undefined: 9,
                '+': 8
            }));
        });
        header.prepend(button);
    }
    {
        const button = document.createElement('button');
        button.textContent = 'Top';
        button.dataset.sortOrder = 'top';
        button.title = 'Prioritize users, then randomize the order';
        button.addEventListener('click', ()=>{
            setAlgo('top');
            openCandidates(getCandidates(topRandom));
        });
        header.prepend(button);
    }
}
function setupUserMenu() {
    const userMenu = document.querySelector('#userMenu');
    if (!userMenu) return;
    getMenuActions().forEach((action)=>userMenu.appendChild(action));
    chatHTML5.myUser = spyOn(chatHTML5.myUser, 'selectedUserid', (selectedUserid)=>{
        const el = document.querySelector(`:where(#userList,#tabs) .userItem[data-id="${selectedUserid}"]`);
        if (!el || !el.dataset.username) return;
        const id = el.dataset.username.split('_')[0];
        userMenu.dataset.username = id;
        userMenu.dataset.status = GM_getValue(`${id}_status`);
        userMenu.dataset.isCooldown = Boolean(isOnCooldown(id)).toString();
        userMenu.dataset.privateCam = Boolean(document.querySelector(`#userList .userItem[data-id="${selectedUserid}"]:has(.webcamBtn.visible i.lock.fa-lock)`)).toString();
    });
}
function setupSidebar() {
    $(document.getElementById('userList')).on('click', '.webcamBtn', function(event) {
        const opened = queryPanels();
        if (opened.length < 10) return;
        const nextUser = $(this).closest('.userItem')[0];
        if (nextUser) {
            var _jsPanel_activePanels_getPanel;
            // event.preventDefault();
            event.stopPropagation();
            // event.stopImmediatePropagation();
            setNextCam(nextUser);
            const panel = document.querySelector('[data-grid-index="9"]');
            if (panel) (_jsPanel_activePanels_getPanel = jsPanel.activePanels.getPanel(panel.id)) === null || _jsPanel_activePanels_getPanel === void 0 ? void 0 : _jsPanel_activePanels_getPanel.close();
        }
    });
}
function setupTools() {
    var _document_querySelector;
    setupHeader();
    setupUserMenu();
    setupSidebar();
    chatHTML5.config['timeBeforeWatchingCamAgain'] = '1000';
    chatHTML5.config['checkOwnStream'] = '1';
    chatHTML5.config['showCountryFlag'] = '1';
    (_document_querySelector = document.querySelector('#sortWebcamtBtn')) === null || _document_querySelector === void 0 ? void 0 : _document_querySelector.click();
}

;// CONCATENATED MODULE: ./src/betterbw/betterbw.user.ts






GM_addStyle(staticraw_namespaceObject);
const whenRoomsModalClosed = Promise.resolve().then(()=>waitToBe('#roomsModal', [
        'aria-hidden'
    ], (el)=>el.getAttribute('aria-hidden') === 'false')).then(()=>waitToBe('#roomsModal', [
        'aria-hidden'
    ], (el)=>el.getAttribute('aria-hidden') !== 'false'));
whenRoomsModalClosed.then(()=>{
    setupTools();
    observePanels();
    openCandidates(getCandidates(topRandom));
});

;// CONCATENATED MODULE: ./src/betterbw/index.ts