Add reddit connection
This commit is contained in:
		
							parent
							
								
									901d126d6b
								
							
						
					
					
						commit
						ab4a28ee31
					
				
							
								
								
									
										5
									
								
								src/connections/Reddit/RedditSettings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/connections/Reddit/RedditSettings.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| export class RedditSettings { | ||||
| 	enabled: boolean = false; | ||||
| 	clientId: string | null = null; | ||||
| 	clientSecret: string | null = null; | ||||
| } | ||||
							
								
								
									
										140
									
								
								src/connections/Reddit/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/connections/Reddit/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| import { | ||||
| 	Config, | ||||
| 	ConnectedAccount, | ||||
| 	ConnectionCallbackSchema, | ||||
| 	ConnectionLoader, | ||||
| 	DiscordApiErrors, | ||||
| } from "@fosscord/util"; | ||||
| import fetch from "node-fetch"; | ||||
| import Connection from "../../util/connections/Connection"; | ||||
| import { RedditSettings } from "./RedditSettings"; | ||||
| 
 | ||||
| interface OAuthTokenResponse { | ||||
| 	access_token: string; | ||||
| 	token_type: string; | ||||
| 	scope: string; | ||||
| 	refresh_token?: string; | ||||
| 	expires_in?: number; | ||||
| } | ||||
| 
 | ||||
| export interface UserResponse { | ||||
| 	verified: boolean; | ||||
| 	coins: number; | ||||
| 	id: string; | ||||
| 	is_mod: boolean; | ||||
| 	has_verified_email: boolean; | ||||
| 	total_karma: number; | ||||
| 	name: string; | ||||
| 	created: number; | ||||
| 	gold_creddits: number; | ||||
| 	created_utc: number; | ||||
| } | ||||
| 
 | ||||
| export interface ErrorResponse { | ||||
| 	message: string; | ||||
| 	error: number; | ||||
| } | ||||
| 
 | ||||
| export default class RedditConnection extends Connection { | ||||
| 	public readonly id = "reddit"; | ||||
| 	public readonly authorizeUrl = "https://www.reddit.com/api/v1/authorize"; | ||||
| 	public readonly tokenUrl = "https://www.reddit.com/api/v1/access_token"; | ||||
| 	public readonly userInfoUrl = "https://oauth.reddit.com/api/v1/me"; | ||||
| 	public readonly scopes = ["identity"]; | ||||
| 	settings: RedditSettings = new RedditSettings(); | ||||
| 
 | ||||
| 	init(): void { | ||||
| 		this.settings = ConnectionLoader.getConnectionConfig( | ||||
| 			this.id, | ||||
| 			this.settings, | ||||
| 		) as RedditSettings; | ||||
| 	} | ||||
| 
 | ||||
| 	getAuthorizationUrl(userId: string): string { | ||||
| 		const state = this.createState(userId); | ||||
| 		const url = new URL(this.authorizeUrl); | ||||
| 
 | ||||
| 		url.searchParams.append("client_id", this.settings.clientId!); | ||||
| 		// TODO: probably shouldn't rely on cdn as this could be different from what we actually want. we should have an api endpoint setting.
 | ||||
| 		url.searchParams.append( | ||||
| 			"redirect_uri", | ||||
| 			`${ | ||||
| 				Config.get().cdn.endpointPrivate || "http://localhost:3001" | ||||
| 			}/connections/${this.id}/callback`,
 | ||||
| 		); | ||||
| 		url.searchParams.append("response_type", "code"); | ||||
| 		url.searchParams.append("scope", this.scopes.join(" ")); | ||||
| 		url.searchParams.append("state", state); | ||||
| 		return url.toString(); | ||||
| 	} | ||||
| 
 | ||||
| 	getTokenUrl(): string { | ||||
| 		return this.tokenUrl; | ||||
| 	} | ||||
| 
 | ||||
| 	async exchangeCode(state: string, code: string): Promise<string> { | ||||
| 		this.validateState(state); | ||||
| 
 | ||||
| 		const url = this.getTokenUrl(); | ||||
| 
 | ||||
| 		return fetch(url.toString(), { | ||||
| 			method: "POST", | ||||
| 			headers: { | ||||
| 				Accept: "application/json", | ||||
| 				Authorization: `Basic ${Buffer.from( | ||||
| 					`${this.settings.clientId}:${this.settings.clientSecret}`, | ||||
| 				).toString("base64")}`,
 | ||||
| 				"Content-Type": "application/x-www-form-urlencoded", | ||||
| 			}, | ||||
| 			body: new URLSearchParams({ | ||||
| 				grant_type: "authorization_code", | ||||
| 				code: code, | ||||
| 				redirect_uri: `${ | ||||
| 					Config.get().cdn.endpointPrivate || "http://localhost:3001" | ||||
| 				}/connections/${this.id}/callback`,
 | ||||
| 			}), | ||||
| 		}) | ||||
| 			.then((res) => res.json()) | ||||
| 			.then((res: OAuthTokenResponse) => res.access_token) | ||||
| 			.catch((e) => { | ||||
| 				console.error( | ||||
| 					`Error exchanging token for ${this.id} connection: ${e}`, | ||||
| 				); | ||||
| 				throw DiscordApiErrors.INVALID_OAUTH_TOKEN; | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	async getUser(token: string): Promise<UserResponse> { | ||||
| 		const url = new URL(this.userInfoUrl); | ||||
| 		return fetch(url.toString(), { | ||||
| 			method: "GET", | ||||
| 			headers: { | ||||
| 				Authorization: `Bearer ${token}`, | ||||
| 			}, | ||||
| 		}).then((res) => res.json()); | ||||
| 	} | ||||
| 
 | ||||
| 	async handleCallback( | ||||
| 		params: ConnectionCallbackSchema, | ||||
| 	): Promise<ConnectedAccount | null> { | ||||
| 		const userId = this.getUserId(params.state); | ||||
| 		const token = await this.exchangeCode(params.state, params.code!); | ||||
| 		const userInfo = await this.getUser(token); | ||||
| 
 | ||||
| 		const exists = await this.hasConnection(userId, userInfo.id.toString()); | ||||
| 
 | ||||
| 		if (exists) return null; | ||||
| 
 | ||||
| 		// TODO: connection metadata
 | ||||
| 
 | ||||
| 		return await this.createConnection({ | ||||
| 			access_token: token, | ||||
| 			user_id: userId, | ||||
| 			external_id: userInfo.id.toString(), | ||||
| 			friend_sync: params.friend_sync, | ||||
| 			name: userInfo.name, | ||||
| 			verified: userInfo.has_verified_email, | ||||
| 			type: this.id, | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Puyodead1
						Puyodead1