[init] Initialize repo & impl authlib

This commit is contained in:
方而静 2023-10-28 11:17:04 +08:00
commit bac5932ab8
Signed by: szTom
GPG Key ID: 072D999D60C6473C
8 changed files with 2395 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Node.js
node_modules/
# Editor
.vscode/
*.swp
# Credential
credential.json
session.json

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Compass Bot
## 登录
在项目根目录下创建 `credentials.json`,包含如下内容:
```json
{
"endpoint": "Yggdrasil API的调用点",
"username": "用户名",
"password": "密码"
}
```
## 运行
```sh
node index.js <server_ip:server_port> --version=<minecraft_version> --owner=<bot_owner>
```

109
authlib.js Normal file
View File

@ -0,0 +1,109 @@
const axios = require('axios');
const { readJsonFile, writeJsonFile } = require('./utils');
async function readCredentials() {
let credentials = await readJsonFile('credential.json');
if (!credentials.endpoint_auth) {
credentials.endpoint_auth = credentials.endpoint + '/authserver';
}
if (!credentials.endpoint_session) {
credentials.endpoint_session = credentials.endpoint + '/sessionserver';
}
if (!credentials.handle) {
credentials.handle = credentials.username;
}
return credentials;
}
async function readSessionCache() {
return readJsonFile('session.json');
}
async function storeSessionCache(data) {
return writeJsonFile('session.json', data);
}
async function yggdrailLogin(credentials) {
let account_info = (await axios.post(`${credentials.endpoint_auth}/authenticate`, {
username: credentials.handle,
password: credentials.password,
})).data;
let profile_info = null;
for (let info of account_info.availableProfiles) {
if (info.name == credentials.username) {
profile_info = info;
break;
}
}
let session_info = (await axios.post(`${credentials.endpoint_auth}/refresh`, {
accessToken: account_info.accessToken,
clientToken: account_info.clientToken,
selectedProfile: profile_info,
})).data;
return {
accessToken: session_info.accessToken,
clientToken: session_info.clientToken,
selectedProfile: session_info.selectedProfile,
};
}
async function yggdrasilAuth(credentials) {
if (credentials == null) {
credentials = await readCredentials();
}
let cache = await readSessionCache();
if (cache != null && cache.selectedProfile?.name != credentials.username) {
cache = null;
}
if (cache != null) {
let vres = await axios.post(`${credentials.endpoint_auth}/validate`, {
accessToken: cache.accessToken,
clientToken: cache.clientToken,
}, {
validateStatus: function (status) {
return status === 403 || status === 204;
}
});
if (vres.status == 403) {
cache = null;
}
}
let session_info = cache;
if (session_info == null) {
session_info = await yggdrailLogin(credentials);
storeSessionCache(session_info);
}
return session_info;
}
async function mineflayer() {
let credentials = await readCredentials();
let session_info = await yggdrasilAuth(credentials);
return {
username: credentials.username,
authServer: credentials.endpoint_auth,
sessionServer: credentials.endpoint_session,
auth: (client, options) => {
client.username = credentials.username;
client.session = session_info;
options.accessToken = session_info.accessToken;
options.clientToken = session_info.clientToken;
options.haveCredentials = true;
client.emit('session', session_info);
options.connect(client);
},
};
}
module.exports = { readCredentials, yggdrasilAuth, mineflayer };

21
control.js Normal file
View File

@ -0,0 +1,21 @@
const mineflayer = require('mineflayer');
const { pathfinder, Movements } = require('mineflayer-pathfinder');
const { GoalNear } = require('mineflayer-pathfinder').goals
function gotoOwner(bot, context) {
let owner = context.owner();
if (!owner) {
console.log('Owner is not configured or is offline');
return false;
}
if (!owner.entity) {
console.log('Owner is out of sight');
return false;
}
let pos = owner.entity.position;
bot.pathfinder.setMovements(context.peaceful_tactic);
bot.pathfinder.setGoal(new GoalNear(pos.x, pos.y, pos.z, 1))
}
module.exports = { gotoOwner };

75
index.js Normal file
View File

@ -0,0 +1,75 @@
const authlib = require('./authlib');
const mineflayer = require('mineflayer');
const { pathfinder, Movements } = require('mineflayer-pathfinder');
const { GoalNear } = require('mineflayer-pathfinder').goals
const repl = require('repl');
const domain = require('domain');
const yargs = require('yargs');
const { parseURL } = require('./utils');
const args = yargs.option('protocal', {
description: 'minecraft server version',
type: 'string',
requiresArg: false,
}).option('owner', {
description: 'bot owner name',
type: 'string',
requiresArg: false
}).help().alias('help', 'h').argv;
const [host, port] = parseURL(args._[0]);
const version = args.protocal;
const repl_domain = domain.create();
repl_domain.on('error', (err) => {
console.error('Caught error:', err);
});
async function main() {
let authinfo = await authlib.mineflayer();
let bot = mineflayer.createBot({
host, port, version,
...authinfo,
});
bot.loadPlugin(pathfinder);
bot.on('kicked', console.warn);
bot.on('error', console.warn);
bot.once('spawn', () => {
repl_domain.run(() => {
let r = repl.start({
prompt: "bot > ",
input: process.stdin,
output: process.stdout,
color: true,
terminal: true,
});
const deafult_tactic = new Movements(bot);
const peaceful_tactic = new Movements(bot);
peaceful_tactic.canDig = false;
peaceful_tactic.scafoldingBlocks = [];
r.context.deafult_tactic = deafult_tactic;
r.context.peaceful_tactic = peaceful_tactic;
r.context.bot = bot;
r.context.authinfo = authinfo;
r.context.Movements = Movements;
r.context.GoalNear = GoalNear;
r.context.mineflayer = mineflayer;
r.context.owner = () => {
if (!args.owner) {
return null;
}
return bot.players[args.owner];
};
r.context.control = require('./control');
r.context.Vec3 = require('vec3').Vec3;
});
});
}
main();

2130
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"axios": "^1.6.0",
"mineflayer": "^4.14.0",
"mineflayer-pathfinder": "^2.4.5",
"prismarine-viewer": "^1.25.0",
"yargs": "^17.7.2"
}
}

22
utils.js Normal file
View File

@ -0,0 +1,22 @@
const fs = require('fs/promises');
async function readJsonFile(path) {
try {
const data = await fs.readFile(path, 'utf8');
return JSON.parse(data);
} catch (error) {
return null;
}
}
function writeJsonFile(path, data) {
const json_string = JSON.stringify(data);
return fs.writeFile(path, json_string, 'utf8');
}
function parseURL(url) {
const [host, port] = url.split(':');
return [host, port ? parseInt(port) : undefined];
}
module.exports = { readJsonFile, writeJsonFile, parseURL };