Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/modules/gameplay/models/TreasureFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class TreasureFinder extends \yii\base\Model
public static function findByEncryptedCode($secretKey, $string)
{
return \Yii::$app->db->createCommand("
SELECT treasure.id as treasure_id, player.id AS player_id
SELECT treasure.id as treasure_id, player.id AS player_id, md5(HEX(AES_ENCRYPT(CONCAT(code, player.id), :secretKey))) as encryptedCode
FROM treasure, player
WHERE md5(HEX(AES_ENCRYPT(CONCAT(code, player.id), :secretKey))) LIKE :code
", [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace app\modules\moderation\controllers;

use Yii;
use app\modules\moderation\models\Abuser;
use app\modules\moderation\models\AbuserSearch;
use yii\web\Controller;
Expand Down
127 changes: 96 additions & 31 deletions backend/modules/moderation/models/Abuser.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,40 +131,105 @@ public static function find()
*/
public function forFailedClaim()
{
$string = preg_replace_callback('/(etsctf|tsctf|sctf|ctf|tf|f)_([a-zA-Z0-9_]+)/i', function ($matches) {
return trim($matches[2]);
}, $this->title);
$secretKey = \Yii::$app->sys->treasure_secret_key;
if (preg_match('/(?:etsctf|tsctf|sctf|ctf|tf|f)_([a-z0-9]+)/i', $this->title, $matches)) {
$claimedHash = $matches[0];
$string = $matches[1];
} else {
$claimedHash = $string = null;
}
if ($string != null) {
$secretKey = \Yii::$app->sys->treasure_secret_key;
$result = \app\modules\gameplay\models\TreasureFinder::findByEncryptedCode($secretKey, '%' . $string . '%');
if (is_array($result) && $result !== []) {
if (array_key_exists('player_id', $result)) {
$originalPlayer = \app\modules\frontend\models\Player::findOne($result['player_id']);
}

if (array_key_exists('treasure_id', $result)) {
$originalTreasure = \app\modules\gameplay\models\Treasure::findOne($result['treasure_id']);
}

// check if its just a typo for the same user so they havent copied a flag
if (array_key_exists('player_id', $result) && intval($result['player_id']) == intval($this->player_id)) {
return 'False alarm this is their own code';
}

$profileLink = \app\widgets\ProfileLink::widget([
'username' => $originalPlayer->username,
'actions' => false
]);
$offenderLink = \app\widgets\ProfileLink::widget([
'username' => $this->player->username,
'actions' => false
]);
if ($this->player->teamPlayer)
$from = sprintf("[%s] from team [%s]", $offenderLink, $this->player->teamPlayer->team->name);
else
$from = "[$offenderLink]";

if ($originalPlayer->teamPlayer)
$to = sprintf("[%s] from team [%s]", $profileLink, $originalPlayer->teamPlayer->team->name);
else
$to = "[$profileLink]";
$msg = "- $from tried to claim code [<small><code>" . trim($claimedHash) . "</code></small>] that belongs to player $to for target [" . $originalTreasure->target->name . "] and treasure [" . $originalTreasure->name . "]";
return $msg;
}
}

return null;
}
/**
* Check if the claimed hash is a near-typo of the player's own actual flag.
* Returns a "false alarm" message if it looks like a typo, null otherwise.
*/
private function checkForTypo(string $claimedHash, $actualHash): ?string
{
$actual = trim($actualHash); // adjust to however you retrieve the player's actual flag
$claimed = trim($claimedHash);

$result = \app\modules\gameplay\models\TreasureFinder::findByEncryptedCode($secretKey, '%' . $string . '%');
if ($actual === '') {
return null;
}

if (is_array($result) && $result !== []) {
if (array_key_exists('player_id', $result)) {
$originalPlayer = \app\modules\frontend\models\Player::findOne($result['player_id']);
}
if (array_key_exists('treasure_id', $result)) {
$originalTreasure = \app\modules\gameplay\models\Treasure::findOne($result['treasure_id']);
$distance = levenshtein(strtolower($claimed), strtolower($actual));

if ($distance === 0 || $distance > 2) {
return null;
}

$offenderLink = \app\widgets\ProfileLink::widget([
'username' => $this->player->username,
'actions' => false
]);

$diff = $this->buildInlineDiff($claimed, $actual);

return "False alarm, [$offenderLink] had a typo in their claim: $diff";
}

/**
* Build a character-level inline diff between two strings.
* Mismatched characters are highlighted: red for claimed, green for actual.
*/
private function buildInlineDiff(string $claimed, string $actual): string
{
$maxLen = max(strlen($claimed), strlen($actual));
$claimedOut = '';
$actualOut = '';

for ($i = 0; $i < $maxLen; $i++) {
$c = $claimed[$i] ?? '';
$a = $actual[$i] ?? '';

if ($c === $a) {
$claimedOut .= htmlspecialchars($c);
$actualOut .= htmlspecialchars($a);
} else {
if ($c !== '') $claimedOut .= '<span style="background:#ffcccc;color:#900;font-weight:bold">' . htmlspecialchars($c) . '</span>';
if ($a !== '') $actualOut .= '<span style="background:#ccffcc;color:#060;font-weight:bold">' . htmlspecialchars($a) . '</span>';
}
$profileLink = \app\widgets\ProfileLink::widget([
'username' => $originalPlayer->username,
'actions' => false
]);
$offenderLink = \app\widgets\ProfileLink::widget([
'username' => $this->player->username,
'actions' => false
]);
if ($this->player->teamPlayer)
$from = sprintf("[%s] from team [%s]", $offenderLink, $this->player->teamPlayer->team->name);
else
$from = "[$offenderLink]";

if ($originalPlayer->teamPlayer)
$to = sprintf("[%s] from team [%s]", $profileLink, $originalPlayer->teamPlayer->team->name);
else
$to = "[$profileLink]";
$msg = "- $from tried to claim code [<small><code>".trim($this->title)."</code></small>] that belongs to player $to for target [" . $originalTreasure->target->name . "] and treasure [" . $originalTreasure->name . "]";
return $msg;
}
return null;

return "<code>$claimedOut</code> vs <code>$actualOut</code>";
}
}