mirror of
https://github.com/Sevichecc/raycast-akkoma-extension.git
synced 2025-04-30 14:49:29 +08:00
feat: add fqn id and open in akkoma action
This commit is contained in:
parent
73b104a968
commit
c93750f4a4
4 changed files with 70 additions and 24 deletions
31
src/api.ts
31
src/api.ts
|
@ -1,6 +1,6 @@
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { OAuth, getPreferenceValues } from "@raycast/api";
|
import { OAuth, getPreferenceValues } from "@raycast/api";
|
||||||
import { Credentials, Preference, Status, StatusResponse } from "./types";
|
import { Credentials, Preference, Status, StatusResponse, Account } from "./types";
|
||||||
import { client } from "./oauth";
|
import { client } from "./oauth";
|
||||||
|
|
||||||
export const fetchToken = async (params: URLSearchParams, errorMessage: string): Promise<OAuth.TokenResponse> => {
|
export const fetchToken = async (params: URLSearchParams, errorMessage: string): Promise<OAuth.TokenResponse> => {
|
||||||
|
@ -11,13 +11,11 @@ export const fetchToken = async (params: URLSearchParams, errorMessage: string):
|
||||||
body: params,
|
body: params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(errorMessage);
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await response.json()) as OAuth.TokenResponse;
|
return (await response.json()) as OAuth.TokenResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const createApp = async (): Promise<Credentials> => {
|
export const createApp = async (): Promise<Credentials> => {
|
||||||
const { instance } = getPreferenceValues<Preference>();
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
|
|
||||||
|
@ -39,6 +37,7 @@ export const createApp = async (): Promise<Credentials> => {
|
||||||
return (await response.json()) as Credentials;
|
return (await response.json()) as Credentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const postNewStatus = async ({
|
export const postNewStatus = async ({
|
||||||
status,
|
status,
|
||||||
visibility,
|
visibility,
|
||||||
|
@ -66,10 +65,24 @@ export const postNewStatus = async ({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error("Failed to pulish :(");
|
||||||
throw new Error("Failed to pulish :(");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (await response.json()) as StatusResponse;
|
return (await response.json()) as StatusResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const fetchAccountInfo = async (): Promise<Account> => {
|
||||||
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
|
const tokenSet = await client.getTokens();
|
||||||
|
|
||||||
|
const response = await fetch(`https://${instance}/api/v1/accounts/verify_credentials`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer " + tokenSet?.accessToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch account's info :(");
|
||||||
|
|
||||||
|
return (await response.json()) as Account;
|
||||||
|
};
|
||||||
|
|
13
src/oauth.ts
13
src/oauth.ts
|
@ -1,6 +1,6 @@
|
||||||
import { OAuth, getPreferenceValues } from "@raycast/api";
|
import { LocalStorage, OAuth, getPreferenceValues } from "@raycast/api";
|
||||||
import { Preference } from "./types";
|
import { Preference } from "./types";
|
||||||
import { fetchToken,createApp} from "./api";
|
import { fetchToken, createApp, fetchAccountInfo } from "./api";
|
||||||
|
|
||||||
export const client = new OAuth.PKCEClient({
|
export const client = new OAuth.PKCEClient({
|
||||||
redirectMethod: OAuth.RedirectMethod.Web,
|
redirectMethod: OAuth.RedirectMethod.Web,
|
||||||
|
@ -16,7 +16,6 @@ const requestAccessToken = async (
|
||||||
authRequest: OAuth.AuthorizationRequest,
|
authRequest: OAuth.AuthorizationRequest,
|
||||||
authCode: string
|
authCode: string
|
||||||
): Promise<OAuth.TokenResponse> => {
|
): Promise<OAuth.TokenResponse> => {
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("client_id", clientId);
|
params.append("client_id", clientId);
|
||||||
params.append("client_secret", clientSecret);
|
params.append("client_secret", clientSecret);
|
||||||
|
@ -33,7 +32,6 @@ const refreshToken = async (
|
||||||
clientSecret: string,
|
clientSecret: string,
|
||||||
refreshToken: string
|
refreshToken: string
|
||||||
): Promise<OAuth.TokenResponse> => {
|
): Promise<OAuth.TokenResponse> => {
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("client_id", clientId);
|
params.append("client_id", clientId);
|
||||||
params.append("client_secret", clientSecret);
|
params.append("client_secret", clientSecret);
|
||||||
|
@ -52,6 +50,7 @@ export const authorize = async (): Promise<void> => {
|
||||||
|
|
||||||
if (tokenSet?.accessToken) {
|
if (tokenSet?.accessToken) {
|
||||||
if (tokenSet.refreshToken && tokenSet.isExpired()) {
|
if (tokenSet.refreshToken && tokenSet.isExpired()) {
|
||||||
|
LocalStorage.clear()
|
||||||
const { client_id, client_secret } = await createApp();
|
const { client_id, client_secret } = await createApp();
|
||||||
await client.setTokens(await refreshToken(client_id, client_secret, tokenSet.refreshToken));
|
await client.setTokens(await refreshToken(client_id, client_secret, tokenSet.refreshToken));
|
||||||
}
|
}
|
||||||
|
@ -64,7 +63,11 @@ export const authorize = async (): Promise<void> => {
|
||||||
clientId: client_id,
|
clientId: client_id,
|
||||||
scope: "read write",
|
scope: "read write",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { authorizationCode } = await client.authorize(authRequest);
|
const { authorizationCode } = await client.authorize(authRequest);
|
||||||
await client.setTokens(await requestAccessToken(client_id, client_secret, authRequest, authorizationCode));
|
await client.setTokens(await requestAccessToken(client_id, client_secret, authRequest, authorizationCode));
|
||||||
|
|
||||||
|
const { fqn, avatar_static } = await fetchAccountInfo();
|
||||||
|
await LocalStorage.setItem("account-fqn", fqn);
|
||||||
|
await LocalStorage.setItem("account-avator", avatar_static);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Form, ActionPanel, Action, showToast, popToRoot, LaunchProps, Toast, Cache ,Icon} from "@raycast/api";
|
import {
|
||||||
|
Form,
|
||||||
|
ActionPanel,
|
||||||
|
Action,
|
||||||
|
showToast,
|
||||||
|
popToRoot,
|
||||||
|
LaunchProps,
|
||||||
|
Toast,
|
||||||
|
Cache,
|
||||||
|
Icon,
|
||||||
|
LocalStorage,
|
||||||
|
getPreferenceValues,
|
||||||
|
} from "@raycast/api";
|
||||||
import { postNewStatus } from "./api";
|
import { postNewStatus } from "./api";
|
||||||
import { Status, AkkomaError, StatusResponse } from "./types";
|
import { Status, AkkomaError, StatusResponse, Preference } from "./types";
|
||||||
import { authorize } from "./oauth";
|
import { authorize } from "./oauth";
|
||||||
|
|
||||||
import VisibilityDropdown from "./components/VisibilityDropdown";
|
import VisibilityDropdown from "./components/VisibilityDropdown";
|
||||||
|
@ -10,20 +22,27 @@ import StatusContent from "./components/statusContent";
|
||||||
const cache = new Cache();
|
const cache = new Cache();
|
||||||
|
|
||||||
export default function Command(props: LaunchProps<{ draftValues: Partial<Status> }>) {
|
export default function Command(props: LaunchProps<{ draftValues: Partial<Status> }>) {
|
||||||
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
const { draftValues } = props;
|
const { draftValues } = props;
|
||||||
const [cw, setCw] = useState<string>(draftValues?.spoiler_text || "");
|
const [cw, setCw] = useState<string>(draftValues?.spoiler_text || "");
|
||||||
const [isMarkdown, setIsMarkdown] = useState<boolean>(true);
|
const [isMarkdown, setIsMarkdown] = useState(true);
|
||||||
|
const [openActionText, setOpenActionText] = useState("Open the last published status");
|
||||||
|
const [fqn, setFqn] = useState("");
|
||||||
|
|
||||||
const cached = cache.get("latest_published_status");
|
const cached = cache.get("latest_published_status");
|
||||||
|
|
||||||
const [statusInfo, setStatusInfo] = useState<StatusResponse>(cached ? JSON.parse(cached) : null);
|
const [statusInfo, setStatusInfo] = useState<StatusResponse>(cached ? JSON.parse(cached) : null);
|
||||||
const [openActionText, setOpenActionText] = useState<string>("Open the last published status");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authorize();
|
const init = async () => {
|
||||||
|
authorize();
|
||||||
|
const newFqn = await LocalStorage.getItem<string>("account-fqn");
|
||||||
|
if (newFqn) setFqn(newFqn);
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = async (values: Partial<Status>) => {
|
const handleSubmit = async (values: Pick<Status, "content_type" | "status" | "spoiler_text" | "visibility">) => {
|
||||||
try {
|
try {
|
||||||
if (!values.status) throw new Error("You might forget the content, right ? |・ω・)");
|
if (!values.status) throw new Error("You might forget the content, right ? |・ω・)");
|
||||||
showToast(Toast.Style.Animated, "Publishing to the Fediverse ... ᕕ( ᐛ )ᕗ");
|
showToast(Toast.Style.Animated, "Publishing to the Fediverse ... ᕕ( ᐛ )ᕗ");
|
||||||
|
@ -51,11 +70,13 @@ export default function Command(props: LaunchProps<{ draftValues: Partial<Status
|
||||||
enableDrafts
|
enableDrafts
|
||||||
actions={
|
actions={
|
||||||
<ActionPanel>
|
<ActionPanel>
|
||||||
<Action.SubmitForm onSubmit={handleSubmit} title="Publish" icon={Icon.Upload} />
|
<Action.SubmitForm onSubmit={handleSubmit} title={"Publish"} icon={Icon.Upload} />
|
||||||
{ statusInfo && <Action.OpenInBrowser url={statusInfo.url} title={openActionText} />}
|
{statusInfo && <Action.OpenInBrowser url={statusInfo.url} title={openActionText} />}
|
||||||
|
<Action.OpenInBrowser url={`https://${instance}/main/friends/`} title="Open Akkoma in Browser" />
|
||||||
</ActionPanel>
|
</ActionPanel>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{fqn && <Form.Description title="Account" text={fqn} />}
|
||||||
<Form.TextField id="spoiler_text" title="CW" placeholder={"content warning"} value={cw} onChange={setCw} />
|
<Form.TextField id="spoiler_text" title="CW" placeholder={"content warning"} value={cw} onChange={setCw} />
|
||||||
<StatusContent isMarkdown={isMarkdown} draftStatus={draftValues?.status} />
|
<StatusContent isMarkdown={isMarkdown} draftStatus={draftValues?.status} />
|
||||||
<VisibilityDropdown />
|
<VisibilityDropdown />
|
||||||
|
|
|
@ -63,6 +63,8 @@ export interface Status {
|
||||||
visibility: VisibilityScope;
|
visibility: VisibilityScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface StatusResponse {
|
export interface StatusResponse {
|
||||||
id: string;
|
id: string;
|
||||||
create_at: Date;
|
create_at: Date;
|
||||||
|
@ -70,3 +72,10 @@ export interface StatusResponse {
|
||||||
application: Application;
|
application: Application;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
acct: string;
|
||||||
|
display_name: string;
|
||||||
|
fqn: string;
|
||||||
|
avatar_static: string;
|
||||||
|
}
|
Loading…
Reference in a new issue