import { exec } from 'node:child_process'; import { overrideConsole } from 'nodejs-better-console'; import Eris from 'eris'; import { Command, Argument } from 'commander'; import KeyEvent from 'keyevent.json' assert { type: 'json' }; import { parse } from 'shell-quote'; import { botToken, botWhitelist, adbIp, adbPort } from './config.js'; overrideConsole(); const eris = new Eris( botToken, { intents: ['directMessages'] } ), adb = command => new Promise((resolve, reject) => exec( `adb -s ${adbIp}:${adbPort} shell ${command}`, ( error, stdout, stderr ) => { if(error || stderr) reject(error || stderr); else resolve(stdout.replace(/\r/g, '').trim()); } )), handleCommand = async command => { const program = new Command(''); let response; program.exitOverride(); program.configureOutput({ writeOut: output => response = { output, isRaw: true }, writeErr: output => response = { output, isRaw: true } }); program .command('shell') .description('Execute arbitrary shell command') .argument('', 'Arbitrary shell command') .action(async command => { response = { output: await adb(command), isRaw: true } }); program .command('home') .alias('h') .description('Go to home') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_HOME}`)); program .command('back') .alias('b') .description('Go back') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_BACK}`)); program .command('p') .description('Play/pause media') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}`)); program .command('play') .description('Play media') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_MEDIA_PLAY}`)); program .command('pause') .description('Pause media') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_MEDIA_PAUSE}`)); program .command('prev') .description('Previous media') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_MEDIA_PREVIOUS}`)); program .command('next') .description('Next media') .action(() => adb(`input keyevent ${KeyEvent.KEYCODE_MEDIA_NEXT}`)); program .command('type') .description('Type text') .argument('', 'Text to type') .action(text => adb(`input text ${text}`)); program .command('press') .description('Press key') .addArgument( new Argument('', 'Key to press') .choices(['tab', 'shift-tab', 'enter']) ) .action(key => adb(`input keyevent ${{ 'tab': KeyEvent.KEYCODE_TAB, 'enter': KeyEvent.KEYCODE_ENTER }[key]}`)); program .command('spotify') .description('Launch Spotify') .action(() => adb('monkey -p com.spotify.music 1')); program .command('mpv') .description('Launch MPV') .argument('', 'Media URL') .action(url => adb(`am start -a android.intent.action.VIEW -d ${url} -t video/any is.xyz.mpv`)); program .command('kiwi') .description('Launch Kiwi browser') .argument('', 'Website URL') .action(url => adb(`am start -a android.intent.action.VIEW -d ${url} -t text/plain com.kiwibrowser.browser`)); try { await program.parseAsync( parse(command), { from: 'user' } ); } catch(error){ if(!error.code.startsWith?.('commander.')) throw error; } return response; }; eris.on( 'error', error => console.error(error) ); eris.on( 'messageCreate', async message => { const { guildID, author: { id: authorID }, channel: { id: channelID }, id: messageID, content } = message, reply = async (content, isCodeBlock) => { const lines = content.split('\n'); let chunk = isCodeBlock ? '```\n' : ''; for(let lineIndex = 0; lineIndex < lines.length; lineIndex++){ const line = lines[lineIndex] + '\n', canAddLine = chunk.length + line.length + (isCodeBlock ? 3 : 0) <= 2000; if(canAddLine) chunk += line; if(!canAddLine || lineIndex === lines.length - 1){ if(isCodeBlock) chunk += '```'; await eris.createMessage( channelID, { content: chunk, messageReference: { messageID } } ); chunk = `\`\`\`\n${line}`; } } }; if(message.author.bot) return; if( guildID || !botWhitelist.includes(authorID) ) return message.addReaction('❌'); await message.addReaction('⏳'); try { const { output, isRaw } = await handleCommand(content) || {}; if(output) await reply(output, isRaw); await message.addReaction('✅'); } catch(error){ console.error(error); await message.addReaction('❌'); } } ); (async () => { await Promise.all([ eris.connect(), new Promise(resolve => eris.once('ready', resolve)) ]); console.log('Ready'); })().catch(error => console.error(error));