download attachments from Gmail using FTP/script

(เอ เรามี Python66 ป่าวหว่า ? แบบอันนี้เขียนลง Django66 ได้ป่ะ ?)
สรุปว่าเอาไปลง Pylons66 ครับ

ดาวน์โหลดแฟ้มจาก Gmail ด้วย FTP (หรือสคริปต์)

ง่าย ๆ ไม่ซับซ้อน แค่เรียกตัว ftp daemon (Gmail-FTP proxy) ให้ทำงาน, แล้วก็ใช้โปรแกรม FTP อะไรก็ได้ไปดึงแฟ้มมา.
โดยแฟ้มที่จะดึงมาได้นั้น จะต้องเป็นแฟ้มแนบ (attachment) ที่อยู่ในจดหมายที่ติดป้ายว่า ‘ftp’.

ตัวโปรแกรม ftp daemon ที่ว่านี้ คือสคริปต์ไพธอน (Python) เล็ก ๆ ที่ชื่อว่า gmailftpd.py
มันอยู่ในแพคเกจ libgmail-docs ของ libgmail.

ก่อนจะใช้งาน gmailftpd นี้ เราจำเป็นต้องมี libgmail กับ mechanize ลงอยู่ในเครื่องก่อน.
ถ้าใช้ Ubuntu/Debian ก็สั่ง sudo apt-get install python-libgmail python-mechanize ได้เลย.

วิธีใช้งาน เราก็เรียก ftp daemon ขึ้นมาก่อน ให้มันทำหน้าที่เป็นเซิร์ฟเวอร์.
เรียกใช้ gmailftpd.py โดยสั่ง python gmailftpd.py ได้เลย.

หลังเรียกให้ ftp daemon ทำงานแล้ว หน้าจอจะประมาณด้านล่าง – จะเห็นว่ามันจะไม่ทำอะไรต่อ จนกว่าจะมี client ติดต่อเข้ามา:

หน้าจอฝั่งแม่ข่าย (ftp daemon – gmailftpd)

bact@edin:~/libgmail$ python demos/gmailftpd.py
FTPServer started at Sun Sep 21 12:32:39 2008
        Local addr: ('127.0.0.1', 8021)

ทีนี้ถ้าเราอยากจะได้แฟ้มอะไรจาก Gmail ก็ใช้โปรแกรม FTP เรียกไปที่ไอพี 127.0.0.1 (localhost) พอร์ต 8021.

ในทีนี้จะใช้ wget เพราะสะดวกดี. วิธีใช้ wget คร่าว ๆ ก็คือ wget –user=ชื่อผู้ใช้ –password=รหัสผ่าน -c โปรโตคอล://ที่อยู่:พอร์ต/ชื่อแฟ้ม เจ้า -c นี่ใส่ไปให้มันโหลดต่อให้ กรณีหลุดกลางทาง จะได้ไม่ต้องเริ่มใหม่หมด.

หลังจากเรียก wget มันก็จะทำอะไรไปตามเรื่องตามราว ประมาณหน้าจอนี้:

หน้าจอฝั่งลูกข่าย (ftp client – wget)

bact@edin:~/test$ wget --user=uabc --password=pxyz -c ftp://127.0.0.1:8021/anthro.txt
--12:35:55--  ftp://127.0.0.1:8021/anthro.txt
           => `anthro.txt'
Connecting to 127.0.0.1:8021... connected.
Logging in as uabc ... Logged in!
==> SYST ... 
Server error, can't determine system type.
==> PWD ... done.
==> TYPE I ... done.  ==> CWD not needed.
==> PASV ... done.    ==> RETR anthro.txt ... done.

    [     < =>                             ] 375           94.27B/s             

12:36:03 (94.25 B/s) - `anthro.txt' saved [375]

bact@edin:~/test$ 

ลอง ls ดู ก็จะเห็นว่าได้แฟ้มมาตามที่ต้องการแล้ว:

bact@edin:~/test$ ls -la anthro.txt 
-rw-r--r-- 1 arthit arthit 375 2008-09-21 12:36 anthro.txt

ทีนี้ฝั่งเซิร์ฟเวอร์แม่ข่าย เขาทำอะไรกัน ลองไปดู:

หน้าจอฝั่งแม่ข่าย (ftp daemon – gmailftpd) ระหว่างที่ลูกข่าย (ftp client) ติดต่อเข้ามา

bact@edin:~/libgmail$ python demos/gmailftpd.py
FTPServer started at Sun Sep 21 12:32:39 2008
        Local addr: ('127.0.0.1', 8021)

Incoming connection from ('127.0.0.1', 57054)
Peer: ('127.0.0.1', 57054)
Data: 'USER uabc'
Data: 'PASS pxyz'
Data: 'SYST'
Data: 'PWD'
Data: 'TYPE I'
Data: 'PASV'
DataChannel started at Sun Sep 21 12:35:59 2008
        Local addr: ('127.0.0.1', 9021)

Data: 'RETR anthro.txt'
Reading `anthro.txt`.

