Skip to content

Commit

Permalink
Add getForumTopicAndPosts(), make way for forum stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
TTTaevas committed Nov 23, 2023
1 parent 2169c19 commit 9cf9579
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Your `refresh_token` can actually also expire at a (purposefully) unknown time,
### Forum
- [ ] Reply Topic
- [ ] Create Topic
- [ ] Get Topic and Posts
- [x] Get Topic and Posts // removing `search` for simplicity
- [ ] Edit Topic
- [ ] Edit Post

Expand Down
62 changes: 62 additions & 0 deletions lib/forum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export interface ForumPost {
created_at: Date
deleted_at: Date | null
edited_at: Date | null
edited_by_id: number | null
forum_id: number
id: number
topic_id: number
user_id: number
body: {
/**
* Post content in HTML format
*/
html: string
/**
* Post content in BBCode format
*/
raw: string
}
}

export interface ForumTopic {
created_at: Date
deleted_at: Date | null
first_post_id: number
forum_id: number
id: number
is_locked: boolean
last_post_id: number
post_count: number
title: string
type: "normal" | "sticky" | "announcement"
updated_at: Date
user_id: number
poll: {
allow_vote_change: boolean
/**
* Can be in the future
*/
ended_at: Date | null
hide_incomplete_results: boolean
last_vote_at: Date | null
max_votes: number
options: {
id: number
text: {
bbcode: string
html: string
}
/**
* Not present if the poll is incomplete and results are hidden
*/
vote_count?: number
}[]
started_at: Date
title: {
bbcode: string
html: string
}
total_vote_count: number
} | null
}
75 changes: 71 additions & 4 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { WikiPage } from "./wiki.js"
import { NewsPost, NewsPostWithContentNavigation } from "./news.js"
import { SearchResultUser, SearchResultWiki } from "./home.js"
import { Rulesets, Mod, Scope } from "./misc.js"
import { ForumPost, ForumTopic } from "./forum.js"

