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