109 lines
4.3 KiB
TypeScript
109 lines
4.3 KiB
TypeScript
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
const qs = require('qs');
|
|
import dotenv from 'dotenv';
|
|
import axiosRetry from 'axios-retry';
|
|
import axiosThrottle from 'axios-request-throttle';
|
|
import { DatabaseService } from '../../db/services/Database';
|
|
|
|
dotenv.config();
|
|
|
|
class RedditSessionManager {
|
|
private static instance: RedditSessionManager;
|
|
public axiosInstance: AxiosInstance;
|
|
|
|
private constructor() {
|
|
axiosThrottle.use(axios, { requestsPerSecond: 1 }); // Throttle setup
|
|
|
|
this.axiosInstance = axios.create({
|
|
baseURL: 'https://oauth.reddit.com/', // Base URL for OAuth2 Reddit API
|
|
headers: {
|
|
'User-Agent': 'CrossTalk PM/0.1 by Whitneywisconson'
|
|
}
|
|
});
|
|
axiosRetry(this.axiosInstance, {
|
|
retries: 3,
|
|
retryDelay: this.retryDelayStrategy,
|
|
retryCondition: this.retryCondition,
|
|
});
|
|
}
|
|
|
|
public static async getInstance(): Promise<RedditSessionManager> {
|
|
if (!RedditSessionManager.instance) {
|
|
RedditSessionManager.instance = new RedditSessionManager();
|
|
await RedditSessionManager.instance.initializeAuthentication();
|
|
}
|
|
return RedditSessionManager.instance;
|
|
}
|
|
|
|
private async initializeAuthentication() {
|
|
// Check the database for an existing token
|
|
const currentToken = await DatabaseService.getCurrentOAuthToken(this.axiosInstance.defaults.baseURL as string);
|
|
if (currentToken && new Date() < new Date(currentToken.expiry_timestamp * 1000)) {
|
|
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${currentToken.access_token}`;
|
|
console.log('Using existing Reddit API token from database.');
|
|
return;
|
|
}
|
|
console.log('No current Reddit API token from database requesting one')
|
|
|
|
// Authenticate with Reddit API to get a new token
|
|
await this.authenticate();
|
|
}
|
|
|
|
private async authenticate() {
|
|
if (!process.env.redditUsername) throw 'No Reddit Username Found in .env'
|
|
if (!process.env.redditPassword) throw 'No Reddit Password Found in .env'
|
|
const redditUsername = process.env.redditUsername as string
|
|
const redditPassword = process.env.redditPassword as string
|
|
const credentials = qs.stringify({
|
|
grant_type: 'password',
|
|
username: redditUsername,
|
|
password: redditPassword,
|
|
});
|
|
|
|
const authString = `${process.env.redditClientId}:${process.env.redditSecret}`;
|
|
const buffer = Buffer.from(authString);
|
|
const base64AuthString = buffer.toString('base64');
|
|
|
|
try {
|
|
const response = await this.axiosInstance.post('https://www.reddit.com/api/v1/access_token', credentials, {
|
|
headers: {
|
|
'User-Agent': `CrossTalk PM/0.1 by ${redditUsername}`, //TODO Dynamically set app name here
|
|
'Authorization': `Basic ${base64AuthString}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
});
|
|
|
|
// Upsert the new token into the database
|
|
await DatabaseService.upsertOAuthToken(
|
|
this.axiosInstance.defaults.baseURL as string,
|
|
{
|
|
access_token: response.data.access_token,
|
|
token_type: response.data.token_type,
|
|
expires_in: response.data.expires_in,
|
|
scope: response.data.scope,
|
|
});
|
|
|
|
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${response.data.access_token}`;
|
|
console.log('Reddit API authenticated successfully.');
|
|
} catch (error) {
|
|
console.error('Error authenticating with Reddit API:', error);
|
|
}
|
|
}
|
|
|
|
private retryDelayStrategy(retryCount: number, error: AxiosError): number {
|
|
const retryAfter = error.response?.headers['retry-after'];
|
|
if (retryAfter) {
|
|
console.log(`429 Retry After: ${retryAfter}`);
|
|
return +retryAfter * 1000;
|
|
}
|
|
return Math.pow(2, retryCount) * 2000;
|
|
}
|
|
|
|
private retryCondition(error: AxiosError): boolean {
|
|
const status = error.response?.status ?? 0;
|
|
return status === 429 || status >= 400;
|
|
}
|
|
}
|
|
|
|
export default RedditSessionManager;
|