FormerlyUtilify is a natively dark-themed addon that aims to focus on practical use.
// ==UserScript==
// @name BlackStar
// @namespace Debloated Xserver utility script.
// @gh https://github.com/yourmelie/FormerlyUtilify
// @version 1.7.3
// @description FormerlyUtilify is a natively dark-themed addon that aims to focus on practical use.
// @author Simon.
// @credits Death Wolf., ReZa, Sorry, Raptor, TunA, Comenxo, Zpayer.
// @match *://*.kogama.com/*
// @match *://*.kogama.com.br/*
// @match *://kogama.com.br/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
"use strict";
const CSS_ID = "BlackStarCSS";
let sheet;
const CSS = `
:root {
--color-primary: #C94838;
--color-primary-rgb: 201, 72, 56;
--color-accent: #e0c4a0;
--color-text: #c9a882;
--color-text-muted: #a8adb3;
--color-text-alt: #b89070;
--color-bg-main: #080608;
--color-bg-panel: #0c0809;
--color-danger: #c06060;
--color-danger-rgb: 180, 40, 40;
--color-danger-dark: #8a5050;
--color-danger-dark-rgb: 160, 40, 40;
--color-black-rgb: 0, 0, 0;
--color-white-rgb: 255, 255, 255;
--rad-sm: 4px;
--rad-md: 7px;
--rad-lg: 9px;
--rad-xl: 11px;
--rad-panel: 12px;
--rad-pill: 20px;
--trans-base: 0.2s;
--trans-smooth: 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--trans-fast: 0.22s cubic-bezier(0.4, 0, 0.2, 1);
--trans-bounce: 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
body#root-page-mobile, #root, #root * {
--Paper-overlay: linear-gradient(rgba(var(--color-black-rgb), 0.45), rgba(var(--color-black-rgb), 0.45)) !important;
--variant-containedBg: rgba(var(--color-black-rgb), 0.45) !important;
}
#root a, .css-1hitfzb {
color: var(--color-primary) !important;
text-decoration: none !important;
transition: color var(--trans-smooth) !important;
}
#root a:hover, .css-1hitfzb:hover { color: var(--color-accent) !important; }
#root * { scrollbar-width: none !important; }
#root *::-webkit-scrollbar { display: none !important; }
._1RMYS, ._1Cwd5 { display: none !important; }
.MuiCollapse-root._2Nols { display: none !important; }
._4OXDk { display: none !important; }
body#root-page-mobile {
background-color: var(--color-bg-main) !important;
background-image: radial-gradient(ellipse at center, #120e0f 0%, #090607 50%, #040304 100%) !important;
background-attachment: fixed !important;
}
.css-1qhuzmd {
color: var(--color-primary) !important;
}
.css-1995t1d, .css-e5yc1l {
background-color: transparent !important;
background: linear-gradient(rgba(var(--color-black-rgb), 0.21), rgba(var(--color-black-rgb), 0.21)) !important;
}
.css-z05bui {
background-color: transparent !important;
background: linear-gradient(rgba(var(--color-black-rgb), 0.47), rgba(var(--color-black-rgb), 0.47)) !important;
backdrop-filter: blur(17px) !important;
border-radius: var(--rad-xl) !important;
}
._1q4mD ._1sUGu ._1u05O background-color: transparent !important; }
._33DXe { background-image: none !important; }
.css-o4yc28 { background-color: transparent !important; border-radius: var(--rad-xl) !important; }
.CgH1- ._2LT6y { display: none !important; }
._13UrL._1F5Kt.CgH1-._2Hovk { width: auto !important; }
.CgH1- {
display: flex !important;
justify-content: left !important;
flex-wrap: wrap !important;
gap: 2px !important;
}
.CgH1- .MuiCard-root {
display: inline-flex !important;
background: none !important;
box-shadow: none !important;
border-radius: 0 !important;
min-width: unset !important;
width: auto !important;
margin: 13px !important;
}
.CgH1- .MuiCardContent-root {
padding: 0 !important;
display: flex !important;
gap: 2px !important;
align-items: baseline !important;
}
.CgH1- ._2fSqj,
.CgH1- ._2LT6y { font-size: 0.82em !important; line-height: 1 !important; }
.CgH1- ._2LT6y::before { content: "·" !important; margin-right: 4px !important; opacity: 0.4 !important; }
.css-em33cn {
background-color: transparent !important;
background: linear-gradient(rgba(var(--color-black-rgb), 0.47), rgba(var(--color-black-rgb), 0.47)) !important;
}
.css-wog98n {
background: linear-gradient(rgba(var(--color-black-rgb), 0.45), rgba(var(--color-black-rgb), 0.45)) !important;
background-color: transparent !important;
}
.css-e8xqt2 {
--variant-containedBg: rgba(var(--color-black-rgb), 0.45) !important;
transition: background-color 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) !important;
}
.css-e8xqt2:hover {
--variant-containedBg: rgba(30,10,10,0.7) !important;
background-color: rgba(30,10,10,0.7) !important;
}
.css-179vt2a { font-size: 0.670rem !important; opacity: 0.3 !important; transition: opacity 0.4s !important; }
.css-179vt2a:hover { opacity: 1 !important; }
._1pEP2, .tRx6U, .css-1atgupc { display: none !important; }
._3TORb {
background-color: transparent !important;
background: linear-gradient(rgba(var(--color-black-rgb), 0.21), rgba(var(--color-black-rgb), 0.21)) !important;
}
.zUJzi, ._375XK, ._375XK ._2drTe, .zUJzi .o_DA6 .uwn5j {
border: none !important;
background: rgba(var(--color-black-rgb), 0.3) !important;
backdrop-filter: blur(6px) !important;
}
.zUJzi .o_DA6 .uwn5j ._3DYYr:hover {
background: rgba(var(--color-primary-rgb), 0.08) !important;
transition: background var(--trans-fast) !important;
}
._375XK .F3PyX {
border: none !important;
}
.zUJzi .o_DA6 .uwn5j ._3DYYr._2dPu4 {
background: rgba(var(--color-primary-rgb), 0.12) !important;
border-left: 2px solid rgba(var(--color-primary-rgb), 0.7) !important;
padding-left: 12px !important;
transition: background var(--trans-fast), border-color var(--trans-fast) !important;
}
.uwn5j ._3DYYr ._1j2Cd {
filter: blur(5px) !important;
transition: filter var(--trans-smooth) !important;
}
.uwn5j ._3DYYr:hover ._1j2Cd {
filter: blur(0px) !important;
}
.zUJzi ._2BvOT ._375XK textarea {
background: transparent !important;
color: var(--color-text) !important;
caret-color: var(--color-primary) !important;
transition: color var(--trans-base) !important;
}
.zUJzi ._2BvOT ._375XK textarea::placeholder {
color: rgba(var(--color-primary-rgb), 0.3) !important;
}
.zUJzi ._2BvOT ._375XK textarea:focus {
color: var(--color-accent) !important;
}
._375XK ._2XaOw ._1j2Cd p {
background: rgba(var(--color-black-rgb), 0.21) !important;
border: 1px solid rgba(var(--color-primary-rgb), 0.18) !important;
border-radius: var(--rad-lg) !important;
backdrop-filter: blur(10px) !important;
padding: 10px 14px !important;
color: var(--color-text-muted) !important;
position: relative !important;
transition: transform var(--trans-fast), border-color var(--trans-fast), background var(--trans-fast) !important;
}
._375XK ._2XaOw ._1j2Cd p:hover {
transform: translateX(5px) !important;
border-color: rgba(var(--color-primary-rgb), 0.35) !important;
background: rgba(var(--color-black-rgb), 0.32) !important;
}
._375XK ._2XaOw ._1j2Cd p::before {
content: "Them" !important;
position: absolute !important;
top: -10px !important;
left: 8px !important;
font-size: 6px !important;
font-weight: 600 !important;
color: rgba(var(--color-primary-rgb), 0.6) !important;
text-transform: uppercase !important;
letter-spacing: 2.5px !important;
padding: 2px 6px !important;
border-radius: var(--rad-sm) !important;
z-index: 10 !important;
}
._375XK ._2XaOw ._1j2Cd._1Xzzq p {
background: rgba(var(--color-primary-rgb), 0.08) !important;
border: 1px solid rgba(var(--color-primary-rgb), 0.25) !important;
border-radius: var(--rad-lg) !important;
backdrop-filter: blur(10px) !important;
padding: 10px 14px !important;
color: var(--color-text) !important;
position: relative !important;
transition: transform var(--trans-fast), border-color var(--trans-fast), background var(--trans-fast) !important;
}
._375XK ._2XaOw ._1j2Cd._1Xzzq p:hover {
transform: translateX(-5px) !important;
border-color: rgba(var(--color-primary-rgb), 0.5) !important;
background: rgba(var(--color-primary-rgb), 0.15) !important;
}
._375XK ._2XaOw ._1j2Cd._1Xzzq p::before {
content: "You" !important;
position: absolute !important;
top: -10px !important;
right: 8px !important;
font-size: 6px !important;
font-weight: 600 !important;
color: rgba(var(--color-primary-rgb), 0.9) !important;
text-transform: uppercase !important;
letter-spacing: 2.5px !important;
padding: 2px 6px !important;
border-radius: var(--rad-sm) !important;
z-index: 10 !important;
}
@keyframes tm-fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.tm-info-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
animation: tm-fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.tm-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 9px;
border: 1px solid rgba(var(--color-primary-rgb), 0.2);
border-radius: var(--rad-pill);
font-size: 11px;
font-weight: 500;
color: var(--color-text);
white-space: nowrap;
cursor: pointer;
transition: border-color var(--trans-base), color var(--trans-base), transform var(--trans-base);
line-height: 1.3;
}
.tm-badge:hover {
border-color: rgba(var(--color-primary-rgb), 0.5);
color: var(--color-accent);
transform: translateY(-1px);
}
.tm-badge.tm-expanded { white-space: normal; word-break: break-word; max-width: 320px; }
.tm-badge-full { display: none; color: var(--color-text-alt); font-size: 10.5px; }
.tm-link {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 9px;
border: 1px solid rgba(var(--color-primary-rgb), 0.18);
border-radius: var(--rad-pill);
font-size: 11px;
font-weight: 500;
color: var(--color-text);
text-decoration: none !important;
white-space: nowrap;
transition: border-color var(--trans-base), color var(--trans-base), transform var(--trans-base);
line-height: 1.3;
}
.tm-link:hover {
border-color: rgba(var(--color-primary-rgb), 0.45);
color: var(--color-accent) !important;
transform: translateY(-1px);
}
.tm-icon { width: 13px; height: 13px; fill: currentColor; opacity: 0.75; flex-shrink: 0; }
.tm-level { font-size: 12px; font-weight: 600; opacity: 0.6; margin-top: 2px; color: var(--color-text); }
._13UrL ._23KvS ._25Vmr ._2IqY6 ._2O_AH { margin-top: 4px !important; }
.bs-feed-card {
cursor: pointer !important;
transition: color 0.3s !important;
}
.bs-feed-card ._2fSqj { color: var(--color-primary) !important; }
.bs-feed-card:hover ._2fSqj { color: var(--color-accent) !important; }
#bs-feed-overlay {
position: fixed;
inset: 0;
background: rgba(var(--color-black-rgb), 0.72);
backdrop-filter: blur(5px);
z-index: 100000;
display: none;
align-items: center;
justify-content: center;
}
#bs-feed-overlay.open { display: flex; }
#bs-feed-panel {
width: min(740px, 93vw);
max-height: 84vh;
background: var(--color-bg-panel);
border: 1px solid rgba(var(--color-primary-rgb), 0.18);
border-radius: var(--rad-panel);
box-shadow: 0 20px 70px rgba(var(--color-black-rgb), 0.85), inset 0 1px 0 rgba(var(--color-primary-rgb), 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
font-family: inherit;
animation: tm-fadeIn var(--trans-fast);
}
#bs-feed-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
border-bottom: 1px solid rgba(var(--color-primary-rgb), 0.12);
background: rgba(var(--color-black-rgb), 0.3);
flex-shrink: 0;
}
#bs-feed-title {
font-size: 10px;
font-weight: 600;
letter-spacing: 3px;
text-transform: uppercase;
color: rgba(var(--color-primary-rgb), 0.6);
}
.bs-feed-hbtn {
padding: 6px 14px;
background: rgba(var(--color-primary-rgb), 0.08);
border: 1px solid rgba(var(--color-primary-rgb), 0.2);
border-radius: var(--rad-md);
color: var(--color-text);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: background var(--trans-base), border-color var(--trans-base), color var(--trans-base), transform 0.15s;
font-family: inherit;
}
.bs-feed-hbtn:hover {
background: rgba(var(--color-primary-rgb), 0.16);
border-color: rgba(var(--color-primary-rgb), 0.4);
color: var(--color-accent);
transform: translateY(-1px);
}
.bs-feed-hbtn:disabled { opacity: 0.35; cursor: not-allowed; transform: none; }
.bs-feed-hbtn.danger {
background: rgba(var(--color-danger-rgb), 0.1);
border-color: rgba(var(--color-danger-rgb), 0.25);
color: var(--color-danger);
}
.bs-feed-hbtn.danger:hover {
background: rgba(var(--color-danger-rgb), 0.2);
border-color: rgba(var(--color-danger-rgb), 0.45);
color: #e08080;
}
#bs-feed-content {
flex: 1;
overflow-y: auto;
padding: 18px 20px;
}
#bs-feed-content::-webkit-scrollbar { display: none; }
.bs-feed-item {
background: rgba(var(--color-white-rgb), 0.02);
border: 1px solid rgba(var(--color-primary-rgb), 0.08);
border-radius: var(--rad-lg);
padding: 14px 16px;
margin-bottom: 10px;
transition: border-color var(--trans-base), background var(--trans-base);
}
.bs-feed-item:hover { border-color: rgba(var(--color-primary-rgb), 0.18); background: rgba(var(--color-white-rgb), 0.03); }
.bs-feed-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.bs-feed-date {
font-size: 10px;
color: rgba(var(--color-primary-rgb), 0.5);
letter-spacing: 0.03em;
}
.bs-feed-type {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: rgba(var(--color-primary-rgb), 0.4);
padding: 2px 7px;
border: 1px solid rgba(var(--color-primary-rgb), 0.15);
border-radius: var(--rad-sm);
}
.bs-feed-text {
font-size: 13px;
color: var(--color-text-muted);
line-height: 1.55;
margin-bottom: 10px;
word-break: break-word;
}
.bs-feed-text a { color: var(--color-primary) !important; }
.bs-feed-text a:hover { color: var(--color-accent) !important; }
.bs-feed-actions {
display: flex;
gap: 6px;
margin-top: 8px;
flex-wrap: wrap;
}
.bs-feed-btn {
padding: 4px 11px;
font-size: 11px;
border-radius: 6px;
border: 1px solid rgba(var(--color-primary-rgb), 0.15);
background: transparent;
color: var(--color-text-muted);
cursor: pointer;
transition: background 0.18s, border-color 0.18s, color 0.18s;
font-family: inherit;
}
.bs-feed-btn:hover {
background: rgba(var(--color-primary-rgb), 0.1);
border-color: rgba(var(--color-primary-rgb), 0.3);
color: var(--color-text);
}
.bs-feed-btn.del {
border-color: rgba(var(--color-danger-dark-rgb), 0.2);
color: var(--color-danger-dark);
}
.bs-feed-btn.del:hover {
background: rgba(var(--color-danger-dark-rgb), 0.12);
border-color: rgba(var(--color-danger-dark-rgb), 0.4);
color: var(--color-danger);
}
.bs-comments {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(var(--color-primary-rgb), 0.08);
display: none;
}
.bs-comments.open { display: block; }
.bs-comment {
background: rgba(var(--color-black-rgb), 0.2);
border: 1px solid rgba(var(--color-white-rgb), 0.04);
border-radius: var(--rad-md);
padding: 9px 12px;
margin-bottom: 7px;
font-size: 12px;
color: var(--color-text-muted);
display: flex;
gap: 10px;
align-items: flex-start;
}
.bs-comment-body { flex: 1; }
.bs-comment-author {
font-size: 11px;
font-weight: 600;
color: var(--color-primary);
margin-bottom: 3px;
}
.bs-comment-del {
background: none;
border: none;
color: rgba(var(--color-danger-dark-rgb), 0.4);
font-size: 14px;
cursor: pointer;
line-height: 1;
padding: 0;
transition: color 0.15s;
flex-shrink: 0;
}
.bs-comment-del:hover { color: var(--color-danger); }
.bs-comment-compose {
display: flex;
gap: 7px;
margin-top: 8px;
}
.bs-comment-input {
flex: 1;
padding: 7px 11px;
background: rgba(var(--color-black-rgb), 0.3);
border: 1px solid rgba(var(--color-primary-rgb), 0.15);
border-radius: var(--rad-md);
color: var(--color-text);
font-size: 12px;
font-family: inherit;
outline: none;
transition: border-color var(--trans-base);
}
.bs-comment-input:focus { border-color: rgba(var(--color-primary-rgb), 0.35); }
.bs-comment-input::placeholder { color: rgba(var(--color-primary-rgb), 0.3); }
.bs-empty { text-align: center; padding: 40px 20px; font-size: 13px; color: rgba(var(--color-primary-rgb), 0.3); font-style: italic; }
.bs-loading { text-align: center; padding: 30px; font-size: 12px; color: rgba(var(--color-primary-rgb), 0.4); letter-spacing: 0.05em; }
@keyframes bs-fb-in {
from { opacity: 0; transform: translateY(3px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes bs-fb-ripple {
from { transform: scale(0); opacity: 0.35; }
to { transform: scale(2.8); opacity: 0; }
}
@keyframes bs-fb-sent-glow {
0% { box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0.45); }
60% { box-shadow: 0 0 0 7px rgba(var(--color-primary-rgb), 0); }
100% { box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0); }
}
.bs-friend-wrap {
position: relative;
display: inline-flex;
animation: bs-fb-in 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.bs-friend-btn {
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
gap: 7px;
padding: 6px 15px 6px 12px;
background: rgba(var(--color-primary-rgb), 0.08);
border: 1px solid rgba(var(--color-primary-rgb), 0.25);
border-radius: 8px;
color: var(--color-text);
font-size: 12px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
letter-spacing: 0.02em;
white-space: nowrap;
transition:
background var(--trans-fast),
border-color var(--trans-fast),
color var(--trans-fast),
transform var(--trans-bounce),
opacity 0.18s ease;
user-select: none;
}
.bs-friend-btn:hover:not(:disabled) {
background: rgba(var(--color-primary-rgb), 0.15);
border-color: rgba(var(--color-primary-rgb), 0.5);
color: var(--color-accent);
transform: translateY(-1px);
}
.bs-friend-btn:active:not(:disabled) { transform: scale(0.97) translateY(0); }
.bs-friend-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.bs-friend-btn.bs-sent {
background: rgba(var(--color-primary-rgb), 0.05);
border-color: rgba(var(--color-primary-rgb), 0.2);
color: rgba(var(--color-primary-rgb), 0.55);
animation: bs-fb-sent-glow 0.55s ease-out;
}
.bs-friend-btn.bs-sent:hover:not(:disabled) {
background: rgba(var(--color-danger-dark-rgb), 0.12);
border-color: rgba(var(--color-danger-dark-rgb), 0.35);
color: var(--color-danger);
}
.bs-friend-btn.bs-busy { pointer-events: none; opacity: 0.5; }
.bs-friend-btn .bs-fb-ripple {
position: absolute;
border-radius: 50%;
width: 60px;
height: 60px;
margin-top: -30px;
margin-left: -30px;
background: rgba(var(--color-primary-rgb), 0.35);
pointer-events: none;
animation: bs-fb-ripple 0.5s ease-out forwards;
}
.bs-friend-btn svg {
flex-shrink: 0;
width: 13px;
height: 13px;
fill: currentColor;
transition: transform var(--trans-fast);
}
.bs-friend-btn:hover:not(:disabled) svg { transform: scale(1.15); }
.bs-friend-btn.bs-sent svg { transform: scale(0.9); }
@keyframes ff-in {
from { opacity: 0; transform: translate(-50%,-48%) scale(0.97); }
to { opacity: 1; transform: translate(-50%,-50%) scale(1); }
}
#ff-panel {
position: fixed;
z-index: 200000;
left: 50%; top: 50%;
transform: translate(-50%,-50%);
width: min(860px, 93vw);
max-height: 84vh;
background: var(--color-bg-panel);
border: 1px solid rgba(var(--color-primary-rgb), 0.18);
border-radius: var(--rad-panel);
box-shadow: 0 24px 80px rgba(var(--color-black-rgb), 0.88), inset 0 1px 0 rgba(var(--color-primary-rgb), 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
font-family: inherit;
animation: ff-in var(--trans-fast) both;
}
#ff-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
border-bottom: 1px solid rgba(var(--color-primary-rgb), 0.12);
background: rgba(var(--color-black-rgb), 0.3);
cursor: grab;
user-select: none;
flex-shrink: 0;
}
#ff-header.dragging { cursor: grabbing; }
#ff-title {
font-size: 10px;
font-weight: 600;
letter-spacing: 3px;
text-transform: uppercase;
color: rgba(var(--color-primary-rgb), 0.6);
}
#ff-hrow { display: flex; align-items: center; gap: 8px; }
#ff-search {
padding: 5px 11px;
background: rgba(var(--color-black-rgb), 0.35);
border: 1px solid rgba(var(--color-primary-rgb), 0.15);
border-radius: var(--rad-md);
color: var(--color-text);
font-size: 12px;
font-family: inherit;
outline: none;
width: 180px;
transition: border-color var(--trans-base);
}
#ff-search:focus { border-color: rgba(var(--color-primary-rgb), 0.35); }
#ff-search::placeholder { color: rgba(var(--color-primary-rgb), 0.3); }
.ff-hbtn {
padding: 5px 13px;
background: rgba(var(--color-primary-rgb), 0.08);
border: 1px solid rgba(var(--color-primary-rgb), 0.2);
border-radius: var(--rad-md);
color: var(--color-text);
font-size: 12px;
font-weight: 500;
cursor: pointer;
font-family: inherit;
transition: background var(--trans-base), border-color var(--trans-base), color var(--trans-base), transform 0.15s;
}
.ff-hbtn:hover {
background: rgba(var(--color-primary-rgb), 0.16);
border-color: rgba(var(--color-primary-rgb), 0.4);
color: var(--color-accent);
transform: translateY(-1px);
}
#ff-body {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 18px 20px;
overflow-y: auto;
}
#ff-body.ff-two { grid-template-columns: repeat(2, 1fr); }
#ff-body::-webkit-scrollbar { display: none; }
@media (max-width: 640px) { #ff-body { grid-template-columns: 1fr !important; } }
.ff-section {
background: rgba(var(--color-white-rgb), 0.02);
border: 1px solid rgba(var(--color-primary-rgb), 0.08);
border-radius: var(--rad-lg);
padding: 14px 16px;
min-height: 120px;
max-height: 58vh;
overflow-y: auto;
transition: border-color var(--trans-base);
}
.ff-section:hover { border-color: rgba(var(--color-primary-rgb), 0.18); }
.ff-section::-webkit-scrollbar { display: none; }
.ff-section h3 {
margin: 0 0 12px;
font-size: 10px;
font-weight: 600;
letter-spacing: 2.5px;
text-transform: uppercase;
color: rgba(var(--color-primary-rgb), 0.55);
}
.ff-entry { display: inline; }
.ff-entry a {
display: inline-block;
color: var(--color-text) !important;
text-decoration: none !important;
font-size: 13px;
padding: 2px 7px;
border-radius: 5px;
transition: background 0.18s, color 0.18s, transform 0.18s;
}
.ff-entry a:hover {
background: rgba(var(--color-primary-rgb), 0.12);
color: var(--color-accent) !important;
transform: translateY(-1px);
}
.ff-sep { color: rgba(var(--color-primary-rgb), 0.25); font-size: 12px; margin-right: 2px; }
.ff-empty {
color: rgba(var(--color-primary-rgb), 0.3);
font-size: 12px;
font-style: italic;
padding: 6px 2px;
}
.ff-loading { color: rgba(var(--color-primary-rgb), 0.35); font-size: 12px; letter-spacing: 0.05em; }
#ff-pill {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
z-index: 200000;
padding: 8px 20px;
background: rgba(var(--color-primary-rgb), 0.1);
border: 1px solid rgba(var(--color-primary-rgb), 0.3);
border-radius: var(--rad-pill);
color: var(--color-text);
font-size: 12px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
display: none;
transition: background var(--trans-base), border-color var(--trans-base), color var(--trans-base), transform var(--trans-base);
}
#ff-pill:hover {
background: rgba(var(--color-primary-rgb), 0.18);
border-color: rgba(var(--color-primary-rgb), 0.5);
color: var(--color-accent);
transform: translateX(-50%) translateY(-2px);
}
@keyframes cd-in {
from { opacity: 0; transform: translateY(3px); }
to { opacity: 1; transform: translateY(0); }
}
#bs-chip {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 5px 12px;
background: rgba(var(--color-primary-rgb), 0.07);
border: 1px solid rgba(var(--color-primary-rgb), 0.18);
border-radius: var(--rad-md);
font-size: 12px;
font-weight: 500;
animation: cd-in 0.28s cubic-bezier(0.4, 0, 0.2, 1) both;
margin-bottom: 8px;
}
.cd-val {
color: var(--color-text);
font-weight: 600;
position: relative;
cursor: default;
}
.cd-val::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%) translateY(3px);
background: var(--color-bg-panel);
border: 1px solid rgba(var(--color-primary-rgb), 0.2);
border-radius: var(--rad-sm);
padding: 3px 8px;
font-size: 10px;
font-weight: 600;
color: var(--color-text);
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.18s, transform 0.18s;
}
.cd-val:hover::after {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.cd-dim { color: rgba(var(--color-primary-rgb), 0.4); font-size: 11px; }
.cd-sep { color: rgba(var(--color-primary-rgb), 0.25); font-size: 11px; }
.cd-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: rgba(var(--color-primary-rgb), 0.55);
flex-shrink: 0;
}
`;
function adoptSheet() {
if (!sheet) {
sheet = new CSSStyleSheet();
sheet.replaceSync(CSS);
}
if (!document.adoptedStyleSheets.includes(sheet)) {
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
}
}
function fallbackInject() {
if (document.getElementById(CSS_ID)) return;
const el = document.createElement("style");
el.id = CSS_ID;
el.textContent = CSS;
(document.head ?? document.documentElement).prepend(el);
}
function inject() {
if (typeof CSSStyleSheet !== "undefined" && "replace" in CSSStyleSheet.prototype) {
adoptSheet();
return;
}
fallbackInject();
}
function watchForHead() {
if (document.head) { inject(); return; }
const obs = new MutationObserver(() => {
if (!document.head) return;
obs.disconnect();
inject();
});
obs.observe(document.documentElement, { childList: true, subtree: true });
}
function guardSPA() {
if (!sheet) return;
new MutationObserver(() => {
if (document.adoptedStyleSheets.includes(sheet)) return;
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
}).observe(document.head ?? document.documentElement, { childList: true });
}
const origin = () => location.origin;
const decodeHTML = s => {
const t = document.createElement("textarea");
t.innerHTML = s;
return t.value;
};
const decodeAll = s => {
if (!s) return "";
let out = s;
for (let i = 0; i < 3; i++) out = decodeHTML(out);
out = out
.replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)))
.replace(/\\n/g, "\n")
.replace(/\u2800/g, " ");
return out.trim();
};
function debounce(fn, ms) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), ms);
};
}
function hookBootstrap() {
let _real = window.kogamaApp;
Object.defineProperty(window, "kogamaApp", {
configurable: true,
set(fn) { _real = fn; },
get() {
return function (options) {
if (options?.bootstrap) window.__capturedBootstrap = options.bootstrap;
return _real?.apply(this, arguments);
};
},
});
}
const waitDesc = () => new Promise(resolve => {
const check = () => {
const b = window.__capturedBootstrap;
const raw = b?.object?.description ?? b?.current_user?.description;
if (raw) { resolve(decodeAll(raw)); return; }
requestAnimationFrame(check);
};
check();
});
const removeReactBits = root => {
if (!document.contains(root)) return;
for (const sel of ['[itemprop="description"]', ".MuiCollapse-root"]) {
root.querySelectorAll(sel).forEach(n => root.contains(n) && n.remove());
}
root.querySelectorAll("button").forEach(b => {
if (/show more/i.test(b.textContent) && root.contains(b)) b.remove();
});
};
const buildBox = text => {
const box = document.createElement("div");
box.id = "tm-desc-box";
Object.assign(box.style, {
overflowY: "auto",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
color: "#a8adb3",
fontSize: "0.92em",
lineHeight: "1.45",
padding: "14px 16px 22px",
marginTop: "6px",
background: "rgba(0,0,0,0.10)",
borderRadius: "8px",
scrollbarGutter: "stable",
overscrollBehavior: "contain",
});
box.textContent = text;
return box;
};
const fitHeight = (box, host) => {
const ph = host.parentElement?.clientHeight ?? 0;
const limit = ph > 0
? Math.floor(ph * 0.36)
: Math.floor(window.innerHeight * 0.29);
box.style.maxHeight = limit + "px";
};
const guardDesc = root => {
let timer = null;
new MutationObserver(() => {
clearTimeout(timer);
timer = setTimeout(() => removeReactBits(root), 50);
}).observe(root, { childList: true, subtree: true });
};
const run = async () => {
const desc = await waitDesc();
const waitMount = () => {
const host = document.querySelector("div._1aUa_");
if (!host) { requestAnimationFrame(waitMount); return; }
if (host.dataset.tmMounted) return;
host.dataset.tmMounted = "1";
Object.assign(host.style, { height: "auto", maxHeight: "unset", overflow: "visible" });
removeReactBits(host);
guardDesc(host);
const box = buildBox(desc);
host.appendChild(box);
fitHeight(box, host);
new ResizeObserver(() => fitHeight(box, host)).observe(document.body);
};
waitMount();
};
function relativeTime(dateStr) {
const s = Math.floor((Date.now() - new Date(dateStr)) / 1000);
if (s < 60) return "just now";
for (const { v, u } of [
{ v: 31536000, u: "y" }, { v: 2592000, u: "mo" },
{ v: 86400, u: "d" }, { v: 3600, u: "h" }, { v: 60, u: "m" },
]) {
if (s >= v) return `${(s / v).toFixed(u === "y" || u === "mo" ? 1 : 0)}${u} ago`;
}
return "just now";
}
function verboseTime(dateStr, prefix) {
const d = new Date(dateStr);
const day = d.getDate();
const sfx = [11,12,13].includes(day % 100) ? "th"
: (["st","nd","rd"][day % 10 - 1] ?? "th");
const months = ["January","February","March","April","May","June",
"July","August","September","October","November","December"];
const tz = -d.getTimezoneOffset();
return `${prefix}${day}${sfx} ${months[d.getMonth()]} ${d.getFullYear()}, `
+ `${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")} `
+ `GMT${tz >= 0 ? "+" : "-"}${Math.floor(Math.abs(tz) / 60)}`;
}
function makeIcon(path) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.classList.add("tm-icon");
const p = document.createElementNS("http://www.w3.org/2000/svg", "path");
p.setAttribute("d", path);
svg.appendChild(p);
return svg;
}
const ICONS = {
calendar: "M7 2v2H5V2H3v4h18V2h-2v2h-2V2h-2v2h-2V2H9v2H7V2zm-4 6v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8H3zm2 2h14v10H5V10z",
eye: "M12 5C5.636 5 2 12 2 12s3.636 7 10 7 10-7 10-7-3.636-7-10-7zm0 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
archive: "M4 3h16l2 4v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7l2-4zm0 4v12h16V7H4zm2 2h12v2H6V9zm0 4h12v2H6v-2z",
};
function makeBadge(iconKey, compact, full) {
const el = document.createElement("div");
el.className = "tm-badge";
const compactSpan = document.createElement("span");
compactSpan.textContent = compact;
const fullSpan = document.createElement("span");
fullSpan.className = "tm-badge-full";
fullSpan.textContent = full;
el.appendChild(makeIcon(ICONS[iconKey]));
el.appendChild(compactSpan);
el.appendChild(fullSpan);
let open = false;
el.addEventListener("click", e => {
e.stopPropagation();
open = !open;
el.classList.toggle("tm-expanded", open);
compactSpan.style.display = open ? "none" : "";
fullSpan.style.display = open ? "inline" : "";
});
return el;
}
function makeArchiveLink() {
const a = document.createElement("a");
a.className = "tm-link";
a.href = `https://web.archive.org/web/*/${location.href}`;
a.target = "_blank";
a.rel = "noopener";
a.appendChild(makeIcon(ICONS.archive));
const span = document.createElement("span");
span.textContent = "Archive";
a.appendChild(span);
return a;
}
function getBootstrapObject() {
if (window.__capturedBootstrap?.object) return window.__capturedBootstrap.object;
for (const s of document.scripts) {
if (!s.textContent?.includes("options.bootstrap")) continue;
try {
const m = s.textContent.match(/options\.bootstrap\s*=\s*({[\s\S]*?});/);
if (m) return Function(`"use strict"; return (${m[1]});`)()?.object ?? null;
} catch {}
}
return null;
}
function getFullBootstrap() {
if (window.__capturedBootstrap) return window.__capturedBootstrap;
for (const s of document.scripts) {
if (!s.textContent?.includes("options.bootstrap")) continue;
try {
const m = s.textContent.match(/options\.bootstrap\s*=\s*(\{[\s\S]+?\});\s*options\.breadcrumb/);
if (m) return JSON.parse(m[1]);
} catch {}
}
return null;
}
function getSelfId() {
const bs = window.__capturedBootstrap ?? getFullBootstrap();
if (!bs) return null;
if (bs.current_user?.id) return bs.current_user.id;
if (bs.object?.is_me === true && bs.object?.id) return bs.object.id;
return null;
}
function injectProfileInfo() {
const span = document.querySelector("div._2IqY6 span._20K92");
if (!span || span.dataset.enhanced) return false;
const data = getBootstrapObject();
if (!data?.created || !data?.last_ping) return false;
span.dataset.enhanced = "1";
span.innerHTML = "";
const row = document.createElement("div");
row.className = "tm-info-row";
row.appendChild(makeBadge("calendar", relativeTime(data.created), verboseTime(data.created, "Created @ ")));
row.appendChild(makeBadge("eye", relativeTime(data.last_ping), verboseTime(data.last_ping, "Last seen @ ")));
row.appendChild(makeArchiveLink());
span.appendChild(row);
return true;
}
function injectLevel() {
const data = getBootstrapObject();
if (typeof data?.level !== "number") return false;
const box = document.querySelector("div._2IqY6");
if (!box) return false;
const h1 = box.querySelector("h1");
if (!h1 || box.querySelector(".tm-level")) return false;
const el = document.createElement("div");
el.className = "tm-level";
el.textContent = "Level " + data.level;
h1.insertAdjacentElement("afterend", el);
return true;
}
function runProfileEnhancements() {
const done = () => injectProfileInfo() && injectLevel();
if (done()) return;
const obs = new MutationObserver(() => { if (done()) obs.disconnect(); });
obs.observe(document.body, { childList: true, subtree: true });
setTimeout(() => obs.disconnect(), 8000);
}
function obfuscateDots() {
const WHITELIST = ["youtube.com", "youtu.be", "fonts.googleapis.com"];
const URL_RE = /\bhttps?:\/\/(?:www\.)?([\w.-]+\.[a-z]{2,})(?:\/[^\s]*)?/gi;
const isInput = el => el && (
el.tagName === "TEXTAREA" ||
(el.tagName === "INPUT" && ["text","search","url","email","tel","password"].includes(el.type))
);
const isWhitelisted = d => WHITELIST.some(w => d === w || d.endsWith("." + w));
const obfuscate = text => text.replace(URL_RE, (match, domain) =>
isWhitelisted(domain) ? match : match.replace(/\./g, "%2E")
);
const process = el => {
const { selectionStart: s, selectionEnd: e } = el;
const next = obfuscate(el.value);
if (next === el.value) return;
el.value = next;
el.setSelectionRange(s, e);
};
document.addEventListener("input", e => {
if (!isInput(e.target)) return;
if (e.inputType && !e.inputType.startsWith("insert")) return;
process(e.target);
}, true);
document.addEventListener("paste", e => {
if (!isInput(e.target)) return;
e.preventDefault();
const text = (e.clipboardData ?? window.clipboardData).getData("text");
document.execCommand("insertText", false, obfuscate(text));
}, true);
}
const XHRInterceptor = (() => {
const _open = XMLHttpRequest.prototype.open;
const _send = XMLHttpRequest.prototype.send;
const _fetch = window.fetch;
const PULSE_RE = /^\/user\/\d+\/pulse\/?$/;
const LDB_RE = /(^|https?:\/\/(?:www\.)?kogama\.com\/)(api\/leaderboard\/around_me\/)([\d]+)(\/top\/?)(.*)$/i;
const PROF_RE = /^\/profile\/(\d+)\/leaderboard(\/|$)/i;
let sessionOverride = "";
let installed = false;
function matchesPulse(resource) {
try {
const raw = resource instanceof Request ? resource.url : String(resource);
return PULSE_RE.test(new URL(raw, location.href).pathname);
} catch { return false; }
}
function getLeaderboardUid() {
const m = location.pathname.match(PROF_RE);
return m ? m[1] : null;
}
function rewriteLdb(urlStr) {
try {
const abs = new URL(String(urlStr), location.href).toString();
const parts = abs.match(LDB_RE);
const uid = getLeaderboardUid();
if (!parts || !uid) return abs;
const prefix = parts[1].startsWith("/") ? location.origin + "/" : parts[1];
return prefix + parts[2] + uid + parts[4] + (parts[5] || "");
} catch { return String(urlStr); }
}
function install() {
if (installed) return;
XMLHttpRequest.prototype.open = function (method, url) {
this.__bs_method = (method || "").toUpperCase();
this.__bs_url = typeof url === "string" ? url : null;
this.__bs_rurl = this.__bs_url ? rewriteLdb(this.__bs_url) : this.__bs_url;
return _open.call(this, method, this.__bs_rurl ?? url, ...Array.from(arguments).slice(2));
};
XMLHttpRequest.prototype.send = function (body) {
if (this.__bs_method === "POST" && this.__bs_url) {
try {
if (PULSE_RE.test(new URL(this.__bs_url, location.href).pathname)) {
this.abort?.();
return;
}
} catch {}
if (sessionOverride && this.__bs_url.includes("/locator/session/")) {
try {
const d = JSON.parse(body);
if (d?.sessionID) { d.sessionID = sessionOverride; body = JSON.stringify(d); }
} catch {}
}
}
return _send.call(this, body);
};
window.fetch = async function (resource, init) {
const method = (init?.method || (resource instanceof Request ? resource.method : "GET")).toUpperCase();
if (method === "POST" && matchesPulse(resource)) {
return Promise.resolve(new Response(null, { status: 204 }));
}
try {
const urlStr = resource instanceof Request ? resource.url : String(resource);
const rewritten = rewriteLdb(urlStr);
if (rewritten !== urlStr) {
if (resource instanceof Request) {
const init2 = {
method: resource.method, headers: resource.headers,
mode: resource.mode, credentials: resource.credentials,
cache: resource.cache, redirect: resource.redirect,
referrer: resource.referrer, referrerPolicy: resource.referrerPolicy,
integrity: resource.integrity, keepalive: resource.keepalive,
signal: resource.signal,
};
try {
const buf = await resource.clone().arrayBuffer();
if (buf.byteLength) init2.body = buf;
} catch {}
return _fetch.call(window, new Request(rewritten, init2));
}
return _fetch.call(window, rewritten, init);
}
} catch {}
return _fetch.call(window, resource, init);
};
window.__bs_origFetch = _fetch;
installed = true;
}
function uninstall() {
if (!installed) return;
XMLHttpRequest.prototype.open = _open;
XMLHttpRequest.prototype.send = _send;
window.fetch = _fetch;
installed = false;
}
function arm() {
install();
window.addEventListener("pageshow", e => { if (e.persisted) install(); });
}
function setSessionOverride(sid) { sessionOverride = sid; }
function applyLdbHighlight() {
const uid = getLeaderboardUid();
if (!uid) return;
document.querySelectorAll("tr._13LmU").forEach(tr => {
if (tr.id !== uid + "Row") tr.classList.remove("_13LmU");
});
const tr = document.getElementById(uid + "Row");
if (tr) tr.classList.add("_13LmU");
}
function armLdb() {
const _push = history.pushState;
const _replace = history.replaceState;
history.pushState = function (...a) { const r = _push.apply(this, a); window.dispatchEvent(new Event("locationchange")); return r; };
history.replaceState = function (...a) { const r = _replace.apply(this, a); window.dispatchEvent(new Event("locationchange")); return r; };
window.addEventListener("popstate", () => window.dispatchEvent(new Event("locationchange")));
window.addEventListener("locationchange", () => setTimeout(applyLdbHighlight, 50));
applyLdbHighlight();
new MutationObserver(applyLdbHighlight).observe(document.documentElement, { childList: true, subtree: true });
}
return { arm, install, uninstall, setSessionOverride, armLdb };
})();
const PulseBlocker = {
arm: () => XHRInterceptor.arm(),
install: () => XHRInterceptor.install(),
uninstall: () => XHRInterceptor.uninstall(),
};
const FeedManager = (() => {
let overlay, panel, content;
let userId = null;
let feedData = [];
let loading = false;
let page = 1;
let hasMore = true;
function esc(text) {
const d = document.createElement("div");
d.textContent = text ?? "";
return d.innerHTML;
}
function timeAgo(dateStr) {
if (!dateStr) return "";
const s = Math.floor((Date.now() - new Date(dateStr)) / 1000);
if (s < 60) return "just now";
for (const [v, u] of [[31536000,"y"],[2592000,"mo"],[86400,"d"],[3600,"h"],[60,"m"]]) {
if (s >= v) return `${Math.floor(s / v)}${u} ago`;
}
return "just now";
}
function parseFeedText(item) {
let data = {};
try {
data = typeof item._data === "string" ? JSON.parse(item._data) : (item.data ?? {});
} catch {}
const msg = data.status_message ?? data.message ?? item.message ?? "";
const type = item.feed_type ?? "status";
return { msg, type };
}
function buildCommentRow(feedId, comment) {
const row = document.createElement("div");
row.className = "bs-comment";
row.dataset.commentId = comment.id;
let text = "";
try {
const d = typeof comment._data === "string" ? JSON.parse(comment._data) : {};
text = d.data ?? d.message ?? comment.message ?? "";
} catch { text = comment.message ?? ""; }
const body = document.createElement("div");
body.className = "bs-comment-body";
body.innerHTML = `<div class="bs-comment-author">${esc(comment.profile_username ?? "unknown")}</div>${esc(text)}`;
const del = document.createElement("button");
del.className = "bs-comment-del";
del.textContent = "×";
del.title = "Delete comment";
del.addEventListener("click", async () => {
del.disabled = true;
try {
await fetch(`${origin()}/api/feed/${feedId}/comment/${comment.id}/`, {
method: "DELETE", credentials: "include"
});
row.style.opacity = "0";
row.style.transition = "opacity 0.2s";
setTimeout(() => row.remove(), 200);
} catch { del.disabled = false; }
});
row.appendChild(body);
row.appendChild(del);
return row;
}
function buildCommentCompose(feedId, commentsSection) {
const wrap = document.createElement("div");
wrap.className = "bs-comment-compose";
const input = document.createElement("input");
input.className = "bs-comment-input";
input.type = "text";
input.placeholder = "Write a comment...";
input.maxLength = 500;
const btn = document.createElement("button");
btn.className = "bs-feed-btn";
btn.textContent = "Post";
const submit = async () => {
const val = input.value.trim();
if (!val) return;
btn.disabled = true;
input.disabled = true;
try {
const res = await fetch(`${origin()}/api/feed/${feedId}/comment/`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ comment: val }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
const newComment = data?.data ?? { id: Date.now(), profile_username: "you", message: val, _data: JSON.stringify({ data: val }) };
commentsSection.insertBefore(buildCommentRow(feedId, newComment), wrap);
input.value = "";
} catch {}
btn.disabled = false;
input.disabled = false;
input.focus();
};
input.addEventListener("keydown", e => { if (e.key === "Enter") submit(); });
btn.addEventListener("click", submit);
wrap.appendChild(input);
wrap.appendChild(btn);
return wrap;
}
async function toggleComments(feedId, btn, commentsSection) {
const open = commentsSection.classList.toggle("open");
btn.textContent = open ? "Hide comments" : "Comments";
if (!open || commentsSection.dataset.loaded) return;
commentsSection.innerHTML = `<div class="bs-loading">loading...</div>`;
try {
const res = await fetch(`${origin()}/api/feed/${feedId}/comment/?count=50`, {
credentials: "include"
});
const data = await res.json();
const comments = Array.isArray(data?.data) ? data.data : [];
commentsSection.innerHTML = "";
if (comments.length === 0) {
commentsSection.innerHTML = `<div style="font-size:11px;color:rgba(201,72,56,0.3);padding:6px 0;">no comments yet</div>`;
} else {
comments.forEach(c => commentsSection.appendChild(buildCommentRow(feedId, c)));
}
commentsSection.appendChild(buildCommentCompose(feedId, commentsSection));
commentsSection.dataset.loaded = "1";
} catch {
commentsSection.innerHTML = `<div class="bs-loading">failed to load</div>`;
}
}
function buildFeedItem(item) {
const { msg, type } = parseFeedText(item);
const div = document.createElement("div");
div.className = "bs-feed-item";
div.dataset.feedId = item.id;
div.innerHTML = `
<div class="bs-feed-meta">
<span class="bs-feed-date">${timeAgo(item.created)}</span>
<span class="bs-feed-type">${esc(type.replace(/_/g," "))}</span>
</div>
<div class="bs-feed-text">${esc(msg) || `<span style="opacity:0.3">no content</span>`}</div>
<div class="bs-feed-actions"></div>
<div class="bs-comments"></div>
`;
const actions = div.querySelector(".bs-feed-actions");
const commentsSection = div.querySelector(".bs-comments");
const commentBtn = document.createElement("button");
commentBtn.className = "bs-feed-btn";
commentBtn.textContent = "Comments";
commentBtn.addEventListener("click", () => toggleComments(item.id, commentBtn, commentsSection));
const delBtn = document.createElement("button");
delBtn.className = "bs-feed-btn del";
delBtn.textContent = "Delete";
delBtn.addEventListener("click", async () => {
if (!confirm("Delete this post?")) return;
delBtn.disabled = true;
try {
await fetch(`${origin()}/api/feed/${userId}/${item.id}/`, {
method: "DELETE", credentials: "include"
});
div.style.opacity = "0";
div.style.transition = "opacity 0.2s";
setTimeout(() => {
div.remove();
feedData = feedData.filter(f => f.id !== item.id);
if (!content.querySelector(".bs-feed-item")) {
content.innerHTML = `<div class="bs-empty">no posts</div>`;
}
}, 200);
} catch { delBtn.disabled = false; }
});
actions.appendChild(commentBtn);
actions.appendChild(delBtn);
return div;
}
async function loadFeed(reset = false) {
if (loading || (!hasMore && !reset)) return;
if (reset) { feedData = []; page = 1; hasMore = true; content.innerHTML = `<div class="bs-loading">loading...</div>`; }
loading = true;
try {
const res = await fetch(`${origin()}/api/feed/${userId}/?page=${page}&count=20`, {
credentials: "omit",
headers: { "Accept": "application/json" },
cache: "no-store"
});
if (!res.ok) throw new Error();
const data = await res.json();
const items = Array.isArray(data) ? data
: (Array.isArray(data?.feed) ? data.feed
: (Array.isArray(data?.data) ? data.data : []));
if (reset) content.innerHTML = "";
if (items.length === 0) {
hasMore = false;
if (reset) content.innerHTML = `<div class="bs-empty">no posts found</div>`;
} else {
feedData.push(...items);
items.forEach(item => content.appendChild(buildFeedItem(item)));
page++;
}
} catch {
if (reset) content.innerHTML = `<div class="bs-empty">failed to load feed</div>`;
}
loading = false;
}
async function deleteAll() {
if (!confirm(`Delete all ${feedData.length} posts? This cannot be undone.`)) return;
const btn = document.getElementById("bs-feed-deleteall");
if (btn) { btn.disabled = true; btn.textContent = "deleting..."; }
let i = 0;
for (const item of [...feedData]) {
try {
await fetch(`${origin()}/api/feed/${userId}/${item.id}/`, {
method: "DELETE", credentials: "include"
});
i++;
if (btn) btn.textContent = `${i}/${feedData.length}`;
await new Promise(r => setTimeout(r, 400));
} catch {}
}
feedData = [];
content.innerHTML = `<div class="bs-empty">all posts deleted</div>`;
if (btn) { btn.disabled = false; btn.textContent = "delete all"; }
}
function buildPanel() {
overlay = document.createElement("div");
overlay.id = "bs-feed-overlay";
overlay.addEventListener("click", e => { if (e.target === overlay) close(); });
panel = document.createElement("div");
panel.id = "bs-feed-panel";
const header = document.createElement("div");
header.id = "bs-feed-header";
const title = document.createElement("div");
title.id = "bs-feed-title";
title.textContent = "Feed Manager";
const btnRow = document.createElement("div");
btnRow.style.cssText = "display:flex;gap:7px;";
const delAllBtn = document.createElement("button");
delAllBtn.id = "bs-feed-deleteall";
delAllBtn.className = "bs-feed-hbtn danger";
delAllBtn.textContent = "delete all";
delAllBtn.addEventListener("click", deleteAll);
const closeBtn = document.createElement("button");
closeBtn.className = "bs-feed-hbtn";
closeBtn.textContent = "×";
closeBtn.style.padding = "6px 12px";
closeBtn.addEventListener("click", close);
btnRow.appendChild(delAllBtn);
btnRow.appendChild(closeBtn);
header.appendChild(title);
header.appendChild(btnRow);
content = document.createElement("div");
content.id = "bs-feed-content";
const sentinel = document.createElement("div");
sentinel.style.height = "1px";
content.appendChild(sentinel);
new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore && !loading) loadFeed();
}).observe(sentinel);
panel.appendChild(header);
panel.appendChild(content);
overlay.appendChild(panel);
document.body.appendChild(overlay);
}
function open() {
if (!overlay) buildPanel();
overlay.classList.add("open");
loadFeed(true);
}
function close() {
overlay?.classList.remove("open");
}
function injectCardButton(uid) {
userId = uid;
const tryInject = () => {
const container = document.querySelector(".CgH1-");
if (!container || container.querySelector(".bs-feed-card")) return false;
const card = document.createElement("div");
card.className = "MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 MuiCard-root _2Hovk bs-feed-card css-wog98n";
const inner = document.createElement("div");
inner.className = "MuiCardContent-root _1l7RM css-15q2cw4";
inner.innerHTML = `<div class="_2fSqj">Feed</div>`;
card.appendChild(inner);
card.addEventListener("click", open);
const ref = container.firstChild ?? null;
if (ref && ref.parentNode !== container) return false;
try { container.insertBefore(card, ref); } catch { try { container.appendChild(card); } catch {} }
return true;
};
if (tryInject()) return;
const obs = new MutationObserver(() => { if (tryInject()) obs.disconnect(); });
obs.observe(document.body, { childList: true, subtree: true });
}
return { injectCardButton };
})();
const FriendActivity = (() => {
const gameCache = {};
const projectCache = {};
let profileId = null;
let pollTimer = null;
let listObserver = null;
let watchRetryTimer = null;
function resolveOwnId() {
const bs = window.__capturedBootstrap;
if (bs?.current_user?.id) return bs.current_user.id;
const ku = window.kogama?.current_user ?? window.kogama?.bootstrap?.current_user;
if (ku?.id) return ku.id;
for (const s of document.scripts) {
const t = s.textContent;
if (!t?.includes('"current_user"')) continue;
try {
const m = t.match(/"current_user"\s*:\s*(\{[\s\S]+?"object_type_id"\s*:\s*\d+\s*\})/);
if (m) {
const cu = JSON.parse(m[1]);
if (cu?.id) return cu.id;
}
} catch {}
}
return null;
}
async function fetchGameTitle(gid) {
if (gameCache[gid]) return gameCache[gid];
try {
const res = await fetch(`${origin()}/games/play/${gid}/`, { credentials: "include" });
if (!res.ok) return null;
const html = await res.text();
const doc = new DOMParser().parseFromString(html, "text/html");
const raw = doc.querySelector("title")?.textContent ?? "";
const title = raw.split(" - KoGaMa")[0].trim() || null;
if (title) gameCache[gid] = title;
return title;
} catch { return null; }
}
async function fetchProjectName(pid) {
if (projectCache[pid]) return projectCache[pid];
try {
const res = await fetch(`${origin()}/game/${pid}/member`, { credentials: "include" });
if (!res.ok) return null;
const data = await res.json();
const name = data?.name ?? data?.data?.[0]?.name ?? null;
if (name) projectCache[pid] = name;
return name;
} catch { return null; }
}
const stampStatus = debounce((username, text) => {
document.querySelectorAll("._1taAL").forEach(el => {
const nameEl = el.querySelector("._3zDi-");
const statusEl = el.querySelector("._40qZj");
if (nameEl?.textContent?.trim() === username && statusEl) {
statusEl.textContent = text;
}
});
}, 80);
function resolveLocation(username, loc) {
if (!loc || loc === "/") return;
const gameMatch = loc.match(/\/games\/play\/(\d+)\//);
if (gameMatch) {
fetchGameTitle(gameMatch[1]).then(t => t && stampStatus(username, t));
return;
}
const projMatch =
loc.match(/\/build\/\d+\/project\/(\d+)\//) ??
loc.match(/\/game\/(\d+)\/member/);
if (projMatch) {
fetchProjectName(projMatch[1]).then(n => n && stampStatus(username, n));
}
}
async function poll() {
if (!profileId) return;
try {
const res = await fetch(`${origin()}/user/${profileId}/friend/chat/`, {
credentials: "include"
});
if (!res.ok) return;
const data = await res.json();
(data?.data ?? []).forEach(f => resolveLocation(f.username, f.location ?? "/"));
} catch {}
}
function processEntry(node) {
if (node.nodeType !== 1) return;
const nameEl = node.querySelector("._3zDi-");
const statusEl = node.querySelector("._40qZj");
if (!nameEl) return;
const loc =
statusEl?.textContent?.trim() ||
node.querySelector("a[href]")?.getAttribute("href") ||
null;
resolveLocation(nameEl.textContent.trim(), loc);
}
function watchList() {
clearTimeout(watchRetryTimer);
const target = document.querySelector("._1Yhgq, ._3Wytz");
if (!target) {
watchRetryTimer = setTimeout(watchList, 1200);
return;
}
target.querySelectorAll("._1lvYU, ._1taAL").forEach(processEntry);
if (listObserver) listObserver.disconnect();
listObserver = new MutationObserver(muts => {
for (const m of muts) m.addedNodes.forEach(processEntry);
});
listObserver.observe(target, { childList: true, subtree: true });
}
function start() {
const tryStart = (attempts = 0) => {
const uid = resolveOwnId();
if (!uid) {
if (attempts < 10) setTimeout(() => tryStart(attempts + 1), 300);
return;
}
if (pollTimer) return;
profileId = uid;
poll();
pollTimer = setInterval(poll, 15000);
watchList();
};
tryStart();
}
return { start };
})();
const FriendButton = (() => {
const SVG_ADD = "M624 208h-64v-64c0-8.8-7.2-16-16-16h-32c-8.8 0-16 7.2-16 16v64h-64c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h64v64c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16v-64h64c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm-400 48c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z";
const SVG_SENT = "M336 288h-16v-16C320 238.4 289.6 208 252 208H76C38.4 208 8 238.4 8 276v16H0v16c0 44.2 35.8 80 80 80h192c44.2 0 80-35.8 80-80v-16h-16zm-48 16c0 26.5-21.5 48-48 48H80c-26.5 0-48-21.5-48-48v-28c0-15.5 12.5-28 28-28H308c8.3 0 16 3.7 21.3 9.5 4.2 4.7 6.7 10.8 6.7 17.5v29zm176-240c-70.7 0-128 57.3-128 128s57.3 128 128 128 128-57.3 128-128S534.7 64 464 64zm64 144h-48v48c0 8.8-7.2 16-16 16s-16-7.2-16-16v-48h-48c-8.8 0-16-7.2-16-16s7.2-16 16-16h48V128c0-8.8 7.2-16 16-16s16 7.2 16 16v48h48c8.8 0 16 7.2 16 16s-7.2 16-16 16z";
function makeSvg(d) {
const ns = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(ns, "svg");
svg.setAttribute("viewBox", "0 0 640 512");
svg.setAttribute("aria-hidden", "true");
const path = document.createElementNS(ns, "path");
path.setAttribute("d", d);
svg.appendChild(path);
return svg;
}
function spawnRipple(btn, evt) {
const rect = btn.getBoundingClientRect();
const spot = document.createElement("span");
spot.className = "bs-fb-ripple";
spot.style.left = (evt.clientX - rect.left) + "px";
spot.style.top = (evt.clientY - rect.top) + "px";
btn.appendChild(spot);
spot.addEventListener("animationend", () => spot.remove(), { once: true });
}
function buildButton(selfId, pid) {
let state = "idle";
const wrap = document.createElement("div");
wrap.className = "bs-friend-wrap";
wrap.dataset.bsFriend = "1";
const btn = document.createElement("button");
btn.className = "bs-friend-btn";
btn.type = "button";
const icon = makeSvg(SVG_ADD);
const label = document.createElement("span");
label.textContent = "Add Friend";
btn.appendChild(icon);
btn.appendChild(label);
wrap.appendChild(btn);
function syncUI() {
if (state === "busy") {
btn.disabled = true;
btn.classList.remove("bs-sent");
btn.classList.add("bs-busy");
return;
}
btn.disabled = false;
btn.classList.remove("bs-busy");
if (state === "sent") {
btn.classList.add("bs-sent");
icon.querySelector("path").setAttribute("d", SVG_SENT);
label.textContent = "Request Sent";
btn.title = "Click to cancel";
return;
}
btn.classList.remove("bs-sent");
icon.querySelector("path").setAttribute("d", SVG_ADD);
label.textContent = "Add Friend";
btn.title = "";
}
btn.addEventListener("click", async evt => {
if (state === "busy") return;
spawnRipple(btn, evt);
const wasIdle = state === "idle";
state = "busy";
syncUI();
try {
const res = await fetch(`${origin()}/user/${selfId}/friend/`, {
method: wasIdle ? "POST" : "DELETE",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id: pid }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
state = wasIdle ? "sent" : "idle";
} catch {
state = wasIdle ? "idle" : "sent";
}
syncUI();
});
return wrap;
}
function tryInject(container, wrap) {
if (!container || !document.contains(container)) return false;
if (container.querySelector("[data-bs-friend]")) return true;
const ref = container.children[1] ?? null;
try {
if (ref) container.insertBefore(wrap, ref);
else container.appendChild(wrap);
} catch {
try { container.appendChild(wrap); } catch { return false; }
}
return true;
}
function start() {
if (!/^\/profile\/\d+\/$/.test(location.pathname)) return;
const bs = getFullBootstrap();
if (!bs) return;
const profileObj = bs.object;
const currentUser = bs.current_user;
if (!profileObj || !currentUser) return;
const friendStatus = bs.friend?.friend_status ?? null;
if (profileObj.is_me === true || bs.is_friend === true || friendStatus === "pending") return;
const wrap = buildButton(currentUser.id, profileObj.id);
const CONTAINER_SEL = "._1Noq6";
function attemptInject() {
return tryInject(document.querySelector(CONTAINER_SEL), wrap);
}
let obs = null;
setTimeout(() => {
if (attemptInject()) return;
obs = new MutationObserver(() => { if (attemptInject()) obs.disconnect(); });
obs.observe(document.body, { childList: true, subtree: true });
setTimeout(() => obs?.disconnect(), 12000);
}, 600);
new MutationObserver(() => {
if (document.contains(wrap)) return;
tryInject(document.querySelector(CONTAINER_SEL), wrap);
}).observe(document.body, { childList: true, subtree: true });
}
return { start };
})();
const ChipDisplay = (() => {
const GAME_RE = /^\/games\/play\/\d+\//;
let attached = false;
let chipObs = null;
async function fetchCounts() {
try {
const res = await fetch(location.href);
const html = await res.text();
const m = html.match(/playing_now_members["']\s*:\s*(\d+).*?playing_now_tourists["']\s*:\s*(\d+)/s);
return m ? { members: +m[1], tourists: +m[2] } : { members: 0, tourists: 0 };
} catch { return { members: 0, tourists: 0 }; }
}
function buildChip(counts) {
document.getElementById("bs-chip")?.remove();
const chip = document.createElement("div");
chip.id = "bs-chip";
const dot = document.createElement("span");
dot.className = "cd-dot";
const total = document.createElement("span");
total.className = "cd-val";
total.textContent = counts.members + counts.tourists;
total.dataset.tip = "Total Players";
const sep1 = document.createElement("span");
sep1.className = "cd-sep";
sep1.textContent = "|";
const members = document.createElement("span");
members.className = "cd-val";
members.textContent = counts.members;
members.dataset.tip = "Members";
const plus = document.createElement("span");
plus.className = "cd-dim";
plus.textContent = "+";
const tourists = document.createElement("span");
tourists.className = "cd-val";
tourists.textContent = counts.tourists;
tourists.dataset.tip = "Tourists";
chip.appendChild(dot);
chip.appendChild(total);
chip.appendChild(sep1);
chip.appendChild(members);
chip.appendChild(plus);
chip.appendChild(tourists);
return chip;
}
function tryMount() {
if (attached) return true;
const anchor = document.querySelector(".MuiStack-root._2-zAt");
if (!anchor) return false;
fetchCounts().then(counts => {
if (!document.contains(anchor)) return;
const chip = buildChip(counts);
const wrapper = document.createElement("div");
wrapper.id = "bs-chip-wrap";
wrapper.dataset.bsChip = "1";
wrapper.appendChild(chip);
anchor.parentElement.insertBefore(wrapper, anchor);
attached = true;
});
return true;
}
function start() {
if (!GAME_RE.test(location.pathname)) return;
if (tryMount()) return;
chipObs = new MutationObserver(() => {
if (!tryMount()) return;
chipObs?.disconnect();
chipObs = null;
});
chipObs.observe(document.body, { childList: true, subtree: true });
new MutationObserver(() => {
if (document.getElementById("bs-chip-wrap")) return;
attached = false;
tryMount();
}).observe(document.body, { childList: true, subtree: true });
}
return { start };
})();
const FasterFriend = (() => {
const PAGE_RE = /^\/profile\/(\d+)\/friends\/?/;
function alphaSort(a, b) {
const sa = String(a || "").toLowerCase();
const sb = String(b || "").toLowerCase();
const al = /^[a-z]/.test(sa);
const bl = /^[a-z]/.test(sb);
if (al && !bl) return -1;
if (!al && bl) return 1;
return sa.localeCompare(sb, undefined, { sensitivity: "base" });
}
async function fetchJSON(url) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 12000);
try {
const res = await fetch(url, { credentials: "include", signal: ctrl.signal });
clearTimeout(t);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
} finally {
clearTimeout(t);
}
}
function makeSectionEl(label) {
const sec = document.createElement("div");
sec.className = "ff-section";
const h = document.createElement("h3");
h.textContent = label;
sec.appendChild(h);
return sec;
}
function renderEntries(sec, items) {
sec.querySelectorAll(".ff-entry, .ff-empty, .ff-loading").forEach(n => n.remove());
if (items.length === 0) {
const n = document.createElement("div");
n.className = "ff-empty";
n.textContent = "none";
sec.appendChild(n);
return;
}
items.sort((a, b) => alphaSort(a.name, b.name)).forEach((it, i) => {
const span = document.createElement("span");
span.className = "ff-entry";
span.dataset.name = it.name.toLowerCase();
const a = document.createElement("a");
a.href = it.href;
a.target = "_blank";
a.rel = "noopener";
a.textContent = it.name;
span.appendChild(a);
if (i < items.length - 1) {
const sep = document.createElement("span");
sep.className = "ff-sep";
sep.textContent = ",";
span.appendChild(sep);
}
sec.appendChild(span);
});
}
function setLoading(sec) {
sec.querySelectorAll(".ff-entry, .ff-empty, .ff-loading").forEach(n => n.remove());
const d = document.createElement("div");
d.className = "ff-loading";
d.textContent = "loading...";
sec.appendChild(d);
}
function errNote(sec) {
sec.querySelectorAll(".ff-loading").forEach(n => n.remove());
const d = document.createElement("div");
d.className = "ff-empty";
d.textContent = "failed to load";
sec.appendChild(d);
}
function filterSections(query) {
document.querySelectorAll(".ff-entry").forEach(span => {
span.style.display = (!query || span.dataset.name.includes(query)) ? "" : "none";
});
}
function setupDrag(panel, header) {
let drag = null;
const fix = () => {
const r = panel.getBoundingClientRect();
panel.style.left = r.left + "px";
panel.style.top = r.top + "px";
panel.style.transform = "";
};
header.addEventListener("mousedown", e => {
if (e.target.closest(".ff-hbtn")) return;
fix();
header.classList.add("dragging");
drag = { ox: e.clientX - parseFloat(panel.style.left), oy: e.clientY - parseFloat(panel.style.top) };
panel.style.transition = "none";
const move = e2 => {
const x = Math.min(Math.max(8, e2.clientX - drag.ox), window.innerWidth - panel.offsetWidth - 8);
const y = Math.min(Math.max(8, e2.clientY - drag.oy), window.innerHeight - panel.offsetHeight - 8);
panel.style.left = x + "px";
panel.style.top = y + "px";
};
const up = () => {
drag = null;
header.classList.remove("dragging");
panel.style.transition = "";
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", up);
};
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", up);
});
}
function buildPanel(isOwn) {
document.getElementById("ff-panel")?.remove();
document.getElementById("ff-pill")?.remove();
const panel = document.createElement("div");
panel.id = "ff-panel";
const header = document.createElement("div");
header.id = "ff-header";
const title = document.createElement("div");
title.id = "ff-title";
title.textContent = isOwn ? "Friends & Requests" : "Friends Overview";
const hrow = document.createElement("div");
hrow.id = "ff-hrow";
const search = document.createElement("input");
search.id = "ff-search";
search.type = "search";
search.placeholder = "Search...";
search.addEventListener("input", () => filterSections(search.value.trim().toLowerCase()));
const closeBtn = document.createElement("button");
closeBtn.className = "ff-hbtn";
closeBtn.textContent = "×";
hrow.appendChild(search);
hrow.appendChild(closeBtn);
header.appendChild(title);
header.appendChild(hrow);
const body = document.createElement("div");
body.id = "ff-body";
if (!isOwn) body.classList.add("ff-two");
const friendsSec = makeSectionEl("Friends");
body.appendChild(friendsSec);
setLoading(friendsSec);
let sentSec, requestsSec, mutualSec;
if (isOwn) {
sentSec = makeSectionEl("Sent");
requestsSec = makeSectionEl("Incoming");
body.appendChild(sentSec);
body.appendChild(requestsSec);
setLoading(sentSec);
setLoading(requestsSec);
} else {
mutualSec = makeSectionEl("Mutual Friends");
body.appendChild(mutualSec);
setLoading(mutualSec);
}
panel.appendChild(header);
panel.appendChild(body);
document.body.appendChild(panel);
const pill = document.createElement("button");
pill.id = "ff-pill";
pill.textContent = "Friends Panel";
document.body.appendChild(pill);
const hide = () => { panel.style.display = "none"; pill.style.display = "block"; };
const show = () => { panel.style.display = ""; pill.style.display = "none"; };
closeBtn.addEventListener("click", hide);
pill.addEventListener("click", show);
document.addEventListener("keydown", e => {
if (e.key !== "Escape") return;
panel.style.display === "none" ? show() : hide();
});
setupDrag(panel, header);
return { friendsSec, sentSec, requestsSec, mutualSec };
}
async function fetchFriends(uid) {
const data = await fetchJSON(`${origin()}/user/${uid}/friend/?count=555`);
return (Array.isArray(data.data) ? data.data : [])
.filter(f => f.friend_status === "accepted")
.map(f => ({ name: f.friend_username || String(f.friend_profile_id), href: `${origin()}/profile/${f.friend_profile_id}/` }));
}
async function fetchRequests(uid) {
const data = await fetchJSON(`${origin()}/user/${uid}/friend/requests/?page=1&count=1000`);
const arr = Array.isArray(data.data) ? data.data : [];
const sent = arr
.filter(r => String(r.profile_id) === String(uid))
.map(r => ({ name: r.friend_username || String(r.friend_profile_id), href: `${origin()}/profile/${r.friend_profile_id}/` }));
const incoming = arr
.filter(r => String(r.profile_id) !== String(uid))
.map(r => ({ name: r.profile_username || String(r.profile_id), href: `${origin()}/profile/${r.profile_id}/` }));
return { sent, incoming };
}
function start() {
const m = location.pathname.match(PAGE_RE);
if (!m) return;
const bs = getFullBootstrap();
const currentUser = bs?.current_user;
if (!currentUser?.id) return;
const viewedId = m[1];
const isOwn = String(currentUser.id) === String(viewedId);
const { friendsSec, sentSec, requestsSec, mutualSec } = buildPanel(isOwn);
fetchFriends(viewedId)
.then(items => renderEntries(friendsSec, items))
.catch(() => errNote(friendsSec));
if (isOwn) {
fetchRequests(viewedId)
.then(({ sent, incoming }) => {
renderEntries(sentSec, sent);
renderEntries(requestsSec, incoming);
})
.catch(() => { errNote(sentSec); errNote(requestsSec); });
return;
}
fetchFriends(currentUser.id)
.then(myFriends => {
const mySet = new Set(myFriends.map(f => f.href));
fetchFriends(viewedId)
.then(theirFriends => renderEntries(mutualSec, theirFriends.filter(f => mySet.has(f.href))))
.catch(() => errNote(mutualSec));
})
.catch(() => errNote(mutualSec));
}
return { start };
})();
PulseBlocker.arm();
hookBootstrap();
watchForHead();
const INIT_DELAY = 150;
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
guardSPA();
XHRInterceptor.armLdb();
setTimeout(() => {
runProfileEnhancements();
FriendButton.start();
FasterFriend.start();
ChipDisplay.start();
const data = getBootstrapObject();
if (data?.is_me && data?.id) FeedManager.injectCardButton(data.id);
FriendActivity.start();
}, INIT_DELAY);
}, { once: true });
} else {
guardSPA();
XHRInterceptor.armLdb();
setTimeout(() => {
runProfileEnhancements();
FriendButton.start();
FasterFriend.start();
ChipDisplay.start();
const data = getBootstrapObject();
if (data?.is_me && data?.id) FeedManager.injectCardButton(data.id);
FriendActivity.start();
}, INIT_DELAY);
}
obfuscateDots();
run();
})();