@@ -47,6 +47,7 @@ const voting = require('./lib/voting');
4747const musicHelper = require ( './lib/music-helper' ) ;
4848const commandHandlers = require ( './lib/command-handlers' ) ;
4949const addHandlers = require ( './lib/add-handlers' ) ;
50+ const githubApp = require ( './lib/github-app' ) ;
5051const gongMessage = fs . readFileSync ( 'templates/messages/gong.txt' , 'utf8' ) . split ( '\n' ) . filter ( Boolean ) ;
5152const voteMessage = fs . readFileSync ( 'templates/messages/vote.txt' , 'utf8' ) . split ( '\n' ) . filter ( Boolean ) ;
5253const ttsMessage = fs . readFileSync ( 'templates/messages/tts.txt' , 'utf8' ) . split ( '\n' ) . filter ( Boolean ) ;
@@ -4744,31 +4745,57 @@ async function _featurerequest(input, channel, userName) {
47444745
47454746 const featureDescription = input . slice ( 1 ) . join ( ' ' ) ;
47464747
4747- // Check if githubToken is configured
4748- const githubToken = config . get ( 'githubToken' ) ;
4749- if ( ! githubToken ) {
4750- logger . warn ( '[FEATUREREQUEST] githubToken not configured' ) ;
4751- _slackMessage (
4752- '❌ *Feature request not configured*\n\n' +
4753- 'To enable this feature, you need a GitHub Personal Access Token:\n\n' +
4754- '1. Go to: https://github.com/settings/tokens\n' +
4755- '2. Click *"Generate new token (classic)"*\n' +
4756- '3. Select scope: `repo` (or `public_repo` for public repos only)\n' +
4757- '4. Set the token via admin command:\n' +
4758- ' `setconfig githubToken ghp_xxxxxxxxxxxx`\n\n' +
4759- '📖 More info: https://github.com/htilly/SlackONOS#configuration' ,
4760- channel
4761- ) ;
4762- return ;
4748+ // Try GitHub App first, fallback to personal access token
4749+ let authToken = null ;
4750+ let authMethod = null ;
4751+
4752+ try {
4753+ const appToken = await githubApp . getGitHubAppToken ( ) ;
4754+ if ( appToken ) {
4755+ authToken = appToken ;
4756+ authMethod = 'GitHub App' ;
4757+ logger . info ( '[FEATUREREQUEST] Using GitHub App authentication' ) ;
4758+ }
4759+ } catch ( error ) {
4760+ logger . warn ( `[FEATUREREQUEST] GitHub App auth failed: ${ error . message } , falling back to personal token` ) ;
4761+ }
4762+
4763+ // Fallback to personal access token
4764+ if ( ! authToken ) {
4765+ const githubToken = config . get ( 'githubToken' ) ;
4766+ if ( ! githubToken ) {
4767+ logger . warn ( '[FEATUREREQUEST] No GitHub authentication configured' ) ;
4768+ _slackMessage (
4769+ '❌ *Feature request not configured*\n\n' +
4770+ 'To enable this feature, configure either:\n\n' +
4771+ '*Option 1: GitHub App (Recommended)*\n' +
4772+ '1. Create GitHub App: https://github.com/settings/apps/new\n' +
4773+ '2. Set permissions: Issues: Write\n' +
4774+ '3. Install on repository\n' +
4775+ '4. Configure via admin commands:\n' +
4776+ ' `setconfig githubAppId 2741767`\n' +
4777+ ' `setconfig githubAppPrivateKey /path/to/private-key.pem`\n' +
4778+ ' `setconfig githubAppInstallationId 106479987`\n\n' +
4779+ '*Option 2: Personal Access Token*\n' +
4780+ '1. Go to: https://github.com/settings/tokens\n' +
4781+ '2. Generate new token (classic) with `repo` scope\n' +
4782+ '3. `setconfig githubToken ghp_xxxxxxxxxxxx`\n\n' +
4783+ '📖 More info: https://github.com/htilly/SlackONOS#configuration' ,
4784+ channel
4785+ ) ;
4786+ return ;
4787+ }
4788+ authToken = githubToken ;
4789+ authMethod = 'Personal Access Token' ;
47634790 }
47644791
47654792 try {
4766- logger . info ( `[FEATUREREQUEST] Creating GitHub issue: ${ featureDescription } ` ) ;
4793+ logger . info ( `[FEATUREREQUEST] Creating GitHub issue: ${ featureDescription } (using ${ authMethod } ) ` ) ;
47674794 // Create GitHub issue with enhancement label
47684795 const response = await fetch ( `https://api.github.com/repos/htilly/SlackONOS/issues` , {
47694796 method : 'POST' ,
47704797 headers : {
4771- 'Authorization' : `token ${ githubToken } ` ,
4798+ 'Authorization' : `Bearer ${ authToken } ` ,
47724799 'Accept' : 'application/vnd.github+json' ,
47734800 'Content-Type' : 'application/json'
47744801 } ,
@@ -4786,11 +4813,44 @@ async function _featurerequest(input, channel, userName) {
47864813 } else {
47874814 const errorText = await response . text ( ) ;
47884815 logger . error ( `[FEATUREREQUEST] GitHub API error: ${ response . status } - ${ errorText } ` ) ;
4816+
4817+ // Handle specific error cases
4818+ if ( response . status === 401 ) {
4819+ // Bad credentials - token is invalid or expired
4820+ if ( authMethod === 'GitHub App' ) {
4821+ _slackMessage (
4822+ '❌ *GitHub App authentication failed*\n\n' +
4823+ 'The GitHub App configuration is invalid. Please check:\n\n' +
4824+ '1. App ID is correct\n' +
4825+ '2. Private key file path is correct and readable\n' +
4826+ '3. Installation ID is correct\n' +
4827+ '4. App is installed on the repository\n\n' +
4828+ 'Or use a Personal Access Token as fallback.' ,
4829+ channel
4830+ ) ;
4831+ } else {
4832+ _slackMessage (
4833+ '❌ *GitHub token invalid or expired*\n\n' +
4834+ 'The configured GitHub token is not valid. Please:\n\n' +
4835+ '1. Go to: https://github.com/settings/tokens\n' +
4836+ '2. Generate a new token (classic) with `repo` scope\n' +
4837+ '3. Update the token via admin command:\n' +
4838+ ' `setconfig githubToken ghp_xxxxxxxxxxxx`\n\n' +
4839+ '📖 More info: https://github.com/htilly/SlackONOS#configuration' ,
4840+ channel
4841+ ) ;
4842+ }
4843+ return ;
4844+ }
4845+
47894846 throw new Error ( `GitHub API error: ${ response . status } - ${ errorText } ` ) ;
47904847 }
47914848 } catch ( err ) {
47924849 logger . error ( `[FEATUREREQUEST] Failed to create issue: ${ err . message } ` , err ) ;
4793- _slackMessage ( `❌ Failed to create feature request: ${ err . message } ` , channel ) ;
4850+ // Only show generic error if we haven't already handled it above
4851+ if ( err . message && ! err . message . includes ( '401' ) ) {
4852+ _slackMessage ( `❌ Failed to create feature request: ${ err . message } ` , channel ) ;
4853+ }
47944854 }
47954855}
47964856
@@ -4941,7 +5001,10 @@ async function _setconfig(input, channel, userName) {
49415001 crossfadeEnabled : { type : 'boolean' } ,
49425002 slackAlwaysThread : { type : 'boolean' } ,
49435003 logLevel : { type : 'string' , minLen : 4 , maxLen : 5 , allowed : [ 'error' , 'warn' , 'info' , 'debug' ] } ,
4944- githubToken : { type : 'string' , minLen : 4 , maxLen : 100 , sensitive : true }
5004+ githubToken : { type : 'string' , minLen : 4 , maxLen : 100 , sensitive : true } ,
5005+ githubAppId : { type : 'string' , minLen : 1 , maxLen : 20 } ,
5006+ githubAppPrivateKey : { type : 'string' , minLen : 50 , maxLen : 5000 , sensitive : true } ,
5007+ githubAppInstallationId : { type : 'string' , minLen : 1 , maxLen : 20 }
49455008 } ;
49465009
49475010 // Make config key case-insensitive
0 commit comments