Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Add the ability to block users based on words/emojis in their username or display name #275

Merged
merged 6 commits into from
May 10, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
make firefox
echo "filename=blue-blocker-firefox-$(make version).zip" >> "$GITHUB_OUTPUT"
- name: publish firefox
uses: wdzeng/firefox-addon@v1.0.5
uses: wdzeng/firefox-addon@v1.1.0-alpha.0
with:
addon-guid: "{119be3f3-597c-4f6a-9caf-627ee431d374}"
xpi-path: "${{ steps.build.outputs.filename }}"
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const DefaultOptions: Config = {
skipFollowerCount: 1e6,
soupcanIntegration: false,
blockPromoted: false,
disallowedWords: [],

// this isn"t set, but is used
// TODO: when migrating to firefox manifest v3, check to see if sets can be stored yet
Expand Down Expand Up @@ -77,12 +78,14 @@ export const ReasonNftAvatar: number = 1;
export const ReasonBusinessVerified: number = 2;
export const ReasonTransphobia: number = 3;
export const ReasonPromoted: number = 4;
export const ReasonDisallowedWordsOrEmojis: number = 5;
export const ReasonMap = {
[ReasonBlueVerified]: 'Twitter Blue verified',
[ReasonNftAvatar]: 'NFT avatar',
[ReasonBusinessVerified]: 'Twitter Business verified',
[ReasonTransphobia]: 'transphobia',
[ReasonPromoted]: 'promoting tweets',
[ReasonDisallowedWordsOrEmojis]: 'disallowed words or emojis',
};

export const LegacyVerifiedUrl: string =
Expand Down
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface Config {
soupcanIntegration: boolean;
blockPromoted: boolean;
blockForUse: boolean;
disallowedWords: string[];
}

interface BlueBlockerUser {
Expand Down
11 changes: 11 additions & 0 deletions src/popup/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,17 @@ <h2><img src="/icon/icon.png" alt="🅱️" style="height: 1.2em" />lue Blocker<
<span name="popup-timer-value"></span>
</div>
</div>
<div class="blockstrings">
<p>Block users with these words in their display name or username:</p>
<div class="blockstrings-option">
<textarea name="blockstrings-input" placeholder="Comma separated, Emojis allowed." id="blockstrings-input"></textarea>
<div class="option">
<p>This option may block non-blue users. <img src="/icon/warn.png" alt="⚠️" class="emoji" /></p>
<span name="blockstrings-input-status"></span>
</div>
</div>

</div>
<div class="safelist">
<p name="safelist-status">
safelist importing must be done on a separate page. click import to be
Expand Down
33 changes: 30 additions & 3 deletions src/popup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ function checkHandler(
});
}

function checkHandlerArrayToString(target: HTMLInputElement, config: Config, key: string) {
// @ts-ignore
const value: string[] = config[key];
let txt = "";
value.forEach(word => {
txt += word + ", ";
});
target.value = txt;

target.addEventListener('input', updateDisallowedWordsInUsernames);
}

function inputMirror(
name: string,
value: any,
Expand Down Expand Up @@ -149,6 +161,20 @@ function exportSafelist() {
});
}

function updateDisallowedWordsInUsernames(changeEvent : Event){
const target = changeEvent.target as HTMLInputElement;
let words = target.value.split(',');
// strip leading/trailing/multiple spaces and filter empties
words = words.map(v => v.replace(/^ +|(?<= ) +| +$/, '')).filter(w => w !== '');
api.storage.sync.set({ disallowedWords: words }).then(() => {
// Update status to let user know options were saved.
document.getElementsByName(target.name + '-status').forEach((status) => {
status.textContent = 'saved';
setTimeout(() => (status.textContent = null), 1000);
});
});
}

// start this immediately so that it's ready when the document loads
const popupPromise = api.storage.local.get({ popupActiveTab: 'quick' });