จะเห็นว่าตัวแม่ข่ายหรือ gmailftpd มันได้รับชื่อผู้ใช้รหัสผ่านจากลูกข่ายหรือ wget, ซึ่ง gmailftpd มันจะเอาข้อมูลนี้ไปล็อกอิน — ตาม API คือใช้ ga.login() โดย ga เป็นออบเจกต์ของคลาส GmailAccount ที่สร้างโดย ga = libgmail.GmailAccount(username, password) ) — จากนั้นก็หาแฟ้มที่ร้องขอ, และส่งไปให้ลูกข่าย

อยากรู้ว่ามันหาแฟ้มได้ยังไง ก็ต้องไปดูในโค้ด gmailftpd.py ตรงเมธอด get_filelist().

    def get_filelist(self):
        r = self.ga.getMessagesByLabel('ftp')
        for th in r:
            for m in th:
                for a in m.attachments:
                    self.filenames[a.filename] = a

คือมันใช้เมธอด getMessagesByLabel() ของคลาส GmailAccount เพื่อหาเฉพาะอีเมลฉบับที่ติดป้ายว่า ‘ftp’, พอได้ผลลัพธ์มาแล้วก็วนลูป for ไปเพื่อเก็บชื่อแฟ้มทั้งหมด

ในโค้ด: r คือ ออบเจกต์ของ GmailSearchResult ซึ่งจะประกอบด้วยชุดของ threads, th คือ thread, m คือ message, a คือ attachment

ถ้าอยากให้มันไปดึงจากที่อื่นมาด้วย ก็แก้ตรงนี้ได้ เช่นเอาจากโฟลเดอร์ก็ใช้ getMessagesByFolder() แต่ระวังมันเยอะเกินละกัน.

เท่าที่ลองกับสองแฟ้ม พบว่า แฟ้มเล็ก ๆ น่าจะสบาย ๆ, แต่กับแฟ้มขนาดใหญ่ (6.4 MB) จะมีปัญหา โหลดไม่ได้ คือ gmailftpd มันจะฟ้อง exception ซะเฉย ๆ แล้วก็ไม่ทำอะไรต่อ — ไม่แน่ใจว่าเป็นเพราะตัวแฟ้มไม่ดีเองรึเปล่า error message มันฟ้องเกี่ยวกับ decode อะไรซักอย่าง อ่านไม่รู้เรื่อง :p

error: uncaptured python exception, closing channel <__main__ .FTPChannel connected 127.0.0.1:43982 at 0xb7d102ec> (:’utf8′ codec can’t decode byte 0xac in position 11: unexpected code byte [/usr/lib/python2.5/asyncore.py|read|68] [/usr/lib/python2.5/asyncore.py|handle_read_event|390] [/usr/lib/python2.5/asynchat.py|handle_read|137] [demos/gmailftpd.py|found_terminator|106] [demos/gmailftpd.py|ftp_RETR|181] [demos/gmailftpd.py|handle_RETR|313] [/home/arthit/dev/libgmail/libgmail.py|_getContent|1507] [/home/arthit/dev/libgmail/libgmail.py|_retrievePage|358] [/usr/lib/python2.5/encodings/utf_8.py|decode|16])

การประยุกต์ใช้ ทำได้หลากหลายมาก แล้วแต่จินตนาการเลย
เช่น อาจเขียนสคริปต์ให้มันดาวน์โหลดแฟ้มแนบใหม่ ๆ มาเก็บไว้ในเครื่องเราไว้
หรือให้ดาวน์โหลดแฟ้มแนบ .pdf จากอีเมลหรือหัวข้อที่กำหนด (เช่นโจทย์การบ้านจากอาจารย์) หรือเอาเฉพาะ .mp3

วิธีทำ อาจจะใช้เชลล์สคริปต์ไปเรียก ftp ผ่าน gmailftpd ก็ได้, หรือจะเขียนไพธอนไปเลยก็ได้ เท่าที่ดู API ของ libgmail มันก็น่าเล่นอยู่.
ทั้งนี้ยังสามารถผสมกับการตั้งค่า filter ใน Gmail ได้ด้วย, คือถ้าคิดว่าแก้สคริปต์มันยุ่งเกิน ก็ไปตั้งค่า filter ใน Gmail ก็ได้ แล้วก็ให้มันติดป้ายอัตโนมัติ, จากนั้นก็ไปแก้เมธอด get_filelist() นิดหน่อย ให้มันดึงจากป้ายที่เราอยากได้.

และ libgmail นี่ทำได้มากกว่าดาวน์โหลดแฟ้มแนบนะ ลองเล่นดู มีอะไรน่าสนก็บอกกันมั่ง 🙂

(สุดท้ายก็ยังหาวิธีดาวน์โหลดเจ้าแฟ้ม 6.4 MB นั่นออกมาไม่ได้ ทำไงดีเนี่ย…. เน็ตก็ห๊วยห่วย – -“)

technorati tags:
,
,

Published by

bact

bact' is a name

One thought on “download attachments from Gmail using FTP/script”

Leave a Reply