Skip to content

Commit

Permalink
Update u301-url-shortener extension (#13096)
Browse files Browse the repository at this point in the history
* Update u301-url-shortener extension

- Merge branch \'contributions/merge-1717948270228818000\'
- Pull contributions
- feat(u301-url-shortener): support API key

* docs: add CHAELOG

* feat(u301-url-shortener): support custom domain

* update

* fix: lint

* feat(u301-url-shortener): shorten using clipboard

* fix: set accept response type

* feat: icon for differrent status

* refactor: remove unused `@raycast/utils`

* Pull contributions

* Pull contributions

* fix: keep the same name

---------

Co-authored-by: Per Nielsen Tikær <per@raycast.com>
  • Loading branch information
shiny and pernielsentikaer committed Jun 26, 2024
1 parent 7ca5f92 commit c5d96ac
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 61 deletions.
7 changes: 7 additions & 0 deletions extensions/u301-url-shortener/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# U301 Url Shortener Changelog

## [Shorten URLs in bulk] - 2024-06-24
- Shorten URLs using clipboard
- Shorten URLs in bulk

## [Custom Domain Support] - 2024-06-21
- Support custom domain

## [Add API Key] - 2024-06-10
- Support API Key

Expand Down
8 changes: 7 additions & 1 deletion extensions/u301-url-shortener/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# U301 URL Shortener

Shorten the selected URL to u301.com.
Shorten URLs to u301.com.

## FAQ

Expand All @@ -9,6 +9,12 @@ Shorten the selected URL to u301.com.
Make sure you have turned on Raycast’s accessibility.
> System Settings > Privacy & Security > Accessibility > Raycast
### How do I use my own domain?
1. Go to https://u301.com to add your domain.
2. Change domain DNS records, add a CNAME record.
3. When the domain gets verified, you can add the domain name in Raycast extension configuration.
4. Configure the API key, makes it possible to shorten URLs with your domain.

### Is an API Key required?
No. If you don't configure it, you will shorten URLs as an anonymous user.

Expand Down
36 changes: 17 additions & 19 deletions extensions/u301-url-shortener/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,20 @@
"commands": [
{
"name": "index",
"title": "Shorten URL",
"title": "Shorten URL Using Clipboard",
"subtitle": "U301.com",
"description": "Shorten URLs in Clipboard",
"mode": "view"
},
{
"name": "shorten-selected-text",
"title": "Shorten URL Using Selected Text",
"subtitle": "U301.com",
"description": "Shorten selected URL to u301.com",
"mode": "no-view"
}
],
"preferences": [
{
"name": "clipboard",
"title": "Shortened URL",
"description": "Copy to Clipboard or Paste to Current Window",
"type": "dropdown",
"required": true,
"default": "clipboard",
"data": [
{
"title": "Copy to Clipboard",
"value": "clipboard"
},
{
"title": "Paste to Current Window",
"value": "paste"
}
]
},
{
"name": "accessToken",
"title": "API Key",
Expand All @@ -45,6 +34,15 @@
"required": false,
"default": "",
"placeholder": "Leave empty to use as an anonymous user"
},
{
"name": "domainName",
"title": "Custom Domain Name",
"description": "Domain for shortened URL, API Key required if you want to use your own custom domain",
"type": "textfield",
"required": false,
"default": "u301.co",
"placeholder": "Leave empty to use default domain"
}
],
"dependencies": {
Expand Down
131 changes: 90 additions & 41 deletions extensions/u301-url-shortener/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,101 @@
import { getSelectedText, Clipboard, showToast, Toast, getPreferenceValues } from "@raycast/api";
import fetch, { Headers } from "node-fetch";
import { Action, ActionPanel, Icon, List } from "@raycast/api";
import { useEffect, useState } from "react";
import { Clipboard, showToast, Toast, open } from "@raycast/api";
import { isValidURL, shortenURL, uniqueArray } from "./util";

function isValidURL(string: string) {
const res = string.match(
/(http(s)?:\/\/.)(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g,
);
return res !== null;
}

async function reportError({ message }: { message: string }) {
await showToast(Toast.Style.Failure, "Error", message.toString());
interface Result {
status: "init" | "shortened" | "error";
url: string;
shortened: string;
errorMessage?: string;
}
export default function Command() {
const [items, setItems] = useState<Result[]>();
const [isLoading, setLoading] = useState(false);

export default async function Command() {
try {
const preferences = getPreferenceValues();
const selectedText = await getSelectedText();
if (!isValidURL(selectedText)) {
return reportError({ message: "Selected text is not a valid URL" });
}

showToast({
const startShortening = async () => {
const toast = showToast({
style: Toast.Style.Animated,
title: "Shortening",
});
const headers = new Headers({
"Content-Type": "application/json",
});
if (preferences.accessToken) {
headers.append("Authorization", `Bearer ${preferences.accessToken}`);
}
const res = await fetch(`https://api.u301.com/v2/shorten?url=${encodeURIComponent(selectedText)}`, {
headers,
const content = await Clipboard.readText();
const lines: Result[] = uniqueArray(content?.split("\n").filter((line) => isValidURL(line))).map((line) => {
return {
url: line.trim(),
shortened: "",
status: "init",
};
});
const { shortened, message = "Failed to shorten" } = (await res.json()) as { shortened: string; message?: string };
if (!shortened) {
return reportError({ message });
if (lines) {
setLoading(true);
for (const i in lines) {
lines[i].status = "shortened";
try {
const { shortened, message } = await shortenURL(lines[i].url);
if (shortened) {
lines[i].status = "shortened";
lines[i].shortened = shortened;
} else {
lines[i].status = "error";
lines[i].errorMessage = message;
}
} catch (error) {
lines[i].status = "error";
lines[i].errorMessage = (error as Error).message;
}
setItems(lines);
}
setLoading(false);
const resultURLs = lines
.map((line) => {
if (line.status === "shortened") {
return line.shortened;
} else {
return line.url;
}
})
.join("\n");

await Clipboard.copy(resultURLs);
await showToast(Toast.Style.Success, "Success", "Copied shortened URLs to clipboard");
}
if (preferences.clipboard === "clipboard") {
await Clipboard.copy(shortened);
} else {
await Clipboard.paste(shortened);
(await toast).hide();
};

useEffect(() => {
startShortening();
}, []);

const getIcon = (item: Result) => {
if (item.status === "shortened") {
return Icon.CheckCircle;
} else if (item.status === "error") {
return Icon.Info;
}
} catch (error) {
return reportError({
message: "Not able to get selected text",
});
}
return Icon.Link;
};

await showToast(Toast.Style.Success, "Success", "Copied shortened URL to clipboard");
return (
<List isLoading={isLoading}>
<List.Section title="URLs">
{items?.map((item, index) => (
<List.Item
actions={
<ActionPanel>
<Action
title="Open URL"
onAction={() => open(item.status === "shortened" ? item.shortened : item.url)}
/>
</ActionPanel>
}
icon={getIcon(item)}
key={index}
subtitle={item.status === "shortened" ? item.url : item.errorMessage}
title={item.status === "shortened" ? item.shortened : item.url}
/>
))}
</List.Section>
{!isLoading && <List.EmptyView icon={Icon.Clipboard} title="Your clipboard does not contain a URL." />}
</List>
);
}
30 changes: 30 additions & 0 deletions extensions/u301-url-shortener/src/shorten-selected-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSelectedText, Clipboard, showToast, Toast } from "@raycast/api";
import { isValidURL, shortenURL } from "./util";

async function reportError({ message }: { message: string }) {
await showToast(Toast.Style.Failure, "Error", message.toString());
}

export default async function Command() {
try {
const selectedText = await getSelectedText();
if (!isValidURL(selectedText)) {
return reportError({ message: "Selected text is not a valid URL" });
}

showToast({
style: Toast.Style.Animated,
title: "Shortening",
});
const { shortened, message = "Failed to shorten" } = await shortenURL(selectedText);
if (!shortened) {
return reportError({ message });
}
await Clipboard.copy(shortened);
await showToast(Toast.Style.Success, "Success", "Copied shortened URL to clipboard");
} catch (error) {
return reportError({
message: "Not able to get selected text",
});
}
}
34 changes: 34 additions & 0 deletions extensions/u301-url-shortener/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getPreferenceValues } from "@raycast/api";
import fetch, { Headers } from "node-fetch";

export function isValidURL(string: string) {
const regex = new RegExp(
/(http(s)?:\/\/.)(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g,
);
return string.match(regex);
}

export function uniqueArray(items?: string[]): string[] {
if (!items) {
return [];
}
return Array.from(new Set(items));
}

export async function shortenURL(url: string) {
const preferences = getPreferenceValues();
const headers = new Headers({
Accept: "application/json",
});
if (preferences.accessToken) {
headers.append("Authorization", `Bearer ${preferences.accessToken}`);
}
let URL = `https://api.u301.com/v2/shorten?url=${encodeURIComponent(url)}`;
if (preferences.domainName) {
URL += `&domain=${preferences.domainName}`;
}
const res = await fetch(URL, {
headers,
});
return (await res.json()) as { shortened: string; message?: string };
}

0 comments on commit c5d96ac

Please sign in to comment.