30 Commits

Author SHA1 Message Date
Plagiatus 264c4c008d single-escape? 2022-11-27 17:18:08 +02:00
Lukas Scheuerle 6e93d2526a try escaping again 2022-11-25 19:02:25 +02:00
Lukas Scheuerle 5f49386817 attempt Json text escape 2022-11-25 18:46:23 +02:00
Lukas Scheuerle cdf7368cc8 Update main.yml 2022-11-23 13:02:44 +02:00
Lukas Scheuerle eeee33e584 Update main.yml 2022-11-23 13:01:35 +02:00
Lukas Scheuerle a8108f8532 Update main.yml 2022-11-23 13:00:33 +02:00
Lukas Scheuerle 57082ee51f Update main.yml 2022-11-23 12:56:45 +02:00
Lukas Scheuerle 5dd225a0cc Update main.yml 2022-11-23 12:55:02 +02:00
Lukas Scheuerle b3d32d4c2b Update main.yml 2022-11-23 12:54:12 +02:00
Lukas Scheuerle a57fcabf73 update to node 16 2022-11-23 10:41:45 +02:00
Lukas Scheuerle 7ed3e250df Update main.yml 2022-11-22 15:51:54 +02:00
Lukas Scheuerle 453a0856d6 update branding 2021-11-22 12:29:20 +02:00
Lukas Scheuerle 90cf5a2fe8 clarify it's about minecraft 2021-11-22 12:10:28 +02:00
Lukas Scheuerle 27e65856d5 add icon and color 2021-11-22 12:09:19 +02:00
Lukas Scheuerle 8457214b63 add LICENSE 2021-11-22 12:00:55 +02:00
Lukas Scheuerle d0c8ef4632 Create LICENSE 2021-11-22 11:32:36 +02:00
Lukas Scheuerle (Plagiatus) 6a758bf3f0 add credits to readme 2021-03-16 12:32:55 +01:00
Lukas Scheuerle 119adfd57b Create README.md 2021-03-16 01:37:56 +01:00
Lukas Scheuerle e651cd4edc Update README.md 2021-03-16 01:37:27 +01:00
Lukas Scheuerle 2f0807172e update readme to include a small bit of instructions. 2021-03-16 01:26:39 +01:00
Lukas Scheuerle (Plagiatus) 58d5eaad1c update to v0.1, including readme 2021-03-16 01:09:34 +01:00
Lukas Scheuerle (Plagiatus) 497b2b5ea9 switch from array to json string array 2021-03-16 00:44:24 +01:00
Lukas Scheuerle (Plagiatus) 44dc459b7f fix main.yml 2021-03-16 00:40:53 +01:00
Lukas Scheuerle (Plagiatus) c6d699713f updated main.yml 2021-03-16 00:39:59 +01:00
Lukas Scheuerle (Plagiatus) 1a17b82888 fix yaml indentation error 2021-03-16 00:33:25 +01:00
Lukas Scheuerle (Plagiatus) ab17b43b2a added RCON connection 2021-03-16 00:31:11 +01:00
Lukas Scheuerle (Plagiatus) 51ef0c18bf fix indentation in yml? 2021-03-15 23:15:11 +01:00
Lukas Scheuerle 3ad0509e8f Update main.yml 2021-03-15 23:10:16 +01:00
Lukas Scheuerle 7925a40d37 Update main.yml 2021-03-15 23:04:52 +01:00
Lukas Scheuerle 61b684e74f Create main.yml 2021-03-15 23:02:19 +01:00
11 changed files with 423 additions and 89 deletions
+23
View File
@@ -0,0 +1,23 @@
name: RCON Test
on:
push:
branches: [ env-variables ]
workflow_dispatch:
env:
DAY_OF_WEEK: Monday
jobs:
rcon_test:
runs-on: ubuntu-latest
name: RCON Test Dev
steps:
- name: RCON Connection
uses: Plagiatus/RCON-Action@v0.2
with:
server: ${{ secrets.rcon_server }}
port: ${{ secrets.rcon_port }}
password: ${{ secrets.rcon_password }}
commands: '["tellraw @a [{\"text\":\"${{env.DAY_OF_WEEK}}\"}]", "say ref: ${{ github.ref }}", "say ip ${{ secrets.rcon_server }}", "say actor ${{ github.actor }}", "reload"]'
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Lukas Scheuerle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+51 -1
View File
@@ -1,3 +1,53 @@
# RCON-Action
Integrate RCON actions into your github workflow
Integrate RCON actions into your github workflow.
Originally created and intended to be used for a more automated use of minecraft datapacks, in tandem with the [FTP Deploy](https://github.com/marketplace/actions/ftp-deploy) action. See [this file](https://github.com/Plagiatus/YeggsMapjam2021/blob/main/.github/workflows/main.yml) for a working example of a combination of these two.
To set up with your Minecraft Server via FTP, copy the [linked example file](https://github.com/Plagiatus/YeggsMapjam2021/blob/main/.github/workflows/main.yml) to `your_repository/.github/workflow/main.yml`, update and add the [secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) to your repository and you're ready to go. That is if your server has both FTP and RCON enabled.
## Example Action File
[File in this repository](https://github.com/Plagiatus/RCON-Action/blob/main/.github/workflows/main.yml)
```yml
name: RCON Test
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
rcon_test:
runs-on: ubuntu-latest
name: RCON Test
steps:
- name: RCON Connection
uses: Plagiatus/RCON-Action@v0.1
with:
server: ${{ secrets.rcon_server }}
port: ${{ secrets.rcon_port }}
password: ${{ secrets.rcon_password }}
commands: '["say hi", "reload"]'
```
## Available Arguments
| name | required | description | default | example |
|-|-|-|-|-|
| server | yes | The server IP | | `127.0.0.1` |
| port | no | The RCON port | `25575` | `12345` |
| password | no | The password | `""` | `Sj3%ka"l` |
| commands | no | Additional commands you want to run, in the form of a JSON String Array | | `'["say hi", "reload"]'` |
| send-push-info | no | Whether a tellraw informing about the push should be sent | `true` | |
| push-info-recipient | no | Changes the selector of the push info tellraw. | `@a` | `@a[tag=admin]` |
It is highly recommended to use [github secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) instead of plaintext inclusions for more sensitive data like the IP or the password.
## Credits
- Node-RCON taken from https://github.com/tehbeard/node-rcon by @tehbeard.
- RCON-Action made by @Plagiatus
+28 -10
View File
@@ -1,13 +1,31 @@
name: Hello World
description: Greet someone and record the time
name: Send RCON commands to minecraft server
description: Send command(s) via RCON connection to a RCON enabled minecraft server
inputs:
who-to-greet:
server:
required: true
description: Who to greet
default: World
outputs:
time:
description: The time we greeted you
description: The server IP address
port:
required: false
description: The server's RCON port
default: 25575
password:
required: false
description: The RCON password to authenticate
default: ""
commands:
required: true
description: Additional commands to send
send-push-info:
required: false
description: Sends information about the push to the server as a tellraw
default: true
push-info-recipient:
required: false
description: Changes the selector of who should be getting the server info.
default: "@a"
runs:
using: node12
main: index.js
using: node16
main: index.js
branding:
icon: arrow-right-circle
color: green
+40 -12
View File
@@ -1,16 +1,44 @@
const core = require("@actions/core")
const github = require("@actions/github")
const core = require("@actions/core");
const github = require( "@actions/github");
const rcon = require( "./node-rcon/RCON.js" );
try {
const nameToGreet = core.getInput("who-to-greet");
console.log("Hello", nameToGreet);
const time = (new Date()).toTimeString();
core.setOutput("time", time);
run()
async function run() {
console.log("💚 thank you for using RCON-Action ( https://github.com/Plagiatus/RCON-Action ).");
console.log("⭐ if you find this helpful, why not drop a star on the repo? Much appreciated!");
console.log("❓ if you have any questions or issues, please let us know by writing an issue");
const payload = JSON.stringify(github.context.payload, undefined, 2);
console.log("The event payload", payload);
} catch (error) {
core.setFailed(error.message)
try {
const inputs = {
server: core.getInput("server"),
port: core.getInput("port"),
password: core.getInput("password"),
commands: JSON.parse(core.getInput("commands")),
sendPushInfo: core.getInput("send-push-info"),
pushInfoRecipient: core.getInput("push-info-recipient"),
}
const payload = JSON.stringify(github.context.payload, undefined, 2);
// console.log(inputs);
// console.log(payload);
const srvr = new rcon();
console.log("Connecting to server via RCON...");
await srvr.connect(inputs.server, inputs.port, inputs.password)
console.log("🟢 Connected and authenticated.");
if (inputs.sendPushInfo) {
await srvr.send(`tellraw ${inputs.pushInfoRecipient} [{"text":"[GitHub] New push from ","color":"gold"},{"text":"${github.context.payload.sender.login}","color":"yellow"},{"text":" detected."}]`)
}
if (inputs.commands.length > 0) {
for (let command of inputs.commands) {
await srvr.send(command);
}
console.log(`📬 Sent all ${inputs.commands.length} additional commands to the server.`)
}
srvr.end();
} catch (error) {
core.setFailed(error.message)
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Tehbeard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+76
View File
@@ -0,0 +1,76 @@
/**
* Functions for managing packets used by the RCON protocol
*/
class Packet {
/**
* Generate a unique max 32 bit ID integer
* @return {Number} Generated ID
*/
static id () {
return Number.parseInt(Math.random().toString(2).substring(2, 32), 2);
}
/**
* Create a new packet buffer with given ID, type and payload
* @param {Number} id A unique max 32 bit client generated request ID
* @param {[type]} type Type of the packet
* @param {String} payload Data to be sent encoded in 'ASCII'
* @return {Buffer} Created packet
*/
static write (id, type, payload) {
// Length of payload in bytes when encoded in ASCII
const length = Buffer.byteLength(payload, 'ascii');
// 14 bytes for the length, ID, type and 2-byte padding
const buffer = Buffer.allocUnsafe(14 + length);
// Offsets are hardcoded for speed
buffer.writeInt32LE(10 + length, 0); // Length
buffer.writeInt32LE(id, 4); // Request ID
buffer.writeInt32LE(type, 8); // Type
buffer.write(payload, 12, 'ascii'); // Payload
buffer.writeInt16LE(0, 12 + length); // Two null bytes of padding
return buffer;
}
/**
* Parse the given packet into a JSON object
* @param {Buffer} packet
* @return {Object} Parsed packet
*/
static read (packet) {
// Length of the rest of the packet
const length = packet.readInt32LE(0);
// Check if we have a valid packet with 2 null bytes of padding in the end
if (packet.length === 4 + length && !packet.readInt16LE(packet.length - 2)) {
// Offsets are hardcoded for speed
return {
length: length,
id: packet.readInt32LE(4),
type: packet.readInt32LE(8),
payload: packet.toString('ascii', 12, packet.length - 2)
};
} else {
throw new Error(`Invalid packet! [${packet}]`);
}
}
}
/**
* RCON packet type Integers understood by the Mineccraft Server
* https://wiki.vg/RCON#Packets
* @type {Object}
*/
Packet.type = {
AUTH: 3,
AUTH_RES: 2,
COMMAND: 2,
COMMAND_RES: 0,
// Invalid type that can be used to detect when the full response has been received
COMMAND_END: 255
};
/**
* Predefined responses the Minecraft Server can send
* @type {Object}
*/
Packet.payload = {
// Response the server sends after receiving a packet with above invalid type
COMMAND_END: `Unknown request ${Packet.type['COMMAND_END'].toString(16)}`
};
module.exports = Packet;
+140
View File
@@ -0,0 +1,140 @@
const net = require('net');
const EventEmitter = require('events');
const Packet = require('./Packet.js');
/**
* Used to create and send commands through a console using the RCON protocol
*/
module.exports = class RCON {
/**
* @param {Number} [timeout=3000] Timeout for connections and responses
*/
constructor (timeout = 3000) {
this.timeout = timeout;
this.online = false;
this.authenticated = false;
this.queue = {
drained: true,
sending: [],
pending: {}
};
this.events = new EventEmitter()
.on('removeListener', (id, listener) => {
clearTimeout(this.queue.pending[id].timer);
delete this.queue.pending[id];
})
.on('newListener', (id, listener) => {
this.queue.pending[id] = {
timer: setTimeout(() => {
this.events.emit(id, new Error(`Packet timed out!`));
this.events.off(id, listener);
}, this.timeout),
payloads: []
};
});
}
/**
* Ends the connection, but doesn't destroy it
*/
end () {
this.socket.end();
}
/**
* Immediately sends out as many queued packets as possible
*/
drain () {
while (this.queue.drained && this.queue.sending.length > 0) {
this.queue.drained = this.socket.write(this.queue.sending.shift());
}
}
/**
* Connects and authenticates the RCON instance
* @param {String} server IP from where to find the server
* @param {Number} port Port from where to connect
* @param {String} password Password with which to connect
* @return {Promise} Resolves if succesful or Rejects an Error if one occured
*/
connect (server, port, password) {
return new Promise((resolve, reject) => {
let data = Buffer.allocUnsafe(0);
this.socket = net.connect({ host: server, port: port })
.on('data', (chunk) => {
data = Buffer.concat([data, chunk]);
try {
let length = data.readInt32LE(0);
while (data.length >= 4 + length) {
const response = Packet.read(data.slice(0, 4 + length));
this.events.emit('' + response.id, response);
data = data.slice(4 + length);
length = data.readInt32LE(0);
}
} catch (error) { /* We don't have enough data yet */ }
})
.on('drain', () => { this.drain(); })
.on('close', () => { this.authenticated = false; this.online = false; })
.on('error', error => { throw error; })
.setTimeout(this.timeout, () => {
this.socket.destroy();
reject(new Error(`Socket timed out when connecting to [${server}:${port}]`));
})
.once('connect', () => {
this.online = true;
this.socket.setTimeout(0);
// Send and process authentication packet
this.socket.write(Packet.write(0, Packet.type['AUTH'], password));
this.events.on(0, result => {
if (result instanceof Error) {
reject(result);
} else {
if (result.type !== Packet.type['AUTH_RES']) {
reject(new Error(`Packet is of wrong type! [${result.type}]`));
} else {
this.events.removeAllListeners(0);
if (result.id === -1) {
reject(new Error(`Authentication failed!`));
} else {
this.authenticated = true;
resolve();
}
}
}
});
});
});
}
/**
* Send the given command through authenticated RCON connection
* @param {String} command
* @return {Promise} Resolves to response or Rejects an Error if one occured
*/
send (command) {
if (!this.online || !this.authenticated) {
return Promise.reject(new Error('The connection needs to be made and authenticated first!'));
} else {
return new Promise((resolve, reject) => {
const id = Packet.id();
this.queue.sending.push(Packet.write(id, Packet.type['COMMAND'], command));
this.queue.sending.push(Packet.write(id, Packet.type['COMMAND_END'], ''));
this.drain();
this.events.on(id, result => {
if (result instanceof Error) {
reject(result);
} else {
if (result.type !== Packet.type['COMMAND_RES']) {
reject(new Error(`Packet is of wrong type! [${result.type}]`));
} else {
if (result.payload === Packet.payload['COMMAND_END']) {
const response = this.queue.pending[result.id].payloads
.reduce((data, chunk) => data + chunk);
this.events.removeAllListeners(id);
resolve(response);
} else {
this.queue.pending[result.id].payloads.push(result.payload);
}
}
}
});
});
}
}
};
+1
View File
@@ -0,0 +1 @@
node-rcon by https://github.com/tehbeard/node-rcon
+22
View File
@@ -0,0 +1,22 @@
var RCON = require('../RCON');
var rcon = new RCON();
rcon.connect('localhost', 22575, 'supersecret')
.then(() => {
console.log('Connected and authenticated.');
return rcon.send('/op superman');
})
.then(response => {
console.log(`Result of op: ${response}`);
})
.catch(error => {
console.error(`An error occured: ${error}`);
});
rcon.send('/deop superman')
.then(response => {
console.log(`Result of deop: ${response}`);
})
.catch(error => {
console.error(`An error occured: ${error}`);
});
-66
View File
@@ -1,66 +0,0 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}