Passed
Push — master ( 0549f4...ca9f9d )
by Dispositif
06:13
created

WikiBotConfig::getCommentary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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