229 lines
No EOL
7.9 KiB
JavaScript
229 lines
No EOL
7.9 KiB
JavaScript
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 AsciiTable from 'ascii-table';
|
|
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('<command>', '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>', 'Text to type')
|
|
.action(text => adb(`input text ${text}`));
|
|
program
|
|
.command('press')
|
|
.description('Press key')
|
|
.addArgument(
|
|
new Argument('<key>', '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('<url>', '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('<url>', 'Website URL')
|
|
.action(url => adb(`am start -a android.intent.action.VIEW -d ${url} -t text/plain com.kiwibrowser.browser`));
|
|
program
|
|
.command('notifications')
|
|
.description('Show notification history')
|
|
.action(async () => {
|
|
const items = [];
|
|
let item;
|
|
for(let line of (await adb('dumpsys notification --noredact')).split('\n')){
|
|
line = line.trim();
|
|
if(line.startsWith('NotificationRecord('))
|
|
items.push(item = { app: line.slice(35).split(' ')[0] });
|
|
if(line.startsWith('android.title='))
|
|
item.title = line.slice(line.indexOf('(') + 1, -1);
|
|
if(line.startsWith('android.text='))
|
|
item.text = line.slice(line.indexOf('(') + 1, -1);
|
|
if(line.startsWith('mCreationTimeMs='))
|
|
item.date = parseInt(line.slice(16));
|
|
}
|
|
items.sort((a, b) => b.date - a.date);
|
|
response = {
|
|
output: new AsciiTable()
|
|
.setHeading('App', 'Time', 'Content')
|
|
.addRowMatrix(items
|
|
.sort((a, b) => b.date - a.date)
|
|
.map(item => [
|
|
item.app,
|
|
new Date(item.date).toLocaleTimeString(),
|
|
`${item.title}${item.title.length + item.text.length < 50 ? ` · ${item.text}` : ''}`
|
|
])
|
|
).toString(),
|
|
isRaw: true
|
|
};
|
|
});
|
|
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)),
|
|
new Promise(resolve => exec(`adb connect ${adbIp}:${adbPort}`, resolve))
|
|
]);
|
|
console.log('Ready');
|
|
})().catch(error => console.error(error)); |