tempmail_sdk 1.3.3 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/tempmail_sdk/providers/altmails.rb +99 -0
- data/lib/tempmail_sdk/providers/apihz.rb +138 -0
- data/lib/tempmail_sdk/providers/awamail.rb +95 -0
- data/lib/tempmail_sdk/providers/best_temp_mail.rb +77 -0
- data/lib/tempmail_sdk/providers/chatgpt_org_uk.rb +129 -0
- data/lib/tempmail_sdk/providers/disposablemail.rb +125 -0
- data/lib/tempmail_sdk/providers/disposablemail_app.rb +66 -0
- data/lib/tempmail_sdk/providers/email10min.rb +152 -0
- data/lib/tempmail_sdk/providers/emailnator.rb +132 -0
- data/lib/tempmail_sdk/providers/emailtemp_org.rb +131 -0
- data/lib/tempmail_sdk/providers/expressinboxhub.rb +118 -0
- data/lib/tempmail_sdk/providers/fakemail.rb +143 -0
- data/lib/tempmail_sdk/providers/haribu.rb +29 -3
- data/lib/tempmail_sdk/providers/linshiyouxiang_net.rb +85 -0
- data/lib/tempmail_sdk/providers/mail_sunls.rb +42 -1
- data/lib/tempmail_sdk/providers/mail_td.rb +161 -0
- data/lib/tempmail_sdk/providers/mailcat_ai.rb +52 -0
- data/lib/tempmail_sdk/providers/maildrop.rb +79 -4
- data/lib/tempmail_sdk/providers/mailgolem.rb +96 -0
- data/lib/tempmail_sdk/providers/mailinator.rb +9 -2
- data/lib/tempmail_sdk/providers/mailtemp_cc.rb +91 -0
- data/lib/tempmail_sdk/providers/minuteinbox.rb +174 -0
- data/lib/tempmail_sdk/providers/moakt.rb +29 -3
- data/lib/tempmail_sdk/providers/mohmal.rb +208 -0
- data/lib/tempmail_sdk/providers/mytempmail_cc.rb +76 -0
- data/lib/tempmail_sdk/providers/openinbox.rb +102 -0
- data/lib/tempmail_sdk/providers/smail_pw.rb +156 -0
- data/lib/tempmail_sdk/providers/socketio_mail.rb +25 -0
- data/lib/tempmail_sdk/providers/tempgbox.rb +118 -0
- data/lib/tempmail_sdk/providers/tempmail_cn.rb +111 -0
- data/lib/tempmail_sdk/providers/tenminutemail_net.rb +183 -0
- data/lib/tempmail_sdk/providers/twentyfourmail_chacuo.rb +83 -0
- data/lib/tempmail_sdk/providers/vip_215.rb +171 -0
- data/lib/tempmail_sdk/registry.rb +163 -1
- data/lib/tempmail_sdk/version.rb +1 -1
- metadata +30 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9633959dc0f47ae3b8b30e6cb69db5e16976773039a186303e47701efd37a6b
|
|
4
|
+
data.tar.gz: 50eed70cbb31235fef5d96ea9d2288cc8d099568bd35946e752875d2c945291d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b54a192368aa851e240a34c91c7e8f2b6fc5b0d5587f6091fabd79f54b89bf7f29dc51c13c57987f499c65b570084aa0754838fbe263d7fbc8d564b5ce2e1bc2
|
|
7
|
+
data.tar.gz: f630396b6757e1dd049a931141f946a99c92bcf28a28b3aeb8c78d69767f1b5c383a580cfefdc1ade984cbbaec051173ec82f904dff11ee9b5a8983dec03d7ea
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# altmails.com 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:
|
|
10
|
+
# 1. GET / 建立 session,从 HTML inline script 提取 CSRF token('_token': 'xxx')
|
|
11
|
+
# 2. GET /random-email-address 获取随机邮箱地址(纯文本响应)
|
|
12
|
+
# 3. 收信:重新建立 session 获取新 CSRF,POST /fetch-emails/{email} 获取邮件列表
|
|
13
|
+
# 4. GET /view/{id} 获取每封邮件正文 HTML
|
|
14
|
+
module Altmails
|
|
15
|
+
CHANNEL = "altmails"
|
|
16
|
+
BASE_URL = "https://tempmail.altmails.com"
|
|
17
|
+
|
|
18
|
+
BROWSER_HEADERS = {
|
|
19
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
20
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
21
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
CSRF_RE = /'_token'\s*:\s*'([^']+)'/.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# 访问首页提取 CSRF token
|
|
29
|
+
# @return [String]
|
|
30
|
+
def fetch_csrf
|
|
31
|
+
resp = Http.get("#{BASE_URL}/", headers: BROWSER_HEADERS)
|
|
32
|
+
resp.raise_for_status
|
|
33
|
+
m = resp.body.match(CSRF_RE)
|
|
34
|
+
raise "altmails: 未能从首页提取 CSRF token" unless m
|
|
35
|
+
|
|
36
|
+
m[1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 创建临时邮箱
|
|
40
|
+
# @return [EmailInfo]
|
|
41
|
+
def generate_email
|
|
42
|
+
csrf = fetch_csrf
|
|
43
|
+
resp = Http.get("#{BASE_URL}/random-email-address",
|
|
44
|
+
headers: BROWSER_HEADERS.merge("Referer" => "#{BASE_URL}/"))
|
|
45
|
+
resp.raise_for_status
|
|
46
|
+
email = resp.body.strip
|
|
47
|
+
raise "altmails: 邮箱地址无效: #{email.inspect}" if email.empty? || !email.include?("@")
|
|
48
|
+
|
|
49
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: csrf)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 获取邮件列表
|
|
53
|
+
# @param email [String]
|
|
54
|
+
# @param token [String] CSRF token(收信时重新获取新 session)
|
|
55
|
+
# @return [Array<Email>]
|
|
56
|
+
def get_emails(email, token)
|
|
57
|
+
_ = token
|
|
58
|
+
csrf = fetch_csrf
|
|
59
|
+
fetch_url = "#{BASE_URL}/fetch-emails/#{URI.encode_www_form_component(email)}"
|
|
60
|
+
form = "_token=#{URI.encode_www_form_component(csrf)}"
|
|
61
|
+
resp = Http.post(fetch_url,
|
|
62
|
+
headers: {
|
|
63
|
+
"Accept" => "application/json, text/plain, */*",
|
|
64
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
65
|
+
"Content-Type" => "application/x-www-form-urlencoded",
|
|
66
|
+
"Origin" => BASE_URL,
|
|
67
|
+
"Referer" => "#{BASE_URL}/",
|
|
68
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
69
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
70
|
+
},
|
|
71
|
+
body: form)
|
|
72
|
+
resp.raise_for_status
|
|
73
|
+
items = begin
|
|
74
|
+
resp.json
|
|
75
|
+
rescue StandardError
|
|
76
|
+
[]
|
|
77
|
+
end
|
|
78
|
+
return [] unless items.is_a?(Array)
|
|
79
|
+
|
|
80
|
+
items.filter_map do |item|
|
|
81
|
+
next unless item.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
item["to"] = email
|
|
84
|
+
mail_id = item["id"].to_s
|
|
85
|
+
unless mail_id.empty?
|
|
86
|
+
begin
|
|
87
|
+
vr = Http.get("#{BASE_URL}/view/#{URI.encode_www_form_component(mail_id)}",
|
|
88
|
+
headers: BROWSER_HEADERS.merge("Referer" => "#{BASE_URL}/"))
|
|
89
|
+
item["html_body"] = vr.body if vr.ok?
|
|
90
|
+
rescue StandardError
|
|
91
|
+
# 忽略正文获取失败
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
Normalize.normalize_email(item, email)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module TempmailSdk
|
|
8
|
+
module Providers
|
|
9
|
+
# apihz(接口盒子)渠道实现
|
|
10
|
+
#
|
|
11
|
+
# 需 id/key 凭据,邮箱有效期 10 分钟。读信须携带创建时的 pwd,
|
|
12
|
+
# 故 token 用 base64(JSON{mail,pwd}) 编码保存。
|
|
13
|
+
module Apihz
|
|
14
|
+
CHANNEL = "apihz"
|
|
15
|
+
BASE_URL = "https://cn.apihz.cn"
|
|
16
|
+
TOK_PREFIX = "apihz1:"
|
|
17
|
+
# 官方公共账号(共享频次),未配置自有 id/key 时使用
|
|
18
|
+
PUBLIC_ID = "88888888"
|
|
19
|
+
PUBLIC_KEY = "88888888"
|
|
20
|
+
DOMAINS = %w[apimail.email apimail.vip].freeze
|
|
21
|
+
|
|
22
|
+
HEADERS = {
|
|
23
|
+
"Accept" => "application/json",
|
|
24
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
25
|
+
"(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# 读取 id/key:优先环境变量 APIHZ_ID/APIHZ_KEY,回退官方公共账号
|
|
31
|
+
def credentials
|
|
32
|
+
id = ENV["APIHZ_ID"].to_s.strip
|
|
33
|
+
key = ENV["APIHZ_KEY"].to_s.strip
|
|
34
|
+
id = PUBLIC_ID if id.empty?
|
|
35
|
+
key = PUBLIC_KEY if key.empty?
|
|
36
|
+
[id, key]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 生成随机本地部分
|
|
40
|
+
def random_local(length = 10)
|
|
41
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
|
42
|
+
Array.new(length) { chars.sample }.join
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# 生成 12 位随机密码
|
|
46
|
+
def random_password
|
|
47
|
+
chars = ("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a
|
|
48
|
+
Array.new(12) { chars.sample }.join
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# token 编码:前缀 + base64(JSON{mail,pwd})
|
|
52
|
+
def encode_token(mail, pwd)
|
|
53
|
+
raw = JSON.generate("mail" => mail, "pwd" => pwd)
|
|
54
|
+
TOK_PREFIX + Base64.strict_encode64(raw)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# token 解码
|
|
58
|
+
def decode_token(token)
|
|
59
|
+
raise "apihz: 无效 token" unless token.to_s.start_with?(TOK_PREFIX)
|
|
60
|
+
|
|
61
|
+
raw = Base64.strict_decode64(token[TOK_PREFIX.length..])
|
|
62
|
+
o = JSON.parse(raw)
|
|
63
|
+
mail = o["mail"].to_s.strip
|
|
64
|
+
pwd = o["pwd"].to_s.strip
|
|
65
|
+
raise "apihz: 无效 token" if mail.empty? || pwd.empty?
|
|
66
|
+
|
|
67
|
+
[mail, pwd]
|
|
68
|
+
rescue ArgumentError, JSON::ParserError
|
|
69
|
+
raise "apihz: 无效 token"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 创建临时邮箱
|
|
73
|
+
# GET /api/mail/mailcache.php?id=&key=&domain=&name=&pwd=&buytype=0
|
|
74
|
+
# @return [EmailInfo]
|
|
75
|
+
def generate_email
|
|
76
|
+
id, key = credentials
|
|
77
|
+
domain = DOMAINS.sample
|
|
78
|
+
name = random_local
|
|
79
|
+
pwd = random_password
|
|
80
|
+
|
|
81
|
+
url = "#{BASE_URL}/api/mail/mailcache.php?" \
|
|
82
|
+
"id=#{URI.encode_www_form_component(id)}&key=#{URI.encode_www_form_component(key)}" \
|
|
83
|
+
"&domain=#{URI.encode_www_form_component(domain)}" \
|
|
84
|
+
"&name=#{URI.encode_www_form_component(name)}" \
|
|
85
|
+
"&pwd=#{URI.encode_www_form_component(pwd)}&buytype=0"
|
|
86
|
+
|
|
87
|
+
resp = Http.get(url, headers: HEADERS)
|
|
88
|
+
resp.raise_for_status
|
|
89
|
+
data = resp.json
|
|
90
|
+
raise "apihz: 创建邮箱失败 #{data['msg']}" if data["code"] != 200 || data["mail"].to_s.empty?
|
|
91
|
+
|
|
92
|
+
final_pwd = data["pwd"].to_s
|
|
93
|
+
final_pwd = pwd if final_pwd.empty?
|
|
94
|
+
|
|
95
|
+
EmailInfo.new(channel: CHANNEL, email: data["mail"], token: encode_token(data["mail"], final_pwd))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 获取邮件列表
|
|
99
|
+
# GET /api/mail/mailgetlist.php?id=&key=&mail=&pwd=&page=1
|
|
100
|
+
# @param token [String]
|
|
101
|
+
# @return [Array<Email>]
|
|
102
|
+
def get_emails(token)
|
|
103
|
+
mail, pwd = decode_token(token)
|
|
104
|
+
id, key = credentials
|
|
105
|
+
|
|
106
|
+
url = "#{BASE_URL}/api/mail/mailgetlist.php?" \
|
|
107
|
+
"id=#{URI.encode_www_form_component(id)}&key=#{URI.encode_www_form_component(key)}" \
|
|
108
|
+
"&mail=#{URI.encode_www_form_component(mail)}" \
|
|
109
|
+
"&pwd=#{URI.encode_www_form_component(pwd)}&page=1"
|
|
110
|
+
|
|
111
|
+
resp = Http.get(url, headers: HEADERS)
|
|
112
|
+
resp.raise_for_status
|
|
113
|
+
data = resp.json
|
|
114
|
+
return [] if data["code"] != 200
|
|
115
|
+
|
|
116
|
+
list = data["data"]
|
|
117
|
+
return [] unless list.is_a?(Array) && !list.empty?
|
|
118
|
+
|
|
119
|
+
list.map do |msg|
|
|
120
|
+
from_addr = msg["frommail"].to_s
|
|
121
|
+
from_addr = msg["fromname"].to_s if from_addr.empty?
|
|
122
|
+
date_val = msg["time2"].to_s
|
|
123
|
+
date_val = msg["time1"].to_s if date_val.empty?
|
|
124
|
+
Normalize.normalize_email({
|
|
125
|
+
"id" => msg["time1"].to_s,
|
|
126
|
+
"from" => from_addr,
|
|
127
|
+
"to" => mail,
|
|
128
|
+
"subject" => msg["subject"].to_s,
|
|
129
|
+
"text" => msg["content"].to_s,
|
|
130
|
+
"html" => msg["content"].to_s,
|
|
131
|
+
"date" => date_val,
|
|
132
|
+
"isRead" => false
|
|
133
|
+
}, mail)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# awamail.com 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:
|
|
10
|
+
# 1. POST /welcome/change_mailbox(空 body)不跟随重定向,从 Set-Cookie 提取 awamail_session
|
|
11
|
+
# 2. GET /welcome/get_emails 携带 Cookie 获取邮件列表
|
|
12
|
+
module Awamail
|
|
13
|
+
CHANNEL = "awamail"
|
|
14
|
+
BASE_URL = "https://awamail.com/welcome"
|
|
15
|
+
|
|
16
|
+
HEADERS = {
|
|
17
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
18
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
|
19
|
+
"Cache-Control" => "no-cache",
|
|
20
|
+
"DNT" => "1",
|
|
21
|
+
"Origin" => "https://awamail.com",
|
|
22
|
+
"Pragma" => "no-cache",
|
|
23
|
+
"Referer" => "https://awamail.com/?lang=zh",
|
|
24
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
25
|
+
"(KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0",
|
|
26
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# 创建临时邮箱
|
|
32
|
+
# POST /welcome/change_mailbox,从 Set-Cookie 提取 awamail_session
|
|
33
|
+
# @return [EmailInfo]
|
|
34
|
+
def generate_email
|
|
35
|
+
resp = Http.post("#{BASE_URL}/change_mailbox",
|
|
36
|
+
headers: HEADERS.merge("Content-Length" => "0"))
|
|
37
|
+
# 不跟随重定向,直接从响应提取 cookie
|
|
38
|
+
session_cookie = resp.cookie_value("awamail_session")
|
|
39
|
+
raise "awamail: 未能提取 awamail_session cookie" if session_cookie.to_s.empty?
|
|
40
|
+
|
|
41
|
+
cookie_str = "awamail_session=#{session_cookie}"
|
|
42
|
+
|
|
43
|
+
data = begin
|
|
44
|
+
resp.json
|
|
45
|
+
rescue StandardError
|
|
46
|
+
{}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
email_addr = data.is_a?(Hash) ? data.dig("data", "email_address").to_s : ""
|
|
50
|
+
if email_addr.empty?
|
|
51
|
+
# 若 POST 响应无邮箱,尝试 GET get_emails 获取
|
|
52
|
+
gr = Http.get("#{BASE_URL}/get_emails",
|
|
53
|
+
headers: HEADERS.merge("Cookie" => cookie_str))
|
|
54
|
+
gr.raise_for_status
|
|
55
|
+
gd = gr.json
|
|
56
|
+
email_addr = gd.is_a?(Hash) ? gd.dig("data", "email_address").to_s : ""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise "awamail: 未能获取邮箱地址" if email_addr.empty?
|
|
60
|
+
|
|
61
|
+
info = EmailInfo.new(channel: CHANNEL, email: email_addr, token: cookie_str)
|
|
62
|
+
if data.is_a?(Hash)
|
|
63
|
+
info.expires_at = data.dig("data", "expired_at").to_s
|
|
64
|
+
info.created_at = data.dig("data", "created_at").to_s
|
|
65
|
+
end
|
|
66
|
+
info
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 获取邮件列表
|
|
70
|
+
# GET /welcome/get_emails,携带 Cookie
|
|
71
|
+
# @param token [String] awamail_session cookie 字符串
|
|
72
|
+
# @param email [String]
|
|
73
|
+
# @return [Array<Email>]
|
|
74
|
+
def get_emails(token, email)
|
|
75
|
+
resp = Http.get("#{BASE_URL}/get_emails",
|
|
76
|
+
headers: HEADERS.merge(
|
|
77
|
+
"Cookie" => token,
|
|
78
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
79
|
+
))
|
|
80
|
+
resp.raise_for_status
|
|
81
|
+
data = resp.json
|
|
82
|
+
return [] unless data.is_a?(Hash) && data["success"]
|
|
83
|
+
|
|
84
|
+
emails_raw = data.dig("data", "emails")
|
|
85
|
+
return [] unless emails_raw.is_a?(Array)
|
|
86
|
+
|
|
87
|
+
emails_raw.filter_map do |raw|
|
|
88
|
+
next unless raw.is_a?(Hash)
|
|
89
|
+
|
|
90
|
+
Normalize.normalize_email(raw, email)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# best-temp-mail.com 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 纯 JSON REST API,无需认证,无需 Session。
|
|
11
|
+
# 创建邮箱: POST /api/v3/createEmail body: {"intToken":"<uuid>"}
|
|
12
|
+
# 获取邮件: POST /api/v3/getEmailList body: {"address":"...","id":"...","intToken":"...","update_tag":"..."}
|
|
13
|
+
module BestTempMail
|
|
14
|
+
CHANNEL = "best-temp-mail"
|
|
15
|
+
API_BASE = "https://best-temp-mail.com/api/v3"
|
|
16
|
+
|
|
17
|
+
HEADERS = {
|
|
18
|
+
"Accept" => "application/json, text/plain, */*",
|
|
19
|
+
"Accept-Language" => "zh-CN,zh;q=0.9,en;q=0.8",
|
|
20
|
+
"Content-Type" => "application/json",
|
|
21
|
+
"Origin" => "https://best-temp-mail.com",
|
|
22
|
+
"Referer" => "https://best-temp-mail.com/",
|
|
23
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
24
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# 创建临时邮箱
|
|
30
|
+
# @return [EmailInfo]
|
|
31
|
+
def generate_email
|
|
32
|
+
int_token = SecureRandom.uuid
|
|
33
|
+
resp = Http.post("#{API_BASE}/createEmail",
|
|
34
|
+
headers: HEADERS,
|
|
35
|
+
json: { "intToken" => int_token })
|
|
36
|
+
resp.raise_for_status
|
|
37
|
+
data = resp.json
|
|
38
|
+
raise "best-temp-mail: 创建邮箱失败 status=#{data['status']}" \
|
|
39
|
+
if data["status"] != "success" || data.dig("data", "address").to_s.empty?
|
|
40
|
+
|
|
41
|
+
d = data["data"]
|
|
42
|
+
token = JSON.generate("intToken" => int_token, "id" => d["id"].to_s, "update_tag" => d["update_tag"].to_s)
|
|
43
|
+
EmailInfo.new(channel: CHANNEL, email: d["address"], token: token)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 获取邮件列表
|
|
47
|
+
# @param email [String]
|
|
48
|
+
# @param token [String] JSON 编码的 intToken/id/update_tag
|
|
49
|
+
# @return [Array<Email>]
|
|
50
|
+
def get_emails(email, token)
|
|
51
|
+
tkn = JSON.parse(token)
|
|
52
|
+
resp = Http.post("#{API_BASE}/getEmailList",
|
|
53
|
+
headers: HEADERS,
|
|
54
|
+
json: {
|
|
55
|
+
"address" => email,
|
|
56
|
+
"id" => tkn["id"].to_s,
|
|
57
|
+
"intToken" => tkn["intToken"].to_s,
|
|
58
|
+
"update_tag" => tkn["update_tag"].to_s
|
|
59
|
+
})
|
|
60
|
+
resp.raise_for_status
|
|
61
|
+
data = resp.json
|
|
62
|
+
return [] unless data.dig("data", "hasNewEmail")
|
|
63
|
+
|
|
64
|
+
emails_raw = data.dig("data", "emails")
|
|
65
|
+
return [] unless emails_raw.is_a?(Array)
|
|
66
|
+
|
|
67
|
+
emails_raw.filter_map do |raw|
|
|
68
|
+
next unless raw.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
Normalize.normalize_email(raw, email)
|
|
71
|
+
end
|
|
72
|
+
rescue JSON::ParserError
|
|
73
|
+
raise "best-temp-mail: 无效的 token 格式"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module TempmailSdk
|
|
7
|
+
module Providers
|
|
8
|
+
# chatgpt.org.uk 渠道实现
|
|
9
|
+
#
|
|
10
|
+
# 流程:
|
|
11
|
+
# 1. GET /api/domains/public 获取可用域名列表(过滤 is_active=1)
|
|
12
|
+
# 2. 本地生成随机用户名,拼接邮箱地址
|
|
13
|
+
# 3. POST /api/inbox-token {"email":"..."} 创建收件箱,返回 inbox JWT 和 gm_sid cookie
|
|
14
|
+
# 4. GET /api/emails?email=... 携带 x-inbox-token 和 gm_sid cookie 获取邮件
|
|
15
|
+
# token 格式: JSON {"gmSid":"...","inbox":"..."}
|
|
16
|
+
module ChatgptOrgUk
|
|
17
|
+
CHANNEL = "chatgpt-org-uk"
|
|
18
|
+
API_BASE = "https://mail.chatgpt.org.uk/api"
|
|
19
|
+
USERNAME_CHARS = ("a".."z").to_a + ("0".."9").to_a
|
|
20
|
+
|
|
21
|
+
HEADERS = {
|
|
22
|
+
"Accept" => "*/*",
|
|
23
|
+
"DNT" => "1",
|
|
24
|
+
"Origin" => "https://mail.chatgpt.org.uk",
|
|
25
|
+
"Referer" => "https://mail.chatgpt.org.uk/zh/",
|
|
26
|
+
"sec-fetch-dest" => "empty",
|
|
27
|
+
"sec-fetch-mode" => "cors",
|
|
28
|
+
"sec-fetch-site" => "same-origin",
|
|
29
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
30
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# 生成随机用户名
|
|
36
|
+
def random_username(length = 10)
|
|
37
|
+
Array.new(length) { USERNAME_CHARS.sample }.join
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 获取可用域名列表
|
|
41
|
+
def fetch_domains
|
|
42
|
+
resp = Http.get("#{API_BASE}/domains/public", headers: HEADERS)
|
|
43
|
+
resp.raise_for_status
|
|
44
|
+
data = resp.json
|
|
45
|
+
raise "chatgpt-org-uk: 获取域名失败" unless data["success"]
|
|
46
|
+
|
|
47
|
+
domains = data.dig("data", "domains") || []
|
|
48
|
+
active = domains.select { |d| d["is_active"] == 1 && !d["domain_name"].to_s.empty? }
|
|
49
|
+
.map { |d| d["domain_name"] }
|
|
50
|
+
raise "chatgpt-org-uk: 无可用域名" if active.empty?
|
|
51
|
+
|
|
52
|
+
active
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 创建收件箱,返回 [inbox_token, gm_sid]
|
|
56
|
+
def create_inbox(email)
|
|
57
|
+
resp = Http.post("#{API_BASE}/inbox-token",
|
|
58
|
+
headers: HEADERS.merge("Content-Type" => "application/json"),
|
|
59
|
+
json: { "email" => email })
|
|
60
|
+
resp.raise_for_status
|
|
61
|
+
gm_sid = resp.cookie_value("gm_sid").to_s
|
|
62
|
+
data = resp.json
|
|
63
|
+
raise "chatgpt-org-uk: inbox-token 响应缺少 token" \
|
|
64
|
+
unless data["success"] && !data.dig("auth", "token").to_s.empty?
|
|
65
|
+
|
|
66
|
+
[data.dig("auth", "token"), gm_sid]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 创建临时邮箱
|
|
70
|
+
# @return [EmailInfo]
|
|
71
|
+
def generate_email
|
|
72
|
+
domains = fetch_domains
|
|
73
|
+
domain = domains.sample
|
|
74
|
+
email = "#{random_username}@#{domain}"
|
|
75
|
+
inbox, gm_sid = create_inbox(email)
|
|
76
|
+
packed = JSON.generate("gmSid" => gm_sid, "inbox" => inbox)
|
|
77
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: packed)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# 获取邮件列表
|
|
81
|
+
# @param email [String]
|
|
82
|
+
# @param token [String] JSON {"gmSid":"...","inbox":"..."}
|
|
83
|
+
# @return [Array<Email>]
|
|
84
|
+
def get_emails(email, token)
|
|
85
|
+
parsed = JSON.parse(token)
|
|
86
|
+
gm_sid = parsed["gmSid"].to_s
|
|
87
|
+
inbox = parsed["inbox"].to_s
|
|
88
|
+
raise "chatgpt-org-uk: inbox token 缺失" if inbox.empty?
|
|
89
|
+
|
|
90
|
+
# gm_sid 丢失时重新创建 session
|
|
91
|
+
if gm_sid.empty?
|
|
92
|
+
inbox, gm_sid = create_inbox(email)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
fetch_emails_with(email, inbox, gm_sid)
|
|
96
|
+
rescue JSON::ParserError
|
|
97
|
+
raise "chatgpt-org-uk: 无效的 token 格式"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 实际拉取邮件,401/403 时重建 session 重试一次
|
|
101
|
+
def fetch_emails_with(email, inbox, gm_sid)
|
|
102
|
+
encoded = URI.encode_www_form_component(email)
|
|
103
|
+
resp = Http.get("#{API_BASE}/emails?email=#{encoded}",
|
|
104
|
+
headers: HEADERS.merge(
|
|
105
|
+
"Cookie" => "gm_sid=#{gm_sid}",
|
|
106
|
+
"x-inbox-token" => inbox
|
|
107
|
+
))
|
|
108
|
+
if [401, 403].include?(resp.status_code)
|
|
109
|
+
new_inbox, new_sid = create_inbox(email)
|
|
110
|
+
resp = Http.get("#{API_BASE}/emails?email=#{encoded}",
|
|
111
|
+
headers: HEADERS.merge(
|
|
112
|
+
"Cookie" => "gm_sid=#{new_sid}",
|
|
113
|
+
"x-inbox-token" => new_inbox
|
|
114
|
+
))
|
|
115
|
+
end
|
|
116
|
+
resp.raise_for_status
|
|
117
|
+
data = resp.json
|
|
118
|
+
raise "chatgpt-org-uk: 获取邮件失败" unless data["success"]
|
|
119
|
+
|
|
120
|
+
emails_raw = data.dig("data", "emails") || []
|
|
121
|
+
emails_raw.filter_map do |raw|
|
|
122
|
+
next unless raw.is_a?(Hash)
|
|
123
|
+
|
|
124
|
+
Normalize.normalize_email(raw, email)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module TempmailSdk
|
|
6
|
+
module Providers
|
|
7
|
+
# disposablemail.com 渠道实现
|
|
8
|
+
#
|
|
9
|
+
# 流程:
|
|
10
|
+
# 1. GET / 获取 PHPSESSID cookie + HTML 中的 CSRF token(CSRF="xxx")
|
|
11
|
+
# 2. GET /index/index?csrf_token={csrf} 创建邮箱,返回 {"email":"user@domain.com"}
|
|
12
|
+
# 3. GET /index/refresh 携带 session cookie 获取邮件列表(捷克语字段: predmet/od/kdy/precteno)
|
|
13
|
+
# 4. GET /email/id/{id} 获取邮件正文 HTML
|
|
14
|
+
# token 存储 "PHPSESSID=xxx" cookie 字符串。
|
|
15
|
+
module Disposablemail
|
|
16
|
+
CHANNEL = "disposablemail"
|
|
17
|
+
BASE_URL = "https://www.disposablemail.com"
|
|
18
|
+
|
|
19
|
+
CSRF_RE = /CSRF="([^"]+)"/.freeze
|
|
20
|
+
|
|
21
|
+
BROWSER_HEADERS = {
|
|
22
|
+
"Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
23
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
24
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
25
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
AJAX_HEADERS = {
|
|
29
|
+
"Accept" => "application/json, text/javascript, */*; q=0.01",
|
|
30
|
+
"Accept-Language" => "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
|
31
|
+
"Referer" => "#{BASE_URL}/",
|
|
32
|
+
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
|
|
33
|
+
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
|
34
|
+
"X-Requested-With" => "XMLHttpRequest"
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# 提取 Set-Cookie 中的 PHPSESSID
|
|
40
|
+
def extract_session(resp)
|
|
41
|
+
resp.set_cookies.each do |line|
|
|
42
|
+
first = line.split(";", 2).first.to_s.strip
|
|
43
|
+
k, v = first.split("=", 2)
|
|
44
|
+
return "#{k}=#{v}" if k == "PHPSESSID"
|
|
45
|
+
end
|
|
46
|
+
""
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 创建临时邮箱
|
|
50
|
+
# @return [EmailInfo]
|
|
51
|
+
def generate_email
|
|
52
|
+
r1 = Http.get("#{BASE_URL}/", headers: BROWSER_HEADERS)
|
|
53
|
+
r1.raise_for_status
|
|
54
|
+
m = r1.body.match(CSRF_RE)
|
|
55
|
+
raise "disposablemail: 未能从首页提取 CSRF token" unless m
|
|
56
|
+
|
|
57
|
+
csrf = m[1]
|
|
58
|
+
session_cookie = extract_session(r1)
|
|
59
|
+
|
|
60
|
+
r2 = Http.get("#{BASE_URL}/index/index?csrf_token=#{csrf}",
|
|
61
|
+
headers: AJAX_HEADERS.merge(session_cookie.empty? ? {} : { "Cookie" => session_cookie }))
|
|
62
|
+
r2.raise_for_status
|
|
63
|
+
|
|
64
|
+
# 若 index 阶段刷新了 PHPSESSID,改用新的
|
|
65
|
+
new_sess = extract_session(r2)
|
|
66
|
+
session_cookie = new_sess unless new_sess.empty?
|
|
67
|
+
|
|
68
|
+
data = JSON.parse(r2.body)
|
|
69
|
+
email = data["email"].to_s.strip
|
|
70
|
+
raise "disposablemail: 邮箱地址无效: #{email.inspect}" if email.empty? || !email.include?("@")
|
|
71
|
+
|
|
72
|
+
EmailInfo.new(channel: CHANNEL, email: email, token: session_cookie)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# 获取邮件列表
|
|
76
|
+
# @param email [String]
|
|
77
|
+
# @param token [String] PHPSESSID cookie 字符串
|
|
78
|
+
# @return [Array<Email>]
|
|
79
|
+
def get_emails(email, token)
|
|
80
|
+
raise "disposablemail: 邮箱地址为空" if email.to_s.strip.empty?
|
|
81
|
+
|
|
82
|
+
headers = AJAX_HEADERS.dup
|
|
83
|
+
headers = headers.merge("Cookie" => token) unless token.to_s.empty?
|
|
84
|
+
|
|
85
|
+
resp = Http.get("#{BASE_URL}/index/refresh", headers: headers)
|
|
86
|
+
resp.raise_for_status
|
|
87
|
+
trimmed = resp.body.strip
|
|
88
|
+
return [] if trimmed == "0" || trimmed.empty? || trimmed == "[]"
|
|
89
|
+
|
|
90
|
+
list = begin
|
|
91
|
+
JSON.parse(trimmed)
|
|
92
|
+
rescue JSON::ParserError
|
|
93
|
+
[]
|
|
94
|
+
end
|
|
95
|
+
return [] unless list.is_a?(Array) && !list.empty?
|
|
96
|
+
|
|
97
|
+
list.map do |item|
|
|
98
|
+
mail_id = item["id"].to_s
|
|
99
|
+
html_body = fetch_body(mail_id, token)
|
|
100
|
+
Normalize.normalize_email({
|
|
101
|
+
"id" => mail_id,
|
|
102
|
+
"from" => item["od"].to_s,
|
|
103
|
+
"to" => email,
|
|
104
|
+
"subject" => item["predmet"].to_s,
|
|
105
|
+
"html" => html_body,
|
|
106
|
+
"date" => item["kdy"].to_s,
|
|
107
|
+
"isRead" => item["precteno"] == "precteno"
|
|
108
|
+
}, email)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 获取单封邮件 HTML 正文
|
|
113
|
+
def fetch_body(mail_id, token)
|
|
114
|
+
return "" if mail_id.empty?
|
|
115
|
+
|
|
116
|
+
headers = AJAX_HEADERS.dup
|
|
117
|
+
headers = headers.merge("Cookie" => token) unless token.to_s.empty?
|
|
118
|
+
resp = Http.get("#{BASE_URL}/email/id/#{mail_id}", headers: headers)
|
|
119
|
+
resp.ok? ? resp.body : ""
|
|
120
|
+
rescue StandardError
|
|
121
|
+
""
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|