2 # Someone's Mattermost scripts.
3 # Copyright (c) 2016-2020 by Someone <someone@somenet.org> (aka. Jan Vales <jan@jvales.net>)
4 # published under MIT-License
26 REXtuwien = re.compile(r'^TU Wien\:(.*) \w\w,?\w?\w? \(.*\).*$', re.IGNORECASE)
27 REXdaten = re.compile(r'==\s*Daten\s*==', re.MULTILINE|re.DOTALL|re.IGNORECASE)
28 REXwdata_mmchan = re.compile(r'\*??\s*\{\{mattermost-channel\|([a-zA-Z0-9_ -]+)\}\}\s*\n{0,2}', re.IGNORECASE)
30 def get_smw_properties(subject, sobj_dict={'sobj': {}}):
33 if prop['property'] not in ret:
34 ret[prop['property']] = []
35 for dataitem in prop['dataitem']:
36 if dataitem['type'] == 9 and dataitem['item'] in sobj_dict['sobj']:
37 ret[prop['property']] += [sobj_dict['sobj'][dataitem['item']]]
39 ret[prop['property']] += [dataitem['item']]
42 def query_smw_properties(site, pagename):
43 res = site.api('browsebysubject', subject=pagename)
45 # gather all sub objects first
47 if 'sobj' in res['query']:
48 for sobj in res['query']['sobj']:
49 ret['sobj'][sobj['subject']] = get_smw_properties(sobj['data'])
51 ret = get_smw_properties(res['query']['data'], ret)
58 def mw_pagename_to_mm_chan_mapping(pagename):
60 mmchdisplayname = REXtuwien.search(pagename).group(1)
61 vowiurlname = mmchdisplayname.replace(' ', '_')
62 mmchname = mmchdisplayname.lower().replace('ä', 'ae').replace('ö', 'oe').replace('ü', 'ue')
63 mmchdisplayname = mmchdisplayname[:63]
65 mmchname = re.sub(r'[^a-zA-Z0-9_]', r'-', mmchname)
66 mmchname = re.sub(r'-+', r'-', mmchname)
67 mmchname = mmchname[:63]
68 mmchname = mmchname.strip('-')
70 # now even group-channels (len == 40) do weird shit. Thanks MM :/
71 # Heisen-Issue: for some reason MM uses sometimes the channel name as id if len == 26 gives and when switching between teams.
72 if len(mmchname) == 54 or len(mmchname) == 40 or len(mmchname) == 26:
73 mmchname = mmchname+'0'
75 return (mmchname, (mmchname, mmchdisplayname, vowiurlname, pagename))
77 print('Failed to process pagemapping for: '+pagename)
78 return ("", ("", "", "", ""))
86 def create_update_spam_mm_channel(mws, mma, basepage_name, changes_to_notify_about=dict()):
87 print('\ncreate_update_spam_mm_channel(): https://vowi.fsinf.at/wiki/'+basepage_name.replace(' ', '_').replace(')', '%29'))
89 # really purge cache :/
90 sp = mws.Pages[basepage_name]
93 if not sp.exists or sp.redirect:
94 print('Page was deleted or moved. ignoring.')
97 # get smw data of basepage
98 semantic_data = query_smw_properties(mws, sp.name)
99 sp_text_daten = sp_text_daten_orig = sp.text(section=1)
100 if REXdaten.match(sp_text_daten) is None:
101 raise Exception('"== Daten ==" not section=1')
104 if 'Ist_veraltet' in semantic_data and semantic_data['Ist_veraltet'][0] == 't':
105 print('Page is veraltet. ignoring.')
109 basepage_name_mapping = mw_pagename_to_mm_chan_mapping(basepage_name)
111 metadata['display_name'] = basepage_name_mapping[1][1]
112 metadata['purpose'] = 'LVA(s) im VoWi: https://vowi.fsinf.at/LVA/'+basepage_name_mapping[1][2]+'?mm'
113 metadata['header'] = 'Links: [LVA(s) im VoWi](https://vowi.fsinf.at/LVA/'+basepage_name_mapping[1][2]+'?mm)'
115 if 'Hat_Kurs-ID' in semantic_data:
116 metadata['header'] += ' - [LVA in TISS](https://tiss.tuwien.ac.at/course/courseDetails.xhtml?courseNr='+semantic_data['Hat_Kurs-ID'][0]+')'
117 if 'Hat_Homepage' in semantic_data:
118 metadata['header'] += ' - [LVA-HP]('+semantic_data['Hat_Homepage'][0]+')'
120 # create channel, if not exist
121 mmchan = mma.get_channel_by_name(config.mm_autochannels_team, basepage_name_mapping[0])
122 if 'status_code' in mmchan and mmchan['status_code'] == 404:
123 print(mma.create_channel(config.mm_autochannels_team, basepage_name_mapping[0], metadata['display_name'], metadata['purpose'], metadata['header']))
126 mmchan = mma.get_channel_by_name(config.mm_autochannels_team, basepage_name_mapping[0])
129 # update wiki:data-section. always.
130 sp_text_daten = re.sub(REXwdata_mmchan, '', sp_text_daten)
131 sp_text_daten = sp_text_daten.strip()
132 sp_text_daten += '\n{{mattermost-channel|'+basepage_name_mapping[0]+'}}'
133 if sp_text_daten != sp_text_daten_orig:
134 print('Updating MW page: '+str(sp.save(sp_text_daten, section=1, summary='mw_vowi')))
138 if 'type' in mmchan and mmchan['type'] == 'O' and ((sp_text_daten != sp_text_daten_orig) or (len(changes_to_notify_about) > 0)):
139 mma.patch_channel(mmchan['id'], metadata)
141 # spam channel if there is anything to spam about
142 if len(changes_to_notify_about) > 0:
143 msg = '``BOT-AUTODELETE-SLOW``\nFolgende Seiten wurden in den letzten 24 Stunden im VoWi editiert:\n'
144 for v in changes_to_notify_about.values():
145 msg += '\nhttps://vowi.fsinf.at/wiki/'+v['title'].replace(' ', '_')
146 msg += '\n\n``footer``\n'+str(random.choice(config.mm_footerlist))
147 print('Posting to channel:' +str(mma.create_post(mmchan['id'], msg)))
151 def process_recent_vowi_changes(mws, mma):
154 with open('data/last.json') as fh:
155 lastdata = json.load(fh)
157 print('Failed to load last.json')
160 if 'failed' in lastdata:
161 changes = lastdata['failed']
163 if 'delme' in lastdata:
164 del lastdata['delme']
166 if 'timestamp' in lastdata:
167 rc = mws.recentchanges(start=lastdata['timestamp'], dir='newer', namespace=3000, show='!bot|!redirect|!minor', toponly=True)
169 rc = mws.recentchanges(dir='newer', namespace=3000, show='!bot|!redirect|!minor', toponly=False)
170 lastdata['timestamp'] = 0
171 lastdata['last_timestamp'] = lastdata['timestamp']
173 if 'rcid' not in lastdata:
177 if c['rcid'] == lastdata['rcid']:
180 basepage = c['title'].split('/', 1)[0]
181 if basepage not in changes:
182 changes[basepage] = dict()
183 changes[basepage][c['title']] = c
184 changes[basepage][c['title']]['timestamp'] = int(time.strftime('%Y%m%d%H%M%S', changes[basepage][c['title']]['timestamp']))
186 if changes[basepage][c['title']]['timestamp'] > lastdata['timestamp']:
187 lastdata['timestamp'] = changes[basepage][c['title']]['timestamp']
188 if changes[basepage][c['title']]['rcid'] > lastdata['rcid']:
189 lastdata['rcid'] = changes[basepage][c['title']]['rcid']
191 lastdata['failed'] = changes
193 # process all pending changes
194 for ch in list(changes):
195 semantic_data = query_smw_properties(mws, ch)
197 # never mind, its outdated anyway ...
198 if 'Ist_veraltet' in semantic_data and semantic_data['Ist_veraltet'][0] == 't':
199 del lastdata['failed'][ch]
204 create_update_spam_mm_channel(mws, mma, ch, changes[ch])
205 del lastdata['failed'][ch]
206 except Exception as ex:
207 print('Exception, skipping: '+ch+' - '+str(sys.exc_info()))
208 traceback.print_exc()
210 with open('data/last.json', 'w') as fh:
211 json.dump(lastdata, fh)
213 if len(lastdata['failed']) != 0:
214 print('Some changes failed to get posted!')
215 pprint.pprint(lastdata)
219 def process_all_LVAs(mws, mma):
220 for p in mws.ask('[[TU Wien:+]][[Ist veraltet::0]]'):
221 create_update_spam_mm_channel(mws, mma, ''.join(p))
225 # remove "has mm-channel" info from outdated lva pages.
226 def process_outdated_LVAs(mws):
227 for p in mws.ask('[[Hat Mattermost-Channel::+]][[Ist veraltet::1]]'):
228 sp = mws.Pages[(''.join(p.keys()))]
229 print('\nProcessing: https://vowi.fsinf.at/wiki/'+sp.name.replace(' ', '_').replace(')', '%29'))
231 sp_text = sp.text(section=1)
233 sp_text = re.sub(REXwdata_mmchan, '', sp_text)
235 print(sp.save(sp_text, section=1, summary='mw_vowi'))
240 def process_outdated_MMChannels(mws, mma, spam_channel=False):
241 # get a dict of all lva-pages that have a mm-channel saved and map them to all the possible channel-names.
242 # Legitimate (not outdated LVAs)
243 mw_page_names_with_mm_channelname_mappings = dict([mw_pagename_to_mm_chan_mapping(c) for c in [''.join(p) for p in mws.ask('[[Hat Mattermost-Channel::+]]')]])
245 # All existing mm channels
246 mm_all_channels_with_infos = {mmchan['name']:mmchan for mmchan in mma.get_team_channels(config.mm_autochannels_team)}
248 # outdated candidates.
249 diff_channel_names = set(mm_all_channels_with_infos.keys()) - set(mw_page_names_with_mm_channelname_mappings.keys())
250 diff_channel_names.discard('town-square')
251 diff_channel_names.discard('off-topic')
254 # dict of all outdated LVA-pages.
255 mw_outdated_page_names_mappings = dict([mw_pagename_to_mm_chan_mapping(c) for c in [''.join(p) for p in mws.ask('[[TU Wien:+]][[Kategorie:LVAs]][[Ist veraltet::1]]')]])
256 # TODO: seems b0rked.
257 # mw_lva_ersetzt_durch = dict()
258 # for p in mws.ask('[[Ersetzt durch::+]]|?Ersetzt durch'):
259 # for k,v in p.items():
260 # mw_lva_ersetzt_durch[k] = v['printouts']['Ersetzt durch'][0]
262 # mw_lva_wirklich_ersetzt_durch = mw_lva_ersetzt_durch.copy()
263 # pprint.pprint(mw_lva_wirklich_ersetzt_durch)
265 # for k,v in mw_lva_ersetzt_durch.items():
267 # while ersatz in mw_lva_ersetzt_durch:
268 # ersatz = mw_lva_ersetzt_durch[ersatz]
269 # mw_lva_wirklich_ersetzt_durch[k] = ersatz
273 msg = '``BOT-AUTODELETE-SLOW``\n#### ``Likely outdated MM channels found``\nClick ``Next...`` to load all channels, to make these links work\nCheck via vowi if there is really no not-outdated LVA-page.\n'
274 msg_inconsistent = ''
275 real_diff_channel_names = diff_channel_names.copy()
276 for d in diff_channel_names:
278 msg += ' + ~'+d+' :arrow_right: https://vowi.fsinf.at/wiki/Spezial:FlexiblePrefix/'+mw_outdated_page_names_mappings[d][2]+'\n'
280 msg_inconsistent += ' + ~'+d+' :warning: ``No known VoWi URL - SWM-Consistency issue?``\n'
281 real_diff_channel_names.discard(d)
283 if len(diff_channel_names) == 0:
284 msg = '``BOT-AUTODELETE-SLOW``\n#### `` No outdated MM channels found`` :)\n'
286 if len(msg_inconsistent) > 0:
287 msg += '\n\nThese ('+str(len(diff_channel_names)-len(real_diff_channel_names))+') are likely SMW inconsistencies and were skipped:\n'+msg_inconsistent
288 msg += 'If there are many of these, consider running ``SemanticMediaWiki/maintenance/rebuildData.php``'
290 # make real_ the real deal
291 diff_channel_names = real_diff_channel_names
292 del real_diff_channel_names
294 print('Posting '+str(len(msg))+' chars to channel:' +str(mma.create_post(config.mm_admin_channel, msg)))
301 for mmchan in diff_mmchan_names:
302 print('notifying: '+mmchan)
305 metadata['purpose'] = 'Outdated? Dieser LVA-Channel wurde als outdated markiert, da alle seine VoWi-LVAs outdated sind. LVA(s) im VoWi: https://vowi.fsinf.at/LVA/'+mw_mmchans_full_all[mmchan][2].replace(' ', '_')+'?mm'
306 metadata['header'] = ':warning: Outdated? :warning: Links: [Outdated LVA(s) im VoWi](https://vowi.fsinf.at/LVA/'+mw_mmchans_full_all[mmchan][2].replace(' ', '_')+'?mm)'
308 metadata['purpose'] = 'Outdated? Dieser LVA-Channel wurde als outdated markiert, da alle seine VoWi-LVAs outdated sind. Sollte dies ein Fehler sein, korrigiere bitte die entsprechende LVA im VoWi.'
309 metadata['header'] = ':warning: Outdated? :warning: Dieser LVA-Channel wurde als outdated markiert, da alle seine VoWi-LVAs outdated sind.'
311 # update channel info.
312 print(mma.patch_channel(mm_mmchans_full[mmchan]['id'], metadata))
314 # post "something changed" info
315 msg = ('``BOT-AUTODELETE-SLOW``\n### Dieser LVA-Channel wurde als outdated markiert, da alle seine VoWi-LVAs outdated sind.\n'
316 'Sollte dies ein Fehler sein, korrigiere bitte die entsprechende LVA-Seite im VoWi.\n'
317 '``Falls die LVA von einer neuen Person übernommen wurde und sich dabei die Durchführung grundlegend geändert hat, oder dies zu erwarten ist, lege bitte eine neue LVA-Seite an.``\n\n'
318 '## :warning: :warning: :warning: :warning: :warning: :warning: :warning:\n\n'
322 msg += ':arrow_right: Die Nachfolge-LVA laut VoWi ist: https://vowi.fsinf.at/LVA/'+mw_lva_ersetzt_durch[mmchan].replace(' ', '_')+' \n'
324 msg += ':warning: Eine Nachfolge-LVA ist nicht bekannt, im VoWi nicht vermerkt, oder konnte nicht ermittelt werden :(\n'
326 msg += ':arrow_right: Der Nachfolge-LVA-MM-Channel dürfte sein: ~'+mw_pagename_to_mm_chan_mapping(mw_lva_ersetzt_durch[mmchan])[0]
328 msg += ':warning: Ein Nachfolge-LVA-MM-Channel konnte nicht ermittelt werden :('
330 print('Posting to channel:' +str(mma.create_post(mm_mmchans_full[mmchan]['id'], msg)))
334 if __name__ == '__main__':
335 def signal_handler(signal, frame):
336 print('SIG received. exitting!')
338 signal.signal(signal.SIGINT, signal_handler)
340 mws = mwclient.Site(config.mw_name, path='/', retry_timeout=120)
341 mws.login(config.mw_user, config.mw_user_pw)
342 mma = mattermost.MMApi(config.mm_api_url)
343 mma.login(config.mm_user, config.mm_user_pw)
345 # Use recent changes to create, update and spam channels.
346 if len(sys.argv) == 1:
347 process_recent_vowi_changes(mws, mma)
350 # "full" run modes - We cant run the whole script at once, due to out db-backup, likely killing our run mid-excecution. :(
351 # "new" should actually not really be needed, as it should be covered by incremental changes.
352 if len(sys.argv) > 1 and sys.argv[1] == 'all-pages':
354 process_all_LVAs(mws, mma)
356 if len(sys.argv) > 1 and sys.argv[1] == 'outdated-pages':
358 process_outdated_LVAs(mws)
360 if len(sys.argv) > 1 and sys.argv[1] == 'outdated-chans':
362 process_outdated_MMChannels(mws, mma, False)
364 if len(sys.argv) > 1 and sys.argv[1] == 'outdated-chans-spam':
366 process_outdated_MMChannels(mws, mma, True)
369 mma.revoke_user_session()