Skip to content

Commit

Permalink
Add API for posting drafts and publishing posts, document it in README
Browse files Browse the repository at this point in the history
  • Loading branch information
ApexDevelopment committed Feb 27, 2024
1 parent 84e451d commit 049a80d
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 33 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ An unofficial API wrapper for cohost.org that uses tRPC.

cohost-api is in very early development!

## Usage

```js
import { Client, PostBuilder } from "cohost-api";

const client = new Client();

async function demo() {
// Log in to Cohost
let user = await client.login("EMAIL_ADDRESS", "YOUR_PASSWORD");

// Create new post
let post = new PostBuilder();
post.addMarkdownBlock("hello from cohost-api!");

// Send to Cohost
user?.projects[0].createDraft(post.build()); // or use .createPost() to publish it immediately
}
```
## Reasons not to use this (yet)
- The Cohost tRPC API is undocumented and not meant for public use.
Expand Down
181 changes: 148 additions & 33 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,102 @@ import crypto = require("crypto");
const COHOST_API_URL = "https://cohost.org/api/v1";
const COHOST_TRPC_URL = "https://cohost.org/api/v1/trpc";

enum PostState {
DRAFT = 0,
PUBLISHED = 1,
}

class Post {
projectHandle: string;
postId: number;
content: {
postState: PostState;
headline: string;
adultContent: boolean;
blocks: {
type: string;
markdown: {
content: string;
};
}[];
cws: string[];
tags: string[];
};

constructor(
projectHandle: string,
postId: number,
content: {
postState: PostState;
headline: string;
adultContent: boolean;
blocks: {
type: string;
markdown: {
content: string;
};
}[];
cws: string[];
tags: string[];
},
) {
this.projectHandle = projectHandle;
this.postId = postId;
this.content = content;
}
}

class PostBuilder {
headline: string;
adultContent: boolean;
blocks: {
type: string;
markdown: {
content: string;
};
}[] = [];
cws: string[] = [];
tags: string[] = [];

constructor(headline: string = "", adultContent: boolean = false) {
this.headline = headline;
this.adultContent = adultContent;
}

addBlock(type: string, markdown: { content: string }) {
this.blocks.push({ type, markdown });
return this;
}

addMarkdownBlock(content: string) {
this.blocks.push({ type: "markdown", markdown: { content } });
return this;
}

addCw(cw: string) {
this.cws.push(cw);
return this;
}

addTag(tag: string) {
this.tags.push(tag);
return this;
}

build() {
return new Post("", 0, {
postState: PostState.DRAFT,
headline: this.headline,
adultContent: this.adultContent,
blocks: this.blocks,
cws: this.cws,
tags: this.tags,
});
}
}

class Project {
private trpc: any;
id: number;
handle: string;
displayName: string;
Expand All @@ -20,35 +115,39 @@ class Project {
flags: string[];
avatarShape: string;

constructor({
projectId,
handle,
displayName,
dek,
description,
avatarURL,
headerURL,
headerPreviewURL,
privacy,
url,
pronouns,
flags,
avatarShape,
}: {
projectId: number;
handle: string;
displayName: string;
dek: string;
description: string;
avatarURL: string;
headerURL: string;
headerPreviewURL: string;
privacy: string;
url: string;
pronouns: string;
flags: string[];
avatarShape: string;
}) {
constructor(
trpc: any,
{
projectId,
handle,
displayName,
dek,
description,
avatarURL,
headerURL,
headerPreviewURL,
privacy,
url,
pronouns,
flags,
avatarShape,
}: {
projectId: number;
handle: string;
displayName: string;
dek: string;
description: string;
avatarURL: string;
headerURL: string;
headerPreviewURL: string;
privacy: string;
url: string;
pronouns: string;
flags: string[];
avatarShape: string;
},
) {
this.trpc = trpc;
this.id = projectId;
this.handle = handle;
this.displayName = displayName;
Expand All @@ -63,14 +162,30 @@ class Project {
this.flags = flags;
this.avatarShape = avatarShape;
}

async createPost(post: Post) {
post.projectHandle = this.handle;
post.content.postState = PostState.PUBLISHED;
post.postId = (await this.trpc.posts.create.mutate(post)).postId;
return post;
}

async createDraft(post: Post) {
post.projectHandle = this.handle;
post.content.postState = PostState.DRAFT;
post.postId = (await this.trpc.posts.create.mutate(post)).postId;
return post;
}
}

class User {
private trpc: any;
projects: Project[] = [];
id: number;
email: string;

constructor(id: number, email: string) {
constructor(trpc: any, id: number, email: string) {
this.trpc = trpc;
this.id = id;
this.email = email;
}
Expand Down Expand Up @@ -126,14 +241,14 @@ class Client {

if (res?.userId) {
this.loggedIn = true;
this.user = new User(res.userId, email);
this.user = new User(this.trpc, res.userId, email);

let projects = (await this.trpc.projects.listEditedProjects.query())
.projects;

if (projects) {
this.user.projects = projects.map((project: any) => {
return new Project(project);
return new Project(this.trpc, project);
});
} else {
console.error("Failed to load projects for user!");
Expand All @@ -144,4 +259,4 @@ class Client {
}
}

export { Client };
export { Client, PostBuilder, PostState };
28 changes: 28 additions & 0 deletions posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@ import { z } from "zod";

const posts = router({
isLiked: t.procedure.query(() => {}),
create: t.procedure
.input(
z.object({
projectHandle: z.string(),
content: z.object({
postState: z.number(),
headline: z.string(),
adultContent: z.boolean(),
blocks: z.array(
z.object({
type: z.string(),
markdown: z.optional(
z.object({
content: z.string(),
}),
),
}),
),
}),
cws: z.array(z.string()),
tags: z.array(z.string()),
}),
)
.mutation(() => {
return {
postId: 0,
};
}),
update: t.procedure
.input(
z.object({
Expand Down

0 comments on commit 049a80d

Please sign in to comment.