47 |
Privacy Policy
48 |
Personal data
49 |
50 | OptiSearch does not collect searching history, browser history, or any other personal data.
51 |
52 |
53 | In future OptiSearch may collect browser version, platform name, display settings and user's OptiSearch settings
54 | (but never data which can help identify user). Such information could be needed for decision
55 | on implementing or removing features as well as finding bugs.
56 |
57 |
58 | It will happen only with your permission.
59 |
60 |
61 |
Local Storage
62 |
63 | OptiSearch uses Chrome (Chromium) or WebExtensions Storage Sync API for storing user's settings, as well as
64 | files
65 | (written by the developpers of the extension) fetched from
66 | Github Gist necessary
67 | for the extension to work.
68 |
69 |
70 |
Reliance on External Services
71 |
72 |
OptiSearch, Copilot in Google, and Gemini next to Google results ("the Extensions")
73 | rely on external services and libraries, including ExtPay.js , which utilizes Stripe for
74 | payment processing and PayPal for donation processing, to provide certain features and
75 | functionalities, such as processing payments for premium memberships and accepting donations.
76 | Additionally, the Extensions integrate with third-party applications, including Microsoft Copilot, ChatGPT, and Google Gemini,
77 | to enhance their functionalities and user experiences. While we make every effort to ensure the smooth operation of the Extensions,
78 | it is important to understand that we may not have full control over these external services, and they may be subject to changes, disruptions, or discontinuation by their respective providers.
79 |
80 |
81 |
Changes to External Services
82 |
83 |
The providers of external services and libraries, including ExtPay.js, Stripe, and PayPal,
84 | may update their services, change their policies, or cease to operate at any time without prior notice.
85 | These changes may impact the functionality of the Extensions, including premium membership payment
86 | processing, donation processing, and other related services. Similarly, third-party applications such as Microsoft Copilot, ChatGPT, and Google Gemini may introduce updates or changes that affect the integration with the Extensions.
87 |
88 |
89 |
Liability and Continuity
90 |
91 |
We will strive to adapt to any changes or disruptions in external services, third-party applications, and payment processors to minimize any potential impact on the Extensions and its users. However, we cannot guarantee the uninterrupted availability or performance of these external services, payment processors, and third-party applications, and we shall not be held liable for any issues arising from such changes or disruptions.
92 |
93 |
94 |
User Responsibility
95 |
96 |
Users of the Extensions are responsible for reviewing the terms and policies of external services, such as Stripe and PayPal, and third-party applications, including Microsoft Copilot, ChatGPT, and Google Gemini, that they interact with through the Extensions. Users should also ensure that they comply with the terms and conditions of these services and applications.
97 |
98 |
99 |
The links to the terms and policies of those services are listed below:
100 |
107 |
108 |
Contact
109 |
110 | If you have any questions or comments about this privacy policy, you may contact me at my email address
111 | info.optisearch@gmail.com .
112 |
113 |
114 | March 2024
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/docs/uninstall.css:
--------------------------------------------------------------------------------
1 | /* :root {
2 | --color: #222;
3 | --bg: rgb(215, 210, 204);
4 | --darker-bg: #eee;
5 | } */
6 |
7 | :root {
8 | --color: rgb(215, 210, 204);
9 | --bg: #1c1c1c;
10 | --darker-bg: #111111;
11 | --dark-grey: rgb(51, 51, 51);
12 | --grey: #111;
13 | --dark-grey: #ddd;
14 | --lighter-grey: #222;
15 | }
16 |
17 | body {
18 | background-color: var(--bg);
19 | color: var(--color);
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | font-family: Open Sans, Arial, system-ui;
24 | overflow-y: hidden;
25 | }
26 |
27 |
28 |
29 | #goodbye {
30 | background-image: url('good_bye.png');
31 | width: 100%;
32 | height: 100%;
33 | position: absolute;
34 | z-index: -10;
35 | left: 0;
36 | }
37 | #over_goodbye {
38 | background: #111e;
39 | width: 100%;
40 | height: 100%;
41 | position: absolute;
42 | z-index: -5;
43 | color: transparent;
44 | }
45 |
46 | h1, h3{
47 | text-align: center;
48 | }
49 |
50 | h1 {
51 | margin: 3em 0;
52 | font-size: 3em;
53 | }
54 |
55 | h3 {
56 | margin: 1em 0;
57 | font-size: 2em;
58 | }
59 |
60 | /* GPX Follower ad */
61 | #gpxfollower {
62 | text-align: left;
63 | display: flex;
64 | flex-direction: column;
65 | align-items: flex-start; /* Align items to the start (left) */
66 | background-color: #fff;
67 | padding: 0.8em 1em;
68 | margin: 1em;
69 | text-decoration: none;
70 | box-shadow: rgb(0 0 0 / 25%) 0px 3px 20px 5px;
71 | border-radius: .5em;
72 | }
73 |
74 | #gpxfollower > #also-from {
75 | font-size: 0.9em;
76 | color: var(--lighter-grey);
77 | margin-bottom: .5em; /* Spacing below "Also from OptiSearch" */
78 | }
79 |
80 | .gpxfollower-app {
81 | display: flex;
82 | align-items: center;
83 | }
84 |
85 | .gpxfollower-app .logo {
86 | width: 6em; /* Adjust as needed */
87 | height: auto;
88 | border-radius: 20%;
89 | }
90 |
91 | #gpxfollower:hover .text-app .app-name {
92 | text-decoration: underline;
93 | }
94 |
95 | .text-app .app-name {
96 | font-weight: bold;
97 | font-size: 1.2em;
98 | margin-bottom: 0.4em;
99 | }
100 | .text-app {
101 | display: flex;
102 | flex-direction: column;
103 | padding: .5em;
104 | margin: 0 2em;
105 | }
106 |
107 | .text-app .app-desc {
108 | color: var(--lighter-grey);
109 | }
110 |
111 | .icon-container {
112 | position: relative;
113 | display: inline-block; /* Ensure the container wraps around the icon and text */
114 | }
115 |
116 | .googleplay {
117 | height: 5em;
118 | width: max-content;
119 | margin: -.6em;
120 | margin-top: .5em;
121 | }
122 |
123 | .new-text {
124 | position: absolute;
125 | top: 0;
126 | right: 0;
127 | font-weight: bold;
128 | color: var(--color);
129 | padding: 3px 5px; /* Adjust as needed */
130 | background-color: red; /* Adjust as needed */
131 | animation: bounce 0.5s infinite alternate; /* Adjust duration and timing as needed */
132 | }
133 |
134 | @keyframes bounce {
135 | 0% {
136 | transform: translate(20%, 20%) rotate(40deg) scale(0.8); /* Initial scale */
137 | }
138 | 100% {
139 | transform: translate(20%, 20%) rotate(40deg) scale(.9); /* Scale at the peak of the bounce */
140 | }
141 | }
--------------------------------------------------------------------------------
/docs/uninstall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | (.*?)<\/div>/),
96 | img32: parseStr(html, /(https:\/\/lh3\.googleusercontent\.com\/[^\s'"]*?s32[^\s'"]*)/),
97 | img64: parseStr(html, /(https:\/\/lh3\.googleusercontent\.com\/[^\s'"]*?s64[^\s'"]*)/),
98 | };
99 | Object.entries(BardSession.accountConfigKeys).forEach(([k, v]) => res[k] = data[v]);
100 | res.hasNotBard = false;
101 | return res;
102 | };
103 | const url = `https://gemini.google.com/u/${user_id}/`;
104 | const r = await bgFetch(url, { credentials: "include", redirect: "manual" });
105 | if (r.status) {
106 | switch (r.status) {
107 | case 0: // redirected, which means that the user is not logged in at this account index
108 | throw BardSession.errors.session;
109 | case 200:
110 | if(r.body) {
111 | return parseData(r.body);
112 | }
113 | throw BardSession.errors.session;
114 | case 429:
115 | throw {
116 | code: "BARD_CAPTCHA",
117 | url,
118 | text: _t("Too many requests. Please solve the captcha and refresh"),
119 | button: _t("Solve Google Gemini captcha"),
120 | };
121 | default:
122 | throw BardSession.errors.session;
123 | }
124 | } else if(typeof r === "string") {
125 | return parseData(r);
126 | }
127 | }
128 |
129 | async send(prompt) {
130 | super.send(prompt);
131 | if (ChatSession.debug) {
132 | return;
133 | }
134 |
135 | const fetchResponse = () => {
136 | return this.api("assistant.lamda.BardFrontendService/StreamGenerate", {}, [
137 | null,
138 | JSON.stringify([[prompt], null, this.session.conversation ?? ["", "", ""]]),
139 | ]);
140 | };
141 |
142 | const parseRawResponse = (raw) => {
143 | const sectionsRegex = /(\d+)\s*\[\s*\[/g;
144 | const sections = [...raw.matchAll(sectionsRegex)];
145 | const blockObjects = sections.map((section, i) => {
146 | const start = section.index + section[1].length;
147 | const end = sections[i + 1]?.index ?? raw.length;
148 | try {
149 | return JSON.parse(raw.slice(start, end));
150 | } catch {
151 | if (e instanceof SyntaxError) return null;
152 | }
153 | });
154 | return blockObjects
155 | .filter((obj) => obj && obj[0] && typeof obj[0][2] === "string") // filter the relevant objects
156 | .map((obj) => JSON.parse(obj[0][2])) // parse them
157 | .find((obj) => obj[4] && obj[4].length); // find the first one that has some answers
158 | };
159 |
160 | const parseConversationId = (jsonResp) => jsonResp[1];
161 | const parseAnswersList = (jsonResp) => jsonResp[4];
162 | const parseSourcesAnswer = (answer) => {
163 | if (!answer[2]) return [];
164 | const sources = answer[2][0];
165 | if (!sources) return [];
166 | return sources.map((s, i) => {
167 | const href = escapeHtml(s[2][0]);
168 | return {
169 | start: s[0],
170 | end: s[1],
171 | href,
172 | html: `
${i+1} `,
173 | };
174 | }).filter(({href}) => href);
175 | };
176 | const parseTextAnswer = (answer) => answer[1][0];
177 | const parseImagesAnswer = (answer) => {
178 | let images = answer[12][1];
179 | if (!images) return [];
180 | return images.map(img => {
181 | const [substr, source, url, title] = [img[7][0], img[1][0][0], img[3][0][0], img[7][2]].map(escapeHtml);
182 | return {
183 | substr,
184 | html: `
185 |
186 |
187 | `.trim(),
188 | };
189 | });
190 | };
191 |
192 | const buildMessage = (answer) => {
193 | let text = parseTextAnswer(answer);
194 | let offset = 0;
195 | const sources = parseSourcesAnswer(answer);
196 | sources.forEach(({end}, i) => {
197 | const position = text.slice(0, end + offset).lastIndexOf(' ');
198 | if (position === -1) return;
199 | text = text.slice(0, position) + `\uF8FD${i}\uF8FE` + text.slice(position);
200 | offset += 3;
201 | });
202 | let bodyHTML = runMarkdown(text).replace(/\uF8FD(\d+)\uF8FE/g, (_, i) => sources[i]?.html);
203 | parseImagesAnswer(answer).forEach(({substr, html}) => {
204 | bodyHTML = bodyHTML.replace(substr, html);
205 | });
206 | return [bodyHTML, sources];
207 | };
208 |
209 | const rawResponse = await fetchResponse();
210 | const formattedResponse = parseRawResponse(rawResponse);
211 | if (!formattedResponse) {
212 | this.chooseGoogleAccount();
213 | return;
214 | }
215 | this.allowSend();
216 |
217 | try {
218 | const answersList = parseAnswersList(formattedResponse);
219 | const firstAnswer = answersList[0];
220 | this.session.conversation = parseConversationId(formattedResponse);
221 | this.session.conversation.push(firstAnswer[0]);
222 | this.onMessage(...buildMessage(firstAnswer));
223 | } catch (e) {
224 | this.onErrorMessage(_t("An error occured while parsing the response:
$error$", e));
225 | }
226 | }
227 |
228 | async chooseGoogleAccount(isError = true) {
229 | const accounts = await BardSession.fetchAvailableAccounts();
230 | const htmlMessage = `
231 | ${
232 | isError
233 | ? _t(
234 | 'This Google account does not have access to Gemini yet, please visit
this link to activate it or choose another Google account for Gemini',
235 | this.urlPrefix
236 | )
237 | : _t("Choose a Google account for Gemini")
238 | }
239 |
240 |
241 | ${accounts.map((a) => `
242 |
243 | ${a.email}
244 |
245 | `
246 | )
247 | .join("")}
248 |
249 | `;
250 | if (isError) {
251 | this.handleActionError({
252 | code: "BARD_ACCOUNT",
253 | text: htmlMessage,
254 | action: "refresh",
255 | });
256 | } else {
257 | this.setCurrentAction("refresh");
258 | this.onMessage(htmlMessage);
259 | }
260 | const input = $("[name=google-account]", this.panel);
261 | input.value = Context.get("googleAccount");
262 | input.addEventListener("change", () => Context.set("googleAccount", parseInt(input.value)));
263 | }
264 |
265 | removeConversation() {
266 | if (
267 | ChatSession.debug ||
268 | !this.session ||
269 | !this.session.conversation ||
270 | this.session.conversation.length === 0
271 | )
272 | return;
273 |
274 | return this.api("batchexecute", { rpcids: "GzXR5e", "source-path": "/" }, [
275 | [["GzXR5e", `["${this.session.conversation[0]}"]`, null, "generic"]],
276 | ]);
277 | }
278 |
279 | api(method, params, fReq) {
280 | params = {
281 | bl: this.session.bl,
282 | rt: "c",
283 | ...params,
284 | };
285 | return bgFetch(
286 | `${this.urlPrefix}/_/BardChatUi/data/${method}?${this.encodeURIParams(params)}`,
287 | {
288 | headers: {
289 | accept: "*/*",
290 | "cache-control": "no-cache",
291 | "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
292 | pragma: "no-cache",
293 | },
294 | body: this.encodeURIParams({
295 | "f.req": fReq,
296 | at: this.session.at,
297 | }),
298 | method: "POST",
299 | mode: "cors",
300 | credentials: "include",
301 | }
302 | );
303 | }
304 |
305 | encodeURIParams(params) {
306 | return Object.entries(params)
307 | .map(([k, v]) => `${k}=${encodeURIComponent(typeof v === "object" ? JSON.stringify(v) : v)}`)
308 | .join("&");
309 | }
310 |
311 | createPanel(directchat = true) {
312 | super.createPanel(directchat);
313 |
314 | const rightButtonsContainer = $(".right-buttons-container", this.panel);
315 | const accountButton = el(
316 | "div",
317 | { className: "bust", title: _t("Switch Google account") },
318 | rightButtonsContainer
319 | );
320 | setSvg(accountButton, SVG.user);
321 | accountButton.addEventListener("click", () => {
322 | this.clear();
323 | this.chooseGoogleAccount(false);
324 | });
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/src/chat/bingchat_session.js:
--------------------------------------------------------------------------------
1 | class BingChatSession extends ChatSession {
2 | properties = {
3 | name: "Copilot",
4 | link: "https://copilot.microsoft.com/",
5 | icon: "src/images/copilot.png",
6 | icon: "copilot.png",
7 | href: "https://copilot.microsoft.com/",
8 | }
9 | static get storageKey() {
10 | return "SAVE_BINGCHAT";
11 | }
12 |
13 | /** @type {HTMLImageElement | null} */
14 | bingIconElement = null;
15 |
16 | constructor() {
17 | super('bingchat');
18 | this.socketID = null;
19 | this.uuid = generateUUID(); // for conversation continuation
20 | }
21 |
22 | async init() {
23 | if (ChatSession.debug) return;
24 | await this.fetchSession();
25 | }
26 |
27 | async fetchSession() {
28 | const session = await BingChatSession.offscreenAction({ action: "session" });
29 | this.session = { conversationId: session.id };
30 | this.session.isStartOfSession = true;
31 | return this.session;
32 | }
33 |
34 | async send(prompt) {
35 | super.send(prompt);
36 | if (ChatSession.debug) {
37 | return;
38 | }
39 | this.bingIconElement?.classList.add('disabled');
40 |
41 | bgWorker({
42 | action: 'session-storage', type: 'set', key: this.uuid,
43 | value: { ...this.session, inputText: prompt }
44 | });
45 |
46 | if(!this.socketID) {
47 | this.socketID = await this.createSocket();
48 | const { packet } = await this.socketReceive();
49 | if (packet !== '{eventWebSocket:"open"}') {
50 | this.onErrorMessage();
51 | err(`Error with Bing Copilot: first packet received is ${packet}`);
52 | return;
53 | }
54 | }
55 |
56 | await this.socketSend(await this.config(prompt));
57 | this.rawMessage = "";
58 | return this.next();
59 | }
60 |
61 | async next() {
62 | const res = await this.socketReceive();
63 | if (!res) {
64 | return;
65 | }
66 | /**@type {{packet: string, readyState: number}} */
67 | const { packet, readyState } = res;
68 | this.session.isStartOfSession = false;
69 |
70 | /**
71 | * @param {*} body
72 | * @returns
73 | */
74 | const parseResponseBody = (body) => {
75 | switch (body.event) {
76 | case "received": return;
77 | case "startMessage": return;
78 | case "appendText":
79 | this.rawMessage += body.text;
80 | break;
81 | case "partCompleted": return;
82 | case "titleUpdate": return;
83 | case "done":
84 | this.allowSend();
85 | return 'close';
86 | default: return;
87 | }
88 | let text = this.rawMessage;
89 | if (!text) return;
90 |
91 | const bodyHTML = runMarkdown(text);
92 |
93 | this.onMessage(
94 | bodyHTML,
95 | );
96 | }
97 |
98 | const response = JSON.parse(packet);
99 | const doClose = parseResponseBody(response);
100 |
101 | if (doClose || readyState === WebSocket.CLOSED)
102 | return;
103 |
104 | return this.next();
105 | }
106 |
107 | removeConversation() {
108 | if (ChatSession.debug || !this.session)
109 | return;
110 | const { conversationId } = this.session;
111 | return BingChatSession.offscreenAction({
112 | action: "delete",
113 | conversationId,
114 | });
115 | }
116 |
117 | async createSocket() {
118 | const url = 'wss://copilot.microsoft.com/c/api/chat?api-version=2';
119 | const res = await BingChatSession.offscreenAction({
120 | action: "socket",
121 | url,
122 | toSend: JSON.stringify({ event: "setOptions", supportedCards: ["image"], ads: null }),
123 | });
124 | if (!('socketID' in res)) {
125 | throw "Socket ID not returned";
126 | }
127 | return res.socketID;
128 | }
129 |
130 | socketSend(body) {
131 | if (this.socketID == null)
132 | throw "Need socket ID to send";
133 | return BingChatSession.offscreenAction({
134 | action: "socket",
135 | socketID: this.socketID,
136 | toSend: JSON.stringify(body),
137 | });
138 | }
139 |
140 | socketReceive() {
141 | if (this.socketID == null)
142 | throw "Need socket ID to receive";
143 | return BingChatSession.offscreenAction({
144 | action: "socket",
145 | socketID: this.socketID,
146 | });
147 | }
148 |
149 | static async offscreenAction(params) {
150 | if (onChrome()) {
151 | await bgWorker({ action: "setup-bing-offscreen" });
152 | }
153 | return await bgWorker({
154 | ...params,
155 | target: 'offscreen',
156 | });
157 | }
158 |
159 | async config(prompt) {
160 | if (!this.session)
161 | throw "Session has to be fetched first";
162 |
163 | return {
164 | event: "send",
165 | conversationId: this.session.conversationId,
166 | content: [{ type: "text", text: prompt }],
167 | mode: "chat",
168 | };
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/chat/chatgpt_session.js:
--------------------------------------------------------------------------------
1 | class ChatGPTSession extends ChatSession {
2 | properties = {
3 | name: "ChatGPT",
4 | link: "https://chatgpt.com",
5 | icon: "src/images/chatgpt.png",
6 | icon: "chatgpt.png",
7 | href: "https://chatgpt.com",
8 | }
9 | static errors = {
10 | session: {
11 | code: 'CHAT_GPT_SESSION',
12 | url: 'https://chatgpt.com',
13 | text: _t("Please login to $AI$, then refresh", "ChatGPT"),
14 | button: _t("Login to $AI$", "ChatGPT"),
15 | },
16 | cloudflare: {
17 | code: 'CHAT_GPT_CLOUDFLARE',
18 | url: 'https://chatgpt.com',
19 | text: _t("Please pass the Cloudflare check on ChatGPT, then refresh"),
20 | button: _t("Cloudflare check"),
21 | },
22 | }
23 | static get storageKey() {
24 | return "SAVE_CHATGPT";
25 | }
26 |
27 | constructor() {
28 | super('chatgpt');
29 | this.eventStreamID = null;
30 | }
31 |
32 | async init() {
33 | if (ChatSession.debug) return;
34 | await this.fetchSession();
35 | await this.fetchModels();
36 | }
37 |
38 | async fetchSession() {
39 | const session = await bgFetch('https://chatgpt.com/api/auth/session', {
40 | credentials: "include",
41 | });
42 | if (session.error) {
43 | if (session.error === 'RefreshAccessTokenError')
44 | throw ChatGPTSession.errors.session;
45 | throw session.error;
46 | }
47 | if (session.status === 403)
48 | throw ChatGPTSession.errors.cloudflare;
49 | if (!session.accessToken)
50 | throw ChatGPTSession.errors.session;
51 | this.session = session;
52 | return this.session;
53 | }
54 |
55 | async registerWebSocket() {
56 | const url = (await this.backendApi("register-websocket", null, 'POST')).wss_url;
57 | this.socketID = await this.createSocket(url);
58 | }
59 |
60 | async fetchModels() {
61 | this.models = (await this.backendApi("models")).models;
62 | return this.models;
63 | }
64 |
65 | async send(prompt) {
66 | super.send(prompt);
67 | if (ChatSession.debug)
68 | return;
69 |
70 | const requirements = await this.backendApi('sentinel/chat-requirements', {
71 | p: "gAAAAAC" + await this.generateProofToken("" + Math.random(), "0"),
72 | });
73 | this.session.sentinelToken = requirements.token;
74 | if (requirements.proofofwork?.required){
75 | this.session.proofToken = "gAAAAAB" + await this.generateProofToken(requirements.proofofwork.seed, requirements.proofofwork.difficulty);
76 | }
77 | const res = await this.backendApi('conversation', this.config(prompt));
78 | if (res.eventStream) {
79 | this.eventStreamID = res.id;
80 | }
81 | await this.next();
82 | }
83 |
84 | async next() {
85 | const fetchPackets = async () => {
86 | const streamData = await this.readStream();
87 | if (!streamData) return [];
88 | let packetBody = null;
89 | if (this.eventStreamID !== null) {
90 | if (streamData.done) return ["DONE"];
91 | if (!streamData.data) return [];
92 | packetBody = streamData.data;
93 | } else {
94 | if (streamData.readyState === WebSocket.CLOSED) return ["DONE"];
95 | if (!streamData.packet) return [];
96 | packetBody = atob(JSON.parse(streamData.packet).body);
97 | }
98 | return packetBody
99 | .split('\n\n')
100 | .map((p, i) => {
101 | if (!p) return null;
102 | let packet = p;
103 | if (i === 0 && !p.startsWith('data: ')) {
104 | packet = this.halfPacket + packet;
105 | this.halfPacket = "";
106 | }
107 | packet = packet.substring(6);
108 | if (packet === "[DONE]") return "DONE";
109 | try {
110 | return JSON.parse(packet);
111 | }
112 | catch (e) {
113 | if (!e instanceof SyntaxError) throw e
114 | this.halfPacket = p;
115 | }
116 | return null;
117 | })
118 | .filter(p => !!p);
119 | };
120 |
121 | const handlePacket = (data) => {
122 | if (data === "DONE") {
123 | this.allowSend();
124 | return true;
125 | }
126 |
127 | this.session.conversation_id = data.conversation_id;
128 | if (data.error) {
129 | this.onErrorMessage(data.error);
130 | return true;
131 | }
132 | if (!data.message) {
133 | return false;
134 | }
135 |
136 | this.session.parent_message_id = data.message.id;
137 | if (!data.message.content?.parts) {
138 | return false;
139 | }
140 | const text = data.message.content.parts[0];
141 | if (text) {
142 | this.onMessage(runMarkdown(text));
143 | }
144 | return false;
145 | }
146 |
147 | const packets = await fetchPackets();
148 |
149 | for (const packet of packets) {
150 | if (handlePacket(packet)) return;
151 | }
152 | return this.next();
153 | }
154 |
155 | async createSocket(url) {
156 | const res = await bgWorker({
157 | action: "websocket",
158 | url,
159 | });
160 | if (!('socketID' in res)) {
161 | throw "Socket ID not returned";
162 | }
163 | return res.socketID;
164 | }
165 |
166 | readStream() {
167 | if (this.eventStreamID !== null) {
168 | return bgWorker({
169 | action: 'event-stream',
170 | id: this.eventStreamID,
171 | });
172 | }
173 |
174 | if (this.socketID !== null) {
175 | return bgWorker({
176 | action: "websocket",
177 | socketID: this.socketID,
178 | });
179 | }
180 |
181 | throw "Need socket or event stream ID to send";
182 | }
183 |
184 | removeConversation() {
185 | if (ChatGPTSession.debug || !this.session || !this.session.conversation_id)
186 | return;
187 | return this.backendApi(`conversation/${this.session.conversation_id}`, {
188 | is_visible: false
189 | }, 'PATCH');
190 | }
191 |
192 | config(prompt) {
193 | if (!this.session)
194 | throw "Session has to be fetched first";
195 | const id = generateUUID();
196 | const pid = this.session.parent_message_id ? this.session.parent_message_id : generateUUID();
197 | return {
198 | action: "next",
199 | conversation_mode: {
200 | kind: "primary_assistant"
201 | },
202 | force_nulligen: false,
203 | force_paragen: false,
204 | force_paragen_model_slug: "",
205 | force_rate_limit: false,
206 | history_and_training_disabled: false,
207 | ...(this.session.conversation_id && { conversation_id: this.session.conversation_id }),
208 | messages: [{
209 | id,
210 | author: { role: "user" },
211 | content: {
212 | content_type: "text",
213 | parts: [prompt],
214 | }
215 | }],
216 | parent_message_id: pid,
217 | model: this.models.at(-1).slug,
218 | suggestions: [],
219 | }
220 | }
221 |
222 | /**
223 | * @param {string} seed float number between 0 and 1 encoded in a string
224 | * @param {string} difficulty integer encoded in a string with leading zeros
225 | * @returns Proof token
226 | */
227 | async generateProofToken(seed, difficulty) {
228 | const config = [
229 | navigator.hardwareConcurrency + screen.width + screen.height,
230 | new Date().toString(),
231 | 4294705152,
232 | 0,
233 | navigator.userAgent,
234 | "",
235 | "",
236 | navigator.language,
237 | navigator.languages.join(","),
238 | 0,
239 | ];
240 | const encodeConfig = (data) => {
241 | const jsonData = JSON.stringify(data);
242 | return btoa(String.fromCharCode(...new TextEncoder().encode(jsonData)));
243 | }
244 | const start = performance.now();
245 | for (let i = 0; i < 3e5; i++) {
246 | config[3] = i, config[9] = Math.round(performance.now() - start);
247 | const base = encodeConfig(config);
248 | const hash = sha3_512(seed + base);
249 | if (hash.slice(0, difficulty.length) <= difficulty) return base;
250 | }
251 | return "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" + encodeConfig(`"${seed}"`);
252 | }
253 |
254 | backendApi(service, body, method) {
255 | if (!method) {
256 | method = body ? "POST" : "GET";
257 | }
258 | const headers = {
259 | authorization: `Bearer ${this.session.accessToken}`,
260 | ...(body && { "content-type": "application/json" }),
261 | }
262 | if (service === "conversation") {
263 | headers["accept"] = 'text/event-stream';
264 | if (this.session.sentinelToken) headers["openai-sentinel-chat-requirements-token"] = this.session.sentinelToken;
265 | if (this.session.proofToken) headers["openai-sentinel-proof-token"] = this.session.proofToken;
266 | }
267 | const params = {
268 | headers,
269 | credentials: "include",
270 | method,
271 | ...(body && { "body": JSON.stringify(body) }),
272 | }
273 | return bgFetch(`https://chatgpt.com/backend-api/${service}`, params);
274 | }
275 | }
--------------------------------------------------------------------------------
/src/chat/init.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | Context.initChat = () => {
3 | if (isOptiSearch && !Context.isActive('chatgpt'))
4 | return;
5 |
6 | Context.chatSession = (() => {
7 | if (typeof BingChatSession !== 'undefined')
8 | return new BingChatSession();
9 | if (typeof BardSession !== 'undefined')
10 | return new BardSession();
11 | if (typeof ChatGPTSession !== 'undefined')
12 | return new ChatGPTSession();
13 | return null;
14 | })();
15 |
16 | if (!Context.chatSession)
17 | return;
18 | Context.chatSession.createPanel(Context.isActive('directchat'));
19 | };
20 | })();
21 |
--------------------------------------------------------------------------------
/src/chat/message.js:
--------------------------------------------------------------------------------
1 | const Author = {
2 | User: 0,
3 | Bot: 1,
4 | }
5 |
6 | class Message {
7 | static USER = 0;
8 | static BOT = 1;
9 | constructor(author = Author.Bot, text = '') {
10 | this.author = author;
11 | this.text = text;
12 | }
13 | }
14 |
15 | class MessageContainer extends Message {
16 | constructor(author, html) {
17 | super(author, html);
18 | this.box = el('div', { className: `box-message-container ${author === Author.User ? 'user' : 'bot'}` });
19 | this.bubble = el('div', { className: `message-container` }, this.box);
20 | this.html = html;
21 | }
22 | /**
23 | * @param {string} html
24 | */
25 | set html(html) {
26 | this.text = html;
27 | this.bubble.innerHTML = html;
28 | if (this.bubble.children.length === 1 && this.bubble.firstChild.tagName === 'P')
29 | this.bubble.innerHTML = this.bubble.firstChild.innerHTML;
30 | prettifyCode(this.bubble);
31 | }
32 | get html() {
33 | return this.text;
34 | }
35 | get el() {
36 | return this.box;
37 | }
38 | }
39 |
40 | class Discussion {
41 | el = el('div', { className: 'discussion-container' })
42 | /** @type {MessageContainer[]} */
43 | messageContainers = []
44 | isScrolledToBottom = true
45 | get length() {
46 | return this.messageContainers.length;
47 | }
48 | appendMessage(messageContainer) {
49 | this.messageContainers.push(messageContainer);
50 | this.el.appendChild(this.messageContainers.at(-1).el);
51 | this.el.scrollTop = this.el.scrollHeight;
52 | }
53 | setLastMessageHTML(html) {
54 | this.isScrolledToBottom = Math.abs(this.el.scrollTop + this.el.offsetHeight - this.el.scrollHeight) <= 1;
55 | if (this.messageContainers.length === 0) {
56 | this.appendMessage(new MessageContainer(Author.Bot, html));
57 | } else {
58 | this.messageContainers.at(-1).html = html;
59 | }
60 | if (this.isScrolledToBottom) {
61 | this.el.scrollTop = this.el.scrollHeight;
62 | }
63 | }
64 | clear() {
65 | this.el.innerHTML = '';
66 | this.messageContainers = [];
67 | this.isScrolledToBottom = true;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/chat/offscreen/bing_socket.js:
--------------------------------------------------------------------------------
1 | window.parent.postMessage('socket-script-ready', '*');
2 |
3 | window.addEventListener('message', async (event) => {
4 | window.parent.postMessage({
5 | message: await handleMessage(event.data.message),
6 | messageId: event.data.messageId,
7 | }, '*');
8 | });
9 |
10 | async function handleMessage(message) {
11 | switch (message.action) {
12 | case 'session':
13 | const response = await fetch("https://copilot.microsoft.com/c/api/conversations", {
14 | method: "POST",
15 | body: null,
16 | credentials: "include",
17 | });
18 | return await response.json();
19 | case 'delete':
20 | return await fetch(`https://copilot.microsoft.com/c/api/conversations/${message.conversationId}`, {
21 | method: "DELETE",
22 | credentials: "include",
23 | }).then(r => r.text());
24 | default:
25 | return handleActionWebsocket(message);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/chat/offscreen/iframe_script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Script running in the iframe that has the correct origin.
3 | */
4 |
5 | (() => {
6 | window.parent.postMessage('iframe-script-ready', '*');
7 |
8 | window.addEventListener('message', onReceiveMessageFromParent);
9 |
10 | function onReceiveMessageFromParent(event) {
11 | if (event.origin !== new URL(chrome.runtime.getURL("")).origin) return;
12 |
13 | const data = event.data;
14 | if (!('scripts' in data)) return;
15 |
16 | data.scripts.forEach(insertScript);
17 | acknowledge(data.messageId, data.scriptElementId);
18 |
19 | window.removeEventListener('message', onReceiveMessageFromParent);
20 | }
21 |
22 | function acknowledge(messageId, scriptElementId) {
23 | window.parent.postMessage({
24 | message: `Script "${scriptElementId}" succesfully injected`,
25 | messageId: messageId,
26 | }, '*');
27 | }
28 |
29 | function insertScript(src) {
30 | const scriptElement = document.createElement('script');
31 | scriptElement.type = 'text/javascript';
32 | scriptElement.src = src;
33 | document.body.appendChild(scriptElement);
34 | }
35 |
36 | })();
--------------------------------------------------------------------------------
/src/chat/offscreen/offscreen.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/chat/offscreen/offscreen.js:
--------------------------------------------------------------------------------
1 | const strings = {
2 | scripts: ["src/background/websocket_utils.js", "src/chat/offscreen/bing_socket.js"],
3 | iframeSrc: "https://copilot.microsoft.com/favicon.ico?bing-chat-gpt-4-in-google",
4 | }
5 |
6 | const socketScriptReady = {
7 | _val: false,
8 | _listener: () => {},
9 | set val(val) {
10 | this._val = val;
11 | this._listener(val);
12 | },
13 | get val() {
14 | return this._val;
15 | },
16 | get promise() {
17 | return new Promise(resolve => this._listener = resolve);
18 | }
19 | };
20 |
21 | setupIframe(strings.scripts.map((src) => chrome.runtime.getURL(src)));
22 |
23 | function setupIframe(scripts) {
24 | const iframe = createIframe(strings.iframeSrc);
25 | window.addEventListener('message', ({data}) => {
26 | switch (data) {
27 | case 'iframe-script-ready':
28 | injectScriptToIframe(iframe, scripts);
29 | break;
30 | case 'socket-script-ready':
31 | socketScriptReady.val = true;
32 | break;
33 | }
34 | });
35 | chrome.runtime.onMessage.addListener(onReceiveMessageFromExtension);
36 | }
37 |
38 | function createIframe(src) {
39 | const iframe = document.createElement('iframe');
40 | iframe.src = src;
41 | document.firstElementChild.appendChild(iframe);
42 | return iframe;
43 | }
44 |
45 | function injectScriptToIframe(iframe, scripts) {
46 | const iframeWindow = iframe.contentWindow;
47 | iframeWindow.postMessage({ scripts }, "*");
48 | }
49 |
50 | function onReceiveMessageFromExtension(message, _, sendResponse) {
51 | if (message.target !== 'offscreen') return;
52 | switch (message.action) {
53 | case 'url':
54 | sendResponse(window.location.href);
55 | break;
56 | default:
57 | sendMessageToIframe(message).then(sendResponse);
58 | break;
59 | }
60 | return true;
61 | }
62 |
63 | async function sendMessageToIframe(message) {
64 | const iframe = document.querySelector('iframe');
65 | if (!iframe) {
66 | throw 'No iframe';
67 | }
68 |
69 | if (!socketScriptReady.val) {
70 | await socketScriptReady.promise;
71 | }
72 |
73 | const messageId = Math.random().toString(36).substring(7);
74 | return new Promise(resolve => {
75 | const messageHandler = (event) => {
76 | if (event.data && event.data.messageId === messageId) {
77 | resolve(event.data.message);
78 | window.removeEventListener('message', messageHandler);
79 | }
80 | };
81 |
82 | window.addEventListener('message', messageHandler);
83 | iframe.contentWindow.postMessage({ message, messageId }, '*');
84 | });
85 | }
86 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | const WhichExtension = (() => {
2 | const iconPath = chrome.runtime.getManifest().icons["128"];
3 | if (iconPath.includes('bingchat'))
4 | return 'bingchat';
5 | if (iconPath.includes('bard'))
6 | return 'bard';
7 | return 'optisearch';
8 | })();
9 | const isOptiSearch = WhichExtension === 'optisearch';
10 | const WhichChat = isOptiSearch ? 'chatgpt' : WhichExtension;
11 | const isDebugMode = !chrome.runtime.getManifest().update_url; // if it is set, it means that we loaded the extension from source (not from the store)
12 |
13 | const webstores = {
14 | 'optisearch': typeof browser === 'undefined' ?
15 | 'https://chrome.google.com/webstore/detail/optisearch/bbojmeobdaicehcopocnfhaagefleiae' :
16 | 'https://addons.mozilla.org/fr/firefox/addon/optisearch',
17 | 'bingchat': typeof browser === 'undefined' ?
18 | 'https://chrome.google.com/webstore/detail/bing-chat-gpt-4-in-google/pcnhobmoglanpljipbomknafhdlcgcng':
19 | 'https://addons.mozilla.org/fr/firefox/addon/bing-chat-gpt-4-in-google',
20 | 'bard': typeof browser === 'undefined' ?
21 | 'https://chrome.google.com/webstore/detail/bard-for-search-engines/pkdmfoabhnkpkcacnmgilaeghiggdbgf':
22 | 'https://addons.mozilla.org/fr/firefox/addon/bard-for-search-engines',
23 | }
24 | const webstore = webstores[WhichExtension];
25 | const donationLink = `https://www.paypal.com/donate?hosted_button_id=${WhichExtension === 'bingchat' ? 'BXBP3JELVS4FL' : 'VPF2BYBDBU5AA'}`;
26 |
27 | const Google = "Google", Ecosia = "Ecosia", Bing = "Bing", Yahoo = "Yahoo", DuckDuckGo = "DuckDuckGo", Baidu = "Baidu", Brave = "Brave Search";
28 | const OrderEngines = [Google, Bing, Baidu, DuckDuckGo, Ecosia, Brave, Yahoo];
29 | const EngineTechnicalNames = {
30 | [Google]: 'google',
31 | [Bing]: 'bing',
32 | [Baidu]: 'baidu',
33 | [DuckDuckGo]: 'duckduckgo',
34 | [Ecosia]: 'ecosia',
35 | [Brave]: 'brave',
36 | [Yahoo]: 'yahoo',
37 | };
38 |
--------------------------------------------------------------------------------
/src/engine-specifics.js:
--------------------------------------------------------------------------------
1 | (function (){
2 | Context.processEngine[Google] = () => {
3 | const udm = parseInt(new URL(window.location.href).searchParams.get("udm") || "0", 10);
4 | if ( udm == 0 || udm == 14 ) return;
5 |
6 | // We are not on the main Google page
7 | Context.rightColumn.parentNode.removeChild(Context.rightColumn);
8 | Context.rightColumn = null;
9 | };
10 |
11 | Context.processEngine[Brave] = () => {
12 | if (!$(".optisearch-start")) {
13 | Context.rightColumn.prepend(el("div", { className: "optisearch-start" }));
14 | }
15 |
16 | setObserver((mutations) => {
17 | const removedBoxes = getRemovedNodes(mutations)
18 | .filter((x) => x.nodeType === Node.ELEMENT_NODE && x.matches(Context.BOX_SELECTOR));
19 | if (removedBoxes.length) {
20 | Context.appendBoxes(removedBoxes);
21 | }
22 | },
23 | Context.rightColumn,
24 | { childList: true }
25 | );
26 | };
27 |
28 | /**
29 | * Special method to deal with Ecosia.
30 | * Because in Ecosia, the main column can be removed after few seconds and added again.
31 | * Also Ecosia is the only engine for which the HTML does not change if it is on mobile
32 | * (only @media CSS instructions make it change).
33 | * This also means that we have to deal with eventual resizing of the page
34 | */
35 | Context.processEngine[Ecosia] = () => {
36 | const searchNav = $(Context.engine.searchNav);
37 | setObserver((mutations) => {
38 | if (getRemovedNodes(mutations).some((n) => n === Context.centerColumn || n === Context.rightColumn)) {
39 | Context.centerColumn = $(Context.engine.centerColumn);
40 | Context.setupRightColumn();
41 | Context.appendBoxes(Context.boxes);
42 | }
43 |
44 | if (!$(Context.engine.searchNav)) {
45 | insertAfter(searchNav, $(Context.engine.searchNavNeighbor));
46 | }
47 | },
48 | document.body,
49 | { childList: true, subtree: true }
50 | );
51 |
52 | if (typeof Context.engine.onMobile !== "number") return;
53 |
54 | let wasOnMobile = Context.computeIsOnMobile();
55 | window.addEventListener("resize", () => {
56 | const isOnMobile = Context.computeIsOnMobile();
57 | if (isOnMobile === wasOnMobile) return;
58 | wasOnMobile = isOnMobile;
59 | const allBoxes = $$(Context.BOX_SELECTOR);
60 | allBoxes.forEach((p) => p.classList[isOnMobile ? "add" : "remove"](Context.MOBILE_CLASS));
61 | Context.appendBoxes(allBoxes);
62 | });
63 | };
64 |
65 | function getRemovedNodes(mutations) {
66 | const removedNodes = [];
67 | for(const mutation of mutations) {
68 | removedNodes.push(...mutation.removedNodes);
69 | }
70 | return removedNodes;
71 | }
72 | })();
73 |
--------------------------------------------------------------------------------
/src/engines.json:
--------------------------------------------------------------------------------
1 | {
2 | "Google": {
3 | "link": "https://www.google.com",
4 | "icon": "https://www.google.com/images/branding/googleg/1x/googleg_standard_color_128dp.png",
5 | "rightColumn": "#rhs",
6 | "centerColumn": "#center_col",
7 | "resultRow": "#rso span:has(> a > h3), #rso div:has(> a [role=link])",
8 | "searchBox": "input.gLFyf.gsfi, input.gLFyf, textarea.gLFyf",
9 | "active": true,
10 | "regex": "^www\\.google\\.",
11 | "style": "[data-optisearch-column]:has(.TzHB6b) .optisearchbox {margin-left: -21px;} .optisearchbox {min-width: 400px;}",
12 | "onMobile": "#cnt > #center_col"
13 | },
14 | "Bing": {
15 | "link": "https://www.bing.com",
16 | "icon": "https://www.bing.com/favicon.ico",
17 | "rightColumn": "#b_context",
18 | "centerColumn": "main",
19 | "resultRow": ".b_algo",
20 | "searchBox": ".b_searchbox#sb_form_q",
21 | "active": true,
22 | "regex": "\\.bing\\.com$",
23 | "style": ".optisearchbox {margin-right: -20px; margin-left: -20px;}",
24 | "onMobile": "meta[name='viewport']"
25 | },
26 | "Ecosia": {
27 | "link": "https://www.ecosia.org",
28 | "icon": "https://cdn-static.ecosia.org/static/icons/favicon.ico",
29 | "rightColumn": ".sidebar.web__sidebar[data-test-id=sidebar]",
30 | "rightColumnRemovable": true,
31 | "searchNav": "div.main-header__search-navigation",
32 | "searchNavNeighbor": "div.main-header__navigation",
33 | "centerColumn": ".mainline",
34 | "resultRow": ".mainline__result-wrapper",
35 | "searchBox": ".search-form__input, #search-form-input",
36 | "active": true,
37 | "regex": "\\.ecosia\\.org$",
38 | "style": ".sidebar.web__sidebar[data-test-id=sidebar]{grid-area: sidebar;}",
39 | "onMobile": 992
40 | },
41 | "Yahoo": {
42 | "link": "https://www.yahoo.com",
43 | "icon": "https://s.yimg.com/oa/build/images/favicons/yahoo.png",
44 | "rightColumn": "#right",
45 | "resultRow": ".dd.algo",
46 | "searchBox": "#yschsp",
47 | "active": false,
48 | "regex": "search\\.yahoo\\.com$"
49 | },
50 | "DuckDuckGo": {
51 | "link": "https://duckduckgo.com",
52 | "icon": "https://duckduckgo.com/favicon.ico",
53 | "rightColumn": ".js-react-sidebar",
54 | "centerColumn": "[data-area='mainline']",
55 | "resultRow": ".react-results--main > li",
56 | "searchBox": "#search_form_input",
57 | "resultsContainer": "[data-area='mainline']",
58 | "active": true,
59 | "regex": "duckduckgo\\.com$",
60 | "style": "[data-optisearch-column=wide] { max-width: var(--opti-max-width) !important; } .cw{max-width:unset;}",
61 | "onMobile": null
62 | },
63 | "Brave Search": {
64 | "link": "https://search.brave.com",
65 | "icon": "https://cdn.search.brave.com/serp/v1/static/brand/16c26cd189da3f0f7ba4e55a584ddde6a7853c9cc340ff9f381afc6cb18e9a1e-favicon-32x32.png",
66 | "rightColumn": ".sidebar",
67 | "centerColumn": "#results",
68 | "resultRow": "#results > .snippet",
69 | "searchBox": "#searchbox",
70 | "active": true,
71 | "regex": "search\\.brave\\.com$",
72 | "style": ".optisearchbox { box-shadow: none; border-radius: var(--border-radius-xlarge); } .column-layout {max-width: 100% !important;} [data-optisearch-column=wide] {max-width: var(--opti-max-width); min-width: var(--opti-max-width) !important;}",
73 | "onMobile": "meta[name='viewport']"
74 | },
75 | "Baidu": {
76 | "link": "https://www.baidu.com/",
77 | "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAPFBMVEVHcEz////////9/f/////p6/2srvK6vPXZ2vrIyvcACt8LG+A9ReUbJuFpb+ooMeFWXOeLj+2bn+98gexUG/hRAAAABHRSTlMAEaz5aKQWgAAAAU9JREFUeAGF092SgyAMBWCr/J54DOr7v+tCRqhdZ7fchJJvMk2QaXrNi/tjLfOr5t2/6zXNY+9DtJhCfoN5GvUTRNYaI4htgGUa21VVJTgnNeJdY4CEmuDmklh8gtgSuvb4BNkSpccncHL9B1j8BJHYXYYI648DRHFJsS0dZKgi3Odg9Vg6KFqXuPsK1my6ABuAt2nGbGBrZxIvgA6WAhGJHTAY6LwNk0arONiLNuDB1oY7LW8JOzpHm/7cQrslhZhY+9F9UNas3YQ1+JxkgA6gXJff4AQBqYBsgEyfoIgcLSYkRx5uJ/IdnNJ6TsmAGLAmRxdoYyDkDrgPYB+JfVJ3oPoGQRTOlasCsRvgG2TpBROixQpYBrDrki157yvNNfiTKuEGYhUUAUACIkLlerW5WMhEy5f9ODZtAmd/OP3ppZx9f01130c9f3+8357/D50CHYMuiWz1AAAAAElFTkSuQmCC",
78 | "rightColumn": "#content_right.cr-offset",
79 | "centerColumn": "#content_left",
80 | "resultRow": ".result",
81 | "searchBox": "#kw",
82 | "active": true,
83 | "regex": "www\\.baidu\\.com$",
84 | "style": "#container.sam_newgrid .right-ceiling {position: static !important;}"
85 | }
86 | }
--------------------------------------------------------------------------------
/src/images/bard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/bard.png
--------------------------------------------------------------------------------
/src/images/bard_conversation_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/bard_conversation_example.png
--------------------------------------------------------------------------------
/src/images/bingchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/bingchat.png
--------------------------------------------------------------------------------
/src/images/bingchat_conversation_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/bingchat_conversation_example.png
--------------------------------------------------------------------------------
/src/images/chatgpt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/chatgpt.png
--------------------------------------------------------------------------------
/src/images/chatgpt_conversation_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/chatgpt_conversation_example.png
--------------------------------------------------------------------------------
/src/images/copilot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/copilot.png
--------------------------------------------------------------------------------
/src/images/engines/Baidu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Baidu.png
--------------------------------------------------------------------------------
/src/images/engines/Bing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Bing.png
--------------------------------------------------------------------------------
/src/images/engines/Brave Search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Brave Search.png
--------------------------------------------------------------------------------
/src/images/engines/DuckDuckGo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/DuckDuckGo.png
--------------------------------------------------------------------------------
/src/images/engines/Ecosia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Ecosia.png
--------------------------------------------------------------------------------
/src/images/engines/Google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Google.png
--------------------------------------------------------------------------------
/src/images/engines/Yahoo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/engines/Yahoo.png
--------------------------------------------------------------------------------
/src/images/genius.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/genius.png
--------------------------------------------------------------------------------
/src/images/gpxfollower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/gpxfollower.png
--------------------------------------------------------------------------------
/src/images/mathworks.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/mathworks.ico
--------------------------------------------------------------------------------
/src/images/mdn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/mdn.png
--------------------------------------------------------------------------------
/src/images/optisearch_conversation_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/optisearch_conversation_example.png
--------------------------------------------------------------------------------
/src/images/stackexchange.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/stackexchange.ico
--------------------------------------------------------------------------------
/src/images/unity.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/unity.ico
--------------------------------------------------------------------------------
/src/images/w3schools.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/w3schools.ico
--------------------------------------------------------------------------------
/src/images/wikipedia.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dj0ulo/OptiSearch/47816328b304a7e699c8af1b271b804542ba1db5/src/images/wikipedia.ico
--------------------------------------------------------------------------------
/src/libs/drawdown.js:
--------------------------------------------------------------------------------
1 | /**
2 | * https://github.com/adamvleggett/drawdown
3 | * (c) Adam Leggett
4 | */
5 |
6 |
7 | ; function markdown(src) {
8 |
9 | var rx_lt = //g;
11 | var rx_space = /\t|\r|\uf8ff/g;
12 | var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g;
13 | var rx_hr = /^([*\-=_] *){3,}$/gm;
14 | var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g;
15 | var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g;
16 | var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g;
17 | var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+))(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g;
18 | var rx_code = /```\w*\n?(((?!```).)*)(```|$)/gs;
19 | var rx_one_line_code = /`(((?!`).)*)(`|$)/g;
20 | var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g;
21 | var rx_table = /\n(( *\|.*?\| *\n)+)/g;
22 | var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/;
23 | var rx_row = /.*\n/g;
24 | var rx_cell = /\||(.*?[^\\])\|/g;
25 | var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g;
26 | var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g;
27 | var rx_stash = /-\d+\uf8ff/g;
28 |
29 | function replace(rex, fn) {
30 | src = src.replace(rex, fn);
31 | }
32 |
33 | function element(tag, content) {
34 | return '<' + tag + '>' + content + '' + tag + '>';
35 | }
36 |
37 | function blockquote(src) {
38 | return src.replace(rx_blockquote, (all, content) => {
39 | return element('blockquote', blockquote(highlight(content.replace(/^ *> */gm, ''))));
40 | });
41 | }
42 |
43 | function list(src) {
44 | return src.replace(rx_list, (all, ind, ol, num, low, content) => {
45 | var entry = element('li', highlight(content.split(
46 | RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g')).map(list).join('
')));
47 |
48 | return '\n' + (ol
49 | ? ''
51 | : parseInt(ol, 36) - 9 + '" style="list-style-type:' + (low ? 'low' : 'upp') + 'er-alpha">') + entry + ' '
52 | : element('ul', entry));
53 | });
54 | }
55 |
56 | function highlight(src) {
57 | return src.replace(rx_highlight, (all, _, p1, emp, sub, sup, small, big, p2, content) => {
58 | return _ + element(
59 | emp ? (p2 ? 'strong' : 'em')
60 | : sub ? (p2 ? 's' : 'sub')
61 | : sup ? 'sup'
62 | : small ? 'small'
63 | : big ? 'big'
64 | : 'code',
65 | highlight(content));
66 | });
67 | }
68 |
69 | function unesc(str) {
70 | return str.replace(rx_escape, '$1');
71 | }
72 |
73 | var stash = [];
74 | var si = 0;
75 |
76 | src = '\n' + src + '\n';
77 |
78 | replace(rx_lt, '<');
79 | replace(rx_gt, '>');
80 | replace(rx_space, ' ');
81 |
82 | // multiline code
83 | replace(rx_code, (all, p1) => {
84 | stash[--si] = element('pre', element('code', p1.trim()));
85 | return si + '\uf8ff';
86 | });
87 |
88 | // blockquote
89 | src = blockquote(src);
90 |
91 | // horizontal rule
92 | replace(rx_hr, ' ');
93 |
94 | // list
95 | src = list(src);
96 | replace(rx_listjoin, '');
97 |
98 | // link or image
99 | replace(rx_link, (all, p1, p2, p3, p4, p5, p6) => {
100 | stash[--si] = p4
101 | ? p2
102 | ? ' '
103 | : '' + unesc(highlight(p3)) + ' '
104 | : p6;
105 | return si + '\uf8ff';
106 | });
107 |
108 | // table
109 | replace(rx_table, (all, table) => {
110 | var sep = table.match(rx_thead)[1];
111 | return '\n' + element('table',
112 | table.replace(rx_row, (row, ri) => {
113 | return row == sep ? '' : element('tr', row.replace(rx_cell, (all, cell, ci) => {
114 | return ci ? element(sep && !ri ? 'th' : 'td', unesc(highlight(cell || ''))) : ''
115 | }))
116 | })
117 | )
118 | });
119 |
120 | // heading
121 | replace(rx_heading, (all, _, p1, p2) => _ + element('h' + p1.length, unesc(highlight(p2))));
122 |
123 | // paragraph
124 | replace(rx_para, (all, content) => element('p', unesc(highlight(content))));
125 |
126 | // one line code
127 | replace(rx_one_line_code, (all, p1) => element('code', p1));
128 |
129 | // stash
130 | replace(rx_stash, (all) => stash[parseInt(all)]);
131 |
132 | return src.trim();
133 | };
--------------------------------------------------------------------------------
/src/libs/sha3.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Minified by jsDelivr using Terser v5.19.2.
3 | * Original file: /npm/js-sha3@0.9.3/src/sha3.js
4 | *
5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
6 | */
7 | /**
8 | * [js-sha3]{@link https://github.com/emn178/js-sha3}
9 | *
10 | * @version 0.9.3
11 | * @author Chen, Yi-Cyuan [emn178@gmail.com]
12 | * @copyright Chen, Yi-Cyuan 2015-2023
13 | * @license MIT
14 | */
15 | !function(){"use strict";var t="input is invalid type",e="object"==typeof window,r=e?window:{};r.JS_SHA3_NO_WINDOW&&(e=!1);var n=!e&&"object"==typeof self;!r.JS_SHA3_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node?r=global:n&&(r=self);for(var i=!r.JS_SHA3_NO_COMMON_JS&&"object"==typeof module&&module.exports,o="function"==typeof define&&define.amd,a=!r.JS_SHA3_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,s="0123456789abcdef".split(""),u=[4,1024,262144,67108864],f=[0,8,16,24],c=[1,0,32898,0,32906,2147483648,2147516416,2147483648,32907,0,2147483649,0,2147516545,2147483648,32777,2147483648,138,0,136,0,2147516425,0,2147483658,0,2147516555,0,139,2147483648,32905,2147483648,32771,2147483648,32770,2147483648,128,2147483648,32778,0,2147483658,2147483648,2147516545,2147483648,32896,2147483648,2147483649,0,2147516424,2147483648],h=[224,256,384,512],p=[128,256],d=["hex","buffer","arrayBuffer","array","digest"],l={128:168,256:136},y=r.JS_SHA3_NO_NODE_JS||!Array.isArray?function(t){return"[object Array]"===Object.prototype.toString.call(t)}:Array.isArray,b=!a||!r.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW&&ArrayBuffer.isView?ArrayBuffer.isView:function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer},v=function(e){var r=typeof e;if("string"===r)return[e,!0];if("object"!==r||null===e)throw new Error(t);if(a&&e.constructor===ArrayBuffer)return[new Uint8Array(e),!1];if(!y(e)&&!b(e))throw new Error(t);return[e,!1]},A=function(t){return 0===v(t)[0].length},g=function(t){for(var e=[],r=0;r>5,this.byteCount=this.blockCount<<2,this.outputBlocks=r>>5,this.extraBytes=(31&r)>>3;for(var n=0;n<50;++n)this.s[n]=0}function I(t,e,r){H.call(this,t,e,r)}H.prototype.update=function(t){if(this.finalized)throw new Error("finalize already called");var e=v(t);t=e[0];for(var r,n,i=e[1],o=this.blocks,a=this.byteCount,s=t.length,u=this.blockCount,c=0,h=this.s;c>2]|=n<>2]|=(192|n>>6)<>2]|=(128|63&n)<=57344?(o[r>>2]|=(224|n>>12)<>2]|=(128|n>>6&63)<>2]|=(128|63&n)<>2]|=(240|n>>18)<>2]|=(128|n>>12&63)<>2]|=(128|n>>6&63)<>2]|=(128|63&n)<>2]|=t[c]<=a){for(this.start=r-a,this.block=o[u],r=0;r>=8);r>0;)i.unshift(r),r=255&(t>>=8),++n;return e?i.push(n):i.unshift(n),this.update(i),i.length},H.prototype.encodeString=function(t){var e=v(t);t=e[0];var r=e[1],n=0,i=t.length;if(r)for(var o=0;o=57344?n+=3:(a=65536+((1023&a)<<10|1023&t.charCodeAt(++o)),n+=4)}else n=i;return n+=this.encode(8*n),this.update(t),n},H.prototype.bytepad=function(t,e){for(var r=this.encode(e),n=0;n>2]|=this.padding[3&e],this.lastByteIndex===this.byteCount)for(t[0]=t[r],e=1;e>4&15]+s[15&t]+s[t>>12&15]+s[t>>8&15]+s[t>>20&15]+s[t>>16&15]+s[t>>28&15]+s[t>>24&15];a%e==0&&(r=g(r),R(r),o=0)}return i&&(t=r[o],u+=s[t>>4&15]+s[15&t],i>1&&(u+=s[t>>12&15]+s[t>>8&15]),i>2&&(u+=s[t>>20&15]+s[t>>16&15])),u},H.prototype.arrayBuffer=function(){this.finalize();var t,e=this.blockCount,r=this.s,n=this.outputBlocks,i=this.extraBytes,o=0,a=0,s=this.outputBits>>3;t=i?new ArrayBuffer(n+1<<2):new ArrayBuffer(s);for(var u=new Uint32Array(t);a>8&255,u[t+2]=e>>16&255,u[t+3]=e>>24&255;s%r==0&&(n=g(n),R(n))}return o&&(t=s<<2,e=n[a],u[t]=255&e,o>1&&(u[t+1]=e>>8&255),o>2&&(u[t+2]=e>>16&255)),u},I.prototype=new H,I.prototype.finalize=function(){return this.encode(this.outputBits,!0),H.prototype.finalize.call(this)};var R=function(t){var e,r,n,i,o,a,s,u,f,h,p,d,l,y,b,v,A,g,B,_,k,w,S,C,x,m,O,z,N,J,M,j,E,H,I,R,F,U,D,V,W,Y,K,q,G,L,P,Q,T,X,Z,$,tt,et,rt,nt,it,ot,at,st,ut,ft,ct;for(n=0;n<48;n+=2)i=t[0]^t[10]^t[20]^t[30]^t[40],o=t[1]^t[11]^t[21]^t[31]^t[41],a=t[2]^t[12]^t[22]^t[32]^t[42],s=t[3]^t[13]^t[23]^t[33]^t[43],u=t[4]^t[14]^t[24]^t[34]^t[44],f=t[5]^t[15]^t[25]^t[35]^t[45],h=t[6]^t[16]^t[26]^t[36]^t[46],p=t[7]^t[17]^t[27]^t[37]^t[47],e=(d=t[8]^t[18]^t[28]^t[38]^t[48])^(a<<1|s>>>31),r=(l=t[9]^t[19]^t[29]^t[39]^t[49])^(s<<1|a>>>31),t[0]^=e,t[1]^=r,t[10]^=e,t[11]^=r,t[20]^=e,t[21]^=r,t[30]^=e,t[31]^=r,t[40]^=e,t[41]^=r,e=i^(u<<1|f>>>31),r=o^(f<<1|u>>>31),t[2]^=e,t[3]^=r,t[12]^=e,t[13]^=r,t[22]^=e,t[23]^=r,t[32]^=e,t[33]^=r,t[42]^=e,t[43]^=r,e=a^(h<<1|p>>>31),r=s^(p<<1|h>>>31),t[4]^=e,t[5]^=r,t[14]^=e,t[15]^=r,t[24]^=e,t[25]^=r,t[34]^=e,t[35]^=r,t[44]^=e,t[45]^=r,e=u^(d<<1|l>>>31),r=f^(l<<1|d>>>31),t[6]^=e,t[7]^=r,t[16]^=e,t[17]^=r,t[26]^=e,t[27]^=r,t[36]^=e,t[37]^=r,t[46]^=e,t[47]^=r,e=h^(i<<1|o>>>31),r=p^(o<<1|i>>>31),t[8]^=e,t[9]^=r,t[18]^=e,t[19]^=r,t[28]^=e,t[29]^=r,t[38]^=e,t[39]^=r,t[48]^=e,t[49]^=r,y=t[0],b=t[1],L=t[11]<<4|t[10]>>>28,P=t[10]<<4|t[11]>>>28,z=t[20]<<3|t[21]>>>29,N=t[21]<<3|t[20]>>>29,st=t[31]<<9|t[30]>>>23,ut=t[30]<<9|t[31]>>>23,Y=t[40]<<18|t[41]>>>14,K=t[41]<<18|t[40]>>>14,H=t[2]<<1|t[3]>>>31,I=t[3]<<1|t[2]>>>31,v=t[13]<<12|t[12]>>>20,A=t[12]<<12|t[13]>>>20,Q=t[22]<<10|t[23]>>>22,T=t[23]<<10|t[22]>>>22,J=t[33]<<13|t[32]>>>19,M=t[32]<<13|t[33]>>>19,ft=t[42]<<2|t[43]>>>30,ct=t[43]<<2|t[42]>>>30,et=t[5]<<30|t[4]>>>2,rt=t[4]<<30|t[5]>>>2,R=t[14]<<6|t[15]>>>26,F=t[15]<<6|t[14]>>>26,g=t[25]<<11|t[24]>>>21,B=t[24]<<11|t[25]>>>21,X=t[34]<<15|t[35]>>>17,Z=t[35]<<15|t[34]>>>17,j=t[45]<<29|t[44]>>>3,E=t[44]<<29|t[45]>>>3,C=t[6]<<28|t[7]>>>4,x=t[7]<<28|t[6]>>>4,nt=t[17]<<23|t[16]>>>9,it=t[16]<<23|t[17]>>>9,U=t[26]<<25|t[27]>>>7,D=t[27]<<25|t[26]>>>7,_=t[36]<<21|t[37]>>>11,k=t[37]<<21|t[36]>>>11,$=t[47]<<24|t[46]>>>8,tt=t[46]<<24|t[47]>>>8,q=t[8]<<27|t[9]>>>5,G=t[9]<<27|t[8]>>>5,m=t[18]<<20|t[19]>>>12,O=t[19]<<20|t[18]>>>12,ot=t[29]<<7|t[28]>>>25,at=t[28]<<7|t[29]>>>25,V=t[38]<<8|t[39]>>>24,W=t[39]<<8|t[38]>>>24,w=t[48]<<14|t[49]>>>18,S=t[49]<<14|t[48]>>>18,t[0]=y^~v&g,t[1]=b^~A&B,t[10]=C^~m&z,t[11]=x^~O&N,t[20]=H^~R&U,t[21]=I^~F&D,t[30]=q^~L&Q,t[31]=G^~P&T,t[40]=et^~nt&ot,t[41]=rt^~it&at,t[2]=v^~g&_,t[3]=A^~B&k,t[12]=m^~z&J,t[13]=O^~N&M,t[22]=R^~U&V,t[23]=F^~D&W,t[32]=L^~Q&X,t[33]=P^~T&Z,t[42]=nt^~ot&st,t[43]=it^~at&ut,t[4]=g^~_&w,t[5]=B^~k&S,t[14]=z^~J&j,t[15]=N^~M&E,t[24]=U^~V&Y,t[25]=D^~W&K,t[34]=Q^~X&$,t[35]=T^~Z&tt,t[44]=ot^~st&ft,t[45]=at^~ut&ct,t[6]=_^~w&y,t[7]=k^~S&b,t[16]=J^~j&C,t[17]=M^~E&x,t[26]=V^~Y&H,t[27]=W^~K&I,t[36]=X^~$&q,t[37]=Z^~tt&G,t[46]=st^~ft&et,t[47]=ut^~ct&rt,t[8]=w^~y&v,t[9]=S^~b&A,t[18]=j^~C&m,t[19]=E^~x&O,t[28]=Y^~H&R,t[29]=K^~I&F,t[38]=$^~q&L,t[39]=tt^~G&P,t[48]=ft^~et&nt,t[49]=ct^~rt&it,t[0]^=c[n],t[1]^=c[n+1]};if(i)module.exports=m;else{for(z=0;z ul {
107 | padding-inline-start: 0px;
108 | }
109 |
110 | #version {
111 | font-size: 11px;
112 | }
113 |
114 | .menu {
115 | margin: 5px;
116 | }
117 |
118 | .menu_title {
119 | display: block;
120 | white-space: nowrap;
121 | font-weight: bold;
122 | user-select: none;
123 | font-size: x-small;
124 | overflow: hidden;
125 | display: flex;
126 | width: 100%;
127 | margin-bottom: 1em;
128 | margin-top: 1em;
129 | }
130 |
131 | .menu_title > span {
132 | padding: 0 2em;
133 | flex: 1 1 100px;
134 | text-align: center;
135 | }
136 |
137 | .menu_title > span > span {
138 | vertical-align: top;
139 | }
140 |
141 | .ad {
142 | display: block;
143 | text-align: center;
144 | margin: 1em 0;
145 | }
146 |
147 | hr {
148 | border-color: rgb(38, 44, 46);
149 | height: 0.5px;
150 | }
151 |
152 | hr.flexchild {
153 | display: inline-block;
154 | flex: 1 1 200px;
155 | }
156 |
157 | label.setting {
158 | /* user-select: none; */
159 | overflow: hidden;
160 | }
161 |
162 | label.setting > :last-child {
163 | vertical-align: top;
164 | }
165 |
166 | .setting-title {
167 | display: inline-block;
168 | text-align: right;
169 | width: 140px;
170 | overflow: hidden;
171 | text-overflow: ellipsis;
172 | margin-right: 10px;
173 | position: relative;
174 | }
175 |
176 | .setting-title .input {
177 | margin-left: 20px;
178 | }
179 |
180 | .checkbox {
181 | height: 15px;
182 | width: 15px;
183 | cursor: pointer;
184 | }
185 |
186 | a {
187 | text-decoration: none;
188 | color: #4590c5;
189 | }
190 |
191 | a:hover {
192 | text-decoration: underline;
193 | }
194 |
195 | select,
196 | input {
197 | background-color: var(--dark-grey);
198 | color: var(--color);
199 | border-radius: 2px;
200 | padding: 2px 5px;
201 | cursor: pointer;
202 | }
203 |
204 | select:focus,
205 | input:focus {
206 | outline: none;
207 | }
208 |
209 | /*** FOOTER ***/
210 |
211 | footer {
212 | margin: auto;
213 | width: 100%;
214 | background-color: var(--bg);
215 | box-shadow: 0px 1px 20px 0px black;
216 | }
217 |
218 | .footlinks-container {
219 | display: flex;
220 | justify-content: space-evenly;
221 | }
222 |
223 | .footlinks-container a {
224 | margin: 0 8px;
225 | }
226 |
227 | .grid {
228 | display: grid;
229 | justify-content: center;
230 | }
231 |
232 | /*** UPGRADE BUTTON ***/
233 |
234 | #premium {
235 | width: 100%;
236 | height: 1.5rem;
237 | margin: 10px 0;
238 | padding: 5px 5px;
239 | }
240 |
241 | .premium-link {
242 | display: block;
243 | text-align: center;
244 | font-size: 0.9em;
245 | margin-bottom: 10px;
246 | color: var(--lighter-grey);
247 | }
248 |
249 | .upgrade-button {
250 | border-radius: 3px;
251 |
252 | cursor: pointer;
253 |
254 | color: #fff;
255 | background-color: #EB9B05;;
256 |
257 | text-align: center;
258 |
259 | transition: 0.3s;
260 | opacity: 0.9;
261 | }
262 |
263 | .upgrade-button:hover {
264 | opacity: 1;
265 | }
266 |
267 | .upgrade-button > span {
268 | display: inline-block;
269 | vertical-align: middle;
270 | line-height: 1.2em;
271 | font-size: 1.3em;
272 | font-weight: bold;
273 | }
274 |
275 | .anim-right {
276 | animation: slideInFromRight 0.5s ease forwards;
277 | grid-column: 3 /4;
278 | opacity: 0;
279 | }
280 | .anim-left {
281 | animation: slideInFromLeft 0.5s ease forwards;
282 | grid-column: 1 /2;
283 | opacity: 0;
284 | }
285 |
286 | @keyframes slideInFromRight {
287 | from {
288 | transform: translateX(+5em);
289 | }
290 | to {
291 | opacity: 1;
292 | transform: translateX(0);
293 | }
294 | }
295 | @keyframes slideInFromLeft {
296 | from {
297 | transform: translateX(-5em);
298 | }
299 | to {
300 | opacity: 1;
301 | transform: translateX(0);
302 | }
303 | }
304 |
305 | /* GPX Follower ad */
306 |
307 | #gpxfollower {
308 | display: flex;
309 | flex-direction: column;
310 | align-items: flex-start; /* Align items to the start (left) */
311 | background-color: var(--darker-bg);
312 | padding: 0.8em 1em;
313 | margin-top: 1em;
314 | text-decoration: none;
315 | }
316 |
317 | #gpxfollower > #also-from {
318 | font-size: 0.9em;
319 | color: var(--lighter-grey);
320 | margin-bottom: .5em; /* Spacing below "Also from OptiSearch" */
321 | }
322 |
323 | .gpxfollower-app {
324 | display: flex;
325 | align-items: center;
326 | }
327 |
328 | .gpxfollower-app img {
329 | width: 72px; /* Adjust as needed */
330 | height: auto;
331 | border-radius: 20%;
332 | }
333 |
334 | #gpxfollower:hover .text-app span:first-child {
335 | text-decoration: underline;
336 | }
337 |
338 | .text-app span:first-child {
339 | font-weight: bold;
340 | font-size: 1.2em;
341 | margin-bottom: 0.4em;
342 | }
343 | .text-app {
344 | display: flex;
345 | flex-direction: column;
346 | padding: .5em;
347 | }
348 |
349 | .text-app span:last-child {
350 | color: var(--lighter-grey);
351 | }
352 |
353 | .icon-container {
354 | position: relative;
355 | display: inline-block; /* Ensure the container wraps around the icon and text */
356 | }
357 |
358 | .new-text {
359 | position: absolute;
360 | top: 0;
361 | right: 0;
362 | font-weight: bold;
363 | color: var(--color);
364 | padding: 3px 5px; /* Adjust as needed */
365 | background-color: red; /* Adjust as needed */
366 | animation: bounce 0.5s infinite alternate; /* Adjust duration and timing as needed */
367 | }
368 |
369 | @keyframes bounce {
370 | 0% {
371 | transform: translate(20%, 20%) rotate(40deg) scale(0.8); /* Initial scale */
372 | }
373 | 100% {
374 | transform: translate(20%, 20%) rotate(40deg) scale(.9); /* Scale at the peak of the bounce */
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/src/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
27 |
28 |
29 |
55 |
56 |
57 |
58 |