Wikipedia／WiktionaryのページIDでの記述の必要性について＆Python 3を利用したページIDのクリップボード経由で取得

# はじめの前のおねがい
できれば「いいね♡」をお願いします。励みになります。

# はじめに
このコードは**Python 3**と**Pythonista 3**に対応しています。ただし、別途``request``モジュールなどが必要です。

# 本コードを実行するとどうなるか
本コードは
1. クリップボード内のWikipediaおよびWiktionaryのURLを検知
1. ページIDを取得
1. ページID参照がされたWikipediaおよびWiktionaryのURLを再コピー

します。その際に、もしも下記の条件の場合、
1. 既にIDに変換されている場合 → 「既に変換済みです」を返します
1. 該当する記事がない場合 → 「変換不可能なリンクです」を返します

となります。

また、おまけ機能として、**そもそもWikipediaおよびWiktionaryのURLではない場合**はANSI文字を**URLエンコード**します。

# ページIDの必要性　— このコードのraison dʼêtre
## Wikipedia／Wiktionaryの記事には、それぞれIDが振り分けられている
例えば、Wikipediaの[Frederik Kortlandt先生の記事](https://en.wikipedia.org/wiki/Frederik_Kortlandt)を考えてみましょう。

Wikipediaの標準的な[https://en.wikipedia.org/wiki/Frederik_Kortlandt](https://en.wikipedia.org/wiki/Frederik_Kortlandt)というURLは見出し語が分かりやすいように、このようなURLの形で表記して記事が見られるようになっているだけで、実体の記事は1項目に1番号のみが割り振られ、“3064937”のようなID番号として「Wikipedia記事ID」が割り振られており、[https://en.wikipedia.org/?curid=3064937](https://en.wikipedia.org/?curid=3064937)という表記は「その記事IDの記事を表示せよ」というものになっています。

これによって、将来的に悪意ある編集者が見出し語を変更したり、あるいは誤表記だったことが分かり見出し語の変更がされ、かつその変更の際「転送の設定がされなかった場合」に、リンク切れになってしまうのを防ぐことができます。

## 編集者によるタイトル変更での弊害
実際に言語学者“Szemerényi Oszvald”の日本語項目は以前は「オズワルド・セメレーニ」で、2000年代までの言語学の学習者はこの表記で習っていた（現在でも習っている？）ために、彼のWikipedia記事を案内する場合は“[https://ja.wikipedia.org/wiki/オズワルド・セメレーニ](https://ja.wikipedia.org/wiki/オズワルド・セメレーニ)”のURLでセメレーニの記事を見ることができましたが、悪意ある記事編集者が、
![2024-03-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1403192/b9b177cc-0dca-bcff-e620-60677bb6265b.png)
このように**転送用の記事を削除してしまった**(2024-03-11現在)ために“[https://ja.wikipedia.org/wiki/セメレーニ・オスヴァルド](https://ja.wikipedia.org/wiki/セメレーニ・オスヴァルド)”でしか記事が見られなくなっていました。とりあえずこの項目は私の方でリダイレクト用のページを作っておきましたので転送されるようになっていますが（2024-03-12現在）、無論、このようにしてもいつ再び削除されるかは分からないのが現状です。

いうまでもなく記事の見出しがセメレーニ・オスヴァルドに変更されたことは**しかるべきことで、非常に重要だと思う**のですが、一方でこの記事の編集者が転送処理を忘れたり**削除してしまう**ことにより、「オズワルド・セメレーニ」で覚えていた人の中には、彼の名前は実際にはセメレーニ・オスヴァルドと表記されるべきで、オズワルド・セメレーニで覚えた人はGoogleの検索結果に彼の名前がないことから、**永遠にその事実を知ることはおろか、彼の記事がきちんと存在することを知る機会を逸してしまう可能性がある**ということが起こり得てしまいます。

## これらの解消に向けて
私としては、
1. 見出し語をきちんと併記しておけばURLが見出し語のリンクでなくても、どこに飛ぶのか可視的である
1. 見出し語が全角文字の場合、全角部分など非ASCIIというか非ANSIの文字部分がURLだと認識されず、例えば“[https://ary.wikipedia.org/wiki/لفارسية](https://ary.wikipedia.org/wiki/لفارسية)”という記事に飛んで欲しいのに半角部分の“[https://ja.wikipedia.org/wiki/](https://ja.wikipedia.org/wiki/)”に飛んでしまったりということが予防できる
1. そもそも〈2〉のような文字をサポートしていないWebページやアプリケーションソフトへのリンクの貼り付けが容易になる
1. 〈2〉と〈3〉の回避策としてパーセントエンコーディングがあるが、どちらにせよパーセントエンコーディングの場合は**可読性に問題**が発生してしまい、かつ**URLの文字数が過度に冗長になってしまう**ため、それを防ぐことができる
といったメリットの多さから、極力、“wikipedia.org/?curid=記事ID”の表記を用いるようにするのが望ましいと考えられます。

すなわち、“لفارسية”のような記事を案内する場合に、
```plaintext
Wikipediaタイトル：لفارسية
Page(s)ID：6103
タイトル込みURL：https://ary.wikipedia.org/wiki/لفارسية
PC用エンコード済みURL：https://ary.wikipedia.org/wiki/%D9%84%D9%81%D8%A7%D8%B1%D8%B3%D9%8A%D8%A9
Mobile用エンコード済みURL：https://ary.m.wikipedia.org/?curid=%D9%84%D9%81%D8%A7%D8%B1%D8%B3%D9%8A%D8%A9

PC用ID込みURL：https://ary.wikipedia.org/?curid=6103
Mobile用ID込みURL：https://ary.m.wikipedia.org/?curid=6103
```
出力されるようにすれば、その中で例えば
```plaintext
Wikipediaタイトル：لفارسية
PC用ID込みURL：https://ary.wikipedia.org/?curid=6103
Mobile用ID込みURL：https://ary.m.wikipedia.org/?curid=6103
```
の部分だけSNSやメッセンジャーにコピペすれば、意図しないエラーを防ぐことができ、可読性を高めることができるということです。

# 必要なモジュール
pipで必要なモジュールは以下の通りです。なお、Python 3.12からrequestモジュールなど一部のモジュールがpipでインストールできないために、方法については「[初中級者向け：MacOS用IDE“CodeRunner 4”でPython仮想環境内でコードを実行する方法](https://qiita.com/ekemtt/items/252298da3e652dbf8ee4)」をご参照ください。
```zsh:URL処理モジュール
pip install urllib.parse
```
```zsh:APIリクエスト処理モジュール
pip install requests
```
```zsh:クリップボードモジュール（Python 3の場合）
pip install pyperclip
```

# ソースコード
このコードは**Python 3**と**Pythonista 3**に対応しています。
```python:Wikipedia_Wiktionary_GetID.py
import sys
import urllib.parse
import requests
import re

try:
    import clipboard
    def get_clipboard():
        return clipboard.get()
    def set_clipboard(text):
        clipboard.set(text)
except ImportError:
    try:
        import pyperclip
        def get_clipboard():
            return pyperclip.paste()
        def set_clipboard(text):
            pyperclip.copy(text)
    except ImportError:
        sys.exit("クリップボードモジュールが利用できません。")
        
class ClipboardURLFetcher:
    def fetch_url(self):
        url = get_clipboard()
        if not url:
            sys.exit("クリップボードに何も入っていません。")
        return urllib.parse.unquote(url)
    
class ClipboardManager:
    @staticmethod
    def copy_to_clipboard(text):
        set_clipboard(text)

class WikiURLProcessor:
    def __init__(self, site):
        self.site = site

    def process_url(self, url):
        if '?curid=' in url:
            sys.exit(f'既に変換済みです：{url}')
        
        if 'https://' not in url or (f'.{self.site}.org/wiki/' not in url and f'.m.{self.site}.org/wiki/' not in url):
            return None
        
        if f'.m.{self.site}.org/wiki/' in url:
            url = url.replace(f'.m.{self.site}.org/wiki/', f'.{self.site}.org/wiki/')
        
        match = re.search(r'https://([a-z\-]+)\.' + re.escape(self.site) + r'\.org/wiki/', url)
        if not match:
            sys.exit(f'無効なURLです：{url}')
        lang = match.group(1)
        text = url.replace(f'https://{lang}.{self.site}.org/wiki/', '')
        page_id = self.get_page_id(lang, text)
        
        if not page_id:
            sys.exit(f'変換不可能なリンクです：{url}')
        
        new_url = f'https://{lang}.{self.site}.org/?curid={page_id}' if self.site == 'wikipedia' else f'https://{lang}.{self.site}.org/w/index.php?curid={page_id}'
        
        ClipboardManager.copy_to_clipboard(new_url)
        
        self.print_info(url, lang, text, page_id)
        
        return new_url
    
    def get_page_id(self, lang, title):
        url = f'https://{lang}.{self.site}.org/w/api.php'
        params = {
            'action': 'query',
            'titles': title,
            'format': 'json'
        }
        response = requests.get(url, params=params)
        data = response.json()
        page = next(iter(data['query']['pages'].values()))
        return page.get('pageid')
    
    def print_info(self, url, lang, text, page_id):
        encoded_url = urllib.parse.quote(url, safe='/:=@,.!?\"\'')
        mobile_url = url.replace(f'.{self.site}.org/wiki/', f'.m.{self.site}.org/?curid=')
        mobile_encoded_url = urllib.parse.quote(mobile_url, safe='/:=@,.!?\"\'')
        print(f'{self.site.capitalize()}タイトル：{text}')
        print(f'Page(s)ID：{page_id}')
        print(f'タイトル込みURL：{url}')
        print(f'PC用エンコード済みURL：{encoded_url}')
        print(f'Mobile用エンコード済みURL：{mobile_encoded_url}')
        print('')
        print(f'{self.site.capitalize()}タイトル：{text}')
        print(f'PC用ID込みURL：https://{lang}.{self.site}.org/?curid={page_id}')
        print(f'Mobile用ID込みURL：https://{lang}.m.{self.site}.org/?curid={page_id}')

def main():
    fetcher = ClipboardURLFetcher()
    url = fetcher.fetch_url()
    if 'wikipedia.org/wiki/' in url:
        WikiURLProcessor('wikipedia').process_url(url)
    elif 'wiktionary.org/wiki/' in url:
        WikiURLProcessor('wiktionary').process_url(url) 

if __name__ == "__main__":
    main()
```
以上です。

# おわりに
今までの記事ではWikipediaとWiktionaryとで別処理をしていましたが、今回のコードで重複しないように工程を変えてみました。