- // ==UserScript==
- // @name Lemmy post utilities - Filter posts by title
- // @namespace Violentmonkey Scripts
- // @description Filters posts on any lemmy instance by text in the title. It can also auto-open image posts, unblur thumbnails, and other things.
- // @match https://*lemmy*.*/*
- // @include https://lemy.nl/*
- // @include https://burggit.moe/*
- // @include https://lemmit.online/*
- // @include https://yiffit.net/*
- // @include https://reddthat.com/*
- // @include https://sh.itjust.works/*
- // @exclude https://lemmyverse.net/*
- // @exclude https://lemmy-status.org/*
- // @exclude https://search-lemmy.com/*
- // @exclude https://join-lemmy.org/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_setClipboard
- // @version 2.2.3
- // @icon data:image/png;base64,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
- // @author Xynoth
- // @license GPT-3
- // @date 14/7/2023, 20:15:03
- // ==/UserScript==
-
- //----------------------------------------
- // CONSTANTS
- //----------------------------------------
-
- // Key constants
- const BLOCKED_TAGS_KEY = "blockedTags";
- const BLOCKED_CASE_SENSITIVE_TAGS_KEY = "blockedCaseSensitiveTags";
- const AUTO_OPEN_MEDIA_POSTS_KEY = "autoOpenMediaPosts";
- const UNBLUR_THUMBNAILS_KEY = "unblurThumbs";
- const SHOW_FILTERED_STUBS_KEY = "showFilteredStubs";
- const COMMAS_AS_SEPARATORS_KEY = "useCommasAsSeparators";
- const CASE_SENSITIVE_KEY = "caseSensitiveTag";
- const WIDGET_HEIGHT_KEY = "widgetHeight";
- const FIX_BROKEN_VIDEO_PREVIEWS_KEY = "fixBrokenVideoPreviews";
- const MARK_POSTS_AS_NSFW_KEY = "markNewPostsAsNSFW";
-
- // Constant selectors
- const postsContainerClass = "post-listings";
- const profileContainerClass = "person-details";
- const searchContainerClass = "search"
- const postContainer = "post-listing";
- const loadingSpinnerSelector = ".icon.spin"; // This is the "creators" select in the search page
- const postCommunityContainer = ".community-link";
- const postCommunityNameContainer = `${postCommunityContainer} > span`;
- const postSrcLink = `div:nth-of-type(2) p a`;
- const fixedPreviewContainerClass = "fixed-preview";
- const fixedPreviewVideoClass = "fixed-preview-video";
- const postAsNSFW = "#post-nsfw[type='checkbox']";
- const createPostContainerId = "createPostForm";
- const editPostClass = "post-form";
-
- // GUI main elements
- const settingsWidgetId = "settings-widget";
- const blockedTagsDialogId = "tags-dialog";
- const dialogBgId = "dialog-bg";
- const settingsWidgetContainerId = "widget-container";
- const blockTagListId = "blocked-insensitive-tag-list";
- const csBlockTagListId = "blocked-sensitive-tags-list";
- const openedPostChecker = "already-opened";
- const filteredPostChecker = "filter-checked";
- const processedPageChecker = "processed-page";
-
- // CSS Color constants
- const caseInsensitiveTagColor = "#dd2222";
- const caseSensitiveTagColor = "#2052b3";
- const primaryBtnColor = "#0052cc";
- const primaryBtnHoverColor = "#0066ff";
- const primaryBtnActiveColor = "#0047b3";
- const widgetBgColor = "#1a1a1b";
- const errorToastColor = "#f94b4b";
- const errorToastBgColor = "#2f0808";
-
- // Other constants
- const logoImg = GM_info.script.icon;
- const namedRegex = "regex\\((.*?)\\):";
-
- // HTML content constants
- const bottomWidgetContent = `
- <div id="${settingsWidgetContainerId}">
- <h1><span id="widget-logo"></span>Lemmy post utilities</h1>
- <div class="form-entry">
- <label>Blocked tags: </label>
- <div class="btn-container">
- <button id="block-tag-btn" type="button">🖊</button>
- </div>
- </div>
- <div class="form-entry">
- <label>
- Show stubs on filter:
- </label>
- <div class="btn-container">
- <span class="switch" id="show-stub-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div class="form-entry">
- <label>
- Fix broken video previews:
- </label>
- <div class="btn-container">
- <span class="switch" id="fix-video-previews-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div class="form-entry">
- <label>
- Auto open media previews:
- </label>
- <div class="btn-container">
- <span class="switch" id="auto-open-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div class="form-entry">
- <label>
- Unblur NSFW thumbnails:
- </label>
- <div class="btn-container">
- <span class="switch" id="unblur-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div class="form-entry">
- <label>
- Mark new posts as NSFW:
- </label>
- <div class="btn-container">
- <span class="switch" id="default-nsfw-posts-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- </div>
- `;
-
- const tagsDialogContent = `
- <div id="blocked-tags-dialog-container">
- <div id ="blocked-tags-dialog-head">
- <h1>Blocked tags editor</h1>
- <button type="button" class="close-dialog-btn">⨯</button>
- </div>
- <div>
- <p>Any tag added here will hide any post that contains the word in its title.</p>
- <p>You can also use some advanced filtering options like the following:</p>
- <ul>
- <li>Use <a href="https://regex101.com/">regex</a> starting a tag with <code>regex:</code></li>
- <li>Create a named regex starting a tag with <code>regex(your-regex-name):</code></li>
- <li>Filter by linked source instead of title starting a tag with <code>source:</code></li>
- <li>Combine <code>source:</code> with any variant of <code>regex:</code></li>
- <li>You can click any tag to copy it's raw value.</li>
- </ul>
- <p>These are the blocked tags you have for this instance:</p>
- <div id="blocked-tags-field-container">
- <div id="blocked-tags-field">
- <p id="empty-blocked-tags">You haven't blocked anything yet.</p>
- <ul id="${blockTagListId}" class="blocked-tags-list" hidden>
- </ul>
- <hr id="blocked-tags-separator" hidden>
- <ul id="${csBlockTagListId}" class="blocked-tags-list" hidden>
- </ul>
- </div>
- <div id="blocked-tags-field-legend">
- <span id="tag-case-insensitive-legend" class="tag-legend">
- <span class="tag-color-legend"></span>
- <small class="tag-label-legend">Case insensitive tag</small>
- </span>
- <span id="tag-case-sensitive-legend" class="tag-legend">
- <span class="tag-color-legend"></span>
- <small class="tag-label-legend">Case sensitive tag</small>
- </span>
- </div>
- </div>
- <p id="clipboard-notice" hidden>Tag value copied to the clipboard!</p>
- <div class="switch-container">
- <label>
- Use commas as tag separators:
- </label>
- <div class="btn-container">
- <span class="switch" id="use-commas-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div class="switch-container">
- <label>
- Add tags as case sensitive:
- </label>
- <div class="btn-container">
- <span class="switch" id="case-sensitive-swt">
- <input type="checkbox">
- <span class="slider round"></span>
- </span>
- </div>
- </div>
- <div id="tag-input-container">
- <input type="text" id="tag-input" placeholder="Add your tags">
- <button type="button" id="tag-save-btn">Save</button>
- </div>
- <label id="blocked-tags-input-error" hidden>Tag must be longer than 1 non-whitespace character!</label>
- </div>
- </div>
- `;
-
- const tagContent = `
- <span title="TOOLTIP-CONTENT">
- <label>TAG-NAME</label>
- <button type="button">⨯</button>
- </span>
- `;
-
- const filteredPostStubContent = `
- <div class="hidden-post-stub-meta-container">
- <p>This post was hidden because it contained the tag 'TAG'.</p>
- <span class="hidden-post-stub-btn-container">
- <button class="show-hidden-post-title-btn" type="button">Show title</button>
- <button class="show-hidden-post-btn" type="button">Show post</button>
- </span>
- </div>
- <span class="stub-hidden-post-title" hidden>
- <br>
- <p>Post title was '<b>POST-TITLE</b>' from <a class="stub-hidden-post-community-link">POST-COMMUNITY</a> community.</p>
- </span>
- `;
-
- const videoSourceContent = `
- <source src="VIDEO-SOURCE" type="video/VIDEO-TYPE">
- `;
-
- // CSS to add to style elements
- const initialCSS = getInitialCSS();
-
- const guiCSS = () => `
-
- /* Fix for stubs leaving empty space at the bottom of the page for some reason */
- html {
- overflow-y: auto;
- }
-
- body {
- overflow-y: clip;
- }
-
- /* The settings widget */
- #${settingsWidgetId} {
- display: flex;
- float: right;
- bottom: 0;
- right: 0.8rem;
- max-width: 15rem;
- position: fixed;
- transform: translateY(${getData(WIDGET_HEIGHT_KEY) ?? storeData(WIDGET_HEIGHT_KEY, "21rem")});
- transition: 200ms ease-in-out transform;
- }
-
- #${settingsWidgetId}:hover {
- transform: translateY(1px);
- }
-
- /* The container for the widget */
- #widget-container {
- flex-direction: column;
- border: 1px solid #333;
- font-size: 0.9rem;
- background-color: ${widgetBgColor};
- width: 100%;
- padding: 1rem;
- border-radius: .5rem .5rem 0 0;
- margin-top: 50px; /* This is the space that will allow showing the popup when hovering over it */
- }
-
- #widget-container:hover {
- margin-top: 0;
- }
-
- /* Widget title */
- #widget-container > h1 {
- font-size: 1rem;
- font-weight: bold;
- height: 1rem;
- }
-
- #widget-logo {
- display: inline-block;
- background-image: url('${logoImg}');
- height: 1rem;
- width: 1rem;
- background-size: contain;
- background-repeat: no-repeat;
- margin-right: 0.5rem;
- }
-
- /* Widget block button */
- #block-tag-btn {
- appearance: none;
- color: #ddd;
- background: rgba(255,255,255,0.1);
- border: none;
- border-radius: 2rem;
- padding: .3rem .5rem;
- }
-
- /* The layout for each label + form control */
- .form-entry {
- display: inline-flex;
- }
-
- .form-entry:not(:first-child) {
- margin-top: 0.8rem;
- }
-
- .form-entry > label {
- display: flex;
- align-items: center;
- justify-content: left;
- width: 10rem;
- }
-
- .btn-container {
- display: grid;
- place-items: center;
- }
-
- /* The dialog to filter tags */
- #${blockedTagsDialogId} {
- appearance: none;
- border: none;
- background-color: ${widgetBgColor};
- padding: 1rem;
- border-radius: 0.5rem;
- box-shadow: 0 0 20px 5px rgba(255,255,255,0.2);
- position: fixed;
- top: 50%;
- bottom: 50%;
- z-index: 1001;
- max-width: 36.5rem;
- }
-
- #dialog-bg {
- display: none;
- background-color: rgba(0,0,0,.5); /* For browsers that don't support backdrop-filter */
- position: fixed;
- height: 100vh;
- width: 100vw;
- backdrop-filter: blur(2px);
- top: 0;
- left: 0;
- z-index: 1000;
- }
-
- #blocked-tags-separator {
- width: 100%;
- margin-left: auto;
- margin-right: auto;
- border-top: 2px solid #666;
- margin-top: .5rem;
- margin-bottom: .5rem;
- }
-
- #blocked-tags-dialog-head {
- display: flex;
- }
-
- #blocked-tags-dialog-head > h1 {
- font-size: 1.5rem;
- margin-bottom: 1rem;
- color: #fff;
- }
-
- #tag-input-container {
- margin-top: 1rem;
- margin-bottom: 1rem;
- display: flex;
- }
-
- #blocked-tags-input-error {
- color: ${errorToastColor};
- font-size: 0.8rem;
- background-color: ${errorToastBgColor};
- padding: .4rem;
- border-radius: .5rem;
- font-weight: bold;
- }
-
- #blocked-tags-field-container {
- margin-bottom: 1rem;
- }
-
- #blocked-tags-field {
- display: grid;
- padding: 1rem;
- max-height: 20rem;
- overflow-y: auto;
- overflow-x: hidden;
- scrollbar-width: thin;
- place-items: center;
- border: 1px solid #555;
- border-radius: 0.3rem;
- }
-
- #empty-blocked-tags {
- color: #666;
- margin: 0;
- padding: 1rem;
- }
-
- #clipboard-notice {
- color: #2f2;
- font-size: 0.8rem;
- width: 100%;
- text-align: center;
- }
-
- #tag-input {
- border: none;
- flex-grow: 100;
- background-color: #333;
- border-radius: .5rem;
- padding: .5rem 1rem;
- width: 80%;
- margin-right: 1rem;
- color: #ddd;
- }
-
- #tag-save-btn {
- appearance: none;
- border: none;
- background-color: ${primaryBtnColor};
- color: #ddd;
- padding: .5rem 1rem;
- border-radius: 0.3rem;
- }
-
- #tag-save-btn:hover {
- background-color: ${primaryBtnHoverColor};
- color: #fff;
- }
-
- #tag-save-btn:active {
- background-color: ${primaryBtnActiveColor};
- color: #fff;
- }
-
- #tag-case-insensitive-legend > span {
- background-color: ${caseInsensitiveTagColor};
- }
-
- #tag-case-sensitive-legend > span {
- background-color: ${caseSensitiveTagColor};
- }
-
- #blocked-tags-field-legend {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- }
-
- .close-dialog-btn {
- appearance: none;
- color: #fff;
- font-weight: bold;
- background-color: transparent;
- border: none;
- padding: 0.2rem 0.6rem;
- position: absolute;
- right: 0;
- top: 0;
- transition: 200ms all;
- }
-
- .close-dialog-btn:hover {
- border-radius: 6rem;
- background-color: rgba(255,255,255,0.2);
- }
-
- .switch-container {
- width: 100%;
- display: flex;
- }
-
- .switch-container > label {
- width: 16rem;
- margin-right: 1rem;
- }
-
- .blocked-tags-list {
- display: flex;
- flex-wrap: wrap;
- max-width: 35.5rem;
- margin-bottom: 0;
- padding: 0;
- }
-
- .blocked-tags-list > li {
- display: inline-flex;
- list-style-type: none;
- }
-
- .tag-legend {
- margin-right: 1rem;
- }
-
- .tag-color-legend {
- display: inline-block;
- width: 7px;
- height: 7px;
- }
-
- .tag-label-legend {
- font-size: 0.6rem;
- }
-
- .blocked-tag {
- border-radius: 0.4rem;
- font-size: 0.8rem;
- color: #fff;
- margin: .2rem;
- cursor: grab;
- }
-
- .blocked-tag[data-dragged-item] {
- cursor: grabbing;
- opacity: 0.7;
- }
-
- .blocked-tag label {
- padding-left: .5rem;
- white-space: pre-wrap;
- cursor: grab;
- }
-
- .blocked-tag button {
- appearance: none;
- color: #fff;
- background: transparent;
- border: none;
- border-radius: .4rem;
- }
-
- .blocked-tag button:hover {
- background-color: rgba(255,255,255,0.2);
- }
-
- .case-insensitive-tag {
- background-color: ${caseInsensitiveTagColor};
- }
-
- .case-sensitive-tag {
- background-color: ${caseSensitiveTagColor};
- }
-
- /* The filtered post stubs */
- .filtered-post-stub p {
- margin-bottom: 0;
- }
-
- .hidden-post-stub-meta-container {
- display: flex;
- }
-
- .hidden-post-stub-btn-container {
- margin-left: auto;
- }
-
- .hidden-post-stub-btn-container > button {
- appearance: none;
- background-color: transparent;
- border: none;
- cursor: pointer;
- color: #3498db;
- margin-right: 1rem;
- }
-
- .hide-post-btn {
- appearance: none;
- color: #f22;
- font-weight: bold;
- font-size: 2rem;
- line-height: 1rem;
- width: 1.8rem;
- background-color: transparent;
- border: none;
- padding: 0.2rem 0.6rem;
- height: 0;
- float: right;
- transition: 200ms all;
- }
-
- /* The switch - the box around the slider */
- .switch {
- position: relative;
- display: inline-block;
- width: 30px;
- height: 16px;
- }
-
- /* Hide default HTML checkbox */
- .switch input {
- opacity: 0;
- width: 0;
- height: 0;
- }
-
- /* The slider */
- .slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #777;
- -webkit-transition: .4s;
- transition: .4s;
- }
-
- .slider:before {
- position: absolute;
- content: "";
- height: 12px;
- width: 12px;
- left: 2px;
- bottom: 2px;
- background-color: white;
- -webkit-transition: .4s;
- transition: .4s;
- }
-
- input:checked + .slider {
- background-color: #2196F3;
- }
-
- input:focus + .slider {
- box-shadow: 0 0 1px #2196F3;
- }
-
- input:checked + .slider:before {
- -webkit-transform: translateX(12px);
- -ms-transform: translateX(12px);
- transform: translateX(12px);
- }
-
- /* Rounded sliders */
- .slider.round {
- border-radius: 34px;
- }
-
- .slider.round:before {
- border-radius: 50%;
- }
-
- `;
-
- const unblurCSS = `
-
- /* Unblurs thumbnails */
- .img-blur {
- filter: none !important;
- }
-
- `
-
- //----------------------------------------
- // GUI AND INITIAL SETUP
- //----------------------------------------
-
- // Load initial settings and store defaults if they didn't exist
- console.info(`Loading data for domain '${document.location.host}'.`);
-
- const blockedTitleTags = getData(BLOCKED_TAGS_KEY) ?? storeData(BLOCKED_TAGS_KEY, []);
- const csBlockedTitleTags = getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY) ?? storeData(BLOCKED_CASE_SENSITIVE_TAGS_KEY, []);
- const expandMediaPosts = getData(AUTO_OPEN_MEDIA_POSTS_KEY) ?? storeData(AUTO_OPEN_MEDIA_POSTS_KEY, false);
- const unblurThumbnails = getData(UNBLUR_THUMBNAILS_KEY) ?? storeData(UNBLUR_THUMBNAILS_KEY, false);
- const useCommasAsSeparators = getData(COMMAS_AS_SEPARATORS_KEY) ?? storeData(COMMAS_AS_SEPARATORS_KEY, true);
- const showStubsForFilteredPosts = getData(SHOW_FILTERED_STUBS_KEY) ?? storeData(SHOW_FILTERED_STUBS_KEY, true);
- const fixPostVideoPreviews = getData(FIX_BROKEN_VIDEO_PREVIEWS_KEY) ?? storeData(FIX_BROKEN_VIDEO_PREVIEWS_KEY, true);
- const caseSensitiveTags = getData(CASE_SENSITIVE_KEY) ?? storeData(CASE_SENSITIVE_KEY, false);
- const markNewPostsAsNSFW = getData(MARK_POSTS_AS_NSFW_KEY) ?? storeData(MARK_POSTS_AS_NSFW_KEY, false);
-
- if (blockedTitleTags.length > 0 || csBlockedTitleTags.length > 0)
- console.info("Waiting for page to fully load to block tags: ", blockedTitleTags, csBlockedTitleTags);
-
- // Add the GUI to control the settings to the document
- const settingWidget = addElement(document.body, "DIV", settingsWidgetId, bottomWidgetContent);
- const tagsDialog = addElement(document.body, "DIALOG", blockedTagsDialogId, tagsDialogContent);
- const dialogBg = addElement(document.body, "DIV", dialogBgId);
-
- // Add initial CSS changes
- updateCSS();
-
- // Load tags into the dialog window
- updateVisibleTags();
-
- // Reflect boolean values of settings on GUI
- document.querySelector("#show-stub-swt > input").checked = showStubsForFilteredPosts;
- document.querySelector("#auto-open-swt > input").checked = expandMediaPosts;
- document.querySelector("#unblur-swt > input").checked = unblurThumbnails;
- document.querySelector("#use-commas-swt > input").checked = useCommasAsSeparators;
- document.querySelector("#case-sensitive-swt > input").checked = caseSensitiveTags;
- document.querySelector("#fix-video-previews-swt > input").checked = fixPostVideoPreviews;
- document.querySelector("#default-nsfw-posts-swt > input").checked = markNewPostsAsNSFW;
-
- // --------- Add event listeners ---------
-
- // Blocked list dialog button
- document.getElementById("block-tag-btn").onclick = () => {
- openDialog(tagsDialog);
- }
-
- // Close dialog button
- tagsDialog.getElementsByClassName("close-dialog-btn")[0].onclick = () => {
- closeDialog(tagsDialog);
- }
-
- // Close dialog on clicking outside the dialog
- dialogBg.onclick = (event) => {
- event.stopPropagation();
- closeDialog(tagsDialog);
- }
-
- // Show stubs on filtering posts
- document.getElementById("show-stub-swt").onclick = (event) => {
- const showStubs = storeData(SHOW_FILTERED_STUBS_KEY, toggleCheckbox(event));
- const postsContainer = getPostContainer();
-
- if (postsContainer)
- postsContainer.getElementsByClassName(postContainer).forEach(post => {
- let siblingNode = post.nextElementSibling;
- if (showStubs && post.hasAttribute("hidden")) {
- // Show sibling separator as well
- if (siblingNode && siblingNode.tagName == "HR")
- siblingNode.removeAttribute("hidden");
-
- // Show post with the stub
- post.removeAttribute("hidden");
- } else if (post.getElementsByClassName("filtered-post-stub")[0]) {
-
- // Remove separator if it exists
- if (siblingNode && siblingNode.tagName == "HR")
- siblingNode.setAttribute("hidden", true);
-
- // Hide post completelly
- post.setAttribute("hidden", true);
- }
- })
- }
-
- // Fix some broken video previews
- document.getElementById("fix-video-previews-swt").onclick = (event) => {
- const fixVideoPreviews = storeData(FIX_BROKEN_VIDEO_PREVIEWS_KEY, toggleCheckbox(event));
- const fixedPreviewContainers = document.getElementsByClassName(fixedPreviewContainerClass);
- const fixedPreviewVideos = document.getElementsByClassName(fixedPreviewVideoClass);
- const postsContainer = getPostContainer();
- const wasProcessed = postsContainer.id === processedPageChecker;
-
- if (!postsContainer)
- return;
-
- if (!wasProcessed)
- postsContainer.setAttribute("id", processedPageChecker);
-
- if (fixVideoPreviews) {
- if (fixedPreviewContainers.length > 0 || fixedPreviewVideos.length > 0) {
- for(let i = 0; i < fixedPreviewContainers.length; i++) {
- fixedPreviewContainers[i].querySelector("picture").setAttribute("hidden", true);
- fixedPreviewVideos[i].removeAttribute("hidden");
- }
- } else {
- fixBrokenVideoPreviews(postsContainer);
- }
- } else if (fixedPreviewContainers.length > 0 || fixedPreviewVideos.length > 0) {
- for(let i = 0; i < fixedPreviewContainers.length; i++) {
- fixedPreviewContainers[i].querySelector("picture").removeAttribute("hidden");
- fixedPreviewVideos[i].setAttribute("hidden", true);
- }
- }
- }
-
- // Auto-open tags on page reload
- document.getElementById("auto-open-swt").onclick = (event) => {
- const openMediaPosts = storeData(AUTO_OPEN_MEDIA_POSTS_KEY, toggleCheckbox(event));
- const postsContainer = getPostContainer();
- const wasProcessed = postsContainer.id === processedPageChecker;
-
- if (!postsContainer)
- return;
-
- if (!wasProcessed)
- postsContainer.setAttribute("id", processedPageChecker);
-
- // Open posts if they weren't already
- if (openMediaPosts) {
- openPosts(postsContainer);
- }
- }
-
- // Unblur setting
- document.getElementById("unblur-swt").onclick = (event) => {
- storeData(UNBLUR_THUMBNAILS_KEY, toggleCheckbox(event));
-
- // Update CSS of site after having changed the setting
- updateCSS();
- }
-
- // Mark new posts as NSFW by default
- document.getElementById("default-nsfw-posts-swt").onclick = (event) => {
- const markAsNSFW = storeData(MARK_POSTS_AS_NSFW_KEY, toggleCheckbox(event));
- const NSFWCheckbox = document.querySelector(postAsNSFW);
- const createPostForm = document.getElementById(createPostContainerId);
-
- if (NSFWCheckbox && !NSFWCheckbox.checked && markAsNSFW && createPostForm)
- NSFWCheckbox.click();
- }
-
- // Auto-open tags on page reload
- document.getElementById("use-commas-swt").onclick = (event) => {
- storeData(COMMAS_AS_SEPARATORS_KEY, toggleCheckbox(event));
- }
-
- // Auto-open tags on page reload
- document.getElementById("case-sensitive-swt").onclick = (event) => {
- storeData(CASE_SENSITIVE_KEY, toggleCheckbox(event));
- }
-
- // Accept pressing enter while in some input to send the data
- document.getElementById("tag-input").onkeydown = (event) => {
- if(event.keyCode === 13){
- document.getElementById("tag-save-btn").click();
- }
- }
-
- // Event for tag save on button click or enter on input of tag blocking
- document.getElementById("tag-save-btn").onclick = () => {
- const tagInput = document.getElementById("tag-input");
- const errorEl = document.getElementById("blocked-tags-input-error");
- const clipboardNoticeEl = document.getElementById("clipboard-notice");
- const tagsToSubmit = document.getElementById("tag-input").value;
- clipboardNoticeEl.setAttribute("hidden", true);
-
- if (tagsToSubmit.trim().length > 1) {
- const isCaseSensitive = getData(CASE_SENSITIVE_KEY);
- const splitOnCommas = getData(COMMAS_AS_SEPARATORS_KEY);
- let tagsAsArray = getTagsAsArray(tagsToSubmit, splitOnCommas);
- let tagsKey;
-
- if (isCaseSensitive)
- tagsKey = BLOCKED_CASE_SENSITIVE_TAGS_KEY;
- else
- tagsKey = BLOCKED_TAGS_KEY;
-
- const oldTags = getData(tagsKey);
-
- tagsAsArray = tagsAsArray.filter(tag => !oldTags.includes(tag));
-
- for(let i = 0; i < tagsAsArray.length; i++) {
- const keywordStart = /^(regex:|source:(regex(\((.*?)\):|:))*|regex\((.*?)\):)/ig;
- console.log(tagsAsArray[i].replace(keywordStart, ""));
- if (tagsAsArray[i].replace(keywordStart, "").trim().length === 0) {
- errorEl.removeAttribute("hidden");
- return;
- }
- }
-
- // Hide the error message if it was visible
- if (errorEl.hasAttribute("hidden"))
- errorEl.setAttribute("hidden", true);
-
- if (tagsAsArray.length > 0) {
- // Get the old array of tags and concatenate the new one
- const allTags = oldTags.concat(tagsAsArray);
-
- // Store the new array of tags and update the tags to show
- storeData(tagsKey, allTags);
- addTagsToDialog(tagsAsArray, tagsKey);
-
- // Hide empty tags element if it was visible and show the tags
- const blockedTagsListContainer = document.getElementById(blockTagListId);
- const csBlockedTagsListContainer = document.getElementById(csBlockTagListId);
-
- // Update tag section visibility if required
- checkForEmptyTags(getData(BLOCKED_TAGS_KEY), getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY));
- }
-
- // Clear input
- tagInput.value = "";
- } else {
- errorEl.removeAttribute("hidden");
- }
- }
-
- //----------------------------------------
- // MAIN METHODS
- //----------------------------------------
-
- // Wait for page to fully load to start doing things
- window.onload = () => {
- const baseContainer = document.getElementById("app");
- let NSFWCheckbox = document.querySelector(postAsNSFW);
- let createPostForm = document.getElementById(createPostContainerId);
- let searchContainer = document.getElementsByClassName(searchContainerClass)[0];
- let postsContainer = getPostContainer();
-
- if (NSFWCheckbox && !NSFWCheckbox.checked && markNewPostsAsNSFW && createPostForm)
- NSFWCheckbox.click();
-
- // Make sure that the widget height is correct
- updateWidgetHeightCSS();
-
- // If there is a posts container in the page
- if (postsContainer) {
-
- // Perform first filter
- if (blockedTitleTags.length > 0 || csBlockedTitleTags.length > 0) {
- console.info("Page loaded, filtering tags...");
- filterPosts(postsContainer);
- }
-
- // Open remaining posts if enabled
- if (expandMediaPosts)
- openPosts(postsContainer);
-
- // Fix video previews if there was any post
- if (fixPostVideoPreviews)
- fixBrokenVideoPreviews(postsContainer);
-
- document.getElementsByClassName(postsContainer.className)[0].setAttribute("id", processedPageChecker);
- }
-
- // Observe the changes of the page to know when to rethrow the filter method when the user changes the page
- const observer = new MutationObserver((e) => {
- if (document.getElementById(processedPageChecker))
- return;
-
- const blockedTags = getData(BLOCKED_TAGS_KEY);
- const csBlockedTags = getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY);
- const autoOpenMedia = getData(AUTO_OPEN_MEDIA_POSTS_KEY);
- const fixVideoPreviews = getData(FIX_BROKEN_VIDEO_PREVIEWS_KEY);
- const markAsNSFW = getData(MARK_POSTS_AS_NSFW_KEY);
-
- searchContainer = document.getElementsByClassName(searchContainerClass)[0];
- NSFWCheckbox = document.querySelector(postAsNSFW);
- createPostForm = document.getElementById(createPostContainerId);
- postsContainer = getPostContainer();
-
- // If on the creation post page, mark as NSFW the post
- if (NSFWCheckbox && !NSFWCheckbox.checked && markAsNSFW && createPostForm)
- NSFWCheckbox.click();
-
- for (let i = 0; i < e.length; i++) {
- if (e[i].target.getElementsByClassName(editPostClass)[0])
- return;
-
- let postEdit = e[i].target.getElementsByClassName("form-control")[0];
- let filteredPostStub = e[i].target.getElementsByClassName("filtered-post-stub")[0];
- let filteredPostBtn = e[i].target.getElementsByClassName("hide-post-btn")[0];
-
- // Prevent filtering to retrigger if editting a post
- if (postEdit && searchContainer)
- return;
-
- // If a post was already filtered don't add stubs again
- if (filteredPostBtn || filteredPostStub)
- continue;
-
- // Perform actions if on one of the pages
- if (postsContainer) {
- if (blockedTags.length > 0 || csBlockedTags.length > 0) {
- console.info("Page reloaded, filtering new posts...");
- filterPosts(postsContainer);
- }
-
- if (autoOpenMedia)
- openPosts(postsContainer);
-
- if (fixVideoPreviews)
- fixBrokenVideoPreviews(postsContainer);
-
- // Mark page as already processed
- document.getElementsByClassName(postsContainer.className)[0].setAttribute("id", processedPageChecker);
- break;
- }
- }
- });
-
- observer.observe(baseContainer, {subtree: true, childList: true});
- }
-
- // Gets initial CSS content
- function getInitialCSS() {
- let style = document.head.getElementsByTagName("style")[0];
- if (!style) {
- style = document.createElement('style');
- document.head.appendChild(style);
- return "";
- }
-
- return style.innerHTML;
- }
-
- // Gets the CSS from the script to be in effect
- function getEffectiveCSS() {
- let fullCSS = guiCSS();
-
- // Check each setting that changes CSS apart from the GUI
- if (getData(UNBLUR_THUMBNAILS_KEY))
- fullCSS += unblurCSS;
-
- return fullCSS;
- }
-
- // Splits tags on commas if necessary
- function getTagsAsArray(tagsString, splitOnCommas=true) {
- if (splitOnCommas) {
- return tagsString.split(",");
- }
- return [tagsString];
- }
-
- // Gets the current page posts container
- function getPostContainer() {
- return document.getElementsByClassName(postsContainerClass)[0] ||
- document.getElementsByClassName(profileContainerClass)[0] ||
- document.getElementsByClassName(searchContainerClass)[0] ||
- document.querySelector(".post");
- }
-
- // Adds an element to the document
- function addElement(parentEl, elementTag, elementId, html="", idAsClass=false) {
- let p = parentEl;
- let newElement = document.createElement(elementTag);
- if (idAsClass)
- newElement.className = elementId;
- else
- newElement.setAttribute('id', elementId);
- newElement.innerHTML = html;
- p.appendChild(newElement);
- return newElement;
- }
-
- // Adds the new tags to the dialog
- function addTagsToDialog(tagArray, tagsType) {
- let tagsContainerSelector = csBlockTagListId;
-
- if (tagsType === BLOCKED_TAGS_KEY)
- tagsContainerSelector = blockTagListId;
-
- const blockedTagsListEl = document.getElementById(tagsContainerSelector);
-
- tagArray.forEach(tag => {
- addTagElement(tag, tagsType, blockedTagsListEl);
- });
- }
-
- // Adds a tag element to the dialog
- function addTagElement(tag, tagType, tagListElement) {
-
- // Get the specific css class for tag type
- let tagSpecificClass = "case-sensitive-tag";
-
- if (tagType === BLOCKED_TAGS_KEY)
- tagSpecificClass = "case-insensitive-tag";
-
- // Add the element to the dialog and bind the button event
- const newTag = addElement(tagListElement,
- "LI",
- `blocked-tag ${tagSpecificClass}`,
- tagContent
- .replace("TAG-NAME", parseTagName(tag))
- .replace("TAG-TYPE", tagType)
- .replace("TOOLTIP-CONTENT", `'${tag}' (${tagSpecificClass})`),
- true);
-
- newTag.getElementsByTagName("button")[0].onclick = () => {
- onRemoveBlockedTag(tagType, tag, newTag);
- }
-
- newTag.getElementsByTagName("label")[0].onclick = () => {
- GM_setClipboard(tag);
- document.getElementById("clipboard-notice").removeAttribute("hidden");
- }
-
- // Mark element as draggable
- newTag.setAttribute("draggable", true);
-
- // Handle drag & drop events
- newTag.ondragstart = (e) => {
- newTag.setAttribute("data-dragged-item", true);
- }
-
- newTag.ondragover = (e) => {
- e.preventDefault();
- }
-
- newTag.ondragend = () => {
- newTag.removeAttribute("data-dragged-item");
- }
-
- newTag.ondrop = (e) => {
- const target = e.target;
- const targetTag = target.nodeName === "LABEL" || target.nodeName == "BUTTON" ?
- target.parentNode.parentNode : target.parentNode;
- const draggedItem = document.querySelector('li[data-dragged-item]');
- const nextTagName = targetTag.getElementsByTagName("label")[0].innerHTML;
- const movedTagName = draggedItem.getElementsByTagName("label")[0].innerHTML;
- let movedTagParentId = targetTag.parentNode.id;
- let draggedTagType = BLOCKED_TAGS_KEY;
-
- if (draggedItem.className.includes("case-sensitive-tag"))
- draggedTagType = BLOCKED_CASE_SENSITIVE_TAGS_KEY;
-
- // Only accept dropping it in the same area
- if (draggedItem.parentNode.id === movedTagParentId) {
-
- // Update order of tags
- let tags = getData(draggedTagType);
-
- // We need to change a bit the logic depending on the position of the element
- if (tags.indexOf(nextTagName) > tags.indexOf(movedTagName)) {
- tags.splice(tags.indexOf(nextTagName) + 1, 0, null);
- targetTag.parentNode.insertBefore(draggedItem, targetTag.nextSibling);
- } else {
- tags.splice(tags.indexOf(nextTagName), 0, null);
- targetTag.parentNode.insertBefore(draggedItem, targetTag);
- }
-
- tags.splice(tags.indexOf(movedTagName), 1);
- tags[tags.indexOf(null)] = movedTagName;
-
- storeData(draggedTagType, tags);
- }
- newTag.removeAttribute('data-dragged-item');
- }
- }
-
- // Updates the CSS of the site
- function updateCSS() {
- let style = document.head.getElementsByTagName("style")[0];
- if (!style) {
- style = document.createElement('style');
- style.innerHTML = css;
- document.head.appendChild(style);
- return;
- }
-
- // Update the CSS with the initial one + the settings CSS
- style.innerHTML = initialCSS + getEffectiveCSS();
- }
-
- // Update the visible part of the settings widget
- function updateWidgetHeightCSS() {
- const widget = document.getElementById(settingsWidgetContainerId);
- const widgetHeight = widget.getBoundingClientRect().height;
- const visibleTopHeight = 10;
- if (widgetHeight > 0 && widgetHeight - visibleTopHeight > 0) {
- const oldHeight = getData(WIDGET_HEIGHT_KEY);
- let updatedHeight = (widgetHeight - visibleTopHeight) + "px";
- if (oldHeight != updatedHeight) {
- storeData(WIDGET_HEIGHT_KEY, (widgetHeight - visibleTopHeight) + "px");
- updateCSS();
- }
- }
- }
-
- // Updates the visible tags in the blocked tags dialog
- function updateVisibleTags() {
- const csBlockedTags = getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY);
- const blockedTags = getData(BLOCKED_TAGS_KEY);
- const blockedTagsListEl = document.getElementById(blockTagListId);
- const csBlockedTagsListEl = document.getElementById(csBlockTagListId);
-
- // Reset list
- blockedTagsListEl.innerHTML = "";
- csBlockedTagsListEl.innerHTML = "";
-
- blockedTags.forEach(tag => {
- addTagElement(tag, BLOCKED_TAGS_KEY, blockedTagsListEl);
- });
-
- csBlockedTags.forEach(tag => {
- addTagElement(tag, BLOCKED_CASE_SENSITIVE_TAGS_KEY, csBlockedTagsListEl);
- });
-
- checkForEmptyTags(blockedTags, csBlockedTags);
- }
-
- // Parses the tag name if it's a named regex
- function parseTagName(tag) {
- if ((tag.toLowerCase().startsWith("regex(") || tag.toLowerCase().startsWith("source:regex(")) && new RegExp(namedRegex, "i").test(tag)) {
- let regexName = getRegexNameFromTag(tag);
- if (regexName != null) {
- return tag.replace(new RegExp(namedRegex + ".*", "i"), `regex: ${regexName}`);
- }
- // If the named regex wasn't correctly formatted we just return the regular tag however it was typed
- return tag;
- }
- return tag;
- }
-
- // Gets the regex name from a named regex tab
- function getRegexNameFromTag(tag) {
- let regexMatch = new RegExp(namedRegex).exec(tag);
- if (regexMatch != null) {
- return regexMatch[1];
- }
- return null;
- }
-
- // Toggles checkboxes on sliders inside the onclick event
- function toggleCheckbox(event) {
- let checkbox = event.target.parentNode.getElementsByTagName("input")[0];
- checkbox.checked = !checkbox.checked;
- return checkbox.checked;
- }
-
- // Opens a dialog box
- function openDialog(dialogSelector) {
- dialogSelector.show();
- dialogBg.style.display = "block";
- }
-
- // Closes the dialog box
- function closeDialog(dialogSelector) {
- dialogSelector.close();
- dialogBg.style.display = "none";
- document.getElementById("blocked-tags-input-error").setAttribute("hidden", true);
- document.getElementById("clipboard-notice").setAttribute("hidden", true);
- }
-
- // Adds the notice for no blocked tag if necessary
- function checkForEmptyTags(blockedTags, csBlockedTags) {
- const emptyTagsEl = document.getElementById("empty-blocked-tags");
- const blockedTagsListEl = document.getElementById(blockTagListId);
- const csBlockedTagsListEl = document.getElementById(csBlockTagListId);
- const tagsSeparatorEl = document.getElementById("blocked-tags-separator");
- const hasAnyBlockedTag = blockedTags.length > 0;
- const hasAnyCsBlockedTag = csBlockedTags.length > 0;
- const hasAnyTag = hasAnyBlockedTag || hasAnyCsBlockedTag;
- const isBlockedTagsListHidden = blockedTagsListEl.hasAttribute("hidden");
- const isCsBlockedTagsListHidden = csBlockedTagsListEl.hasAttribute("hidden");
-
- // Check if any has tags
- if (hasAnyTag) {
- emptyTagsEl.setAttribute("hidden", true);
- if (hasAnyBlockedTag)
- blockedTagsListEl.removeAttribute("hidden");
- else
- blockedTagsListEl.setAttribute("hidden", true);
-
- if (hasAnyCsBlockedTag)
- csBlockedTagsListEl.removeAttribute("hidden");
- else
- csBlockedTagsListEl.setAttribute("hidden", true);
-
- // Either both have tags or at least one has tags
- if (hasAnyBlockedTag && hasAnyCsBlockedTag)
- tagsSeparatorEl.removeAttribute("hidden");
- else
- tagsSeparatorEl.setAttribute("hidden", true);
-
- // If it has no tags
- } else if (!hasAnyTag) {
- emptyTagsEl.removeAttribute("hidden");
- blockedTagsListEl.setAttribute("hidden", true);
- tagsSeparatorEl.setAttribute("hidden", true);
- csBlockedTagsListEl.setAttribute("hidden", true);
- }
- }
-
- // Checks if the page is loading
- function checkIfPageIsLoading(postsContainer, posts) {
- const searchContainer = document.getElementsByClassName(searchContainerClass)[0];
- const emptyResultsContainer = document.querySelector(loadingSpinnerSelector);
- return posts.length === 0 && postsContainer === searchContainer && emptyResultsContainer;
- }
-
- // Removes a tag from the view and the storeData
- function onRemoveBlockedTag(tagType, tagToRemove, tagElement) {
- // Remove the actual tag element
- tagElement.parentNode.removeChild(tagElement);
-
- // Update the storage
- let allTags = getData(tagType);
- let tagIndex = allTags.indexOf(tagToRemove);
-
- if (tagIndex != -1)
- allTags.splice(tagIndex, 1);
-
- storeData(tagType, allTags);
-
- // Toggle visibility of the elements where the tag was removed if they are empty
- checkForEmptyTags(getData(BLOCKED_TAGS_KEY), getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY));
- }
-
- // Filters posts by words from the title as specified by the user
- function filterPosts(postsContainer) {
- let posts = postsContainer.getElementsByClassName(postContainer);
-
- if (checkIfPageIsLoading(postsContainer, posts)) {
- let intervalCount = 0;
-
- // Check for posts in 1 second intervals
- const checkInterval = setInterval(() => {
- posts = document.getElementsByClassName(postContainer);
- // Finish interval checking the search finished or 30 seconds passed
- if (posts.length > 0 || intervalCount > 30 || !document.querySelector(loadingSpinnerSelector)) {
- clearInterval(checkInterval);
- filterPostsInPage(posts);
- }
- intervalCount++;
- }, 1000);
-
- return;
- }
-
- filterPostsInPage(posts);
- }
-
- // Filters the posts in the current page
- function filterPostsInPage(postsList) {
- const showStubs = getData(SHOW_FILTERED_STUBS_KEY);
- const blockedTags = getData(BLOCKED_TAGS_KEY);
- const csBlockedTags = getData(BLOCKED_CASE_SENSITIVE_TAGS_KEY);
-
- const blockedTagsByType = {
- byTitle: [],
- bySource: []
- }
-
- const csBlockedTagsByType = {
- byTitle: [],
- bySource: []
- }
-
- // Get type of filter for each tag first to avoid unnecesary iterations
- blockedTags.forEach(tag => {
- if (tag.startsWith("source:"))
- blockedTagsByType.bySource.push(tag);
- else
- blockedTagsByType.byTitle.push(tag);
- })
-
- csBlockedTags.forEach(tag => {
- if (tag.startsWith("source:"))
- csBlockedTagsByType.bySource.push(tag);
- else
- csBlockedTagsByType.byTitle.push(tag);
- })
-
- // Filter every post
- Array.from(postsList).forEach(post => {
- if (post.getElementsByClassName(editPostClass)[0])
- return;
-
- post.setAttribute(filteredPostChecker, true);
- const communityEl = post.querySelector(postCommunityNameContainer);
- const communityLinkEl = post.querySelector(postCommunityContainer);
- const sourceEl = post.querySelector(postSrcLink)
- let titleEl = post.querySelector(".post-title h1 span");
- let source = null;
- let community;
- let communityLink;
- let title;
-
- // Make sure we get a valid title selector
- if (!titleEl)
- titleEl = post.querySelector(".post-title h1 a");
- title = titleEl.innerHTML;
-
- // Make sure community info is in post, otherwise get from page assuming the user
- // is in the community view instead, where the community isn't in the posts but the page
- if (!communityEl) {
- community = document.querySelector(postCommunityNameContainer).innerHTML;
- communityLink = document.querySelector(postCommunityContainer).href;
- } else {
- community = communityEl.innerHTML;
- communityLink = communityLinkEl.href;
- }
-
- if (sourceEl)
- source = sourceEl.href;
-
- const postInfo = {
- post: post,
- title: title,
- communityName: community,
- communityLink: communityLink,
- source: source
- }
-
- // Filter posts by title first
- const wasFiltered = filterPost(blockedTagsByType.byTitle, csBlockedTagsByType.byTitle, showStubs, postInfo);
-
- // Filter posts by source after if it wasn't already filtered by title
- if (!wasFiltered && source)
- filterPost(blockedTagsByType.bySource, csBlockedTagsByType.bySource, showStubs, postInfo, true);
- })
- }
-
- function filterPost(blockedTags, csBlockedTags, showStubs, postInfo, filterBySource=false) {
- // Filter posts using case insensitive tags
- if (blockedTags.length > 0) {
- for(let i = 0; i < blockedTags.length; i++) {
- let tag = blockedTags[i];
-
- if (filterBySource)
- tag = tag.substring(7);
-
- const regex = new RegExp(escapeRegex(tag), "i");
- if (regex.test(filterBySource ? postInfo.source : postInfo.title)) {
- removePost(postInfo, blockedTags[i], showStubs);
-
- return true;
- }
- }
- }
-
- // Filter posts using case sensitive tags
- if (csBlockedTags.length > 0) {
- for(let i = 0; i < csBlockedTags.length; i++) {
- const tag = csBlockedTags[i];
-
- if (filterBySource)
- tag = tag.substring(7);
-
- const regex = new RegExp(escapeRegex(tag));
- if (regex.test(filterBySource ? postInfo.source : postInfo.title)) {
- removePost(postInfo, csBlockedTags[i], showStubs);
- return true;
- }
- }
- }
- }
-
- // Escapes the special characters entered by a user
- function escapeRegex(regex) {
- // Escape \'s in regex with double \\ if it's a regex to be used in "new RegExp()" method
- if (regex.toLowerCase().startsWith("regex:"))
- return regex.substring(6).replace("\\", "\\");
-
- // If it's a named regex
- else if (regex.toLowerCase().startsWith("regex(") && new RegExp(namedRegex, "i").test(regex)) {
- const regStart = `regex(${getRegexNameFromTag(regex)}):`;
- return regex.substring(regStart.length).replace("\\", "\\");
- }
- return regex.replace(/([()[{*+.$^\\|?])/g, '\\$1');
-
- }
-
- // Filters out a post
- function removePost(postInfo, tag, showStubs) {
- const post = postInfo.post;
-
- // Hide the content of the children and add a stub by default
- addFilterStubToPost(postInfo, tag)
- hidePostChildren(post);
-
- // If the setting to show stubs is disabled we hide the post completelly
- if (!showStubs) {
- // Removes the posts from the body completelly and logs it to the console
- console.info(`Removing post with title ${postInfo.title} from community ${postInfo.communityName} because the post contained the tag ${parseTagName(tag)}.`);
- post.setAttribute("hidden", true);
-
- let siblingNode = post.nextElementSibling;
-
- // Remove separator if it exists
- if (siblingNode && siblingNode.tagName == "HR")
- siblingNode.setAttribute("hidden", true);
- }
- }
-
- // Hides all elements of the post children
- function hidePostChildren(post) {
- for (let i = 0; i < post.children.length; i++) {
- let child = post.children[i];
- if (child.className === "filtered-post-stub" || child.className === "hide-post-btn")
- continue;
- child.style.height = "0";
- child.style.width = "0";
- child.style.visibility = "hidden";
- }
- }
-
- // Adds a filter stub after filtering a post
- function addFilterStubToPost(postInfo, tag) {
- let stub = addElement(postInfo.post, "DIV", "filtered-post-stub",
- filteredPostStubContent.replace("TAG", parseTagName(tag))
- .replace("POST-TITLE", postInfo.title)
- .replace("POST-COMMUNITY", postInfo.communityName), true);
- stub.getElementsByClassName("stub-hidden-post-community-link")[0].href = postInfo.communityLink;
-
- // Add actions for the buttons
- stub.getElementsByClassName("show-hidden-post-title-btn")[0].onclick = () => {
- stub.getElementsByClassName("stub-hidden-post-title")[0].removeAttribute("hidden");
- stub.getElementsByClassName("show-hidden-post-title-btn")[0].setAttribute("hidden", true);
- }
-
- stub.getElementsByClassName("show-hidden-post-btn")[0].onclick = () => {
- stub.setAttribute("hidden", true);
- postInfo.post.style= "";
-
- // Make sure the title button element is hidden
- stub.getElementsByClassName("stub-hidden-post-title")[0].setAttribute("hidden", true);
- stub.getElementsByClassName("show-hidden-post-title-btn")[0].removeAttribute("hidden");
-
- // Prepend the post re-remover if it didn't exist yet
- let postRemoveEl = postInfo.post.getElementsByClassName("hide-post-btn")[0];
- if (!postInfo.post.getElementsByClassName("hide-post-btn")[0]) {
- postRemoveEl = document.createElement("BUTTON");
- postRemoveEl.className = "hide-post-btn";
- postRemoveEl.innerHTML = "🗶";
- postRemoveEl.setAttribute("type", "button");
- postInfo.post.prepend(postRemoveEl);
-
- postRemoveEl.onclick = () => {
- stub.removeAttribute("hidden");
- postRemoveEl.setAttribute("hidden", true);
- hidePostChildren(postInfo.post);
- }
- } else
- postRemoveEl.removeAttribute("hidden");
-
-
- for (let i = 0; i < postInfo.post.children.length; i++) {
- postInfo.post.children[i].style = "";
- }
- }
- }
-
- // Opens media posts so that the image or video is shown for all posts in the current page of the timeline
- function openPosts(postsContainer) {
- let posts = postsContainer.getElementsByClassName(postContainer);
-
- if (checkIfPageIsLoading(postsContainer, posts)) {
- let intervalCount = 0;
-
- // Check for posts in 1 second intervals
- const checkInterval = setInterval(() => {
- posts = document.getElementsByClassName(postContainer);
-
- // Finish interval checking the search finished or 30 seconds passed
- if (posts.length > 0 || intervalCount > 30 || !document.querySelector(loadingSpinnerSelector)) {
- clearInterval(checkInterval);
- clickPostsThumbnail(posts);
- }
- intervalCount++;
- }, 1000);
-
- return;
- }
-
- clickPostsThumbnail(posts)
- }
-
- // Clicks the thumbnail of the post to open it
- function clickPostsThumbnail(posts) {
- Array.from(posts).forEach(post => {
- const postHasMedia = post.querySelector("picture, video");
- let clickableThumbnail = post.querySelector("button.thumbnail");
- if (postHasMedia) {
- if (!clickableThumbnail)
- clickableThumbnail = post.querySelector("a.text-body[href$='.mp4'] .thumbnail, a.text-body[href$='.webm'] .thumbnail, a.text-body[href^='https://www.redgifs.com/watch'] .thumbnail")
-
- if (clickableThumbnail && !post.getElementsByClassName("filtered-post-stub")[0])
- clickableThumbnail.click();
- post.setAttribute(openedPostChecker, true);
- }
- })
- }
-
- // Fixes imgur previews showing as jpg instead of the actual video
- function fixBrokenVideoPreviews(postsContainer) {
- let posts = postsContainer.getElementsByClassName(postContainer);
-
- if (checkIfPageIsLoading(postsContainer, posts)) {
- let intervalCount = 0;
-
- // Check for posts in 1 second intervals
- const checkInterval = setInterval(() => {
- posts = document.getElementsByClassName(postContainer);
- // Finish interval checking the search finished or 30 seconds passed
- if (posts.length > 0 || intervalCount > 30 || !document.querySelector(loadingSpinnerSelector)) {
- clearInterval(checkInterval);
- updateVideoPreviews(posts);
- }
- intervalCount++;
- }, 1000);
-
- return;
- }
- updateVideoPreviews(posts);
- }
-
- // Updates the video previews when needed so that they work again
- function updateVideoPreviews(posts) {
- Array.from(posts).forEach(post => {
- const postSourceLinkEl = post.querySelector(postSrcLink);
- const imageLinkContainer = post.querySelectorAll("div:nth-of-type(3) > a:not(.btn)");
-
- if (postSourceLinkEl && imageLinkContainer.length > 0) {
- const postSourceLink = postSourceLinkEl.href;
- let newSrc = postSourceLink;
-
- if (postSourceLink.includes("i.imgur.com") && postSourceLink.endsWith(".gifv"))
- newSrc = postSourceLink.replace(".gifv", ".mp4");
- else if (postSourceLink.includes("imgur.com"))
- newSrc = postSourceLink.replace("imgur.com", "i.imgur.com") + ".mp4";
-
- if (postSourceLink.includes("i.redd.it") && postSourceLink.endsWith(".gif"))
- newSrc = imageLinkContainer[0].href;
-
- // Only apply changes if the src was suposed to be different
- if (newSrc != postSourceLink) {
- imageLinkContainer.forEach(imageContainer => {
- let pictureContainer = imageContainer.querySelector("picture");
- if (pictureContainer) {
- pictureContainer.setAttribute("hidden", true);
- let videoElement = addElement(post.querySelector("div:nth-child(3).my-2"), "VIDEO",
- `embed-responsive-item ${fixedPreviewVideoClass} col-12`,
- videoSourceContent.replace("VIDEO-SOURCE", newSrc)
- .replace("VIDEO-TYPE", newSrc.split(".").at(-1)),
- true)
- videoElement.setAttribute("controls", "");
- videoElement.parentNode.classList.add("embed-responsive", fixedPreviewContainerClass);
- }
- });
- }
- }
- });
- }
- //----------------------------------------
- // STORAGE METHODS
- //----------------------------------------
-
- // Composes the key with the current instance name to store data per-instance
- function composeInstanceKey(key) {
- return document.location.host + "->" + key;
- }
-
- // Saves data to the storage of the userscript
- function storeData(key, value) {
- const instanceKey = composeInstanceKey(key);
- let treatedValue = value;
- if (typeof value === "array" || typeof value === "object")
- treatedValue = JSON.stringify(value);
- GM_setValue(instanceKey, treatedValue);
- return treatedValue;
- }
-
- // Gets data from the userscript storage
- function getData(key) {
- const instanceKey = composeInstanceKey(key);
- let value = GM_getValue(instanceKey);
-
- if (value === null || value === undefined)
- return null;
-
- if (typeof value === "string") {
- let isValueArray = value.startsWith("[") && value.endsWith("]");
- let isValueObject = value.startsWith("{") && value.endsWith("}");
- if (isValueArray || isValueObject)
- return JSON.parse(value);
- }
- return value;
- }