fedbox 0.0.1 → 0.0.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.
- package/QUICKSTART.md +90 -0
- package/README.md +32 -13
- package/bin/cli.js +252 -23
- package/lib/actions.js +312 -0
- package/lib/server.js +422 -64
- package/package.json +1 -1
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Fedbox Quickstart
|
|
2
|
+
|
|
3
|
+
Clean slate setup with ngrok federation.
|
|
4
|
+
|
|
5
|
+
## 1. Clean existing data
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Remove database only
|
|
9
|
+
fedbox clean
|
|
10
|
+
|
|
11
|
+
# Remove everything (database + config + keys)
|
|
12
|
+
fedbox clean --all
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 2. Initialize
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
fedbox init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Prompts for: username, display name, bio, port (default 3000).
|
|
22
|
+
|
|
23
|
+
Creates `fedbox.json` with generated keypair.
|
|
24
|
+
|
|
25
|
+
## 3. Start server
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
fedbox start
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Server runs at `http://localhost:3000/{username}`
|
|
32
|
+
|
|
33
|
+
## 4. Expose with ngrok
|
|
34
|
+
|
|
35
|
+
In another terminal:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ngrok http 3000
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Copy the https URL (e.g., `https://abc123.ngrok-free.app`)
|
|
42
|
+
|
|
43
|
+
## 5. Configure domain
|
|
44
|
+
|
|
45
|
+
Edit `fedbox.json`, add domain (without https://):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"domain": "abc123.ngrok-free.app",
|
|
50
|
+
...
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Restart server (`Ctrl+C`, then `fedbox start`).
|
|
55
|
+
|
|
56
|
+
## 6. Test federation
|
|
57
|
+
|
|
58
|
+
From Mastodon, search for `@{username}@{domain}`
|
|
59
|
+
|
|
60
|
+
## 7. Post something
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
fedbox post "Hello, Fediverse!"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## URI Structure (Solid-compatible)
|
|
67
|
+
|
|
68
|
+
| URI | Purpose |
|
|
69
|
+
|-----|---------|
|
|
70
|
+
| `/{username}` | Profile (HTML + JSON-LD) |
|
|
71
|
+
| `/{username}#me` | WebID (Actor ID) |
|
|
72
|
+
| `/{username}/inbox` | Inbox |
|
|
73
|
+
| `/{username}/outbox` | Outbox |
|
|
74
|
+
| `/{username}/posts/{id}` | Individual post |
|
|
75
|
+
| `/{username}#main-key` | Public key |
|
|
76
|
+
|
|
77
|
+
## All Commands
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
fedbox init # Setup identity
|
|
81
|
+
fedbox start # Start server
|
|
82
|
+
fedbox status # Show config
|
|
83
|
+
fedbox post "text" # Post to followers
|
|
84
|
+
fedbox follow @user@dom # Follow someone
|
|
85
|
+
fedbox timeline # View feed
|
|
86
|
+
fedbox reply <url> "text"# Reply to post
|
|
87
|
+
fedbox posts # View own posts
|
|
88
|
+
fedbox clean # Remove database
|
|
89
|
+
fedbox clean --all # Remove everything
|
|
90
|
+
```
|
package/README.md
CHANGED
|
@@ -15,10 +15,32 @@ fedbox init
|
|
|
15
15
|
|
|
16
16
|
# Start your server
|
|
17
17
|
fedbox start
|
|
18
|
+
|
|
19
|
+
# Post something!
|
|
20
|
+
fedbox post "Hello, Fediverse!"
|
|
18
21
|
```
|
|
19
22
|
|
|
20
23
|
That's it. You're on the Fediverse.
|
|
21
24
|
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Setup
|
|
29
|
+
fedbox init # Set up your identity
|
|
30
|
+
fedbox start # Start the server
|
|
31
|
+
fedbox status # Show your profile info
|
|
32
|
+
|
|
33
|
+
# Social
|
|
34
|
+
fedbox post "text" # Post a message to followers
|
|
35
|
+
fedbox follow @user@domain # Follow someone
|
|
36
|
+
fedbox timeline # View posts from people you follow
|
|
37
|
+
fedbox reply <url> "text" # Reply to a post
|
|
38
|
+
fedbox posts # View your own posts
|
|
39
|
+
|
|
40
|
+
# Help
|
|
41
|
+
fedbox help # Show all commands
|
|
42
|
+
```
|
|
43
|
+
|
|
22
44
|
## Federation (so Mastodon can find you)
|
|
23
45
|
|
|
24
46
|
To federate with the wider Fediverse, you need a public HTTPS URL. The easiest way:
|
|
@@ -42,28 +64,25 @@ Restart your server, and you're federated! Search for `@yourname@abc123.ngrok.io
|
|
|
42
64
|
## What You Get
|
|
43
65
|
|
|
44
66
|
- **Your own identity** — `@you@yourdomain.com`
|
|
67
|
+
- **Post from CLI** — `fedbox post "Hello world"`
|
|
68
|
+
- **Follow anyone** — `fedbox follow @user@mastodon.social`
|
|
69
|
+
- **View timeline** — `fedbox timeline`
|
|
70
|
+
- **Reply to posts** — `fedbox reply <url> "Nice!"`
|
|
45
71
|
- **ActivityPub compatible** — Works with Mastodon, Pleroma, Pixelfed, etc.
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
|
|
50
|
-
## Commands
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
fedbox init # Set up your identity
|
|
54
|
-
fedbox start # Start the server
|
|
55
|
-
fedbox status # Show current config
|
|
56
|
-
fedbox help # Show help
|
|
57
|
-
```
|
|
72
|
+
- **HTTP Signature verification** — Secure federation
|
|
73
|
+
- **Rate limiting** — Protection against abuse
|
|
74
|
+
- **Persistent storage** — SQLite database
|
|
75
|
+
- **Beautiful profile page** — Dark theme, shows your posts
|
|
58
76
|
|
|
59
77
|
## How It Works
|
|
60
78
|
|
|
61
79
|
Fedbox uses [microfed](https://github.com/micro-fed/microfed.org) for ActivityPub primitives:
|
|
62
80
|
|
|
63
81
|
- **Profile** — Your actor/identity
|
|
64
|
-
- **Inbox** — Receive follows, likes, boosts
|
|
82
|
+
- **Inbox** — Receive follows, likes, boosts, posts
|
|
65
83
|
- **Outbox** — Your posts
|
|
66
84
|
- **WebFinger** — So others can find you
|
|
85
|
+
- **HTTP Signatures** — Secure signed requests
|
|
67
86
|
|
|
68
87
|
Data is stored in SQLite (`data/fedbox.db`).
|
|
69
88
|
|
package/bin/cli.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Fedbox CLI
|
|
5
5
|
* Zero to Fediverse in 60 seconds
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createInterface } from 'readline'
|
|
9
|
-
import { existsSync, writeFileSync, mkdirSync } from 'fs'
|
|
10
|
-
import { join } from 'path'
|
|
9
|
+
import { existsSync, writeFileSync, mkdirSync, readFileSync, unlinkSync, rmSync } from 'fs'
|
|
11
10
|
import { generateKeypair } from 'microfed/auth'
|
|
12
11
|
|
|
13
12
|
const rl = createInterface({
|
|
@@ -30,6 +29,12 @@ const COMMANDS = {
|
|
|
30
29
|
init: runInit,
|
|
31
30
|
start: runStart,
|
|
32
31
|
status: runStatus,
|
|
32
|
+
post: runPost,
|
|
33
|
+
follow: runFollow,
|
|
34
|
+
timeline: runTimeline,
|
|
35
|
+
reply: runReply,
|
|
36
|
+
posts: runPosts,
|
|
37
|
+
clean: runClean,
|
|
33
38
|
help: runHelp
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -49,7 +54,6 @@ async function main() {
|
|
|
49
54
|
async function runInit() {
|
|
50
55
|
console.log(BANNER)
|
|
51
56
|
|
|
52
|
-
// Check if already initialized
|
|
53
57
|
if (existsSync('fedbox.json')) {
|
|
54
58
|
console.log('⚠️ Already initialized. Delete fedbox.json to start over.\n')
|
|
55
59
|
process.exit(1)
|
|
@@ -57,7 +61,6 @@ async function runInit() {
|
|
|
57
61
|
|
|
58
62
|
console.log('Let\'s get you on the Fediverse!\n')
|
|
59
63
|
|
|
60
|
-
// Gather info
|
|
61
64
|
const username = await ask('👤 Username (e.g., alice): ')
|
|
62
65
|
const displayName = await ask('📛 Display name (e.g., Alice): ') || username
|
|
63
66
|
const summary = await ask('📝 Bio (optional): ') || ''
|
|
@@ -66,7 +69,6 @@ async function runInit() {
|
|
|
66
69
|
console.log('\n🔐 Generating keypair...')
|
|
67
70
|
const { publicKey, privateKey } = generateKeypair()
|
|
68
71
|
|
|
69
|
-
// Create config
|
|
70
72
|
const config = {
|
|
71
73
|
username: username.toLowerCase().replace(/[^a-z0-9]/g, ''),
|
|
72
74
|
displayName,
|
|
@@ -77,12 +79,10 @@ async function runInit() {
|
|
|
77
79
|
createdAt: new Date().toISOString()
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
// Create data directory
|
|
81
82
|
if (!existsSync('data')) {
|
|
82
83
|
mkdirSync('data')
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
// Save config
|
|
86
86
|
writeFileSync('fedbox.json', JSON.stringify(config, null, 2))
|
|
87
87
|
console.log('✅ Config saved to fedbox.json')
|
|
88
88
|
|
|
@@ -118,7 +118,6 @@ async function runStart() {
|
|
|
118
118
|
|
|
119
119
|
console.log('🚀 Starting server...\n')
|
|
120
120
|
|
|
121
|
-
// Dynamic import to avoid loading before init
|
|
122
121
|
const { startServer } = await import('../lib/server.js')
|
|
123
122
|
await startServer()
|
|
124
123
|
}
|
|
@@ -129,39 +128,269 @@ async function runStatus() {
|
|
|
129
128
|
process.exit(1)
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
const config = JSON.parse(
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
const config = JSON.parse(readFileSync('fedbox.json', 'utf8'))
|
|
132
|
+
|
|
133
|
+
// Get follower/following counts
|
|
134
|
+
let followers = 0, following = 0
|
|
135
|
+
try {
|
|
136
|
+
const { initStore, getFollowerCount, getFollowingCount } = await import('../lib/store.js')
|
|
137
|
+
initStore()
|
|
138
|
+
followers = getFollowerCount()
|
|
139
|
+
following = getFollowingCount()
|
|
140
|
+
} catch {}
|
|
135
141
|
|
|
136
142
|
console.log(`
|
|
137
143
|
╔═══════════════════════════════════════════╗
|
|
138
144
|
║ 📊 FEDBOX STATUS ║
|
|
139
145
|
╚═══════════════════════════════════════════╝
|
|
140
146
|
|
|
141
|
-
Username:
|
|
142
|
-
Name:
|
|
143
|
-
Port:
|
|
144
|
-
Domain:
|
|
145
|
-
|
|
147
|
+
Username: @${config.username}
|
|
148
|
+
Name: ${config.displayName}
|
|
149
|
+
Port: ${config.port}
|
|
150
|
+
Domain: ${config.domain || '(not set - run with ngrok)'}
|
|
151
|
+
Followers: ${followers}
|
|
152
|
+
Following: ${following}
|
|
153
|
+
Created: ${config.createdAt}
|
|
154
|
+
`)
|
|
155
|
+
|
|
156
|
+
rl.close()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function runPost() {
|
|
160
|
+
if (!existsSync('fedbox.json')) {
|
|
161
|
+
console.log('❌ Not initialized. Run: fedbox init\n')
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const content = process.argv[3]
|
|
166
|
+
if (!content) {
|
|
167
|
+
console.log('Usage: fedbox post "Your message here"')
|
|
168
|
+
process.exit(1)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { post } = await import('../lib/actions.js')
|
|
172
|
+
|
|
173
|
+
console.log('📝 Creating post...')
|
|
174
|
+
const result = await post(content)
|
|
175
|
+
|
|
176
|
+
console.log(`
|
|
177
|
+
✅ Posted!
|
|
178
|
+
|
|
179
|
+
ID: ${result.noteId}
|
|
180
|
+
Content: ${content}
|
|
181
|
+
Delivered to: ${result.delivered.success} followers (${result.delivered.failed} failed)
|
|
182
|
+
`)
|
|
183
|
+
|
|
184
|
+
rl.close()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function runFollow() {
|
|
188
|
+
if (!existsSync('fedbox.json')) {
|
|
189
|
+
console.log('❌ Not initialized. Run: fedbox init\n')
|
|
190
|
+
process.exit(1)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const handle = process.argv[3]
|
|
194
|
+
if (!handle) {
|
|
195
|
+
console.log('Usage: fedbox follow @user@domain')
|
|
196
|
+
process.exit(1)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { follow } = await import('../lib/actions.js')
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const result = await follow(handle)
|
|
203
|
+
console.log(`
|
|
204
|
+
✅ Follow request sent!
|
|
205
|
+
|
|
206
|
+
User: ${result.actor.preferredUsername || result.actor.name}
|
|
207
|
+
Actor: ${result.actor.id}
|
|
208
|
+
|
|
209
|
+
Waiting for them to accept...
|
|
210
|
+
`)
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.log(`❌ ${err.message}`)
|
|
213
|
+
process.exit(1)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
rl.close()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function runTimeline() {
|
|
220
|
+
if (!existsSync('fedbox.json')) {
|
|
221
|
+
console.log('❌ Not initialized. Run: fedbox init\n')
|
|
222
|
+
process.exit(1)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { timeline } = await import('../lib/actions.js')
|
|
226
|
+
const posts = timeline(20)
|
|
227
|
+
|
|
228
|
+
if (posts.length === 0) {
|
|
229
|
+
console.log(`
|
|
230
|
+
📭 Your timeline is empty.
|
|
231
|
+
|
|
232
|
+
Follow some people with: fedbox follow @user@domain
|
|
233
|
+
`)
|
|
234
|
+
rl.close()
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log(`
|
|
239
|
+
╔═══════════════════════════════════════════╗
|
|
240
|
+
║ 📰 TIMELINE ║
|
|
241
|
+
╚═══════════════════════════════════════════╝
|
|
242
|
+
`)
|
|
243
|
+
|
|
244
|
+
for (const post of posts) {
|
|
245
|
+
const author = post.author?.split('/').pop() || 'unknown'
|
|
246
|
+
const content = post.content
|
|
247
|
+
.replace(/<[^>]*>/g, '') // Strip HTML
|
|
248
|
+
.slice(0, 200)
|
|
249
|
+
const date = new Date(post.published).toLocaleString()
|
|
250
|
+
|
|
251
|
+
console.log(`┌─ @${author} · ${date}`)
|
|
252
|
+
console.log(`│ ${content}`)
|
|
253
|
+
if (post.inReplyTo) {
|
|
254
|
+
console.log(`│ ↩️ Reply to: ${post.inReplyTo}`)
|
|
255
|
+
}
|
|
256
|
+
console.log(`└─ ${post.id}`)
|
|
257
|
+
console.log()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
rl.close()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runReply() {
|
|
264
|
+
if (!existsSync('fedbox.json')) {
|
|
265
|
+
console.log('❌ Not initialized. Run: fedbox init\n')
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const postUrl = process.argv[3]
|
|
270
|
+
const content = process.argv[4]
|
|
271
|
+
|
|
272
|
+
if (!postUrl || !content) {
|
|
273
|
+
console.log('Usage: fedbox reply <post-url> "Your reply"')
|
|
274
|
+
process.exit(1)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { reply } = await import('../lib/actions.js')
|
|
278
|
+
|
|
279
|
+
console.log('💬 Sending reply...')
|
|
280
|
+
const result = await reply(postUrl, content)
|
|
281
|
+
|
|
282
|
+
console.log(`
|
|
283
|
+
✅ Reply sent!
|
|
284
|
+
|
|
285
|
+
ID: ${result.noteId}
|
|
286
|
+
In reply to: ${postUrl}
|
|
287
|
+
Delivered to: ${result.delivered.success} inboxes
|
|
146
288
|
`)
|
|
147
289
|
|
|
148
290
|
rl.close()
|
|
149
291
|
}
|
|
150
292
|
|
|
293
|
+
async function runPosts() {
|
|
294
|
+
if (!existsSync('fedbox.json')) {
|
|
295
|
+
console.log('❌ Not initialized. Run: fedbox init\n')
|
|
296
|
+
process.exit(1)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { myPosts } = await import('../lib/actions.js')
|
|
300
|
+
const posts = myPosts(20)
|
|
301
|
+
|
|
302
|
+
if (posts.length === 0) {
|
|
303
|
+
console.log(`
|
|
304
|
+
📭 You haven't posted anything yet.
|
|
305
|
+
|
|
306
|
+
Create a post with: fedbox post "Hello, Fediverse!"
|
|
307
|
+
`)
|
|
308
|
+
rl.close()
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
console.log(`
|
|
313
|
+
╔═══════════════════════════════════════════╗
|
|
314
|
+
║ 📝 YOUR POSTS ║
|
|
315
|
+
╚═══════════════════════════════════════════╝
|
|
316
|
+
`)
|
|
317
|
+
|
|
318
|
+
for (const post of posts) {
|
|
319
|
+
const date = new Date(post.published).toLocaleString()
|
|
320
|
+
console.log(`┌─ ${date}`)
|
|
321
|
+
console.log(`│ ${post.content}`)
|
|
322
|
+
if (post.in_reply_to) {
|
|
323
|
+
console.log(`│ ↩️ Reply to: ${post.in_reply_to}`)
|
|
324
|
+
}
|
|
325
|
+
console.log(`└─ ${post.id}`)
|
|
326
|
+
console.log()
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
rl.close()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function runClean() {
|
|
333
|
+
const all = process.argv[3] === '--all'
|
|
334
|
+
|
|
335
|
+
console.log('🧹 Cleaning up...\n')
|
|
336
|
+
|
|
337
|
+
// Remove database
|
|
338
|
+
if (existsSync('data/fedbox.db')) {
|
|
339
|
+
unlinkSync('data/fedbox.db')
|
|
340
|
+
console.log(' ✓ Removed data/fedbox.db')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Remove data directory if empty
|
|
344
|
+
if (existsSync('data')) {
|
|
345
|
+
try {
|
|
346
|
+
rmSync('data', { recursive: false })
|
|
347
|
+
console.log(' ✓ Removed data/')
|
|
348
|
+
} catch {
|
|
349
|
+
// Directory not empty, that's ok
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Remove config if --all
|
|
354
|
+
if (all && existsSync('fedbox.json')) {
|
|
355
|
+
unlinkSync('fedbox.json')
|
|
356
|
+
console.log(' ✓ Removed fedbox.json')
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log('\n✅ Clean complete!')
|
|
360
|
+
|
|
361
|
+
if (!all) {
|
|
362
|
+
console.log('\n Tip: Use "fedbox clean --all" to also remove config')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
rl.close()
|
|
366
|
+
}
|
|
367
|
+
|
|
151
368
|
function runHelp() {
|
|
152
369
|
console.log(`
|
|
153
370
|
${BANNER}
|
|
154
|
-
Usage: fedbox <command>
|
|
371
|
+
Usage: fedbox <command> [args]
|
|
372
|
+
|
|
373
|
+
Setup:
|
|
374
|
+
init Set up a new Fediverse identity
|
|
375
|
+
start Start the server
|
|
376
|
+
status Show current configuration
|
|
377
|
+
|
|
378
|
+
Social:
|
|
379
|
+
post "text" Post a message to your followers
|
|
380
|
+
follow @user@dom Follow a remote user
|
|
381
|
+
timeline View posts from people you follow
|
|
382
|
+
reply <url> "text" Reply to a post
|
|
383
|
+
posts View your own posts
|
|
155
384
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
status Show current configuration
|
|
160
|
-
help Show this help
|
|
385
|
+
Other:
|
|
386
|
+
clean Remove database (add --all to also remove config)
|
|
387
|
+
help Show this help
|
|
161
388
|
|
|
162
389
|
Quick start:
|
|
163
390
|
$ fedbox init
|
|
164
391
|
$ fedbox start
|
|
392
|
+
$ fedbox post "Hello, Fediverse!"
|
|
393
|
+
$ fedbox follow @user@mastodon.social
|
|
165
394
|
|
|
166
395
|
For federation (so Mastodon can find you):
|
|
167
396
|
$ ngrok http 3000
|