key | size |
---|---|
{{$b}} | {{$size}} | 12 | {{end}} 13 |
12 | #{{.Channel}} 13 |
14 | 20 |{{.Ref}}
18 |34 | {{range $k, $v := .Follows}} 35 | {{Avatar $k}} 36 | {{end}} 37 |
38 | 39 |" + string(m.Encode()) + "") 183 | } 184 | 185 | err := ContentTemplates.ExecuteTemplate(buf, t+".tpl", struct { 186 | Message *ssb.SignedMessage 187 | Content interface{} 188 | Levels int 189 | }{m, md, levels - 1}) 190 | if err != nil { 191 | log.Println(err) 192 | } 193 | return template.HTML(buf.String()) 194 | }, 195 | }).ParseGlob("templates/content/*.tpl")) 196 | } 197 | 198 | var PageTemplates = template.Must(template.New("index").Funcs(template.FuncMap{ 199 | "Avatar": func(ref ssb.Ref) template.HTML { 200 | if ref.Type != ssb.RefFeed { 201 | return "" 202 | } 203 | var a *social.About 204 | datastore.DB().View(func(tx *bolt.Tx) error { 205 | a = social.GetAbout(tx, ref) 206 | return nil 207 | }) 208 | buf := &bytes.Buffer{} 209 | err := ContentTemplates.ExecuteTemplate(buf, "follow.tpl", struct { 210 | About *social.About 211 | Ref ssb.Ref 212 | }{a, ref}) 213 | if err != nil { 214 | log.Println(err) 215 | } 216 | return template.HTML(buf.String()) 217 | }, 218 | "RenderContent": func(m *ssb.SignedMessage, levels int) template.HTML { 219 | t, md := m.DecodeMessage() 220 | if t == "" { 221 | return template.HTML("") 222 | } 223 | tpl := ContentTemplates.Lookup(t + ".tpl") 224 | if tpl == nil { 225 | return template.HTML("
" + string(m.Encode()) + "") 226 | } 227 | buf := &bytes.Buffer{} 228 | err := ContentTemplates.ExecuteTemplate(buf, t+".tpl", struct { 229 | Message *ssb.SignedMessage 230 | Content interface{} 231 | Levels int 232 | }{m, md, levels - 1}) 233 | if err != nil { 234 | log.Println(err) 235 | } 236 | return template.HTML(buf.String()) 237 | }, 238 | "HasBlob": func(ref ssb.Ref) bool { 239 | return blobs.Get(datastore).Has(ref) 240 | }, 241 | "RenderContentTemplate": func(m *ssb.SignedMessage, levels int, tpl string) template.HTML { 242 | t, md := m.DecodeMessage() 243 | buf := &bytes.Buffer{} 244 | err := ContentTemplates.ExecuteTemplate(buf, tpl+".tpl", struct { 245 | Message *ssb.SignedMessage 246 | Content interface{} 247 | Levels int 248 | }{m, md, levels - 1}) 249 | if err != nil { 250 | log.Println(err) 251 | } 252 | return template.HTML(t + buf.String()) 253 | }, 254 | "Decode": func(m *ssb.SignedMessage) interface{} { 255 | _, mb := m.DecodeMessage() 256 | return mb 257 | }, 258 | }).ParseGlob("templates/pages/*.tpl")) 259 | 260 | func init() { 261 | log.Println(ContentTemplates.DefinedTemplates()) 262 | log.Println(PageTemplates.DefinedTemplates()) 263 | } 264 | 265 | func RegisterWebui() { 266 | bi := boltinspect.New(datastore.DB()) 267 | 268 | http.HandleFunc("/bolt", bi.InspectEndpoint) 269 | 270 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) 271 | 272 | http.HandleFunc("/", Index) 273 | http.Handle("/favicon.ico", http.NotFoundHandler()) 274 | http.HandleFunc("/channel", Channel) 275 | http.HandleFunc("/post", Post) 276 | http.HandleFunc("/search", Search) 277 | http.HandleFunc("/publish/post", PublishPost) 278 | http.HandleFunc("/publish/about", PublishAbout) 279 | http.HandleFunc("/publish/follow", PublishFollow) 280 | http.HandleFunc("/publish/vote", PublishVote) 281 | http.HandleFunc("/gossip/add", GossipAdd) 282 | http.HandleFunc("/gossip/accept", GossipAccept) 283 | 284 | http.HandleFunc("/feed", FeedPage) 285 | http.HandleFunc("/thread", ThreadPage) 286 | 287 | http.HandleFunc("/profile", Profile) 288 | 289 | http.HandleFunc("/admin", Admin) 290 | http.HandleFunc("/addpub", AddPub) 291 | http.HandleFunc("/rebuild", Rebuild) 292 | 293 | http.HandleFunc("/blob", Blob) 294 | http.HandleFunc("/blobinfo", BlobInfo) 295 | 296 | http.HandleFunc("/repo", RepoInfo) 297 | http.HandleFunc("/repo/want", RepoWant) 298 | 299 | http.HandleFunc("/raw", Raw) 300 | 301 | http.HandleFunc("/upload", Upload) 302 | 303 | go http.ListenAndServe("localhost:9823", nil) 304 | } 305 | 306 | func Upload(rw http.ResponseWriter, req *http.Request) { 307 | f, _, err := req.FormFile("upload") 308 | if err != nil { 309 | log.Println(err) 310 | PageTemplates.ExecuteTemplate(rw, "upload.tpl", nil) 311 | return 312 | } 313 | buf, _ := ioutil.ReadAll(f) 314 | bs := datastore.ExtraData("blobStore").(*blobs.BlobStore) 315 | ref := bs.Add(buf) 316 | http.Redirect(rw, req, "/blobinfo?id="+url.QueryEscape(ref.String()), http.StatusFound) 317 | } 318 | 319 | func PublishPost(rw http.ResponseWriter, req *http.Request) { 320 | p := &social.Post{} 321 | p.Type = "post" 322 | p.Root = ssb.ParseRef(req.FormValue("root")) 323 | p.Branch = ssb.ParseRef(req.FormValue("branch")) 324 | p.Channel = req.FormValue("channel") 325 | p.Text = req.FormValue("text") 326 | datastore.GetFeed(datastore.PrimaryRef).PublishMessage(p) 327 | http.Redirect(rw, req, req.FormValue("returnto"), http.StatusSeeOther) 328 | } 329 | 330 | func PublishVote(rw http.ResponseWriter, req *http.Request) { 331 | p := &social.Vote{} 332 | p.Type = "vote" 333 | p.Vote.Link = ssb.ParseRef(req.FormValue("link")) 334 | p.Vote.Value = 1 335 | p.Vote.Reason = "" 336 | datastore.GetFeed(datastore.PrimaryRef).PublishMessage(p) 337 | http.Redirect(rw, req, req.FormValue("returnto"), http.StatusSeeOther) 338 | } 339 | 340 | func PublishAbout(rw http.ResponseWriter, req *http.Request) { 341 | p := &social.About{} 342 | p.Type = "about" 343 | p.About = datastore.PrimaryRef 344 | p.Name = req.FormValue("name") 345 | f, _, err := req.FormFile("upload") 346 | if err == nil { 347 | buf, _ := ioutil.ReadAll(f) 348 | bs := datastore.ExtraData("blobStore").(*blobs.BlobStore) 349 | ref := bs.Add(buf) 350 | p.Image = &social.Image{} 351 | p.Image.Link = ref 352 | } 353 | datastore.GetFeed(datastore.PrimaryRef).PublishMessage(p) 354 | http.Redirect(rw, req, "/profile", http.StatusSeeOther) 355 | } 356 | 357 | func PublishFollow(rw http.ResponseWriter, req *http.Request) { 358 | feed := ssb.ParseRef(req.FormValue("feed")) 359 | if feed.Type == ssb.RefInvalid { 360 | http.Redirect(rw, req, req.FormValue("returnto"), http.StatusSeeOther) 361 | } 362 | p := &graph.Contact{} 363 | p.Type = "contact" 364 | p.Contact = feed 365 | following := true 366 | p.Following = &following 367 | datastore.GetFeed(datastore.PrimaryRef).PublishMessage(p) 368 | http.Redirect(rw, req, req.FormValue("returnto"), http.StatusSeeOther) 369 | } 370 | 371 | func GossipAdd(rw http.ResponseWriter, req *http.Request) { 372 | host := req.FormValue("host") 373 | if host == "" { 374 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 375 | return 376 | } 377 | portStr := req.FormValue("port") 378 | if portStr == "" { 379 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 380 | return 381 | } 382 | port, err := strconv.ParseInt(portStr, 10, 64) 383 | if err != nil { 384 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 385 | return 386 | } 387 | key := ssb.ParseRef(req.FormValue("key")) 388 | if key.Type != ssb.RefFeed { 389 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 390 | return 391 | } 392 | 393 | pub := gossip.Pub{ 394 | Host: host, 395 | Port: int(port), 396 | Link: key, 397 | } 398 | gossip.AddPub(datastore, pub) 399 | 400 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 401 | } 402 | 403 | func GossipAccept(rw http.ResponseWriter, req *http.Request) { 404 | invite := req.FormValue("invite") 405 | if invite == "" { 406 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 407 | return 408 | } 409 | parts := strings.Split(invite, "~") 410 | if len(parts) != 2 { 411 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 412 | return 413 | } 414 | addrparts := strings.Split(parts[0], ":") 415 | if len(addrparts) != 3 { 416 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 417 | return 418 | } 419 | port, err := strconv.ParseInt(addrparts[1], 10, 64) 420 | if err != nil { 421 | log.Println(err) 422 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 423 | return 424 | } 425 | follow := req.FormValue("follow") 426 | 427 | pub := gossip.Pub{ 428 | Host: addrparts[0], 429 | Port: int(port), 430 | Link: ssb.ParseRef(addrparts[2]), 431 | } 432 | 433 | seed, err := base64.StdEncoding.DecodeString(parts[1]) 434 | if err != nil { 435 | log.Println(err) 436 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 437 | return 438 | } 439 | 440 | err = gossip.AcceptInvite(datastore, pub, seed) 441 | 442 | if err != nil { 443 | log.Println(err) 444 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 445 | return 446 | } 447 | 448 | if follow == "follow" { 449 | p := &graph.Contact{} 450 | p.Type = "contact" 451 | p.Contact = pub.Link 452 | following := true 453 | p.Following = &following 454 | datastore.GetFeed(datastore.PrimaryRef).PublishMessage(p) 455 | } 456 | 457 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 458 | } 459 | 460 | func Rebuild(rw http.ResponseWriter, req *http.Request) { 461 | module := req.FormValue("module") 462 | if module != "" { 463 | if module == "all" { 464 | datastore.RebuildAll() 465 | } else { 466 | datastore.Rebuild(module) 467 | } 468 | } 469 | http.Redirect(rw, req, "/admin", http.StatusSeeOther) 470 | } 471 | 472 | func calcSize(tx *bolt.Tx, b *bolt.Bucket) (size int) { 473 | b.ForEach(func(k, v []byte) error { 474 | size += len(k) 475 | if v == nil { 476 | size += calcSize(tx, b.Bucket(k)) 477 | } else { 478 | size += len(v) 479 | } 480 | return nil 481 | }) 482 | return 483 | } 484 | 485 | func Admin(rw http.ResponseWriter, req *http.Request) { 486 | size := map[string]int{} 487 | datastore.DB().View(func(tx *bolt.Tx) error { 488 | tx.ForEach(func(k []byte, b *bolt.Bucket) error { 489 | size[string(k)] = calcSize(tx, b) 490 | return nil 491 | }) 492 | return nil 493 | }) 494 | 495 | modules := []string{} 496 | for module := range ssb.AddMessageHooks { 497 | modules = append(modules, module) 498 | } 499 | err := PageTemplates.ExecuteTemplate(rw, "admin.tpl", struct { 500 | Modules []string 501 | DBSize map[string]int 502 | }{ 503 | modules, 504 | size, 505 | }) 506 | if err != nil { 507 | log.Println(err) 508 | } 509 | } 510 | 511 | func AddPub(rw http.ResponseWriter, req *http.Request) { 512 | err := PageTemplates.ExecuteTemplate(rw, "addpub.tpl", struct { 513 | }{}) 514 | //does it matter that nothing is there? 515 | if err != nil { 516 | log.Println(err) 517 | } 518 | } 519 | 520 | func Index(rw http.ResponseWriter, req *http.Request) { 521 | pageStr := req.FormValue("page") 522 | if pageStr == "" { 523 | pageStr = "1" 524 | } 525 | i, err := strconv.Atoi(pageStr) 526 | if err != nil { 527 | log.Println(err) 528 | } 529 | nextPage := strconv.Itoa(i + 1) 530 | prevPage := strconv.Itoa(i - 1) 531 | p := i * 25 532 | distStr := req.FormValue("dist") 533 | if distStr == "" { 534 | distStr = "2" 535 | } 536 | dist, _ := strconv.ParseInt(distStr, 10, 64) 537 | var messages []*ssb.SignedMessage 538 | if dist == 0 { 539 | f := datastore.GetFeed(datastore.PrimaryRef) 540 | messages = f.LatestCount(int(p), 0) 541 | } else { 542 | messages = datastore.LatestCountFiltered(int(p), int(p-25), graph.GetFollows(datastore, datastore.PrimaryRef, int(dist))) 543 | } 544 | err = PageTemplates.ExecuteTemplate(rw, "index.tpl", struct { 545 | Messages []*ssb.SignedMessage 546 | PageStr string 547 | NextPage string 548 | PrevPage string 549 | }{ 550 | messages, 551 | pageStr, 552 | nextPage, 553 | prevPage, 554 | }) 555 | if err != nil { 556 | log.Println(err) 557 | } 558 | } 559 | 560 | func FeedPage(rw http.ResponseWriter, req *http.Request) { 561 | feedRaw := req.FormValue("id") 562 | feed := ssb.ParseRef(feedRaw) 563 | 564 | pageStr := req.FormValue("page") 565 | if pageStr == "" { 566 | pageStr = "1" 567 | } 568 | i, err := strconv.Atoi(pageStr) 569 | if err != nil { 570 | log.Println(err) 571 | } 572 | nextPage := strconv.Itoa(i + 1) 573 | prevPage := strconv.Itoa(i - 1) 574 | p := (i * 25) - 25 575 | 576 | var about *social.About 577 | datastore.DB().View(func(tx *bolt.Tx) error { 578 | about = social.GetAbout(tx, feed) 579 | return nil 580 | }) 581 | var messages []*ssb.SignedMessage 582 | f := datastore.GetFeed(feed) 583 | messages = f.LatestCount(25, p) 584 | // messages = datastore.LatestCountFiltered(25, 0, graph.GetFollows(datastore, feed, int(dist))) 585 | 586 | follows := graph.GetFollows(datastore, feed, 1) 587 | 588 | err = PageTemplates.ExecuteTemplate(rw, "feed.tpl", struct { 589 | Messages []*ssb.SignedMessage 590 | Profile *social.About 591 | Ref ssb.Ref 592 | PageStr string 593 | NextPage string 594 | PrevPage string 595 | Follows map[ssb.Ref]int 596 | }{ 597 | messages, 598 | about, 599 | feed, 600 | pageStr, 601 | nextPage, 602 | prevPage, 603 | follows, 604 | }) 605 | if err != nil { 606 | log.Println(err) 607 | } 608 | } 609 | 610 | func ThreadPage(rw http.ResponseWriter, req *http.Request) { 611 | threadRaw := req.FormValue("id") 612 | threadRef := ssb.ParseRef(threadRaw) 613 | 614 | root := datastore.Get(nil, threadRef) 615 | 616 | channel := "" 617 | 618 | _, p := root.DecodeMessage() 619 | 620 | if post, ok := p.(*social.Post); ok { 621 | channel = post.Channel 622 | } 623 | var messages []*ssb.SignedMessage 624 | datastore.DB().View(func(tx *bolt.Tx) error { 625 | messages = social.GetThread(tx, threadRef) 626 | return nil 627 | }) 628 | 629 | feedRaw := datastore.PrimaryRef.String() 630 | feed := ssb.ParseRef(feedRaw) 631 | 632 | var about *social.About 633 | datastore.DB().View(func(tx *bolt.Tx) error { 634 | about = social.GetAbout(tx, feed) 635 | return nil 636 | }) 637 | reply := root.Key() 638 | if len(messages) > 0 { 639 | reply = messages[len(messages)-1].Key() 640 | } 641 | 642 | err := PageTemplates.ExecuteTemplate(rw, "thread.tpl", struct { 643 | Root *ssb.SignedMessage 644 | Channel string 645 | Reply ssb.Ref 646 | Messages []*ssb.SignedMessage 647 | Profile *social.About 648 | }{ 649 | root, 650 | channel, 651 | reply, 652 | messages, 653 | about, 654 | }) 655 | if err != nil { 656 | log.Println(err) 657 | } 658 | } 659 | 660 | func Post(rw http.ResponseWriter, req *http.Request) { 661 | post := req.FormValue("id") 662 | if post == "" { 663 | http.NotFound(rw, req) 664 | return 665 | } 666 | message := datastore.Get(nil, ssb.ParseRef(post)) 667 | if message == nil { 668 | http.NotFound(rw, req) 669 | return 670 | } 671 | _, content := message.DecodeMessage() 672 | raw := message.Encode() 673 | p, ok := content.(*social.Post) 674 | if !ok { 675 | PageTemplates.ExecuteTemplate(rw, "message.tpl", struct { 676 | Message *ssb.SignedMessage 677 | }{ 678 | message, 679 | }) 680 | return 681 | } 682 | var votes []*ssb.SignedMessage 683 | datastore.DB().View(func(tx *bolt.Tx) error { 684 | votes = social.GetVotes(tx, message.Key()) 685 | return nil 686 | }) 687 | err := PageTemplates.ExecuteTemplate(rw, "post.tpl", struct { 688 | Message *ssb.SignedMessage 689 | Content *social.Post 690 | Votes []*ssb.SignedMessage 691 | Raw []byte 692 | }{ 693 | message, 694 | p, 695 | votes, 696 | raw, 697 | }) 698 | if err != nil { 699 | log.Println(err) 700 | } 701 | } 702 | 703 | func Profile(rw http.ResponseWriter, req *http.Request) { 704 | feedRaw := datastore.PrimaryRef.String() 705 | distStr := req.FormValue("dist") 706 | if distStr == "" { 707 | distStr = "0" 708 | } 709 | feed := ssb.ParseRef(feedRaw) 710 | dist, _ := strconv.ParseInt(distStr, 10, 64) 711 | 712 | var about *social.About 713 | datastore.DB().View(func(tx *bolt.Tx) error { 714 | about = social.GetAbout(tx, feed) 715 | return nil 716 | }) 717 | var messages []*ssb.SignedMessage 718 | if dist == 0 { 719 | f := datastore.GetFeed(feed) 720 | messages = f.LatestCount(25, 0) 721 | } else { 722 | messages = datastore.LatestCountFiltered(25, 0, graph.GetFollows(datastore, feed, int(dist))) 723 | } 724 | err := PageTemplates.ExecuteTemplate(rw, "profile.tpl", struct { 725 | Messages []*ssb.SignedMessage 726 | Profile *social.About 727 | Ref ssb.Ref 728 | }{ 729 | messages, 730 | about, 731 | feed, 732 | }) 733 | if err != nil { 734 | log.Println(err) 735 | } 736 | } 737 | 738 | func Channel(rw http.ResponseWriter, req *http.Request) { 739 | channel := req.FormValue("channel") 740 | if channel == "" { 741 | Index(rw, req) 742 | return 743 | } 744 | pageStr := req.FormValue("page") 745 | if pageStr == "" { 746 | pageStr = "1" 747 | } 748 | i, err := strconv.Atoi(pageStr) 749 | if err != nil { 750 | log.Println(err) 751 | } 752 | nextPage := strconv.Itoa(i + 1) 753 | prevPage := strconv.Itoa(i - 1) 754 | p := i * 25 755 | messages := channels.GetChannelLatest(datastore, channel, int(p), int(p-24)) 756 | //set back to 100 posts per page^^ 757 | //this can be changed to support page loads with arbitrary slices from channel posts bucket 758 | //that zero is the start value 759 | err = PageTemplates.ExecuteTemplate(rw, "channel.tpl", struct { 760 | Messages []*ssb.SignedMessage 761 | Channel string 762 | PageStr string 763 | NextPage string 764 | PrevPage string 765 | }{ 766 | messages, 767 | channel, 768 | pageStr, 769 | nextPage, 770 | prevPage, 771 | }) 772 | if err != nil { 773 | log.Println(err) 774 | } 775 | } 776 | 777 | func Search(rw http.ResponseWriter, req *http.Request) { 778 | query := req.FormValue("q") 779 | if query == "" { 780 | Index(rw, req) 781 | return 782 | } 783 | if query[0] == '#' { 784 | http.Redirect(rw, req, "/channel?channel="+query[1:], http.StatusFound) 785 | return 786 | } 787 | r := ssb.ParseRef(query) 788 | switch r.Type { 789 | case ssb.RefBlob: 790 | http.Redirect(rw, req, "/blob?id="+url.QueryEscape(r.String()), http.StatusFound) 791 | return 792 | case ssb.RefMessage: 793 | msg := datastore.Get(nil, r) 794 | if msg != nil { 795 | _, repo := msg.DecodeMessage() 796 | if _, ok := repo.(*git.RepoRoot); ok { 797 | http.Redirect(rw, req, "/repo?id="+url.QueryEscape(r.String()), http.StatusFound) 798 | return 799 | } 800 | } 801 | http.Redirect(rw, req, "/post?id="+url.QueryEscape(r.String()), http.StatusFound) 802 | return 803 | case ssb.RefFeed: 804 | http.Redirect(rw, req, "/feed?id="+url.QueryEscape(r.String()), http.StatusFound) 805 | return 806 | } 807 | 808 | messages := search.Search(datastore, query, 50) 809 | err := PageTemplates.ExecuteTemplate(rw, "search.tpl", struct { 810 | Messages []*ssb.SignedMessage 811 | }{ 812 | messages, 813 | }) 814 | if err != nil { 815 | log.Println(err) 816 | } 817 | } 818 | 819 | func Blob(rw http.ResponseWriter, req *http.Request) { 820 | id := req.FormValue("id") 821 | if id == "" { 822 | http.NotFound(rw, req) 823 | return 824 | } 825 | r := ssb.ParseRef(id) 826 | bs := datastore.ExtraData("blobStore").(*blobs.BlobStore) 827 | if !bs.Has(r) { 828 | bs.Want(r) 829 | bs.WaitFor(r) 830 | } 831 | rc := bs.Get(r) 832 | defer rc.Close() 833 | rw.Header().Set("Cache-Control", "max-age=31556926") 834 | io.Copy(rw, rc) 835 | } 836 | 837 | func BlobInfo(rw http.ResponseWriter, req *http.Request) { 838 | id := req.FormValue("id") 839 | if id == "" { 840 | http.NotFound(rw, req) 841 | return 842 | } 843 | r := ssb.ParseRef(id) 844 | PageTemplates.ExecuteTemplate(rw, "blob.tpl", struct { 845 | ID ssb.Ref 846 | }{ 847 | ID: r, 848 | }) 849 | } 850 | 851 | func Raw(rw http.ResponseWriter, req *http.Request) { 852 | id := req.FormValue("id") 853 | if id == "" { 854 | http.NotFound(rw, req) 855 | return 856 | } 857 | r := ssb.ParseRef(id) 858 | m := datastore.Get(nil, r) 859 | if m != nil { 860 | buf := m.Encode() 861 | rw.Write(buf) 862 | } 863 | } 864 | 865 | func RepoInfo(rw http.ResponseWriter, req *http.Request) { 866 | id := req.FormValue("id") 867 | if id == "" { 868 | http.NotFound(rw, req) 869 | return 870 | } 871 | r := ssb.ParseRef(id) 872 | repo := git.Get(datastore, r) 873 | if repo == nil { 874 | http.NotFound(rw, req) 875 | return 876 | } 877 | bs := repo.ListBlobs() 878 | updates := repo.ListUpdates() 879 | issues := repo.Issues() 880 | err := PageTemplates.ExecuteTemplate(rw, "repo.tpl", struct { 881 | Blobs []ssb.Ref 882 | Updates []ssb.Ref 883 | Issues []*ssb.SignedMessage 884 | Ref ssb.Ref 885 | }{ 886 | bs, 887 | updates, 888 | issues, 889 | r, 890 | }) 891 | if err != nil { 892 | log.Println(err) 893 | } 894 | } 895 | 896 | func RepoWant(rw http.ResponseWriter, req *http.Request) { 897 | id := req.FormValue("id") 898 | if id == "" { 899 | http.NotFound(rw, req) 900 | return 901 | } 902 | r := ssb.ParseRef(id) 903 | repo := git.Get(datastore, r) 904 | if repo == nil { 905 | http.NotFound(rw, req) 906 | return 907 | } 908 | repo.WantAll() 909 | http.Redirect(rw, req, "/repo?id="+url.QueryEscape(r.String()), http.StatusFound) 910 | } 911 | -------------------------------------------------------------------------------- /cmd/sbotcli/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | -------------------------------------------------------------------------------- /cmd/sbotcli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/urfave/cli" 9 | 10 | r "net/rpc" 11 | 12 | "github.com/andyleap/go-ssb/cmd/sbot/rpc" 13 | ) 14 | 15 | func main() { 16 | app := cli.NewApp() 17 | 18 | client, _ := r.Dial("tcp", "127.0.0.1:9822") 19 | 20 | app.Commands = []cli.Command{ 21 | { 22 | Name: "gossip.add", 23 | Aliases: []string{"g.a"}, 24 | Usage: "add a peer to the gossip list", 25 | Action: func(c *cli.Context) error { 26 | if c.NArg() != 3 { 27 | return fmt.Errorf("Expected 3 arguments") 28 | } 29 | port, err := strconv.Atoi(c.Args().Get(1)) 30 | if err != nil { 31 | return err 32 | } 33 | req := rpc.AddPubReq{ 34 | Host: c.Args().Get(0), 35 | Port: port, 36 | PubKey: c.Args().Get(2), 37 | } 38 | res := rpc.AddPubRes{} 39 | return client.Call("Gossip.AddPub", req, &res) 40 | }, 41 | }, 42 | { 43 | Name: "feed.post", 44 | Aliases: []string{"f.p"}, 45 | Usage: "publish a new post to a feed", 46 | Flags: []cli.Flag{ 47 | cli.StringFlag{ 48 | Name: "feed", 49 | Usage: "Feed to publish to", 50 | }, 51 | cli.StringFlag{ 52 | Name: "root", 53 | Usage: "Root post to respond to", 54 | }, 55 | cli.StringFlag{ 56 | Name: "branch", 57 | Usage: "Branch post to respond to", 58 | }, 59 | cli.StringFlag{ 60 | Name: "channel", 61 | Usage: "Channel to publish to", 62 | }, 63 | }, 64 | Action: func(c *cli.Context) error { 65 | if c.NArg() != 1 { 66 | return fmt.Errorf("Expected 1 argument") 67 | } 68 | req := rpc.PostReq{ 69 | Feed: c.String("feed"), 70 | Text: c.Args().Get(0), 71 | Root: c.String("root"), 72 | Branch: c.String("branch"), 73 | Channel: c.String("channel"), 74 | } 75 | res := rpc.PostRes{} 76 | return client.Call("Feed.Post", req, &res) 77 | }, 78 | }, 79 | { 80 | Name: "feed.follow", 81 | Aliases: []string{"f.f"}, 82 | Usage: "follow a feed", 83 | Flags: []cli.Flag{ 84 | cli.StringFlag{ 85 | Name: "feed", 86 | Usage: "Feed to publish to", 87 | }, 88 | }, 89 | Action: func(c *cli.Context) error { 90 | if c.NArg() != 1 { 91 | return fmt.Errorf("Expected 1 argument") 92 | } 93 | req := rpc.FollowReq{ 94 | Feed: c.String("feed"), 95 | Contact: c.Args().Get(0), 96 | } 97 | res := rpc.FollowRes{} 98 | return client.Call("Feed.Follow", req, &res) 99 | }, 100 | }, 101 | { 102 | Name: "feed.name", 103 | Aliases: []string{"f.n"}, 104 | Usage: "set a name for the feed", 105 | Flags: []cli.Flag{ 106 | cli.StringFlag{ 107 | Name: "feed", 108 | Usage: "Feed to publish to", 109 | }, 110 | }, 111 | Action: func(c *cli.Context) error { 112 | if c.NArg() != 1 { 113 | return fmt.Errorf("Expected 1 argument") 114 | } 115 | req := rpc.AboutReq{ 116 | Feed: c.String("feed"), 117 | Name: c.Args().Get(0), 118 | } 119 | res := rpc.AboutRes{} 120 | return client.Call("Feed.About", req, &res) 121 | }, 122 | }, 123 | } 124 | app.Run(os.Args) 125 | } 126 | -------------------------------------------------------------------------------- /compression.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | var Compression1 = []byte(`{ 4 | "previous": ", 5 | "author": ", 6 | "sequence": , 7 | "timestamp": , 8 | "hash": "sha256", 9 | "content": { 10 | "type": "about"contact"post"vote", 11 | "text": ", 12 | "channel": ", 13 | "root": ", 14 | "branch": ", 15 | "recps": ", 16 | "mentions": [], 17 | "about": ", 18 | "name": ", 19 | "image": ", 20 | "contact": ", 21 | "following": true, 22 | "blocking": false, 23 | "vote": { 24 | "link": ", 25 | "value": 1, 26 | "reason": "Dug" 27 | } 28 | "repo": ", 29 | } 30 | "signature: ", 31 | }`) 32 | 33 | var Compression2 = []byte(`{ 34 | "previous": ", 35 | "author": ", 36 | "sequence": , 37 | "timestamp": , 38 | "hash": "sha256", 39 | "content": { 40 | "type": "about"contact"post"vote", 41 | "text": ", 42 | "channel": ", 43 | "root": ", 44 | "branch": ", 45 | "recps": ", 46 | "mentions": [], 47 | "about": ", 48 | "name": ", 49 | "image": ", 50 | "contact": ", 51 | "following": true, 52 | "blocking": false, 53 | "vote": { 54 | "link": ", 55 | "value": 1, 56 | "reason": "Dug" 57 | } 58 | "repo": " 59 | } 60 | "signature: " 61 | }`) 62 | -------------------------------------------------------------------------------- /dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/boltdb/bolt" 7 | 8 | "github.com/andyleap/go-ssb" 9 | ) 10 | 11 | type Record struct { 12 | Name string `json:"name"` 13 | Class string `json:"class"` 14 | Type string `json:"type"` 15 | Data json.RawMessage `json:"data"` 16 | } 17 | 18 | type DNS struct { 19 | ssb.MessageBody 20 | Record Record `json:"record"` 21 | Branch []ssb.Ref `json:"branch"` 22 | } 23 | 24 | func init() { 25 | ssb.MessageTypes["ssb-dns"] = func() interface{} { return &DNS{} } 26 | ssb.RebuildClearHooks["dns"] = func(tx *bolt.Tx) error { 27 | tx.DeleteBucket([]byte("dns")) 28 | } 29 | ssb.AddMessageHooks["dns"] = func(m *ssb.SignedMessage, tx *bolt.Tx) error { 30 | _, mb := m.DecodeMessage() 31 | if mbr, ok := mb.(*DNS); ok { 32 | PubBucket, err := tx.CreateBucketIfNotExists([]byte("dns")) 33 | if err != nil { 34 | return err 35 | } 36 | buf, _ := json.Marshal(mbr) 37 | err = PubBucket.Put([]byte(m.Key()), buf) 38 | if err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // go-ssb project doc.go 2 | 3 | /* 4 | go-ssb document 5 | */ 6 | package ssb 7 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "unicode/utf16" 5 | "unicode/utf8" 6 | 7 | "golang.org/x/text/encoding" 8 | "golang.org/x/text/transform" 9 | ) 10 | 11 | func RemoveUnsupported(e *encoding.Encoder) *encoding.Encoder { 12 | return &encoding.Encoder{Transformer: &errorHandler{e, errorToRemove}} 13 | } 14 | 15 | type errorHandler struct { 16 | *encoding.Encoder 17 | handler func(dst []byte, r rune, err repertoireError) (n int, ok bool) 18 | } 19 | 20 | // TODO: consider making this error public in some form. 21 | type repertoireError interface { 22 | Replacement() byte 23 | } 24 | 25 | func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { 26 | nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF) 27 | for err != nil { 28 | rerr, ok := err.(repertoireError) 29 | if !ok { 30 | return nDst, nSrc, err 31 | } 32 | r, sz := utf8.DecodeRune(src[nSrc:]) 33 | n, ok := h.handler(dst[nDst:], r, rerr) 34 | if !ok { 35 | return nDst, nSrc, transform.ErrShortDst 36 | } 37 | err = nil 38 | nDst += n 39 | if nSrc += sz; nSrc < len(src) { 40 | var dn, sn int 41 | dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF) 42 | nDst += dn 43 | nSrc += sn 44 | } 45 | } 46 | return nDst, nSrc, err 47 | } 48 | 49 | func errorToRemove(dst []byte, r rune, err repertoireError) (n int, ok bool) { 50 | if len(dst) < 1 { 51 | return 0, false 52 | } 53 | dst[0] = byte(r) 54 | return 1, true 55 | } 56 | 57 | func ToJSBinary(src []byte) []byte { 58 | runes := []rune(string(src)) 59 | utf := utf16.Encode(runes) 60 | out := make([]byte, len(utf)) 61 | for i, r := range utf { 62 | out[i] = byte(r) 63 | } 64 | return out 65 | } 66 | -------------------------------------------------------------------------------- /feed_test.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/user" 9 | "path/filepath" 10 | "testing" 11 | "time" 12 | 13 | "cryptoscope.co/go/secretstream" 14 | "cryptoscope.co/go/secretstream/secrethandshake" 15 | "github.com/cryptix/go-muxrpc" 16 | "github.com/go-kit/kit/log" 17 | ) 18 | 19 | func TestSlurp(t *testing.T) { 20 | sbotAppKey, _ := base64.StdEncoding.DecodeString("1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=") 21 | u, _ := user.Current() 22 | localKey, err := secrethandshake.LoadSSBKeyPair(filepath.Join(u.HomeDir, ".ssb", "secret")) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | var conn net.Conn 27 | c, err := secretstream.NewClient(*localKey, sbotAppKey) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | var remotPubKey = localKey.Public 32 | d, err := c.NewDialer(remotPubKey) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | fmt.Println("Connecting to local sbot") 37 | conn, err = d("tcp", "127.0.0.1:8008") 38 | fmt.Println("Connected to local sbot") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) 43 | 44 | client := muxrpc.NewClient(logger, conn) 45 | 46 | output := make(chan *SignedMessage) 47 | 48 | fmt.Println("@" + base64.StdEncoding.EncodeToString(localKey.Public[:]) + ".ed25519") 49 | os.Remove("feeds.db") 50 | fmt.Println("test1") 51 | feedstore, _ := OpenDataStore("feeds.db", filepath.Join(u.HomeDir, ".ssb", "secret")) 52 | fmt.Println("test2") 53 | f := feedstore.GetFeed(Ref("@" + base64.StdEncoding.EncodeToString(localKey.Public[:]) + ".ed25519")) 54 | fmt.Println("test3") 55 | seq := 0 56 | 57 | latest := f.Latest() 58 | 59 | if latest != nil { 60 | seq = latest.Sequence + 1 61 | } 62 | 63 | go func() { 64 | client.Source("createHistoryStream", output, map[string]interface{}{"id": "@" + base64.StdEncoding.EncodeToString(localKey.Public[:]) + ".ed25519", "keys": false, "seq": seq}, 0, false) 65 | close(output) 66 | }() 67 | 68 | for m := range output { 69 | fmt.Println(m) 70 | err = f.AddMessage(m) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | time.Sleep(120 * time.Second) 76 | } 77 | -------------------------------------------------------------------------------- /feeds.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "sync" 9 | "time" 10 | 11 | "cryptoscope.co/go/secretstream/secrethandshake" 12 | "github.com/boltdb/bolt" 13 | "golang.org/x/crypto/ed25519" 14 | ) 15 | 16 | func itob(v int) []byte { 17 | b := make([]byte, 8) 18 | binary.BigEndian.PutUint64(b, uint64(v)) 19 | return b 20 | } 21 | 22 | func btoi(b []byte) int { 23 | return int(binary.BigEndian.Uint64(b)) 24 | } 25 | 26 | var initMethods = []func(ds *DataStore){} 27 | 28 | func RegisterInit(f func(ds *DataStore)) { 29 | initMethods = append(initMethods, f) 30 | } 31 | 32 | type DataStore struct { 33 | db *bolt.DB 34 | 35 | feedlock sync.Mutex 36 | feeds map[Ref]*Feed 37 | 38 | Topic *MessageTopic 39 | 40 | PrimaryKey *secrethandshake.EdKeyPair 41 | PrimaryRef Ref 42 | 43 | extraData map[string]interface{} 44 | extraDataLock sync.Mutex 45 | 46 | Keys map[Ref]Signer 47 | } 48 | 49 | func init() { 50 | RegisterInit(func(ds *DataStore) { 51 | ds.RegisterMethod("feed.Publish", func(feed Ref, message interface{}) error { 52 | return ds.GetFeed(feed).PublishMessage(message) 53 | }) 54 | ds.RegisterMethod("feed.Latest", func(feed Ref) *SignedMessage { 55 | return ds.GetFeed(feed).Latest() 56 | }) 57 | }) 58 | /*AddMessageHooks["recompress"] = func(m *SignedMessage, tx *bolt.Tx) error { 59 | FeedsBucket, err := tx.CreateBucketIfNotExists([]byte("feeds")) 60 | if err != nil { 61 | return err 62 | } 63 | FeedBucket, err := FeedsBucket.CreateBucketIfNotExists(m.Author.DBKey()) 64 | if err != nil { 65 | return err 66 | } 67 | FeedLogBucket, err := FeedBucket.CreateBucketIfNotExists([]byte("log")) 68 | if err != nil { 69 | return err 70 | } 71 | FeedLogBucket.FillPercent = 1 72 | buf := m.Compress() 73 | err = FeedLogBucket.Put(itob(m.Sequence), buf) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | }*/ 79 | } 80 | 81 | func (ds *DataStore) RegisterMethod(name string, method interface{}) { 82 | RPCMethods, ok := ds.ExtraData("RPCMethods").(map[string]interface{}) 83 | if !ok { 84 | RPCMethods = map[string]interface{}{} 85 | } 86 | RPCMethods[name] = method 87 | ds.SetExtraData("RPCMethods", RPCMethods) 88 | } 89 | 90 | func (ds *DataStore) ExtraData(name string) interface{} { 91 | ds.extraDataLock.Lock() 92 | defer ds.extraDataLock.Unlock() 93 | return ds.extraData[name] 94 | } 95 | 96 | func (ds *DataStore) SetExtraData(name string, data interface{}) { 97 | ds.extraDataLock.Lock() 98 | defer ds.extraDataLock.Unlock() 99 | ds.extraData[name] = data 100 | } 101 | 102 | func (ds *DataStore) DB() *bolt.DB { 103 | return ds.db 104 | } 105 | 106 | func (ds *DataStore) Close() { 107 | err := ds.db.Close() 108 | if err != nil { 109 | log.Println("error closing db:", err) 110 | } 111 | } 112 | 113 | type Feed struct { 114 | store *DataStore 115 | ID Ref 116 | 117 | Topic *MessageTopic 118 | LatestSeq int 119 | SeqLock sync.Mutex 120 | 121 | addChan chan *SignedMessage 122 | 123 | waiting map[int]*SignedMessage 124 | waitingLock sync.Mutex 125 | waitingSignal *sync.Cond 126 | } 127 | 128 | type Pointer struct { 129 | Sequence int 130 | LogKey int 131 | Author []byte 132 | } 133 | 134 | func (p Pointer) Marshal() []byte { 135 | buf := make([]byte, len(p.Author)+16) 136 | binary.BigEndian.PutUint64(buf[0:], uint64(p.Sequence)) 137 | binary.BigEndian.PutUint64(buf[8:], uint64(p.LogKey)) 138 | copy(buf[16:], p.Author) 139 | return buf 140 | } 141 | 142 | func (p *Pointer) Unmarshal(buf []byte) { 143 | p.Author = make([]byte, len(buf)-16) 144 | p.Sequence = int(binary.BigEndian.Uint64(buf[0:])) 145 | p.LogKey = int(binary.BigEndian.Uint64(buf[8:])) 146 | copy(p.Author, buf[16:]) 147 | } 148 | 149 | func OpenDataStore(path string, primaryKey *secrethandshake.EdKeyPair) (*DataStore, error) { 150 | db, err := bolt.Open(path, 0600, nil) 151 | if err != nil { 152 | return nil, err 153 | } 154 | ds := &DataStore{ 155 | db: db, 156 | feeds: map[Ref]*Feed{}, 157 | Topic: NewMessageTopic(), 158 | extraData: map[string]interface{}{}, 159 | Keys: map[Ref]Signer{}, 160 | } 161 | ds.PrimaryKey = primaryKey 162 | ds.PrimaryRef, _ = NewRef(RefFeed, ds.PrimaryKey.Public[:], RefAlgoEd25519) 163 | ds.Keys[ds.PrimaryRef] = &SignerEd25519{ed25519.PrivateKey(ds.PrimaryKey.Secret[:])} 164 | 165 | for _, im := range initMethods { 166 | im(ds) 167 | } 168 | 169 | return ds, nil 170 | } 171 | 172 | func (ds *DataStore) GetFeed(feedID Ref) *Feed { 173 | ds.feedlock.Lock() 174 | defer ds.feedlock.Unlock() 175 | if feed, ok := ds.feeds[feedID]; ok { 176 | return feed 177 | } 178 | if feedID.Type != RefFeed { 179 | return nil 180 | } 181 | feed := &Feed{ 182 | store: ds, 183 | ID: feedID, 184 | Topic: NewMessageTopic(), 185 | addChan: make(chan *SignedMessage, 10), 186 | waiting: map[int]*SignedMessage{}, 187 | waitingSignal: sync.NewCond(&sync.Mutex{}), 188 | } 189 | go func() { 190 | for m := range feed.addChan { 191 | feed.waitingLock.Lock() 192 | feed.SeqLock.Lock() 193 | if m.Sequence > feed.LatestSeq { 194 | feed.waiting[m.Sequence] = m 195 | } 196 | feed.SeqLock.Unlock() 197 | feed.waitingLock.Unlock() 198 | feed.waitingSignal.Broadcast() 199 | } 200 | }() 201 | go feed.processMessageQueue() 202 | m := feed.Latest() 203 | if m != nil { 204 | feed.LatestSeq = m.Sequence 205 | } 206 | 207 | feed.Topic.Register(ds.Topic.Send, true) 208 | ds.feeds[feedID] = feed 209 | return feed 210 | } 211 | 212 | func (ds *DataStore) Get(tx *bolt.Tx, post Ref) (m *SignedMessage) { 213 | var err error 214 | if tx == nil { 215 | tx, err = ds.db.Begin(false) 216 | if err != nil { 217 | return 218 | } 219 | defer tx.Rollback() 220 | } 221 | PointerBucket := tx.Bucket([]byte("pointer")) 222 | if PointerBucket == nil { 223 | return 224 | } 225 | pdata := PointerBucket.Get(post.DBKey()) 226 | if pdata == nil { 227 | return 228 | } 229 | p := Pointer{} 230 | p.Unmarshal(pdata) 231 | FeedsBucket := tx.Bucket([]byte("feeds")) 232 | if FeedsBucket == nil { 233 | return 234 | } 235 | FeedBucket := FeedsBucket.Bucket(p.Author) 236 | if FeedBucket == nil { 237 | return 238 | } 239 | LogBucket := FeedBucket.Bucket([]byte("log")) 240 | if LogBucket == nil { 241 | return 242 | } 243 | msgdata := LogBucket.Get(itob(p.Sequence)) 244 | if msgdata == nil { 245 | return 246 | } 247 | m = DecompressMessage(msgdata) 248 | return 249 | } 250 | 251 | func GetMsg(tx *bolt.Tx, post Ref) (m *SignedMessage) { 252 | PointerBucket := tx.Bucket([]byte("pointer")) 253 | if PointerBucket == nil { 254 | return 255 | } 256 | pdata := PointerBucket.Get(post.DBKey()) 257 | if pdata == nil { 258 | return 259 | } 260 | p := Pointer{} 261 | p.Unmarshal(pdata) 262 | FeedsBucket := tx.Bucket([]byte("feeds")) 263 | if FeedsBucket == nil { 264 | return 265 | } 266 | FeedBucket := FeedsBucket.Bucket(p.Author) 267 | if FeedBucket == nil { 268 | return 269 | } 270 | LogBucket := FeedBucket.Bucket([]byte("log")) 271 | if LogBucket == nil { 272 | return 273 | } 274 | msgdata := LogBucket.Get(itob(p.Sequence)) 275 | if msgdata == nil { 276 | return 277 | } 278 | m = DecompressMessage(msgdata) 279 | return 280 | } 281 | 282 | var AddMessageHooks = map[string]func(m *SignedMessage, tx *bolt.Tx) error{} 283 | 284 | func (f *Feed) AddMessage(m *SignedMessage) error { 285 | if m != nil { 286 | f.addChan <- m 287 | } 288 | return nil 289 | } 290 | 291 | func (f *Feed) processMessageQueue() { 292 | for { 293 | f.waitingSignal.L.Lock() 294 | f.waitingSignal.Wait() 295 | f.waitingSignal.L.Unlock() 296 | newMsgs := []*SignedMessage{} 297 | err := f.store.db.Update(func(tx *bolt.Tx) error { 298 | f.waitingLock.Lock() 299 | f.SeqLock.Lock() 300 | defer func() { 301 | f.SeqLock.Unlock() 302 | f.waitingLock.Unlock() 303 | }() 304 | for { 305 | m, ok := f.waiting[f.LatestSeq+1] 306 | delete(f.waiting, f.LatestSeq+1) 307 | if !ok { 308 | break 309 | } 310 | 311 | if m.Author != f.ID { 312 | continue 313 | } 314 | if f.store.Get(nil, m.Key()) != nil { 315 | continue 316 | } 317 | err := m.Verify(tx, f) 318 | if err != nil { 319 | //fmt.Println(err) 320 | //fmt.Println((string(m.Message.Content))) 321 | fmt.Print("-") 322 | return err 323 | } 324 | err = f.addMessage(tx, m) 325 | if err != nil { 326 | fmt.Println("Bolt: ", err) 327 | return err 328 | } 329 | 330 | f.LatestSeq = m.Sequence 331 | fmt.Print("*") 332 | newMsgs = append(newMsgs, m) 333 | } 334 | return nil 335 | }) 336 | if err != nil { 337 | continue 338 | } 339 | for _, m := range newMsgs { 340 | f.Topic.Send <- m 341 | } 342 | } 343 | } 344 | 345 | func (f *Feed) addMessage(tx *bolt.Tx, m *SignedMessage) error { 346 | FeedsBucket, err := tx.CreateBucketIfNotExists([]byte("feeds")) 347 | if err != nil { 348 | return err 349 | } 350 | FeedBucket, err := FeedsBucket.CreateBucketIfNotExists(f.ID.DBKey()) 351 | if err != nil { 352 | return err 353 | } 354 | FeedLogBucket, err := FeedBucket.CreateBucketIfNotExists([]byte("log")) 355 | if err != nil { 356 | return err 357 | } 358 | FeedLogBucket.FillPercent = 1 359 | buf := m.Compress() 360 | err = FeedLogBucket.Put(itob(m.Sequence), buf) 361 | if err != nil { 362 | return err 363 | } 364 | LogBucket, err := tx.CreateBucketIfNotExists([]byte("log")) 365 | if err != nil { 366 | return err 367 | } 368 | LogBucket.FillPercent = 1 369 | seq, err := LogBucket.NextSequence() 370 | if err != nil { 371 | return err 372 | } 373 | err = LogBucket.Put(itob(int(seq)), m.Key().DBKey()) 374 | if err != nil { 375 | return err 376 | } 377 | PointerBucket, err := tx.CreateBucketIfNotExists([]byte("pointer")) 378 | if err != nil { 379 | return err 380 | } 381 | pointer := Pointer{Sequence: m.Sequence, LogKey: int(seq), Author: m.Author.DBKey()} 382 | buf = pointer.Marshal() 383 | err = PointerBucket.Put(m.Key().DBKey(), buf) 384 | if err != nil { 385 | return err 386 | } 387 | for module, hook := range AddMessageHooks { 388 | err = hook(m, tx) 389 | if err != nil { 390 | return fmt.Errorf("Bolt %s hook: %s", module, err) 391 | } 392 | } 393 | return nil 394 | } 395 | 396 | var RebuildClearHooks = map[string]func(tx *bolt.Tx) error{} 397 | 398 | func (ds *DataStore) RebuildAll() { 399 | log.Println("Starting rebuild of all indexes") 400 | count := 0 401 | ds.db.Update(func(tx *bolt.Tx) error { 402 | for module, hook := range RebuildClearHooks { 403 | err := hook(tx) 404 | if err != nil { 405 | return fmt.Errorf("Bolt %s hook: %s", module, err) 406 | } 407 | } 408 | 409 | LogBucket, err := tx.CreateBucketIfNotExists([]byte("log")) 410 | if err != nil { 411 | return err 412 | } 413 | cursor := LogBucket.Cursor() 414 | _, v := cursor.First() 415 | for v != nil { 416 | for module, hook := range AddMessageHooks { 417 | err = hook(ds.Get(tx, DBRef(v)), tx) 418 | if err != nil { 419 | return fmt.Errorf("Bolt %s hook: %s", module, err) 420 | } 421 | } 422 | count++ 423 | _, v = cursor.Next() 424 | } 425 | return nil 426 | }) 427 | log.Println("Finished rebuild of all modules") 428 | log.Println("Reindexed", count, "posts") 429 | } 430 | 431 | func (ds *DataStore) Rebuild(module string) { 432 | log.Println("Starting rebuild of", module) 433 | count := 0 434 | ds.db.Update(func(tx *bolt.Tx) error { 435 | if clear, ok := RebuildClearHooks[module]; ok { 436 | err := clear(tx) 437 | if err != nil { 438 | return err 439 | } 440 | } 441 | 442 | LogBucket, err := tx.CreateBucketIfNotExists([]byte("log")) 443 | if err != nil { 444 | return err 445 | } 446 | cursor := LogBucket.Cursor() 447 | _, v := cursor.First() 448 | for v != nil { 449 | AddMessageHooks[module](ds.Get(tx, DBRef(v)), tx) 450 | count++ 451 | _, v = cursor.Next() 452 | } 453 | return nil 454 | }) 455 | log.Println("Finished rebuild of", module) 456 | log.Println("Reindexed", count, "messages") 457 | } 458 | 459 | func (ds *DataStore) LatestCountFiltered(num int, start int, filter map[Ref]int) (msgs []*SignedMessage) { 460 | ds.db.View(func(tx *bolt.Tx) error { 461 | LogBucket := tx.Bucket([]byte("log")) 462 | if LogBucket == nil { 463 | return nil 464 | } 465 | cur := LogBucket.Cursor() 466 | _, val := cur.Last() 467 | for len(msgs) < num { 468 | for i := 0; i < start; i++ { 469 | _, val = cur.Prev() 470 | if val == nil { 471 | break 472 | } 473 | } 474 | if val == nil { 475 | break 476 | } 477 | msg := ds.Get(tx, DBRef(val)) 478 | 479 | if _, ok := filter[msg.Author]; ok && msg.Type() != "" { 480 | msgs = append(msgs, msg) 481 | } 482 | _, val = cur.Prev() 483 | } 484 | return nil 485 | }) 486 | return 487 | } 488 | 489 | func (f *Feed) PublishMessage(body interface{}) error { 490 | content, _ := Encode(body) 491 | 492 | m := &Message{ 493 | Author: f.ID, 494 | Timestamp: float64(time.Now().UnixNano() / int64(time.Millisecond)), 495 | Hash: "sha256", 496 | Content: content, 497 | Sequence: 1, 498 | } 499 | 500 | if l := f.Latest(); l != nil { 501 | key := l.Key() 502 | m.Previous = &key 503 | m.Sequence = l.Sequence + 1 504 | for m.Timestamp <= l.Timestamp { 505 | m.Timestamp += 0.01 506 | } 507 | } 508 | 509 | signer := f.store.Keys[f.ID] 510 | if signer == nil { 511 | return fmt.Errorf("Cannot sign message without signing key for feed") 512 | } 513 | sm := m.Sign(signer) 514 | c := f.Topic.Register(nil, true) 515 | err := f.AddMessage(sm) 516 | if err != nil { 517 | return err 518 | } 519 | 520 | for newm := range c { 521 | if newm.Key() == sm.Key() { 522 | f.Topic.Unregister(c) 523 | return nil 524 | } 525 | } 526 | 527 | return nil 528 | } 529 | 530 | func (f *Feed) Latest() (m *SignedMessage) { 531 | f.store.db.View(func(tx *bolt.Tx) error { 532 | FeedsBucket := tx.Bucket([]byte("feeds")) 533 | if FeedsBucket == nil { 534 | return nil 535 | } 536 | FeedBucket := FeedsBucket.Bucket(f.ID.DBKey()) 537 | if FeedBucket == nil { 538 | return nil 539 | } 540 | FeedLogBucket := FeedBucket.Bucket([]byte("log")) 541 | if FeedLogBucket == nil { 542 | return nil 543 | } 544 | cur := FeedLogBucket.Cursor() 545 | _, val := cur.Last() 546 | m = DecompressMessage(val) 547 | return nil 548 | }) 549 | return 550 | } 551 | 552 | func (f *Feed) LatestCount(num int, start int) (msgs []*SignedMessage) { 553 | f.store.db.View(func(tx *bolt.Tx) error { 554 | FeedsBucket := tx.Bucket([]byte("feeds")) 555 | if FeedsBucket == nil { 556 | return nil 557 | } 558 | FeedBucket := FeedsBucket.Bucket(f.ID.DBKey()) 559 | if FeedBucket == nil { 560 | return nil 561 | } 562 | FeedLogBucket := FeedBucket.Bucket([]byte("log")) 563 | if FeedLogBucket == nil { 564 | return nil 565 | } 566 | cur := FeedLogBucket.Cursor() 567 | _, val := cur.Last() 568 | for i := 0; i < start; i++ { 569 | _, val = cur.Prev() 570 | } 571 | for l1 := 0; l1 < num; l1++ { 572 | if val == nil { 573 | break 574 | } 575 | msg := DecompressMessage(val) 576 | if msg.Type() != "" { 577 | msgs = append(msgs, msg) 578 | } 579 | _, val = cur.Prev() 580 | } 581 | return nil 582 | }) 583 | return 584 | } 585 | 586 | func (f *Feed) GetSeq(tx *bolt.Tx, seq int) (m *SignedMessage) { 587 | if tx == nil { 588 | tx, _ = f.store.db.Begin(false) 589 | defer tx.Rollback() 590 | } 591 | FeedsBucket := tx.Bucket([]byte("feeds")) 592 | if FeedsBucket == nil { 593 | return nil 594 | } 595 | FeedBucket := FeedsBucket.Bucket(f.ID.DBKey()) 596 | if FeedBucket == nil { 597 | return nil 598 | } 599 | FeedLogBucket := FeedBucket.Bucket([]byte("log")) 600 | if FeedLogBucket == nil { 601 | return nil 602 | } 603 | val := FeedLogBucket.Get(itob(seq)) 604 | if val == nil { 605 | return nil 606 | } 607 | m = DecompressMessage(val) 608 | return m 609 | } 610 | 611 | var ErrLogClosed = errors.New("LogClosed") 612 | 613 | func (f *Feed) Log(seq int, live bool) chan *SignedMessage { 614 | c := make(chan *SignedMessage, 10) 615 | go func() { 616 | liveChan := make(chan *SignedMessage, 10) 617 | if live { 618 | f.Topic.Register(liveChan, false) 619 | } else { 620 | close(liveChan) 621 | } 622 | err := f.store.db.View(func(tx *bolt.Tx) error { 623 | FeedsBucket := tx.Bucket([]byte("feeds")) 624 | if FeedsBucket == nil { 625 | return nil 626 | } 627 | FeedBucket := FeedsBucket.Bucket(f.ID.DBKey()) 628 | if FeedBucket == nil { 629 | return nil 630 | } 631 | FeedLogBucket := FeedBucket.Bucket([]byte("log")) 632 | if FeedLogBucket == nil { 633 | return nil 634 | } 635 | err := FeedLogBucket.ForEach(func(k, v []byte) error { 636 | m := DecompressMessage(v) 637 | if m.Sequence < seq { 638 | return nil 639 | } 640 | seq = m.Sequence 641 | select { 642 | case c <- m: 643 | case <-time.After(1000 * time.Millisecond): 644 | close(c) 645 | return ErrLogClosed 646 | } 647 | return nil 648 | }) 649 | if err != nil { 650 | return err 651 | } 652 | return nil 653 | }) 654 | if err != nil { 655 | return 656 | } 657 | for m := range liveChan { 658 | if m.Sequence < seq { 659 | continue 660 | } 661 | seq = m.Sequence 662 | c <- m 663 | } 664 | close(c) 665 | }() 666 | return c 667 | } 668 | -------------------------------------------------------------------------------- /follower.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | func (f *Feed) Follow(seq int, live bool, handler func(m *SignedMessage) error, done chan struct{}) error { 4 | for { 5 | f.SeqLock.Lock() 6 | if f.LatestSeq >= seq { 7 | f.SeqLock.Unlock() 8 | m := f.GetSeq(nil, seq) 9 | if m != nil { 10 | err := handler(m) 11 | if err != nil { 12 | return err 13 | } 14 | } 15 | seq++ 16 | } else { 17 | if !live { 18 | f.SeqLock.Unlock() 19 | return nil 20 | } 21 | c := f.Topic.Register(nil, false) 22 | f.SeqLock.Unlock() 23 | for { 24 | select { 25 | case m := <-c: 26 | err := handler(m) 27 | if err != nil { 28 | return nil 29 | } 30 | case <-done: 31 | f.Topic.Unregister(c) 32 | return nil 33 | } 34 | } 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /git/git.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | 6 | "github.com/andyleap/go-ssb" 7 | "github.com/andyleap/go-ssb/blobs" 8 | ) 9 | 10 | type Repo struct { 11 | ds *ssb.DataStore 12 | Ref ssb.Ref 13 | } 14 | 15 | type RepoRoot struct { 16 | ssb.MessageBody 17 | Name string `json:"name"` 18 | } 19 | 20 | /* 21 | { 22 | type: 'git-update', 23 | repo: MsgId, 24 | repoBranch: [ MsgId ]?, 25 | refsBranch: [ MsgId ]?, 26 | refs: { : String|null }?, 27 | packs: [ BlobLink ]?, 28 | indexes: [ BlobLink ]?, 29 | head: string?, 30 | commits: [ { 31 | sha1: String, 32 | title: String, 33 | body: String?, 34 | parents: [ String ]?, 35 | } ]?, 36 | commits_more: Number?, 37 | num_objects: Number?, 38 | object_ids: [ String ]?, 39 | } 40 | */ 41 | type Commit struct { 42 | Sha1 string `json:"sha1"` 43 | Title string `json:"title"` 44 | Body string `json:"body,omitempty"` 45 | Parents []string `json:"parents,omitempty"` 46 | } 47 | 48 | type RepoUpdate struct { 49 | ssb.MessageBody 50 | Repo ssb.Ref `json:"repo"` 51 | RepoBranch []ssb.Ref `json:"repoBranch,omitempty"` 52 | RefsBranch []ssb.Ref `json:"refsBranch,omitempty"` 53 | Refs map[ssb.Ref]string `json:"refs,omitempty"` 54 | Packs []blobs.BlobLink `json:"packs,omitempty"` 55 | Indexes []blobs.BlobLink `json:"indexes,omitempty"` 56 | Head string `json:"Head,omitempty"` 57 | Commits []Commit `json:"commits,omitempty"` 58 | CommitsMore int `json:"commits_more,omitempty"` 59 | NumObjects int `json:"num_objects,omitempty"` 60 | ObjectIDs []string `json:"object_ids,omitempty"` 61 | } 62 | 63 | type RepoIssue struct { 64 | ssb.MessageBody 65 | Project ssb.Ref `json:"project"` 66 | Text string `json:"text"` 67 | } 68 | 69 | func init() { 70 | ssb.RebuildClearHooks["git"] = func(tx *bolt.Tx) error { 71 | tx.DeleteBucket([]byte("repos")) 72 | return nil 73 | } 74 | ssb.AddMessageHooks["git"] = func(m *ssb.SignedMessage, tx *bolt.Tx) error { 75 | _, mb := m.DecodeMessage() 76 | if _, ok := mb.(*RepoRoot); ok { 77 | ReposBucket, err := tx.CreateBucketIfNotExists([]byte("repos")) 78 | if err != nil { 79 | return err 80 | } 81 | repoBucket, err := ReposBucket.CreateBucketIfNotExists(m.Key().DBKey()) 82 | if err != nil { 83 | return err 84 | } 85 | err = repoBucket.Put([]byte("info"), m.Compress()) 86 | if err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | if update, ok := mb.(*RepoUpdate); ok { 92 | ReposBucket, err := tx.CreateBucketIfNotExists([]byte("repos")) 93 | if err != nil { 94 | return err 95 | } 96 | repoBucket, err := ReposBucket.CreateBucketIfNotExists(update.Repo.DBKey()) 97 | if err != nil { 98 | return err 99 | } 100 | updateBucket, err := repoBucket.CreateBucketIfNotExists([]byte("updates")) 101 | if err != nil { 102 | return err 103 | } 104 | err = updateBucket.Put(m.Key().DBKey(), []byte{}) 105 | if err != nil { 106 | return err 107 | } 108 | blobBucket, err := repoBucket.CreateBucketIfNotExists([]byte("blobs")) 109 | if err != nil { 110 | return err 111 | } 112 | for _, pack := range update.Packs { 113 | err = blobBucket.Put(pack.Link.DBKey(), []byte{}) 114 | if err != nil { 115 | return err 116 | } 117 | } 118 | for _, index := range update.Indexes { 119 | err = blobBucket.Put(index.Link.DBKey(), []byte{}) 120 | if err != nil { 121 | return err 122 | } 123 | } 124 | } 125 | if issue, ok := mb.(*RepoIssue); ok { 126 | ReposBucket, err := tx.CreateBucketIfNotExists([]byte("repos")) 127 | if err != nil { 128 | return err 129 | } 130 | repoBucket, err := ReposBucket.CreateBucketIfNotExists(issue.Project.DBKey()) 131 | if err != nil { 132 | return err 133 | } 134 | issueBucket, err := repoBucket.CreateBucketIfNotExists([]byte("issues")) 135 | if err != nil { 136 | return err 137 | } 138 | err = issueBucket.Put(m.Key().DBKey(), []byte{}) 139 | if err != nil { 140 | return err 141 | } 142 | } 143 | 144 | return nil 145 | } 146 | ssb.MessageTypes["git-repo"] = func(mb ssb.MessageBody) interface{} { 147 | return &RepoRoot{MessageBody: mb} 148 | } 149 | ssb.MessageTypes["git-update"] = func(mb ssb.MessageBody) interface{} { 150 | return &RepoUpdate{MessageBody: mb} 151 | } 152 | ssb.MessageTypes["issue"] = func(mb ssb.MessageBody) interface{} { 153 | return &RepoIssue{MessageBody: mb} 154 | } 155 | } 156 | 157 | func Get(ds *ssb.DataStore, r ssb.Ref) *Repo { 158 | msg := ds.Get(nil, r) 159 | if msg == nil || msg.Type() != "git-repo" { 160 | return nil 161 | } 162 | return &Repo{ 163 | ds: ds, 164 | Ref: r, 165 | } 166 | } 167 | 168 | func (repo *Repo) WantAll() { 169 | repo.ds.DB().View(func(tx *bolt.Tx) error { 170 | 171 | ReposBucket := tx.Bucket([]byte("repos")) 172 | if ReposBucket == nil { 173 | return nil 174 | } 175 | repoBucket := ReposBucket.Bucket(repo.Ref.DBKey()) 176 | if repoBucket == nil { 177 | return nil 178 | } 179 | blobBucket := repoBucket.Bucket([]byte("blobs")) 180 | if blobBucket == nil { 181 | return nil 182 | } 183 | blobBucket.ForEach(func(k, v []byte) error { 184 | r := ssb.DBRef(k) 185 | blobs.Get(repo.ds).Want(r) 186 | 187 | return nil 188 | }) 189 | return nil 190 | }) 191 | } 192 | 193 | func (repo *Repo) ListBlobs() (b []ssb.Ref) { 194 | repo.ds.DB().View(func(tx *bolt.Tx) error { 195 | 196 | ReposBucket := tx.Bucket([]byte("repos")) 197 | if ReposBucket == nil { 198 | return nil 199 | } 200 | repoBucket := ReposBucket.Bucket(repo.Ref.DBKey()) 201 | if repoBucket == nil { 202 | return nil 203 | } 204 | blobBucket := repoBucket.Bucket([]byte("blobs")) 205 | if blobBucket == nil { 206 | return nil 207 | } 208 | blobBucket.ForEach(func(k, v []byte) error { 209 | r := ssb.DBRef(k) 210 | b = append(b, r) 211 | return nil 212 | }) 213 | return nil 214 | }) 215 | return 216 | } 217 | 218 | func (repo *Repo) ListUpdates() (b []ssb.Ref) { 219 | repo.ds.DB().View(func(tx *bolt.Tx) error { 220 | 221 | ReposBucket := tx.Bucket([]byte("repos")) 222 | if ReposBucket == nil { 223 | return nil 224 | } 225 | repoBucket := ReposBucket.Bucket(repo.Ref.DBKey()) 226 | if repoBucket == nil { 227 | return nil 228 | } 229 | blobBucket := repoBucket.Bucket([]byte("updates")) 230 | if blobBucket == nil { 231 | return nil 232 | } 233 | blobBucket.ForEach(func(k, v []byte) error { 234 | r := ssb.DBRef(k) 235 | b = append(b, r) 236 | return nil 237 | }) 238 | return nil 239 | }) 240 | return 241 | } 242 | 243 | func (repo *Repo) Issues() (issues []*ssb.SignedMessage) { 244 | repo.ds.DB().View(func(tx *bolt.Tx) error { 245 | 246 | ReposBucket := tx.Bucket([]byte("repos")) 247 | if ReposBucket == nil { 248 | return nil 249 | } 250 | repoBucket := ReposBucket.Bucket(repo.Ref.DBKey()) 251 | if repoBucket == nil { 252 | return nil 253 | } 254 | blobBucket := repoBucket.Bucket([]byte("issues")) 255 | if blobBucket == nil { 256 | return nil 257 | } 258 | blobBucket.ForEach(func(k, v []byte) error { 259 | r := ssb.DBRef(k) 260 | issues = append(issues, repo.ds.Get(tx, r)) 261 | return nil 262 | }) 263 | return nil 264 | }) 265 | return 266 | } 267 | -------------------------------------------------------------------------------- /go-ssb.go: -------------------------------------------------------------------------------- 1 | // go-ssb project go-ssb.go 2 | package ssb 3 | -------------------------------------------------------------------------------- /gossip/gossip.go: -------------------------------------------------------------------------------- 1 | package gossip 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "time" 10 | 11 | "github.com/boltdb/bolt" 12 | 13 | "github.com/andyleap/go-ssb" 14 | "github.com/andyleap/go-ssb/graph" 15 | "github.com/andyleap/go-ssb/muxrpcManager" 16 | "github.com/andyleap/muxrpc" 17 | "github.com/andyleap/muxrpc/codec" 18 | 19 | "cryptoscope.co/go/secretstream" 20 | "cryptoscope.co/go/secretstream/secrethandshake" 21 | ) 22 | 23 | type Pub struct { 24 | Link ssb.Ref `json:"key"` 25 | Host string `json:"host"` 26 | Port int `json:"port"` 27 | } 28 | 29 | type PubAnnounce struct { 30 | ssb.MessageBody 31 | Pub Pub `json:"address"` 32 | } 33 | 34 | func AddPub(ds *ssb.DataStore, pb Pub) { 35 | ds.DB().Update(func(tx *bolt.Tx) error { 36 | PubBucket, err := tx.CreateBucketIfNotExists([]byte("pubs")) 37 | if err != nil { 38 | return err 39 | } 40 | buf, _ := json.Marshal(pb) 41 | PubBucket.Put(pb.Link.DBKey(), buf) 42 | return nil 43 | }) 44 | } 45 | 46 | func AcceptInvite(ds *ssb.DataStore, pb Pub, invite []byte) error { 47 | if len(invite) != 32 { 48 | return fmt.Errorf("Invite seed length wrong, got %d, expecting 32", len(invite)) 49 | } 50 | keypair, err := secrethandshake.GenEdKeyPair(bytes.NewReader(invite)) 51 | fmt.Println(base64.StdEncoding.EncodeToString(keypair.Public[:]), ":", base64.StdEncoding.EncodeToString(keypair.Secret[:])) 52 | if err != nil { 53 | return err 54 | } 55 | c, err := secretstream.NewClient(*keypair, sbotAppKey) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | var pubKey [32]byte 61 | rawpubKey := pb.Link.Raw() 62 | copy(pubKey[:], rawpubKey) 63 | 64 | d, err := c.NewDialer(pubKey) 65 | if err != nil { 66 | return err 67 | } 68 | conn, err := d("tcp", fmt.Sprintf("%s:%d", pb.Host, pb.Port)) 69 | if err != nil { 70 | return err 71 | } 72 | muxconn := muxrpc.New(conn, nil) 73 | go muxconn.Handle() 74 | useReq := struct { 75 | Feed ssb.Ref `json:"feed"` 76 | }{ 77 | ds.PrimaryRef, 78 | } 79 | err = muxconn.Call("invite.use", nil, useReq) 80 | if err != nil { 81 | return err 82 | } 83 | AddPub(ds, pb) 84 | return nil 85 | } 86 | 87 | var sbotAppKey []byte 88 | 89 | func init() { 90 | sbotAppKey, _ = base64.StdEncoding.DecodeString("1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=") 91 | ssb.RebuildClearHooks["gossip"] = func(tx *bolt.Tx) error { 92 | tx.DeleteBucket([]byte("pubs")) 93 | return nil 94 | } 95 | ssb.AddMessageHooks["gossip"] = func(m *ssb.SignedMessage, tx *bolt.Tx) error { 96 | _, mb := m.DecodeMessage() 97 | if mbp, ok := mb.(*PubAnnounce); ok { 98 | if mbp.Pub.Link.Type != ssb.RefFeed { 99 | return nil 100 | } 101 | PubBucket, err := tx.CreateBucketIfNotExists([]byte("pubs")) 102 | if err != nil { 103 | return err 104 | } 105 | buf, _ := json.Marshal(mbp.Pub) 106 | err = PubBucket.Put(mbp.Pub.Link.DBKey(), buf) 107 | if err != nil { 108 | return err 109 | } 110 | return nil 111 | } 112 | return nil 113 | } 114 | ssb.MessageTypes["pub"] = func(mb ssb.MessageBody) interface{} { 115 | return &PubAnnounce{MessageBody: mb} 116 | } 117 | 118 | ssb.RegisterInit(func(ds *ssb.DataStore) { 119 | handlers, ok := ds.ExtraData("muxrpcHandlers").(map[string]func(conn *muxrpc.Conn, req int32, args json.RawMessage)) 120 | if !ok { 121 | handlers = map[string]func(conn *muxrpc.Conn, req int32, args json.RawMessage){} 122 | ds.SetExtraData("muxrpcHandlers", handlers) 123 | } 124 | handlers["createHistoryStream"] = func(conn *muxrpc.Conn, req int32, rm json.RawMessage) { 125 | params := struct { 126 | Id ssb.Ref `json:"id"` 127 | Seq int `json:"seq"` 128 | Live bool `json:"live"` 129 | }{ 130 | ssb.Ref{}, 131 | 0, 132 | false, 133 | } 134 | args := []interface{}{¶ms} 135 | json.Unmarshal(rm, &args) 136 | f := ds.GetFeed(params.Id) 137 | go func() { 138 | err := f.Follow(params.Seq, params.Live, func(m *ssb.SignedMessage) error { 139 | err := conn.Send(&codec.Packet{ 140 | Req: -req, 141 | Type: codec.JSON, 142 | Body: m.Encode(), 143 | Stream: true, 144 | }) 145 | return err 146 | }, conn.Done) 147 | if err != nil { 148 | log.Println(err) 149 | return 150 | } 151 | conn.Send(&codec.Packet{ 152 | Req: -req, 153 | Type: codec.JSON, 154 | Body: []byte("true"), 155 | Stream: true, 156 | EndErr: true, 157 | }) 158 | }() 159 | } 160 | onConnects, ok := ds.ExtraData("muxrpcOnConnect").(map[string]func(conn *muxrpc.Conn)) 161 | if !ok { 162 | onConnects = map[string]func(conn *muxrpc.Conn){} 163 | ds.SetExtraData("muxrpcOnConnect", onConnects) 164 | } 165 | onConnects["replicate"] = func(conn *muxrpc.Conn) { 166 | i := 0 167 | for feed := range graph.GetFollows(ds, ds.PrimaryRef, 2) { 168 | go func(feed ssb.Ref, i int) { 169 | time.Sleep(time.Duration(i) * 1 * time.Millisecond) 170 | f := ds.GetFeed(feed) 171 | if f == nil { 172 | return 173 | } 174 | seq := 0 175 | if f.Latest() != nil { 176 | seq = f.Latest().Sequence + 1 177 | } 178 | go func() { 179 | reply := func(p *codec.Packet) { 180 | if p.Type != codec.JSON { 181 | fmt.Println(p, string(p.Body)) 182 | return 183 | } 184 | var m *ssb.SignedMessage 185 | err := json.Unmarshal(p.Body, &m) 186 | if err != nil { 187 | fmt.Println(err, p, string(p.Body)) 188 | return 189 | } 190 | f.AddMessage(m) 191 | } 192 | err := conn.Source("createHistoryStream", reply, map[string]interface{}{"id": f.ID, "seq": seq, "live": true, "keys": false}) 193 | if err != nil { 194 | log.Println(err) 195 | } 196 | }() 197 | }(feed, i) 198 | i++ 199 | } 200 | } 201 | }) 202 | 203 | } 204 | 205 | func Replicate(ds *ssb.DataStore) { 206 | go func() { 207 | 208 | sss, _ := secretstream.NewServer(*ds.PrimaryKey, sbotAppKey) 209 | l, err := sss.Listen("tcp", ":8008") 210 | if err != nil { 211 | fmt.Println(err) 212 | return 213 | } 214 | for { 215 | conn, err := l.Accept() 216 | if err != nil { 217 | fmt.Println(err) 218 | return 219 | } 220 | remPubKey := conn.RemoteAddr().(secretstream.Addr).PubKey() 221 | remRef, _ := ssb.NewRef(ssb.RefFeed, remPubKey, ssb.RefAlgoEd25519) 222 | go muxrpcManager.HandleConn(ds, remRef, conn) 223 | } 224 | }() 225 | go func() { 226 | ed := ds.ExtraData("muxrpcConns").(*muxrpcManager.ExtraData) 227 | ssc, _ := secretstream.NewClient(*ds.PrimaryKey, sbotAppKey) 228 | pubList := GetPubs(ds) 229 | t := time.NewTicker(5 * time.Second) 230 | for range t.C { 231 | fmt.Println("tick") 232 | ed.Lock.Lock() 233 | connCount := len(ed.Conns) 234 | ed.Lock.Unlock() 235 | if connCount >= 3 { 236 | continue 237 | } 238 | if len(pubList) == 0 { 239 | pubList = GetPubs(ds) 240 | } 241 | if len(pubList) == 0 { 242 | continue 243 | } 244 | pub := pubList[0] 245 | pubList = pubList[1:] 246 | 247 | ed.Lock.Lock() 248 | _, ok := ed.Conns[pub.Link] 249 | ed.Lock.Unlock() 250 | if ok { 251 | continue 252 | } 253 | 254 | var pubKey [32]byte 255 | rawpubKey := pub.Link.Raw() 256 | copy(pubKey[:], rawpubKey) 257 | 258 | d, err := ssc.NewDialer(pubKey) 259 | if err != nil { 260 | continue 261 | } 262 | go func() { 263 | log.Println("Connecting to ", pub) 264 | conn, err := d("tcp", fmt.Sprintf("%s:%d", pub.Host, pub.Port)) 265 | if err != nil { 266 | log.Println(err) 267 | return 268 | } 269 | end := time.NewTimer(5 * time.Minute) 270 | go func() { 271 | for range end.C { 272 | conn.Close() 273 | } 274 | }() 275 | muxrpcManager.HandleConn(ds, pub.Link, conn) 276 | end.Stop() 277 | }() 278 | 279 | } 280 | }() 281 | } 282 | 283 | func GetPubs(ds *ssb.DataStore) (pds []*Pub) { 284 | ds.DB().View(func(tx *bolt.Tx) error { 285 | PubBucket := tx.Bucket([]byte("pubs")) 286 | if PubBucket == nil { 287 | return nil 288 | } 289 | PubBucket.ForEach(func(k, v []byte) error { 290 | var pd *Pub 291 | json.Unmarshal(v, &pd) 292 | pds = append(pds, pd) 293 | return nil 294 | }) 295 | return nil 296 | }) 297 | return 298 | } 299 | 300 | /*func HandleConn(ds *ssb.DataStore, muxConn *muxrpc.Client) { 301 | muxConn.HandleSource("createHistoryStream", func(rm json.RawMessage) chan interface{} { 302 | params := struct { 303 | Id ssb.Ref `json:"id"` 304 | Seq int `json:"seq"` 305 | Live bool `json:"live"` 306 | }{ 307 | ssb.Ref{}, 308 | 0, 309 | false, 310 | } 311 | args := []interface{}{¶ms} 312 | json.Unmarshal(rm, &args) 313 | f := ds.GetFeed(params.Id) 314 | if f.ID == ds.PrimaryRef { 315 | fmt.Println(params) 316 | fmt.Println(string(rm)) 317 | } 318 | c := make(chan interface{}) 319 | go func() { 320 | for m := range f.Log(params.Seq, params.Live) { 321 | fmt.Println("Sending", m.Author, m.Sequence) 322 | c <- m 323 | } 324 | close(c) 325 | }() 326 | return c 327 | }) 328 | 329 | go func() { 330 | i := 0 331 | for feed := range graph.GetFollows(ds, ds.PrimaryRef, 2) { 332 | go func(feed ssb.Ref, i int) { 333 | time.Sleep(time.Duration(i) * 50 * time.Millisecond) 334 | reply := make(chan *ssb.SignedMessage) 335 | f := ds.GetFeed(feed) 336 | if f == nil { 337 | return 338 | } 339 | seq := 0 340 | if f.Latest() != nil { 341 | seq = f.Latest().Sequence + 1 342 | } 343 | go func() { 344 | muxConn.Source("createHistoryStream", reply, map[string]interface{}{"id": f.ID, "seq": seq, "live": true, "keys": false}) 345 | close(reply) 346 | }() 347 | for m := range reply { 348 | if m.Sequence == 0 { 349 | continue 350 | } 351 | fmt.Print("*") 352 | f.AddMessage(m) 353 | } 354 | }(feed, i) 355 | i++ 356 | } 357 | }() 358 | muxConn.Handle() 359 | } 360 | */ 361 | -------------------------------------------------------------------------------- /graph/graph.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/andyleap/go-ssb" 7 | "github.com/boltdb/bolt" 8 | ) 9 | 10 | type Relation struct { 11 | Following bool 12 | Blocking bool 13 | } 14 | 15 | type Contact struct { 16 | ssb.MessageBody 17 | Contact ssb.Ref `json:"contact"` 18 | Following *bool `json:"following,omitempty"` 19 | Blocking *bool `json:"blocking,omitempty"` 20 | } 21 | 22 | func init() { 23 | ssb.RebuildClearHooks["graph"] = func(tx *bolt.Tx) error { 24 | tx.DeleteBucket([]byte("graph")) 25 | return nil 26 | } 27 | ssb.AddMessageHooks["graph"] = handleGraph 28 | ssb.MessageTypes["contact"] = func(mb ssb.MessageBody) interface{} { return &Contact{MessageBody: mb} } 29 | } 30 | 31 | func handleGraph(m *ssb.SignedMessage, tx *bolt.Tx) error { 32 | _, mb := m.DecodeMessage() 33 | if mbc, ok := mb.(*Contact); ok { 34 | GraphBucket, err := tx.CreateBucketIfNotExists([]byte("graph")) 35 | if err != nil { 36 | return err 37 | } 38 | if mbc.Contact.Type != ssb.RefFeed { 39 | return nil 40 | } 41 | FeedBucket, err := GraphBucket.CreateBucketIfNotExists(m.Author.DBKey()) 42 | var r Relation 43 | json.Unmarshal(FeedBucket.Get(mbc.Contact.DBKey()), &r) 44 | if err != nil { 45 | return err 46 | } 47 | if mbc.Following != nil { 48 | r.Following = *mbc.Following 49 | } 50 | if mbc.Blocking != nil { 51 | r.Blocking = *mbc.Blocking 52 | } 53 | buf, _ := json.Marshal(r) 54 | err = FeedBucket.Put(mbc.Contact.DBKey(), buf) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func GetFollows(ds *ssb.DataStore, feed ssb.Ref, depth int) (follows map[ssb.Ref]int) { 63 | follows = map[ssb.Ref]int{} 64 | follows[feed] = 0 65 | ds.DB().View(func(tx *bolt.Tx) error { 66 | GraphBucket := tx.Bucket([]byte("graph")) 67 | if GraphBucket == nil { 68 | return nil 69 | } 70 | for l1 := 0; l1 < depth; l1++ { 71 | for k, v := range follows { 72 | if v == l1 { 73 | FeedBucket := GraphBucket.Bucket(k.DBKey()) 74 | if FeedBucket == nil { 75 | continue 76 | } 77 | FeedBucket.ForEach(func(k, v []byte) error { 78 | if len(k) == 0 { 79 | return nil 80 | } 81 | if _, ok := follows[ssb.DBRef(k)]; !ok { 82 | var r Relation 83 | json.Unmarshal(v, &r) 84 | if r.Following { 85 | follows[ssb.DBRef(k)] = l1 + 1 86 | } 87 | } 88 | return nil 89 | }) 90 | } 91 | } 92 | } 93 | return nil 94 | }) 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "golang.org/x/crypto/ed25519" 7 | ) 8 | 9 | type Signer interface { 10 | Sign([]byte) Signature 11 | } 12 | 13 | type SignerEd25519 struct { 14 | Private ed25519.PrivateKey 15 | } 16 | 17 | func (k SignerEd25519) Sign(content []byte) Signature { 18 | return Signature(base64.StdEncoding.EncodeToString(ed25519.Sign(k.Private, content)) + ".sig.ed25519") 19 | } 20 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "crypto/sha256" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "strings" 11 | 12 | "github.com/boltdb/bolt" 13 | ) 14 | 15 | type SignedMessage struct { 16 | Message 17 | Signature Signature `json:"signature"` 18 | } 19 | 20 | type Message struct { 21 | Previous *Ref `json:"previous"` 22 | Author Ref `json:"author"` 23 | Sequence int `json:"sequence"` 24 | Timestamp float64 `json:"timestamp"` 25 | Hash string `json:"hash"` 26 | Content json.RawMessage `json:"content"` 27 | } 28 | 29 | func Encode(i interface{}) ([]byte, error) { 30 | var buf bytes.Buffer 31 | enc := json.NewEncoder(&buf) 32 | enc.SetEscapeHTML(false) 33 | enc.SetIndent("", " ") 34 | err := enc.Encode(i) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return bytes.Trim(buf.Bytes(), "\n"), nil 39 | } 40 | 41 | func (m *SignedMessage) Verify(tx *bolt.Tx, f *Feed) error { 42 | buf, err := Encode(m.Message) 43 | if err != nil { 44 | return err 45 | } 46 | err = m.Signature.Verify(buf, m.Author) 47 | if err != nil { 48 | return err 49 | } 50 | if m.Sequence == 1 { 51 | return nil 52 | } 53 | latest := f.GetSeq(tx, m.Sequence-1) 54 | if latest == nil && m.Previous != nil { 55 | fmt.Println(string(m.Encode())) 56 | return fmt.Errorf("Expected message") 57 | } 58 | if m.Previous == nil && latest == nil { 59 | return nil 60 | } 61 | if m.Previous == nil && latest != nil { 62 | fmt.Println(string(m.Encode())) 63 | return fmt.Errorf("Error: expected previous %s but found %s", latest.Key(), "") 64 | } 65 | if latest != nil && m.Sequence == latest.Sequence { 66 | return fmt.Errorf("Error: Repeated message") 67 | } 68 | if *m.Previous != latest.Key() { 69 | /*buf, _ := Encode(latest) 70 | buf2 := ToJSBinary(buf) 71 | 72 | buf3, _ := Encode(m) 73 | fmt.Printf("\n%q\n%q\n%q\n", string(buf), string(buf2), string(buf3))*/ 74 | return fmt.Errorf("Error: expected previous %s but found %s", latest.Key(), *m.Previous) 75 | } 76 | if m.Sequence != latest.Sequence+1 || m.Timestamp <= latest.Timestamp { 77 | return fmt.Errorf("Error: out of order") 78 | } 79 | return nil 80 | } 81 | 82 | func (m *SignedMessage) Encode() []byte { 83 | buf, _ := Encode(m) 84 | return buf 85 | } 86 | 87 | func (m *SignedMessage) Compress() []byte { 88 | buf := m.Encode() 89 | cbuf := bytes.Buffer{} 90 | cbuf.WriteByte(2) 91 | cwrite, _ := flate.NewWriterDict(&cbuf, 9, Compression2) 92 | cwrite.Write(buf) 93 | cwrite.Flush() 94 | cwrite.Close() 95 | return cbuf.Bytes() 96 | } 97 | 98 | func DecompressMessage(cbuf []byte) *SignedMessage { 99 | switch cbuf[0] { 100 | case 1: 101 | reader := flate.NewReaderDict(bytes.NewReader(cbuf[1:]), Compression1) 102 | buf, _ := ioutil.ReadAll(reader) 103 | reader.Close() 104 | var m *SignedMessage 105 | json.Unmarshal(buf, &m) 106 | return m 107 | case 2: 108 | reader := flate.NewReaderDict(bytes.NewReader(cbuf[1:]), Compression2) 109 | buf, _ := ioutil.ReadAll(reader) 110 | reader.Close() 111 | var m *SignedMessage 112 | json.Unmarshal(buf, &m) 113 | return m 114 | default: 115 | var m *SignedMessage 116 | json.Unmarshal(cbuf, &m) 117 | return m 118 | } 119 | 120 | } 121 | 122 | func (m *SignedMessage) Key() Ref { 123 | if m == nil { 124 | return Ref{} 125 | } 126 | buf, _ := Encode(m) 127 | /*enc := RemoveUnsupported(charmap.ISO8859_1.NewEncoder()) 128 | buf, err := enc.Bytes(buf) 129 | if err != nil { 130 | panic(err) 131 | }*/ 132 | buf = ToJSBinary(buf) 133 | switch strings.ToLower(m.Hash) { 134 | case "sha256": 135 | hash := sha256.Sum256(buf) 136 | ref, _ := NewRef(RefMessage, hash[:], RefAlgoSha256) 137 | return ref 138 | } 139 | fmt.Println(string(buf)) 140 | return Ref{} 141 | } 142 | 143 | func (m *Message) Sign(s Signer) *SignedMessage { 144 | content, _ := Encode(m) 145 | sig := s.Sign(content) 146 | return &SignedMessage{Message: *m, Signature: sig} 147 | } 148 | -------------------------------------------------------------------------------- /messagetopic.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import "sync" 4 | 5 | type MessageTopic struct { 6 | lock sync.Mutex 7 | recps map[chan *SignedMessage]bool 8 | Send chan *SignedMessage 9 | } 10 | 11 | func NewMessageTopic() *MessageTopic { 12 | mt := &MessageTopic{Send: make(chan *SignedMessage, 10), recps: map[chan *SignedMessage]bool{}} 13 | go mt.process() 14 | return mt 15 | } 16 | 17 | func (mt *MessageTopic) Close() { 18 | close(mt.Send) 19 | } 20 | 21 | func (mt *MessageTopic) process() { 22 | for m := range mt.Send { 23 | func() { 24 | mt.lock.Lock() 25 | defer mt.lock.Unlock() 26 | for recp, strict := range mt.recps { 27 | if strict { 28 | recp <- m 29 | } else { 30 | select { 31 | case recp <- m: 32 | default: 33 | delete(mt.recps, recp) 34 | close(recp) 35 | } 36 | } 37 | } 38 | }() 39 | 40 | } 41 | mt.lock.Lock() 42 | defer mt.lock.Unlock() 43 | for recp := range mt.recps { 44 | delete(mt.recps, recp) 45 | close(recp) 46 | } 47 | } 48 | 49 | func (mt *MessageTopic) Register(recp chan *SignedMessage, strict bool) chan *SignedMessage { 50 | mt.lock.Lock() 51 | defer mt.lock.Unlock() 52 | if recp == nil { 53 | recp = make(chan *SignedMessage, 1) 54 | } 55 | mt.recps[recp] = strict 56 | return recp 57 | } 58 | 59 | func (mt *MessageTopic) Unregister(recp chan *SignedMessage) { 60 | mt.lock.Lock() 61 | defer mt.lock.Unlock() 62 | 63 | if _, ok := mt.recps[recp]; ok { 64 | delete(mt.recps, recp) 65 | close(recp) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /messagetypes.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type MessageBody struct { 8 | Type string `json:"type"` 9 | Message *Message `json:"-"` 10 | } 11 | 12 | var MessageTypes = map[string]func(mb MessageBody) interface{}{} 13 | 14 | func (m *Message) DecodeMessage() (t string, mb interface{}) { 15 | Type := &MessageBody{} 16 | json.Unmarshal(m.Content, &Type) 17 | Type.Message = m 18 | if mf, ok := MessageTypes[Type.Type]; ok { 19 | mb = mf(*Type) 20 | } 21 | t = Type.Type 22 | json.Unmarshal(m.Content, &mb) 23 | return 24 | } 25 | 26 | func (m *Message) Type() string { 27 | Type := &MessageBody{} 28 | json.Unmarshal(m.Content, &Type) 29 | return Type.Type 30 | } 31 | -------------------------------------------------------------------------------- /muxrpcManager/muxrpcManager.go: -------------------------------------------------------------------------------- 1 | package muxrpcManager 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "sync" 7 | 8 | "github.com/andyleap/go-ssb" 9 | "github.com/andyleap/muxrpc" 10 | ) 11 | 12 | type ExtraData struct { 13 | Lock sync.Mutex 14 | Conns map[ssb.Ref]*muxrpc.Conn 15 | } 16 | 17 | func init() { 18 | ssb.RegisterInit(func(ds *ssb.DataStore) { 19 | ed := &ExtraData{Conns: map[ssb.Ref]*muxrpc.Conn{}} 20 | ds.SetExtraData("muxrpcConns", ed) 21 | }) 22 | } 23 | 24 | func HandleConn(ds *ssb.DataStore, ref ssb.Ref, conn io.ReadWriteCloser) { 25 | ed := ds.ExtraData("muxrpcConns").(*ExtraData) 26 | 27 | handlers := ds.ExtraData("muxrpcHandlers").(map[string]func(conn *muxrpc.Conn, req int32, args json.RawMessage)) 28 | 29 | muxConn := muxrpc.New(conn, handlers) 30 | 31 | ed.Lock.Lock() 32 | ed.Conns[ref] = muxConn 33 | ed.Lock.Unlock() 34 | 35 | onConnect, onConnectOK := ds.ExtraData("muxrpcOnConnect").(map[string]func(conn *muxrpc.Conn)) 36 | 37 | if onConnectOK { 38 | for _, oc := range onConnect { 39 | go oc(muxConn) 40 | } 41 | } 42 | 43 | muxConn.Handle() 44 | ed.Lock.Lock() 45 | delete(ed.Conns, ref) 46 | ed.Lock.Unlock() 47 | } 48 | -------------------------------------------------------------------------------- /ref.go: -------------------------------------------------------------------------------- 1 | package ssb 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "errors" 8 | "strings" 9 | 10 | "golang.org/x/crypto/ed25519" 11 | ) 12 | 13 | type Ref struct { 14 | Type RefType 15 | Data string 16 | Algo RefAlgo 17 | } 18 | 19 | type RefType int 20 | 21 | func (rt RefType) String() string { 22 | switch rt { 23 | case RefFeed: 24 | return "@" 25 | case RefMessage: 26 | return "%" 27 | case RefBlob: 28 | return "&" 29 | default: 30 | return "?" 31 | } 32 | } 33 | 34 | const ( 35 | RefInvalid RefType = iota 36 | RefFeed 37 | RefMessage 38 | RefBlob 39 | ) 40 | 41 | type RefAlgo int 42 | 43 | func (ra RefAlgo) String() string { 44 | switch ra { 45 | case RefAlgoSha256: 46 | return "sha256" 47 | case RefAlgoEd25519: 48 | return "ed25519" 49 | default: 50 | return "???" 51 | } 52 | } 53 | 54 | const ( 55 | RefAlgoInvalid RefAlgo = iota 56 | RefAlgoSha256 57 | RefAlgoEd25519 58 | ) 59 | 60 | var ( 61 | ErrInvalidRefType = errors.New("Invalid Ref Type") 62 | ErrInvalidRefAlgo = errors.New("Invalid Ref Algo") 63 | ErrInvalidSig = errors.New("Invalid Signature") 64 | ErrInvalidHash = errors.New("Invalid Hash") 65 | ) 66 | 67 | func NewRef(typ RefType, raw []byte, algo RefAlgo) (Ref, error) { 68 | return Ref{typ, string(raw), algo}, nil 69 | } 70 | 71 | func (r Ref) Raw() []byte { 72 | return []byte(r.Data) 73 | } 74 | 75 | func (r Ref) DBKey() []byte { 76 | return append([]byte{byte(r.Type), byte(r.Algo)}, []byte(r.Data)...) 77 | } 78 | 79 | func DBRef(ref []byte) Ref { 80 | return Ref{Type: RefType(ref[0]), Data: string(ref[2:]), Algo: RefAlgo(ref[1])} 81 | } 82 | 83 | func ParseRef(ref string) Ref { 84 | parts := strings.Split(strings.Trim(ref, "@%&"), ".") 85 | if len(parts) != 2 { 86 | return Ref{} 87 | } 88 | r := Ref{} 89 | switch ref[0] { 90 | case '@': 91 | r.Type = RefFeed 92 | case '%': 93 | r.Type = RefMessage 94 | case '&': 95 | r.Type = RefBlob 96 | default: 97 | return Ref{} 98 | } 99 | switch strings.ToLower(parts[1]) { 100 | case "sha256": 101 | r.Algo = RefAlgoSha256 102 | case "ed25519": 103 | r.Algo = RefAlgoEd25519 104 | default: 105 | return Ref{} 106 | } 107 | buf, _ := base64.StdEncoding.DecodeString(parts[0]) 108 | r.Data = string(buf) 109 | return r 110 | } 111 | 112 | func (r Ref) String() string { 113 | if r.Type == RefInvalid || r.Algo == RefAlgoInvalid { 114 | return "" 115 | } 116 | return r.Type.String() + base64.StdEncoding.EncodeToString([]byte(r.Data)) + "." + r.Algo.String() 117 | } 118 | 119 | func (r Ref) MarshalText() (text []byte, err error) { 120 | return []byte(r.String()), nil 121 | } 122 | 123 | func (r *Ref) UnmarshalText(text []byte) error { 124 | *r = ParseRef(string(text)) 125 | return nil 126 | } 127 | 128 | func (r Ref) IsMessage() bool { 129 | return r.Type == RefMessage 130 | } 131 | 132 | func (r Ref) CheckHash(content []byte) error { 133 | switch r.Algo { 134 | case RefAlgoSha256: 135 | contentHash := sha256.Sum256(content) 136 | if bytes.Equal(r.Raw(), contentHash[:]) { 137 | return nil 138 | } 139 | return ErrInvalidHash 140 | } 141 | return ErrInvalidHash 142 | } 143 | 144 | type Signature string 145 | 146 | type SigAlgo int 147 | 148 | const ( 149 | SigAlgoInvalid SigAlgo = iota 150 | SigAlgoEd25519 151 | ) 152 | 153 | func (s Signature) Algo() SigAlgo { 154 | parts := strings.Split(string(s), ".") 155 | if len(parts) != 3 || parts[1] != "sig" { 156 | return SigAlgoInvalid 157 | } 158 | switch strings.ToLower(parts[2]) { 159 | case "ed25519": 160 | return SigAlgoEd25519 161 | } 162 | return SigAlgoInvalid 163 | } 164 | 165 | func (s Signature) Raw() []byte { 166 | b64 := strings.Split(string(s), ".")[0] 167 | raw, err := base64.StdEncoding.DecodeString(b64) 168 | if err != nil { 169 | return nil 170 | } 171 | 172 | return raw 173 | } 174 | 175 | func (s Signature) Verify(content []byte, r Ref) error { 176 | switch s.Algo() { 177 | case SigAlgoEd25519: 178 | if r.Algo != RefAlgoEd25519 { 179 | return ErrInvalidSig 180 | } 181 | rawkey := r.Raw() 182 | if rawkey == nil { 183 | return nil 184 | } 185 | 186 | key := ed25519.PublicKey(rawkey) 187 | if ed25519.Verify(key, content, s.Raw()) { 188 | return nil 189 | } 190 | return ErrInvalidSig 191 | } 192 | return ErrInvalidSig 193 | } 194 | -------------------------------------------------------------------------------- /rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net" 8 | "reflect" 9 | 10 | "github.com/andyleap/go-ssb" 11 | ) 12 | 13 | type Request struct { 14 | Method string `json:"method"` 15 | Params json.RawMessage `json:"params"` 16 | ID interface{} `json:"id"` 17 | } 18 | 19 | type Response struct { 20 | Result interface{} `json:"result"` 21 | Error interface{} `json:"error"` 22 | ID interface{} `json:"id"` 23 | } 24 | 25 | func ServeConn(datastore *ssb.DataStore, conn io.ReadWriteCloser) { 26 | reader := json.NewDecoder(conn) 27 | writer := json.NewEncoder(conn) 28 | resp := make(chan interface{}) 29 | defer conn.Close() 30 | go func() { 31 | for v := range resp { 32 | writer.Encode(v) 33 | } 34 | }() 35 | for { 36 | var req Request 37 | err := reader.Decode(&req) 38 | if err != nil { 39 | return 40 | } 41 | RPCMethods, ok := datastore.ExtraData("RPCMethods").(map[string]interface{}) 42 | if !ok { 43 | if req.ID != nil { 44 | resp <- Response{Result: nil, Error: "No such method", ID: req.ID} 45 | } 46 | continue 47 | } 48 | method, ok := RPCMethods[req.Method] 49 | if !ok { 50 | if req.ID != nil { 51 | resp <- Response{Result: nil, Error: "No such method", ID: req.ID} 52 | } 53 | continue 54 | } 55 | go func() { 56 | defer func() { 57 | if r := recover(); r != nil { 58 | if req.ID != nil { 59 | resp <- Response{Result: nil, Error: fmt.Sprintf("Panic while running method: %s", r), ID: req.ID} 60 | } 61 | } 62 | }() 63 | rval := reflect.ValueOf(method) 64 | if rval.Kind() != reflect.Func { 65 | if req.ID != nil { 66 | resp <- Response{Result: nil, Error: "No such method", ID: req.ID} 67 | } 68 | return 69 | } 70 | params := []reflect.Value{} 71 | decodeparams := []interface{}{} 72 | rtype := rval.Type() 73 | for l1 := 0; l1 < rtype.NumIn(); l1++ { 74 | pval := reflect.New(rtype.In(l1)) 75 | params = append(params, pval.Elem()) 76 | decodeparams = append(decodeparams, pval.Interface()) 77 | } 78 | err := json.Unmarshal(req.Params, &decodeparams) 79 | if err != nil { 80 | if req.ID != nil { 81 | resp <- Response{Result: nil, Error: fmt.Sprintf("Error decoding method parameters: %s", err), ID: req.ID} 82 | } 83 | } 84 | ret := rval.Call(params) 85 | if req.ID != nil { 86 | if rtype.NumOut() == 2 { 87 | if ret[1].Interface() != nil { 88 | resp <- Response{Result: nil, Error: ret[1].Interface().(error).Error(), ID: req.ID} 89 | } else { 90 | resp <- Response{Result: ret[0], Error: nil, ID: req.ID} 91 | } 92 | } else { 93 | if rtype.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) { 94 | if ret[0].Interface() != nil { 95 | resp <- Response{Result: nil, Error: ret[0].Interface().(error).Error(), ID: req.ID} 96 | } else { 97 | resp <- Response{Result: true, Error: nil, ID: req.ID} 98 | } 99 | } else { 100 | resp <- Response{Result: ret[0], Error: nil, ID: req.ID} 101 | } 102 | } 103 | } 104 | }() 105 | } 106 | } 107 | 108 | func ListenAndServe(datastore *ssb.DataStore, n string, a string) error { 109 | l, err := net.Listen(n, a) 110 | defer l.Close() 111 | if err != nil { 112 | return err 113 | } 114 | for { 115 | c, err := l.Accept() 116 | if err != nil { 117 | return err 118 | } 119 | go func() { 120 | ServeConn(datastore, c) 121 | }() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /search/search.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/andyleap/go-ssb" 7 | "github.com/andyleap/go-ssb/social" 8 | "github.com/boltdb/bolt" 9 | ) 10 | 11 | func Search(ds *ssb.DataStore, term string, max int) (found []*ssb.SignedMessage) { 12 | ds.DB().View(func(tx *bolt.Tx) error { 13 | 14 | LogBucket := tx.Bucket([]byte("log")) 15 | if LogBucket == nil { 16 | return nil 17 | } 18 | cursor := LogBucket.Cursor() 19 | _, v := cursor.Last() 20 | for v != nil { 21 | m := ds.Get(tx, ssb.DBRef(v)) 22 | _, md := m.DecodeMessage() 23 | if post, ok := md.(*social.Post); ok { 24 | if strings.Contains(post.Text, term) { 25 | found = append(found, m) 26 | if max > 0 && len(found) >= max { 27 | return nil 28 | } 29 | } 30 | } 31 | _, v = cursor.Prev() 32 | } 33 | return nil 34 | }) 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /social/social.go: -------------------------------------------------------------------------------- 1 | package social 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/andyleap/go-ssb" 9 | "github.com/boltdb/bolt" 10 | ) 11 | 12 | func itob(v int) []byte { 13 | b := make([]byte, 8) 14 | binary.BigEndian.PutUint64(b, uint64(v)) 15 | return b 16 | } 17 | 18 | type Link struct { 19 | Link ssb.Ref `json:"link"` 20 | } 21 | 22 | type Image struct { 23 | image 24 | } 25 | 26 | type image struct { 27 | Link ssb.Ref `json:"link"` 28 | Width int `json:"width,omitempty"` 29 | Height int `json:"height,omitempty"` 30 | Name string `json:"name,omitempty"` 31 | Size int `json:"size,omitempty"` 32 | Type string `json:"type,omitempty"` 33 | } 34 | 35 | func (i *Image) UnmarshalJSON(b []byte) error { 36 | err := json.Unmarshal(b, &i.image) 37 | if err != nil { 38 | err = json.Unmarshal(b, &i.Link) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | type Post struct { 47 | ssb.MessageBody 48 | Text string `json:"text"` 49 | Channel string `json:"channel,omitempty"` 50 | Root ssb.Ref `json:"root,omitempty"` 51 | Branch ssb.Ref `json:"branch,omitempty"` 52 | Recps []Link `json:"recps,omitempty"` 53 | Mentions []Link `json:"mentions,omitempty"` 54 | } 55 | 56 | type About struct { 57 | ssb.MessageBody 58 | About ssb.Ref `json:"about"` 59 | Name string `json:"name,omitempty"` 60 | Image *Image `json:"image,omitempty"` 61 | } 62 | 63 | type Vote struct { 64 | ssb.MessageBody 65 | Vote struct { 66 | Link ssb.Ref `json:"link"` 67 | Value int `json:"value"` 68 | Reason string `json:"reason,omitempty"` 69 | } `json:"vote"` 70 | } 71 | 72 | func init() { 73 | ssb.MessageTypes["post"] = func(mb ssb.MessageBody) interface{} { return &Post{MessageBody: mb} } 74 | ssb.MessageTypes["about"] = func(mb ssb.MessageBody) interface{} { return &About{MessageBody: mb} } 75 | ssb.MessageTypes["vote"] = func(mb ssb.MessageBody) interface{} { return &Vote{MessageBody: mb} } 76 | ssb.RebuildClearHooks["social"] = func(tx *bolt.Tx) error { 77 | tx.DeleteBucket([]byte("votes")) 78 | tx.DeleteBucket([]byte("threads")) 79 | b, _ := tx.CreateBucketIfNotExists([]byte("feeds")) 80 | b.ForEach(func(k, v []byte) error { 81 | b.Bucket(k).Delete([]byte("about")) 82 | return nil 83 | }) 84 | 85 | return nil 86 | } 87 | ssb.AddMessageHooks["social"] = func(m *ssb.SignedMessage, tx *bolt.Tx) error { 88 | _, mb := m.DecodeMessage() 89 | if mba, ok := mb.(*About); ok { 90 | if mba.About == m.Author { 91 | FeedsBucket, err := tx.CreateBucketIfNotExists([]byte("feeds")) 92 | if err != nil { 93 | return err 94 | } 95 | FeedBucket, err := FeedsBucket.CreateBucketIfNotExists(m.Author.DBKey()) 96 | if err != nil { 97 | return err 98 | } 99 | aboutdata := FeedBucket.Get([]byte("about")) 100 | var a About 101 | if aboutdata != nil { 102 | json.Unmarshal(aboutdata, &a) 103 | } 104 | if mba.Name != "" { 105 | a.Name = mba.Name 106 | } 107 | if mba.Image != nil { 108 | a.Image = mba.Image 109 | } 110 | buf, err := json.Marshal(a) 111 | if err != nil { 112 | return err 113 | } 114 | err = FeedBucket.Put([]byte("about"), buf) 115 | if err != nil { 116 | return err 117 | } 118 | } 119 | } 120 | if vote, ok := mb.(*Vote); ok { 121 | VotesBucket, err := tx.CreateBucketIfNotExists([]byte("votes")) 122 | if err != nil { 123 | return err 124 | } 125 | votesRaw := VotesBucket.Get(vote.Vote.Link.DBKey()) 126 | var votes []ssb.Ref 127 | if votesRaw != nil { 128 | json.Unmarshal(votesRaw, &votes) 129 | } 130 | votes = append(votes, m.Key()) 131 | buf, _ := json.Marshal(votes) 132 | 133 | err = VotesBucket.Put(vote.Vote.Link.DBKey(), buf) 134 | if err != nil { 135 | return err 136 | } 137 | } 138 | if post, ok := mb.(*Post); ok { 139 | if post.Root.Type != ssb.RefInvalid { 140 | ThreadsBucket, err := tx.CreateBucketIfNotExists([]byte("threads")) 141 | if err != nil { 142 | return err 143 | } 144 | ThreadBucket, err := ThreadsBucket.CreateBucketIfNotExists(post.Root.DBKey()) 145 | if err != nil { 146 | return err 147 | } 148 | logBucket, err := ThreadBucket.CreateBucketIfNotExists([]byte("log")) 149 | if err != nil { 150 | return err 151 | } 152 | logBucket.FillPercent = 1 153 | seq, err := logBucket.NextSequence() 154 | if err != nil { 155 | return err 156 | } 157 | logBucket.Put(itob(int(seq)), m.Key().DBKey()) 158 | 159 | timeBucket, err := ThreadBucket.CreateBucketIfNotExists([]byte("time")) 160 | if err != nil { 161 | return err 162 | } 163 | i := int(m.Timestamp * float64(time.Millisecond)) 164 | for timeBucket.Get(itob(i)) != nil { 165 | i++ 166 | } 167 | timeBucket.Put(itob(i), m.Key().DBKey()) 168 | } 169 | } 170 | return nil 171 | } 172 | } 173 | 174 | func GetAbout(tx *bolt.Tx, ref ssb.Ref) (a *About) { 175 | FeedsBucket := tx.Bucket([]byte("feeds")) 176 | if FeedsBucket == nil { 177 | return 178 | } 179 | FeedBucket := FeedsBucket.Bucket(ref.DBKey()) 180 | if FeedBucket == nil { 181 | return 182 | } 183 | aboutdata := FeedBucket.Get([]byte("about")) 184 | if aboutdata == nil { 185 | return 186 | } 187 | json.Unmarshal(aboutdata, &a) 188 | return 189 | } 190 | 191 | func GetVotes(tx *bolt.Tx, ref ssb.Ref) []*ssb.SignedMessage { 192 | VotesBucket := tx.Bucket([]byte("votes")) 193 | if VotesBucket == nil { 194 | return nil 195 | } 196 | votesRaw := VotesBucket.Get(ref.DBKey()) 197 | var voteRefs []ssb.Ref 198 | if votesRaw != nil { 199 | json.Unmarshal(votesRaw, &voteRefs) 200 | } 201 | votes := make([]*ssb.SignedMessage, 0, len(voteRefs)) 202 | for _, r := range voteRefs { 203 | msg := ssb.GetMsg(tx, r) 204 | if msg == nil { 205 | continue 206 | } 207 | votes = append(votes, msg) 208 | } 209 | return votes 210 | } 211 | 212 | func GetThread(tx *bolt.Tx, ref ssb.Ref) []*ssb.SignedMessage { 213 | ThreadsBucket := tx.Bucket([]byte("threads")) 214 | if ThreadsBucket == nil { 215 | return nil 216 | } 217 | ThreadBucket := ThreadsBucket.Bucket(ref.DBKey()) 218 | if ThreadBucket == nil { 219 | return nil 220 | } 221 | timeBucket := ThreadBucket.Bucket([]byte("time")) 222 | if timeBucket == nil { 223 | return nil 224 | } 225 | thread := []*ssb.SignedMessage{} 226 | timeBucket.ForEach(func(k, v []byte) error { 227 | msg := ssb.GetMsg(tx, ssb.DBRef(v)) 228 | if msg != nil { 229 | thread = append(thread, msg) 230 | } 231 | return nil 232 | }) 233 | return thread 234 | } 235 | --------------------------------------------------------------------------------