mirror of
https://github.com/Sevichecc/raycast-akkoma-extension.git
synced 2025-04-30 22:49:30 +08:00
feat: upload file to remote
This commit is contained in:
parent
57cd5db168
commit
24f904a60f
4 changed files with 98 additions and 19 deletions
43
src/api.ts
43
src/api.ts
|
@ -1,6 +1,16 @@
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
import fs from "fs";
|
||||||
|
import { FormData, File } from "node-fetch";
|
||||||
import { OAuth, getPreferenceValues } from "@raycast/api";
|
import { OAuth, getPreferenceValues } from "@raycast/api";
|
||||||
import { Credentials, Preference, Status, StatusResponse, Account } from "./types";
|
import {
|
||||||
|
Credentials,
|
||||||
|
Preference,
|
||||||
|
Status,
|
||||||
|
StatusResponse,
|
||||||
|
Account,
|
||||||
|
StatusAttachment,
|
||||||
|
UploadAttachResponse,
|
||||||
|
} 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> => {
|
||||||
|
@ -43,6 +53,7 @@ export const postNewStatus = async ({
|
||||||
sensitive,
|
sensitive,
|
||||||
scheduled_at,
|
scheduled_at,
|
||||||
content_type,
|
content_type,
|
||||||
|
media_ids,
|
||||||
}: Partial<Status>): Promise<StatusResponse> => {
|
}: Partial<Status>): Promise<StatusResponse> => {
|
||||||
const { instance } = getPreferenceValues<Preference>();
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
const tokenSet = await client.getTokens();
|
const tokenSet = await client.getTokens();
|
||||||
|
@ -51,7 +62,7 @@ export const postNewStatus = async ({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + tokenSet?.accessToken,
|
Authorization: `Bearer ${tokenSet?.accessToken}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
status,
|
status,
|
||||||
|
@ -60,6 +71,7 @@ export const postNewStatus = async ({
|
||||||
sensitive,
|
sensitive,
|
||||||
content_type,
|
content_type,
|
||||||
scheduled_at,
|
scheduled_at,
|
||||||
|
media_ids,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,7 +87,7 @@ export const fetchAccountInfo = async (): Promise<Account> => {
|
||||||
const response = await fetch(`https://${instance}/api/v1/accounts/verify_credentials`, {
|
const response = await fetch(`https://${instance}/api/v1/accounts/verify_credentials`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer " + tokenSet?.accessToken,
|
Authorization: `Bearer ${tokenSet?.accessToken}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,3 +95,28 @@ export const fetchAccountInfo = async (): Promise<Account> => {
|
||||||
|
|
||||||
return (await response.json()) as Account;
|
return (await response.json()) as Account;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const uploadAttachment = async ({ file, description }: StatusAttachment): Promise<UploadAttachResponse> => {
|
||||||
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
|
const tokenSet = await client.getTokens();
|
||||||
|
|
||||||
|
const attachment = fs.readFileSync(file);
|
||||||
|
const attachmentData = new File([attachment], file);
|
||||||
|
await attachmentData.arrayBuffer();
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", attachmentData);
|
||||||
|
formData.append("description", description ?? "");
|
||||||
|
|
||||||
|
const response = await fetch(`https://${instance}/api/v1/media/`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokenSet?.accessToken}`,
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Could not upload attechments");
|
||||||
|
|
||||||
|
return (await response.json()) as UploadAttachResponse;
|
||||||
|
};
|
||||||
|
|
|
@ -2,17 +2,21 @@ import { Form, LaunchProps } from "@raycast/api";
|
||||||
import VisibilityDropdown from "./components/VisibilityDropdown";
|
import VisibilityDropdown from "./components/VisibilityDropdown";
|
||||||
import SimpleCommand from "./simple-status";
|
import SimpleCommand from "./simple-status";
|
||||||
import { Status } from "./types";
|
import { Status } from "./types";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface CommandProps extends LaunchProps<{ draftValues: Status }> {
|
interface CommandProps extends LaunchProps<{ draftValues: Status }> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DetailCommand(props: CommandProps) {
|
export default function DetailCommand(props: CommandProps) {
|
||||||
|
const [files, setFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleCommand {...props}>
|
<SimpleCommand {...props}>
|
||||||
|
<Form.FilePicker id="files" value={files} onChange={setFiles}/>
|
||||||
|
{files.length !== 0 && <Form.TextArea id="description" title="Alt text" />}
|
||||||
<Form.DatePicker id="datepicker" title="Scheduled Time" />
|
<Form.DatePicker id="datepicker" title="Scheduled Time" />
|
||||||
<VisibilityDropdown />
|
<VisibilityDropdown />
|
||||||
<Form.FilePicker id="files" />
|
|
||||||
</SimpleCommand>
|
</SimpleCommand>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
LaunchProps,
|
LaunchProps,
|
||||||
} from "@raycast/api";
|
} from "@raycast/api";
|
||||||
|
|
||||||
import { postNewStatus } from "./api";
|
import { postNewStatus, uploadAttachment } from "./api";
|
||||||
import { AkkomaError, StatusResponse, Preference, Status } from "./types";
|
import { AkkomaError, StatusResponse, Preference, Status } from "./types";
|
||||||
import { authorize } from "./oauth";
|
import { authorize } from "./oauth";
|
||||||
|
|
||||||
|
@ -28,6 +28,11 @@ interface CommandProps extends LaunchProps<{ draftValues: SimpleStatus }> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface StausForm extends Status {
|
||||||
|
files: string[];
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function SimpleCommand(props: CommandProps) {
|
export default function SimpleCommand(props: CommandProps) {
|
||||||
const { instance } = getPreferenceValues<Preference>();
|
const { instance } = getPreferenceValues<Preference>();
|
||||||
const { draftValues } = props;
|
const { draftValues } = props;
|
||||||
|
@ -52,15 +57,29 @@ export default function SimpleCommand(props: CommandProps) {
|
||||||
init();
|
init();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = async (values: Status) => {
|
const handleSubmit = async ({ spoiler_text, status, scheduled_at, visibility, files, description }: StausForm) => {
|
||||||
try {
|
try {
|
||||||
if (!values.status) throw new Error("You might forget the content, right ? |・ω・)");
|
if (!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 ... ᕕ( ᐛ )ᕗ");
|
||||||
|
|
||||||
const response = await postNewStatus({
|
const mediaIds = await Promise.all(
|
||||||
...values,
|
files?.map(async (file) => {
|
||||||
|
const { id} = await uploadAttachment({ file, description });
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const newStatus: Partial<Status> = {
|
||||||
|
spoiler_text,
|
||||||
|
status,
|
||||||
|
scheduled_at,
|
||||||
|
visibility,
|
||||||
content_type: isMarkdown ? "text/markdown" : "text/plain",
|
content_type: isMarkdown ? "text/markdown" : "text/plain",
|
||||||
});
|
media_ids: mediaIds,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await postNewStatus(newStatus);
|
||||||
|
|
||||||
setStatusInfo(response);
|
setStatusInfo(response);
|
||||||
cache.set("latest_published_status", JSON.stringify(response));
|
cache.set("latest_published_status", JSON.stringify(response));
|
||||||
|
@ -75,7 +94,7 @@ export default function SimpleCommand(props: CommandProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCw = (value:boolean) => {
|
const handleCw = (value: boolean) => {
|
||||||
setShowCw(value);
|
setShowCw(value);
|
||||||
if (cwRef.current) {
|
if (cwRef.current) {
|
||||||
cwRef.current.focus();
|
cwRef.current.focus();
|
||||||
|
@ -105,7 +124,7 @@ export default function SimpleCommand(props: CommandProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StatusContent isMarkdown={isMarkdown} draftStatus={draftValues?.status} />
|
<StatusContent isMarkdown={isMarkdown} draftStatus={draftValues?.status} />
|
||||||
<VisibilityDropdown />
|
{!props.children && <VisibilityDropdown />}
|
||||||
{props.children}
|
{props.children}
|
||||||
<Form.Checkbox id="markdown" title="Markdown" label="" value={isMarkdown} onChange={setIsMarkdown} storeValue />
|
<Form.Checkbox id="markdown" title="Markdown" label="" value={isMarkdown} onChange={setIsMarkdown} storeValue />
|
||||||
<Form.Checkbox id="showCw" title="Sensitive" label="" value={showCw} onChange={handleCw} storeValue />
|
<Form.Checkbox id="showCw" title="Sensitive" label="" value={showCw} onChange={handleCw} storeValue />
|
||||||
|
|
33
src/types.ts
33
src/types.ts
|
@ -47,20 +47,20 @@ interface Poll {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Status {
|
export interface Status {
|
||||||
spoiler_text: string;
|
spoiler_text?: string;
|
||||||
status: string;
|
status: string;
|
||||||
content_type: string;
|
content_type: string;
|
||||||
expires_in: number;
|
visibility: VisibilityScope;
|
||||||
|
expires_in?: number;
|
||||||
in_reply_to_conversation_id?: string;
|
in_reply_to_conversation_id?: string;
|
||||||
in_reply_to_id?: string;
|
in_reply_to_id?: string;
|
||||||
language: string;
|
language?: string;
|
||||||
media_ids: string[];
|
media_ids?: string[];
|
||||||
poll?: Poll;
|
poll?: Poll;
|
||||||
preview?: boolean | string | number;
|
preview?: boolean | string | number;
|
||||||
scheduled_at: Date;
|
scheduled_at?: Date;
|
||||||
sensitive: string | boolean | number;
|
sensitive?: string | boolean | number;
|
||||||
to?: string[];
|
to?: string[];
|
||||||
visibility: VisibilityScope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusResponse {
|
export interface StatusResponse {
|
||||||
|
@ -77,3 +77,22 @@ export interface Account {
|
||||||
fqn: string;
|
fqn: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StatusAttachment {
|
||||||
|
file: string;
|
||||||
|
description?: string;
|
||||||
|
focus?: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadAttachResponse{
|
||||||
|
description: string | null;
|
||||||
|
id: string;
|
||||||
|
pleroma: {
|
||||||
|
mime_type: string;
|
||||||
|
}
|
||||||
|
preview_url: string;
|
||||||
|
remote_url: string | null;
|
||||||
|
text_url: string;
|
||||||
|
type: "image" | "video" | "audio" | "unknown",
|
||||||
|
url: string;
|
||||||
|
}
|
Loading…
Reference in a new issue