Skip to content

Commit

Permalink
💌 Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
mxbaylee committed Sep 26, 2024
0 parents commit d214aa5
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"root": true,
"env": {
"es2020": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"]
}
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 22.5
- name: Install dependencies
run: |
npm install
- name: Run the test file
run: |
npm test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.DS_Store
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"printWidth": 120,
"singleQuote": false
}
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 💌 Email Generator

![screenshot](./assets/screenshot.png)

This package is used to generate unique email addresses for wildcard domains based on the
active tab and some random hashes.

* Install the [Browser Extension](https://www.raycast.com/browser-extension)
* Install this plugin
Binary file added assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "email-generator-raycast",
"title": "Email Generator",
"description": "Generate dynamic email addresses for registration.",
"icon": "icon.png",
"author": "mxbaylee",
"license": "MIT",
"categories": ["Security"],
"preferences": [
{
"title": "Email Domain",
"name": "emailDomain",
"type": "textfield",
"required": true,
"description": "Enter the domain of your provider.",
"default": "privaterelay.example.com",
"placeholder": "privaterelay.example.com"
}
],
"commands": [
{
"icon": "icon.png",
"name": "index",
"title": "Generate Email",
"description": "Generate dynamic email addresses for registration.",
"mode": "view"
}
],
"dependencies": {
"@raycast/api": "^1.28.0"
},
"devDependencies": {
"@types/node": "~22.5.0",
"@types/react": "^17.0.28",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"typescript": "^4.4.3",
"vitest": "^2.1.1"
},
"scripts": {
"test": "vitest",
"build": "ray build -e dist",
"dev": "ray develop"
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
29 changes: 29 additions & 0 deletions raycast-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference types="@raycast/api">

/* 🚧 🚧 🚧
* This file is auto-generated from the extension's manifest.
* Do not modify manually. Instead, update the `package.json` file.
* 🚧 🚧 🚧 */

/* eslint-disable @typescript-eslint/ban-types */

type ExtensionPreferences = {
/** Email Domain - Enter the domain of your provider. */
"emailDomain": string
}

/** Preferences accessible in all the extension's commands */
declare type Preferences = ExtensionPreferences

declare namespace Preferences {
/** Preferences accessible in the `index` command */
export type Index = ExtensionPreferences & {}
}

declare namespace Arguments {
/** Arguments passed to the `index` command */
export type Index = {}
}



100 changes: 100 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env node

// Required parameters:
// @raycast.schemaVersion 1
// @raycast.title 💌 Email
// @raycast.mode compact

// Optional parameters:
// @raycast.icon assets/icon.png
// @raycast.packageName Email Generator

// Documentation:
// @raycast.description Generate dynamic email addresses for registration.
// @raycast.author jnnarudbnojmtq
// @raycast.authorURL https://raycast.com/jnnarudbnojmtq

import {
Action,
ActionPanel,
BrowserExtension,
Color,
getPreferenceValues,
Icon,
List,
showToast,
Toast,
} from "@raycast/api";
import { useEffect, useState } from "react";
import { generateRandomHex, getName } from "./util";

interface Preferences {
emailDomain: string;
}

export default function Command() {
const { emailDomain } = getPreferenceValues<Preferences>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [activeDomain, setActiveDomain] = useState<string>();
const [error, setError] = useState<string|false>(false);

useEffect(() => {
if (error) {
showToast({
style: Toast.Style.Failure,
title: error,
});
setError(false);
} else if (!activeDomain) {
showToast({
style: Toast.Style.Failure,
title: 'Failed to parse active tab.',
});
}
}, [error, activeDomain]);

useEffect(() => {
if (activeDomain) return;
BrowserExtension.getTabs().then((tabs) => {
setIsLoading(false);
const activeTab = tabs.find((tab) => tab.active);
if (activeTab) {
setActiveDomain(getName(activeTab.url));
} else {
setError('No active tab found');
}
});
}, []);

return (
<List
isLoading={isLoading}
filtering={false}
>
{ [6, 11, 16].map((length: number) => {
const emailAddress = `${activeDomain}.${generateRandomHex(length)}@${emailDomain}`;
return (
<List.Item
icon={Icon.Envelope}
key={length}
title={emailAddress}
actions={
<ActionPanel>
<Action.CopyToClipboard title="Copy Email Address" content={emailAddress} />
</ActionPanel>
}
accessories={[
{
icon: Icon.CopyClipboard,
tag: {
value: `${length}`,
color: Color.Magenta
}
},
]}
/>
);
})}
</List>
);
}
73 changes: 73 additions & 0 deletions src/util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, it, expect } from 'vitest';
import { generateRandomHex, getName } from './util';

describe('generateRandomHex', () => {
it('should generate a random hex string of the specified length', () => {
const length = 10;
const randomHex = generateRandomHex(length);
expect(randomHex).toHaveLength(length);
expect(randomHex).toMatch(/^[0-9a-fA-F]+$/);
});

it('should generate a long hex string', () => {
const length = 10000;
const randomHex = generateRandomHex(length);
expect(randomHex).toHaveLength(length);
expect(randomHex).toMatch(/^[0-9a-fA-F]+$/);
});

it('should generate empty text string', () => {
const length = 0;
const randomHex = generateRandomHex(length);
expect(randomHex).toHaveLength(length);
});

it('should not throw an error for negative length', () => {
const randomHex = generateRandomHex(-10);
expect(randomHex).toHaveLength(0);
});
});

describe('getName', () => {
const testCases = [
[
'https://www.example.com/3508253/checkouts/ee6751f27ea600d0c133445710f11db0?_ga=2.208438866.1566561875.1678469276-1096666253.1678469276',
'example'
],
[
'http://abc.hostname.com/somethings/anything/',
'abc.hostname'
],
[
'ftp://www.hostname.com/somethings/anything/',
'hostname'
],
[
'mailto:[email protected]?subject=princess.sleeps',
'wiggles'
],
[
'calendar.google.com',
'calendar.google'
],
[
'https://calendar.google.com/calendar/u/0/r/week',
'calendar.google'
],
[
'https://www.geeksforgeeks.org/python-os-environ-object/',
'geeksforgeeks'
],
[
'google.biz',
'google'
],
];

testCases.forEach(([rawInput, expected]) => {
it(`should return "${expected}" for input "${rawInput}"`, () => {
const actual = getName(rawInput);
expect(actual).toBe(expected);
});
});
});
20 changes: 20 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { URL } from 'url';
import crypto from 'crypto';
// const { URL } = require('url');

export function getName(rawInput: string): string {
const hasProtocol = rawInput.includes('://');
if (!hasProtocol) {
rawInput = 'http://' + rawInput;
}

const inputArg = new URL(rawInput);
const domain = inputArg.hostname.replace(/^www\./, '');
const segments = domain.split('.');
return segments.length === 1 ? segments[0] : segments.slice(0, -1).join('.');
}

export function generateRandomHex(rawLength: number): string {
const length = Math.max(rawLength, 0);
return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
}
17 changes: 17 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 22",
"include": ["src/**/*"],
"exclude": ["node_modules"],
"compilerOptions": {
"lib": ["es2020"],
"module": "commonjs",
"target": "es2020",
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react-jsx"
}
}

0 comments on commit d214aa5

Please sign in to comment.