Expand Down Expand Up @@ -220,6 +246,7 @@ document.addEventListener('DOMContentLoaded', () => {
const blockPromoted = document.getElementById('block-promoted-tweets') as HTMLInputElement;
const blockForUse = document.getElementById('block-for-use') as HTMLInputElement;
const soupcanIntegration = document.getElementById('soupcan-integration') as HTMLInputElement;
const disallowedWordsInput = document.getElementById('blockstrings-input') as HTMLInputElement;

api.storage.sync.get(DefaultOptions).then((_config) => {
const config = _config as Config;
Expand All @@ -244,10 +271,11 @@ document.addEventListener('DOMContentLoaded', () => {
optionName: 'skip-follower-count-option',
});
checkHandler(blockPromoted, config, 'blockPromoted');
checkHandler(blockForUse, config, 'blockForUse')
checkHandler(blockForUse, config, 'blockForUse');
checkHandler(soupcanIntegration, config, 'soupcanIntegration', {
optionName: '', // integration isn't controlled by the toggle, so unset
});
checkHandlerArrayToString(disallowedWordsInput, config, 'disallowedWords');

document
.getElementsByName('skip-follower-count-value')
Expand Down Expand Up @@ -320,7 +348,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
});


// set the block value immediately
api.storage.local.get({ BlockCounter: 0 }).then(items => {
blockedUsersCount.textContent = commafy(items.BlockCounter);
Expand All @@ -330,7 +357,7 @@ document.addEventListener('DOMContentLoaded', () => {
blockedUsersCount.textContent = commafy(items.BlockCounter.newValue);
QueueLength().then(count => {
blockedUserQueueLength.textContent = commafy(count);
});
});
}
if (items.hasOwnProperty('BlockQueue')) {
blockedUserQueueLength.textContent = commafy(items.BlockQueue.newValue.length);
Expand Down
17 changes: 15 additions & 2 deletions src/popup/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ input[type=number] {
width: 7em;
height: 1em;
}
input[type=number], select {
input[type=number], select, textarea {
top: -0.1em;
position: relative;
display: inline-block;
Expand All @@ -273,7 +273,7 @@ input[type=number], select {
-o-transition: var(--transition) var(--fadetime);
transition: var(--transition) var(--fadetime);
}
input[type=number]:hover, select:hover {
input[type=number]:hover, select:hover, textarea:hover {
color: var(--interact);
border-color: var(--borderhover);
box-shadow: 0 0 10px 3px var(--activeshadowcolor);
Expand All @@ -282,6 +282,19 @@ input[type=number]:focus, select:focus {
color: var(--textcolor);
}

textarea {
text-align: left;
min-height: 1.5em;
min-width: 20em;
}

.blockstrings-option {
margin-left: 1em;
}
.blockstrings-option > .option {
margin-top: .1em;
}

.gold-check {
margin: -0.2em 0;
width: 1.2em;
Expand Down
14 changes: 14 additions & 0 deletions src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ReasonExternal,
IntegrationStateDisabled,
IntegrationStateReceiveOnly,
ReasonDisallowedWordsOrEmojis,
} from './constants';

import {
Expand Down Expand Up @@ -645,6 +646,19 @@ export async function BlockBlueVerified(user: BlueBlockerUser, config: Config) {
return;
}

// Step 0: Check for disallowed words or emojis in usernames.
const disallowedWordsWithoutEmptyStrings = config.disallowedWords.filter(word => word !== '');
if (disallowedWordsWithoutEmptyStrings.length > 0){
// this makes extra sure that emojis are always detected, regardless if they are attached to a word or another string of emojis.
// the 'i' makes the test case insensitive, which helps users not have to worry about typing the same word multiple times with different variations.
const disallowedWordsAndEmojis = new RegExp(`${config.disallowedWords.map(word=>word.split('').join(' *')).join('(?= |$)|')}(?= |$)`, 'i');
const usernameToTest = (user.legacy.name).replace(/(?<= ) +/, '');
if (disallowedWordsAndEmojis.test(usernameToTest)) {
queueBlockUser(user, String(user.rest_id), ReasonDisallowedWordsOrEmojis);
console.log(logstr, `${config.mute ? 'muted' : 'blocked'} ${formattedUserName} for having disallowed words/emojis in their username.`);
}
}

const legacyDbRejectMessage =
'could not access the legacy verified database, skip legacy has been disabled.';
// step 1: is user verified
Expand Down