21/10/2025, 20:41:33
// ==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