export { User, UserWithKudosu, UserWithCountry, UserWithCountryCover, UserWithCountryCoverGroupsStatisticsrulesets, UserWithCountryCoverGroupsStatisticsSupport,
UserExtended, UserExtendedWithStatisticsrulesets,
Expand Down Expand Up @@ -51,7 +52,8 @@ export { Rulesets, Mod, Scope } from "./misc.js"
* @returns x, but with it (or what it contains) now having the correct type
*/
function correctType(x: any): any {
const bannedProperties = ["name", "version", "author"]
// raw and bbcode because forum, author and version because changelog
const bannedProperties = ["name", "version", "author", "raw", "bbcode"]

if (typeof x === "boolean") {
return x
Expand Down Expand Up @@ -80,11 +82,12 @@ function correctType(x: any): any {
* @param client_id The Client ID, find it at https://osu.ppy.sh/home/account/edit#new-oauth-application
* @param redirect_uri The specified Application Callback URL, aka where the user will be redirected upon clicking the button to authorize
* @param scopes What you want to do with/as the user
* @param server (defaults to https://osu.ppy.sh) The API server
* @returns The link people should click on
*/
export function generateAuthorizationURL(client_id: number, redirect_uri: string, scopes: Scope[]): string {
export function generateAuthorizationURL(client_id: number, redirect_uri: string, scopes: Scope[], server: string = "https://osu.ppy.sh"): string {
const s = String(scopes).replace(/,/g, "%20")
return `https://osu.ppy.sh/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${s}&response_type=code`
return `${server}/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${s}&response_type=code`
}

/**
Expand Down Expand Up @@ -183,7 +186,8 @@ export class API {
method: "post",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
"Content-Type": "application/json",
"User-Agent": "osu-api-v2-js (https://github.com/TTTaevas/osu-api-v2-js)"
},
body: JSON.stringify(body)
})
Expand Down Expand Up @@ -811,4 +815,67 @@ export class API {
const lookup = post.id !== undefined ? post.id : post.slug
return await this.request("get", `news/${lookup}`, {key})
}


// FORUM STUFF

/**
*
* @param topic_id
* @param text
* @scope forum.write
*/
private async replyForumTopic(topic: {id: number} | ForumTopic, text: string) {
return await this.request("post", `forums/topics/${topic.id}/reply`, {body: text})
}

/**
*
* @param forum_id
* @param title
* @param text
* @param poll
* @scope forum.write
*/
private async createForumTopic(forum_id: number, title: string, text: string, poll?: {title: string, options: string[], max_options?: number,
vote_change?: boolean, length_days: number, hide_results?: boolean}) {
const with_poll = poll !== undefined
return await this.request("post", "forums/topics", {forum_id, title, body: text, with_poll})
}

/**
*
* @remarks The oldest post of a topic is the text of a topic
* @param topic The topic in question
* @param limit (defaults to 20, max 50) How many `posts` maximum?
* @param sort (defaults to "id_asc") "id_asc" to have the oldest post at the beginning of the `posts` array, "id_desc" to have the newest instead
* @param first_post (ignored if `cursor_string`) An Object with the id of the first post to be returned in `posts`
* @param cursor_string Use a response's `cursor_string` with the same parameters to get the next "page" of results, so `posts` in this instance!
*/
async getForumTopicAndPosts(topic: {id: number} | ForumTopic, limit: number = 20, sort: "id_asc" | "id_desc" = "id_asc", first_post?: {id: number} | ForumPost,
cursor_string?: string): Promise<{posts: ForumPost[], topic: ForumTopic, cursor_string: string}> {
const start = sort === "id_asc" && first_post ? first_post.id : undefined
const end = sort === "id_desc" && first_post ? first_post.id : undefined
return await this.request("get", `forums/topics/${topic.id}`, {sort, limit, start, end, cursor_string})
}

/**
*
* @param topic_id
* @param new_title
* @scope forum.write
*/
private async editForumTopicTitle(topic: {id: number} | ForumTopic, new_title: string) {
return await this.request("put", `forums/topics/${topic.id}`, {"forum_topic[topic_title]": new_title})
}

/**
*
* @param post_id
* @param new_text
* @scope forum.write
*/
private async editForumPost(post: {id: number} | ForumPost, new_text: string) {
return await this.request("put", `forums/topics/${post.id}`, {body: new_text})
}
}
50 changes: 25 additions & 25 deletions lib/news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
* Expected from api.getNews(), NewsPostWithContentNavigation
*/
export interface NewsPost {
id: number
author: string
/**
* Link to view the file on GitHub
*/
edit_url: string
/**
* Link to the first image in the document
*/
first_image: string | null
published_at: Date
updated_at: Date
/**
* Filename without the extension, used in URLs
*/
slug: string
title: string
id: number
author: string
/**
* Link to view the file on GitHub
*/
edit_url: string
/**
* Link to the first image in the document
*/
first_image: string | null
published_at: Date
updated_at: Date
/**
* Filename without the extension, used in URLs
*/
slug: string
title: string
}

/**
* Expected from api.getNewsPost()
*/
export interface NewsPostWithContentNavigation extends NewsPost {
/**
* With HTML
*/
content: string
navigation: {
newer?: NewsPost
older?: NewsPost
}
/**
* With HTML
*/
content: string
navigation: {
newer?: NewsPost
older?: NewsPost
}
}
9 changes: 6 additions & 3 deletions lib/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const testRankingStuff = async (rank_gen: tsj.SchemaGenerator): Promise<boolean>
/**
* Check if searchUser() and similar work fine
*/
const testHomeStuff = async (home_gen: tsj.SchemaGenerator, news_gen: tsj.SchemaGenerator): Promise<boolean> => {
const testHomeStuff = async (home_gen: tsj.SchemaGenerator, news_gen: tsj.SchemaGenerator, forum_gen: tsj.SchemaGenerator): Promise<boolean> => {
let okay = true

let f1 = await <Promise<ReturnType<typeof api.searchUser> | false>>attempt("\nsearchUser: ", api.searchUser("Tae", 2))
Expand All @@ -245,12 +245,14 @@ const testHomeStuff = async (home_gen: tsj.SchemaGenerator, news_gen: tsj.Schema
if (!isOk(f4, !f4 || (f4.length >= 1 && validate(f4, "NewsPost", news_gen)))) okay = false
let f5 = await <Promise<ReturnType<typeof api.getNewsPost> | false>>attempt("getNewsPost: ", api.getNewsPost({id: 26}))
if (!isOk(f5, !f5 || (f5.title === "Official osu! Fanart Contest 5 Begins!" && validate(f5, "NewsPostWithContentNavigation", news_gen)))) okay = false
let f6 = await <Promise<ReturnType<typeof api.getForumTopicAndPosts> | false>>attempt("getForumTopicAndPosts: ", api.getForumTopicAndPosts({id: 1848236}, 2))
if (!isOk(f6, !f6 || (f6.topic.title === "survey" && validate(f6.topic, "ForumTopic", forum_gen) && validate(f6.posts, "ForumPost", forum_gen)))) okay = false

return okay
}

const test = async (id: string, secret: string): Promise<void> => {
api = await osu.API.createAsync({id: Number(id), secret}, undefined, "all")
api = await osu.API.createAsync({id: Number(id), secret}, undefined, "all") //"http://127.0.0.1:8080")

const score_gen = tsj.createGenerator({path: "lib/score.ts", additionalProperties: true})
const user_gen = tsj.createGenerator({path: "lib/user.ts", additionalProperties: true})
Expand All @@ -264,7 +266,8 @@ const test = async (id: string, secret: string): Promise<void> => {
const e = await testRankingStuff(tsj.createGenerator({path: "lib/ranking.ts", additionalProperties: true}))
const f = await testHomeStuff(
tsj.createGenerator({path: "lib/home.ts", additionalProperties: true}),
tsj.createGenerator({path: "lib/news.ts", additionalProperties: true})
tsj.createGenerator({path: "lib/news.ts", additionalProperties: true}),
tsj.createGenerator({path: "lib/forum.ts", additionalProperties: true})
)

const arr = [a,b,c,d,e,f]
Expand Down
7 changes: 4 additions & 3 deletions lib/tests/test_authorized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@ import { exec } from "child_process"
import util from "util"

const prompt = promptSync({sigint: true})
const server = "https://dev.ppy.sh"

async function test(id: string | undefined, secret: string | undefined, redirect_uri: string | undefined) {
if (id === undefined) {throw new Error("no ID env var")}
if (secret === undefined) {throw new Error("no SECRET env var")}
if (redirect_uri === undefined) {throw new Error("no REDIRECT_URI env var")}

let url = osu.generateAuthorizationURL(Number(id), redirect_uri, ["public", "friends.read"])
let url = osu.generateAuthorizationURL(Number(id), redirect_uri, ["public", "friends.read"], server)
exec(`xdg-open "${url}"`)
let code = prompt(`What code do you get from: ${url}\n\n`)

let api = await osu.API.createAsync({id: Number(id), secret}, {code, redirect_uri}, "all")
let api = await osu.API.createAsync({id: Number(id), secret}, {code, redirect_uri}, "all", server)
api.access_token = "a"
api.expires = new Date(1980)
let r = await api.getResourceOwner()
console.log(r.username)
}

test(process.env.ID, process.env.SECRET, process.env.REDIRECT_URI)
test(process.env.DEV_ID, process.env.DEV_SECRET, process.env.REDIRECT_URI)

0 comments on commit 9cf9579

Please sign in to comment.