Python 搭配 Automator 上傳檔案到 Github

落葉滿長安發表於2021-05-17

這篇文章一開始釋出在我的部落格,其中的上傳圖片的解決方案,我是越用越覺得好用,分享給大家。

自從寫部落格以來,一直有上傳圖片的需求,一開始用第三方圖床,例如微博等等,其缺點是圖片不屬於自己,某一天可能就無法訪問了。後來也折騰過騰訊、阿里的物件儲存。直到後來發現可以把檔案存在 github 上,再配合 jsdelivr 的 CDN,在國內訪問速度也挺快。

至於上傳圖片的工具,用過 iPicuPicpicGo,其中 iPic 是付費的,他們的好處是支援不同的圖床、配置方便、不需要折騰。

本著能少安裝軟體就不安裝的原則,我寫了一個 Python 指令碼,配合系統自帶的 Automator ,實現了把資料夾裡的檔案(包括圖片)、微信群裡的圖片或者剪下板的圖片上傳到 github,如果你開了”接力”功能,你也可以上傳在手機裡複製圖片。

效果

這裡本來是一段演示視訊,但是本站無法顯示,如果有需要,可以去原地址看。

Python 指令碼

在電腦上新建一個 Python 檔案,例如upload-file.py,把下面的內容複製進去。

import io
import os
import re
import json
import time
import base64
import requests
import subprocess

from urllib import parse
# Pillow
from PIL import Image, ImageGrab
# PyObjC
from AppKit import NSPasteboard, NSURLPboardType

# 從剪下板獲取完整的檔案路徑
def getPath():
    # https://developer.apple.com/documentation/appkit/nspasteboard/pasteboardtype
    pb = NSPasteboard.generalPasteboard()

    url = pb.stringForType_(NSURLPboardType)

    if url is None:
        return None
    else:
        plistBytes = url.encode("utf-8")
        pathByte = re.findall(rb'<string>file://(.+?)</string>', plistBytes)[0]
        pathUTF8 = pathByte.decode('utf-8')
        return parse.unquote(pathUTF8)

# 把資料推送到 github
def push(base64, filename):
    url = "https://api.github.com/repos/[yourGithubName]/[yourRepository]/contents/" + filename

    headers = {"Authorization": "token [yourToken]"}

    data = json.dumps({
        "message": "auto commit",
        "committer": {
            "name": "[yourName]",
            "email": "[yourEmail]"
        },
        "content": base64
    })

    response = requests.put(url=url, data=data, headers=headers)

    return response

# 從檔案路徑得到檔案字尾
def getExtension(path):
  return os.path.splitext(path)[1]

# 用 Base64 對給定的 Bytes 進行編碼
def bytesToBase64(data):
    return base64.b64encode(data).decode('utf-8')

# 拼接檔名
def getFilename(extension):
    return time.strftime("%Y%m%d%H%M%S", time.localtime()) + extension

# 處理 github 返回的響應
def handleResponse(response, filename, extension):
    if response.status_code == 201:
        url = buildUrl(filename, extension)
        copyToClipboard(url)
        icon = "✅"
    else:
        icon = "❌"

    return icon + " " + response.reason

# 處理本地檔案
def handleLocalFile(path):
    with open(path, 'rb') as f:

        extension = getExtension(path)

        base64 = bytesToBase64(f.read())

        filename = getFilename(extension)

        response = push(base64, filename)

        return handleResponse(response, filename, extension)

# 處理剪下板的檔案,與本地檔案不同的是,剪下板的檔案好像是在記憶體中,它並沒有一個檔案路徑(有待研究)
def handleClipboard():
    image = ImageGrab.grabclipboard()

    if isinstance(image, Image.Image):

        buffer = io.BytesIO()

        imageFormat = "png"

        extension = "." + imageFormat

        image.save(buffer, imageFormat)

        base64 = bytesToBase64(buffer.getvalue())

        filename = getFilename(extension)

        response = push(base64, filename)

        return handleResponse(response, filename, extension)
    else:
        return "❌ 剪下板的內容不是圖片"

