Passed
Push — master ( 5eccc7...1faa52 )
by Dispositif
02:45
created

WikiBotConfig::isNoBotTag()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 8
nop 2
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 4
rs 10
c 0
b 0
f 0
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\Logger;
14
use App\Infrastructure\ServiceFactory;
15
use App\Infrastructure\SMS;
16
use Bluora\LaravelGitInfo\GitInfo;
17
use DateInterval;
18
use DateTimeImmutable;
19
use DomainException;
20
use Exception;
21
use Mediawiki\Api\UsageException;
22
use Psr\Log\LoggerInterface;
23
use Throwable;
24
25
/**
26
 * Define wiki configuration of the bot.
27
 * See also .env file for parameters.
28
 * Class WikiBotConfig.
29
 */
30
class WikiBotConfig
31
{
32
    const WATCHPAGE_FILENAME = __DIR__.'/resources/watch_pages.json';
33
34
    const EXIT_ON_CHECK_WATCHPAGE = false;
35
36
    // do not stop if they play with {stop} on bot talk page
37
    const BLACKLIST_EDITOR  = ['NB80'];
38
39
    const BOT_FLAG = false;
40
41
    const MODE_AUTO = false;
42
43
    const EXIT_ON_WIKIMESSAGE = true;
44
45
    const EDIT_LAPS = 20;
46
47
    const EDIT_LAPS_MANUAL = 20;
48
49
    const EDIT_LAPS_AUTOBOT = 60;
50
51
    const EDIT_LAPS_FLAGBOT = 8;
52
53
54
    public $taskName = 'Améliorations bibliographiques';
55
56
    public static $gitVersion;
57
58
    /**
59
     * @var DateTimeImmutable
60
     */
61
    private $lastCheckStopDate;
62
    /**
63
     * @var LoggerInterface
64
     */
65
    public $log;
66
67
    public function __construct()
68
    {
69
        $this->log = new Logger();
70
        ini_set('user_agent', getenv('USER_AGENT'));
71
    }
72
73
    /**
74
     * Return start of wiki edit commentary.
75
     *
76
     * @return string
77
     */
78
    public function getCommentary(): string
79
    {
80
        return sprintf(
81
            '[%s] %s',
82
            str_replace('v', '', self::getGitVersion()),
83
            $this->taskName
84
        );
85
    }
86
87
    /**
88
     * Return last version (tag) from Git.
89
     *
90
     * @return string|null
91
     */
92 1
    public static function getGitVersion(): ?string
93
    {
94 1
        if (self::$gitVersion) {
95
            return self::$gitVersion;
96
        }
97 1
        $git = new GitInfo();
98 1
        $raw = $git->version();
99 1
        if (preg_match('#^(v[0-9.a-e]+)#', $raw, $matches) > 0) {
100
            self::$gitVersion = $matches[1];
101
102
            return self::$gitVersion;
103
        }
104
105 1
        return null;
106
    }
107
108
    /**
109
     * Throws Exception if "{{stop}}" or "STOP" on talk page.
110
     *
111
     * @param bool|null $botTalk
112
     *
113
     * @throws UsageException
114
     */
115
    public function checkStopOnTalkpage(?bool $botTalk = false): void
116
    {
117
        $title = 'Discussion_utilisateur:'.getenv('BOT_NAME');
118
119
        if ($this->lastCheckStopDate
120
            && new DateTimeImmutable() < $this->lastCheckStopDate->add(
121
                new DateInterval('PT2M')
122
            )
123
        ) {
124
            return;
125
        }
126
        $this->lastCheckStopDate = new DateTimeImmutable();
127
128
        // don't catch Exception (stop process if error)
129
        $wiki = ServiceFactory::wikiApi();
130
        $pageAction = new WikiPageAction($wiki, $title);
131
        $text = $pageAction->getText();
132
        $lastEditor = $pageAction->getLastEditor() ?? 'unknown';
133
134
        if (preg_match('#({{stop}}|{{Stop}}|STOP)#', $text) > 0) {
135
            echo date('Y-m-d H:i');
136
            echo sprintf(
137
                "\n*** STOP ON TALK PAGE BY %s ***\n\n",
138
                $lastEditor
139
            );
140
            if(in_array($lastEditor, static::BLACKLIST_EDITOR)){
141
                return;
142
            }
143
144
            if (class_exists(SMS::class)) {
145
                try {
146
                    new SMS('WikiBotConfig stop by '.$lastEditor);
147
                } catch (Exception $e) {
148
                    unset($e);
149
                }
150
            }
151
            if ($botTalk && class_exists(TalkBotConfig::class)) {
152
                try {
153
                    (new TalkBotConfig())->botTalk();
154
                } catch (Throwable $e) {
155
                    unset($e);
156
                }
157
            }
158
159
            throw new DomainException('STOP on talk page');
160
        }
161
    }
162
163
    /**
164
     * Is there a new message on the discussion page of the bot (or owner) ?
165
     * Stop on new message ?
166
     *
167
     * @throws ConfigException
168
     */
169
    public function checkWatchPages()
170
    {
171
        foreach ($this->getWatchPages() as $title => $lastTime) {
172
            $pageTime = $this->getTimestamp($title);
173
174
            // the page has been edited since last check ?
175
            if (!$pageTime || $pageTime !== $lastTime) {
176
                echo sprintf(
177
                    "WATCHPAGE '%s' has been edited since %s.\n",
178
                    $title,
179
                    $lastTime
180
                );
181
182
                // Ask? Mettre à jour $watchPages ?
183
                echo "Replace with $title => '$pageTime'";
184
185
                if (static::EXIT_ON_CHECK_WATCHPAGE) {
186
                    echo "EXIT_ON_CHECK_WATCHPAGE\n";
187
188
                    throw new DomainException('exit from check watchpages');
189
                }
190
            }
191
        }
192
    }
193
194
    /**
195
     * @return array
196
     *
197
     * @throws ConfigException
198
     */
199
    protected function getWatchPages(): array
200
    {
201
        if (!file_exists(static::WATCHPAGE_FILENAME)) {
202
            throw new ConfigException('No watchpage file found.');
203
        }
204
205
        try {
206
            $json = file_get_contents(static::WATCHPAGE_FILENAME);
207
            $array = json_decode($json, true);
208
        } catch (Throwable $e) {
209
            throw new ConfigException('Watchpage file malformed.');
210
        }
211
212
        return $array;
213
    }
214
215
    private function getTimestamp(string $title): ?string
216
    {
217
        $wiki = ServiceFactory::wikiApi();
218
        $page = new WikiPageAction($wiki, $title);
219
220
        return $page->page->getRevisions()->getLatest()->getTimestamp();
221
    }
222
223
    /**
224
     * How many minutes since last edit ? Do not to disturb human editors !
225
     *
226
     * @param string $title
227
     *
228
     * @return int minutes
229
     */
230
    public function minutesSinceLastEdit(string $title): int
231
    {
232
        $time = $this->getTimestamp($title);  // 2011-09-02T16:31:13Z
233
234
        return (int) round((time() - strtotime($time)) / 60);
235
    }
236
237
    /**
238
     * Detect {{nobots}}, {{bots|deny=all}}, {{bots|deny=MyBot,BobBot}}.
239
     * Relevant out of the "main" wiki-namespace (talk pages, etc).
240
     *
241
     * @param string      $text
242
     * @param string|null $botName
243
     *
244
     * @return bool
245
     */
246 4
    private static function isNoBotTag(string $text, ?string $botName = null): bool
247
    {
248 4
        $botName = ($botName) ? $botName : getenv('BOT_NAME');
249 4
        $denyReg = (!empty($botName)) ? '|\{\{bots ?\| ?deny\=[^\}]*'.preg_quote($botName, '#').'[^\}]*\}\}' : '';
250
251 4
        if (preg_match('#({{nobots}}|{{bots ?\| ?(optout|deny) ?= ?all ?}}'.$denyReg.')#i', $text) > 0) {
252 2
            return true;
253
        }
254
255 2
        return false;
256
    }
257
258
    /**
259
     * Detect wiki-templates restricting the edition on a frwiki page.
260
     *
261
     * @param string $text
262
     *
263
     * @return bool
264
     */
265 6
    public static function isEditionRestricted(string $text, ?string $botName = null): bool
266
    {
267 6
        if (preg_match('#{{Protection#i', $text) > 0
268 5
            || preg_match('#\{\{3R\}\}#', $text) > 0
269 6
            || self::isNoBotTag($text, $botName)
270
        ) {
271 4
            return true;
272
        }
273
274 2
        return false;
275
    }
276
}
277