Passed
Push — master ( b85d4e...511693 )
by Dispositif
02:38
created

Bot::checkStopOnTalkpage()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 26
c 0
b 0
f 0
nc 11
nop 1
dl 0
loc 39
rs 8.0555
1
<?php
2
/**
3
 * This file is part of dispositif/wikibot application
4
 * 2019 : Philippe M. <[email protected]>
5
 * For the full copyright and MIT license information, please view the LICENSE file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Application;
11
12
use App\Domain\Exceptions\ConfigException;
13
use App\Infrastructure\ServiceFactory;
14
use App\Infrastructure\SMS;
15
use Bluora\LaravelGitInfo\GitInfo;
16
use DateInterval;
17
use DateTimeImmutable;
18
use Exception;
19
use Mediawiki\Api\UsageException;
20
use Throwable;
21
22
/**
23
 * Define wiki configuration of the bot.
24
 * See also .env file for parameters.
25
 * Class Bot.
26
 */
27
class Bot
28
{
29
    const WIKI_STATE_FILENAME = __DIR__.'/resources/wiki_state.json';
30
31
    const WATCHPAGE_FILENAME = __DIR__.'/resources/watch_pages.json';
32
33
    const EXIT_ON_CHECK_WATCHPAGE = true;
34
35
    const BOT_FLAG = false;
36
37
    const MODE_AUTO = false;
38
39
    const EXIT_ON_WIKIMESSAGE = true;
40
41
    const EDIT_LAPS = 20;
42
43
    const EDIT_LAPS_MANUAL = 20;
44
45
    const EDIT_LAPS_AUTOBOT = 60;
46
47
    const EDIT_LAPS_FLAGBOT = 8;
48
49
    public $taskName = 'Améliorations bibliographiques';
50
51
    public static $gitVersion;
52
53
    /**
54
     * @var DateTimeImmutable
55
     */
56
    private $lastCheckStopDate;
57
58
    public function __construct()
59
    {
60
        ini_set('user_agent', getenv('USER_AGENT'));
61
    }
62
63
    /**
64
     * Return start of wiki edit commentary.
65
     *
66
     * @return string
67
     */
68
    public function getCommentary(): string
69
    {
70
        return sprintf(
71
            '[%s] %s',
72
            str_replace('v', '', self::getGitVersion()),
73
            $this->taskName
74
        );
75
    }
76
77
    /**
78
     * Return last version (tag) from Git.
79
     *
80
     * @return string|null
81
     */
82
    public static function getGitVersion(): ?string
83
    {
84
        if (self::$gitVersion) {
85
            return self::$gitVersion;
86
        }
87
        $git = new GitInfo();
88
        $raw = $git->version();
89
        if (preg_match('#^(v[0-9.a-e]+)#', $raw, $matches) > 0) {
90
            self::$gitVersion = $matches[1];
91
92
            return self::$gitVersion;
93
        }
94
95
        return null;
96
    }
97
98
    public function checkStopOnTalkpage(?bool $botTalk = false): void
99
    {
100
        $title = 'Discussion_utilisateur:'.getenv('BOT_NAME');
101
102
        if ($this->lastCheckStopDate
103
            && new DateTimeImmutable() < $this->lastCheckStopDate->add(
104
                new DateInterval('PT2M')
105
            )
106
        ) {
107
            return;
108
        }
109
        $this->lastCheckStopDate = new DateTimeImmutable();
110
111
        $wiki = ServiceFactory::wikiApi();
112
        $pageAction = new WikiPageAction($wiki, $title);
113
        $text = $pageAction->getText();
114
        $lastEditor = $pageAction->getLastEditor() ?? 'unknown';
115
116
        if (preg_match('#({{stop}}|{{Stop}}|STOP)#', $text) > 0) {
117
            echo date('Y-m-d H:i');
118
            echo sprintf(
119
                "\n*** STOP ON TALK PAGE BY %s ***\n\n",
120
                $lastEditor
121
            );
122
            if (class_exists(SMS::class)) {
123
                try {
124
                    new SMS('Bot stop by '.$lastEditor);
125
                } catch (Exception $e) {
126
                    unset($e);
127
                }
128
            }
129
            if ($botTalk && class_exists(ZiziBot::class)) {
130
                try {
131
                    (new ZiziBot())->botTalk();
132
                } catch (UsageException $e) {
133
                    unset($e);
134
                }
135
            }
136
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
137
        }
138
    }
139
140
    /**
141
     * Is there a new message on the discussion page of the bot (or owner) ?
142
     * Stop on new message ?
143
     *
144
     * @throws ConfigException
145
     */
146
    public function checkWatchPages()
147
    {
148
        foreach ($this->getWatchPages() as $title => $lastTime) {
149
            $pageTime = $this->getTimestamp($title);
150
151
            // the page has been edited since last check ?
152
            if (!$pageTime || $pageTime !== $lastTime) {
153
                echo sprintf(
154
                    "WATCHPAGE '%s' has been edited since %s.\n",
155
                    $title,
156
                    $lastTime
157
                );
158
159
                // Ask? Mettre à jour $watchPages ?
160
                echo "Replace with $title => '$pageTime'";
161
162
                if (self::EXIT_ON_CHECK_WATCHPAGE) {
163
                    echo "EXIT_ON_CHECK_WATCHPAGE\n";
164
                    exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
165
                }
166
            }
167
        }
168
    }
169
170
    /**
171
     * @return array
172
     * @throws ConfigException
173
     */
174
    protected function getWatchPages(): array
175
    {
176
        if (!file_exists(static::WATCHPAGE_FILENAME)) {
177
            throw new ConfigException('No watchpage file found.');
178
        }
179
180
        try {
181
            $json = file_get_contents(static::WATCHPAGE_FILENAME);
182
            $array = json_decode($json, true);
183
        } catch (Throwable $e) {
184
            throw new ConfigException('Watchpage file malformed.');
185
        }
186
187
        return $array;
188
    }
189
190
    private function getTimestamp(string $title): ?string
191
    {
192
        $wiki = ServiceFactory::wikiApi();
193
        $page = new WikiPageAction($wiki, $title);
194
195
        return $page->page->getRevisions()->getLatest()->getTimestamp();
196
    }
197
198
    /**
199
     * How many minutes since last edit ? Do not to disturb human editors !
200
     *
201
     * @param string $title
202
     *
203
     * @return int minutes
204
     */
205
    public function minutesSinceLastEdit(string $title): int
206
    {
207
        $time = $this->getTimestamp($title);  // 2011-09-02T16:31:13Z
208
209
        return (int)round((time() - strtotime($time)) / 60);
210
    }
211
212
    /**
213
     * Detect {{nobots}}, {{bots|deny=all}}, {{bots|deny=MyBot,BobBot}}.
214
     * Relevant out of the "main" wiki-namespace (talk pages, etc).
215
     *
216
     * @param string      $text
217
     * @param string|null $botName
218
     *
219
     * @return bool
220
     */
221
    private static function isNoBotTag(string $text, ?string $botName = null): bool
222
    {
223
        $botName = ($botName) ? $botName : getenv('BOT_NAME');
224
        $denyReg = (!empty($botName)) ? '|\{\{bots ?\| ?deny\=[^\}]*'.preg_quote($botName, '#').'[^\}]*\}\}' : '';
225
226
        if (preg_match('#({{nobots}}|{{bots ?\| ?(optout|deny) ?= ?all ?}}'.$denyReg.')#i', $text) > 0) {
227
            return true;
228
        }
229
230
        return false;
231
    }
232
233
    /**
234
     * Detect wiki-templates restricting the edition on a frwiki page.
235
     *
236
     * @param string $text
237
     *
238
     * @return bool
239
     */
240
    public static function isEditionRestricted(string $text, ?string $botName = null): bool
241
    {
242
        if (preg_match('#\{\{Protection#i', $text) > 0
243
            || preg_match('#\{\{3R\}\}#', $text) > 0
244
            || self::isNoBotTag($text, $botName)
245
        ) {
246
            return true;
247
        }
248
249
        return false;
250
    }
251
}
252