TelegramService::bind()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 4
eloc 14
c 2
b 1
f 0
nc 4
nop 3
dl 0
loc 20
rs 9.7998
1
<?php
2
3
namespace app\core\services;
4
5
use app\core\models\AuthClient;
6
use app\core\models\Transaction;
7
use app\core\models\User;
8
use app\core\traits\ServiceTrait;
9
use app\core\types\AnalysisDateType;
10
use app\core\types\AuthClientStatus;
11
use app\core\types\AuthClientType;
12
use app\core\types\ReportType;
0 ignored issues
show
Bug introduced by
The type app\core\types\ReportType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use app\core\types\TelegramAction;
14
use app\core\types\TransactionRating;
15
use app\core\types\TransactionType;
16
use TelegramBot\Api\BotApi;
17
use TelegramBot\Api\Client;
18
use TelegramBot\Api\Exception;
19
use TelegramBot\Api\InvalidArgumentException;
20
use TelegramBot\Api\Types\CallbackQuery;
21
use TelegramBot\Api\Types\Inline\InlineKeyboardMarkup;
22
use TelegramBot\Api\Types\Message;
23
use Yii;
24
use yii\base\BaseObject;
25
use yii\base\InvalidConfigException;
26
use yii\db\Exception as DBException;
27
use yii\helpers\Json;
28
use yiier\graylog\Log;
29
use yiier\helpers\Setup;
30
31
class TelegramService extends BaseObject
32
{
33
    use ServiceTrait;
34
35
    /**
36
     * @return Client|object
37
     */
38
    public static function newClient()
39
    {
40
        try {
41
            return Yii::createObject(Client::class, [params('telegramToken')]);
42
        } catch (InvalidConfigException $e) {
43
            return new Client(params('telegramToken'));
0 ignored issues
show
Bug introduced by
It seems like params('telegramToken') can also be of type yii\web\Session; however, parameter $token of TelegramBot\Api\Client::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

43
            return new Client(/** @scrutinizer ignore-type */ params('telegramToken'));
Loading history...
44
        }
45
    }
46
47
    /**
48
     * @param User $user
49
     * @param string $token
50
     * @param Message $message
51
     * @throws DBException
52
     */
53
    public function bind(User $user, string $token, Message $message): void
54
    {
55
        Yii::error($message, 'telegram_message' . $token);
0 ignored issues
show
Bug introduced by
$message of type TelegramBot\Api\Types\Message is incompatible with the type array|string expected by parameter $message of yii\BaseYii::error(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

55
        Yii::error(/** @scrutinizer ignore-type */ $message, 'telegram_message' . $token);
Loading history...
56
57
        $conditions = [
58
            'type' => AuthClientType::TELEGRAM,
59
            'user_id' => $user->id,
60
            'status' => AuthClientStatus::ACTIVE
61
        ];
62
        if (!$model = AuthClient::find()->where($conditions)->one()) {
63
            $model = new AuthClient();
64
            $model->load($conditions, '');
65
        }
66
        $model->client_username = (string)($message->getFrom()->getUsername() ?: $message->getFrom()->getFirstName());
67
        $model->client_id = (string)$message->getFrom()->getId();
68
        $model->data = $message->toJson();
0 ignored issues
show
Bug Best Practice introduced by
The property data does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
69
        if (!$model->save()) {
70
            throw new DBException(Setup::errorMessage($model->firstErrors));
71
        }
72
        User::updateAll(['password_reset_token' => null], ['id' => $user->id]);
73
    }
74
75
    /**
76
     * @param CallbackQuery $message
77
     * @param Client $bot
78
     * @throws Exception
79
     * @throws InvalidArgumentException
80
     * @throws \Throwable
81
     */
82
    public function callbackQuery(CallbackQuery $message, Client $bot)
83
    {
84
        /** @var BotApi $bot */
85
        $data = Json::decode($message->getData());
86
        switch (data_get($data, 'action')) {
87
            case TelegramAction::RECORD_DELETE:
88
                /** @var Transaction $model */
89
                if ($model = Transaction::find()->where(['id' => data_get($data, 'id')])->one()) {
90
                    $transaction = Yii::$app->db->beginTransaction();
91
                    try {
92
                        foreach ($model->records as $record) {
93
                            $record->delete();
94
                        }
95
                        $text = '记录成功被删除';
96
                        $transaction->commit();
97
                        $bot->editMessageText(
98
                            $message->getFrom()->getId(),
99
                            $message->getMessage()->getMessageId(),
100
                            $text
101
                        );
102
                    } catch (\Exception $e) {
103
                        $transaction->rollBack();
104
                        Log::error('删除记录失败', ['model' => $model->attributes, 'e' => (string)$e]);
105
                    }
106
                } else {
107
                    $text = '删除失败,记录已被删除或者不存在';
108
                    $replyToMessageId = $message->getMessage()->getMessageId();
109
                    $bot->sendMessage($message->getFrom()->getId(), $text, null, false, $replyToMessageId);
110
                }
111
112
                break;
113
            case TelegramAction::TRANSACTION_RATING:
114
                $id = data_get($data, 'id');
115
                if ($this->transactionService->updateRating($id, data_get($data, 'value'))) {
0 ignored issues
show
Bug introduced by
It seems like data_get($data, 'value') can also be of type null; however, parameter $rating of app\core\services\Transa...Service::updateRating() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
                if ($this->transactionService->updateRating($id, /** @scrutinizer ignore-type */ data_get($data, 'value'))) {
Loading history...
Bug introduced by
It seems like $id can also be of type null; however, parameter $id of app\core\services\Transa...Service::updateRating() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
                if ($this->transactionService->updateRating(/** @scrutinizer ignore-type */ $id, data_get($data, 'value'))) {
Loading history...
116
                    $replyMarkup = $this->getRecordMarkup(Transaction::findOne($id));
117
                    $bot->editMessageReplyMarkup(
118
                        $message->getFrom()->getId(),
119
                        $message->getMessage()->getMessageId(),
120
                        $replyMarkup
121
                    );
122
                } else {
123
                    $text = '评分失败,记录已被删除或者不存在';
124
                    $replyToMessageId = $message->getMessage()->getMessageId();
125
                    $bot->sendMessage($message->getFrom()->getId(), $text, null, false, $replyToMessageId);
126
                }
127
128
                break;
129
            default:
130
                # code...
131
                break;
132
        }
133
    }
134
135
    public function getRecordMarkup(Transaction $model)
136
    {
137
        $tests = TransactionRating::texts();
138
        $rating = [];
139
        foreach (TransactionRating::names() as $key => $name) {
140
            $rating[$key] = null;
141
        }
142
        if ($model->rating) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $model->rating of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
143
            $rating[$model->rating] = 1;
144
        }
145
        $items = [
146
            [
147
                'text' => '🚮删除',
148
                'callback_data' => Json::encode([
149
                    'action' => TelegramAction::RECORD_DELETE,
150
                    'id' => $model->id
151
                ]),
152
            ],
153
            [
154
                'text' => '😍' . $tests[TransactionRating::MUST] . $rating[TransactionRating::MUST],
155
                'callback_data' => Json::encode([
156
                    'action' => TelegramAction::TRANSACTION_RATING,
157
                    'id' => $model->id,
158
                    'value' => TransactionRating::MUST
159
                ]),
160
            ],
161
            [
162
                'text' => '😐' . $tests[TransactionRating::NEED] . $rating[TransactionRating::NEED],
163
                'callback_data' => Json::encode([
164
                    'action' => TelegramAction::TRANSACTION_RATING,
165
                    'id' => $model->id,
166
                    'value' => TransactionRating::NEED
167
                ]),
168
            ],
169
            [
170
                'text' => '💩' . $tests[TransactionRating::WANT] . $rating[TransactionRating::WANT],
171
                'callback_data' => Json::encode([
172
                    'action' => TelegramAction::TRANSACTION_RATING,
173
                    'id' => $model->id,
174
                    'value' => TransactionRating::WANT
175
                ]),
176
            ]
177
        ];
178
179
        return new InlineKeyboardMarkup([$items]);
180
    }
181
182
    /**
183
     * @param string $messageText
184
     * @param null $keyboard
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $keyboard is correct as it would always require null to be passed?
Loading history...
185
     * @param int $userId
186
     * @return void
187
     */
188
    public function sendMessage(string $messageText, $keyboard = null, int $userId = 0): void
189
    {
190
        $userId = $userId ?: Yii::$app->user->id;
191
        $telegram = AuthClient::find()->select('data')->where([
192
            'user_id' => $userId,
193
            'type' => AuthClientType::TELEGRAM
194
        ])->scalar();
195
        if (!$telegram) {
196
            return;
197
        }
198
        $telegram = Json::decode($telegram);
199
        if (empty($telegram['chat']['id'])) {
200
            return;
201
        }
202
        $bot = TelegramService::newClient();
203
        /** @var BotApi $bot */
204
        try {
205
            $bot->sendMessage($telegram['chat']['id'], $messageText, null, false, null, $keyboard);
206
        } catch (InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
207
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
208
        }
209
    }
210
211
212
    public function getMessageTextByTransaction(Transaction $model, string $title = '记账成功')
213
    {
214
        $text = "{$title}\n";
215
        $text .= '交易类目: #' . $model->category->name . "\n";
216
        $text .= '交易类型: #' . TransactionType::texts()[$model->type] . "\n";
217
        $text .= "交易时间: {$model->date}\n"; // todo add tag
218
        if (in_array($model->type, [TransactionType::EXPENSE, TransactionType::TRANSFER])) {
219
            $fromAccountName = $model->fromAccount->name;
220
            $fromAccountBalance = Setup::toYuan($model->fromAccount->balance_cent);
221
            $text .= "支付账户: #{$fromAccountName} (余额:{$fromAccountBalance})\n";
222
        }
223
        if (in_array($model->type, [TransactionType::INCOME, TransactionType::TRANSFER])) {
224
            $toAccountName = $model->toAccount->name;
225
            $toAccountBalance = Setup::toYuan($model->toAccount->balance_cent);
226
            $text .= "收款账户: #{$toAccountName} (余额:{$toAccountBalance})\n";
227
        }
228
        $text .= '金额:' . Setup::toYuan($model->amount_cent);
229
        return $text;
230
    }
231
232
    /**
233
     * @param int $userId
234
     * @param string $type
235
     * @return void
236
     * @throws \Exception
237
     */
238
    public function sendReport(int $userId, string $type): void
239
    {
240
        \Yii::$app->user->setIdentity(User::findOne($userId));
0 ignored issues
show
Bug introduced by
app\core\models\User::findOne($userId) of type yii\db\ActiveRecord is incompatible with the type null|yii\web\IdentityInterface expected by parameter $identity of yii\web\User::setIdentity(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

240
        \Yii::$app->user->setIdentity(/** @scrutinizer ignore-type */ User::findOne($userId));
Loading history...
241
        $text = $this->telegramService->getReportTextByType($type);
242
        $this->telegramService->sendMessage($text);
243
    }
244
245
    /**
246
     * @param string $type
247
     * @return string
248
     * @throws \Exception
249
     */
250
    public function getReportTextByType(string $type)
251
    {
252
        $recordOverview = $this->analysisService->recordOverview;
253
        $text = "收支报告\n";
254
255
        $title = data_get($recordOverview, "{$type}.text");
256
        $expense = data_get($recordOverview, "{$type}.overview.expense", 0);
257
        $income = data_get($recordOverview, "{$type}.overview.income", 0);
258
        $surplus = data_get($recordOverview, "{$type}.overview.surplus", 0);
259
        $text .= "{$title}统计:已支出 {$expense},已收入 {$income},结余 {$surplus}\n";
260
261
        $type = AnalysisDateType::CURRENT_MONTH;
262
        $title = data_get($recordOverview, "{$type}.text");
263
        $expense = data_get($recordOverview, "{$type}.overview.expense", 0);
264
        $income = data_get($recordOverview, "{$type}.overview.income", 0);
265
        $surplus = data_get($recordOverview, "{$type}.overview.surplus", 0);
266
        $text .= "{$title}统计:已支出 {$expense},已收入 {$income},结余 {$surplus}\n";
267
268
        return $text;
269
    }
270
}
271