Threads
`threads.list`, `list_all`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` और `list_attachments`।
पढ़ना
from openemail import openemail page = openemail.threads.list( folder='inbox', query='from:ada', label_ids=['INBOX', 'IMPORTANT'], limit=25,) next_page = ( openemail.threads.list(folder='inbox', cursor=page['nextCursor']) if page['nextCursor'] else None) thread = openemail.threads.get('thread_…')print(thread['messageCount'], thread['hasUnread'], thread['totalReplies'])API थ्रेड को pageToken से पेज करता है। क्लाइंट उसे आपको nextCursor के रूप में देता है और cursor के रूप में वापस लेता है, हर दूसरी सूची की तरह, और list_all तथा iterate उसका पीछा आपके लिए करते हैं। यह अपारदर्शी है: जो दिया गया वही वापस भेजें और कभी खुद न बनाएँ।
सूची के फ़िल्टर snake_case में keyword आर्ग्युमेंट हैं (label_ids=, date_from=), जबकि अनुरोध body की keys API के camelCase नाम ही रखती हैं (update पर addLabelIds)। पेज और थ्रेड dict के रूप में लौटते हैं, इसलिए page['nextCursor'] और thread['messageCount'] उन्हें पढ़ते हैं।
from datetime import datetime, timedelta, timezone from openemail import openemail now = datetime.now(timezone.utc) last_week = openemail.threads.list_all( sort='oldest', date_from=now - timedelta(days=7), date_to=now, from_contacts=True,) for thread in openemail.threads.iterate(sort='sender'): print(thread['id'])sort, date_from, date_to और from_contacts थ्रेड सूची के अपने नियंत्रण हैं। sort newest, oldest, sender या subject है, तारीख़ें datetime या ISO 8601 string लेती हैं और दोनों सिरे शामिल हैं, और from_contacts वह मेल रखता है जिसका सबसे नया संदेश किसी सहेजे गए संपर्क से आया। हर क्रम बिना किसी थ्रेड को छोड़े या दोहराए आख़िर तक पेज होता है। बिना tzinfo वाला datetime स्थानीय समय माना जाता है।
व्यवस्थित करना
from datetime import datetime, timedelta, timezone from openemail import openemail openemail.threads.update('thread_…', { 'read': True, 'addLabelIds': ['USER_DONE'], 'removeLabelIds': ['INBOX'],}) openemail.threads.trash('thread_…')openemail.threads.snooze('thread_…', datetime.now(timezone.utc) + timedelta(days=1))openemail.threads.unsnooze('thread_…')यहाँ हर backend पर पढ़े जाने की स्थिति खुद एक लेबल है, इसलिए वह लेबल सूचियों के साथ चलती है और दोनों सेट करने पर क्रम निश्चित रहता है। तीनों फ़ील्ड में से कम से कम एक होना चाहिए।
addLabelIds labels.list से ids और ARCHIVE व STARRED जैसी सिस्टम ids लेता है। किसी लेबल का नाम न लेने वाली id बनाई नहीं जाती बल्कि 422 label_not_found के साथ अस्वीकार होती है, इसलिए पहले labels.create से लेबल बनाएँ। threads.list(folder='USER_DONE') किसी लेबल वाला हर थ्रेड दिखाता है, चाहे वह किसी भी फ़ोल्डर में हो।
किसी संदेश पर attachment
import base64from pathlib import Path from openemail import openemail files = openemail.threads.list_attachments('thread_…', 'message_…') for file in files: print(file['filename'], file['contentType'], file['size']) if file['content']: name = Path(file['filename']).name Path(name).write_bytes(base64.b64decode(file['content']))content base64 है, और जब संग्रहीत बाइट्स नहीं मिल पातीं तो खाली string, इसलिए decode करने से पहले उसकी लंबाई जाँचें। एन्क्रिप्टेड संदेश का ciphertext इसी सूची में है और किसी भी दूसरी फ़ाइल की तरह डाउनलोड होता है; PGP/MIME संस्करण वाला भाग और कोई detached signature नहीं हैं। उनकी id encryption.parts में रहती हैं, और बस इतना ही।
ऐसा संदेश जो एन्क्रिप्टेड आया
यह SDK न एन्क्रिप्ट करता है न डिक्रिप्ट: यह वह संदेश नहीं खोल सकता जिसे किसी और ने एन्क्रिप्ट किया, और न एन्क्रिप्टेड संदेश भेज सकता है। अगर send अनुरोध कोई एन्क्रिप्शन चिह्न लिए आता है तो उसे अस्वीकार कर दिया जाता है, क्योंकि बिना कुंजी वाले क्लाइंट को ऐसा दावा करने का कोई हक़ नहीं। OpenEmail ऐप में बनी कुंजियाँ उसी ब्राउज़र में रहती हैं जिसने उन्हें बनाया और यहाँ तक पहुँचती ही नहीं, और जब वह ब्राउज़र कोई सीलबंद संदेश खोलता है तो plaintext उसी में रह जाता है, और यह कॉल जो संग्रहित संदेश पढ़ती है वह अब भी ciphertext है। threads.get आपको जो देता है वह envelope है, पहचाना हुआ। PGP- या S/MIME-लिपटा आया संदेश एक encryption dict लिए चलता है, ताकि ख़ाली decodedBody अकेली चीज़ न रह जाए जो आपको थमाई गई हो। यही वह इकलौती key है जिसकी अनुपस्थिति का अंदाज़ा लगाकर आप बच नहीं सकते, और openemail.types में MessageEncryption उसका वर्णन करता है।
import sys from openemail import is_sealed, openemail thread = openemail.threads.get('thread_…') for message in thread['messages']: if not message.get('encryption'): continue if not is_sealed(message): continue print('cannot read this one:', message['encryption']['format'], file=sys.stderr)is_sealed से शाखा बनाएँ, फ़ील्ड की मौजूदगी से कभी नहीं। पाँच में से दो प्रारूप, pgp-signed और smime-signed, ऐसी body बताते हैं जो एक detached signature के साथ साफ़ रूप में आई थी, इसलिए मौजूदगी पर रोक लगाना वह मेल छिपा देता है जिसे किसी को छिपाना ही नहीं था, और उपयोगकर्ता न उसे देख सकता है न समझा सकता है। is_sealed ठीक इसीलिए शिप होता है: सर्वर सीलबंद समुच्चय को एक बार बताता है, और union से निकाली गई तीसरी प्रति वही प्रति है जो भटक जाती है।
अनुपस्थिति का अर्थ plaintext नहीं है। detection शिप होने से पहले संग्रहीत हर संदेश पर, और हर उस चीज़ पर जो मेलबॉक्स तक ऐसे रास्ते से पहुँची जहाँ detector कभी नहीं चला, encryption अनुपस्थित है। यह दर्ज करता है कि किसी ने देखा ही नहीं। यह तथ्य हमारी coverage के बारे में है, मेल के बारे में नहीं, और इसे कुछ भी backfill नहीं करता।
ये बाकियों से कहाँ अलग हैं
ThreadResource.messagesकी हर प्रविष्टि एकMessageResourceहै, एक सादाdict[str, Any]जिसका type किसी फ़ील्ड का नाम नहीं लेता,encryptionका भी नहीं। फ़ील्ड को type देना क्लाइंट का ऐसे normalisation का दावा करना होता जो कोई करता ही नहीं।encryptionकोmessage.get('encryption')से पढ़ें औरis_sealedसे शाखा बनाएँ, क्योंकि जो क्लाइंट इस पर शाखा नहीं बना सकता वह सीलबंद संदेश को ख़ाली संदेश की तरह पढ़ता है।- जिस request को ईमानदारी से पूरा नहीं किया जा सकता वह 422
capability_unsupportedहै, न कि ऐसा रिस्पॉन्स जो सही दिखे और चुपचाप गलत हो।
पैरामीटर: threads.list
folderstr- कौन-सा फ़ोल्डर सूचीबद्ध करना है। सर्वर इसका डिफ़ॉल्ट `inbox` रखता है, इसलिए इसे छोड़ देना सूची को सब कुछ तक चौड़ा नहीं करता बल्कि संकरा करता है। यह `query` खोज पर भी लागू होता है, जब तक query खुद `in:` से या `is:sent` जैसे फ़ोल्डर `is:` से किसी फ़ोल्डर का नाम न ले ले।
querystr- मेलबॉक्स की खोज वाक्यरचना। सादे शब्द सभी आने चाहिए, और हर एक ढीला मिलान करता है: case, उच्चारण-चिह्न और विभाजक अनदेखे किए जाते हैं और किसी लंबे शब्द का हिस्सा भी गिना जाता है, इसलिए `min` और `ben jamin` दोनों "Benjamin" ढूँढ लेते हैं। उद्धृत वाक्यांश case और उच्चारण-चिह्नों को छोड़कर जैसा लिखा है वैसा ही मिलाया जाता है, इसलिए `"ben jamin"` से "Ben-Jamin" नहीं मिलता, और जब खोजने को कुछ और बचा हो तो भरने वाले शब्द छोड़ दिए जाते हैं। जब कुछ भी ठीक-ठीक मेल नहीं खाता, तो उसकी जगह मिलती-जुलती वर्तनियाँ लौटाई जाती हैं, इसलिए `benjimin` से "Benjamin" मिल जाता है: कोई सादा शब्द, या `from:`, `to:`, `cc:`, `subject:`, `body:`, `filename:` अथवा `label:` का मान, चार से सात अक्षरों का हो तो किसी शब्द की शुरुआत से एक टाइपो (एक बदला हुआ, छूटा हुआ, अतिरिक्त या आपस में अदला-बदला अक्षर) से, और आठ या अधिक अक्षरों का हो तो दो से अलग हो सकता है, जबकि उद्धरण चिह्नों में लिखा वाक्यांश, अंक वाला शब्द, इससे छोटा शब्द और बाहर रखा गया शब्द अब भी केवल ठीक-ठीक ही मेल खाते हैं, और आगे के पेज भी इसी तरह खोजते हैं। `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` और `older_than:1y` जैसे ऑपरेटरों से संकरा करें, और उन्हें `OR`, कोष्ठकों तथा आगे लगे `-` से जोड़ें; जो मान खोज इस्तेमाल नहीं कर सकती वह संकरा करने के बजाय अनदेखा कर दिया जाता है। शब्द और `from:`, `to:`, `cc:`, `subject:` तथा `body:` ऑपरेटर नवीनतम संदेश का प्रेषक, प्राप्तकर्ता, subject और markup हटाकर उसकी body के पहले 4,000 वर्ण पढ़ते हैं, जबकि `filename:` और `has:` पूरी बातचीत का हर attachment पढ़ते हैं और लेबल तथा फ़ोल्डर पूरी बातचीत पढ़ते हैं। यह उसी index को संकरा करता है जिसे बिना छाने वाली सूची पढ़ती है। सीलबंद संदेश कोई body टेक्स्ट संग्रहीत नहीं करते, इसलिए केवल उनका प्रेषक, प्राप्तकर्ता और subject ही मिलान कर सकते हैं। कोई सादा शब्द बातचीत के किसी भी अटैचमेंट के नाम से भी मेल खाता है, चाहे वह किसी भी संदेश के साथ आया हो।
label_idsstr | Sequence[str]- सूची को केवल इन लेबल वाले थ्रेड तक सीमित करें। endpoint अल्पविराम से अलग की गई string लेता है, और क्लाइंट list या tuple को आपके लिए एक string में जोड़ देता है। आप कितने नाम लें, इसकी कोई सीमा नहीं।
limitint- कितने थ्रेड लौटाने हैं, 1 से 100 तक। छोड़ने पर handler 25 इस्तेमाल करता है। डिफ़ॉल्ट schema में नहीं handler में रहता है, इसलिए अनुपस्थित मान और स्पष्ट 25 एक जैसा व्यवहार करते हैं।
cursorstr- पिछले पेज का `nextCursor`, हूबहू वापस भेजा गया। यह API का `pageToken` ही है, बस उसी नाम से जो हर दूसरी सूची इस्तेमाल करती है, और यह अपारदर्शी है, इसलिए इसे कभी न बनाएँ न संपादित करें।
प्रतिक्रिया: Page[ThreadSummaryResource]
itemslist[ThreadSummaryResource]- इस पेज के हर थ्रेड के लिए एक प्रविष्टि, API के `data` envelope से बाहर उठाई हुई। हर प्रविष्टि केवल एक object चिह्न और एक id है। सूची में कोई subject, स्निपेट, प्रतिभागी या लेबल नहीं होते, इसलिए इससे ज़्यादा कुछ चाहिए तो जिन थ्रेड पर चाहिए उन पर `threads.get` कॉल करना होगा।
items[].idstr- थ्रेड की id, जिसे बिना बदले `threads.get`, `threads.update` और बाकी को देना है। पंक्ति चाहे छनी हुई सूची से आई हो या `query` खोज से, id वही रहती है।
hasMorebool- क्या आगे कोई पेज है, जो `nextCursor` से निकाला जाता है जहाँ API इसे नहीं बताता।
nextCursorstr | None- API का `nextPageToken`, जिसे अगले पेज के लिए `cursor` के रूप में वापस भेजना है, या `None` जब आगे कोई पेज नहीं। ख़ाली token `None` में बदल दिया जाता है, इसलिए falsy जाँच और `None` जाँच एक ही नतीजा देती हैं।