IMHelper.php 8.18 KB
<?php

namespace App\Helpers;

use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Tencent\TLSSigAPIv2;

class IMHelper
{
    private string $host;

    private string $key;

    private string $secret;

    private string $identifier;

    public const SYSTEM_USER = 'system';

    public function __construct()
    {
        $this->host       = config('services.im.domain');
        $this->key        = config('services.im.key');
        $this->secret     = config('services.im.secret');
        $this->identifier = config('services.im.identifier');
    }

    /**
     * @throws \Exception
     */
    private function createSignature(string $identifier): string
    {
        return (new TLSSigAPIv2($this->key, $this->secret))->genUserSig($identifier);
    }

    /**
     * @throws \Exception
     */
    private function getSystemSignature(): string
    {
        return $this->createSignature($this->identifier);
    }

    /**
     * @throws \Exception
     */
    public static function getUserSignature(int|string $userId): string
    {
        return (new static())->createSignature(self::userKeyToAccount($userId));
    }

    /**
     * @throws \Exception
     */
    public function getUrl(string $uri): string
    {
        return $this->host . '/' . $uri . '?' . Arr::query(['sdkappid' => $this->key, 'identifier' => $this->identifier, 'usersig' => $this->getSystemSignature(), 'random' => random_int(0, 4294967295), 'contenttype' => 'json']);
    }

    /**
     * @param string $uri
     * @param array  $data
     * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
     * @throws \Exception
     */
    public static function send(string $uri, array $data): PromiseInterface|Response
    {
        $host = (new static())->getUrl($uri);

        $result = Http::asJson()->post($host, $data);


        Log::channel('im')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [
            'uri' => $uri, 'data' => $data, 'result' => $result->json()
        ]);

        return $result;
    }

    /**
     * @param int|string $userId
     * @return string
     */
    public static function userKeyToAccount(int|string $userId): string
    {
        return config('services.im.prefix') . $userId;
    }

    /**
     * @param string $account
     * @return string
     */
    public static function accountToUserKey(string $account): string
    {
        return Str::replace(config('services.im.prefix'), '', $account);
    }

    /**
     * @param string       $title
     * @param string       $desc
     * @param string|array $ext
     * @return array
     * @throws \JsonException
     */
    public static function createOfflineMessage(string $title, string $desc = '', string|array $ext = ''): array
    {
        return [
            "PushFlag"    => 0,
            "Title"       => $title,
            "Desc"        => $desc,
            "Ext"         => is_array($ext) ? json_encode($ext, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $ext,
            "AndroidInfo" => ["Sound" => "android.mp3"],
            "ApnsInfo"    => ["Sound" => "apns.mp3", "BadgeMode" => 1]
        ];
    }

    /**
     * @param string $type
     * @param array  $content
     * @return array
     */
    public static function createOnlineMessage(string $type, array $content): array
    {
        return [
            'MsgType'    => $type,
            'MsgContent' => [
                ...Arr::map($content, static fn($item) => is_array($item) ? json_encode($item, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $item),
                'Sound' => 'dingdong.aiff'
            ]
        ];
    }

    /**
     * @param array $content
     * @param array $ext
     * @return array
     */
    public static function createCustomMessage(array $content, array $ext = []): array
    {
        return self::createOnlineMessage('TIMCustomElem', [...$content, "Ext" => $ext,]);
    }

    /**
     * @param string $form
     * @param string $to
     * @param array  $payload
     * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
     * @throws \Exception
     */
    public static function sendSingleChatMessage(string $form, string $to, array $payload): PromiseInterface|Response
    {
        return self::send('v4/openim/sendmsg', [
                "SyncOtherMachine"  => 2,
                'IsNeedReadReceipt' => 1,
                "MsgRandom"         => random_int(0, 4294967295),
                "From_Account"      => $form,
                "To_Account"        => self::userKeyToAccount($to)
            ] + $payload);
    }

    /**
     * @param string $form
     * @param array  $to
     * @param array  $payload
     * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
     * @throws \Exception
     */
    public static function sendBatchChatMessage(string $form, array $to, array $payload): PromiseInterface|Response
    {
        return self::send('v4/openim/batchsendmsg', [
                "SyncOtherMachine"  => 2,
                'IsNeedReadReceipt' => 1,
                'MsgRandom'         => random_int(0, 4294967295),
                "From_Account"      => is_numeric($form) ? self::userKeyToAccount($form) : $form,
                "To_Account"        => Arr::map($to, static fn($item) => self::userKeyToAccount($item)),
            ] + $payload);
    }

    /**
     * @param string $form
     * @param string $to
     * @param string $msgKey
     * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
     * @throws \Exception
     */
    public static function rollbackChatMessage(string $form, string $to, string $msgKey): PromiseInterface|Response
    {
        info('IM_ROLLBACK', [$form, $to, $msgKey]);
        return self::send('v4/openim/admin_msgwithdraw', ["From_Account" => $form, "To_Account" => is_numeric($to) ? self::userKeyToAccount($to) : $to, "MsgKey" => $msgKey]);
    }

    /**
     * @throws \Exception
     */
    public static function checkAccount(array $account): PromiseInterface|Response
    {
        return self::send('v4/im_open_login_svc/account_check ', [
            'CheckItem' => Arr::map($account, static fn($item) => ['UserID' => self::userKeyToAccount($item)])
        ]);
    }

    /**
     * @throws \JsonException
     * @throws \Exception
     */
    public static function importSingleAccount(string $userId, string|array $name, string $avatar = ''): PromiseInterface|Response
    {
        return self::send('v4/im_open_login_svc/account_import', [
            'UserID'  => self::userKeyToAccount($userId),
            'Nick'    => is_array($name) ? json_encode($name, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $name,
            'FaceUrl' => $avatar
        ]);
    }

    /**
     * @throws \Exception
     */
    public static function updateSingleAccountInfo(string $userId, array $profileItem): PromiseInterface|Response
    {
        return self::send('v4/profile/portrait_set', ['From_Account' => self::userKeyToAccount($userId), 'ProfileItem' => $profileItem]);
    }


    /**
     * @throws \Exception
     */
    public static function createGroup(string $userId, array $data = []): PromiseInterface|Response
    {
        return self::send('v4/group_open_http_svc/create_group', [
            'Type'          => 'Public',
            'GroupId'       => config('services.im.prefix') . now()->format('YmdHisu'),
            'Owner_Account' => self::userKeyToAccount($userId),
            ...$data
        ]);
    }


    /**
     * @throws \Random\RandomException
     * @throws \Exception
     */
    public static function sendGroupMessage(string $groupId, array $data): PromiseInterface|Response
    {
        return self::send('v4/group_open_http_svc/send_group_msg', ["GroupId" => $groupId, "Random" => random_int(0, 4294967295),] + $data);
    }

    /**
     * @param string $groupId
     * @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
     * @throws \Exception
     */
    public static function deleteGroup(string $groupId): PromiseInterface|Response
    {
        return self::send('v4/group_open_http_svc/destroy_group', ['GroupId' => $groupId]);
    }
}