User:Uo4tie/bot
Python code to copy the informations in the sidebar of the Key:* and Tag:* description pages to the data item via MediaWiki Action API. See the Not copied ... categories in Category:Data item issues.
Copyright (C) 2026 uo4tie
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
#!/usr/bin/python3 """ # SPDX-License-Identifier: AGPL-3.0-or-later """ import wikibase # wikibase.copy_to_wikibase(API,Category,language,template-parameter,for statment: p-number) # wikibase.copy_to_wikibase("wbsetdescription","Category:Not_copied_description","DE","description",None) # description # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not_copied_image","DE","image","P28") # image # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied group","DE","group","P25") # group # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied onNode","DE","onNode","P33") # onNode # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied onWay","DE","onWay","P34") # onWay # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied onArea","DE","onArea","P35") # onArea # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied onRelation","DE","onRelation","P36") # onRelation # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied osmcarto-rendering","DE","osmcarto-rendering","P39") # osmcarto-rendering # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied status","DE","status","P6") # status # wikibase.copy_to_wikibase("wbcreateclaim","Category:Not copied statuslink","DE","statuslink","P25") # statuslink
#!/usr/bin/python3 """ adapted from MediaWiki API Demos at https://www.mediawiki.org/wiki/API:Categorymembers under MIT License; assisted by AI # SPDX-License-Identifier: AGPL-3.0-or-later """ import requests import login import wikitext_parser import category import json def print_tee(*args): with open("failure.txt", "a", encoding="utf-8") as f: print(*args) print(*args, file=f) def strip_language_prefix(page_title): LANGUAGES=[ 'Af:', 'Ar:', 'Ast:', 'Az:', 'Be:', 'Bg:', 'Bn:', 'Br:', 'Bs:', 'Ca:', 'Cnr:', 'Cs', 'Da:', 'DE:', 'Diq:', 'El:', 'en:', 'Eo:', 'ES:', 'Et:', 'Eu:', 'Fa:', 'Fi:', 'FR:', 'Fy', 'Gcf:', 'Gd:', 'Gl:', 'He:', 'Hi:', 'Hr:', 'Ht:', 'Hu:', 'Hy:', 'Ia:', 'Id:', 'Io:', 'Is', 'IT:', 'JA:', 'Ka:', 'Ko:', 'Ku:', 'Lb:', 'Lt:', 'Lv:', 'Mk:', 'Ml:', 'Mn:', 'Mr:', 'Ms', 'My:', 'Ne:', 'NL:', 'Nn:', 'No:', 'Oc:', 'Pa:', 'Pl:', 'Pnb:', 'Ps:', 'Pt:', 'Ro:', 'RU', 'Sc:', 'Shn:', 'Si:', 'Sk:', 'Skr:', 'Sl:', 'Sq:', 'Sr:', 'Sr-latn:', 'Sv:', 'Ta:', 'Th', 'Tl:', 'Tr:', 'Tzm:', 'Uk:', 'Vi:', 'Yue:', 'Zh-hans:', 'Zh-hant:' ] for lang in LANGUAGES: if page_title.startswith(lang): return page_title[len(lang):] return page_title def get_data_item(page_title): import requests page_title = strip_language_prefix(page_title) S = requests.Session() URL = "https://test.wiki.openstreetmap.org/w/api.php" PARAMS = { "action": "query", "prop": "pageprops", "ppprop": "wikibase_item", "titles": page_title, "format": "json" } R = S.get(url=URL, params=PARAMS) DATA = R.json() PAGES = DATA['query']['pages'] page = list(PAGES.values())[0] if page.get('pageid') == '-1': return None item = page.get('pageprops', {}).get('wikibase_item') if not item: return None return item def get_description(page): import requests S = requests.Session() URL = "https://test.wiki.openstreetmap.org/w/api.php" LANGUAGES=[ 'Af:', 'Ar:', 'Ast:', 'Az:', 'Be:', 'Bg:', 'Bn:', 'Br:', 'Bs:', 'Ca:', 'Cnr:', 'Cs', 'Da:', 'DE:', 'Diq:', 'El:', 'en:', 'Eo:', 'ES:', 'Et:', 'Eu:', 'Fa:', 'Fi:', 'FR:', 'Fy', 'Gcf:', 'Gd:', 'Gl:', 'He:', 'Hi:', 'Hr:', 'Ht:', 'Hu:', 'Hy:', 'Ia:', 'Id:', 'Io:', 'Is', 'IT:', 'JA:', 'Ka:', 'Ko:', 'Ku:', 'Lb:', 'Lt:', 'Lv:', 'Mk:', 'Ml:', 'Mn:', 'Mr:', 'Ms', 'My:', 'Ne:', 'NL:', 'Nn:', 'No:', 'Oc:', 'Pa:', 'Pl:', 'Pnb:', 'Ps:', 'Pt:', 'Ro:', 'RU', 'Sc:', 'Shn:', 'Si:', 'Sk:', 'Skr:', 'Sl:', 'Sq:', 'Sr:', 'Sr-latn:', 'Sv:', 'Ta:', 'Th', 'Tl:', 'Tr:', 'Tzm:', 'Uk:', 'Vi:', 'Yue:', 'Zh-hans:', 'Zh-hant:' ] wbptlanguage = None title = page for prefix in LANGUAGES: if page.startswith(prefix): wbptlanguage = prefix[:-1].lower() title = page[len(prefix):] break # if no prefix is found, use en if wbptlanguage is None: wbptlanguage = "en" PARAMS = { "action": "query", "prop": "pageterms", "titles": title, "wbptterms": "description", "wbptlanguage": wbptlanguage, "format": "json" } R = S.get(url=URL, params=PARAMS) DATA = R.json() PAGES = DATA["query"]["pages"] if "-1" in PAGES: return None page_data = next(iter(PAGES.values())) if "terms" in page_data: descriptions = page_data["terms"].get("description", []) if descriptions: return descriptions[0] return None def write_wikibase(session, entity_id, language, value, summary, bot_flag, csrftoken, action, p, copy): params = { "action": action, "id": entity_id, "language": language, "value": value, "summary": summary, "token": csrftoken, "format": "json" } if p: # if create claim params = { "action": action, "entity": entity_id, "property": p, "snaktype": "value", "value": value, "summary": summary, "token": csrftoken, "format": "json" } if copy == "image" or copy == "osmcarto-rendering": # if file if value.startswith("File:"): valuef = value[5:] params["value"] = json.dumps(valuef) # remove file: else: return copy+" is no media file" if copy == "onNode" or copy == "onWay" or copy == "onArea" or copy == "onRelation": if value == "yes": params["value"] = json.dumps({ "entity-type":"item", "numeric-id":8000 }) elif value == "no": params["value"] = json.dumps({ "entity-type":"item", "numeric-id":8001 }) else: return copy+" is unspecifyed" if copy == "group": groups={ "properties": "4671", "restrictions": "4672", "highways": "4673", "annotations": "4674", "railways": "4675", "natural": "4676", "maritime": "4677", "man made": "4678", "amenities": "4679", "power": "4680", "references": "4681", "addresses": "4682", "shops": "4683", "names": "4684", "historic": "4685", "buildings": "4686", "lifecycle": "4687", "tourism": "4688", "sports": "4689", "waterways": "4690", "aeroways": "4691", "religion": "4692", "barriers": "4693", "places": "4694", "routes": "4695", "boundaries": "4696", "marine": "4697", "military": "4698", "social networks": "4699", "accessibility": "4700", "bicycles": "4701", "landuse": "4702", "cycleways": "4704", "public transport": "4705", "placement": "4707", "food and beverages": "4708", "leisure": "4709", "aerialways": "4710", "bridges": "4712", "crafts": "5149", "offices": "19544", "water management": "19791", "agriculture": "19792", "advertising": "21406", "hazards": "21472", "health": "21698", "parking": "21703", "marine navigation": "21865", "emergencies": "21935", "crossings": "21939", "animals": "21940", "clubs": "21941", "social facilities": "21942", "vending machines": "21943", "education": "21945", "diplomacy": "21953", "ferries": "22093", "hiking": "22096", "waste processing": "22141", "water": "22143", "pipelines": "22250", "infrastructure": "22257", "weather": "22993", "climbing": "23061", "children": "23366", "suitable for children": "23453", "not suitable for children": "23454", "for adults only": "23456", "coffee": "23550", "sustainability": "23565", "circular economy": "23566", "top-level": "23586", "metro": "23614", "communications": "23726", "meals": "23751", "point of interest": "23782", "subkey": "23785", "vague": "23800", "payment methods": "23910", "currencies": "23921", "drinks": "24003" } params["value"] = json.dumps({ "entity-type":"item", "numeric-id": groups.get(value) }) if copy == "status": status={ "approved": "15", "voting": "17", "proposed": "20", "de facto": "13", "in use": "14", "deprecated": "5061", "discardable": "7550", "imported": "21146", "obsolete": "5060", "undefined": "22135" } params["value"] = json.dumps({ "entity-type":"item", "numeric-id": status.get(value) }) if bot_flag: params["bot"] = "true" WIKI_URL = "https://test.wiki.openstreetmap.org/w/api.php" response = session.post(url=WIKI_URL, data=params) data = response.json() return data def copy_to_wikibase(action,cat,lang,copy,p): # get login S, csrf_token = login.login() print("-----------------") # get pages pages = category.get_category_members_for_language(cat,lang) for page in pages: ENTITY_ID = get_data_item(page) if ENTITY_ID == None: print_tee("❌ Failure.",page+":","Entitiy ID is empty:",ENTITY_ID) print("-----------------") continue LANGUAGE = lang.lower() if page.startswith(str(lang+":Key:")) or page.startswith("Key:"): if page.endswith(":*"): VALUE = wikitext_parser.get_param_for_page(page, "KeyPrefixDescription", copy) else: VALUE = wikitext_parser.get_param_for_page(page, "KeyDescription", copy) elif page.startswith(str(lang+":Tag:")) or page.startswith("Tag:"): VALUE = wikitext_parser.get_param_for_page(page, "ValueDescription", copy) else: print_tee("❌ Failure.",page+"do not start with",str(lang+":Key:"),"or",str(lang+":Tag:")) print("-----------------") continue if VALUE == None: print_tee("❌ Failure.",page+":","Couldn't get the",copy,"parameter:",VALUE) print("-----------------") continue if "{{" in VALUE: print_tee("❌ Failure.",page+":","In",copy," is a template: ",VALUE) print("-----------------") continue SUMMARY = "Bot: Test: Updating "+copy+" from wiki page [["+page+"]]" BOT_FLAG = True # write print(f"write {copy} for {page } to {ENTITY_ID} ({LANGUAGE})...") result = write_wikibase( S, ENTITY_ID, LANGUAGE, VALUE, SUMMARY, BOT_FLAG, csrf_token, action, p, copy ) # print result if "nochange" in result: print_tee("⚠️ No change!") print_tee(result) elif "success" in result: print("✅ Sucess!") print(f"Result: {result}") else: print_tee("❌ Failure:") if "Anti-Missbrauchs-Maßnahme" in str(result): print_tee("Limit!") print_tee(result) break print_tee(result) print("-----------------")
#! /usr/bin/python3 """ adapted from MediaWiki API Demos at https://www.mediawiki.org/wiki/API:Categorymembers under MIT License; assisted by AI # SPDX-License-Identifier: AGPL-3.0-or-later """ def extract_param(data, template_name, param_name): text = data.get("parse", {}).get("wikitext", {}).get("*", "") # --- 1. Die gesuchte Vorlage inkl. Verschachtelung isolieren --- start = text.find("{{" + template_name) if start == -1: return None depth, i = 1, start + 2 while i < len(text) and depth: if text.startswith("{{", i): depth += 1; i += 2 elif text.startswith("}}", i): depth -= 1; i += 2 else: i += 1 body = text[start + 2 : i - 2] # Inhalt zwischen {{ ... }} # --- 2. Body an Top-Level-Pipes '|' trennen (ignoriert | in {{...}}) --- parts, cur, depth, eq = [], "", 0, -1 j = 0 while j < len(body): if body.startswith("{{", j): depth += 1; cur += "{{"; j += 2 elif body.startswith("}}", j): depth -= 1; cur += "}}"; j += 2 else: ch = body[j]; j += 1 if depth == 0 and ch == "|": parts.append((cur, eq)); cur, eq = "", -1 else: if depth == 0 and ch == "=" and eq == -1: eq = len(cur) # erstes '=' auf oberster Ebene merken cur += ch parts.append((cur, eq)) # --- 3. Gesuchten Parameter heraussuchen --- for value, eq in parts: if eq > -1 and value[:eq].strip().lower() == param_name.strip().lower(): return value[eq + 1:].strip() return None def get_param_for_page(page_title,template,param): import requests S = requests.Session() URL = "https://test.wiki.openstreetmap.org/w/api.php" PARAMS = { "action": "parse", "page": page_title, "prop": "wikitext", "format": "json" } R = S.get(url=URL, params=PARAMS) DATA = R.json() return extract_param(DATA, template, param)
#!/usr/bin/python3 """ adapted from MediaWiki API Demos at https://www.mediawiki.org/wiki/API:Categorymembers under MIT License; assisted by AI # SPDX-License-Identifier: AGPL-3.0-or-later """ def get_category_members_for_language(category, language: str) -> list: import requests S = requests.Session() URL = "https://test.wiki.openstreetmap.org/w/api.php" # Languages with their sortkey prefixes (in alphabetical order) LANGUAGES=['Af', 'Ar', 'Ast', 'Az', 'Be', 'Bg', 'Bn', 'Br', 'Bs', 'Ca', 'Cnr', 'Cs', 'Da', 'DE', 'Diq', 'El', 'en', 'Eo', 'ES', 'Et', 'Eu', 'Fa', 'Fi', 'FR', 'Fy', 'Gcf', 'Gd', 'Gl', 'He', 'Hi', 'Hr', 'Ht', 'Hu', 'Hy', 'Ia', 'Id', 'Io', 'Is', 'IT', 'JA', 'Ka', "Key:", 'Ko', 'Ku', 'Lb', 'Lt', 'Lv', 'Mk', 'Ml', 'Mn', 'Mr', 'Ms', 'My', 'Ne', 'NL', 'Nn', 'No', 'Oc', 'Pa', 'Pl', 'Pnb', 'Ps', 'Pt', 'Ro', 'RU','Sc', 'Shn', 'Si', 'Sk', 'Skr', 'Sl', 'Sq', 'Sr', 'Sr-latn', 'Sv', 'Ta', "Tag:", 'Th','Tl', 'Tr', 'Tzm', 'Uk', 'Vi', 'Yue', 'Zh-hans', 'Zh-hant'] # test if language is in list try: index = LANGUAGES.index(language) except ValueError: print(f"get_category_members_for_language: Language '{language}' not found") return [] start_prefix = language end_prefix = LANGUAGES[index + 1] if index + 1 < len(LANGUAGES) else None PARAMS = { "action": "query", "cmtitle": category, "cmlimit": "2", "list": "categorymembers", "format": "json", "cmsort": "sortkey", "cmdir": "ascending", "cmstartsortkeyprefix": start_prefix } if end_prefix: PARAMS["cmendsortkeyprefix"] = end_prefix R = S.get(url=URL, params=PARAMS) DATA = R.json() PAGES = DATA.get("query", {}).get("categorymembers", []) # Filter pages that actually start with the language code filtered_titles = [p['title'] for p in PAGES if p.get('title', '').startswith(start_prefix)] return filtered_titles
#!/usr/bin/python3 """ adapted from MediaWiki API Demos at https://www.mediawiki.org/wiki/API:Categorymembers under MIT License; assisted by AI # SPDX-License-Identifier: AGPL-3.0-or-later """ import requests import sys # Configuration - Set these values or pass as arguments BOT_USERNAME = "" # Format: MainAccountName@BotName BOT_PASSWORD = "" # Generated via Special:BotPasswords WIKI_URL = "https://test.wiki.openstreetmap.org/w/api.php" def get_login_token(session): #1. get login token params = { "action": "query", "meta": "tokens", "type": "login", "format": "json" } response = session.get(url=WIKI_URL, params=params) data = response.json() if "query" in data and "tokens" in data["query"]: return data["query"]["tokens"]["logintoken"] else: raise Exception(f"Fehler beim Abrufen des Login-Tokens: {data}") def login_with_bot_password(session, username, password, login_token): #2. login with bot passwort params = { "action": "login", "lgname": username, "lgpassword": password, "lgtoken": login_token, "format": "json" } response = session.post(url=WIKI_URL, data=params) data = response.json() if data.get("login", {}).get("result") == "Success": print("Erfolgreich eingeloggt") return True elif data.get("login", {}).get("result") == "NeedToken": # Zweiter Schritt bei Some Wikis login_token = data["login"]["token"] params["lgtoken"] = login_token response = session.post(url=WIKI_URL, data=params) data = response.json() if data.get("login", {}).get("result") == "Success": print("Erfolgreich eingeloggt (2-Schritte)") return True else: raise Exception(f"Login fehlgeschlagen: {data}") else: raise Exception(f"Login fehlgeschlagen: {data}") def get_csrf_token(session): #3. get CSRF token params = { "action": "query", "meta": "tokens", "format": "json" } response = session.get(url=WIKI_URL, params=params) data = response.json() if "query" in data and "tokens" in data["query"]: return data["query"]["tokens"]["csrftoken"] else: raise Exception(f"Fehler beim Abrufen des CSRF-Tokens: {data}") def login(): # create session S = requests.Session() try: #1. get login token print("get login token ...") login_token = get_login_token(S) #2. login with bot passwort print("logging in ...") login_with_bot_password(S, BOT_USERNAME, BOT_PASSWORD, login_token) #3. get CSRF-Token print("getting SRF token ...") csrf_token = get_csrf_token(S) except Exception as e: print(f"❌ Failure: {e}") sys.exit(1) return S, csrf_token