Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ module.exports = {
'@typescript-eslint'
],
overrides: [
{
files: ['*.js', 'utils/*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
}
},
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
Expand All @@ -29,8 +35,9 @@ module.exports = {
],
// Causes false positives with reactive and auto subscriptions
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'no-sequences': 'off',
'svelte/missing-declaration': "off",
'svelte/missing-declaration': 'off',
}
}
],
Expand Down
29 changes: 24 additions & 5 deletions docs/YOUTUBE_ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ YouTube actions almost always depend on these fields. If you drop any of them, y
- Account identity: `x-goog-authuser` must match the active YouTube account (multi-login breaks without it)
- Visitor identity: `x-goog-visitor-id`
- Client identity: `x-youtube-client-name` and `x-youtube-client-version`
- Delegated channel identity: `x-goog-pageid` from `DELEGATED_SESSION_ID` when present

If you are unsure where a value comes from, stop and find it in:

Expand Down Expand Up @@ -60,14 +61,29 @@ Examples:

- block: `moderateLiveChatEndpoint` (and friends)
- report: `getReportFormEndpoint` (flow can be multi-step)
- delete/retract: look for the delete/retract endpoint in the same way
- delete/retract: `moderateLiveChatEndpoint` from the delete/retract menu item

If an endpoint is missing, log enough context to diagnose:

- which endpoint types we found
- which ones we did not
- which message/menu payload we used to ask for the menu

## Delete / Retract Flow

Delete/retract is not a separate obvious top-level API. It is a context-menu action:

1. Use the message's `contextMenuEndpoint.liveChatItemContextMenuEndpoint.params`.
2. Call `live_chat/get_item_context_menu` with those params and the same Innertube identity headers YouTube uses.
3. Search the response tree for `menuServiceItemRenderer.serviceEndpoint.moderateLiveChatEndpoint`.
4. Prefer candidates whose icon is `DELETE`.
5. Fall back to label text only after endpoint and icon checks, because labels are localized and less stable.
6. POST the selected endpoint params to `live_chat/moderate`.

For streamer/mod deletes, do not infer capability from author id alone. YouTube exposes the delete affordance on each message via `inlineActionButtons`; parse that into `canDelete` and use it with the message's context-menu params.

For self retraction, use the same endpoint discovery path. It may still be a `moderateLiveChatEndpoint`; the important distinction is the menu item YouTube returned for that message/account state, not a different hardcoded endpoint.

## Keep Requests Correlated

If you proxy Innertube calls through a background/service worker, keep request/response events correlated by request id.
Expand All @@ -76,14 +92,18 @@ Do not use global listeners or "last response wins" patterns. Two actions can ov

## UI State: Be Honest

When we apply an action locally, do it only when YouTube confirms success.
When we apply an action locally, separate request success from YouTube's later chat-state echo.

For delete/retract:

- on success: remove the message from display (and tolerate YouTube later echoing a "retracted" update)
- on local request success: mark the message as pending deleted, but keep the original runs available locally
- on `markChatItemAsDeletedAction`: replace with YouTube's deleted-state message
- on `removeChatItemAction`: treat the target message as pending deleted if YouTube only tells us to remove the item
- on `markChatItemsByAuthorAsDeletedAction`: apply the deleted-state message to messages from that author
- if YouTube provides `showOriginalContentMessage`, keep it as a view-original toggle for moderator contexts
- on failure: keep it visible and surface an error

If you fake success, users will trust the UI less than the native UI.
Do not mutate the parsed message text in place. Keep the original parsed runs and choose the displayed runs at the render edge. Otherwise pending state, "view deleted message", and later YouTube confirmation can clobber each other.

## HAR + DevTools Tips (So You Do Not Lose The Payload)

Expand All @@ -98,4 +118,3 @@ If you fake success, users will trust the UI less than the native UI.
- Wrong Innertube client name/version (YouTube serves different schemas)
- SAPISIDHASH removed or computed for the wrong origin
- Context menu parsing tied to item index instead of endpoint types

8 changes: 0 additions & 8 deletions src/components/HyperchatButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,6 @@
cursor: pointer;
transition: box-shadow 0.2s;
}
.toggleButton .floating-icon {
position: absolute;
bottom: 5px;
right: 3px;
width: 15px;
height: 15px;
}

.toggleButton.disabled {
color: var(--yt-live-chat-secondary-text-color);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
icon: d.icon,
text: d.text,
value: d.value.toString(),
onClick: () => useBanHammer(message, d.value, $port)
onClick: () => { useBanHammer(message, d.value, $port); }
}));

const openReplyTargetSuperchat = () => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/SettingsButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { createPopup } from '../ts/chat-utils';
import { isLiveTL } from '../ts/chat-constants';
import outline from '../assets/outline.svg?raw';
import { onDestroy, onMount } from "svelte";
import { onDestroy, onMount } from 'svelte';

const openSettings = () => {
createPopup(chrome.runtime.getURL(`${isLiveTL ? 'hyperchat/' : ''}options.html${document.documentElement.getAttribute('dark') === '' ? '?dark' : ''}`));
Expand Down Expand Up @@ -65,4 +65,4 @@
justify-content: center;
fill: var(--yt-spec-text-primary);
}
</style>
</style>