Home / Developers / Minecraft Bedrock Edition
πŸͺ¨

Minecraft Bedrock Edition

PHP 8.1 PocketMine-MP

Overview

Reward your Bedrock Edition players for voting on topgservers. This PocketMine-MP plugin polls the vote-check API to detect new votes and grants in-game rewards automatically. It supports both polling and webhook-based approaches.

Prerequisites

  • A Minecraft Bedrock server running PocketMine-MP (5.0+)
  • PHP 8.1 or newer
  • topgservers API key β€” generate one in My Servers β†’ API
  • Webhook secret (optional, for real-time rewards)
  • PHP cURL extension enabled

Installation

1 Create the plugin structure

Create a `TopGVote` folder inside `plugins/` with the standard PocketMine layout.

Directory structure
plugins/TopGVote/
β”œβ”€β”€ src/topgservers/vote/
β”‚   β”œβ”€β”€ Main.php
β”‚   └── VoteCheckTask.php
β”œβ”€β”€ plugin.yml
└── resources/
    └── config.yml

2 Create plugin.yml

Register the plugin with PocketMine.

plugin.yml
name: TopGVote
version: 1.0.0
api: 5.0.0
main: topgservers\vote\Main
description: topgservers vote reward integration
author: topgservers
commands:
  votestatus:
    description: Check your vote status
    usage: /votestatus
    permission: topgvote.check
permissions:
  topgvote.check:
    default: true

3 Install the plugin

Copy the `TopGVote/` folder into your server's `plugins/` directory and restart the server. PocketMine will load it automatically.

Configuration

resources/config.yml
# topgservers Vote Plugin Configuration
api-key: "tgs_your_api_key_here"
webhook-secret: "whsec_your_secret_here"

# Poll interval in seconds
poll-interval: 60

# Reward settings
rewards:
  - command: "give {player} diamond 3"
  - command: "give {player} emerald 5"

# Message sent after vote reward
reward-message: "Β§aThanks for voting on topgservers! Β§7Check your inventory."

Vote Check

Call the /api/v1/vote-check endpoint to determine if a player has voted today.

VoteCheckTask.php
<?php
declare(strict_types=1);

namespace topgservers\vote;

use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;

class VoteCheckTask extends AsyncTask
{
    public function __construct(
        private string $apiKey,
        private string $playerName,
    ) {}

    public function onRun(): void
    {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => 'https://topgservers.net/api/v1/vote-check?username='
                . urlencode($this->playerName),
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->apiKey,
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode === 200) {
            $data = json_decode($response, true);
            $this->setResult($data['voted'] ?? false);
        } else {
            $this->setResult(false);
        }
    }

    public function onCompletion(): void
    {
        $voted = $this->getResult();
        if (!$voted) return;

        $server = Server::getInstance();
        $player = $server->getPlayerExact($this->playerName);
        if ($player === null) return;

        // Dispatch reward commands
        $plugin = $server->getPluginManager()->getPlugin('TopGVote');
        if ($plugin instanceof Main) {
            $plugin->grantReward($player);
        }
    }
}

Webhook Receiver

Verify the X-TopG-Signature header using HMAC-SHA256 to ensure webhook payloads are authentic.

Webhook verification (PHP)
<?php
// Standalone webhook endpoint β€” run separately from PocketMine
// e.g., php -S 0.0.0.0:8090 webhook.php

$webhookSecret = 'whsec_your_secret_here';

$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_TOPG_SIGNATURE'] ?? '';

if (!verifySignature($body, $signature, $webhookSecret)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($body, true);
$username = $data['username'] ?? '';

if ($username) {
    // Write to a shared file or database that PocketMine reads
    $votesFile = __DIR__ . '/pending_votes.json';
    $votes = file_exists($votesFile)
        ? json_decode(file_get_contents($votesFile), true)
        : [];
    $votes[$username] = date('Y-m-d');
    file_put_contents($votesFile, json_encode($votes));
}

http_response_code(200);

function verifySignature(
    string $body,
    string $sigHeader,
    string $secret
): bool {
    $parts = [];
    foreach (explode(',', $sigHeader) as $part) {
        [$key, $value] = explode('=', $part, 2);
        $parts[$key] = $value;
    }

    $timestamp = $parts['t'] ?? '';
    $received  = $parts['v1'] ?? '';

    $expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);

    return hash_equals($expected, $received);
}

Reward Examples

Main.php β€” reward logic
<?php
declare(strict_types=1);

namespace topgservers\vote;

use pocketmine\player\Player;
use pocketmine\plugin\PluginBase;
use pocketmine\scheduler\ClosureTask;

class Main extends PluginBase
{
    private array $rewarded = [];

    public function onEnable(): void
    {
        $this->saveDefaultConfig();

        $interval = $this->getConfig()->get('poll-interval', 60) * 20; // ticks
        $apiKey = $this->getConfig()->get('api-key', '');

        $this->getScheduler()->scheduleRepeatingTask(
            new ClosureTask(function () use ($apiKey): void {
                foreach ($this->getServer()->getOnlinePlayers() as $player) {
                    $name = $player->getName();
                    if (isset($this->rewarded[$name])) continue;

                    $this->getServer()->getAsyncPool()->submitTask(
                        new VoteCheckTask($apiKey, $name)
                    );
                }
            }),
            $interval
        );
    }

    public function grantReward(Player $player): void
    {
        $name = $player->getName();
        $this->rewarded[$name] = true;

        $rewards = $this->getConfig()->get('rewards', []);
        foreach ($rewards as $reward) {
            $cmd = str_replace('{player}', $name, $reward['command']);
            $this->getServer()->dispatchCommand(
                $this->getServer()->getConsoleSender(),
                $cmd
            );
        }

        $msg = $this->getConfig()->get('reward-message', 'Thanks for voting!');
        $player->sendMessage($msg);
    }
}

Notes & Tips

PocketMine's AsyncTask system ensures HTTP requests don't block the server tick. The `$rewarded` array resets on plugin reload. For persistence, save to a JSON file in the plugin's data folder. Bedrock player names are their Xbox gamertags β€” they must match their topgservers username exactly.