Passed
Push — master ( c84974...1a8235 )
by Daimona
02:33 queued 44s
created

Wiki::cloneWithDomain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme\Wiki;
4
5
use BotRiconferme\Config;
6
use BotRiconferme\Exception\EditException;
7
use BotRiconferme\Exception\LoginException;
8
use BotRiconferme\Exception\APIRequestException;
9
use BotRiconferme\Exception\MissingPageException;
10
use BotRiconferme\Exception\MissingSectionException;
11
use BotRiconferme\Request\RequestBase;
12
use Psr\Log\LoggerInterface;
13
14
/**
15
 * Class for wiki interaction, contains some requests shorthands
16
 */
17
class Wiki {
18
	/** @var bool */
19
	private static $loggedIn = false;
20
	/** @var LoggerInterface */
21
	private $logger;
22
	/** @var string */
23
	private $domain;
24
	/** @var string[] */
25
	private $tokens;
26
27
	/**
28
	 * @param LoggerInterface $logger
29
	 * @param string $domain The URL of the wiki, if different from default
30
	 */
31
	public function __construct( LoggerInterface $logger, string $domain = DEFAULT_URL ) {
32
		$this->logger = $logger;
33
		$this->setDomain( $domain );
34
	}
35
36
	/**
37
	 * @param string $domain
38
	 */
39
	public function setDomain( string $domain ) {
40
		$this->domain = $domain;
41
	}
42
43
	/**
44
	 * Gets the content of a wiki page
45
	 *
46
	 * @param string $title
47
	 * @param int|null $section
48
	 * @return string
49
	 * @throws MissingPageException
50
	 * @throws MissingSectionException
51
	 */
52
	public function getPageContent( string $title, int $section = null ) : string {
53
		$msg = "Retrieving content of $title" . ( $section !== null ? ", section $section" : '' );
54
		$this->logger->debug( $msg );
55
		$params = [
56
			'action' => 'query',
57
			'titles' => $title,
58
			'prop' => 'revisions',
59
			'rvslots' => 'main',
60
			'rvprop' => 'content'
61
		];
62
63
		if ( $section !== null ) {
64
			$params['rvsection'] = $section;
65
		}
66
67
		$req = RequestBase::newFromParams( $params )->setUrl( $this->domain );
68
		$data = $req->execute();
69
		$page = reset( $data->query->pages );
70
		if ( isset( $page->missing ) ) {
71
			throw new MissingPageException( $title );
72
		}
73
74
		$mainSlot = $page->revisions[0]->slots->main;
75
76
		// @phan-suppress-next-line PhanImpossibleTypeComparison $section can be null
77
		if ( $section !== null && isset( $mainSlot->nosuchsection ) ) {
78
			throw new MissingSectionException( $title, $section );
79
		}
80
		return $mainSlot->{ '*' };
81
	}
82
83
	/**
84
	 * Basically a wrapper for action=edit
85
	 *
86
	 * @param array $params
87
	 * @throws EditException
88
	 */
89
	public function editPage( array $params ) {
90
		$this->login();
91
92
		$params = [
93
			'action' => 'edit',
94
			'token' => $this->getToken( 'csrf' ),
95
		] + $params;
96
97
		if ( Config::getInstance()->get( 'bot-edits' ) ) {
98
			$params['bot'] = 1;
99
		}
100
101
		$res = RequestBase::newFromParams( $params )
102
			->setUrl( $this->domain )
103
			->setPost()
104
			->execute();
105
106
		$editData = $res->edit;
107
		if ( $editData->result !== 'Success' ) {
108
			if ( isset( $editData->captcha ) ) {
109
				throw new EditException( 'Got captcha!' );
110
			}
111
			throw new EditException( $editData->info ?? reset( $editData ) );
112
		}
113
	}
114
115
	/**
116
	 * Login wrapper. Checks if we're already logged in and clears tokens cache
117
	 * @throws LoginException
118
	 */
119
	public function login() {
120
		if ( self::$loggedIn ) {
121
			return;
122
		}
123
124
		// Yes, this is an easter egg.
125
		$this->logger->info( 'Logging in. Username: BotRiconferme, password: correctHorseBatteryStaple' );
126
127
		$params = [
128
			'action' => 'login',
129
			'lgname' => Config::getInstance()->get( 'username' ),
130
			'lgpassword' => Config::getInstance()->get( 'password' ),
131
			'lgtoken' => $this->getToken( 'login' )
132
		];
133
134
		try {
135
			$res = RequestBase::newFromParams( $params )->setUrl( $this->domain )->setPost()->execute();
136
		} catch ( APIRequestException $e ) {
137
			throw new LoginException( $e->getMessage() );
138
		}
139
140
		if ( !isset( $res->login->result ) || $res->login->result !== 'Success' ) {
141
			throw new LoginException( 'Unknown error' );
142
		}
143
144
		self::$loggedIn = true;
145
		// Clear tokens cache
146
		$this->tokens = [];
147
		$this->logger->info( 'Login succeeded' );
148
	}
149
150
	/**
151
	 * Get a token, cached.
152
	 *
153
	 * @param string $type
154
	 * @return string
155
	 */
156
	public function getToken( string $type ) : string {
157
		if ( !isset( $this->tokens[ $type ] ) ) {
158
			$params = [
159
				'action' => 'query',
160
				'meta'   => 'tokens',
161
				'type'   => $type
162
			];
163
164
			$req = RequestBase::newFromParams( $params )->setUrl( $this->domain );
165
			$res = $req->execute();
166
167
			$this->tokens[ $type ] = $res->query->tokens->{ "{$type}token" };
168
		}
169
170
		return $this->tokens[ $type ];
171
	}
172
173
	/**
174
	 * Get the timestamp of the creation of the given page
175
	 *
176
	 * @param string $title
177
	 * @return int
178
	 */
179
	public function getPageCreationTS( string $title ) : int {
180
		$params = [
181
			'action' => 'query',
182
			'prop' => 'revisions',
183
			'titles' => $title,
184
			'rvprop' => 'timestamp',
185
			'rvslots' => 'main',
186
			'rvlimit' => 1,
187
			'rvdir' => 'newer'
188
		];
189
190
		$res = RequestBase::newFromParams( $params )->setUrl( $this->domain )->execute();
191
		$data = $res->query->pages;
192
		return strtotime( reset( $data )->revisions[0]->timestamp );
193
	}
194
195
	/**
196
	 * Sysop-level inifinite protection for a given page
197
	 *
198
	 * @param string $title
199
	 * @param string $reason
200
	 */
201
	public function protectPage( string $title, string $reason ) {
202
		$this->logger->info( "Protecting page $title" );
203
		$this->login();
204
205
		$params = [
206
			'action' => 'protect',
207
			'title' => $title,
208
			'protections' => 'edit=sysop|move=sysop',
209
			'expiry' => 'infinite',
210
			'reason' => $reason,
211
			'token' => $this->getToken( 'csrf' )
212
		];
213
214
		RequestBase::newFromParams( $params )->setUrl( $this->domain )->setPost()->execute();
215
	}
216
}
217