diff --git a/assets/icon-128-greyscale.png b/assets/icon-128-greyscale.png
new file mode 100644
index 0000000..5160d01
Binary files /dev/null and b/assets/icon-128-greyscale.png differ
diff --git a/constants.js b/constants.js
index 222cb4f..f595cea 100644
--- a/constants.js
+++ b/constants.js
@@ -20,8 +20,10 @@ export const DefaultOptions = {
skipVerified: true,
skipAffiliated: true,
skip1Mplus: true,
+ skipFollowerCount: 1e6,
blockNftAvatars: false,
blockInterval: 15,
+ popupTimer: 30,
// this isn't set, but is used
// TODO: when migrating to firefox manifest v3, check to see if sets can be stored yet
diff --git a/injected/inject.js b/injected/inject.js
index e34d344..3efd711 100644
--- a/injected/inject.js
+++ b/injected/inject.js
@@ -21,7 +21,17 @@
// determine if request is a timeline/tweet-returning request
const parsedUrl = RequestRegex.exec(this._url);
if(this._url && parsedUrl) {
- document.dispatchEvent(new CustomEvent("blue-blocker-event", { detail: { url : this._url, parsedUrl, body: this.response, request: { headers: this._requestHeaders } } }));
+ document.dispatchEvent(new CustomEvent("blue-blocker-event", {
+ detail: {
+ parsedUrl,
+ url : this._url,
+ body: this.response,
+ request: {
+ headers: this._requestHeaders,
+ },
+ status: this.status,
+ },
+ }));
}
});
return send.apply(this, arguments);
diff --git a/makefile b/makefile
index 06df58f..80410d6 100644
--- a/makefile
+++ b/makefile
@@ -10,6 +10,7 @@ firefox:
mv manifest.json chrome-manifest.json
mv firefox-manifest.json manifest.json
zip "blue-blocker-firefox-${VERSION}.zip" \
+ assets/icon-128-greyscale.png \
assets/icon-128.png \
assets/icon.png \
assets/error.png \
@@ -17,6 +18,7 @@ firefox:
models/* \
parsers/* \
popup/* \
+ pages/* \
manifest.json \
LICENSE \
readme.md \
@@ -30,6 +32,7 @@ chrome:
# rm "blue-blocker-chrome-${VERSION}.zip"
# endif
zip "blue-blocker-chrome-${VERSION}.zip" \
+ assets/icon-128-greyscale.png \
assets/icon-128.png \
assets/icon.png \
assets/error.png \
@@ -37,6 +40,7 @@ chrome:
models/* \
parsers/* \
popup/* \
+ pages/* \
manifest.json \
LICENSE \
readme.md \
diff --git a/models/block_counter.js b/models/block_counter.js
index a41286f..689a4d4 100644
--- a/models/block_counter.js
+++ b/models/block_counter.js
@@ -23,7 +23,7 @@ export class BlockCounter {
// try to access the critical point
await this.storage.set({ [criticalPointKey]: { refId: this._refId, time: (new Date()).valueOf() + interval * 1.5 } });
await new Promise(r => setTimeout(r, 10)); // wait a second to make sure any other sets have resolved
- cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey].refId;
+ cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey]?.refId;
}
else {
// sleep for a little bit to let the other tab(s) release the critical point
diff --git a/models/block_queue.js b/models/block_queue.js
index d7d546e..bc02810 100644
--- a/models/block_queue.js
+++ b/models/block_queue.js
@@ -23,7 +23,7 @@ export class BlockQueue {
// try to access the critical point
await this.storage.set({ [criticalPointKey]: { refId: this._refId, time: (new Date()).valueOf() + interval * 1.5 } });
await new Promise(r => setTimeout(r, 10)); // wait a second to make sure any other sets have resolved
- cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey].refId;
+ cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey]?.refId;
}
else {
// sleep for a little bit to let the other tab(s) release the critical point
diff --git a/models/queue_consumer.js b/models/queue_consumer.js
index f6ca6af..9e7a56f 100644
--- a/models/queue_consumer.js
+++ b/models/queue_consumer.js
@@ -33,7 +33,7 @@ export class QueueConsumer {
// try to access the critical point
await this.storage.set({ [criticalPointKey]: { refId: this._refId, time: (new Date()).valueOf() + this._interval * 1.5 } });
await new Promise(r => setTimeout(r, 10)); // wait a second to make sure any other sets have resolved
- cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey].refId;
+ cpRefId = (await this.storage.get({ [criticalPointKey]: null }))[criticalPointKey]?.refId;
}
else {
return false;
diff --git a/pages/queue.html b/pages/queue.html
new file mode 100644
index 0000000..f24dda1
--- /dev/null
+++ b/pages/queue.html
@@ -0,0 +1,22 @@
+
+
+
+
+ Blue Blocker Queue
+
+
+
+
+
+
+
+
Block Queue
+
Users will not be added to the queue or blocked while this page is open
+
+ loading...
+
+
+
+
+
+
diff --git a/pages/queue.js b/pages/queue.js
new file mode 100644
index 0000000..1aa092b
--- /dev/null
+++ b/pages/queue.js
@@ -0,0 +1,81 @@
+import { BlockQueue } from "../models/block_queue.js";
+import { FormatLegacyName } from "../utilities.js";
+import { api, logstr } from "../constants.js";
+
+// Define constants that shouldn't be exported to the rest of the addon
+const queue = new BlockQueue(api.storage.local);
+
+// we need to obtain and hold on to the critical point as long as this tab is
+// open so that any twitter tabs that are open are unable to block users
+setInterval(async () => {
+ await queue.getCriticalPoint()
+}, 500);
+
+async function unqueueUser(user_id, safelist) {
+ // because this page holds onto the critical point, we can modify the queue
+ // without worrying about if it'll affect another tab
+ if (safelist) {
+ api.storage.sync.get({ unblocked: { } }).then(items => {
+ items.unblocked[String(user_id)] = null;
+ api.storage.sync.set(items);
+ });
+ }
+
+ const items = await api.storage.local.get({ BlockQueue: [] });
+
+ for (let i = 0; i < items.BlockQueue.length; i++) {
+ if (items.BlockQueue[i].user_id === user_id) {
+ items.BlockQueue.splice(i, 1);
+ break;
+ }
+ }
+
+ await api.storage.local.set(items);
+}
+
+// interval doesn't run immediately, so do that here
+queue.getCriticalPoint()
+.then(() => api.storage.local.get({ BlockQueue: [] }))
+.then(items => {
+ const queueDiv = document.getElementById("block-queue");
+
+ if (items.BlockQueue.length === 0) {
+ queueDiv.textContent = "your block queue is empty";
+ return;
+ }
+
+ queueDiv.innerHTML = null;
+
+ items.BlockQueue.forEach(item => {
+ const { user, user_id } = item;
+ const div = document.createElement("div");
+
+ const p = document.createElement("p");
+ p.innerHTML = `${user.legacy.name} (@${user.legacy.screen_name})`;
+ div.appendChild(p);
+
+ const remove = document.createElement("button");
+ remove.onclick = () => {
+ div.removeChild(remove);
+ unqueueUser(user_id, false).then(() => {
+ queueDiv.removeChild(div);
+ });
+ console.log(logstr, `removed ${FormatLegacyName(user)} from queue`);
+ };
+ remove.textContent = "remove";
+ div.appendChild(remove);
+
+ const never = document.createElement("button");
+ never.onclick = () => {
+ div.removeChild(never);
+ unqueueUser(user_id, true).then(() => {
+ queueDiv.removeChild(div);
+ });
+ console.log(logstr, `removed and safelisted ${FormatLegacyName(user)} from queue`);
+ };
+ never.textContent = "never block";
+ div.appendChild(never);
+
+ queueDiv.appendChild(div);
+ });
+});
diff --git a/pages/style.css b/pages/style.css
new file mode 100644
index 0000000..e219aff
--- /dev/null
+++ b/pages/style.css
@@ -0,0 +1,124 @@
+html {
+ --transition: ease;
+ --fadetime: 0.15s;
+ --interact: #1da1f2;
+ --bg0color: #000000;
+ --bg1color: #1e1f25;
+ --bg2color: #151416;
+ --bg3color: var(--bordercolor);
+ --blockquote: var(--bordercolor);
+ --textcolor: #DDD;
+ --bordercolor: #2D333A;
+ --linecolor: var(--bordercolor);
+ --borderhover: var(--interact);
+ --subtle: #EEEEEE80;
+ --shadowcolor: #00000080;
+ --activeshadowcolor: #000000B3;
+ --screen-cover: #00000080;
+ --border-size: 1px;
+ --border-radius: 3px;
+
+ background: var(--bg0color);
+}
+html * {
+ font-family: Bitstream Vera Sans, DejaVu Sans, Arial, Helvetica, sans-serif;
+}
+body {
+ background: var(--bg1color);
+ color: var(--textcolor);
+}
+html, body, main, h1, p {
+ margin: 0;
+}
+main {
+ color: var(--textcolor);
+ text-align: center;
+ width: 100%;
+ background: #1E1F25;
+}
+.inner {
+ padding: 25px;
+}
+.subtitle {
+ margin-bottom: 25px;
+}
+
+
+#block-queue {
+ bottom: 0;
+ display: inline-flex;
+ align-items: flex-start;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#block-queue div {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: url('../assets/icon.png') var(--bg2color);
+ background-repeat: no-repeat;
+ background-size: 2.5em;
+ background-position-x: 1em;
+ background-position-y: center;
+ pointer-events: all;
+ padding: 1em 1.5em 1em 4.25em;
+ margin: 0 0 25px;
+ border: var(--border-size) solid var(--bordercolor);
+ border-radius: var(--border-radius);
+ color: var(--textcolor);
+ box-shadow: 0 2px 3px 1px var(--shadowcolor);
+ min-height: calc(2em + 4px);
+}
+#block-queue div:last-child {
+ margin-bottom: 0;
+}
+#block-queue div.error {
+ background: url('../assets/error.png') var(--bg1color);
+ background-repeat: no-repeat;
+ background-size: 2.5em;
+ background-position-x: 1em;
+ background-position-y: center;
+}
+
+#block-queue div a,
+#block-queue div a:active,
+#block-queue div a:focus {
+ color: var(--textcolor);
+ text-shadow: 0 2px 3px 1px var(--shadowcolor);
+ -webkit-transition: var(--transition) var(--fadetime);
+ -moz-transition: var(--transition) var(--fadetime);
+ -o-transition: var(--transition) var(--fadetime);
+ transition: var(--transition) var(--fadetime);
+ cursor: pointer;
+}
+
+#block-queue div a:hover {
+ text-shadow: 0 0 10px 3px var(--activeshadowcolor);
+ color: var(--interact);
+}
+
+#block-queue div button,
+#block-queue div button:active,
+#block-queue div button:focus {
+ margin-left: 1em;
+ font-size: 1em;
+ padding: 0.5em 1em;
+ color: var(--textcolor);
+ background: var(--bg1color);
+ box-shadow: 0 2px 3px 1px var(--shadowcolor);
+ border: var(--border-size) solid var(--bordercolor);
+ border-radius: var(--border-radius);
+ -webkit-transition: var(--transition) var(--fadetime);
+ -moz-transition: var(--transition) var(--fadetime);
+ -o-transition: var(--transition) var(--fadetime);
+ transition: var(--transition) var(--fadetime);
+ cursor: pointer;
+}
+
+#block-queue div button:hover {
+ box-shadow: 0 0 10px 3px var(--activeshadowcolor);
+ border-color: var(--interact);
+ color: var(--interact);
+}
+
diff --git a/parsers/instructions.js b/parsers/instructions.js
index 54375fd..def36f6 100644
--- a/parsers/instructions.js
+++ b/parsers/instructions.js
@@ -51,7 +51,7 @@ export const IgnoreTweetTypes = new Set([
"TimelineTimelineCursor",
]);
-function handleTweetObject(obj, headers, config) {
+function handleTweetObject(obj, config) {
let ptr = obj;
for (const key of UserObjectPath) {
if (ptr.hasOwnProperty(key)) {
@@ -62,21 +62,21 @@ function handleTweetObject(obj, headers, config) {
console.error(logstr, "could not parse tweet", obj);
return;
}
- BlockBlueVerified(ptr, headers, config);
+ BlockBlueVerified(ptr, config);
}
-export function ParseTimelineTweet(tweet, headers, config) {
+export function ParseTimelineTweet(tweet, config) {
if(tweet.itemType=="TimelineTimelineCursor") {
return;
}
// Handle retweets and quoted tweets (check the retweeted user, too)
if(tweet?.tweet_results?.result?.quoted_status_result) {
- handleTweetObject(tweet.tweet_results.result.quoted_status_result.result, headers, config);
+ handleTweetObject(tweet.tweet_results.result.quoted_status_result.result, config);
} else if(tweet?.tweet_results?.result?.legacy?.retweeted_status_result) {
- handleTweetObject(tweet.tweet_results.result.legacy.retweeted_status_result.result, headers, config);
+ handleTweetObject(tweet.tweet_results.result.legacy.retweeted_status_result.result, config);
}
- handleTweetObject(tweet, headers, config);
+ handleTweetObject(tweet, config);
}
export function HandleInstructionsResponse(e, body, config) {
@@ -124,13 +124,13 @@ export function HandleInstructionsResponse(e, body, config) {
case "TimelineTimelineItem":
if (tweet.content.itemContent.itemType=="TimelineTweet") {
- ParseTimelineTweet(tweet.content.itemContent, e.detail.request.headers, config);
+ ParseTimelineTweet(tweet.content.itemContent, config);
}
break;
case "TimelineTimelineModule":
for (const innerTweet of tweet.content.items) {
- ParseTimelineTweet(innerTweet.item.itemContent, e.detail.request.headers, config)
+ ParseTimelineTweet(innerTweet.item.itemContent, config)
}
break;
@@ -144,9 +144,4 @@ export function HandleInstructionsResponse(e, body, config) {
}
}
}
-
- if (isAddToModule) {
- tweets.moduleItems = tweets.entries[0]?.content?.items || [];
- delete tweets.entries;
- }
}
diff --git a/parsers/search.js b/parsers/search.js
index e3efc7d..b48ac3f 100644
--- a/parsers/search.js
+++ b/parsers/search.js
@@ -1,7 +1,7 @@
import { BlockBlueVerified } from "../shared.js";
// This file handles requests made pertaining to search results.
-export function HandleTypeahead(e, body, config) {
+export function HandleTypeahead(_, body, config) {
// This endpoints appears to be extra/miscilaneous response data returned
// when doing a search. it has a user list in it, so run it through the gamut!
if (!body.users) {
@@ -25,6 +25,6 @@ export function HandleTypeahead(e, body, config) {
},
super_following: false, // meh
rest_id: user.id_str,
- }, e.detail.request.headers, config);
+ }, config);
}
}
diff --git a/parsers/timeline.js b/parsers/timeline.js
index 3fe40e3..50802ab 100644
--- a/parsers/timeline.js
+++ b/parsers/timeline.js
@@ -3,7 +3,7 @@ import { BlockBlueVerified } from "../shared.js";
// including the "For You" page as well as the "Following" page. it also
// seems to work for the "adaptive.json" response from search results
-export function HandleForYou(e, body, config) {
+export function HandleForYou(_, body, config) {
// This API endpoint currently does not deliver information required for
// block filters (in particular, it's missing affiliates_highlighted_label).
// The above doesn't seem completely true. it's missing affiliates specifically
@@ -28,6 +28,6 @@ export function HandleForYou(e, body, config) {
},
super_following: user.ext?.superFollowMetadata?.r?.ok?.superFollowing,
rest_id: user_id,
- }, e.detail.request.headers, config)
+ }, config)
}
}
diff --git a/popup/index.html b/popup/index.html
index cbfb790..a2c36c6 100644
--- a/popup/index.html
+++ b/popup/index.html
@@ -8,14 +8,14 @@
lue Blocker
- blocked users so far! ( queued)
+ blocked users so far! ( queued: view)
@@ -45,7 +45,7 @@ lue Blocker
- allow blocking of people I follow
+ block people I follow
@@ -55,7 +55,7 @@ lue Blocker
- allow blocking of people who follow me
+ block people who follow me
@@ -65,7 +65,7 @@ lue Blocker
- skip users verified by other means
+ skip legacy verified users (unreliable)
@@ -75,7 +75,7 @@ lue Blocker
- skip users verified through affiliations
+ skip users verified via businesses ()
@@ -85,10 +85,14 @@ lue Blocker
- skip users w/ 1M+ followers
+ skip users with over 1M followers
+
+
threshold: followers
+
+
-
-
+
+
@@ -109,6 +113,16 @@
lue Blocker
+