2022-07-13 15:41:41 +02:00
// @ts-expect-error Types :(
import config from '../../files/config.toml' ;
// @ts-expect-error Types :(
import utilities from '../../files/utilities.toml' ;
import MiniSearch from 'minisearch' ;
import { Logger } from './Logger' ;
import { APIApplicationCommandOptionChoice } from 'discord-api-types/v10' ;
2022-07-13 17:49:09 +02:00
import { Database } from 'bun:sqlite' ;
2022-07-14 08:13:18 +02:00
import { discordChoicesRegex } from './regexes' ;
2022-07-13 15:41:41 +02:00
interface Issue {
id : number ;
repository : string ;
title : string ;
number : number ;
state : 'open' | 'closed' ,
2022-07-13 17:49:09 +02:00
created_at : string ;
closed_at : string | null ;
2022-07-13 15:41:41 +02:00
html_url : string ;
user_login : string ;
user_html_url : string ;
2022-07-13 18:28:59 +02:00
type : '(IS)' | '(PR)' ;
2022-07-13 15:41:41 +02:00
}
interface PullRequest extends Issue {
2022-07-13 17:49:09 +02:00
merged_at : string | null ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
export const db = new Database ( './files/database.sqlite' ) ;
await db . exec ( 'DROP TABLE IF EXISTS issuesandprs' ) ;
await db . exec ( 'CREATE TABLE issuesandprs (id INTEGER PRIMARY KEY, repository TEXT, title TEXT, number INTEGER, state TEXT, created_at TEXT, closed_at TEXT, merged_at TEXT, html_url TEXT, user_login TEXT, user_html_url TEXT, type TEXT)' ) ;
const addToDb = db . prepare (
'INSERT INTO issuesandprs (repository, title, number, state, created_at, closed_at, merged_at, html_url, user_login, user_html_url, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
) ;
export let issues : number = 0 ;
export let pulls : number = 0 ;
2022-07-13 15:41:41 +02:00
export const fetchIssues = async ( ) = > {
for await ( const repository of utilities . github . repositories ) {
let page = 1 ;
while ( true ) {
const res = await ( await fetch ( ` https://api.github.com/repos/ ${ repository } /issues?per_page=100&page= ${ page } &state=all ` , {
headers : {
'Content-Type' : 'application/json' ,
'User-Agent' : 'bun-discord-bot' ,
'Authorization' : ` token ${ config . api . github_personal_access_token } `
}
} ) ) . json ( ) as any ;
for ( const issue of res ) {
if ( 'pull_request' in issue ) continue ;
2022-07-13 17:49:09 +02:00
// @ts-expect-error it works
2022-07-13 17:54:04 +02:00
await addToDb . run ( [
2022-07-13 17:49:09 +02:00
issue . repository_url . replace ( 'https://api.github.com/repos/' , '' ) ,
issue . title ,
issue . number ,
issue . state ,
issue . created_at ,
issue . closed_at ,
null ,
issue . html_url ,
issue . user . login ,
issue . user . html_url ,
2022-07-13 18:28:59 +02:00
'(IS)'
2022-07-13 17:49:09 +02:00
] ) ;
issues ++ ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
Logger . debug ( ` Fetching issues for ${ repository } - ${ issues } * ${ page } ` ) ;
2022-07-13 15:41:41 +02:00
page ++ ;
if ( res . length === 0 ) {
break ;
}
}
2022-07-13 17:49:09 +02:00
Logger . success ( ` Issues have been fetched for ${ repository } - ${ issues } ` ) ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 22:18:14 +02:00
issues = null ;
Object . freeze ( issues ) ;
2022-07-13 15:41:41 +02:00
}
export const fetchPullRequests = async ( ) = > {
for await ( const repository of utilities . github . repositories ) {
let page = 1 ;
while ( true ) {
const res = await ( await fetch ( ` https://api.github.com/repos/ ${ repository } /pulls?per_page=100&page= ${ page } &state=all ` , {
headers : {
'Content-Type' : 'application/json' ,
'User-Agent' : 'bun-discord-bot' ,
'Authorization' : ` token ${ config . api . github_personal_access_token } `
}
} ) ) . json ( ) as any ;
for ( const pull of res ) {
2022-07-13 17:49:09 +02:00
// @ts-expect-error it works
2022-07-13 17:54:04 +02:00
await addToDb . run ( [
2022-07-13 17:49:09 +02:00
pull . html_url . replace ( 'https://github.com/' , '' ) . replace ( ` /pull/ ${ pull . number } ` , '' ) ,
pull . title ,
pull . number ,
pull . state ,
pull . created_at ,
pull . closed_at ,
pull . merged_at ,
pull . html_url ,
pull . user . login ,
pull . user . html_url ,
'(PR)'
] ) ;
pulls ++ ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
Logger . debug ( ` Fetching pull requests for ${ repository } - ${ pulls } * ${ page } ` ) ;
2022-07-13 15:41:41 +02:00
page ++ ;
if ( res . length === 0 ) {
break ;
}
}
2022-07-13 17:49:09 +02:00
Logger . success ( ` Pull requests have been fetched for ${ repository } - ${ pulls } ` ) ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 22:18:14 +02:00
pulls = null ;
Object . freeze ( pulls ) ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
export const setIssue = async ( issue : Issue ) = > {
const exists = await db . prepare ( ` SELECT * FROM issuesandprs WHERE number = ${ issue . number } AND repository = ' ${ issue . repository } ' ` ) . get ( ) ;
if ( typeof exists == 'object' ) {
db . exec ( ` UPDATE issuesandprs SET state = ' ${ issue . state } ', closed_at = ' ${ issue . closed_at } ', title = ' ${ issue . title } ' WHERE number = ${ issue . number } AND repository = ' ${ issue . repository } ' ` ) ;
} else {
// @ts-expect-error
addToDb . run ( [
2022-07-14 08:59:18 +02:00
issue . repository ,
2022-07-13 17:49:09 +02:00
issue . title ,
issue . number ,
issue . state ,
issue . created_at ,
issue . closed_at ,
null ,
issue . html_url ,
issue . user_login ,
issue . user_html_url ,
2022-07-13 18:28:59 +02:00
'(IS)'
2022-07-13 17:49:09 +02:00
] ) ;
}
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
export const setPullRequest = async ( pull : PullRequest ) = > {
const exists = await db . prepare ( ` SELECT * FROM issuesandprs WHERE number = ${ pull . number } AND repository = ' ${ pull . repository } ' ` ) . get ( ) ;
if ( typeof exists == 'object' ) {
db . exec ( ` UPDATE issuesandprs SET state = ' ${ pull . state } ', closed_at = ' ${ pull . closed_at } ', merged_at = ' ${ pull . merged_at } ', title = ' ${ pull . title } ' WHERE number = ${ pull . number } AND repository = ' ${ pull . repository } ' ` ) ;
} else {
// @ts-expect-error
addToDb . run ( [
2022-07-14 08:59:18 +02:00
pull . repository ,
2022-07-13 17:49:09 +02:00
pull . title ,
pull . number ,
pull . state ,
pull . created_at ,
pull . closed_at ,
pull . merged_at ,
pull . html_url ,
pull . user_login ,
pull . user_html_url ,
2022-07-13 18:28:59 +02:00
'(IS)'
2022-07-13 17:49:09 +02:00
] ) ;
}
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
export const deleteIssueOrPR = ( number : number , repository : string ) = > {
db . exec ( ` DELETE FROM issuesandprs WHERE repository = ' ${ repository } ' AND number = ${ number } ` ) ;
2022-07-13 15:41:41 +02:00
}
2022-07-13 17:49:09 +02:00
export const search = async ( query : string , repository : string ) : Promise < APIApplicationCommandOptionChoice [ ] > = > {
2022-07-13 15:41:41 +02:00
try {
2022-07-13 17:49:09 +02:00
const arrayFiltered = await db . prepare ( ` SELECT * FROM issuesandprs WHERE repository = ' ${ repository } ' ` ) . all ( ) ;
2022-07-13 15:41:41 +02:00
if ( ! query ) {
2022-07-13 17:49:09 +02:00
const array = arrayFiltered . slice ( 0 , 25 ) ;
2022-07-13 15:41:41 +02:00
return array . map ( ( issueOrPr : Issue | PullRequest ) = > new Object ( {
2022-07-14 08:13:18 +02:00
name : ` ${ issueOrPr . type } ${ formatEmojiStatus ( issueOrPr ) } ${ issueOrPr . title . slice ( 0 , 93 ) . replace ( discordChoicesRegex , '' ) } ` ,
2022-07-13 15:41:41 +02:00
value : issueOrPr.number.toString ( )
} ) ) as APIApplicationCommandOptionChoice [ ]
}
const searcher = new MiniSearch ( {
2022-07-13 18:28:59 +02:00
fields : query.startsWith ( '#' ) ? [ 'number' ] : [ 'title' ] ,
storeFields : [ 'title' , 'number' , 'type' , 'state' , 'merged_at' ] ,
2022-07-13 15:41:41 +02:00
searchOptions : {
fuzzy : 3 ,
processTerm : term = > term . toLowerCase ( ) ,
} ,
} ) ;
2022-07-13 17:49:09 +02:00
searcher . addAll ( arrayFiltered ) ;
2022-07-13 15:41:41 +02:00
const result = searcher . search ( query ) ;
return ( result as unknown as Issue [ ] | PullRequest [ ] ) . slice ( 0 , 25 ) . map ( ( issueOrPr : Issue | PullRequest ) = > new Object ( {
2022-07-14 08:13:18 +02:00
name : ` ${ issueOrPr . type } ${ formatEmojiStatus ( issueOrPr ) } ${ issueOrPr . title . slice ( 0 , 93 ) . replace ( discordChoicesRegex , '' ) } ` ,
2022-07-13 15:41:41 +02:00
value : issueOrPr.number.toString ( )
} ) ) as APIApplicationCommandOptionChoice [ ]
} catch ( e ) {
return [ ] ;
}
}
2022-07-13 17:49:09 +02:00
export const getIssueOrPR = async ( number : number , repository : string ) : Promise < Issue | PullRequest > = > {
const issueOrPR = await db . prepare ( ` SELECT * FROM issuesandprs WHERE repository = ' ${ repository } ' AND number = ${ number } ` ) . get ( ) ;
return issueOrPR ;
2022-07-13 18:28:59 +02:00
}
export const formatStatus = ( data : Issue | PullRequest ) = > {
let operation = '' ;
let timestamp = '' ;
switch ( data . state as 'open' | 'closed' | 'all' ) {
case 'open' :
operation = 'opened' ;
timestamp = ` <t: ${ Math . floor ( new Date ( data . created_at ) . getTime ( ) / 1000 ) } :R> ` ;
break ;
case 'closed' :
operation = ( data as PullRequest ) . merged_at ? 'merged' : 'closed' ;
timestamp = ( data as PullRequest ) . merged_at
? ` <t: ${ Math . floor ( new Date ( ( data as PullRequest ) . merged_at ) . getTime ( ) / 1000 ) } :R> `
: ` <t: ${ Math . floor ( new Date ( data . closed_at ) . getTime ( ) / 1000 ) } :R> ` ;
break ;
}
return ` ${ operation } ${ timestamp } ` ;
}
export const formatEmojiStatus = ( data : Issue | PullRequest ) = > {
let emoji = '' ;
switch ( data . state as 'open' | 'closed' | 'all' ) {
case 'open' :
emoji = '🟢' ;
break ;
case 'closed' :
emoji = '🔴' ;
break ;
}
2022-07-14 08:13:18 +02:00
if ( data . type === '(PR)' && ( data as PullRequest ) . merged_at ) emoji = '🟣' ;
2022-07-13 18:28:59 +02:00
return emoji ;
2022-07-13 15:41:41 +02:00
}