Passed
Push — dev ( 4ef148...eba0dc )
by Dispositif
07:17
created

WikiBotConfig::getCommentary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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