Created
November 26, 2024 09:23
-
-
Save Strajk/9bf369a060e2e3e3eec4f1353147c71b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Name: Messages 2FA codes | |
| // Description: Search for 2FA codes in your Messages, within the last 30 minutes | |
| // Ackowledgements: | |
| // - https://github.com/squatto/alfred-imessage-2fa/ | |
| // - https://github.com/raycast/extensions/tree/main/extensions/imessage-2fa | |
| import "@johnlindquist/kit" | |
| import Database from 'better-sqlite3'; | |
| let preferences = { | |
| lookBackMinutes: 30, | |
| ignoreRead: false, | |
| } | |
| export type TMessage = { | |
| guid: string; | |
| message_date: string; // 2024-11-26 06:11:18 | |
| sender: string; // e.g. amazon.de or +49123456789 | |
| service: string; // e.g. SMS | |
| text: string; | |
| } | |
| const db = new Database(home("Library/Messages/chat.db")); | |
| let output = await arg({ | |
| placeholder: "Select a message or start typing to search", | |
| choices: async (input) => { | |
| let stmt = db.prepare(dbQuery(input)); | |
| let messages = stmt.all() as TMessage[]; | |
| return messages.map((m) => ({ | |
| name: m.text, | |
| tag: extractCode(m.text) ?? "no code", | |
| description: `${m.message_date} • ${m.sender} • ${m.service}`, | |
| value: m.text, | |
| preview: `<div class="p-2 text-sm">${m.text}</div>`, | |
| })); | |
| }, | |
| actions: [ | |
| { | |
| name: "Copy Whole Message", | |
| flag: "copyWholeMessage", | |
| visible: true, | |
| shortcut: `${cmd}+c`, | |
| }, | |
| ] | |
| }) | |
| if (flag.copyWholeMessage) { | |
| clipboard.writeText(output) | |
| notify("Whole message copied to clipboard") | |
| } else { | |
| let code = extractCode(output) | |
| if (code) { | |
| clipboard.writeText(code) | |
| notify(`Code: ${code} copied to clipboard`) | |
| } else { | |
| clipboard.writeText(output) | |
| notify("No code found. Copied whole message to clipboard instead") | |
| } | |
| } | |
| // Helpers | |
| // === | |
| function dbQuery(qs: string = "") { | |
| let baseQuery = /* sql */` | |
| select | |
| message.guid, | |
| message.rowid, | |
| ifnull(handle.uncanonicalized_id, chat.chat_identifier) AS sender, | |
| message.service, | |
| datetime(message.date / 1000000000 + 978307200, 'unixepoch', 'localtime') AS message_date, | |
| message.text | |
| from message | |
| left join chat_message_join on chat_message_join.message_id = message.ROWID | |
| left join chat on chat.ROWID = chat_message_join.chat_id | |
| left join handle on message.handle_id = handle.ROWID | |
| where message.is_from_me = 0 | |
| and message.text is not null | |
| and length(message.text) > 0 | |
| and | |
| datetime(message.date / 1000000000 + strftime('%s', '2001-01-01'), 'unixepoch', 'localtime') | |
| >= | |
| datetime('now', '-${preferences.lookBackMinutes} minutes', 'localtime') | |
| `; | |
| if (preferences.ignoreRead) baseQuery += " and message.is_read = 0"; | |
| if (!qs) { // search for code | |
| baseQuery = /* sql */`${baseQuery} and ( | |
| -- Matches 3 alphanumeric (e.g., 'ABC') | |
| message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| -- Matches 4 alphanumeric (e.g., 'ABCD') | |
| or message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| -- Matches 5 alphanumeric (e.g., 'ABCDE') | |
| or message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| -- Matches 6 alphanumeric (e.g., 'ABCDEF') | |
| or message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| -- Matches format '123-456' | |
| or message.text glob '*[0-9][0-9][0-9]-[0-9][0-9][0-9]*' | |
| -- Matches 7 alphanumeric (e.g., 'ABCDEFG') | |
| or message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| -- Matches 8 alphanumeric (e.g., 'ABCDEFGH') | |
| or message.text glob '*[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]*' | |
| )`; | |
| } else { // Search for text | |
| baseQuery = /* sql */`${baseQuery} and message.text like '%${qs}%'`; | |
| } | |
| return `${baseQuery} \norder by message.date desc limit 100`.trim(); | |
| } | |
| export function extractCode(original: string) { | |
| // remove URLs | |
| const urlRegex = new RegExp( | |
| "\\b((https?|ftp|file):\\/\\/|www\\.)[-A-Z0-9+&@#\\/%?=~_|$!:,.;]*[A-Z0-9+&@#\\/%=~_|$]", | |
| "ig" | |
| ); | |
| let message = original.replaceAll(urlRegex, ""); | |
| if (message.trim() === "") return ""; | |
| let m; | |
| let code; | |
| // Look for specific patterns first | |
| if ((m = /^(\d{4,8})(\sis your.*code)/.exec(message)) !== null) { | |
| // 4-8 digits followed by "is your [...] code" | |
| // examples: | |
| // "2773 is your Microsoft account verification code" | |
| code = m[1]; | |
| } else if ( | |
| (m = /(code\s*:|is\s*:|码|use code|autoriza(?:ca|çã)o\s*:|c(?:o|ó)digo\s*:)\s*(\w{4,8})($|\s|\\R|\t|\b|\.|,)/i.exec( | |
| message | |
| )) !== null | |
| ) { | |
| // "code:" OR "is:" OR "use code", optional whitespace, then 4-8 consecutive alphanumeric characters | |
| // examples: | |
| // "Your Airbnb verification code is: 1234." | |
| // "Your verification code is: 1234, use it to log in" | |
| // "Here is your authorization code:9384" | |
| // "【抖音】验证码9316,用于手机验证" | |
| // "Your healow verification code is : 7579." | |
| // "TRUSTED LOCATION PASSCODE: mifsuc" | |
| // "Código de Autorização: 12345678" | |
| code = m[2]; | |
| } else { | |
| // more generic, brute force patterns | |
| // remove phone numbers | |
| // we couldn't do this before, because some auth codes resemble text shortcodes, which would be filtered by this regex | |
| const phoneRegex = new RegExp( | |
| // https://stackoverflow.com/a/123666 | |
| /(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?/, | |
| "ig" | |
| ); | |
| const originalMessage = message; | |
| message = message.replaceAll(phoneRegex, ""); | |
| if ((m = /(^|\s|\\R|\t|\b|G-|:)(\d{5,8})($|\s|\\R|\t|\b|\.|,)/.exec(message)) !== null) { | |
| // 5-8 consecutive digits | |
| // examples: | |
| // "您的验证码是 199035,10分钟内有效,请勿泄露" | |
| // "登录验证码:627823,您正在尝试【登录】,10分钟内有效" | |
| // "【赛验】验证码 54538" | |
| // "Enter this code to log in:59678." | |
| // "G-315643 is your Google verification code" | |
| // "Enter the code 765432, and then click the button to log in." | |
| // "Your code is 45678!" | |
| // "Your code is:98765!" | |
| code = m[2]; | |
| } else if ((m = /\b(?=[A-Z]*[0-9])(?=[0-9]*[A-Z])[0-9A-Z]{3,8}\b/.exec(message)) !== null) { | |
| // 3-8 character uppercase alphanumeric string, containing at least one letter and one number | |
| // examples: | |
| // "5WGU8G" | |
| // "Your code is: 5WGU8G" | |
| // "CWGUG8" | |
| // "CWGUG8 is your code" | |
| // "7645W453" | |
| code = m[0]; | |
| } else if ((m = /(^|code:|is:|\b)\s*(\d{3})-(\d{3})($|\s|\\R|\t|\b|\.|,)/i.exec(message)) !== null) { | |
| // line beginning OR "code:" OR "is:" OR word boundary, optional whitespace, 3 consecutive digits, a hyphen, then 3 consecutive digits | |
| // but NOT a phone number (###-###-####) | |
| // examples: | |
| // "123-456" | |
| // "Your Stripe verification code is: 719-839." | |
| // and make sure it isn't a phone number | |
| // doesn't match: <first digits>-<second digits>-<4 consecutive digits> | |
| const first = m[2]; | |
| const second = m[3]; | |
| code = `${first}${second}`; | |
| } else if ((m = /(code|is):?\s*(\d{3,8})($|\s|\\R|\t|\b|\.|,)/i.exec(originalMessage)) !== null) { | |
| // "code" OR "is" followed by an optional ":" + optional whitespace, then 3-8 consecutive digits | |
| // examples: | |
| // "Please enter code 548 on Zocdoc." | |
| code = m[2]; | |
| } else { | |
| // console.log("no code found in message"); | |
| } | |
| } | |
| return code; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment