11import logging
2+ from dataclasses import dataclass
23from datetime import datetime
3- from typing import List , Optional
4+ from typing import Any , List , Optional
45
56import mwparserfromhell
67from pywikibot .data .api import QueryGenerator
5253EMPTY_COLUMN = "{{center|—}}"
5354
5455
56+ def parse_date (string : str | None ) -> datetime | None :
57+ if string is None :
58+ return None
59+ return datetime .strptime (string , MEDIAWIKI_DATE_FORMAT )
60+
61+
62+ def format_date (date : datetime | None , sortkey : bool = True ) -> str :
63+ if date is None :
64+ return EMPTY_COLUMN
65+
66+ if sortkey :
67+ return 'class="nowrap" data-sort-value={} | {}' .format (
68+ date .strftime (MEDIAWIKI_DATE_FORMAT ), date .strftime (HUMAN_DATE_FORMAT )
69+ )
70+
71+ return date .strftime (HUMAN_DATE_FORMAT )
72+
73+
74+ @dataclass
75+ class Block :
76+ id : int
77+ by : str
78+ reason : str
79+ at : str
80+ expiry : str
81+ partial : bool
82+
83+ def format (self ) -> str :
84+ return "%s by {{no ping|%s}} on %s%s.<br/>Block reason is '%s{{'}}" % (
85+ "Partially blocked" if self .partial else "Blocked" ,
86+ self .by ,
87+ format_date (parse_date (self .at ), sortkey = False ),
88+ (
89+ "to expire at {}" .format (
90+ format_date (parse_date (self .expiry ), sortkey = False )
91+ )
92+ if self .expiry != "infinite"
93+ else ""
94+ ),
95+ self .format_reason (),
96+ )
97+
98+ def format_reason (self ) -> str :
99+ return (
100+ self .reason .replace ("[[Category:" , "[[:Category:" )
101+ .replace ("[[category:" , "[[:category:" )
102+ .replace ("{" , "{" )
103+ .replace ("<" , "<" )
104+ .replace (">" , ">" )
105+ )
106+
107+ @classmethod
108+ def parse (cls , data : dict [str , Any ]) -> "Block" :
109+ return cls (
110+ id = data ["blockid" ],
111+ by = data ["blockedby" ],
112+ reason = data ["blockreason" ],
113+ at = data ["blockedtimestamp" ],
114+ expiry = data ["blockexpiry" ],
115+ partial = data .get ("blockpartial" , False ) is not False ,
116+ )
117+
118+
55119class BotStatusData :
56120 def __init__ (
57121 self ,
58122 * ,
59123 name : str ,
60- operators : List [str ],
61- last_edit_timestamp : Optional [ str ] ,
62- last_log_timestamp : Optional [ str ] ,
63- last_operator_activity_timestamp : Optional [ str ] ,
64- edit_count : Optional [ int ] ,
65- groups : Optional [ List [ str ] ],
66- block_data : Optional [ dict ],
124+ operators : list [str ],
125+ last_edit_timestamp : str | None ,
126+ last_log_timestamp : str | None ,
127+ last_operator_activity_timestamp : datetime | None ,
128+ edit_count : int | None ,
129+ groups : list [ str ],
130+ blocks : list [ Block ],
67131 ):
68132 self .name = name
69133 self .operators = set (operators )
@@ -74,16 +138,16 @@ def __init__(
74138 self .last_operator_activity_timestamp = None
75139
76140 if last_edit_timestamp is not None :
77- self .last_edit_timestamp = self . parse_date (last_edit_timestamp )
141+ self .last_edit_timestamp = parse_date (last_edit_timestamp )
78142 self .last_activity_timestamp = self .last_edit_timestamp
79143
80144 if last_log_timestamp is not None :
81- self .last_log_timestamp = self . parse_date (last_log_timestamp )
145+ self .last_log_timestamp = parse_date (last_log_timestamp )
82146 if self .last_edit_timestamp is None :
83147 self .last_activity_timestamp = self .last_log_timestamp
84148 else :
85149 self .last_activity_timestamp = max (
86- self .last_log_timestamp , self .last_edit_timestamp
150+ self .last_log_timestamp , self .last_edit_timestamp # type: ignore
87151 )
88152
89153 if last_operator_activity_timestamp :
@@ -96,7 +160,7 @@ def __init__(
96160 else :
97161 self .groups = []
98162
99- self .block_data = block_data
163+ self .blocks = blocks
100164
101165 @staticmethod
102166 def new_for_unknown (name : str ) -> "BotStatusData" :
@@ -107,8 +171,8 @@ def new_for_unknown(name: str) -> "BotStatusData":
107171 last_log_timestamp = None ,
108172 last_operator_activity_timestamp = None ,
109173 edit_count = None ,
110- groups = None ,
111- block_data = None ,
174+ groups = [] ,
175+ blocks = [] ,
112176 )
113177
114178 def format_number (self , number : Optional [int ], sortkey = True ):
@@ -119,76 +183,34 @@ def format_number(self, number: Optional[int], sortkey=True):
119183 return 'class="nowrap" data-sort-value={} | {:,}' .format (number , number )
120184 return "{:,}" .format (number )
121185
122- def parse_date (self , string ):
123- if string is None :
124- return None
125- return datetime .strptime (string , MEDIAWIKI_DATE_FORMAT )
126-
127- def format_date (self , date , sortkey = True ):
128- if date is None :
129- return EMPTY_COLUMN
130-
131- if sortkey :
132- return 'class="nowrap" data-sort-value={} | {}' .format (
133- date .strftime (MEDIAWIKI_DATE_FORMAT ), date .strftime (HUMAN_DATE_FORMAT )
134- )
135-
136- return date .strftime (HUMAN_DATE_FORMAT )
137-
138- def format_block_reason (self ):
139- return (
140- self .block_data ["reason" ]
141- .replace ("[[Category:" , "[[:Category:" )
142- .replace ("[[category:" , "[[:category:" )
143- .replace ("{" , "{" )
144- .replace ("<" , "<" )
145- .replace (">" , ">" )
146- )
147-
148- def format_block (self ):
149- date = self .parse_date (self .block_data ["at" ])
150-
151- return (
152- "data-sort-value=%s | %s by {{no ping|%s}} on %s%s.<br/>Block reason is '%s{{'}}"
153- % (
154- date .strftime (MEDIAWIKI_DATE_FORMAT ),
155- "Partially blocked" if self .block_data ["partial" ] else "Blocked" ,
156- self .block_data ["by" ],
157- self .format_date (date , sortkey = False ),
158- (
159- "to expire at {}" .format (
160- self .format_date (self .block_data ["expiry" ], sortkey = False )
161- )
162- if self .block_data ["expiry" ] != "infinite"
163- else ""
164- ),
165- self .format_block_reason (),
166- )
167- )
168-
169- def format_extra_details (self ):
186+ def format_extra_details (self ) -> str :
170187 details = []
171188
172189 if len (self .groups ) > 0 :
173190 details .append ("Extra groups: " + ", " .join (self .groups ))
174- if self .block_data is not None :
175- details .append (self .format_block ())
191+ if self .blocks :
192+ if len (self .blocks ) == 1 :
193+ details .append (self .blocks [0 ].format ())
194+ else :
195+ details .append (
196+ "\n " .join ([f"* { block .format ()} " for block in self .blocks ])
197+ )
176198
177199 return "\n ----\n " .join (details )
178200
179- def to_table_row (self ):
201+ def to_table_row (self ) -> str :
180202 return TABLE_ROW_FORMAT % (
181203 self .name ,
182204 self .format_operators (),
183205 self .format_number (self .edit_count ),
184- self . format_date (self .last_activity_timestamp ),
185- self . format_date (self .last_edit_timestamp ),
186- self . format_date (self .last_log_timestamp ),
187- self . format_date (self .last_operator_activity_timestamp ),
206+ format_date (self .last_activity_timestamp ),
207+ format_date (self .last_edit_timestamp ),
208+ format_date (self .last_log_timestamp ),
209+ format_date (self .last_operator_activity_timestamp ),
188210 self .format_extra_details (),
189211 )
190212
191- def format_operators (self ):
213+ def format_operators (self ) -> str :
192214 if len (self .operators ) == 0 :
193215 return EMPTY_COLUMN
194216 return "{{no ping|" + "}}, {{no ping|" .join (sorted (self .operators )) + "}}"
@@ -224,16 +246,13 @@ def get_bot_data(self, username):
224246 if "query" in data :
225247 data = data ["query" ]
226248
227- block = None
228- if "blockid" in data ["users" ][0 ]:
229- block = {
230- "id" : data ["users" ][0 ]["blockid" ],
231- "by" : data ["users" ][0 ]["blockedby" ],
232- "reason" : data ["users" ][0 ]["blockreason" ],
233- "at" : data ["users" ][0 ]["blockedtimestamp" ],
234- "expiry" : data ["users" ][0 ]["blockexpiry" ],
235- "partial" : "blockpartial" in data ["users" ][0 ],
236- }
249+ blocks = []
250+ if "blockcomponents" in data ["users" ][0 ]:
251+ blocks = [
252+ Block .parse (block ) for block in data ["users" ][0 ]["blockcomponents" ]
253+ ]
254+ elif "blockid" in data ["users" ][0 ]:
255+ blocks .append (Block .parse (data ["users" ][0 ]))
237256
238257 operators = []
239258 for page_id in data ["pages" ]:
@@ -322,7 +341,7 @@ def get_bot_data(self, username):
322341 last_operator_activity_timestamp = operator_activity ,
323342 edit_count = data ["users" ][0 ]["editcount" ],
324343 groups = data ["users" ][0 ]["groups" ],
325- block_data = block ,
344+ blocks = blocks ,
326345 )
327346
328347 raise Exception ("Failed loading bot data for " + username + ": " + str (data ))
0 commit comments