// ==UserScript== // @name Filter X.com Location // @namespace mailto:arabalika@noreplay.codeberg.org // @version 0.2.6 // @description Script to filter X content by user location. // @match https://x.com/* // @grant GM_xmlhttpRequest // @connect filter-x-api.phdogee.workers.dev // @license AGPL3.0 // @downloadURL https://update.greasyfork.icu/scripts/557600/Filter%20Xcom%20Location.user.js // @updateURL https://update.greasyfork.icu/scripts/557600/Filter%20Xcom%20Location.meta.js // ==/UserScript== (() => { // --- CONFIGURATION --- const queryUrl = "https://x.com/i/api/graphql/XRqGa7EeokUU5kppkh13EA/AboutAccountQuery"; const authToken = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"; const REMOTE_DB_URL = "https://filter-x-api.phdogee.workers.dev"; // SETTINGS const READ_BATCH_DELAY = 75; // Wait 75ms to collect users for Reading const WRITE_BATCH_DELAY = 2000; // Wait 2s to collect users for Writing (Uploading) const WRITE_BATCH_SIZE = 5; // Or upload immediately if we have 5 users waiting const REQUEST_DELAY = 2000; const RANDOM_JITTER = 500; const RATE_LIMIT_PAUSE = 60000; const MAX_RETRIES = 5; const CACHE_TTL = 1000 * 60 * 60 * 24 * 30; const CONCURRENT_API_LIMIT = 2; const csrfToken = decodeURIComponent(document.cookie.match(/(?:^|; )ct0=([^;]+)/)?.[1] || ""); const cache = new Map(); const pending = new Map(); const apiQueue = []; let activeRequests = 0; // concurrency control let isRateLimited = false; // READ Batching let readBuffer = []; let readTimeout = null; // WRITE Batching let writeBuffer = new Map(); let writeTimeout = null; const storedValues = localStorage.getItem("tweetFilterValues"); let filterValues = storedValues ? storedValues.split("\n").filter(Boolean) : []; let filterMode = localStorage.getItem("tweetFilterMode") || "blacklist"; let filterEnabled = localStorage.getItem("tweetFilterEnabled") === "true"; const countryFlags = new Map([ ["Afghanistan", "๐Ÿ‡ฆ๐Ÿ‡ซ"], ["ร…land Islands", "๐Ÿ‡ฆ๐Ÿ‡ฝ"], ["Albania", "๐Ÿ‡ฆ๐Ÿ‡ฑ"], ["Algeria", "๐Ÿ‡ฉ๐Ÿ‡ฟ"], ["American Samoa", "๐Ÿ‡ฆ๐Ÿ‡ธ"], ["Andorra", "๐Ÿ‡ฆ๐Ÿ‡ฉ"], ["Angola", "๐Ÿ‡ฆ๐Ÿ‡ด"], ["Anguilla", "๐Ÿ‡ฆ๐Ÿ‡ฎ"], ["Antarctica", "๐Ÿ‡ฆ๐Ÿ‡ถ"], ["Antigua and Barbuda", "๐Ÿ‡ฆ๐Ÿ‡ฌ"], ["Argentina", "๐Ÿ‡ฆ๐Ÿ‡ท"], ["Armenia", "๐Ÿ‡ฆ๐Ÿ‡ฒ"], ["Aruba", "๐Ÿ‡ฆ๐Ÿ‡ผ"], ["Australia", "๐Ÿ‡ฆ๐Ÿ‡บ"], ["Austria", "๐Ÿ‡ฆ๐Ÿ‡น"], ["Azerbaijan", "๐Ÿ‡ฆ๐Ÿ‡ฟ"], ["Bahamas", "๐Ÿ‡ง๐Ÿ‡ธ"], ["Bahrain", "๐Ÿ‡ง๐Ÿ‡ญ"], ["Bangladesh", "๐Ÿ‡ง๐Ÿ‡ฉ"], ["Barbados", "๐Ÿ‡ง๐Ÿ‡ง"], ["Belarus", "๐Ÿ‡ง๐Ÿ‡พ"], ["Belgium", "๐Ÿ‡ง๐Ÿ‡ช"], ["Belize", "๐Ÿ‡ง๐Ÿ‡ฟ"], ["Benin", "๐Ÿ‡ง๐Ÿ‡ฏ"], ["Bermuda", "๐Ÿ‡ง๐Ÿ‡ฒ"], ["Bhutan", "๐Ÿ‡ง๐Ÿ‡น"], ["Bolivia, Plurinational State of", "๐Ÿ‡ง๐Ÿ‡ด"], ["Bonaire, Sint Eustatius and Saba", "๐Ÿ‡ง๐Ÿ‡ถ"], ["Bosnia and Herzegovina", "๐Ÿ‡ง๐Ÿ‡ฆ"], ["Botswana", "๐Ÿ‡ง๐Ÿ‡ผ"], ["Bouvet Island", "๐Ÿ‡ง๐Ÿ‡ป"], ["Brazil", "๐Ÿ‡ง๐Ÿ‡ท"], ["British Indian Ocean Territory", "๐Ÿ‡ฎ๐Ÿ‡ด"], ["Brunei Darussalam", "๐Ÿ‡ง๐Ÿ‡ณ"], ["Bulgaria", "๐Ÿ‡ง๐Ÿ‡ฌ"], ["Burkina Faso", "๐Ÿ‡ง๐Ÿ‡ซ"], ["Burundi", "๐Ÿ‡ง๐Ÿ‡ฎ"], ["Cambodia", "๐Ÿ‡ฐ๐Ÿ‡ญ"], ["Cameroon", "๐Ÿ‡จ๐Ÿ‡ฒ"], ["Canada", "๐Ÿ‡จ๐Ÿ‡ฆ"], ["Cape Verde", "๐Ÿ‡จ๐Ÿ‡ป"], ["Cayman Islands", "๐Ÿ‡ฐ๐Ÿ‡พ"], ["Central African Republic", "๐Ÿ‡จ๐Ÿ‡ซ"], ["Chad", "๐Ÿ‡น๐Ÿ‡ฉ"], ["Chile", "๐Ÿ‡จ๐Ÿ‡ฑ"], ["China", "๐Ÿ‡จ๐Ÿ‡ณ"], ["Christmas Island", "๐Ÿ‡จ๐Ÿ‡ฝ"], ["Cocos (Keeling) Islands", "๐Ÿ‡จ๐Ÿ‡จ"], ["Colombia", "๐Ÿ‡จ๐Ÿ‡ด"], ["Comoros", "๐Ÿ‡ฐ๐Ÿ‡ฒ"], ["Congo", "๐Ÿ‡จ๐Ÿ‡ฌ"], ["Congo, the Democratic Republic of the", "๐Ÿ‡จ๐Ÿ‡ฉ"], ["Cook Islands", "๐Ÿ‡จ๐Ÿ‡ฐ"], ["Costa Rica", "๐Ÿ‡จ๐Ÿ‡ท"], ["Cรดte d'Ivoire", "๐Ÿ‡จ๐Ÿ‡ฎ"], ["Croatia", "๐Ÿ‡ญ๐Ÿ‡ท"], ["Cuba", "๐Ÿ‡จ๐Ÿ‡บ"], ["Curaรงao", "๐Ÿ‡จ๐Ÿ‡ผ"], ["Cyprus", "๐Ÿ‡จ๐Ÿ‡พ"], ["Czech Republic", "๐Ÿ‡จ๐Ÿ‡ฟ"], ["Denmark", "๐Ÿ‡ฉ๐Ÿ‡ฐ"], ["Djibouti", "๐Ÿ‡ฉ๐Ÿ‡ฏ"], ["Dominica", "๐Ÿ‡ฉ๐Ÿ‡ฒ"], ["Dominican Republic", "๐Ÿ‡ฉ๐Ÿ‡ด"], ["Ecuador", "๐Ÿ‡ช๐Ÿ‡จ"], ["Egypt", "๐Ÿ‡ช๐Ÿ‡ฌ"], ["El Salvador", "๐Ÿ‡ธ๐Ÿ‡ป"], ["Equatorial Guinea", "๐Ÿ‡ฌ๐Ÿ‡ถ"], ["Eritrea", "๐Ÿ‡ช๐Ÿ‡ท"], ["Estonia", "๐Ÿ‡ช๐Ÿ‡ช"], ["Ethiopia", "๐Ÿ‡ช๐Ÿ‡น"], ["Falkland Islands (Malvinas)", "๐Ÿ‡ซ๐Ÿ‡ฐ"], ["Faroe Islands", "๐Ÿ‡ซ๐Ÿ‡ด"], ["Fiji", "๐Ÿ‡ซ๐Ÿ‡ฏ"], ["Finland", "๐Ÿ‡ซ๐Ÿ‡ฎ"], ["France", "๐Ÿ‡ซ๐Ÿ‡ท"], ["French Guiana", "๐Ÿ‡ฌ๐Ÿ‡ซ"], ["French Polynesia", "๐Ÿ‡ต๐Ÿ‡ซ"], ["French Southern Territories", "๐Ÿ‡น๐Ÿ‡ซ"], ["Gabon", "๐Ÿ‡ฌ๐Ÿ‡ฆ"], ["Gambia", "๐Ÿ‡ฌ๐Ÿ‡ฒ"], ["Georgia", "๐Ÿ‡ฌ๐Ÿ‡ช"], ["Germany", "๐Ÿ‡ฉ๐Ÿ‡ช"], ["Ghana", "๐Ÿ‡ฌ๐Ÿ‡ญ"], ["Gibraltar", "๐Ÿ‡ฌ๐Ÿ‡ฎ"], ["Greece", "๐Ÿ‡ฌ๐Ÿ‡ท"], ["Greenland", "๐Ÿ‡ฌ๐Ÿ‡ฑ"], ["Grenada", "๐Ÿ‡ฌ๐Ÿ‡ฉ"], ["Guadeloupe", "๐Ÿ‡ฌ๐Ÿ‡ต"], ["Guam", "๐Ÿ‡ฌ๐Ÿ‡บ"], ["Guatemala", "๐Ÿ‡ฌ๐Ÿ‡น"], ["Guernsey", "๐Ÿ‡ฌ๐Ÿ‡ฌ"], ["Guinea", "๐Ÿ‡ฌ๐Ÿ‡ณ"], ["Guinea-Bissau", "๐Ÿ‡ฌ๐Ÿ‡ผ"], ["Guyana", "๐Ÿ‡ฌ๐Ÿ‡พ"], ["Haiti", "๐Ÿ‡ญ๐Ÿ‡น"], ["Heard Island and McDonald Islands", "๐Ÿ‡ญ๐Ÿ‡ฒ"], ["Holy See (Vatican City State)", "๐Ÿ‡ป๐Ÿ‡ฆ"], ["Honduras", "๐Ÿ‡ญ๐Ÿ‡ณ"], ["Hong Kong", "๐Ÿ‡ญ๐Ÿ‡ฐ"], ["Hungary", "๐Ÿ‡ญ๐Ÿ‡บ"], ["Iceland", "๐Ÿ‡ฎ๐Ÿ‡ธ"], ["India", "๐Ÿ‡ฎ๐Ÿ‡ณ"], ["Indonesia", "๐Ÿ‡ฎ๐Ÿ‡ฉ"], ["Iran, Islamic Republic of", "๐Ÿ‡ฎ๐Ÿ‡ท"], ["Iraq", "๐Ÿ‡ฎ๐Ÿ‡ถ"], ["Ireland", "๐Ÿ‡ฎ๐Ÿ‡ช"], ["Isle of Man", "๐Ÿ‡ฎ๐Ÿ‡ฒ"], ["Israel", "๐Ÿ‡ฎ๐Ÿ‡ฑ"], ["Italy", "๐Ÿ‡ฎ๐Ÿ‡น"], ["Jamaica", "๐Ÿ‡ฏ๐Ÿ‡ฒ"], ["Japan", "๐Ÿ‡ฏ๐Ÿ‡ต"], ["Jersey", "๐Ÿ‡ฏ๐Ÿ‡ช"], ["Jordan", "๐Ÿ‡ฏ๐Ÿ‡ด"], ["Kazakhstan", "๐Ÿ‡ฐ๐Ÿ‡ฟ"], ["Kenya", "๐Ÿ‡ฐ๐Ÿ‡ช"], ["Kiribati", "๐Ÿ‡ฐ๐Ÿ‡ฎ"], ["Korea, Democratic People's Republic of", "๐Ÿ‡ฐ๐Ÿ‡ต"], ["Korea, Republic of", "๐Ÿ‡ฐ๐Ÿ‡ท"], ["Kuwait", "๐Ÿ‡ฐ๐Ÿ‡ผ"], ["Kyrgyzstan", "๐Ÿ‡ฐ๐Ÿ‡ฌ"], ["Lao People's Democratic Republic", "๐Ÿ‡ฑ๐Ÿ‡ฆ"], ["Latvia", "๐Ÿ‡ฑ๐Ÿ‡ป"], ["Lebanon", "๐Ÿ‡ฑ๐Ÿ‡ง"], ["Lesotho", "๐Ÿ‡ฑ๐Ÿ‡ธ"], ["Liberia", "๐Ÿ‡ฑ๐Ÿ‡ท"], ["Libya", "๐Ÿ‡ฑ๐Ÿ‡พ"], ["Liechtenstein", "๐Ÿ‡ฑ๐Ÿ‡ฎ"], ["Lithuania", "๐Ÿ‡ฑ๐Ÿ‡น"], ["Luxembourg", "๐Ÿ‡ฑ๐Ÿ‡บ"], ["Macao", "๐Ÿ‡ฒ๐Ÿ‡ด"], ["Macedonia, the Former Yugoslav Republic of", "๐Ÿ‡ฒ๐Ÿ‡ฐ"], ["Madagascar", "๐Ÿ‡ฒ๐Ÿ‡ฌ"], ["Malawi", "๐Ÿ‡ฒ๐Ÿ‡ผ"], ["Malaysia", "๐Ÿ‡ฒ๐Ÿ‡พ"], ["Maldives", "๐Ÿ‡ฒ๐Ÿ‡ป"], ["Mali", "๐Ÿ‡ฒ๐Ÿ‡ฑ"], ["Malta", "๐Ÿ‡ฒ๐Ÿ‡น"], ["Marshall Islands", "๐Ÿ‡ฒ๐Ÿ‡ญ"], ["Martinique", "๐Ÿ‡ฒ๐Ÿ‡ถ"], ["Mauritania", "๐Ÿ‡ฒ๐Ÿ‡ท"], ["Mauritius", "๐Ÿ‡ฒ๐Ÿ‡บ"], ["Mayotte", "๐Ÿ‡พ๐Ÿ‡น"], ["Mexico", "๐Ÿ‡ฒ๐Ÿ‡ฝ"], ["Micronesia, Federated States of", "๐Ÿ‡ซ๐Ÿ‡ฒ"], ["Moldova, Republic of", "๐Ÿ‡ฒ๐Ÿ‡ฉ"], ["Monaco", "๐Ÿ‡ฒ๐Ÿ‡จ"], ["Mongolia", "๐Ÿ‡ฒ๐Ÿ‡ณ"], ["Montenegro", "๐Ÿ‡ฒ๐Ÿ‡ช"], ["Montserrat", "๐Ÿ‡ฒ๐Ÿ‡ธ"], ["Morocco", "๐Ÿ‡ฒ๐Ÿ‡ฆ"], ["Mozambique", "๐Ÿ‡ฒ๐Ÿ‡ฟ"], ["Myanmar", "๐Ÿ‡ฒ๐Ÿ‡ฒ"], ["Namibia", "๐Ÿ‡ณ๐Ÿ‡ฆ"], ["Nauru", "๐Ÿ‡ณ๐Ÿ‡ท"], ["Nepal", "๐Ÿ‡ณ๐Ÿ‡ต"], ["Netherlands", "๐Ÿ‡ณ๐Ÿ‡ฑ"], ["New Caledonia", "๐Ÿ‡ณ๐Ÿ‡จ"], ["New Zealand", "๐Ÿ‡ณ๐Ÿ‡ฟ"], ["Nicaragua", "๐Ÿ‡ณ๐Ÿ‡ฎ"], ["Niger", "๐Ÿ‡ณ๐Ÿ‡ช"], ["Nigeria", "๐Ÿ‡ณ๐Ÿ‡ฌ"], ["Niue", "๐Ÿ‡ณ๐Ÿ‡บ"], ["Norfolk Island", "๐Ÿ‡ณ๐Ÿ‡ซ"], ["Northern Mariana Islands", "๐Ÿ‡ฒ๐Ÿ‡ต"], ["Norway", "๐Ÿ‡ณ๐Ÿ‡ด"], ["Oman", "๐Ÿ‡ด๐Ÿ‡ฒ"], ["Pakistan", "๐Ÿ‡ต๐Ÿ‡ฐ"], ["Palau", "๐Ÿ‡ต๐Ÿ‡ผ"], ["Palestine, State of", "๐Ÿ‡ต๐Ÿ‡ธ"], ["Panama", "๐Ÿ‡ต๐Ÿ‡ฆ"], ["Papua New Guinea", "๐Ÿ‡ต๐Ÿ‡ฌ"], ["Paraguay", "๐Ÿ‡ต๐Ÿ‡พ"], ["Peru", "๐Ÿ‡ต๐Ÿ‡ช"], ["Philippines", "๐Ÿ‡ต๐Ÿ‡ญ"], ["Pitcairn", "๐Ÿ‡ต๐Ÿ‡ณ"], ["Poland", "๐Ÿ‡ต๐Ÿ‡ฑ"], ["Portugal", "๐Ÿ‡ต๐Ÿ‡น"], ["Puerto Rico", "๐Ÿ‡ต๐Ÿ‡ท"], ["Qatar", "๐Ÿ‡ถ๐Ÿ‡ฆ"], ["Rรฉunion", "๐Ÿ‡ท๐Ÿ‡ช"], ["Romania", "๐Ÿ‡ท๐Ÿ‡ด"], ["Russian Federation", "๐Ÿ‡ท๐Ÿ‡บ"], ["Rwanda", "๐Ÿ‡ท๐Ÿ‡ผ"], ["Saint Barthรฉlemy", "๐Ÿ‡ง๐Ÿ‡ฑ"], ["Saint Helena, Ascension and Tristan da Cunha", "๐Ÿ‡ธ๐Ÿ‡ญ"], ["Saint Kitts and Nevis", "๐Ÿ‡ฐ๐Ÿ‡ณ"], ["Saint Lucia", "๐Ÿ‡ฑ๐Ÿ‡จ"], ["Saint Martin (French part)", "๐Ÿ‡ฒ๐Ÿ‡ซ"], ["Saint Pierre and Miquelon", "๐Ÿ‡ต๐Ÿ‡ฒ"], ["Saint Vincent and the Grenadines", "๐Ÿ‡ป๐Ÿ‡จ"], ["Samoa", "๐Ÿ‡ผ๐Ÿ‡ธ"], ["San Marino", "๐Ÿ‡ธ๐Ÿ‡ฒ"], ["Sao Tome and Principe", "๐Ÿ‡ธ๐Ÿ‡น"], ["Saudi Arabia", "๐Ÿ‡ธ๐Ÿ‡ฆ"], ["Senegal", "๐Ÿ‡ธ๐Ÿ‡ณ"], ["Serbia", "๐Ÿ‡ท๐Ÿ‡ธ"], ["Seychelles", "๐Ÿ‡ธ๐Ÿ‡จ"], ["Sierra Leone", "๐Ÿ‡ธ๐Ÿ‡ฑ"], ["Singapore", "๐Ÿ‡ธ๐Ÿ‡ฌ"], ["Sint Maarten (Dutch part)", "๐Ÿ‡ธ๐Ÿ‡ฝ"], ["Slovakia", "๐Ÿ‡ธ๐Ÿ‡ฐ"], ["Slovenia", "๐Ÿ‡ธ๐Ÿ‡ฎ"], ["Solomon Islands", "๐Ÿ‡ธ๐Ÿ‡ง"], ["Somalia", "๐Ÿ‡ธ๐Ÿ‡ด"], ["South Africa", "๐Ÿ‡ฟ๐Ÿ‡ฆ"], ["South Georgia and the South Sandwich Islands", "๐Ÿ‡ฌ๐Ÿ‡ธ"], ["South Sudan", "๐Ÿ‡ธ๐Ÿ‡ธ"], ["Spain", "๐Ÿ‡ช๐Ÿ‡ธ"], ["Sri Lanka", "๐Ÿ‡ฑ๐Ÿ‡ฐ"], ["Sudan", "๐Ÿ‡ธ๐Ÿ‡ฉ"], ["Suriname", "๐Ÿ‡ธ๐Ÿ‡ท"], ["Svalbard and Jan Mayen", "๐Ÿ‡ธ๐Ÿ‡ฏ"], ["Eswatini", "๐Ÿ‡ธ๐Ÿ‡ฟ"], ["Sweden", "๐Ÿ‡ธ๐Ÿ‡ช"], ["Switzerland", "๐Ÿ‡จ๐Ÿ‡ญ"], ["Syrian Arab Republic", "๐Ÿ‡ธ๐Ÿ‡พ"], ["Taiwan", "๐Ÿ‡น๐Ÿ‡ผ"], ["Tajikistan", "๐Ÿ‡น๐Ÿ‡ฏ"], ["Tanzania, United Republic of", "๐Ÿ‡น๐Ÿ‡ฟ"], ["Thailand", "๐Ÿ‡น๐Ÿ‡ญ"], ["Timor-Leste", "๐Ÿ‡น๐Ÿ‡ฑ"], ["Togo", "๐Ÿ‡น๐Ÿ‡ฌ"], ["Tokelau", "๐Ÿ‡น๐Ÿ‡ฐ"], ["Tonga", "๐Ÿ‡น๐Ÿ‡ด"], ["Trinidad and Tobago", "๐Ÿ‡น๐Ÿ‡น"], ["Tunisia", "๐Ÿ‡น๐Ÿ‡ณ"], ["Turkey", "๐Ÿ‡น๐Ÿ‡ท"], ["Turkmenistan", "๐Ÿ‡น๐Ÿ‡ฒ"], ["Turks and Caicos Islands", "๐Ÿ‡น๐Ÿ‡จ"], ["Tuvalu", "๐Ÿ‡น๐Ÿ‡ป"], ["Uganda", "๐Ÿ‡บ๐Ÿ‡ฌ"], ["Ukraine", "๐Ÿ‡บ๐Ÿ‡ฆ"], ["United Arab Emirates", "๐Ÿ‡ฆ๐Ÿ‡ช"], ["United Kingdom", "๐Ÿ‡ฌ๐Ÿ‡ง"], ["United States", "๐Ÿ‡บ๐Ÿ‡ธ"], ["United States Minor Outlying Islands", "๐Ÿ‡บ๐Ÿ‡ฒ"], ["Uruguay", "๐Ÿ‡บ๐Ÿ‡พ"], ["Uzbekistan", "๐Ÿ‡บ๐Ÿ‡ฟ"], ["Vanuatu", "๐Ÿ‡ป๐Ÿ‡บ"], ["Venezuela, Bolivarian Republic of", "๐Ÿ‡ป๐Ÿ‡ช"], ["Viet Nam", "๐Ÿ‡ป๐Ÿ‡ณ"], ["Virgin Islands, British", "๐Ÿ‡ป๐Ÿ‡ฌ"], ["Virgin Islands, U.S.", "๐Ÿ‡ป๐Ÿ‡ฎ"], ["Wallis and Futuna", "๐Ÿ‡ผ๐Ÿ‡ซ"], ["Western Sahara", "๐Ÿ‡ช๐Ÿ‡ญ"], ["Yemen", "๐Ÿ‡พ๐Ÿ‡ช"], ["Zambia", "๐Ÿ‡ฟ๐Ÿ‡ฒ"], ["Zimbabwe", "๐Ÿ‡ฟ๐Ÿ‡ผ"], ["Europe", "๐ŸŒ"], ["East Asia & Pacific", "๐ŸŒ"], ["North America", "๐ŸŒŽ"], ["South America", "๐ŸŒŽ"], ["Eastern Europe (Non-EU)", "๐ŸŒ"], ["West Asia", "๐ŸŒ"], ["South Asia", "๐ŸŒ"], ["Australasia", "๐ŸŒ"], ]); const dbPromise = new Promise((resolve) => { const request = indexedDB.open("aboutAccountCache", 1); request.onupgradeneeded = () => request.result.createObjectStore("countries"); request.onsuccess = () => resolve(request.result); request.onerror = () => resolve(null); }); if (!csrfToken) return; const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // --- HELPER: CSP BYPASS --- const gmFetch = (url, options) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || "GET", url: url, headers: options.headers, data: options.body, onload: (res) => { resolve({ ok: res.status >= 200 && res.status < 300, status: res.status, json: () => Promise.resolve(JSON.parse(res.responseText)) }); }, onerror: (e) => reject(e) }); }); }; // --- 1. LOCAL STORAGE LOGIC --- const saveCountry = (username, country) => { dbPromise.then((db) => { if (!db) return; const tx = db.transaction("countries", "readwrite"); tx.objectStore("countries").put({ country, timestamp: Date.now() }, username); }); }; const getFromDb = (username) => { return dbPromise.then((db) => { if (!db) return null; return new Promise((resolve) => { const tx = db.transaction("countries", "readonly"); const req = tx.objectStore("countries").get(username); req.onsuccess = () => resolve(req.result); req.onerror = () => resolve(null); }); }); }; // --- 2. REMOTE INTERACTION (BATCH READ & WRITE) --- // A. BATCH READ let readBlocked = false; const flushReadBatch = async () => { const batch = [...new Set(readBuffer)]; readBuffer = []; readTimeout = null; if (batch.length === 0) return; if (readBlocked || REMOTE_DB_URL.includes("YOUR-SUBDOMAIN")) { batch.forEach(u => apiQueue.push(u)); processApiQueue(); return; } try { const res = await gmFetch(REMOTE_DB_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "batch_read", usernames: batch }) }); if (!res.ok) { if (res.status === 429 || res.status >= 500) { console.warn(`[Filter X] Cloudflare Read Failed (${res.status}). Switching to local-only mode.`); readBlocked = true; } throw new Error(`Worker returned ${res.status}`); } const results = await res.json(); batch.forEach(username => { if (results[username]) { const country = results[username]; cache.set(username, country); saveCountry(username, country); finalizeUser(username, country); } else { apiQueue.push(username); processApiQueue(); } }); } catch (e) { console.warn("[Filter X] Read Batch Failed", e); batch.forEach(u => apiQueue.push(u)); processApiQueue(); } }; const addToReadBatch = (username) => { readBuffer.push(username); if (!readTimeout) readTimeout = setTimeout(flushReadBatch, READ_BATCH_DELAY); }; // B. BATCH WRITE let writeBlocked = false; const flushWriteBatch = async () => { if (writeBlocked) { writeBuffer.clear(); return; } const batch = Array.from(writeBuffer, ([username, location]) => ({ username, location })); writeBuffer.clear(); writeTimeout = null; if (batch.length === 0) return; if (REMOTE_DB_URL.includes("YOUR-SUBDOMAIN")) return; try { const res = await gmFetch(REMOTE_DB_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "batch_write", entries: batch }) }); if (res.status === 429) { writeBlocked = true; return; } } catch (e) { console.warn("[Filter X] Write Error", e); } }; const addToWriteBatch = (username, location) => { if (writeBlocked) return; if (!username || !location) return; writeBuffer.set(username, location); if (writeBuffer.size >= WRITE_BATCH_SIZE) { if (writeTimeout) clearTimeout(writeTimeout); flushWriteBatch(); } else if (!writeTimeout) { writeTimeout = setTimeout(flushWriteBatch, WRITE_BATCH_DELAY); } }; // --- 3. X API LOGIC --- const fetchCountry = async (username) => { for (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) { try { const params = new URLSearchParams({ variables: JSON.stringify({ screenName: username }) }); const res = await fetch(`${queryUrl}?${params.toString()}`, { method: "GET", headers: { authorization: authToken, "x-csrf-token": csrfToken }, credentials: "include", }); if (res.status === 429) return "RATE_LIMIT"; if (!res.ok) throw new Error(`HTTP ${res.status}`); const about = (await res.json())?.data?.user_result_by_screen_name?.result?.about_profile; return about?.account_based_in; } catch (e) { await wait(1000 * (attempt + 1) + Math.random() * 500); } } return null; }; const processApiQueue = () => { if (isRateLimited || activeRequests >= CONCURRENT_API_LIMIT || apiQueue.length === 0) return; while (activeRequests < CONCURRENT_API_LIMIT && apiQueue.length > 0) { const username = apiQueue.shift(); if (cache.has(username)) { finalizeUser(username, cache.get(username)); continue; } activeRequests++; (async () => { try { const country = await fetchCountry(username); if (country === "RATE_LIMIT") { isRateLimited = true; apiQueue.unshift(username); setTimeout(() => { isRateLimited = false; processApiQueue(); }, RATE_LIMIT_PAUSE); return; } if (country) { cache.set(username, country); saveCountry(username, country); addToWriteBatch(username, country); finalizeUser(username, country); } else { pending.delete(username); } } finally { activeRequests--; const delay = (REQUEST_DELAY / CONCURRENT_API_LIMIT) + (Math.random() * RANDOM_JITTER); setTimeout(processApiQueue, delay); } })(); } }; // --- 4. DOM LOGIC --- const finalizeUser = (username, country) => { const targets = pending.get(username); if (!targets) return; targets.forEach(({ container, tweet, type }) => applyCountry(username, container, tweet, country, type)); pending.delete(username); }; const enqueue = (username, container, tweet, type = "tweet") => { if (cache.has(username)) { applyCountry(username, container, tweet, cache.get(username), type); return; } const existingTargets = pending.get(username); if (existingTargets) { existingTargets.push({ container, tweet, type }); return; } pending.set(username, [{ container, tweet, type }]); getFromDb(username).then((record) => { if (!pending.has(username)) return; if (record && (Date.now() - record.timestamp < CACHE_TTL)) { cache.set(username, record.country); finalizeUser(username, record.country); } else { addToReadBatch(username); } }); }; const applyCountry = (username, container, targetElement, country, type = "tweet") => { const flag = countryFlags.get(country) || country; const flagText = ` ${flag}`; let matchesFilter = false; if (filterEnabled && filterValues.length && country) { const lowerCountry = country.toLowerCase(); const match = filterValues.some((value) => lowerCountry.includes(value.toLowerCase())); matchesFilter = filterMode === "whitelist" ? !match : match; } const shouldBlock = matchesFilter; if (container) { const existing = container.querySelector(".filter-x-flag"); let flagNode; if (existing) { flagNode = existing; flagNode.textContent = flagText; } else { flagNode = document.createElement("span"); flagNode.className = "filter-x-flag"; flagNode.dataset.accountBasedIn = "true"; flagNode.textContent = flagText; flagNode.style.padding = type === "profile" ? "0px" : "0px 10px"; flagNode.style.cursor = "help"; container.appendChild(flagNode); } flagNode.title = country; } if (type === "tweet" && targetElement) { const tweetWrapper = targetElement.closest?.('[data-testid="tweet"]') || targetElement.parentElement?.parentElement?.parentElement; if (tweetWrapper) { tweetWrapper.style.display = shouldBlock ? "none" : ""; } } }; const handleTweet = (tweet) => { const userNameRoot = tweet.querySelector('[data-testid="User-Name"]'); if (!userNameRoot) return; const anchor = userNameRoot.querySelector("a"); const username = anchor?.getAttribute("href")?.replace(/^\//, ""); if (!username) return; let container = anchor; while (container.parentElement && container.parentElement !== userNameRoot) { container = container.parentElement; } enqueue(username, container, tweet, "tweet"); }; const handleProfile = (headerStats) => { const pathParts = window.location.pathname.split('/'); const username = pathParts[1]; if (!username) return; const ignoreList = ["home", "explore", "notifications", "messages", "settings", "search", "bookmarks"]; if (ignoreList.includes(username)) return; const primaryCol = document.querySelector('[data-testid="primaryColumn"]'); const nameContainer = primaryCol?.querySelector('[data-testid="UserName"]'); enqueue(username, nameContainer, null, "profile"); }; const onUrlChange = () => { const profileHeader = document.querySelector('[data-testid="UserProfileHeader_Items"]'); if (profileHeader) { delete profileHeader.dataset.filterXObserved; profileHeader.querySelector('.filter-x-flag')?.remove(); handleProfile(profileHeader); } }; // Hook into history API const originalPushState = history.pushState; history.pushState = function() { const res = originalPushState.apply(this, arguments); onUrlChange(); return res; }; const originalReplaceState = history.replaceState; history.replaceState = function() { const res = originalReplaceState.apply(this, arguments); onUrlChange(); return res; }; window.addEventListener('popstate', onUrlChange); const observerCallback = (entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { handleTweet(entry.target); observer.unobserve(entry.target); } }); }; const tweetObserver = new IntersectionObserver(observerCallback, { rootMargin: "200px 0px" }); const findTweets = (root) => { if (root.matches?.('[data-testid="tweet"]') && !root.dataset.filterXObserved) { root.dataset.filterXObserved = "true"; tweetObserver.observe(root); return; } if (root.querySelectorAll) { const tweets = root.querySelectorAll('[data-testid="tweet"]:not([data-filter-x-observed])'); for (let i = 0; i < tweets.length; i++) { const tweet = tweets[i]; tweet.dataset.filterXObserved = "true"; tweetObserver.observe(tweet); } } }; const findProfile = (root) => { const profileHeader = root.matches?.('[data-testid="UserProfileHeader_Items"]') ? root : root.querySelector?.('[data-testid="UserProfileHeader_Items"]'); if (!profileHeader) return; const currentUsername = window.location.pathname.split('/')[1]; const observedUsername = profileHeader.dataset.filterXObserved; if (observedUsername && observedUsername !== currentUsername) { profileHeader.querySelector('.filter-x-flag')?.remove(); profileHeader.dataset.filterXObserved = ""; // Force reset } if (!profileHeader.dataset.filterXObserved && currentUsername) { profileHeader.dataset.filterXObserved = currentUsername; handleProfile(profileHeader); } }; const findNav = (root) => { const nav = root.querySelector?.('nav[data-testid="AppTabBar"]') || root.querySelector?.('nav[role="navigation"]'); if (!nav || nav.querySelector("[data-cutoff-config]")) return; const wrapper = document.createElement("div"); wrapper.dataset.cutoffConfig = "true"; wrapper.style.margin = "8px 0"; wrapper.style.fontFamily = 'TwitterChirp, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto'; wrapper.innerHTML = `
`; const button = wrapper.querySelector("button"); const menu = wrapper.querySelector("div"); const checkbox = menu?.querySelector('input[type="checkbox"]'); const textarea = menu?.querySelector("textarea"); const select = menu?.querySelector("select"); if (!button || !menu || !checkbox || !textarea || !select) return; button.addEventListener("click", () => { menu.style.display = "block"; button.setAttribute("aria-expanded", "true"); }); document.addEventListener("click", (event) => { if (!wrapper.contains(event.target)) { menu.style.display = "none"; button.setAttribute("aria-expanded", "false"); } }); checkbox.addEventListener("change", () => { filterEnabled = checkbox.checked; localStorage.setItem("tweetFilterEnabled", filterEnabled.toString()); document.querySelectorAll('[data-testid="tweet"]').forEach(t => handleTweet(t)); const profileH = document.querySelector('[data-testid="UserProfileHeader_Items"]'); if(profileH) handleProfile(profileH); }); textarea.addEventListener("change", () => { filterValues = textarea.value .split("\n") .map((value) => value.trim()) .filter(Boolean); localStorage.setItem("tweetFilterValues", filterValues.join("\n")); document.querySelectorAll('[data-testid="tweet"]').forEach(t => handleTweet(t)); const profileH = document.querySelector('[data-testid="UserProfileHeader_Items"]'); if(profileH) handleProfile(profileH); }); select.addEventListener("change", () => { filterMode = select.value; localStorage.setItem("tweetFilterMode", filterMode); document.querySelectorAll('[data-testid="tweet"]').forEach(t => handleTweet(t)); const profileH = document.querySelector('[data-testid="UserProfileHeader_Items"]'); if(profileH) handleProfile(profileH); }); nav.appendChild(wrapper); }; findNav(document); findTweets(document); findProfile(document); new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { findTweets(node); if (node.tagName === 'NAV' || node.querySelector?.('nav')) findNav(node); if (node.querySelector?.('[data-testid="UserProfileHeader_Items"]')) findProfile(node); } } } }).observe(document, { childList: true, subtree: true }); })();