mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-24 06:15:48 +02:00
Add option to hide tweets by users with NFT avatars
This commit is contained in:
parent
4751a948e7
commit
1ced72388b
@ -26,6 +26,7 @@ sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
|
||||
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableAnimatedImages { get; set; } = true;
|
||||
public bool HideTweetsByNftUsers { get; set; } = false;
|
||||
|
||||
private bool _enableSmoothScrolling = true;
|
||||
private string _customCefArgs = null;
|
||||
|
@ -40,7 +40,7 @@ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateChecker up
|
||||
|
||||
PrepareLoad();
|
||||
|
||||
AddButton("General", () => new TabSettingsGeneral(tweetDeckFunctions.ReloadColumns, updates));
|
||||
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadToTweetDeck, tweetDeckFunctions.ReloadColumns, updates));
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(this.browser.CreateExampleNotification()));
|
||||
AddButton("Sounds", () => new TabSettingsSounds(() => tweetDeckFunctions.PlaySoundNotification(true)));
|
||||
AddButton("Tray", () => new TabSettingsTray());
|
||||
|
71
Dialogs/Settings/TabSettingsGeneral.Designer.cs
generated
71
Dialogs/Settings/TabSettingsGeneral.Designer.cs
generated
@ -42,6 +42,8 @@ private void InitializeComponent() {
|
||||
this.checkFocusDmInput = new System.Windows.Forms.CheckBox();
|
||||
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
|
||||
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
|
||||
this.labelTweetDeckSettings = new System.Windows.Forms.Label();
|
||||
this.checkHideTweetsByNftUsers = new System.Windows.Forms.CheckBox();
|
||||
this.labelBrowserPath = new System.Windows.Forms.Label();
|
||||
this.comboBoxCustomBrowser = new System.Windows.Forms.ComboBox();
|
||||
this.labelSearchEngine = new System.Windows.Forms.Label();
|
||||
@ -87,11 +89,11 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.checkUpdateNotifications.AutoSize = true;
|
||||
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 307);
|
||||
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 381);
|
||||
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
||||
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
|
||||
this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19);
|
||||
this.checkUpdateNotifications.TabIndex = 11;
|
||||
this.checkUpdateNotifications.TabIndex = 13;
|
||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@ -99,12 +101,12 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.btnCheckUpdates.AutoSize = true;
|
||||
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 331);
|
||||
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 405);
|
||||
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnCheckUpdates.Name = "btnCheckUpdates";
|
||||
this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25);
|
||||
this.btnCheckUpdates.TabIndex = 12;
|
||||
this.btnCheckUpdates.TabIndex = 14;
|
||||
this.btnCheckUpdates.Text = "Check Updates Now";
|
||||
this.btnCheckUpdates.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@ -124,12 +126,12 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.checkBestImageQuality.AutoSize = true;
|
||||
this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 122);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 259);
|
||||
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
|
||||
this.checkBestImageQuality.Name = "checkBestImageQuality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(125, 19);
|
||||
this.checkBestImageQuality.TabIndex = 5;
|
||||
this.checkBestImageQuality.Text = "Best Image Quality";
|
||||
this.checkBestImageQuality.Size = new System.Drawing.Size(182, 19);
|
||||
this.checkBestImageQuality.TabIndex = 9;
|
||||
this.checkBestImageQuality.Text = "Download Best Image Quality";
|
||||
this.checkBestImageQuality.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkOpenSearchInFirstColumn
|
||||
@ -163,11 +165,11 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.labelZoom.AutoSize = true;
|
||||
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 203);
|
||||
this.labelZoom.Location = new System.Drawing.Point(3, 155);
|
||||
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelZoom.Name = "labelZoom";
|
||||
this.labelZoom.Size = new System.Drawing.Size(39, 15);
|
||||
this.labelZoom.TabIndex = 8;
|
||||
this.labelZoom.TabIndex = 6;
|
||||
this.labelZoom.Text = "Zoom";
|
||||
//
|
||||
// zoomUpdateTimer
|
||||
@ -179,21 +181,21 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.panelZoom.Controls.Add(this.trackBarZoom);
|
||||
this.panelZoom.Controls.Add(this.labelZoomValue);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 219);
|
||||
this.panelZoom.Location = new System.Drawing.Point(0, 171);
|
||||
this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
|
||||
this.panelZoom.Name = "panelZoom";
|
||||
this.panelZoom.Size = new System.Drawing.Size(300, 35);
|
||||
this.panelZoom.TabIndex = 9;
|
||||
this.panelZoom.TabIndex = 7;
|
||||
//
|
||||
// checkAnimatedAvatars
|
||||
//
|
||||
this.checkAnimatedAvatars.AutoSize = true;
|
||||
this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 146);
|
||||
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 307);
|
||||
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
|
||||
this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19);
|
||||
this.checkAnimatedAvatars.TabIndex = 6;
|
||||
this.checkAnimatedAvatars.TabIndex = 11;
|
||||
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
|
||||
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
|
||||
//
|
||||
@ -201,11 +203,11 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.labelUpdates.AutoSize = true;
|
||||
this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 281);
|
||||
this.labelUpdates.Location = new System.Drawing.Point(0, 355);
|
||||
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
|
||||
this.labelUpdates.Name = "labelUpdates";
|
||||
this.labelUpdates.Size = new System.Drawing.Size(69, 19);
|
||||
this.labelUpdates.TabIndex = 10;
|
||||
this.labelUpdates.TabIndex = 12;
|
||||
this.labelUpdates.Text = "UPDATES";
|
||||
//
|
||||
// flowPanelLeft
|
||||
@ -217,11 +219,13 @@ private void InitializeComponent() {
|
||||
this.flowPanelLeft.Controls.Add(this.checkFocusDmInput);
|
||||
this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn);
|
||||
this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
|
||||
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
|
||||
this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
|
||||
this.flowPanelLeft.Controls.Add(this.checkSmoothScrolling);
|
||||
this.flowPanelLeft.Controls.Add(this.labelZoom);
|
||||
this.flowPanelLeft.Controls.Add(this.panelZoom);
|
||||
this.flowPanelLeft.Controls.Add(this.labelTweetDeckSettings);
|
||||
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
|
||||
this.flowPanelLeft.Controls.Add(this.checkHideTweetsByNftUsers);
|
||||
this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
|
||||
this.flowPanelLeft.Controls.Add(this.labelUpdates);
|
||||
this.flowPanelLeft.Controls.Add(this.checkUpdateNotifications);
|
||||
this.flowPanelLeft.Controls.Add(this.btnCheckUpdates);
|
||||
@ -271,14 +275,37 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.checkSmoothScrolling.AutoSize = true;
|
||||
this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 170);
|
||||
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 122);
|
||||
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkSmoothScrolling.Name = "checkSmoothScrolling";
|
||||
this.checkSmoothScrolling.Size = new System.Drawing.Size(117, 19);
|
||||
this.checkSmoothScrolling.TabIndex = 7;
|
||||
this.checkSmoothScrolling.TabIndex = 5;
|
||||
this.checkSmoothScrolling.Text = "Smooth Scrolling";
|
||||
this.checkSmoothScrolling.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelTweetDeckSettings
|
||||
//
|
||||
this.labelTweetDeckSettings.AutoSize = true;
|
||||
this.labelTweetDeckSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||
this.labelTweetDeckSettings.Location = new System.Drawing.Point(0, 233);
|
||||
this.labelTweetDeckSettings.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
|
||||
this.labelTweetDeckSettings.Name = "labelTweetDeckSettings";
|
||||
this.labelTweetDeckSettings.Size = new System.Drawing.Size(67, 19);
|
||||
this.labelTweetDeckSettings.TabIndex = 8;
|
||||
this.labelTweetDeckSettings.Text = "TWITTER";
|
||||
//
|
||||
// checkHideTweetsByNftUsers
|
||||
//
|
||||
this.checkHideTweetsByNftUsers.AutoSize = true;
|
||||
this.checkHideTweetsByNftUsers.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.checkHideTweetsByNftUsers.Location = new System.Drawing.Point(6, 283);
|
||||
this.checkHideTweetsByNftUsers.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
|
||||
this.checkHideTweetsByNftUsers.Name = "checkHideTweetsByNftUsers";
|
||||
this.checkHideTweetsByNftUsers.Size = new System.Drawing.Size(231, 19);
|
||||
this.checkHideTweetsByNftUsers.TabIndex = 10;
|
||||
this.checkHideTweetsByNftUsers.Text = "Hide Tweets by Users with NFT Avatars";
|
||||
this.checkHideTweetsByNftUsers.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelBrowserPath
|
||||
//
|
||||
this.labelBrowserPath.AutoSize = true;
|
||||
@ -594,5 +621,7 @@ private void InitializeComponent() {
|
||||
private System.Windows.Forms.Label labelFirstDayOfWeek;
|
||||
private System.Windows.Forms.ComboBox comboBoxFirstDayOfWeek;
|
||||
private System.Windows.Forms.Label labelUI;
|
||||
private System.Windows.Forms.CheckBox checkHideTweetsByNftUsers;
|
||||
private System.Windows.Forms.Label labelTweetDeckSettings;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
namespace TweetDuck.Dialogs.Settings {
|
||||
sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
||||
private readonly Action reloadTweetDeck;
|
||||
private readonly Action reloadColumns;
|
||||
|
||||
private readonly UpdateChecker updates;
|
||||
@ -27,9 +28,10 @@ sealed partial class TabSettingsGeneral : FormSettings.BaseTab {
|
||||
private readonly int searchEngineIndexDefault;
|
||||
private readonly int searchEngineIndexCustom;
|
||||
|
||||
public TabSettingsGeneral(Action reloadColumns, UpdateChecker updates) {
|
||||
public TabSettingsGeneral(Action reloadTweetDeck, Action reloadColumns, UpdateChecker updates) {
|
||||
InitializeComponent();
|
||||
|
||||
this.reloadTweetDeck = reloadTweetDeck;
|
||||
this.reloadColumns = reloadColumns;
|
||||
|
||||
this.updates = updates;
|
||||
@ -43,8 +45,6 @@ public TabSettingsGeneral(Action reloadColumns, UpdateChecker updates) {
|
||||
toolTip.SetToolTip(checkFocusDmInput, "Places cursor into Direct Message input\r\nfield when opening a conversation.");
|
||||
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
|
||||
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
|
||||
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
|
||||
toolTip.SetToolTip(checkSmoothScrolling, "Toggles smooth mouse wheel scrolling.");
|
||||
toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
|
||||
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
|
||||
@ -53,13 +53,21 @@ public TabSettingsGeneral(Action reloadColumns, UpdateChecker updates) {
|
||||
checkFocusDmInput.Checked = Config.FocusDmInput;
|
||||
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
|
||||
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
||||
checkSmoothScrolling.Checked = Config.EnableSmoothScrolling;
|
||||
|
||||
trackBarZoom.SetValueSafe(Config.ZoomLevel);
|
||||
labelZoomValue.Text = trackBarZoom.Value + "%";
|
||||
|
||||
// twitter
|
||||
|
||||
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
|
||||
toolTip.SetToolTip(checkHideTweetsByNftUsers, "Hides tweets created by users who use Twitter's NFT avatar integration.\r\nThis feature is somewhat experimental, some accounts might not be detected immediately.");
|
||||
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
|
||||
|
||||
checkBestImageQuality.Checked = Config.BestImageQuality;
|
||||
checkHideTweetsByNftUsers.Checked = Config.HideTweetsByNftUsers;
|
||||
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
|
||||
|
||||
// updates
|
||||
|
||||
toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
|
||||
@ -135,11 +143,13 @@ public override void OnReady() {
|
||||
checkFocusDmInput.CheckedChanged += checkFocusDmInput_CheckedChanged;
|
||||
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
|
||||
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
|
||||
checkSmoothScrolling.CheckedChanged += checkSmoothScrolling_CheckedChanged;
|
||||
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
|
||||
|
||||
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
|
||||
checkHideTweetsByNftUsers.CheckedChanged += checkHideTweetsByNftUsers_CheckedChanged;
|
||||
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
|
||||
|
||||
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
|
||||
btnCheckUpdates.Click += btnCheckUpdates_Click;
|
||||
|
||||
@ -177,15 +187,6 @@ private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventA
|
||||
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
|
||||
}
|
||||
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||
}
|
||||
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
|
||||
}
|
||||
|
||||
private void checkSmoothScrolling_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableSmoothScrolling = checkSmoothScrolling.Checked;
|
||||
}
|
||||
@ -205,6 +206,24 @@ private void zoomUpdateTimer_Tick(object sender, EventArgs e) {
|
||||
|
||||
#endregion
|
||||
|
||||
#region Twitter
|
||||
|
||||
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.BestImageQuality = checkBestImageQuality.Checked;
|
||||
}
|
||||
|
||||
private void checkHideTweetsByNftUsers_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.HideTweetsByNftUsers = checkHideTweetsByNftUsers.Checked;
|
||||
BeginInvoke(reloadTweetDeck);
|
||||
}
|
||||
|
||||
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e) {
|
||||
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
|
||||
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => reloadColumns());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Updates
|
||||
|
||||
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e) {
|
||||
|
@ -45,6 +45,7 @@ import limit_loaded_dm_count from "./tweetdeck/limit_loaded_dm_count.js";
|
||||
import make_retweets_lowercase from "./tweetdeck/make_retweets_lowercase.js";
|
||||
import middle_click_tweet_icon_actions from "./tweetdeck/middle_click_tweet_icon_actions.js";
|
||||
import move_accounts_above_hashtags_in_search from "./tweetdeck/move_accounts_above_hashtags_in_search.js";
|
||||
import mute_accounts_with_nft_avatars from "./tweetdeck/mute_accounts_with_nft_avatars.js";
|
||||
import offline_notification from "./tweetdeck/offline_notification.js";
|
||||
import open_search_externally from "./tweetdeck/open_search_externally.js";
|
||||
import open_search_in_first_column from "./tweetdeck/open_search_in_first_column.js";
|
||||
|
@ -37,6 +37,7 @@ if (!("$TDX" in window)) {
|
||||
* @property {boolean} [expandLinksOnHover]
|
||||
* @property {number} [firstDayOfWeek]
|
||||
* @property {boolean} [focusDmInput]
|
||||
* @property {boolean} [hideTweetsByNftUsers]
|
||||
* @property {boolean} [keepLikeFollowDialogsOpen]
|
||||
* @property {boolean} [muteNotifications]
|
||||
* @property {boolean} [notificationMediaPreviews]
|
||||
|
@ -40,6 +40,7 @@ if (!("TD" in window)) {
|
||||
* @property {TD_Column_Model} model
|
||||
* @property {boolean} notificationsDisabled
|
||||
* @property {function} reloadTweets
|
||||
* @property {ChirpBase[]} updateArray
|
||||
* @property {{ columnWidth: number }} visibility
|
||||
*/
|
||||
|
||||
@ -272,10 +273,14 @@ if (!("TD" in window)) {
|
||||
* @typedef TwitterClient
|
||||
* @type {Object}
|
||||
*
|
||||
* @property {string} API_BASE_URL
|
||||
* @property {function(id: string)} addIdToMuteList
|
||||
* @property {function(chirp: ChirpBase)} callback
|
||||
* @property {string} chirpId
|
||||
* @property {TwitterConversations} conversations
|
||||
* @property {function(ids: string[], onSuccess: function(users: TwitterUser[]), onError: function)} getUsersByIds
|
||||
* @property {function(url: string, data: object, method: "GET"|"POST", responseProcessor: function, onSuccess: function, onError: function)} makeTwitterCall
|
||||
* @property {function(json: string[]): TwitterUser[]} processUsers
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -360,6 +365,7 @@ if (!("TD" in window)) {
|
||||
* @typedef TwitterUserJSON
|
||||
* @type {Object}
|
||||
*
|
||||
* @property {boolean} [ext_has_nft_avatar]
|
||||
* @property {string} id
|
||||
* @property {string} id_str
|
||||
* @property {string} name
|
||||
|
210
Resources/Content/tweetdeck/globals/user_nft_status.js
Normal file
210
Resources/Content/tweetdeck/globals/user_nft_status.js
Normal file
@ -0,0 +1,210 @@
|
||||
import { TD } from "../../api/td.js";
|
||||
import { checkPropertyExists, noop } from "../../api/utils.js";
|
||||
|
||||
function isSupported() {
|
||||
return checkPropertyExists(TD, "controller", "clients", "getPreferredClient") &&
|
||||
checkPropertyExists(TD, "services", "TwitterClient", "prototype", "API_BASE_URL") &&
|
||||
checkPropertyExists(TD, "services", "TwitterClient", "prototype", "makeTwitterCall") &&
|
||||
checkPropertyExists(TD, "services", "TwitterClient", "prototype", "processUsers") &&
|
||||
checkPropertyExists(TD, "services", "TwitterUser", "prototype");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {function(id: string)[]}
|
||||
*/
|
||||
const nftUserListeners = [];
|
||||
|
||||
/**
|
||||
* @type {Map<string, boolean>}
|
||||
*/
|
||||
const knownStatus = new Map();
|
||||
|
||||
/**
|
||||
* @type {Map<string, function(result: boolean)[]>}
|
||||
*/
|
||||
const deferredCallbacks = new Map();
|
||||
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const usersInQueue = new Set();
|
||||
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const usersPending = new Set();
|
||||
|
||||
let checkTimer = -1;
|
||||
|
||||
function requestQueuedUserInfo() {
|
||||
if (usersInQueue.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = [];
|
||||
|
||||
for (const id of usersInQueue) {
|
||||
if (ids.length === 100) {
|
||||
break;
|
||||
}
|
||||
|
||||
ids.push(id);
|
||||
usersInQueue.delete(id);
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
const data = {
|
||||
user_id: ids.join(",")
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {TwitterUserJSON[]} users
|
||||
*/
|
||||
const processUserData = function(users) {
|
||||
for (const user of users) {
|
||||
setUserNftStatus(user.id_str, user.ext_has_nft_avatar === true);
|
||||
}
|
||||
};
|
||||
|
||||
const client = TD.controller.clients.getPreferredClient();
|
||||
|
||||
client.makeTwitterCall(client.API_BASE_URL + "users/lookup.json?include_ext_has_nft_avatar=1", data, "POST", processUserData, noop, function() {
|
||||
// In case of API error, assume the users are not associated with NFTs so that callbacks can fire.
|
||||
for (const id of ids) {
|
||||
setUserNftStatus(id, false);
|
||||
}
|
||||
});
|
||||
|
||||
if (usersInQueue.size === 0) {
|
||||
checkTimer = -1;
|
||||
}
|
||||
else {
|
||||
checkTimer = window.setTimeout(requestQueuedUserInfo, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided callback function with the result of whether a user id is associated with NFTs.
|
||||
* If the user id is null, it will be presumed as not associated with NFTs.
|
||||
* @param {string|null} id
|
||||
* @param {function(nft: boolean)} [callback]
|
||||
*/
|
||||
function checkUserNftStatusCallback(id, callback) {
|
||||
if (id === null) {
|
||||
callback && callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const status = knownStatus.get(id);
|
||||
|
||||
if (status !== undefined) {
|
||||
callback && callback(status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
let callbackList = deferredCallbacks.get(id);
|
||||
if (callbackList === undefined) {
|
||||
deferredCallbacks.set(id, callbackList = []);
|
||||
}
|
||||
|
||||
callbackList.push(callback);
|
||||
}
|
||||
|
||||
if (usersPending.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
usersInQueue.add(id);
|
||||
usersPending.add(id);
|
||||
|
||||
window.clearTimeout(checkTimer);
|
||||
checkTimer = window.setTimeout(requestQueuedUserInfo, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user id is associated with NFTs, but only using already known results.
|
||||
* If the user id is null or has not been checked yet, it will be presumed as not associated with NFTs.
|
||||
* @param {string|null} id
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function checkUserNftStatusImmediately(id) {
|
||||
return id !== null && knownStatus.get(id) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that gets called when a user is added to the list of users associated with NFTs.
|
||||
* If some users were already known to be associated with NFTs before registering the listener, the listener will be called for every user.
|
||||
* @param {function(id: string)} listener
|
||||
*/
|
||||
export function addNftUserListener(listener) {
|
||||
nftUserListeners.push(listener);
|
||||
|
||||
for (const entry of knownStatus.entries()) {
|
||||
if (entry[1]) {
|
||||
listener(entry[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a user id is associated with NFTs.
|
||||
* @param {string} id
|
||||
* @param {boolean} status
|
||||
*/
|
||||
export function setUserNftStatus(id, status) {
|
||||
usersInQueue.delete(id);
|
||||
usersPending.delete(id);
|
||||
|
||||
if (knownStatus.get(id) !== status) {
|
||||
knownStatus.set(id, status);
|
||||
|
||||
if (status) {
|
||||
for (const listener of nftUserListeners) {
|
||||
try {
|
||||
listener(id);
|
||||
} catch (e) {
|
||||
console.error("Error in NFT user listener: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deferredCallbacks.has(id)) {
|
||||
for (const callback of deferredCallbacks.get(id)) {
|
||||
callback(status);
|
||||
}
|
||||
|
||||
deferredCallbacks.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided callback function with the result of whether a user id is associated with NFTs.
|
||||
* @param {string} id
|
||||
* @param {function(nft: boolean)} [callback]
|
||||
*/
|
||||
export const checkUserNftStatus = isSupported() ? checkUserNftStatusCallback : function(id, callback) {
|
||||
callback && callback(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function that returns the user id from a tweet.
|
||||
* @param {ChirpBase} tweet
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function getTweetUserId(tweet) {
|
||||
const user = tweet.user;
|
||||
return typeof user === "object" && typeof user.id === "string" ? user.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears known status of users who are not associated with NFTs, in case they became associated with NFTs in the meantime.
|
||||
*/
|
||||
window.setInterval(function() {
|
||||
for (const entry of knownStatus.entries()) {
|
||||
if (!entry[1]) {
|
||||
knownStatus.delete(entry[0]);
|
||||
}
|
||||
}
|
||||
}, 1000 * 60 * 60);
|
@ -0,0 +1,54 @@
|
||||
import { $TDX } from "../api/bridge.js";
|
||||
import { replaceFunction } from "../api/patch.js";
|
||||
import { TD } from "../api/td.js";
|
||||
import { ensurePropertyExists } from "../api/utils.js";
|
||||
import { addNftUserListener, checkUserNftStatus, checkUserNftStatusImmediately, getTweetUserId, setUserNftStatus } from "./globals/user_nft_status.js";
|
||||
|
||||
export default function() {
|
||||
if (!$TDX.hideTweetsByNftUsers) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensurePropertyExists(TD, "controller", "clients", "getPreferredClient");
|
||||
ensurePropertyExists(TD, "services", "TwitterClient", "prototype", "addIdToMuteList");
|
||||
ensurePropertyExists(TD, "services", "TwitterUser", "prototype");
|
||||
ensurePropertyExists(TD, "vo", "Column", "prototype", "addItemsToIndex");
|
||||
|
||||
addNftUserListener(function(id) {
|
||||
TD.controller.clients.getPreferredClient().addIdToMuteList(id);
|
||||
});
|
||||
|
||||
replaceFunction(TD.services.TwitterUser.prototype, "fromJSONObject", function(func, args) {
|
||||
/** @type {TwitterUser} */
|
||||
const user = func.apply(this, args);
|
||||
|
||||
if (args.length > 0 && typeof args[0] === "object") {
|
||||
const id = user.id;
|
||||
const json = args[0];
|
||||
|
||||
if ("ext_has_nft_avatar" in json) {
|
||||
setUserNftStatus(id, json.ext_has_nft_avatar === true);
|
||||
}
|
||||
else {
|
||||
checkUserNftStatus(id);
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
});
|
||||
|
||||
replaceFunction(TD.vo.Column.prototype, "mergeAndRenderChirps", function(func, args) {
|
||||
/** @type ChirpBase[] */
|
||||
const tweets = args[0];
|
||||
|
||||
if (Array.isArray(tweets)) {
|
||||
for (let i = tweets.length - 1; i >= 0; i--) {
|
||||
if (checkUserNftStatusImmediately(getTweetUserId(tweets[i]))) {
|
||||
tweets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func.apply(this, args);
|
||||
});
|
||||
};
|
@ -4,6 +4,7 @@ import { replaceFunction } from "../api/patch.js";
|
||||
import { TD } from "../api/td.js";
|
||||
import { checkPropertyExists, ensurePropertyExists } from "../api/utils.js";
|
||||
import { getColumnName } from "./globals/get_column_name.js";
|
||||
import { checkUserNftStatus, getTweetUserId } from "./globals/user_nft_status.js";
|
||||
|
||||
/**
|
||||
* Event callback for a new tweet.
|
||||
@ -67,20 +68,7 @@ const onNewTweet = (function() {
|
||||
* @param {TD_Column} column
|
||||
* @param {ChirpBase} tweet
|
||||
*/
|
||||
return function(column, tweet) {
|
||||
if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent) {
|
||||
if (checkTweetCache(recentMessages, tweet.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (checkTweetCache(recentTweets, tweet.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startRecentTweetTimer();
|
||||
|
||||
const showTweetNotification = function(column, tweet) {
|
||||
if (column.model.getHasNotification()) {
|
||||
const sensitive = isSensitive(tweet);
|
||||
const previews = $TDX.notificationMediaPreviews && (!sensitive || TD.settings.getDisplaySensitiveMedia());
|
||||
@ -184,6 +172,36 @@ const onNewTweet = (function() {
|
||||
$TD.onTweetSound();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {TD_Column} column
|
||||
* @param {ChirpBase} tweet
|
||||
*/
|
||||
return function(column, tweet) {
|
||||
if (tweet instanceof TD.services.TwitterConversation || tweet instanceof TD.services.TwitterConversationMessageEvent) {
|
||||
if (checkTweetCache(recentMessages, tweet.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (checkTweetCache(recentTweets, tweet.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startRecentTweetTimer();
|
||||
|
||||
if (!$TDX.hideTweetsByNftUsers) {
|
||||
showTweetNotification(column, tweet);
|
||||
}
|
||||
else {
|
||||
checkUserNftStatus(getTweetUserId(tweet), function(nft) {
|
||||
if (!nft) {
|
||||
showTweetNotification(column, tweet);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
|
@ -402,6 +402,7 @@
|
||||
<None Include="Resources\Content\tweetdeck\globals\reload_columns.js" />
|
||||
<None Include="Resources\Content\tweetdeck\globals\retrieve_tweet.js" />
|
||||
<None Include="Resources\Content\tweetdeck\globals\show_tweet_detail.js" />
|
||||
<None Include="Resources\Content\tweetdeck\globals\user_nft_status.js" />
|
||||
<None Include="Resources\Content\tweetdeck\handle_extra_mouse_buttons.js" />
|
||||
<None Include="Resources\Content\tweetdeck\hook_theme_settings.js" />
|
||||
<None Include="Resources\Content\tweetdeck\inject_css.js" />
|
||||
@ -410,6 +411,7 @@
|
||||
<None Include="Resources\Content\tweetdeck\make_retweets_lowercase.js" />
|
||||
<None Include="Resources\Content\tweetdeck\middle_click_tweet_icon_actions.js" />
|
||||
<None Include="Resources\Content\tweetdeck\move_accounts_above_hashtags_in_search.js" />
|
||||
<None Include="Resources\Content\tweetdeck\mute_accounts_with_nft_avatars.js" />
|
||||
<None Include="Resources\Content\tweetdeck\offline_notification.js" />
|
||||
<None Include="Resources\Content\tweetdeck\open_search_externally.js" />
|
||||
<None Include="Resources\Content\tweetdeck\open_search_in_first_column.js" />
|
||||
|
@ -9,6 +9,7 @@ public interface IAppUserConfiguration {
|
||||
bool ExpandLinksOnHover { get; }
|
||||
bool FirstRun { get; }
|
||||
bool FocusDmInput { get; }
|
||||
bool HideTweetsByNftUsers { get; }
|
||||
bool IsCustomSoundNotificationSet { get; }
|
||||
bool KeepLikeFollowDialogsOpen { get; }
|
||||
bool MuteNotifications { get; }
|
||||
|
@ -13,7 +13,7 @@ internal static string Generate(IAppUserConfiguration config, Environment enviro
|
||||
static string Bool(bool value) => value ? "true;" : "false;";
|
||||
static string Str(string value) => $"\"{value}\";";
|
||||
|
||||
StringBuilder build = new StringBuilder(384).Append("(function(x){");
|
||||
StringBuilder build = new StringBuilder(414).Append("(function(x){");
|
||||
|
||||
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||
|
||||
@ -24,6 +24,7 @@ internal static string Generate(IAppUserConfiguration config, Environment enviro
|
||||
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
|
||||
build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
|
||||
build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
|
||||
build.Append("x.hideTweetsByNftUsers=").Append(Bool(config.HideTweetsByNftUsers));
|
||||
build.Append("x.firstDayOfWeek=").Append(config.CalendarFirstDay == -1 ? JQuery.GetDatePickerDayOfWeek(Lib.Culture.DateTimeFormat.FirstDayOfWeek) : config.CalendarFirstDay);
|
||||
}
|
||||
|
||||
|
@ -249,6 +249,9 @@ private sealed class ResourceRequestHandler : BaseResourceRequestHandler {
|
||||
case ResourceType.Xhr when url.Contains(UrlVersionCheck):
|
||||
return RequestHandleResult.Cancel.Instance;
|
||||
|
||||
case ResourceType.Xhr when url.Contains("://api.twitter.com/") && url.Contains("include_entities=1") && !url.Contains("&include_ext_has_nft_avatar=1"):
|
||||
return new RequestHandleResult.Redirect(url.Replace("include_entities=1", "include_entities=1&include_ext_has_nft_avatar=1"));
|
||||
|
||||
default:
|
||||
return base.Handle(url, resourceType);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user