Pythonista3 & Python 3: Wikipedia, WiktionaryのID取得、AmazonのASIN取得、そうでない場合はパーセントエンコード

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

# はじめに
今まで私のマイページ[^1]では「Python3, Pythonista3: Wikipedia PageIDに変換したりAmazonのASINに変換したり[^2]」などで、1つのコードであれもやりたい、これもやりたいと、色々な機能を含めてきたわけですが、実際に自分で運用していく中で、まあこのくらいのことができれば良いだろうという段階まで出来上がりました。つまり

1. WikipediaのページID取得
1. WiktionaryのページID取得
1. AmazonのASIN取得
1. GoogleMapの住所形式から「[乗換案内](https://www.jorudan.co.jp/norikae/)」の住所への入力の簡便化
1. パーセントエンコード[^3]

をそれぞれ、`http://`以降の文字列を参考にして判別し、`〒`の場合は「[乗換案内](https://www.jorudan.co.jp/norikae/)」の住所検索用に郵便番号の部分を取ったものを出力し、それ以外の場合はパーセントエンコードをするといったものです。

iPhoneでは[別記事](https://qiita.com/ekemtt/items/d4b62eb8146ca76286b6)ではYouTube Liveのチャット取得機能も搭載していますが、Pythonista 3の場合はiPhone上にPythonista 3上で新しいファイルを作成保存する際に余計な工程が必要となり面倒なために省略していますが、それでも外出先に**ちまちま**とLineやTwitter[^4]、Facebook Messengerに、これらのリンクを貼り付ける場合に1つのコードで済ませられるようにするというのが、作成した最大の動機だったりします。

[^1]:「頭痛が痛い」みたいな表現になってしまいますよね。
[^2]:https://qiita.com/ekemtt/items/ee0c207ba1e09a1f3fe7
[^3]:_a.k.a._ URLエンコード
[^4]:この文脈で“X”と記載するとわけのわからないことになる名称的に欠陥のあるアプリってどうなのだろう……。（あくまで意見論評）

# 手順
1. URLをクリップボードにコピー
1. Pythonista 3あるいはPythonista 3コードが実行できるIDEを起動
1. 本コードに必要なモジュール[^5]をインストール[^6]
1. 本コードをコピペ
1. 本コードを実行
1. 変換後のものをクリップボードに再コピー
1. コンソールに変換後のものや付属データを吐き出す

[^5]:_a.k.a._ ライブラリ
[^6]:Pythonista 3上でのモジュールのインストールは、ここでは説明を省略させてください。


# ソースコード
パソコン版などのPython 3でも使い回しできるように、```pyperclip```と```clipboard```とで動作を振り分けるようにしました。
```python:ClipboardConverter.py
import re
import sys
import urllib.parse
import requests

# クリップボードモジュール振り分け
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 BaseURLProcessor:
    def process_url(self, url):
        raise NotImplementedError

# Amazonプロセッサー
class AmazonURLProcessor(BaseURLProcessor):
    def process_url(self, url):
        if 'https://www.amazon' not in url:
            return None
        
        domain = self.extract_domain(url)
        asin = self.extract_asin(url)
        if asin:
            short_url = self.generate_short_url(asin, domain)
            self.end_to_amznconv(short_url)
            
        patterns = {
            '/gp/product/': '/product/',
            '/gp/aw/d/': '/aw/d/'
        }
        
        for suffix, pattern in patterns.items():
            if suffix in url:
                short_url = self.asin_url(url, pattern, domain)
                self.end_to_amznconv(short_url)
        return short_url
    
    def extract_domain(self, url):
        match = re.search(r'https://www\.amazon\.([a-z.]+)/', url)
        if match:
            return match.group(1)
        return None
    
    def extract_asin(self, url):
        match = re.search(r'/dp/(\w{10})', url)
        if match:
            return match.group(1)
        return None
    
    def asin_url(self, url, pattern, domain):
        pos = url.find(pattern) + len(pattern)
        asin = url[pos:pos + 10]
        return self.generate_short_url(asin, domain)
    
    def generate_short_url(self, asin, domain):
        return f'https://www.amazon.{domain}/dp/{asin}'
    
    def end_to_amznconv(self, short_url):
        print(f'クリップボードにコピー完了(Amazon):{short_url}')
        return

# Wikipedia & Wiktionaryプロセッサー
class WikiURLProcessor(BaseURLProcessor):
    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}'
        
        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}')

# URLエンコードプロセッサー
class TextURLEncoder(BaseURLProcessor):
    def process_url(self, text):
        encoded_text = urllib.parse.quote(text, safe='/:=@,.!?\"\'')
        print(f'URLエンコードされたテキストをクリップボードにコピーしました:{encoded_text}')
        return encoded_text

# GoogleMap→乗換案内プロセッサー
class AddressProcessor(BaseURLProcessor):
    def process_url(self, in_text):
        if '〒' in in_text[:2]:
            url = in_text[:9]
            text = in_text.replace(url, '')
            address = text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))
            address = address.replace('−','-')
            address = address.lstrip()
            zip = url.replace('〒', '')
            address = address.replace(' ', '\n')
            print(zip)
            print(address)
            return address
        else:
            return None

# メイン処理
def main():
    fetcher = ClipboardURLFetcher()
    url = fetcher.fetch_url()
    
    processors = [
        AddressProcessor(),
        AmazonURLProcessor(),
        WikiURLProcessor(site='wikipedia'),  # Wikipedia用の処理
        WikiURLProcessor(site='wiktionary'),  # Wiktionary用の処理
        TextURLEncoder()
    ]
    
    for processor in processors:
        new_url = processor.process_url(url)
        if new_url is not None:
            ClipboardManager.copy_to_clipboard(new_url)
            break

# # スクリプトが直接実行された場合にmain関数を呼び出す
if __name__ == "__main__":
    main()
```

# 終わりに
極力、``class``と``def``を使用して、他のコードに転用できるように整理しましたが、まだ効率が十分ではありません。