رشتهها
`threads.list`، `list_all`، `iterate`، `get`، `update`، `trash`، `snooze`، `unsnooze` و `list_attachments`.
خواندن
page = client.threads.list( folder: "inbox", query: "from:ada", label_ids: ["INBOX", "IMPORTANT"], limit: 25) if page.next_cursor next_page = client.threads.list(folder: "inbox", cursor: page.next_cursor) puts next_page.items.sizeend thread = client.threads.get("CAHk7pQ2x9LmZ4-mail.example.com")puts thread[:messageCount], thread[:hasUnread], thread[:totalReplies]API رشتهها را با یک pageToken صفحهبندی میکند. کلاینت آن را بهصورت next_cursor به شما میدهد و بهصورت cursor: پس میگیرد، مثل هر فهرست دیگری، و list_all و iterate آن را برایتان دنبال میکنند. این مقدار مبهم است: همان چیزی را که گرفتهاید پس بدهید و هرگز خودتان یکی نسازید.
فیلترهای فهرست، کلیدواژههای Ruby به شکل snake_case هستند (label_ids:، date_from:)، در حالی که فیلدهای بدنهٔ درخواست نامهای camelCase در API را نگه میدارند (addLabelIds: روی update). هر رشته بهصورت یک Hash با کلیدهای Symbol برمیگردد، پس thread[:messageCount] تعداد را میخواند.
last_week = client.threads.list_all( sort: "oldest", date_from: Time.now - (7 * 86_400), date_to: Time.now, from_contacts: true)puts last_week.size client.threads.iterate(sort: "sender") do |thread| puts thread[:id]endsort:، date_from:، date_to: و from_contacts: کنترلهای خودِ فهرست رشتهها هستند. sort: یکی از newest، oldest، sender یا subject است، و OpenEmail::THREAD_SORTS آنها را نام میبرد. تاریخها یک Time، یک DateTime یا یک String با قالب ISO 8601 همراه با ساعت و offset میگیرند، و هر دو سر شاملاند. Date در Ruby بهصورت تاریخ خالی فرستاده میشود، که این فیلدها آن را با یک 422 رد میکنند. from_contacts: true نامههایی را نگه میدارد که تازهترین پیامشان از یک مخاطب ذخیرهشده آمده است. هر ترتیب تا انتها صفحهبندی میشود بیآنکه رشتهای جا بیفتد یا تکرار شود.
list_all وقتی آخرین صفحه رسید یک Array برمیگرداند. iterate هر رشته را به یک بلاک yield میکند و صفحهٔ بعد را فقط وقتی حلقه به آن نیاز دارد میگیرد. بدون بلاک یک Enumerator برمیگرداند، پس first(10) یا lazy به محض اینکه آنچه لازم دارند را داشته باشند میایستند.
سازماندهی
thread_id = "CAHk7pQ2x9LmZ4-mail.example.com" client.threads.update(thread_id, read: true, addLabelIds: ["USER_DONE"], removeLabelIds: ["INBOX"]) client.threads.trash(thread_id)client.threads.snooze(thread_id, Time.now + 86_400)client.threads.unsnooze(thread_id)وضعیت خوانده شدن اینجا روی هر backend یک برچسب است، پس همراه فهرستهای برچسب میآید، و وقتی هر دو را تنظیم کنید ترتیب ثابت است: حذفها پیش از افزودنها اعمال میشوند، پس شناسهای که در هر دو فهرست باشد در نهایت روی رشته میماند. دستکم یکی از آن سه فیلد باید حاضر باشد.
addLabelIds شناسههایی از labels.list و شناسههای سیستمی مانند ARCHIVE و STARRED را میگیرد. شناسهای که به هیچ برچسبی اشاره نکند بهجای ساخته شدن با یک 422 label_not_found رد میشود، پس ابتدا برچسب را با labels.create بسازید. client.threads.list(folder: "USER_DONE") هر رشتهای را که یک برچسب دارد فهرست میکند، در هر پوشهای که باشد.
پیوستهای یک پیام
files = client.threads.list_attachments("CAHk7pQ2x9LmZ4-mail.example.com", "message_4c1b257a") files.each do |file| puts "#{file[:filename]} #{file[:contentType]} #{file[:size]}" File.binwrite(file[:filename], file[:content].unpack1("m")) unless file[:content].to_s.empty?endlist_attachments یک Array از Hashها برمیگرداند. content بهصورت base64 است، که unpack1("m") آن را به یک String دودویی تبدیل میکند، و وقتی بایتهای ذخیرهشده پیدا نشوند یک String خالی است، پس پیش از رمزگشایی طولش را بررسی کنید. متن رمزِ یک پیام رمزگذاریشده در این فهرست هست و مثل هر فایل دیگری دانلود میشود. بخش نسخهٔ PGP/MIME و هر امضای جداگانه در آن نیستند. آنها تنها شناسههایشان را در encryption.parts نگه میدارند و بس.
پیامی که رمزنگاریشده رسیده است
این gem نه رمزگذاری میکند و نه رمزگشایی. نمیتواند پیامی را که کسی دیگر رمزگذاری کرده باز کند، و نمیتواند پیامی رمزگذاریشده بفرستد. اگر درخواست ارسال نشانگر رمزگذاری با خود داشته باشد رد میشود، چون کلاینتی که کلید ندارد حقی هم برای ادعای آن ندارد. کلیدهایی که در برنامهٔ OpenEmail ساخته میشوند در همان مرورگری که ساختهشان میمانند و به اینجا نمیرسند. وقتی آن مرورگر پیامی مهرومومشده را باز میکند متن آشکار در همانجا میماند، و پیام ذخیرهشدهای که این فراخوانی میخواند همچنان متن رمز است. آنچه threads.get به شما میدهد پاکت است، بازشناختهشده. پیامی که پیچیده در PGP یا S/MIME رسیده باشد یک Hash به نام encryption دارد، تا یک decodedBody خالی دیگر تنها چیزی نباشد که به شما داده میشود. encryption تنها فیلد پیام است که API به آن متعهد است، چون تنها فیلدی است که نمیتوان با حدس زدن دربارهٔ نبودنش کنار آمد.
thread = client.threads.get("CAHk7pQ2x9LmZ4-mail.example.com") thread[:messages].each do |message| next unless message[:encryption] next unless OpenEmail.sealed?(message) warn "cannot read this one: #{message[:encryption][:format]}"endبا OpenEmail.sealed? شاخه بزنید، هرگز بر اساس حضور خودِ فیلد. دو تا از پنج قالب، pgp-signed و smime-signed، بدنهای را توصیف میکنند که بهصورت آشکار همراه یک امضای جداگانه رسیده است، پس شرط گذاشتن روی حضور، ایمیلی را پنهان میکند که هیچکس نیازی به پنهان کردنش نداشته، و کاربر نه میتواند ببیندش نه توضیحش دهد. OpenEmail.sealed? دقیقاً به همین دلیل وجود دارد. سرور مجموعهٔ مهرومومشده را یک بار بیان میکند، نسخهٔ gem از همان منبع تولید میشود، و نسخهٔ سومی که با دست نوشته شود همان نسخهای است که واگرا میشود. OpenEmail::MESSAGE_ENCRYPTION_FORMATS هر پنج قالب را نام میبرد.
نبودن به معنای متن آشکار نیست. encryption روی هر پیامی که پیش از عرضهٔ تشخیص ذخیره شده باشد غایب است، و روی هر چیزی که از مسیری به صندوق پستی رسیده باشد که تشخیصدهنده هرگز روی آن اجرا نشده است. این فیلد ثبت میکند که کسی نگاه نکرده است، یعنی واقعیتی دربارهٔ پوشش ما نه دربارهٔ خودِ ایمیل، و هیچچیز آن را بهصورت عقبگرد پر نمیکند.
تفاوتهای این بخش با بقیه
- هر مدخل در
messagesیک رشته همان Hashی است که صندوق پستی ذخیره کرده، بدون فهرست ثابتی از فیلدها. وعدهٔ بیشتر یعنی کلاینت نرمالسازیای را ادعا کند که هیچکس انجامش نمیدهد.encryptionتنها فیلدی است که API به هر حال به آن متعهد است، چون کلاینتی که نتواند روی آن شاخه بزند یک پیام مهرومومشده را پیامی خالی میخواند. - درخواستی که نتوان با وفاداری پاسخش داد یک 422
capability_unsupportedاست که بهصورتOpenEmail::ValidationErrorraise میشود، نه پاسخی که درست به نظر برسد و بیصدا نادرست باشد.
پارامترها: threads.list
folderString- کدام پوشه فهرست شود. سرور آن را به `inbox` پیشفرض میکند، پس ننوشتنش فهرست را باریک میکند نه اینکه به همهچیز گستردهاش کند. روی جستوجوی `query:` هم اعمال میشود، مگر آنکه خودِ کوئری با `in:` یا یک `is:` پوشهای مانند `is:sent` پوشهای را نام ببرد.
queryString- نحو جستوجوی صندوق پستی. همهٔ واژههای ساده باید حاضر باشند و هرکدام آزادانه تطبیق مییابد: بزرگی و کوچکی حروف، اعراب و جداکنندهها نادیده گرفته میشوند و بخشی از واژهای بلندتر هم به حساب میآید، پس `min` و `ben jamin` هر دو «Benjamin» را پیدا میکنند. عبارت داخل گیومه جز از نظر بزرگی حروف و اعراب دقیقاً همانطور که نوشته شده تطبیق مییابد، پس `"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:` فرستنده، گیرندگان، موضوع و 4,000 نویسهٔ نخستِ بدنهٔ آخرین پیام را با نشانهگذاریِ حذفشده میخوانند، در حالی که `filename:` و `has:` همهٔ پیوستهای کل گفتوگو را میخوانند، و برچسبها و پوشهها کل گفتوگو را. همان ایندکسی را باریک میکند که فهرست بدون فیلتر میخواند. پیامهای مهرومومشده هیچ متن بدنهای ذخیره نمیکنند، پس تنها فرستنده، گیرندگان و موضوعشان میتواند تطبیق یابد. یک واژهٔ ساده با نام هر پیوستی در گفتوگو هم مطابقت میکند، هر پیامی که آن را آورده باشد.
label_idsString or Array<String>- فهرست را به رشتههایی محدود کنید که این برچسبها را دارند. اندپوینت یک String جداشده با کاما میگیرد، و کلاینت یک Array یا Set را برایتان به آن تبدیل میکند. محدودیتی برای تعداد برچسبهایی که نام میبرید وجود ندارد.
limitInteger- چند رشته بازگردانده شود، از 1 تا 100. اگر داده نشود، هندلر از 25 استفاده میکند. پیشفرض بهجای اسکیما در خودِ هندلر است، پس نبودن مقدار و 25 صریح یکسان رفتار میکنند.
cursorString- مقدار `next_cursor` صفحهٔ پیشین، که عیناً پس داده میشود. همان `pageToken` در API است با نامی که هر فهرست دیگری به کار میبرد، و مبهم است، پس هرگز یکی نسازید یا ویرایشش نکنید.
پاسخ: OpenEmail::Page
itemsArray<Hash>- برای هر رشته در این صفحه یک Hash، بیرونکشیده از پاکت `data` در API. هرکدام تنها یک نشانگر `object` و یک `id` است. این فهرست نه موضوع دارد، نه خلاصه، نه مشارکتکنندگان و نه برچسب، پس هر چیز بیشتری یعنی فراخوانی `threads.get` روی رشتههایی که میخواهید.
items[].idString- شناسهٔ رشته، که به شکل `item[:id]` خوانده میشود، تا بیتغییر به `threads.get`، `threads.update` و بقیه داده شود. چه ردیف از یک فهرست فیلترشده آمده باشد چه از یک جستوجوی `query:`، همان شناسه است.
has_more?Boolean- اینکه صفحهٔ دیگری هست یا نه، که هر جا API آن را بیان کند از همان گرفته میشود و هر جا نکند از `next_cursor` مشتق میشود.
next_cursorString or nil- همان `nextPageToken` در API، که برای صفحهٔ بعدی بهصورت `cursor:` پس فرستاده میشود، یا وقتی صفحهٔ دیگری نباشد nil. توکن خالی به nil نرمال میشود، پس `if page.next_cursor` و بررسی nil همداستاناند.