# 把給定的字串複製到剪下板
def copyToClipboard(data):
    p = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)

    p.stdin.write(data.encode("utf-8"))

    p.stdin.close()

    p.communicate()

# 判斷是否是圖片
def isImage(extension):
    return extension in [".png", ".jpeg", ".gif", "jpg"]

# 按照 jsdelivr 的規則構建 url
def buildUrl(filename, extension):
    url = "https://cdn.jsdelivr.net/gh/[yourGithubName]/[yourRepository]@[theBranch]/" + filename

    if isImage(extension):
        url = markdown(url)

    return url

# 按照 markdown 語法拼接 url
def markdown(url):
    return "![](" + url + ")"

if __name__ == '__main__':
    path = getPath()

    if path is None:
        toast = handleClipboard()
    else:
        toast = handleLocalFile(path)

    print(toast)

配置項

這個 Python 指令碼其中的一些個人資訊沒有做成可配置的,需要手動改一下,改的時候把中括號去掉。

  • [yourGithubName]:github 的名字
  • [yourRepository]:儲存檔案的倉庫名
  • [yourToken]:在這裡生成的 token,需要具有訪問倉庫的許可權。
  • [yourGithubEmail]:任意一個郵箱,通常填寫的是提交人的郵箱。
  • [theBranch]:想要把檔案儲存到哪個分支,如果沒有特殊需求,通常是 master 或者 main。

安裝 python 依賴

執行pip3 install pyobjc安裝 pyobjc

執行pip3 install pip install Pillow安裝 Pillow

  • pyobjc:一個 Python 和 Object-C 之間的橋樑,使得我們可以在 Python 中使用 AppKit 提供的功能,例如訪問剪下板。
  • Pillow:一個 Python 的影像處理庫

這兩個庫的功能可能重複了,pyobjc 可能也具備處理影像的功能,但是我沒有深入研究。

在命令列執行指令碼上傳檔案

配置好之後,這個指令碼就可以使用了。使用截圖工具截一張圖片,然後在終端裡執行python3 /path-to-file/upload-file.py,如果沒出錯,終端會返回提示資訊。或者也可以在訪達中複製一個已經存在的檔案,然後執行指令碼。

如果檔案很大,上傳花費的時間可能很久。另外由於 jsdelivr 的限制,好像是超過 20M 的檔案,就不可以訪問了。關於這一點,暫時沒有好辦法,只能儘可能的把圖片或者檔案控制在 20M 之內。

搭配 Automator 使用

上面的指令碼已經可以使用了,但是每次跑命令列依然很麻煩,蘋果電腦提供了一個叫做 Automator(自動操作) 的軟體,可以幫助我們建立工作流程,我們可以藉助它來幫我們跑指令碼。

新建快速操作

開啟 Automator,新建一個快速操作:

配置開始上傳提示

這一步的目的是讓 AppleScript 彈出一個提示。

新增一個執行 AppleScript 指令碼:

在其中寫上:

on run {input, parameters}
    display notification "⬆️ 開始上傳"
    return input
end run

配置 shell 指令碼

增加一個執行 Shell 指令碼(注意使用 bash來執行指令碼):

在其中寫上:

/usr/local/bin/python3 <<EOF

把上一步中配置好的 Python 指令碼複製到這裡

EOF

其中的 /usr/local/bin/python3 <<EOFEOF寫法是一個 Hack,如果直接選擇使用 python 執行,會出現找不到 Python 第三方庫的情況,所以用了這個 Hack 的辦法。

配置上傳結束提示

這一步的目的是顯示 Python 指令碼返回的字串,方便我們判斷圖片是否上傳成功。

新增一個執行 AppleScript 指令碼,在其中寫上:

on run {input, parameters}
  -- 使用訊息通知顯示 python 輸出的字串
    display notification input as string
    return input
end run

最後儲存檔案(預設儲存位置是~/Library/Services),到此已經配置完成。

設定快捷鍵

可以在設定中,為剛才建立的服務設定一個快捷鍵。

image-20210430111422275

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章