NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @id https://github.com/kvr000/zbynek-strava-util/ZbynekStravaFilterClubUnwanted/ // @name Zbynek Strava Filter Club Unwanted // @namespace https://github.com/kvr000/zbynek-strava-util/ // @description Strava - filter unwanted members from club activities // @author Zbynek Vyskovsky, kvr000@gmail.com https://github.com/kvr000/ // @copyright 2020+, Zbynek Vyskovsky,kvr000@gmail.com (https://github.com/kvr000/zbynek-strava-util/) // @license Apache-2.0 // @homepage https://github.com/kvr000/zbynek-strava-util/ // @homepageURL https://github.com/kvr000/zbynek-strava-util/ // @downloadURL https://raw.githubusercontent.com/kvr000/zbynek-strava-util/master/ZbynekStravaFilterClubUnwanted/ZbynekStravaFilterClubUnwanted.user.js // @updateURL https://raw.githubusercontent.com/kvr000/zbynek-strava-util/master/ZbynekStravaFilterClubUnwanted/ZbynekStravaFilterClubUnwanted.user.js // @supportURL https://github.com/kvr000/zbynek-strava-util/issues/ // @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J778VRUGJRZRG&item_name=Support+features+development.¤cy_code=CAD&source=url // @version 1.0.1 // @grant GM_addStyle // @include https://www.strava.com/clubs/*/recent_activity // @include http://www.strava.com/clubs/*/recent_activity // @include https://strava.com/clubs/*/recent_activity // @include http://strava.com/clubs/*/recent_activity // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // @run-at document-idle // ==/UserScript== /*jshint loopfunc:true */ (function() { 'use strict'; let $ = window.jQuery; let unwanted; try { unwanted = JSON.parse(window.localStorage.getItem('ZbynekStravaClubFilterUnwanted.unwantedAthletes')) || {}; } catch (err) { console.log(err); unwanted = {}; } function needXpath(doc, xpath, start) { let node; if ((node = doc.evaluate(xpath, start, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue) != null) { return node; } throw new Error("Cannot find node: " + xpath); } function listXpath(doc, xpath, start) { let elements = []; for (let xpathOut = doc.evaluate(xpath, start), el = null; (el = xpathOut.iterateNext()); ) { elements.push(el); } return elements; } function removeXpath(doc, xpath, start) { let node; if ((node = doc.evaluate(xpath, start, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue) != null) { node.remove(); } } function createElementEx(doc, name, attrs, children) { let element = doc.createElement(name); if (attrs) { $.each(attrs, function (k, v) { if (k === 'class') element.setAttribute(k, v); else element[k] = v; }); } if (children) { if (!$.isArray(children)) { throw new Error("Passed non-array as children object: "+children); } $.each(children, function (k, v) { element.appendChild(v); }); } return element; } function initializeUi() { GM_addStyle( ".zbynek-strava-club-filter-unwanted-menu { position: absolute; display: inline-block }\n"+ ".zbynek-strava-club-filter-unwanted-menu .items { display: none; position: absolute; padding: 12px 16px; background-color: #ffcccc }"+ ".zbynek-strava-club-filter-unwanted-menu:hover .items { display: block; }" ); } function processUnwantedAthletes() { let activities = listXpath(document, "//*[@data-react-class = 'Activity' or @data-react-class = 'GroupActivity']//*[./div[@data-testid = 'entry-header']]", document); $.each(activities, function (i, activityEl) { try { let containerEl = document.evaluate("ancestor::div[contains(concat(' ', @class, ' '), ' react-card-container ')]", activityEl, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; let athleteId = document.evaluate(".//a[@id = 'zbynek-strava-club-filter-unwanted-hide']", activityEl, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue.athleteId; let hide = unwanted[athleteId]; if (hide === true) { containerEl.style.display = 'none'; } else { containerEl.style.display = 'block'; } } catch (err) { console.log(err); } }); } function hideUnwantedAthlete(athleteId) { unwanted[athleteId] = true; window.localStorage.setItem('ZbynekStravaClubFilterUnwanted.unwantedAthletes', JSON.stringify(unwanted)); processUnwantedAthletes(); } function updateFeed() { let tabsEl = needXpath(document, "//div[@class = 'spans11']/ul[@class = 'tabs']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE); removeXpath(document, "./li[div[@class = 'zbynek-strava-club-filter-unwanted-tab-menu']]", tabsEl); tabsEl.appendChild( createElementEx(document, "li", null, [ createElementEx(document, "div", { class: 'zbynek-strava-club-filter-unwanted-menu tab' }, [ document.createTextNode("Unwanted members"), createElementEx(document, "ul", { class: 'items' }, [ createElementEx(document, "li", { onclick: updateFeed }, [ document.createTextNode('Refresh') ]), createElementEx(document, "li", { onclick: function() { unwanted = {}; hideUnwantedAthlete(""); } }, [ document.createTextNode('Reset') ]), createElementEx(document, "li", null, [ createElementEx(document, "a", { href: "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J778VRUGJRZRG&item_name=Support+features+development.¤cy_code=CAD&source=url", target: "_blank" }, [ document.createTextNode('Donate and support development') ]) ]), ]) ]) ]) ); let activities = listXpath(document, "//*[@data-react-class = 'Activity' or @data-react-class = 'GroupActivity']//*[./div[@data-testid = 'entry-header']]", document); $.each(activities, function (i, activityEl) { try { let athleteId = document.evaluate("div//a[@data-testid = 'owner-avatar']/@href", activityEl, null, XPathResult.STRING_TYPE).stringValue; let menuEl = createElementEx(document, "div", { style: "float: right;", id: 'zbynek-strava-club-filter-unwanted-menu' }, [ createElementEx(document, "a", { athleteId: athleteId, id: 'zbynek-strava-club-filter-unwanted-hide', onclick: function() { hideUnwantedAthlete(athleteId); } }, [ document.createTextNode("Filter out athlete") ]), ]); removeXpath(document, ".//div[@id = 'zbynek-strava-club-filter-unwanted-menu']", activityEl); document.evaluate(".//div[@data-testid = 'share']", activityEl, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue.insertAdjacentElement('beforebegin', menuEl); } catch (err) { console.log(err); } }); processUnwantedAthletes(); } function setupListener() { let feedEl = needXpath(document, "//div[@class = 'spans11']//div[contains(concat(' ', @class, ' '), ' feed ')]", document); new MutationObserver(updateFeed).observe(feedEl, { attributes: false, childList: true, subtree: false }); } initializeUi(); updateFeed(); setupListener(); })();