-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpostblog_demo.html
228 lines (217 loc) · 8.94 KB
/
postblog_demo.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>atproto bluesky post embed blog demo</title>
<script type="importmap">
{
"imports": {
"magika": "https://cdn.jsdelivr.net/npm/magika@0.2.13/dist/mjs/magika.js",
"fs/promises": "https://cdn.jsdelivr.net/npm/memfs-browser@3.5.10302/dist/memfs.esm.js",
"@tensorflow/tfjs": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.22.0/dist/tf.fesm.js",
"@atproto/common-web": "https://cdn.jsdelivr.net/npm/@atproto/common-web@0.3.1/dist/tid.js/+esm"
}
}
</script>
<script type="module">
import safeBuffer from "https://cdn.jsdelivr.net/npm/safe-buffer@5.2.1/+esm"
window.Buffer = safeBuffer.Buffer
</script>
<script type="module">
import {TID} from '@atproto/common-web'
import { Magika } from "magika"
const magika = new Magika()
await magika.load()
const response = await fetch('https://raw.githubusercontent.com/google/magika/refs/heads/main/python/src/magika/config/content_types_kb.min.json')
const content_types = await response.json()
const $ = document.querySelector.bind(document)
let instance = null
let handle = null
let jwt = null
let did = null
let blobs = []
let tid = undefined
async function detech_file_type(file) {
if (file.type) return file.type
const prediction = await magika.identifyBytesFull(new Uint8Array(await file.arrayBuffer()))
return content_types[prediction.label].mime_type
}
async function xrpc(xrpc_method, {params = {}, data = null}) {
let api = new URL(`https://${instance}/xrpc/${xrpc_method}`)
let searchParams = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
searchParams.set(key, value)
}
api.search = searchParams.toString()
let method = 'GET'
let headers = {}
let body = null
if (jwt !== null) {
headers['Authorization'] = `Bearer ${jwt}`
}
if (data) {
method = 'POST'
if (data instanceof File) {
headers['Content-Type'] = await detech_file_type(data)
body = data
} else {
headers['Content-Type'] = 'application/json'
body = JSON.stringify(data)
}
}
let response = await fetch(api.toString(), {headers, method, body})
let content_type = response.headers.get('content-type')
if (content_type.startsWith('application/json')) return await response.json()
return await response.text()
}
async function auth() {
instance = $('#instance').value
handle = $('#handle').value
let password = $('#password').value
let data = {
identifier: handle,
password
}
let result = await xrpc('com.atproto.server.createSession', {data})
$('#auth-result').innerText = JSON.stringify(result)
jwt = result.accessJwt
did = result.did
handle = result.handle
}
window.auth = auth
async function upload_blob() {
if (jwt === null) return alert('missing jwt')
let file = $('#blob').files[0]
let result = await xrpc('com.atproto.repo.uploadBlob', {data: file})
let repr_ele = document.createElement('li')
repr_ele.innerText = JSON.stringify(result)
$('#blob-list').appendChild(repr_ele)
blobs.push(result.blob)
}
window.upload_blob = upload_blob
async function create_post() {
if (jwt === null) return alert('missing jwt')
tid = TID.next(tid)
const rkey = tid.toString()
let text = $('#post-text').value
let at_uri = `at://${did}/app.bsky.feed.post/${rkey}`
// uri reference to self, magic
let uri = `https://did-on-hukoubook-com.pages.dev/postblog_demo?show=${encodeURIComponent(at_uri)}`
$('#external-uri').value = uri
let title = $('#external-title').value
let description = $('#external-description').value
let thumb = null
if ($('#external-thumb').files.length) {
let result = await xrpc('com.atproto.repo.uploadBlob', {data: $('#external-thumb').files[0]})
thumb = result.blob
}
let result = await xrpc('com.atproto.repo.uploadBlob', {data: $('#blog').files[0]})
let blog = result.blob
let data = {
repo: handle,
"collection": "app.bsky.feed.post",
rkey,
"record": {
"$type": "app.bsky.feed.post",
text,
"createdAt": new Date().toISOString(),
"embed": {
"$type": "app.bsky.embed.external",
external: {
uri, title, description, thumb,
// attach magic
blobs, blog
}
}
}
}
result = await xrpc('com.atproto.repo.createRecord', {data})
$('#create-post-result').value = JSON.stringify(result)
let preview_ele = document.createElement('a')
preview_ele.href = uri
preview_ele.innerText = 'preview blog'
$('body').appendChild(preview_ele)
}
window.create_post = create_post
async function resolve_pds_instance(at_did) {
let did_uri = undefined
if (at_did.startsWith('did:plc:')) {
did_uri = `https://plc.directory/${at_did}`
} else {
did_uri = `${at_did.slice('did:web:'.length)}/.well-known/did.json`
}
let response = await fetch(did_uri)
let result = await response.json()
for (let service of result.service) {
if (service.id === '#atproto_pds') {
let endpoint = new URL(service.serviceEndpoint)
instance = endpoint.host
return
}
}
throw Error('missing pds service')
}
async function show_blog() {
let url = new URL(location.href)
let at_uri = url.searchParams.get('show')
if (!at_uri) return
let [at_did, at_collection, at_rkey] = at_uri.slice('at://'.length).split('/')
await resolve_pds_instance(at_did)
let params = {repo: at_did, collection: at_collection, rkey: at_rkey}
let result = await xrpc('com.atproto.repo.getRecord', {params})
$('#post-content').innerText = JSON.stringify(result)
let post = result.value
let blog_blob = post.embed.external.blog
if (!blog_blob) return alert('no blog found in this post')
params = {
did: at_did, cid: blog_blob.ref.$link
}
result = await xrpc('com.atproto.sync.getBlob', {params})
$('#blog-content').innerText = result
}
window.show_blog = show_blog
if (document.readyState === 'loading') {
// 仍在加载,等待事件
document.addEventListener('DOMContentLoaded', show_blog);
} else {
// DOM 已就绪
show_blog();
}
</script>
</head>
<body>
<label for="instance">pds instance</label>
<input id="instance" name="instance" type="text" value="network.hukoubook.com"><br>
<label for="handle">account handle</label>
<input id="handle" name="handle" type="text" value="testfirst.hukoubook.com"><br>
<label for="password">app password</label>
<input id="password" name="password" type="password"><br>
<button onclick="auth()">auth</button>
<p id="auth-result"></p>
<label for="blob">blob upload</label>
<input id="blob" name="blob" type="file"><br>
<button onclick="upload_blob()">upload</button>
<ol id="blob-list">
</ol>
<p>all above blob will attach to post in external object under blobs key</p>
<label for="post-text">post text</label>
<input id="post-text" name="post-text" type="text"><br>
<label for="external-uri">external uri</label>
<input id="external-uri" name="external-uri" type="text" disabled><br>
<label for="external-title">external title</label>
<input id="external-title" name="external-title" type="text"><br>
<label for="external-description">external description</label>
<input id="external-description" name="external-description" type="text"><br>
<label for="external-thumb">external thumb image</label>
<input id="external-thumb" name="external-thumb" type="file"><br>
<label for="blog">blog markdown file</label>
<input id="blog" name="blog" type="file"><br>
<button onclick="create_post()">create post</button>
<p id="create-post-result"></p>
<h3>show post & blog</h3>
<button onclick="show_blog()">show</button>
<textarea id="post-content"></textarea>
<textarea id="blog-content"></textarea>
</body>
</html>