├── .gitignore
├── README.md
├── index.html
├── logo.jpg
├── manifest.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── icons
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon32.png
│ └── icon48.png
└── vite.svg
├── scripts
└── twitter.js
├── src
├── App.css
├── App.tsx
├── Bookmarks.tsx
├── SearchBar.tsx
├── assets
│ └── solid.svg
├── background.ts
├── bookmark-tojson.js
├── content.js
├── index.css
├── index.tsx
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Twitter Advanced Search Exptension
2 |
3 | - Chrome Webstore link: https://chromewebstore.google.com/detail/twitter-advanced-search/ngglldgjkjekfbmbfpankdmmknabbgmp?hl=en
4 |
5 | ## How to build it from source
6 |
7 | - Clone this repository
8 | - Run `npm install`
9 | - Run `npm run build`
10 | - Go to `chrome://extensions/`
11 | - Enable developer mode
12 | - Click on `Load unpacked extension`
13 | - Select the `dist` folder
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
55 |
56 |
57 | {tabs.map((tab) => (
58 | -
59 |
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 |
75 | {tabs.map((tab) => (
76 |
77 | {tab.content}
78 |
79 | ))}
80 |
81 |
82 | {/*
83 |
90 |
93 |
94 |
*/}
95 |
96 |
97 |
104 |
114 |
115 |
116 |
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/src/Bookmarks.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal, onMount } from "solid-js";
2 |
3 | export default function Bookmarks() {
4 | const [bookmarks, setBookmarks] = createSignal([]);
5 | const [searchQuery, setSearchQuery] = createSignal("");
6 |
7 | const saveTweets = () => {
8 | console.log("clicked");
9 | chrome.runtime.sendMessage({ message: "injectScript" });
10 | }
11 |
12 | onMount(() => {
13 | chrome.runtime.sendMessage({ action: "getLocalStorage" }, (response) => {
14 | console.log(response.data.length);
15 | setBookmarks(response.data)
16 | });
17 | });
18 |
19 | const filteredBookmarks = () => {
20 | const query = searchQuery().toLowerCase();
21 | return bookmarks().filter(tweet =>
22 | Object.values(tweet).some(value =>
23 | typeof value === "string" && value.toLowerCase().includes(query)
24 | )
25 | );
26 | };
27 |
28 | const highlightText = (text) => {
29 | const query = searchQuery().toLowerCase();
30 | if (query === "") return text;
31 |
32 | const regex = new RegExp(`(${query})`, "gi");
33 | return text.split(regex).map((part, _) =>
34 | regex.test(part.toLowerCase()) ? (
35 |
44 | {bookmarks().length === 0 ? (
45 |
48 |
52 |
53 | ) : (
54 |
87 | )}
88 |
89 | );
90 | }
91 |
92 | //object example
93 | // {
94 | // text: tweetText,
95 | // authorName: authorName,
96 | // authorHandle: authorHandle,
97 | // authorProfilePic: authorProfilePic,
98 | // date: tweetTimeEl.getAttribute("datetime"),
99 | // url: tweetUrl,
100 | // media: {
101 | // images: tweetImgsSrc.length > 0 ? tweetImgsSrc : null,
102 | // videoThumbnails: tweetVideoThumbnails.length > 0 ? tweetVideoThumbnails : null,
103 | // },
104 | // };
--------------------------------------------------------------------------------
/src/SearchBar.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal, onMount } from "solid-js";
2 |
3 | //im making a twitter advanced search extension
4 |
5 | export default function SearchBar() {
6 | const [word, setWord] = createSignal("");
7 | const [account, setAccount] = createSignal("");
8 | const [minLikes, setMinLikes] = createSignal(0);
9 | const [includeReplies, setIncludeReplies] = createSignal(true);
10 | const [fromDate, setFromDate] = createSignal("");
11 | const [toDate, setToDate] = createSignal("");
12 | const [follows, setFollows] = createSignal(false);
13 | let accountHistory: string[] = getAccountLocalStorage();
14 | const avoidList = ["home", "explore", "notifications", "messages", "i", "settings"];
15 |
16 | // Check for initial preference
17 | onMount(() => {
18 | chrome.runtime.sendMessage({ query: "getURL" }, (response) => {
19 | if (response.tabURL) {
20 | const url = new URL(response.tabURL);
21 | const pathSegments = url.pathname.split('/').filter(Boolean);
22 | if (pathSegments.length > 0 && url.hostname === "x.com") {
23 | const twitterUsername = pathSegments[0];
24 | if (avoidList.includes(twitterUsername)) {
25 | return;
26 | }
27 | setAccount(twitterUsername); // Your state update function
28 | }
29 | }
30 | });
31 |
32 | });
33 |
34 | //function to store key value pair in local storage
35 | function setLocalStorage(key: string, value: any) {
36 | localStorage.setItem(key, JSON.stringify(value));
37 | }
38 |
39 | //function to get key value pair from local storage
40 | function getLocalStorage(key: string) {
41 | const value = localStorage.getItem(key);
42 | if (value) {
43 | return JSON.parse(value);
44 | }
45 | }
46 |
47 | //function to store a account in local storage
48 | function setAccountLocalStorage(value: string) {
49 | const accounts = new Set(getAccountLocalStorage());
50 | accounts.add(value);
51 | setLocalStorage("accounts", Array.from(accounts));
52 | }
53 | //function to get accounts from local storage
54 | function getAccountLocalStorage() {
55 | const accounts = getLocalStorage("accounts");
56 | if (accounts) {
57 | return accounts.slice(-5);
58 | }
59 | return [];
60 | }
61 |
62 | const onSubmit = (e: any) => {
63 | e.preventDefault();
64 | let url = "https://x.com/search?lang=en&q=";
65 | if (word()) {
66 | url += word();
67 | }
68 | if (account()) {
69 | url += "%20(from%3A" + account() + ")";
70 | setAccountLocalStorage(account());
71 | }
72 | if (minLikes()) {
73 | url += "%20min_faves%3A" + minLikes();
74 | }
75 | if (!includeReplies()) {
76 | url += "%20-filter%3Areplies";
77 | }
78 | if (follows()) {
79 | url += "%20filter%3Afollows";
80 | }
81 | if (fromDate()) {
82 | url += "%20since%3A" + fromDate();
83 | }
84 | if (toDate()) {
85 | url += "%20until%3A" + toDate();
86 | }
87 | window.open(url, "_blank");
88 | };
89 |
90 |
91 |
92 | return (
93 |
233 |
filterRecommendedList(e.target.value)}
238 | value={props.value ? props.value : ""}
239 | />
240 |
243 | {/* recommended list show when input tag is on focus */}
244 | {list() && (
245 |
246 | {list().map((item) => (
247 |
setRecommendedValue(item)} class="px-3 py-2.5 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-gray-900 cursor-pointer">
248 | {item}
249 |
250 | ))}
251 |
252 | )}
253 |
254 | {/* subtext */}
255 |
256 |
257 | {props.subtext}
258 |
259 |
260 |
261 | );
262 | }
263 |
264 | //checkbox component
265 | interface CheckboxComponentProps {
266 | label?: string;
267 | value?: boolean;
268 | onChange?: (e: any) => void;
269 | }
270 |
271 | function CheckboxComponent({ label, value, onChange }: CheckboxComponentProps) {
272 | const id = Math.random().toString(36).substr(2, 9);
273 | return (
274 |